index.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. module.exports = function (io) {
  2. var app = require('express')();
  3. // Body parsing
  4. var bodyParser = require('body-parser');
  5. app.use(bodyParser.urlencoded({extended: true}));
  6. app.use(bodyParser.json());
  7. // Validation
  8. var expressValidator = require('express-validator');
  9. app.use(expressValidator());
  10. // Child process, for spawning youtube-dl
  11. var child_process = require('child_process');
  12. // The location of the youtube-dl executable
  13. var YOUTUBE_DL_LOC = '/usr/bin/youtube-dl'
  14. // Keep track of music to play, playing, and played
  15. var playlist = [];
  16. var playing = 'None';
  17. var played = [];
  18. var Player = require('player');
  19. // Create a player with the proper error handling.
  20. function playerCreator(song) {
  21. if (song)
  22. return new Player(song)
  23. .on('error', function(err) {playerHandleError(playlist, played)})
  24. return new Player()
  25. .on('error', function(err) {playerHandleError(playlist, played)})
  26. }
  27. player = playerCreator();
  28. playerState = 'playing';
  29. io.on('connection', function(socket){
  30. io.emit(playerState);
  31. socket.on('chat message', function(msg){
  32. io.emit('chat message', msg);
  33. });
  34. // TODO handle button when nothing is playing.
  35. socket.on('pause', function() {
  36. console.log('pausing');
  37. io.emit('player-transition', 'Pausing');
  38. player.pause();
  39. playerState = 'paused';
  40. // Player is a high quality plugin, and breaks if you spam
  41. // pause(). So put a 3 second delay in between
  42. // transitions.
  43. // TODO Find a better way to handle this.
  44. setTimeout(function() {
  45. io.emit('paused');
  46. }, 3000);
  47. });
  48. socket.on('play', function() {
  49. console.log('playing');
  50. io.emit('player-transition', 'Playing');
  51. player.pause();
  52. playerState = 'playing';
  53. setTimeout(function() {
  54. io.emit('playing');
  55. }, 3000);
  56. });
  57. });
  58. // Player functionality
  59. // Songs move from playlist (if nothing is playing presently), to playing, to played.
  60. // TODO check if song already exists in played.
  61. // event: on error (No next song was found).
  62. function playerHandleError(playlist, played) {
  63. // TODO Workaround for a bug in player: wait a couple seconds so the
  64. // songs don't overlap.
  65. // Move the recently playing song to played.
  66. played.push(playing);
  67. if (playlist.length != 0) {
  68. // Play the next song.
  69. player = playerCreator(playlist[0]);
  70. player.play();
  71. // Remove the added song.
  72. playing = playlist[0];
  73. playlist.shift();
  74. } else {
  75. // Reset the object, resetting its playlist.
  76. player = playerCreator();
  77. playing = 'None';
  78. }
  79. }
  80. // Add a song and start the playlist, if it's empty.
  81. function playerAddSong(song) {
  82. if (player.list.length == 0) {
  83. // If this is the player's first song, start it.
  84. player = playerCreator(song);
  85. player.play();
  86. playing = song;
  87. } else {
  88. // Otherwise add it to the queue
  89. playlist.push(song);
  90. }
  91. }
  92. /* GET home page. */
  93. app.get('/', function(req, res, next) {
  94. var playlist_view;
  95. if(playlist.length) {
  96. playlist_view = '<p>Playlist:</p><ul>';
  97. for(var i=0; i < playlist.length; i++) {
  98. playlist_view +=
  99. '<li>' +
  100. playlist[i].replace(/^\.\/downloads\//, '') .replace(/\.mp3$/, '') +
  101. '</li>';
  102. }
  103. playlist_view += '</ul>';
  104. } else {
  105. playlist_view = '<p>No songs in playlist.</p>';
  106. }
  107. var playing_view = '<p>Now playing: ' + playing.replace(/^\.\/downloads\//, '') .replace(/\.mp3$/, '') + '</p>';
  108. var played_view;
  109. if(played.length) {
  110. played_view = '<p>Played:</p><ul>';
  111. for(var i=0; i < played.length; i++) {
  112. played_view +=
  113. '<li>' +
  114. played[i].replace(/^\.\/downloads\//, '') .replace(/\.mp3$/, '') +
  115. '</li>';
  116. }
  117. played_view += '</ul>';
  118. } else {
  119. played_view = '<p>No played songs.</p>';
  120. }
  121. res.render('index', { playlist_view: playlist_view,
  122. playing_view: playing_view,
  123. played_view: played_view });
  124. });
  125. app.get('/youtube', function(req, res, next) {
  126. res.render('youtube');
  127. });
  128. app.use(expressValidator({
  129. customValidators: {
  130. dlSuccess: function(video) {
  131. // Test if the requested video is available.
  132. // TODO make this non-blocking so music doesn't stop
  133. var youtube_dl = child_process.spawnSync(YOUTUBE_DL_LOC, ['-s', video]);
  134. // DEBUG: for when I forget to change the var
  135. if(youtube_dl.stderr == null) {
  136. console.log('Your youtube-dl location is probably invalid.');
  137. }
  138. if (youtube_dl.stderr.toString()) {
  139. return false;
  140. }
  141. return true;
  142. }
  143. }
  144. }
  145. ));
  146. app.post('/youtube', function(req, res) {
  147. var video = req.body.video;
  148. req.checkBody('video', 'URL is not valid.').dlSuccess();
  149. var errors = req.validationErrors();
  150. if(errors){
  151. res.render('youtube', {
  152. errors:errors
  153. });
  154. } else {
  155. var youtube_dl = child_process.spawn(YOUTUBE_DL_LOC, ['-x', '--audio-format', 'mp3', '-o', 'downloads/%(title)s.%(ext)s', video]);
  156. youtube_dl.on('close', (code) => {
  157. console.log('Done getting ' + video);
  158. var error;
  159. youtube_dl.stderr.on('data', (data) => {
  160. error = data;
  161. console.log(`Error getting video: ${data}`);
  162. // TODO give error to user when they return to /
  163. });
  164. if(!error) {
  165. var youtube_dl_get_title = child_process.spawnSync(YOUTUBE_DL_LOC, ['--get-title', video]);
  166. console.log(youtube_dl_get_title.stdout.toString());
  167. playerAddSong('./downloads/' + youtube_dl_get_title.stdout.toString()
  168. .replace('\n', '')
  169. .replace(new RegExp('"', 'g'), '\'')
  170. + '.mp3');
  171. }
  172. res.redirect('/');
  173. });
  174. }
  175. });
  176. app.get('/file', function(req, res, next) {
  177. res.render('file');
  178. });
  179. // Return the router.
  180. return app;
  181. // module.exports = app;
  182. };