stations.js 7.7 KB

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