diff options
author | Florrie <towerofnix@gmail.com> | 2019-09-20 16:54:13 -0300 |
---|---|---|
committer | Florrie <towerofnix@gmail.com> | 2019-09-20 16:54:13 -0300 |
commit | 6bad90e8e0db9c9273de984be53a1ca61b4d8a24 (patch) | |
tree | 45e5f2a74a4fe9f6d939590a5d547be939071596 /backend.js | |
parent | a5d3b710eb46e58708b8dbb51f5231ba534561fd (diff) |
WIP - support multiple players at once
Currently bug-free and doesn't change anything about existing mtui behavior! Meta N to create a new player, meta up/down to switch between which one you're interacting with. Each player has its own queue. Eventually (soon(TM)) there'll be much better UI to go with all this!
Diffstat (limited to 'backend.js')
-rw-r--r-- | backend.js | 321 |
1 files changed, 188 insertions, 133 deletions
diff --git a/backend.js b/backend.js index 57910e9..e8601c5 100644 --- a/backend.js +++ b/backend.js @@ -28,24 +28,22 @@ const fs = require('fs') const writeFile = promisify(fs.writeFile) const readFile = promisify(fs.readFile) -class Backend extends EventEmitter { - constructor() { +class QueuePlayer extends EventEmitter { + constructor({ + getRecordFor + }) { super() this.player = null this.playingTrack = null - this.recordStore = new RecordStore() - this.throttleMetadata = throttlePromise(10) - this.metadataDictionary = {} this.queueGrouplike = {name: 'Queue', isTheQueue: true, items: []} this.pauseNextTrack = false this.playedTrackToEnd = false + this.timeData = null - this.rootDirectory = process.env.HOME + '/.mtui' - this.metadataPath = this.rootDirectory + '/track-metadata.json' + this.getRecordFor = getRecordFor } - async setup() { this.player = await getPlayer() @@ -55,79 +53,16 @@ class Backend extends EventEmitter { } } - await this.loadMetadata() - this.player.on('printStatusLine', data => { if (this.playingTrack) { - this.emit('printStatusLine', data) + this.timeData = data + this.emit('received time data', data, this) } }) return true } - - async readMetadata() { - try { - return JSON.parse(await readFile(this.metadataPath)) - } catch (error) { - // Just stop. It's okay to fail to load metadata. - return null - } - } - - async loadMetadata() { - Object.assign(this.metadataDictionary, await this.readMetadata()) - } - - async saveMetadata() { - const newData = Object.assign({}, await this.readMetadata(), this.metadataDictionary) - await writeFile(this.metadataPath, JSON.stringify(newData)) - } - - getMetadataFor(item) { - const key = this.metadataDictionary[item.downloaderArg] - return this.metadataDictionary[key] || null - } - - async processMetadata(item, reprocess = false, top = true) { - let counter = 0 - - if (isGroup(item)) { - const results = await Promise.all(item.items.map(x => this.processMetadata(x, reprocess, false))) - counter += results.reduce((acc, n) => acc + n, 0) - } else process: { - if (!reprocess && this.getMetadataFor(item)) { - break process - } - - await this.throttleMetadata(async () => { - const filePath = await this.download(item) - const metadataReader = getMetadataReaderFor(filePath) - const data = await metadataReader(filePath) - - this.metadataDictionary[item.downloaderArg] = filePath - this.metadataDictionary[filePath] = data - }) - - this.emit('processMetadata progress', this.throttleMetadata.queue.length) - - counter++ - } - - if (top) { - await this.saveMetadata() - } - - return counter - } - - - getRecordFor(item) { - return this.recordStore.getRecord(item) - } - - queue(topItem, afterItem = null, {movePlayingTrack = true} = {}) { const { items } = this.queueGrouplike const newTrackIndex = items.length @@ -389,49 +324,9 @@ class Backend extends EventEmitter { this.emit('queue updated') } - seekAhead(seconds) { - this.player.seekAhead(seconds) - } - - seekBack(seconds) { - this.player.seekBack(seconds) - } - - togglePause() { - this.player.togglePause() - } - - setPause(value) { - this.player.setPause(value) - } - - toggleLoop() { - this.player.toggleLoop() - } - - setLoop(value) { - this.player.setLoop(value) - } - - volUp(amount = 10) { - this.player.volUp(amount) - } - - volDown(amount = 10) { - this.player.volDown(amount) - } - - setVolume(value) { - this.player.setVolume(value) - } - - setPauseNextTrack(value) { - this.pauseNextTrack = !!value - } - async stopPlaying() { - // We emit this so playTrack doesn't immediately start a new track. - // We aren't *actually* about to play a new track. + // We emit this so the active play() call doesn't immediately start a new + // track. We aren't *actually* about to play a new track. this.emit('playing new track') await this.player.kill() this.clearPlayingTrack() @@ -559,10 +454,185 @@ class Backend extends EventEmitter { if (this.playingTrack !== null) { const oldTrack = this.playingTrack this.playingTrack = null + this.timeData = null this.emit('playing', null, oldTrack) } } + async download(item) { + if (isGroup(item)) { + // TODO: Download all children (recursively), show a confirmation prompt + // if there are a lot of items (remember to flatten). + return + } + + // Don't start downloading an item if we're already downloading it! + if (this.getRecordFor(item).downloading) { + return + } + + const arg = item.downloaderArg + this.getRecordFor(item).downloading = true + try { + return await getDownloaderFor(arg)(arg) + } finally { + this.getRecordFor(item).downloading = false + } + } + + seekAhead(seconds) { + this.player.seekAhead(seconds) + } + + seekBack(seconds) { + this.player.seekBack(seconds) + } + + togglePause() { + this.player.togglePause() + } + + setPause(value) { + this.player.setPause(value) + } + + toggleLoop() { + this.player.toggleLoop() + } + + setLoop(value) { + this.player.setLoop(value) + } + + volUp(amount = 10) { + this.player.volUp(amount) + } + + volDown(amount = 10) { + this.player.volDown(amount) + } + + setVolume(value) { + this.player.setVolume(value) + } + + setPauseNextTrack(value) { + this.pauseNextTrack = !!value + } + + get playSymbol() { + if (this.player && this.playingTrack) { + if (this.player.isPaused) { + return '⏸' + } else { + return '▶' + } + } else { + return '.' + } + } +} + +class Backend extends EventEmitter { + constructor() { + super() + + this.queuePlayers = [] + + this.recordStore = new RecordStore() + this.throttleMetadata = throttlePromise(10) + this.metadataDictionary = {} + + this.rootDirectory = process.env.HOME + '/.mtui' + this.metadataPath = this.rootDirectory + '/track-metadata.json' + } + + async setup() { + const error = await this.addQueuePlayer() + if (error.error) { + return error + } + + await this.loadMetadata() + + return true + } + + async addQueuePlayer() { + const queuePlayer = new QueuePlayer({ + getRecordFor: item => this.getRecordFor(item) + }) + + const error = await queuePlayer.setup() + if (error.error) { + return error + } + + this.queuePlayers.push(queuePlayer) + this.emit('added queue player', queuePlayer) + + return queuePlayer + } + + async readMetadata() { + try { + return JSON.parse(await readFile(this.metadataPath)) + } catch (error) { + // Just stop. It's okay to fail to load metadata. + return null + } + } + + async loadMetadata() { + Object.assign(this.metadataDictionary, await this.readMetadata()) + } + + async saveMetadata() { + const newData = Object.assign({}, await this.readMetadata(), this.metadataDictionary) + await writeFile(this.metadataPath, JSON.stringify(newData)) + } + + getMetadataFor(item) { + const key = this.metadataDictionary[item.downloaderArg] + return this.metadataDictionary[key] || null + } + + async processMetadata(item, reprocess = false, top = true) { + let counter = 0 + + if (isGroup(item)) { + const results = await Promise.all(item.items.map(x => this.processMetadata(x, reprocess, false))) + counter += results.reduce((acc, n) => acc + n, 0) + } else process: { + if (!reprocess && this.getMetadataFor(item)) { + break process + } + + await this.throttleMetadata(async () => { + const filePath = await this.download(item) + const metadataReader = getMetadataReaderFor(filePath) + const data = await metadataReader(filePath) + + this.metadataDictionary[item.downloaderArg] = filePath + this.metadataDictionary[filePath] = data + }) + + this.emit('processMetadata progress', this.throttleMetadata.queue.length) + + counter++ + } + + if (top) { + await this.saveMetadata() + } + + return counter + } + + getRecordFor(item) { + return this.recordStore.getRecord(item) + } + getDuration(item) { let noticedMissingMetadata = false @@ -588,24 +658,9 @@ class Backend extends EventEmitter { return {seconds, string, noticedMissingMetadata, approxSymbol} } - async download(item) { - if (isGroup(item)) { - // TODO: Download all children (recursively), show a confirmation prompt - // if there are a lot of items (remember to flatten). - return - } - - // Don't start downloading an item if we're already downloading it! - if (this.getRecordFor(item).downloading) { - return - } - - const arg = item.downloaderArg - this.getRecordFor(item).downloading = true - try { - return await getDownloaderFor(arg)(arg) - } finally { - this.getRecordFor(item).downloading = false + async stopPlayingAll() { + for (const queuePlayer of this.queuePlayers) { + await queuePlayer.stopPlaying() } } } |