« get me outta code hell

http-music - Command-line music player + utils (not a server!)
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--man/http-music.110
-rwxr-xr-xsrc/http-music.js23
-rw-r--r--src/loop-play.js47
-rw-r--r--todo.txt11
4 files changed, 82 insertions, 9 deletions
diff --git a/man/http-music.1 b/man/http-music.1
index 625b174..5fc93fb 100644
--- a/man/http-music.1
+++ b/man/http-music.1
@@ -22,6 +22,16 @@ 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.
+.TP
+.BR q
+Quits the http-music process and stops music currently being played. (\fB^C\fR and \fB^D\fR also work.)
+
+
+
 .SH OPTIONS
 .TP
 .BR \-c ", " \-\-clear
diff --git a/src/http-music.js b/src/http-music.js
index fee1b79..3d7e217 100755
--- a/src/http-music.js
+++ b/src/http-music.js
@@ -239,7 +239,28 @@ setupDefaultPlaylist('./playlist.json')
         return
       }
 
-      return loopPlay(picker, downloader, playOpts)
+      const play = loopPlay(picker, downloader, playOpts)
+
+      // We're looking to gather standard input one keystroke at a time.
+      process.stdin.setRawMode(true)
+
+      process.stdin.on('data', data => {
+        if (Buffer.from('s').equals(data)) {
+          play.skip()
+        }
+
+        if (
+          Buffer.from('q').equals(data) ||
+          Buffer.from([0x03]).equals(data) || // ^C
+          Buffer.from([0x04]).equals(data) // ^D
+        ) {
+          play.kill()
+          process.stdout.write('\n')
+          process.exit(0)
+        }
+      })
+
+      return play.promise
     } else {
       return activePlaylist
     }
diff --git a/src/loop-play.js b/src/loop-play.js
index 5205025..ff77940 100644
--- a/src/loop-play.js
+++ b/src/loop-play.js
@@ -12,7 +12,7 @@ const sanitize = require('sanitize-filename')
 
 const writeFile = promisify(fs.writeFile)
 
-module.exports = async function loopPlay(picker, downloader, playArgs = []) {
+module.exports = function loopPlay(picker, downloader, playArgs = []) {
   // Looping play function. Takes one argument, the "pick" function,
   // which returns a track to play. Preemptively downloads the next
   // track while the current one is playing for seamless continuation
@@ -20,6 +20,8 @@ module.exports = async function loopPlay(picker, downloader, playArgs = []) {
   // function is null (or similar). Optionally takes a second argument
   // used as arguments to the `play` process (before the file name).
 
+  let playProcess, convertProcess
+
   async function downloadNext() {
     const picked = picker()
 
@@ -36,7 +38,9 @@ module.exports = async function loopPlay(picker, downloader, playArgs = []) {
     const wavFile = tempDir + `/.${sanitize(title)}.wav`
 
     try {
-      await convert(downloadFile, wavFile)
+      const convertPromise = convert(downloadFile, wavFile)
+      convertProcess = convertPromise.process
+      await convertPromise
     } catch(err) {
       console.warn("Failed to convert " + title)
       console.warn("Selecting a new track\n")
@@ -47,12 +51,39 @@ module.exports = async function loopPlay(picker, downloader, playArgs = []) {
     return wavFile
   }
 
-  let wavFile = await downloadNext()
+  async function main() {
+    let wavFile = await downloadNext()
+
+    while (wavFile) {
+      const nextPromise = downloadNext()
+
+      // What a mouthful!
+      const playPromise = playFile(wavFile, playArgs)
+      playProcess = playPromise.process
 
-  while (wavFile) {
-    const nextPromise = downloadNext()
-    await playFile(wavFile, playArgs)
-    wavFile = await nextPromise
+      try {
+        await playPromise
+      } catch(err) {
+        console.warn(err)
+      }
+
+      wavFile = await nextPromise
+    }
+  }
+
+  const promise = main()
+
+  return {
+    promise,
+
+    skip: function() {
+      if (playProcess) playProcess.kill()
+    },
+
+    kill: function() {
+      if (playProcess) playProcess.kill()
+      if (convertProcess) convertProcess.kill()
+    }
   }
 }
 
@@ -63,5 +94,5 @@ function convert(fromFile, toFile) {
 
 function playFile(file, opts = []) {
   const play = spawn('play', [...opts, file])
-  return promisifyProcess(play)
+  return Object.assign(promisifyProcess(play), {process: play})
 }
diff --git a/todo.txt b/todo.txt
index 02df97a..adf4456 100644
--- a/todo.txt
+++ b/todo.txt
@@ -96,3 +96,14 @@ TODO: Make a --help/-h/-? option that directs helpless users to the man page.
 TODO: Make a way to write the current playlist to a file. I think just renaming
       the debug-playlist-log option could work, since you could pipe that to a
       file through your shell.
+
+TODO: Figure out a less "hacky" way to kill the process. Ideally we shouldn't
+      have to handle ^C and ^D ourselves; for instance right now ^Z is actually
+      broken, since we aren't using the shell's normal way of handling any
+      keyboard controls such as those!
+
+TODO: A way to kill the up-next song.
+
+TODO: Separate the code in loop-play.js to be a bit nicer.
+
+TODO: Cleaning up http-music.js would be nice as well!