« get me outta code hell

http-music - Command-line music player + utils (not a server!)
about summary refs log tree commit diff
path: root/src/loop-play.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/loop-play.js')
-rw-r--r--src/loop-play.js100
1 files changed, 97 insertions, 3 deletions
diff --git a/src/loop-play.js b/src/loop-play.js
index 10b1d5f..aca518a 100644
--- a/src/loop-play.js
+++ b/src/loop-play.js
@@ -3,24 +3,111 @@
 const { spawn } = require('child_process')
 const FIFO = require('fifo-js')
 const EventEmitter = require('events')
+const { getDownloaderFor } = require('./downloaders')
+
+class DownloadController extends EventEmitter {
+  waitForDownload() {
+    // Returns a promise that resolves when a download is
+    // completed.  Note that this isn't necessarily the download
+    // that was initiated immediately before a call to
+    // waitForDownload (if any), since that download may have
+    // been canceled (see cancel).  You can also listen for the
+    // 'downloaded' event instead.
+
+    return new Promise(resolve => {
+      this.once('downloaded', file => resolve(file))
+    })
+  }
+
+  async download(downloader, arg) {
+    // Downloads a file.  This doesn't return anything; use
+    // waitForDownload to get the result of this.
+    // (The reasoning is that it's possible for a download to
+    // be canceled and replaced with a new download (see cancel)
+    // which would void the result of the old download.)
+
+    let canceled = false
+    this.once('skipped', () => {
+      canceled = true
+    })
+
+    const file = await downloader(arg)
+
+    if (!canceled) {
+      this.emit('downloaded', file)
+    }
+  }
+
+  cancel() {
+    // Cancels the current download.  This doesn't cancel any
+    // waitForDownload promises, though -- you'll need to start
+    // a new download to resolve those.
+
+    this.emit('skipped')
+  }
+}
 
 class PlayController {
-  constructor(picker) {
+  constructor(picker, downloadController) {
     this.currentTrack = null
     this.playArgs = []
     this.process = null
     this.picker = picker
+    this.downloadController = downloadController
   }
 
   async loopPlay() {
+    let next
+
+    let downloadNext = () => {
+      if (this.startNextDownload() !== null) {
+        return this.downloadController.waitForDownload().then(_next => {
+          next = _next
+        })
+      } else {
+        next = null
+        return Promise.resolve()
+      }
+    }
+
+    await downloadNext()
+
+    while (next) {
+      await Promise.all([
+        this.playFile(next),
+        downloadNext()
+      ])
+    }
+  }
+
+  startNextDownload() {
+    // TODO: Is there a method for this?
+    // TODO: Handle/test null return from picker.
+    const arg = this.picker()[1]
+
+    if (arg === null) {
+      return null
+    } else {
+      const downloader = getDownloaderFor(arg)
+      this.downloadController.download(downloader, arg)
+    }
+  }
+
+  async old_loopPlay() {
     // Playing music in a loop isn't particularly complicated; essentially, we
     // just want to keep picking and playing tracks until none is picked.
 
     let nextTrack = await this.picker()
 
+    await this.downloadManager.download(getDownloaderFor(nextTrack), nextTrack)
+
+    let downloadNext
+
     while (nextTrack) {
       this.currentTrack = nextTrack
 
+      this.downloadManager.download(getDownloaderFor(nextTrack), nextTrack)
+
       await this.playFile(nextTrack[1])
 
       nextTrack = await this.picker()
@@ -35,7 +122,7 @@ class PlayController {
       file
     ])
 
-    this.process.stderr.on('data', data => {
+    const handleData = data => {
       const match = data.toString().match(
         /(..):(..):(..) \/ (..):(..):(..) \(([0-9]+)%\)/
       )
@@ -70,6 +157,12 @@ class PlayController {
           `\x1b[K~ (${percentStr}%) ${curStr} / ${lenStr}\r`
         )
       }
+    }
+
+    this.process.stderr.on('data', handleData)
+
+    this.process.once('exit', () => {
+      this.process.stderr.removeListener('data', handleData)
     })
 
     return new Promise(resolve => {
@@ -136,7 +229,8 @@ module.exports = function loopPlay(picker, playArgs = []) {
   // function is null (or similar). Optionally takes a second argument
   // used as arguments to the `play` process (before the file name).
 
-  const playController = new PlayController(picker)
+  const downloadController = new DownloadController()
+  const playController = new PlayController(picker, downloadController)
   playController.playArgs = playArgs
 
   const promise = playController.loopPlay()