diff options
author | Florrie <towerofnix@gmail.com> | 2019-07-05 21:10:32 -0300 |
---|---|---|
committer | Florrie <towerofnix@gmail.com> | 2019-07-05 22:02:46 -0300 |
commit | 08a3150fe9c9427bdb9e46d0cde14ad30747008d (patch) | |
tree | a0ad6e20e9745997038d4d57821083ae7f21e2e5 /ui.js | |
parent | c2a89f91684df916aaf8f66eab33280ad99b6b1b (diff) |
Separate backend from UI
Diffstat (limited to 'ui.js')
-rw-r--r-- | ui.js | 697 |
1 files changed, 137 insertions, 560 deletions
diff --git a/ui.js b/ui.js index 834269e..c2c3e67 100644 --- a/ui.js +++ b/ui.js @@ -1,12 +1,27 @@ +// The UI in MTUI! Interfaces with the backend to form the complete mtui app. + +'use strict' + const { getAllCrawlersForArg } = require('./crawlers') -const { getMetadataReaderFor } = require('./metadata-readers') -const { getDownloaderFor } = require('./downloaders') -const { getPlayer } = require('./players') -const { parentSymbol, isGroup, isTrack, getItemPath, getItemPathString, flattenGrouplike, countTotalItems, shuffleOrderOfGroups, cloneGrouplike, getNameWithoutTrackNumber } = require('./playlist-utils') -const { shuffleArray, throttlePromise, getTimeStringsFromSec } = require('./general-util') const processSmartPlaylist = require('./smart-playlist') const UndoManager = require('./undo-manager') -const RecordStore = require('./record-store') + +const { + shuffleArray, + getTimeStringsFromSec +} = require('./general-util') + +const { + cloneGrouplike, + countTotalItems, + flattenGrouplike, + getItemPath, + getNameWithoutTrackNumber, + isGroup, + isTrack, + parentSymbol, + shuffleOrderOfGroups +} = require('./playlist-utils') const { ui: { @@ -30,11 +45,6 @@ const { } } = require('./tui-lib') -const fs = require('fs') -const { promisify } = require('util') -const readFile = promisify(fs.readFile) -const writeFile = promisify(fs.writeFile) - const input = {} const keyBindings = [ @@ -124,23 +134,37 @@ telc.isSelect = input.isSelect telc.isBackspace = input.isBackspace class AppElement extends FocusElement { - constructor() { + constructor(backend) { super() - this.player = null - this.recordStore = new RecordStore() + this.backend = backend + + this.backend.on('playing', (track, oldTrack) => { + if (track) { + this.playbackInfoElement.updateTrack(track) + if (this.queueListingElement.currentItem === oldTrack) { + this.queueListingElement.selectAndShow(track) + } + } else { + this.playbackInfoElement.clearInfo() + } + this.updateQueueLengthLabel() + }) + + this.backend.on('printStatusLine', data => { + this.playbackInfoElement.updateProgress(data, this.backend.player) + this.updateQueueLengthLabel() + }) + + this.backend.on('processMetadata progress', remaining => { + this.metadataStatusLabel.text = `Processing metadata - ${remaining} to go.` + }) + + // TODO: Move edit mode stuff to the backend! this.undoManager = new UndoManager() - this.throttleMetadata = throttlePromise(10) - this.metadataDictionary = {} - this.queueGrouplike = {name: 'Queue', isTheQueue: true, items: []} this.markGrouplike = {name: 'Marked', items: []} this.editMode = false - this.rootDirectory = process.env.HOME + '/.mtui' - this.metadataPath = this.rootDirectory + '/track-metadata.json' - - this.loadMetadata() - // We add this is a child later (so that it's on top of every element). this.menu = new ContextMenu() @@ -164,7 +188,7 @@ class AppElement extends FocusElement { this.queueListingElement = new QueueListingElement(this) this.setupCommonGrouplikeListingEvents(this.queueListingElement) - this.queueListingElement.loadGrouplike(this.queueGrouplike) + this.queueListingElement.loadGrouplike(this.backend.queueGrouplike) this.paneRight.addChild(this.queueListingElement) this.queueLengthLabel = new Label('') @@ -173,8 +197,8 @@ class AppElement extends FocusElement { this.queueTimeLabel = new Label('') this.paneRight.addChild(this.queueTimeLabel) - this.queueListingElement.on('queue', item => this.playGrouplikeItem(item)) - this.queueListingElement.on('remove', item => this.unqueueGrouplikeItem(item)) + this.queueListingElement.on('queue', item => this.play(item)) + this.queueListingElement.on('remove', item => this.unqueue(item)) this.queueListingElement.on('shuffle', () => this.shuffleQueue()) this.queueListingElement.on('clear', () => this.clearQueue()) this.queueListingElement.on('select main listing', @@ -226,25 +250,26 @@ class AppElement extends FocusElement { {label: 'Suspend', action: () => this.suspend()} ]}, {text: 'Playback', menuFn: () => { - const { items } = this.queueGrouplike - const curIndex = items.indexOf(this.playingTrack) + const { playingTrack } = this.backend + const { items } = this.backend.queueGrouplike + const curIndex = items.indexOf(playingTrack) const next = (curIndex >= 0) && items[curIndex + 1] const previous = (curIndex >= 0) && items[curIndex - 1] return [ - {label: this.playingTrack ? `("${this.playingTrack.name}")` : '(No track playing.)'}, + {label: playingTrack ? `("${playingTrack.name}")` : '(No track playing.)'}, {divider: true}, - this.playingTrack && {element: this.playingControl}, + playingTrack && {element: this.playingControl}, {element: this.loopingControl}, {element: this.volumeSlider}, (next || previous) && {divider: true}, - previous && {label: `Previous (${previous.name})`, action: () => this.playPreviousTrack(this.playingTrack)}, - next && {label: `Next (${next.name})`, action: () => this.playNextTrack(this.playingTrack)}, + previous && {label: `Previous (${previous.name})`, action: () => this.backend.playPrevious(playingTrack)}, + next && {label: `Next (${next.name})`, action: () => this.backend.playNext(playingTrack)}, next && {label: '- Play later', action: () => this.playLater(next)} ] }}, {text: 'Queue', menuFn: () => { - const { items } = this.queueGrouplike + const { items } = this.backend.queueGrouplike const curIndex = items.indexOf(this.playingTrack) return [ @@ -257,18 +282,18 @@ class AppElement extends FocusElement { ]) this.playingControl = new ToggleControl('Pause?', { - setValue: val => this.setPause(val), - getValue: () => this.player.isPaused + setValue: val => this.backend.setPause(val), + getValue: () => this.backend.player.isPaused }) this.loopingControl = new ToggleControl('Loop current track?', { - setValue: val => this.setLoop(val), - getValue: () => this.player.isLooping + setValue: val => this.backend.setLoop(val), + getValue: () => this.backend.player.isLooping }) this.volumeSlider = new SliderElement('Volume', { - setValue: val => this.setVolume(val), - getValue: () => this.player.volume + setValue: val => this.backend.setVolume(val), + getValue: () => this.backend.player.volume }) } @@ -391,40 +416,37 @@ class AppElement extends FocusElement { } } - playSooner(item) { - this.distributeQueueGrouplikeItem(item, { - how: 'randomly', - rangeEnd: this.queueGrouplike.items.indexOf(item) - }) + play(item) { + this.backend.play(item) + } + + unqueue(item) { + let focusItem = this.queueListingElement.currentItem + focusItem = this.backend.unqueue(item, focusItem) + + this.queueListingElement.buildItems() + this.updateQueueLengthLabel() - // It may not have queued as soon as the user wants; in that acse, they'll + if (focusItem) { + this.queueListingElement.selectAndShow(focusItem) + } + } + + playSooner(item) { + this.backend.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. this.queueListingElement.selectAndShow(item) } playLater(item) { - this.handleQueueOptions(item, { - where: 'distribute-randomly', - skip: true - }) - + this.backend.playLater(item) // Just for consistency with playSooner (you can press ^-L to quickly get // back to the current track). this.queueListingElement.selectAndShow(item) } - playLater(item) { - this.handleQueueOptions(item, { - where: 'distribute-randomly', - skip: true - }) - - // Just for consistency. - this.queueListingElement.selectAndShow(item) - } - - showMenuForItemElement(el, listing) { const emitControls = play => () => { this.handleQueueOptions(item, { @@ -445,7 +467,7 @@ class AppElement extends FocusElement { {divider: true}, {label: 'Play later', action: () => this.playLater(item)}, {label: 'Play sooner', action: () => this.playSooner(item)}, - {label: 'Remove from queue', action: () => this.unqueueGrouplikeItem(item)} + {label: 'Remove from queue', action: () => this.unqueue(item)} ] } else { items = [ @@ -482,7 +504,7 @@ class AppElement extends FocusElement { {label: 'Process metadata (new entries)', action: () => this.processMetadata(item, false)}, {label: 'Process metadata (reprocess)', action: () => this.processMetadata(item, true)}, - {label: 'Remove from queue', action: () => this.unqueueGrouplikeItem(item)} + {label: 'Remove from queue', action: () => this.unqueue(item)} ] } @@ -548,27 +570,8 @@ class AppElement extends FocusElement { }) } - async setup() { - this.player = await getPlayer() - - if (!this.player) { - return { - error: "Sorry, it doesn't look like there's an audio player installed on your computer. Can you try installing MPV (https://mpv.io) or SoX?" - } - } - - this.player.on('printStatusLine', data => { - if (this.playingTrack) { - this.playbackInfoElement.updateProgress(data, this.player) - this.updateQueueLengthLabel() - } - }) - - return true - } - async shutdown() { - await this.player.kill() + await this.backend.stopPlaying() this.emit('quitRequested') } @@ -624,23 +627,23 @@ class AppElement extends FocusElement { } else if ((telc.isLeft(keyBuf) || telc.isRight(keyBuf)) && this.menubar.isSelected) { return // le sigh } else if (input.isRight(keyBuf)) { - this.seekAhead(10) + this.backend.seekAhead(10) } else if (input.isLeft(keyBuf)) { - this.seekBack(10) + this.backend.seekBack(10) } else if (input.isTogglePause(keyBuf)) { - this.togglePause() + this.backend.togglePause() } else if (input.isToggleLoop(keyBuf)) { - this.toggleLoop() + this.backend.toggleLoop() } else if (input.isVolumeUp(keyBuf)) { - this.volUp() + this.backend.volUp() } else if (input.isVolumeDown(keyBuf)) { - this.volDown() + this.backend.volDown() } else if (input.isStop(keyBuf)) { - this.clearPlayingTrack() + this.backend.stopPlaying() } else if (input.isSkipBack(keyBuf)) { - this.playPreviousTrack(this.playingTrack, true) + this.backend.playPrevious(this.backend.playingTrack, true) } else if (input.isSkipAhead(keyBuf)) { - this.playNextTrack(this.playingTrack, true) + this.backend.playNext(this.backend.playingTrack, true) } else if (input.isFocusTabber(keyBuf) && this.tabber.selectable) { this.root.select(this.tabber) } else if (input.isFocusQueue(keyBuf) && this.queueListingElement.selectable) { @@ -704,20 +707,12 @@ class AppElement extends FocusElement { } shuffleQueue() { - const queue = this.queueGrouplike - const index = queue.items.indexOf(this.playingTrack) + 1 // This is 0 if no track is playing - const initialItems = queue.items.slice(0, index) - const remainingItems = queue.items.slice(index) - const newItems = initialItems.concat(shuffleArray(remainingItems)) - queue.items = newItems + this.backend.shuffleQueue() this.queueListingElement.buildItems() } clearQueue() { - // Clear the queue so that there aren't any items left in it (except for - // the track that's currently playing). - this.queueGrouplike.items = this.queueGrouplike.items - .filter(item => item === this.playingTrack) + this.backend.clearQueue() this.queueListingElement.buildItems() this.queueListingElement.selectNone() this.updateQueueLengthLabel() @@ -727,57 +722,16 @@ class AppElement extends FocusElement { } } - 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) - } - - stopPlaying() { - // We emit this so playTrack doesn't immediately start a new track. - // We aren't *actually* about to play a new track. - this.emit('playing new track') - this.player.kill() - } - // TODO: I'd like to name/incorporate this function better.. for now it's // just directly moved from the old event listener on grouplikeListings for // 'queue'. handleQueueOptions(item, {where = 'end', order = 'normal', play = false, skip = false} = {}) { const passedItem = item - if (skip && this.playingTrack === item) { - this.playNextTrack(this.playingTrack) + let { playingTrack } = this.backend + + if (skip && playingTrack === item) { + this.backend.playNext(playingTrack) } if (isGroup(item)) { @@ -794,391 +748,70 @@ class AppElement extends FocusElement { if (where === 'next' || where === 'next-selected' || where === 'end') { let afterItem = null if (where === 'next') { - afterItem = this.playingTrack + afterItem = playingTrack } else if (where === 'next-selected') { afterItem = this.queueListingElement.currentItem } - this.queueGrouplikeItem(item, afterItem, { + this.backend.queue(item, afterItem, { movePlayingTrack: order === 'normal' }) + this.queueListingElement.buildItems() if (isTrack(passedItem)) { this.queueListingElement.selectAndShow(passedItem) } } else if (where.startsWith('distribute-')) { - this.distributeQueueGrouplikeItem(item, { + this.backend.distributeQueue(item, { how: where.slice('distribute-'.length) }) + this.queueListingElement.buildItems() } - if (play) { - this.playGrouplikeItem(item) - } - } - - queueGrouplikeItem(topItem, afterItem = null, {movePlayingTrack = true} = {}) { - const newTrackIndex = this.queueGrouplike.items.length - - // The position which new tracks should be added at, if afterItem is - // passed. - const afterIndex = afterItem && this.queueGrouplike.items.indexOf(afterItem) - - // Keeps track of how many tracks have been added; this is used so that - // a whole group can be queued in order after a given item. - let grouplikeOffset = 0 - - const recursivelyAddTracks = item => { - // For groups, just queue all children. - if (isGroup(item)) { - for (const child of item.items) { - recursivelyAddTracks(child) - } - - return - } - - const items = this.queueGrouplike.items - - // You can't put the same track in the queue twice - we automatically - // remove the old entry. (You can't for a variety of technical reasons, - // but basically you either have the display all bork'd, or new tracks - // can't be added to the queue in the right order (because Object.assign - // is needed to fix the display, but then you end up with a new object - // that doesn't work with indexOf).) - if (items.includes(item)) { - // HOWEVER, if the "moveCurrentTrack" option is false, and that item - // is the one that's currently playing, we won't do anything with it - // at all. - if (!movePlayingTrack && item === this.playingTrack) { - return - } - items.splice(items.indexOf(item), 1) - } - - if (afterItem === 'FRONT') { - items.unshift(item) - } else if (afterItem) { - items.splice(afterIndex + 1 + grouplikeOffset, 0, item) - } else { - items.push(item) - } - - grouplikeOffset++ - } - - recursivelyAddTracks(topItem) - this.queueListingElement.buildItems() - this.updateQueueLengthLabel() - - // This is the first new track, if a group was queued. - const newTrack = this.queueGrouplike.items[newTrackIndex] - - return newTrack - } - - distributeQueueGrouplikeItem(grouplike, {how = 'evenly', rangeEnd = 'end-of-queue'}) { - if (isTrack(grouplike)) { - grouplike = {items: [grouplike]} - } - - const queue = this.queueGrouplike - const newItems = flattenGrouplike(grouplike).items - - // Expressly do an initial pass and unqueue the items we want to queue - - // otherwise they would mess with the math we do afterwords. - for (const item of newItems) { - if (queue.items.includes(item)) { - /* - if (!movePlayingTrack && item === this.playingTrack) { - // NB: if uncommenting this code, splice item from newItems and do - // continue instead of return! - return - } - */ - queue.items.splice(queue.items.indexOf(item), 1) - } - } - - const distributeStart = queue.items.indexOf(this.playingTrack) + 1 - - let distributeEnd - if (rangeEnd === 'end-of-queue') { - distributeEnd = queue.items.length - } else if (typeof rangeEnd === 'number') { - distributeEnd = Math.min(queue.items.length, rangeEnd) - } else { - throw new Error('Invalid rangeEnd: ' + rangeEnd) - } - - const distributeSize = distributeEnd - distributeStart - - const queueItem = (item, insertIndex) => { - if (queue.items.includes(item)) { - /* - if (!movePlayingTrack && item === this.playingTrack) { - return - } - */ - queue.items.splice(queue.items.indexOf(item), 1) - } else { - offset++ - } - queue.items.splice(insertIndex, 0, item) - } - - if (how === 'evenly') { - let offset = 0 - for (const item of newItems) { - const insertIndex = distributeStart + Math.floor(offset) - queue.items.splice(insertIndex, 0, item) - offset++ - offset += distributeSize / newItems.length - } - } else if (how === 'randomly') { - const indexes = newItems.map(() => Math.floor(Math.random() * distributeSize)) - indexes.sort() - for (let i = 0; i < newItems.length; i++) { - const item = newItems[i] - const insertIndex = distributeStart + indexes[i] + i - queue.items.splice(insertIndex, 0, item) - } - } - - this.queueListingElement.buildItems() this.updateQueueLengthLabel() - } - - unqueueGrouplikeItem(topItem) { - // This function has support to unqueue groups - it removes all tracks in - // the group recursively. (You can never unqueue a group itself from the - // queue listing because groups can't be added directly to the queue.) - - const recursivelyUnqueueTracks = item => { - // For groups, just unqueue all children. (Groups themselves can't be - // added to the queue, so we don't need to worry about removing them.) - if (isGroup(item)) { - for (const child of item.items) { - recursivelyUnqueueTracks(child) - } - - return - } - - // Don't unqueue the currently-playing track - this usually causes more - // trouble than it's worth. - if (item === this.playingTrack) { - return - } - - const items = this.queueGrouplike.items - - // If we're unqueueing the item which is currently focused by the cursor, - // just move the cursor ahead. - if (item === focusItem) { - focusItem = items[items.indexOf(focusItem) + 1] - // ...Unless that puts it at past the end of the list, in which case, move - // it behind the item we're removing. - if (!focusItem) { - focusItem = items[items.length - 2] - } - } - - if (items.includes(item)) { - items.splice(items.indexOf(item), 1) - } - } - - let focusItem = this.queueListingElement.currentItem - - recursivelyUnqueueTracks(topItem) - this.queueListingElement.buildItems() - this.updateQueueLengthLabel() - - if (focusItem) { - this.queueListingElement.selectAndShow(focusItem) - } - } - - async downloadGrouplikeItem(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.recordStore.getRecord(item).downloading) { - return - } - - const arg = item.downloaderArg - this.recordStore.getRecord(item).downloading = true - try { - return await getDownloaderFor(arg)(arg) - } finally { - this.recordStore.getRecord(item).downloading = false - } - } - - async playGrouplikeItem(item) { - if (this.player === null) { - throw new Error('Attempted to play before a player was loaded') - } - - let playingThisTrack = true - this.emit('playing new track') - this.once('playing new track', () => { - playingThisTrack = false - }) - - // If it's a group, play the first track. - if (isGroup(item)) { - item = flattenGrouplike(item).items[0] - } - - // If there is no item (e.g. an empty group), well.. don't do anything. - if (!item) { - return - } - - playTrack: { - // No downloader argument? That's no good - stop here. - // TODO: An error icon on this item, or something??? - if (!item.downloaderArg) { - break playTrack - } - - // If, by the time the track is downloaded, we're playing something - // different from when the download started, assume that we just want to - // keep listening to whatever new thing we started. - - const oldTrack = this.playingTrack - - const downloadFile = await this.downloadGrouplikeItem(item) - - if (this.playingTrack !== oldTrack) { - return - } - - await this.player.kill() - this.playingTrack = item - this.playbackInfoElement.updateTrack(item) - this.updateQueueLengthLabel() - - if (this.queueListingElement.currentItem === oldTrack) { - this.queueListingElement.selectAndShow(item) - } - - await Promise.all([ - writeFile(this.rootDirectory + '/current-track.txt', - getItemPathString(item)), - writeFile(this.rootDirectory + '/current-track.json', - JSON.stringify(item, null, 2)) - ]) - - await this.player.playFile(downloadFile) - } - - // playingThisTrack now means whether the track played through to the end - // (true), or was stopped by a different track being started (false). - - if (playingThisTrack) { - if (!this.playNextTrack(item)) { - this.clearPlayingTrack() - } - } - } - - playNextTrack(track, automaticallyQueueNextTrack = false) { - if (!track) { - return false - } - - const queue = this.queueGrouplike - let queueIndex = queue.items.indexOf(track) - if (queueIndex === -1) { - return false - } - queueIndex++ - if (queueIndex >= queue.items.length) { - if (automaticallyQueueNextTrack) { - const parent = track[parentSymbol] - if (!parent) { - return false - } - const index = parent.items.indexOf(track) - const nextItem = parent.items[index + 1] - if (!nextItem) { - return false - } - this.queueGrouplikeItem(nextItem, false) - queueIndex = queue.items.length - 1 - } else { - return false - } + if (play) { + this.play(item) } - - this.playGrouplikeItem(queue.items[queueIndex]) - return true } - playPreviousTrack(track, automaticallyQueuePreviousTrack = false) { - if (!track) { - return false + async processMetadata(item) { + if (this.clearMetadataStatusTimeout) { + clearTimeout(this.clearMetadataStatusTimeout) } - const queue = this.queueGrouplike - let queueIndex = queue.items.indexOf(track) - if (queueIndex === -1) { - return false - } - queueIndex-- + this.metadataStatusLabel.text = 'Processing metadata...' + this.metadataStatusLabel.visible = true + this.fixLayout() - if (queueIndex < 0) { - if (automaticallyQueuePreviousTrack) { - const parent = track[parentSymbol] - if (!parent) { - return false - } - const index = parent.items.indexOf(track) - const previousItem = parent.items[index - 1] - if (!previousItem) { - return false - } - this.queueGrouplikeItem(previousItem, 'FRONT') - queueIndex = 0 - } else { - return false - } - } + const counter = await this.backend.processMetadata(item) - this.playGrouplikeItem(queue.items[queueIndex]) - return true - } + const tracksMsg = (counter === 1) ? '1 track' : `${counter} tracks` + this.metadataStatusLabel.text = `Done processing metadata of ${tracksMsg}!` - clearPlayingTrack() { - this.playingTrack = null - this.stopPlaying() - this.playbackInfoElement.clearInfo() - this.updateQueueLengthLabel() + this.clearMetadataStatusTimeout = setTimeout(() => { + this.clearMetadataStatusTimeout = null + this.metadataStatusLabel.text = '' + this.metadataStatusLabel.visible = false + this.fixLayout() + }, 3000) } updateQueueLengthLabel() { - const { items } = this.queueGrouplike + const { playingTrack } = this.backend + const { items } = this.backend.queueGrouplike let noticedMissingMetadata = false const durationFn = (acc, track) => { - const metadata = this.getMetadataFor(track) + const metadata = this.backend.getMetadataFor(track) if (!metadata) noticedMissingMetadata = true return acc + (metadata && metadata.duration) || 0 } let trackRemainSec = 0 - if (this.playingTrack) { + if (playingTrack) { const { curSecTotal = 0, lenSecTotal = 0 } = this.playbackInfoElement.timeData trackRemainSec = lenSecTotal - curSecTotal } @@ -1190,8 +823,8 @@ class AppElement extends FocusElement { // info element). let index = 0 - if (this.playingTrack) { - index = items.indexOf(this.playingTrack) + if (playingTrack) { + index = items.indexOf(playingTrack) // If it's NOT counted by the playback info element's time data yet, // we skip this - the current track is counted as "ahead" and its // duration will be tallied like the rest of the "ahead" tracks. @@ -1206,7 +839,7 @@ class AppElement extends FocusElement { const { duration } = getTimeStringsFromSec(0, totalRemainSec) - this.queueLengthLabel.text = (this.playingTrack && items.includes(this.playingTrack) + this.queueLengthLabel.text = (playingTrack && items.includes(playingTrack) ? `(${this.playSymbol} ${index} / ${items.length})` : `(${items.length})`) @@ -1219,66 +852,10 @@ class AppElement extends FocusElement { this.queueTimeLabel.y = this.paneRight.contentH - 1 } - 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)) - } - - async processMetadata(item, reprocess = false, top = true) { - if (top) { - this.metadataStatusLabel.text = 'Processing metadata...' - this.metadataStatusLabel.visible = true - this.fixLayout() - } - - if (isGroup(item)) { - await Promise.all(item.items.map(x => this.processMetadata(x, reprocess, false))) - } else process: { - if (!reprocess && this.getMetadataFor(item)) { - break process - } - - await this.throttleMetadata(async () => { - const filePath = await this.downloadGrouplikeItem(item) - const metadataReader = getMetadataReaderFor(filePath) - const data = await metadataReader(filePath) - - this.metadataDictionary[item.downloaderArg] = filePath - this.metadataDictionary[filePath] = data - }) - - this.metadataStatusLabel.text = `Processing metadata - ${this.throttleMetadata.queue.length} to go.` - } - - if (top) { - this.metadataStatusLabel.text = '' - this.metadataStatusLabel.visible = false - this.fixLayout() - await this.saveMetadata() - } - } - - getMetadataFor(item) { - const key = this.metadataDictionary[item.downloaderArg] - return this.metadataDictionary[key] || null - } - get playSymbol() { - if (this.player && this.playingTrack) { - if (this.player.isPaused) { + const { player, playingTrack } = this.backend + if (player && playingTrack) { + if (player.isPaused) { return '⏸' } else { return '▶' @@ -1403,7 +980,7 @@ class GrouplikeListingElement extends Form { } else if (telc.isCharacter(keyBuf, 'G')) { this.form.selectAndShow(this.grouplike.items[this.grouplike.items.length - 1]) } else if (keyBuf[0] === 12 && this.grouplike.isTheQueue) { // ctrl-L - this.form.selectAndShow(this.app.playingTrack) + this.form.selectAndShow(this.app.backend.playingTrack) } else { return super.keyPressed(keyBuf) } @@ -2087,7 +1664,7 @@ class InteractiveGrouplikeItemElement extends BasicGrouplikeItemElement { drawTo(writable) { if (!this.hideMetadata) { - const metadata = this.app.getMetadataFor(this.item) + const metadata = this.app.backend.getMetadataFor(this.item) if (metadata) { const durationString = getTimeStringsFromSec(0, metadata.duration).duration this.rightText = ` (${durationString}) ` @@ -2149,7 +1726,7 @@ class InteractiveGrouplikeItemElement extends BasicGrouplikeItemElement { const braille = '⠈⠐⠠⠄⠂⠁' const brailleChar = braille[Math.floor(Date.now() / 250) % 6] - const record = this.app.recordStore.getRecord(this.item) + const record = this.app.backend.getRecordFor(this.item) if (this.isMarked) { writable.write('M') @@ -2161,7 +1738,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.playingTrack === this.item) { + } else if (this.app.backend.playingTrack === this.item) { writable.write('\u25B6') } else { writable.write(' ') @@ -3071,4 +2648,4 @@ class Menubar extends ListScrollForm { } } -module.exports.AppElement = AppElement +module.exports = AppElement |