diff options
Diffstat (limited to 'src/loop-play.js')
-rw-r--r-- | src/loop-play.js | 100 |
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() |