« 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:
-rwxr-xr-xsrc/play.js161
1 files changed, 117 insertions, 44 deletions
diff --git a/src/play.js b/src/play.js
index db83f13..558a561 100755
--- a/src/play.js
+++ b/src/play.js
@@ -362,49 +362,49 @@ async function main(args) {
       console.warn("If you're piping into http-music, this is normal.")
     }
 
-    process.stdin.on('data', data => {
-      const escModifier = Buffer.from('\x1b[')
-      const shiftModifier = Buffer.from('1;2')
-
-      const esc = num => Buffer.concat([escModifier, Buffer.from([num])])
-
-      const shiftEsc = num => (
-        Buffer.concat([escModifier, shiftModifier, Buffer.from([num])])
-      )
-
-      const equalsChar = char => (
-        Buffer.from(char.toLowerCase()).equals(data) ||
-        Buffer.from(char.toUpperCase()).equals(data)
-      )
+    const commands = {
+      'doNothing': function() {},
 
-      if (Buffer.from([0x20]).equals(data)) {
+      // TODO: Separate pause and unpause commands
+      'toggle_pause': function() {
         player.togglePause()
-      }
+      },
 
-      if (esc(0x43).equals(data)) {
-        player.seekAhead(5)
-      }
+      'quit': function() {
+        playController.stop().then(() => {
+          process.exit(0)
+        })
+      },
 
-      if (esc(0x44).equals(data)) {
-        player.seekBack(5)
-      }
+      'seek': function(seconds) {
+        // TODO: Does it even make sense to have these two methods be
+        // separate?
+        if (seconds < 0) {
+          player.seekBack(-seconds)
+        } else {
+          player.seekAhead(seconds)
+        }
+      },
 
-      if (shiftEsc(0x43).equals(data)) {
-        player.seekAhead(30)
-      }
+      'changeVolume': function(diff) {
+        // TODO: Why have these be separate?
+        if (diff < 0) {
+          player.volDown(-diff)
+        } else {
+          player.volUp(diff)
+        }
+      },
 
-      if (shiftEsc(0x44).equals(data)) {
-        player.seekBack(30)
-      }
+      // TODO: Skip back/ahead multiple tracks at once
 
-      if (esc(0x41).equals(data) || equalsChar('p')) { // Previous
+      'skipBack': function() {
         clearConsoleLine()
         console.log("Skipping backwards. (Press I for track info!")
 
         playController.skipBack()
-      }
+      },
 
-      if (esc(0x42).equals(data) || equalsChar('s')) { // Skip
+      'skipAhead': function() {
         clearConsoleLine()
         console.log(
           "Skipping the track that's currently playing. " +
@@ -412,17 +412,13 @@ async function main(args) {
         )
 
         playController.skip()
-      }
-
-      if (shiftEsc(0x41).equals(data)) {
-        player.volUp(10)
-      }
+      },
 
-      if (shiftEsc(0x42).equals(data)) {
-        player.volDown(10)
-      }
+      'skip': function() {
+        commands.skipAhead()
+      },
 
-      if (Buffer.from([0x7f]).equals(data)) {
+      'skipUpNext': function() {
         clearConsoleLine()
         console.log("Skipping the track that's up next.")
 
@@ -432,22 +428,99 @@ async function main(args) {
             " (Press I for track info!)"
           )
         })
-      }
+      },
 
-      if (equalsChar('i') || equalsChar('t')) {
+      // TODO: Number of history/up-next tracks to show.
+      'showTrackInfo': function() {
         clearConsoleLine()
         playController.logTrackInfo()
       }
+    }
+
+    const splitChars = str => str.split('').map(char => char.charCodeAt(0))
+
+    const simpleKeybindings = {
+      space: [0x20],
+      esc: [0x1b], escape: [0x1b],
+      up: [0x1b, ...splitChars('[A')],
+      down: [0x1b, ...splitChars('[B')],
+      right: [0x1b, ...splitChars('[C')],
+      left: [0x1b, ...splitChars('[D')],
+      shiftUp: [0x1b, ...splitChars('[1;2A')],
+      shiftDown: [0x1b, ...splitChars('[1;2B')],
+      shiftRight: [0x1b, ...splitChars('[1;2C')],
+      shiftLeft: [0x1b, ...splitChars('[1;2D')],
+      delete: [0x7f]
+    }
+
+    // TODO: Load these from a file
+    // TODO: Verify that each command exists
+    const commandBindings = {
+      bindings: [
+        [['space'], 'togglePause'],
+        [['left'], 'seek', -5],
+        [['right'], 'seek', +5],
+        [['shiftLeft'], 'seek', -30],
+        [['shiftRight'], 'seek', +30],
+        [['up'], 'skipBack'],
+        [['down'], 'skipAhead'],
+        [['s'], 'skipAhead'],
+        [['delete'], 'skipUpNext'],
+        [['i'], 'showTrackInfo'],
+        [['t'], 'showTrackInfo'],
+        [['q'], 'quit']
+      ]
+    }
+
+    process.stdin.on('data', data => {
+      const escModifier = Buffer.from('\x1b[')
+      const shiftModifier = Buffer.from('1;2')
+
+      const esc = num => Buffer.concat([escModifier, Buffer.from([num])])
+
+      const shiftEsc = num => (
+        Buffer.concat([escModifier, shiftModifier, Buffer.from([num])])
+      )
+
+      const equalsChar = char => (
+        Buffer.from(char.toLowerCase()).equals(data) ||
+        Buffer.from(char.toUpperCase()).equals(data)
+      )
 
       if (
-        equalsChar('q') ||
-        Buffer.from('q').equals(data) ||
         Buffer.from([0x03]).equals(data) || // ^C
         Buffer.from([0x04]).equals(data) // ^D
       ) {
         playController.stop().then(() => {
           process.exit(0)
         })
+
+        return
+      }
+
+      for (let [ keyBinding, command, ...args ] of commandBindings.bindings) {
+        let run = true
+
+        // TODO: "Compile" keybindings upon loading them
+        const buffer = Buffer.from(keyBinding.map(item => {
+          if (typeof item === 'number') {
+            return [item]
+          } else if (Object.keys(simpleKeybindings).includes(item)) {
+            return simpleKeybindings[item]
+          } else if (typeof item === 'string' && item.length === 1) {
+            return [item.charCodeAt(0)]
+          } else {
+            // Error
+            console.warn('Invalid keybinding part?', item, 'in', keyBinding)
+            return [0xFF]
+          }
+        }).reduce((a, b) => a.concat(b), []))
+
+        run = buffer.equals(data)
+
+        if (run && Object.keys(commands).includes(command)) {
+          commands[command](...args)
+        }
       }
     })