index.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. // Express and http
  2. var express = require('express');
  3. var app = express();
  4. var http = require('http').Server(app);
  5. // view engine setup
  6. var path = require('path');
  7. app.set('views', path.join(__dirname, 'views'));
  8. app.set('view engine', 'hbs');
  9. // logging
  10. var logger = require('morgan');
  11. app.use(logger('dev'));
  12. // Body parsing and validating
  13. var bodyParser = require('body-parser');
  14. app.use(bodyParser.urlencoded({extended: true}));
  15. app.use(bodyParser.json());
  16. var expressValidator = require('express-validator');
  17. app.use(expressValidator());
  18. // Sockets
  19. var io = require('socket.io').listen(http);
  20. // Unused (for now) cookies and favicons
  21. //var favicon = require('serve-favicon');
  22. //var cookieParser = require('cookie-parser');
  23. // uncomment after placing your favicon in /public
  24. //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
  25. //app.use(cookieParser());
  26. app.use(express.static(path.join(__dirname, 'public')));
  27. // Child process, for spawning youtube-dl
  28. var child_process = require('child_process');
  29. // The location of the youtube-dl executable
  30. var YOUTUBE_DL_LOC = '/usr/bin/youtube-dl'
  31. // Keep track of music to play, playing, and played
  32. var playlist = [];
  33. var playing = 'None';
  34. var played = [];
  35. // Player module
  36. var Player = require('player');
  37. // Create a player with the proper error handling.
  38. function playerCreator(song) {
  39. if (song)
  40. return new Player(song)
  41. .on('error', function(err) {playerHandleError(playlist, played)})
  42. return new Player()
  43. .on('error', function(err) {playerHandleError(playlist, played)})
  44. }
  45. player = playerCreator();
  46. // Player functionality
  47. // Songs move from playlist (if nothing is playing presently), to playing, to played.
  48. // TODO check if song already exists in played.
  49. // event: on error (No next song was found).
  50. function playerHandleError(playlist, played) {
  51. // TODO Workaround for a bug in player: wait a couple seconds so the
  52. // songs don't overlap.
  53. // Move the recently playing song to played.
  54. played.push(playing);
  55. if (playlist.length != 0) {
  56. // Play the next song.
  57. player = playerCreator(playlist[0]);
  58. player.play();
  59. // Remove the added song.
  60. playing = playlist[0];
  61. playlist.shift();
  62. } else {
  63. // Reset the object, resetting its playlist.
  64. player = playerCreator();
  65. playing = 'None';
  66. }
  67. }
  68. // Add a song and start the playlist, if it's empty.
  69. function playerAddSong(song) {
  70. if (player.list.length == 0) {
  71. // If this is the player's first song, start it.
  72. player = playerCreator(song);
  73. player.play();
  74. playing = song;
  75. } else {
  76. // Otherwise add it to the queue
  77. playlist.push(song);
  78. }
  79. }
  80. /* GET home page. */
  81. app.get('/', function(req, res, next) {
  82. var playlist_view;
  83. if(playlist.length) {
  84. playlist_view = '<p>Playlist:</p><ul>';
  85. for(var i=0; i < playlist.length; i++) {
  86. playlist_view +=
  87. '<li>' +
  88. playlist[i].replace(/^\.\/downloads\//, '') .replace(/\.mp3$/, '') +
  89. '</li>';
  90. }
  91. playlist_view += '</ul>';
  92. } else {
  93. playlist_view = '<p>No songs in playlist.</p>';
  94. }
  95. var playing_view = '<p>Now playing: ' + playing.replace(/^\.\/downloads\//, '') .replace(/\.mp3$/, '') + '</p>';
  96. var played_view;
  97. if(played.length) {
  98. played_view = '<p>Played:</p><ul>';
  99. for(var i=0; i < played.length; i++) {
  100. played_view +=
  101. '<li>' +
  102. played[i].replace(/^\.\/downloads\//, '') .replace(/\.mp3$/, '') +
  103. '</li>';
  104. }
  105. played_view += '</ul>';
  106. } else {
  107. played_view = '<p>No played songs.</p>';
  108. }
  109. res.render('index', { playlist_view: playlist_view,
  110. playing_view: playing_view,
  111. played_view: played_view });
  112. });
  113. app.get('/youtube', function(req, res, next) {
  114. res.render('youtube');
  115. });
  116. app.use(expressValidator({
  117. customValidators: {
  118. dlSuccess: function(video) {
  119. // Test if the requested video is available.
  120. // TODO make this non-blocking so music doesn't stop
  121. var youtube_dl = child_process.spawnSync(YOUTUBE_DL_LOC, ['-s', video]);
  122. // DEBUG: for when I forget to change the var
  123. if(youtube_dl.stderr == null) {
  124. console.log("Your youtube-dl location is probably invalid.");
  125. }
  126. if (youtube_dl.stderr.toString()) {
  127. return false;
  128. }
  129. return true;
  130. }
  131. }
  132. }
  133. ));
  134. app.post('/youtube', function(req, res) {
  135. var video = req.body.video;
  136. req.checkBody('video', 'URL is not valid.').dlSuccess();
  137. var errors = req.validationErrors();
  138. if(errors){
  139. res.render('youtube', {
  140. errors:errors
  141. });
  142. } else {
  143. var youtube_dl = child_process.spawn(YOUTUBE_DL_LOC, ['-x', '--audio-format', 'mp3', '-o', 'downloads/%(title)s.%(ext)s', video]);
  144. youtube_dl.on('close', (code) => {
  145. console.log("Done getting " + video);
  146. var error;
  147. youtube_dl.stderr.on('data', (data) => {
  148. error = data;
  149. console.log(`Error getting video: ${data}`);
  150. // TODO give error to user when they return to /
  151. });
  152. if(!error) {
  153. var youtube_dl_get_title = child_process.spawnSync(YOUTUBE_DL_LOC, ['--get-title', video]);
  154. console.log(youtube_dl_get_title.stdout.toString());
  155. playerAddSong('./downloads/' + youtube_dl_get_title.stdout.toString()
  156. .replace('\n', '')
  157. .replace(new RegExp('"', 'g'), '\'')
  158. + ".mp3");
  159. }
  160. res.redirect('/');
  161. });
  162. }
  163. });
  164. app.get('/file', function(req, res, next) {
  165. res.render('file');
  166. });
  167. // error handlers
  168. // catch 404 and forward to error handler
  169. app.use(function(req, res, next) {
  170. var err = new Error('Not Found');
  171. err.status = 404;
  172. next(err);
  173. });
  174. // error handlers
  175. // development error handler
  176. // will print stacktrace
  177. if (app.get('env') === 'development') {
  178. app.use(function(err, req, res, next) {
  179. res.status(err.status || 500);
  180. res.render('error', {
  181. message: err.message,
  182. error: err
  183. });
  184. });
  185. }
  186. // production error handler
  187. // no stacktraces leaked to user
  188. app.use(function(err, req, res, next) {
  189. res.status(err.status || 500);
  190. res.render('error', {
  191. message: err.message,
  192. error: {}
  193. });
  194. });
  195. io.on('connection', function(socket){
  196. console.log('a user connected');
  197. });
  198. http.listen(3000, function(){
  199. console.log('listening on *:3000');
  200. });