stations.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. 'use strict';
  2. const async = require('async');
  3. const request = require('request');
  4. const io = require('../io');
  5. const db = require('../db');
  6. const cache = require('../cache');
  7. const notifications = require('../notifications');
  8. const utils = require('../utils');
  9. /**
  10. * Loads a station into the cache, and sets up all the related logic
  11. *
  12. * @param {String} stationId - the id of the station
  13. * @param {Function} cb - gets called when this function completes
  14. */
  15. function initializeAndReturnStation (stationId, cb) {
  16. async.waterfall([
  17. // first check the cache for the station
  18. (next) => cache.hget('stations', stationId, next),
  19. // if the cached version exist
  20. (station, next) => {
  21. if (station) return next(true, station);
  22. db.models.station.find({ id: stationId }, next);
  23. },
  24. // if the station exists in the DB, add it to the cache
  25. (station, next) => {
  26. if (!station) return cb('Station by that id does not exist');
  27. station = cache.schemas.station(station);
  28. cache.hset('stations', station.id, station, (err) => next(err, station));
  29. }
  30. ], (err, station) => {
  31. if (err && err !== true) return cb(err);
  32. // get notified when the next song for this station should play, so that we can notify our sockets
  33. let notification = notifications.subscribe(`stations.nextSong?id=${station.id}`, () => {
  34. // get the station from the cache
  35. cache.hget('stations', station.id, (err, station) => {
  36. if (station) {
  37. // notify all the sockets on this station to go to the next song
  38. async.waterfall(io.sockets.clients().map((socket) => (next) => {
  39. // fetch the sockets session
  40. cache.hget('sessions', socket.sessionId, (err, session) => {
  41. if (session.stationId == station.id) {
  42. socket.emit('notification:stations.nextSong');
  43. }
  44. next();
  45. });
  46. }), (err) => {
  47. // schedule a notification to be dispatched when the next song ends
  48. notifications.schedule(`stations.nextSong?id=${station.id}`, 5000);
  49. });
  50. }
  51. // the station doesn't exist anymore, unsubscribe from it
  52. else {
  53. notifications.remove(notification);
  54. }
  55. });
  56. }, true);
  57. cb(null, station);
  58. });
  59. }
  60. module.exports = {
  61. /**
  62. * Get a list of all the stations
  63. *
  64. * @param session
  65. * @param cb
  66. * @return {{ status: String, stations: Array }}
  67. */
  68. index: (session, cb) => {
  69. // TODO: the logic should be a bit more personalized to the users preferred genres
  70. // and it should probably just a different cache table then 'stations'
  71. cache.hgetall('stations', (err, stations) => {
  72. if (err && err !== true) {
  73. return cb({
  74. status: 'error',
  75. message: 'An error occurred while obtaining the stations'
  76. });
  77. }
  78. let arr = [];
  79. for (let prop in stations) {
  80. console.log(prop);
  81. arr.push(stations[prop]);
  82. }
  83. cb({ status: 'success', stations: arr });
  84. });
  85. },
  86. /**
  87. * Joins the station by its id
  88. *
  89. * @param session
  90. * @param stationId - the station id
  91. * @param cb
  92. * @return {{ status: String, userCount: Integer }}
  93. */
  94. join: (session, stationId, cb) => {
  95. initializeAndReturnStation(stationId, (err, station) => {
  96. if (err && err !== true) {
  97. return cb({ status: 'error', message: 'An error occurred while joining the station' });
  98. }
  99. if (station) {
  100. if (session) session.stationId = stationId;
  101. cache.client.hincrby('station.userCounts', stationId, 1, (err, userCount) => {
  102. if (err) return cb({ status: 'error', message: 'An error occurred while joining the station' });
  103. cb({ status: 'success', userCount });
  104. });
  105. }
  106. else {
  107. cb({ status: 'failure', message: `That station doesn't exist` });
  108. }
  109. });
  110. },
  111. /**
  112. * Skips the users current station
  113. *
  114. * @param session
  115. * @param stationId - the station id
  116. * @param cb
  117. * @return {{ status: String, skipCount: Integer }}
  118. */
  119. skip: (session, stationId, cb) => {
  120. if (!session) return cb({ status: 'failure', message: 'You must be logged in to skip a song!' });
  121. initializeAndReturnStation(stationId, (err, station) => {
  122. if (err && err !== true) {
  123. return cb({ status: 'error', message: 'An error occurred while skipping the station' });
  124. }
  125. if (station) {
  126. cache.client.hincrby('station.skipCounts', session.stationId, 1, (err, skipCount) => {
  127. session.skippedSong = true;
  128. if (err) return cb({ status: 'error', message: 'An error occurred while skipping the station' });
  129. cache.hget('station.userCounts', session.stationId, (err, userCount) => {
  130. cb({ status: 'success', skipCount });
  131. });
  132. });
  133. }
  134. else {
  135. cb({ status: 'failure', message: `That station doesn't exist` });
  136. }
  137. });
  138. },
  139. /**
  140. * Leaves the users current station
  141. *
  142. * @param session
  143. * @param cb
  144. * @return {{ status: String, userCount: Integer }}
  145. */
  146. leave: (session, cb) => {
  147. let stationId = "edm";
  148. initializeAndReturnStation(stationId, (err, station) => {
  149. if (err && err !== true) {
  150. return cb({ status: 'error', message: 'An error occurred while leaving the station' });
  151. }
  152. if (session) session.stationId = null;
  153. else if (station) {
  154. cache.client.hincrby('station.userCounts', stationId, -1, (err, userCount) => {
  155. if (err) return cb({ status: 'error', message: 'An error occurred while leaving the station' });
  156. cb({ status: 'success', userCount });
  157. });
  158. } else {
  159. cb({ status: 'failure', message: `That station doesn't exist, it may have been deleted` });
  160. }
  161. });
  162. },
  163. addSong: (session, station, song, cb) => {
  164. // if (!session.logged_in) return cb({ status: 'failure', message: 'You must be logged in to add a song' });
  165. const params = [
  166. 'part=snippet,contentDetails,statistics,status',
  167. `id=${encodeURIComponent(song.id)}`,
  168. `key=${config.get('apis.youtube.key')}`
  169. ].join('&');
  170. request(`https://www.googleapis.com/youtube/v3/videos?${params}`, (err, res, body) => {
  171. if (err) {
  172. console.error(err);
  173. return cb({ status: 'error', message: 'Failed to find song from youtube' });
  174. }
  175. const newSong = new db.models.song({
  176. id: json.items[0].id,
  177. title: json.items[0].snippet.title,
  178. duration: utils.convertTime(json.items[0].contentDetails.duration),
  179. thumbnail: json.items[0].snippet.thumbnails.high.url
  180. });
  181. // save the song to the database
  182. newSong.save(err => {
  183. if (err) {
  184. console.error(err);
  185. return cb({ status: 'error', message: 'Failed to save song from youtube to the database' });
  186. }
  187. stations.getStation(station).playlist.push(newSong);
  188. cb({ status: 'success', data: stations.getStation(station.playlist) });
  189. });
  190. });
  191. }
  192. };