From 296a7e71a62fafc297a2666c7ecaf4eca7b459a4 Mon Sep 17 00:00:00 2001 From: liam4 Date: Sun, 11 Jun 2017 20:31:24 -0300 Subject: Log track info, and more --- man/http-music.1 | 16 +++++++++-- src/downloaders.js | 3 +- src/http-music.js | 17 +++++++++++ src/loop-play.js | 75 +++++++++++++++++++++++++++++++++++++----------- src/promisify-process.js | 7 ++--- todo.txt | 2 ++ 6 files changed, 95 insertions(+), 25 deletions(-) diff --git a/man/http-music.1 b/man/http-music.1 index 5fc93fb..3bda2f5 100644 --- a/man/http-music.1 +++ b/man/http-music.1 @@ -24,12 +24,22 @@ It can be used anywhere with a Node environment, requiring only two commonly ins .SH KEYBOARD CONTROLS .TP -.BR s -Skips the currently playing track. +.BR +Assigns a new track to be played next, overwriting the current up-next track. + +.TP +.BR i +Shows information (title, URL/path) on the currently playing track. +(\fBt\fR also works.) + .TP .BR q -Quits the http-music process and stops music currently being played. (\fB^C\fR and \fB^D\fR also work.) +Quits the http-music process and stops music currently being played. +(\fB^C\fR and \fB^D\fR also work.) +.TP +.BR s +Skips the currently playing track. .SH OPTIONS diff --git a/src/downloaders.js b/src/downloaders.js index 5f65346..28dab92 100644 --- a/src/downloaders.js +++ b/src/downloaders.js @@ -24,13 +24,14 @@ function makeYouTubeDownloader() { const tempDir = tempy.directory() const opts = [ + '--quiet', '--extract-audio', '--audio-format', 'wav', '--output', tempDir + '/dl.%(ext)s', arg ] - return promisifyProcess(spawn('youtube-dl', opts), false) + return promisifyProcess(spawn('youtube-dl', opts)) .then(() => tempDir + '/dl.wav') } } diff --git a/src/http-music.js b/src/http-music.js index f6c1a52..1392c34 100755 --- a/src/http-music.js +++ b/src/http-music.js @@ -246,10 +246,20 @@ setupDefaultPlaylist('./playlist.json') process.stdin.on('data', data => { if (Buffer.from('s').equals(data)) { + // console.log( + // "Skipping the track that's currently playing. " + + // "(Press I for track info!)" + // ) + play.skipCurrent() } if (Buffer.from([0x7f]).equals(data)) { // Delete + console.log( + "Skipping the track that's up next. " + + "(Press I for track info!)" + ) + play.skipUpNext() } @@ -262,6 +272,13 @@ setupDefaultPlaylist('./playlist.json') process.stdout.write('\n') process.exit(0) } + + if ( + Buffer.from('i').equals(data) || + Buffer.from('t').equals(data) + ) { + play.logTrackInfo() + } }) return play.promise diff --git a/src/loop-play.js b/src/loop-play.js index b49d9e4..cf6d829 100644 --- a/src/loop-play.js +++ b/src/loop-play.js @@ -5,8 +5,13 @@ const promisifyProcess = require('./promisify-process') const sanitize = require('sanitize-filename') const tempy = require('tempy') -class DownloadController { +const EventEmitter = require('events') + +class DownloadController extends EventEmitter { constructor(picker, downloader) { + super() + + this.pickedTrack = null this.process = null this.requestingSkipUpNext = false this.isDownloading = false @@ -31,6 +36,11 @@ class DownloadController { return false } + // Having the picked song being available is handy, for UI stuff (i.e. for + // being displayed to the user through the console). + this.pickedTrack = picked + this.emit('trackPicked', picked) + // The picked result is an array containing the title of the track (only // really used to display to the user) and an argument to be passed to the // downloader. The downloader argument doesn't have to be anything in @@ -38,13 +48,6 @@ class DownloadController { // It's up to the downloader to decide what to do with it. const [ title, downloaderArg ] = picked - console.log(`Downloading ${title}..\nDownloader arg: ${downloaderArg}`) - - // The "from" file is downloaded by the downloader (given in the - // DownloadController constructor) using the downloader argument we just - // got. - const from = await this.downloader(downloaderArg) - // The "to" file is simply a WAV file. We give this WAV file a specific // name - the title of the track we got earlier, sanitized to be file-safe // - so that when `play` outputs the name of the song, it's obvious to the @@ -52,6 +55,14 @@ class DownloadController { const tempDir = tempy.directory() const to = tempDir + `/.${sanitize(title)}.wav` + // We'll use this wav file later, to actually play the track. + this.wavFile = to + + // The "from" file is downloaded by the downloader (given in the + // DownloadController constructor) using the downloader argument we just + // got. + const from = await this.downloader(downloaderArg) + // Now that we've got the `to` and `from` file names, we can actually do // the convertion. We don't want any output from `avconv` at all, since the // output of `play` will usually be displayed while `avconv` is running, @@ -60,10 +71,9 @@ class DownloadController { '-loglevel', 'quiet', '-i', from, to ]) - // It's handy to store the output WAV file (the "to" file) and the `avconv` - // process; the WAV file is used later to be played, and the convert - // process is stored so it can be killed before it finishes. - this.wavFile = to + // We store the convert process so that we can kill it before it finishes, + // if that's most convenient (e.g. if skipping the current song or quitting + // the entire program). this.process = convertProcess // Now it's only a matter of time before the process is finished. @@ -72,9 +82,6 @@ class DownloadController { try { await promisifyProcess(convertProcess) } catch(err) { - console.warn("Failed to convert " + title) - console.warn("Selecting a new track\n") - // There's a chance we'll fail, though. That could happen if the passed // "from" file doesn't actually contain audio data. In that case, we // have to attempt this whole process over again, so that we get a @@ -82,6 +89,15 @@ class DownloadController { // file; if that's the case, and the convert process is failing on it, // we could end up in an infinite loop. That would be bad, since there // isn't any guarding against a situation like that here.) + + // Usually we'll log a warning message saying that the convertion failed, + // but if we're requesting a skip-up-next, it's expected for the avconv + // process to fail; so in that case we don't bother warning the user. + if (!this.requestingSkipUpNext) { + console.warn("Failed to convert " + title) + console.warn("Selecting a new track") + } + return await this.downloadNext() } @@ -121,10 +137,17 @@ class DownloadController { class PlayController { constructor(downloadController) { + this.currentTrack = null + this.upNextTrack = null this.playArgs = [] this.process = null this.downloadController = downloadController + + this.downloadController.on('trackPicked', track => { + console.log('Changed:', track[0]) + this.upNextTrack = track + }) } async loopPlay() { @@ -134,17 +157,19 @@ class PlayController { await this.downloadController.downloadNext() while (this.downloadController.wavFile) { - const nextPromise = this.downloadController.downloadNext() + this.currentTrack = this.downloadController.pickedTrack const file = this.downloadController.wavFile const playProcess = spawn('play', [...this.playArgs, file]) const playPromise = promisifyProcess(playProcess) this.process = playProcess + const nextPromise = this.downloadController.downloadNext() + try { await playPromise } catch(err) { - console.warn(err) + console.warn(err + '\n') } await nextPromise @@ -187,6 +212,22 @@ module.exports = function loopPlay(picker, downloader, playArgs = []) { kill: function() { playController.killProcess() downloadController.killProcess() + }, + + logTrackInfo: function() { + if (playController.currentTrack) { + const [ curTitle, curArg ] = playController.currentTrack + console.log(`Playing: \x1b[1m${curTitle} \x1b[2m${curArg}\x1b[0m`) + } else { + console.log("No song currently playing.") + } + + if (playController.upNextTrack) { + const [ nextTitle, nextArg ] = playController.upNextTrack + console.log(`Up next: \x1b[1m${nextTitle} \x1b[2m${nextArg}\x1b[0m`) + } else { + console.log("No song up next.") + } } } } diff --git a/src/promisify-process.js b/src/promisify-process.js index d1c09d7..d330055 100644 --- a/src/promisify-process.js +++ b/src/promisify-process.js @@ -3,9 +3,9 @@ const { Writable } = require('stream') module.exports = function promisifyProcess(proc) { - // Takes a process (from the child_process module) and returns a promise that - // resolves when the process exits (or rejects with a warning, if the exit - // code is non-zero). + // Takes a process (from the child_process module) and returns a promise + // that resolves when the process exits (or rejects, if the exit code is + // non-zero). return new Promise((resolve, reject) => { proc.stdout.pipe(process.stdout) @@ -15,7 +15,6 @@ module.exports = function promisifyProcess(proc) { if (code === 0) { resolve() } else { - console.error("Process failed!", proc.spawnargs) reject(code) } }) diff --git a/todo.txt b/todo.txt index 5cfac31..8ef5e44 100644 --- a/todo.txt +++ b/todo.txt @@ -112,3 +112,5 @@ TODO: A way to kill the up-next song. TODO: A way to see information about the currently playing song, as well as the up-next song. + +TODO: A way to see the previously played songs, and to skip back (or forwards). -- cgit 1.3.0-6-gf8a5