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 /ui.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 'ui.js')
-rw-r--r-- | ui.js | 230 |
1 files changed, 157 insertions, 73 deletions
diff --git a/ui.js b/ui.js index aafe064..de3ab98 100644 --- a/ui.js +++ b/ui.js @@ -84,6 +84,9 @@ const keyBindings = [ ['isFocusLabels', 'L', {caseless: false}], // todo: better key? to let isToggleLoop be caseless again ['isSelectUp', telc.isShiftUp], ['isSelectDown', telc.isShiftDown], + ['isPreviousPlayer', telc.isMetaUp], + ['isNextPlayer', telc.isMetaDown], + ['isNewPlayer', keyBuf => keyBuf.equals(Buffer.from([0x1b, 0x6e]))], // Number pad ['isUp', '8'], @@ -150,11 +153,12 @@ class AppElement extends FocusElement { this.isPartyHost = false this.bindListeners() - this.attachListeners() + this.initialAttachListeners() this.config = Object.assign({ canControlPlayback: true, canControlQueue: true, + canControlQueuePlayers: true, canProcessMetadata: true, canSuspend: true, menubarColor: 4, // blue @@ -198,7 +202,6 @@ class AppElement extends FocusElement { this.queueListingElement = new QueueListingElement(this) this.setupCommonGrouplikeListingEvents(this.queueListingElement) - this.queueListingElement.loadGrouplike(this.backend.queueGrouplike) this.paneRight.addChild(this.queueListingElement) this.queueLengthLabel = new Label('') @@ -220,9 +223,9 @@ class AppElement extends FocusElement { this.playbackInfoElement = new PlaybackInfoElement() this.playbackPane.addChild(this.playbackInfoElement) - this.playbackInfoElement.on('seek back', () => this.backend.seekBack(5)) - this.playbackInfoElement.on('seek ahead', () => this.backend.seekAhead(5)) - this.playbackInfoElement.on('toggle pause', () => this.backend.togglePause()) + this.playbackInfoElement.on('seek back', () => this.SQP.seekBack(5)) + this.playbackInfoElement.on('seek ahead', () => this.SQP.seekAhead(5)) + this.playbackInfoElement.on('toggle pause', () => this.SQP.togglePause()) this.partyTop = new DisplayElement() this.partyBottom = new DisplayElement() @@ -277,8 +280,8 @@ class AppElement extends FocusElement { this.config.canSuspend && {label: 'Suspend', action: () => this.suspend()} ]}, {text: 'Playback', menuFn: () => { - const { playingTrack } = this.backend - const { items } = this.backend.queueGrouplike + const { playingTrack } = this.SQP + const { items } = this.SQP.queueGrouplike const curIndex = items.indexOf(playingTrack) const next = (curIndex >= 0) && items[curIndex + 1] const previous = (curIndex >= 0) && items[curIndex - 1] @@ -291,13 +294,13 @@ class AppElement extends FocusElement { {element: this.pauseNextControl}, {element: this.volumeSlider}, {divider: true}, - previous && {label: `Previous (${previous.name})`, action: () => this.backend.playPrevious(playingTrack)}, - next && {label: `Next (${next.name})`, action: () => this.backend.playNext(playingTrack)}, + previous && {label: `Previous (${previous.name})`, action: () => this.SQP.playPrevious(playingTrack)}, + next && {label: `Next (${next.name})`, action: () => this.SQP.playNext(playingTrack)}, next && {label: '- Play later', action: () => this.playLater(next)} ] }}, {text: 'Queue', menuFn: () => { - const { items } = this.backend.queueGrouplike + const { items } = this.SQP.queueGrouplike const curIndex = items.indexOf(this.playingTrack) return [ @@ -310,49 +313,82 @@ class AppElement extends FocusElement { ]) this.playingControl = new ToggleControl('Pause?', { - setValue: val => this.backend.setPause(val), - getValue: () => this.backend.player.isPaused, + setValue: val => this.SQP.setPause(val), + getValue: () => this.SQP.player.isPaused, getEnabled: () => this.config.canControlPlayback }) this.loopingControl = new ToggleControl('Loop current track?', { - setValue: val => this.backend.setLoop(val), - getValue: () => this.backend.player.isLooping, + setValue: val => this.SQP.setLoop(val), + getValue: () => this.SQP.player.isLooping, getEnabled: () => this.config.canControlPlayback }) this.pauseNextControl = new ToggleControl('Pause when this track ends?', { - setValue: val => this.backend.setPauseNextTrack(val), - getValue: () => this.backend.pauseNextTrack, + setValue: val => this.SQP.setPauseNextTrack(val), + getValue: () => this.SQP.pauseNextTrack, getEnabled: () => this.config.canControlPlayback }) this.volumeSlider = new SliderElement('Volume', { - setValue: val => this.backend.setVolume(val), - getValue: () => this.backend.player.volume, + setValue: val => this.SQP.setVolume(val), + getValue: () => this.SQP.player.volume, getEnabled: () => this.config.canControlPlayback }) + + this.selectQueuePlayer(this.backend.queuePlayers[0]) } bindListeners() { - this.handlePlaying = this.handlePlaying.bind(this) - this.handlePrintStatusLine = this.handlePrintStatusLine.bind(this) - this.handleProcessMetadataProgress = this.handleProcessMetadataProgress.bind(this) - this.handleQueueUpdated = this.handleQueueUpdated.bind(this) + for (const key of [ + 'handlePlaying', + 'handleReceivedTimeData', + 'handleProcessMetadataProgress', + 'handleQueueUpdated', + 'handleAddedQueuePlayer' + ]) { + this[key] = this[key].bind(this) + } } - attachListeners() { - this.backend.on('playing', this.handlePlaying) - this.backend.on('printStatusLine', this.handlePrintStatusLine) - this.backend.on('processMetadata progress', this.handleProcessMetadataProgress) - this.backend.on('queue updated', this.handleQueueUpdated) + initialAttachListeners() { + this.attachBackendListeners() + for (const queuePlayer of this.backend.queuePlayers) { + this.attachQueuePlayerListeners(queuePlayer) + } } removeListeners() { - this.backend.removeListener('playing', this.handlePlaying) - this.backend.removeListener('printStatusLine', this.handlePrintStatusLine) + this.removeBackendListeners() + for (const queuePlayer of this.backend.queuePlayers) { + this.removeQueuePlayerListeners(queuePlayer) + } + } + + attachQueuePlayerListeners(queuePlayer) { + queuePlayer.on('received time data', this.handleReceivedTimeData) + queuePlayer.on('playing', this.handlePlaying) + queuePlayer.on('queue updated', this.handleQueueUpdated) + } + + removeQueuePlayerListeners(queuePlayer) { + queuePlayer.removeListener('receivedTimeData', this.handleReceivedTimeData) + queuePlayer.removeListener('playing', this.handlePlaying) + queuePlayer.removeListener('queue updated', this.handleQueueUpdated) + } + + attachBackendListeners() { + this.backend.on('processMetadata progress', this.handleProcessMetadataProgress) + this.backend.on('added queue player', this.handleAddedQueuePlayer) + } + + removeBackendListeners() { this.backend.removeListener('processMetadata progress', this.handleProcessMetadataProgress) - this.backend.removeListener('queue updated', this.handleQueueUpdated) + this.backend.removeListener('added queue player', this.handleAddedQueuePlayer) + } + + handleAddedQueuePlayer(queuePlayer) { + this.attachQueuePlayerListeners(queuePlayer) } handlePlaying(track, oldTrack) { @@ -367,9 +403,11 @@ class AppElement extends FocusElement { this.updateQueueLengthLabel() } - handlePrintStatusLine(data) { - this.playbackInfoElement.updateProgress(data, this.backend.player) - this.updateQueueLengthLabel() + handleReceivedTimeData(data, queuePlayer) { + if (queuePlayer === this.SQP) { + this.playbackInfoElement.updateProgress(data, queuePlayer.player) + this.updateQueueLengthLabel() + } } handleProcessMetadataProgress(remaining) { @@ -380,6 +418,45 @@ class AppElement extends FocusElement { this.queueListingElement.buildItems() } + selectQueuePlayer(queuePlayer) { + // You can use this.SQP as a shorthand to get this. + this.selectedQueuePlayer = queuePlayer + + this.playbackInfoElement.updateTrack(queuePlayer.playingTrack) + if (queuePlayer.timeData) { + this.playbackInfoElement.updateProgress(queuePlayer.timeData, queuePlayer.player) + } + + this.queueListingElement.loadGrouplike(queuePlayer.queueGrouplike) + } + + selectNextQueuePlayer() { + const { queuePlayers } = this.backend + let index = queuePlayers.indexOf(this.SQP) + 1 + if (index >= queuePlayers.length) { + index = 0 + } + this.selectQueuePlayer(queuePlayers[index]) + } + + selectPreviousQueuePlayer() { + const { queuePlayers } = this.backend + let index = queuePlayers.indexOf(this.SQP) - 1 + if (index <= -1) { + index = queuePlayers.length - 1 + } + this.selectQueuePlayer(queuePlayers[index]) + } + + async addQueuePlayer() { + if (!this.config.canControlQueuePlayers) { + return false + } + + const queuePlayer = await this.backend.addQueuePlayer() + this.selectQueuePlayer(queuePlayer) + } + selected() { if (this.paneLeft.visible) { this.root.select(this.tabber) @@ -397,7 +474,7 @@ class AppElement extends FocusElement { this.tabber.addTab(grouplikeListing) this.tabber.selectTab(grouplikeListing) - grouplikeListing.on('download', item => this.backend.download(item)) + grouplikeListing.on('download', item => this.SQP.download(item)) grouplikeListing.on('browse', item => grouplikeListing.loadGrouplike(item)) grouplikeListing.on('queue', (item, opts) => this.handleQueueOptions(item, opts)) @@ -530,7 +607,7 @@ class AppElement extends FocusElement { return } - this.backend.play(item) + this.SQP.play(item) } unqueue(item) { @@ -539,7 +616,7 @@ class AppElement extends FocusElement { } let focusItem = this.queueListingElement.currentItem - focusItem = this.backend.unqueue(item, focusItem) + focusItem = this.SQP.unqueue(item, focusItem) this.queueListingElement.buildItems() this.updateQueueLengthLabel() @@ -554,7 +631,7 @@ class AppElement extends FocusElement { return } - this.backend.playSooner(item) + this.SQP.playSooner(item) // It may not have queued as soon as the user wants; in that case, they'll // want to queue it sooner again. Automatically reselect the track so that // this they don't have to navigate back to it by hand. @@ -566,7 +643,7 @@ class AppElement extends FocusElement { return } - this.backend.playLater(item) + this.SQP.playLater(item) // Just for consistency with playSooner (you can press ^-L to quickly get // back to the current track). this.queueListingElement.selectAndShow(item) @@ -577,7 +654,7 @@ class AppElement extends FocusElement { return } - this.backend.clearQueuePast(item) + this.SQP.clearQueuePast(item) this.queueListingElement.selectAndShow(item) } @@ -586,7 +663,7 @@ class AppElement extends FocusElement { return } - this.backend.clearQueueUpTo(item) + this.SQP.clearQueueUpTo(item) this.queueListingElement.selectAndShow(item) } @@ -740,7 +817,7 @@ class AppElement extends FocusElement { async shutdown() { if (this.config.stopPlayingUponQuit) { - await this.backend.stopPlaying() + await this.backend.stopPlayingAll() } this.emit('quitRequested') @@ -878,23 +955,23 @@ class AppElement extends FocusElement { if ((telc.isLeft(keyBuf) || telc.isRight(keyBuf)) && this.menubar.isSelected) { return // le sigh } else if (input.isRight(keyBuf)) { - this.backend.seekAhead(10) + this.SQP.seekAhead(10) } else if (input.isLeft(keyBuf)) { - this.backend.seekBack(10) + this.SQP.seekBack(10) } else if (input.isTogglePause(keyBuf)) { - this.backend.togglePause() + this.SQP.togglePause() } else if (input.isToggleLoop(keyBuf)) { - this.backend.toggleLoop() + this.SQP.toggleLoop() } else if (input.isVolumeUp(keyBuf)) { - this.backend.volUp() + this.SQP.volUp() } else if (input.isVolumeDown(keyBuf)) { - this.backend.volDown() + this.SQP.volDown() } else if (input.isStop(keyBuf)) { - this.backend.stopPlaying() + this.SQP.stopPlaying() } else if (input.isSkipBack(keyBuf)) { - this.backend.playPrevious(this.backend.playingTrack, true) + this.SQP.playPrevious(this.SQP.playingTrack, true) } else if (input.isSkipAhead(keyBuf)) { - this.backend.playNext(this.backend.playingTrack, true) + this.SQP.playNext(this.SQP.playingTrack, true) } } @@ -928,6 +1005,12 @@ class AppElement extends FocusElement { this.tabber.nextTab() } else if (this.tabber.isSelected && keyBuf.equals(Buffer.from(['T'.charCodeAt(0)]))) { this.tabber.previousTab() + } else if (input.isPreviousPlayer(keyBuf)) { + this.selectPreviousQueuePlayer() + } else if (input.isNextPlayer(keyBuf)) { + this.selectNextQueuePlayer() + } else if (input.isNewPlayer(keyBuf)) { + this.addQueuePlayer() } else { super.keyPressed(keyBuf) } @@ -963,11 +1046,11 @@ class AppElement extends FocusElement { } shuffleQueue() { - this.backend.shuffleQueue() + this.SQP.shuffleQueue() } clearQueue() { - this.backend.clearQueue() + this.SQP.clearQueue() this.queueListingElement.selectNone() this.updateQueueLengthLabel() @@ -980,12 +1063,16 @@ class AppElement extends FocusElement { // just directly moved from the old event listener on grouplikeListings for // 'queue'. handleQueueOptions(item, {where = 'end', order = 'normal', play = false, skip = false} = {}) { + if (!this.config.canControlQueue) { + return + } + const passedItem = item - let { playingTrack } = this.backend + let { playingTrack } = this.SQP if (skip && playingTrack === item) { - this.backend.playNext(playingTrack) + this.SQP.playNext(playingTrack) } if (isGroup(item)) { @@ -1011,7 +1098,7 @@ class AppElement extends FocusElement { afterItem = this.queueListingElement.currentItem } - this.backend.queue(item, afterItem, { + this.SQP.queue(item, afterItem, { movePlayingTrack: order === 'normal' }) @@ -1019,7 +1106,7 @@ class AppElement extends FocusElement { this.queueListingElement.selectAndShow(passedItem) } } else if (where.startsWith('distribute-')) { - this.backend.distributeQueue(item, { + this.SQP.distributeQueue(item, { how: where.slice('distribute-'.length) }) } @@ -1058,8 +1145,8 @@ class AppElement extends FocusElement { } updateQueueLengthLabel() { - const { playingTrack } = this.backend - const { items } = this.backend.queueGrouplike + const { playingTrack } = this.SQP + const { items } = this.SQP.queueGrouplike let trackRemainSec = 0 @@ -1091,7 +1178,7 @@ class AppElement extends FocusElement { const { duration } = getTimeStringsFromSec(0, totalRemainSec) this.queueLengthLabel.text = (playingTrack && items.includes(playingTrack) - ? `(${this.playSymbol} ${index} / ${items.length})` + ? `(${this.SQP.playSymbol} ${index} / ${items.length})` : `(${items.length})`) this.queueTimeLabel.text = `(${duration + approxSymbol})` @@ -1102,18 +1189,13 @@ class AppElement extends FocusElement { this.queueTimeLabel.y = this.paneRight.contentH - 1 } - get playSymbol() { - const { player, playingTrack } = this.backend - if (player && playingTrack) { - if (player.isPaused) { - return '⏸' - } else { - return '▶' - } - } else { - return '.' - } + get SQP() { + // Just a convenient shorthand. + return this.selectedQueuePlayer } + + get selectedQueuePlayer() { return this.getDep('selectedQueuePlayer') } + set selectedQueuePlayer(v) { return this.setDep('selectedQueuePlayer', v) } } class GrouplikeListingElement extends Form { @@ -1232,7 +1314,7 @@ class GrouplikeListingElement extends Form { this.form.selectAndShow(this.grouplike.items[this.grouplike.items.length - 1]) } else if (keyBuf[0] === 12) { // ctrl-L if (this.grouplike.isTheQueue) { - this.form.selectAndShow(this.app.backend.playingTrack) + this.form.selectAndShow(this.app.SQP.playingTrack) } else { this.toggleExpandLabels() } @@ -1437,7 +1519,9 @@ class GrouplikeListingElement extends Form { this.form.scrollSelectedElementIntoView() } this.jumpElement.visible = false - this.root.select(this) + if (this.jumpElement.isSelected) { + this.root.select(this) + } this.fixLayout() } @@ -2345,7 +2429,7 @@ class InteractiveGrouplikeItemElement extends BasicGrouplikeItemElement { writable.write('G') } else if (record.downloading) { writable.write(braille[Math.floor(Date.now() / 250) % 6]) - } else if (this.app.backend.playingTrack === this.item) { + } else if (this.app.SQP.playingTrack === this.item) { writable.write('\u25B6') } else { writable.write(' ') |