« get me outta code hell

mtui - Music Text User Interface - user-friendly command line music player
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--backend.js9
-rw-r--r--ui.js180
2 files changed, 156 insertions, 33 deletions
diff --git a/backend.js b/backend.js
index e2213d5..e38fe2f 100644
--- a/backend.js
+++ b/backend.js
@@ -85,8 +85,9 @@ class QueuePlayer extends EventEmitter {
 
     this.player.on('printStatusLine', data => {
       if (this.playingTrack) {
+        const oldTimeData = this.timeData
         this.timeData = data
-        this.emit('received time data', data, this)
+        this.emit('received time data', data, oldTimeData, this)
       }
     })
 
@@ -368,7 +369,7 @@ class QueuePlayer extends EventEmitter {
   }
 
 
-  async play(item, startTime) {
+  async play(item, startTime = 0) {
     if (this.player === null) {
       throw new Error('Attempted to play before a player was loaded')
     }
@@ -415,7 +416,7 @@ class QueuePlayer extends EventEmitter {
 
       this.timeData = null
       this.playingTrack = item
-      this.emit('playing', this.playingTrack, oldTrack, this)
+      this.emit('playing', this.playingTrack, oldTrack, startTime, this)
 
       await this.player.kill()
       if (this.playedTrackToEnd) {
@@ -536,7 +537,7 @@ class QueuePlayer extends EventEmitter {
       const oldTrack = this.playingTrack
       this.playingTrack = null
       this.timeData = null
-      this.emit('playing', null, oldTrack, this)
+      this.emit('playing', null, oldTrack, 0, this)
     }
   }
 
diff --git a/ui.js b/ui.js
index bc7f68d..a5481c8 100644
--- a/ui.js
+++ b/ui.js
@@ -536,7 +536,7 @@ class AppElement extends FocusElement {
     this.updateQueueLengthLabel()
   }
 
-  async handlePlaying(track, oldTrack, queuePlayer) {
+  async handlePlaying(track, oldTrack, startTime, queuePlayer) {
     const PIE = this.getPlaybackInfoElementForQueuePlayer(queuePlayer)
     if (PIE) {
       PIE.updateTrack()
@@ -544,6 +544,7 @@ class AppElement extends FocusElement {
 
     if (queuePlayer === this.SQP) {
       this.updateQueueLengthLabel()
+      this.queueListingElement.collapseTimestamps(oldTrack)
       if (track && this.queueListingElement.currentItem === oldTrack) {
         this.queueListingElement.selectAndShow(track)
       }
@@ -553,12 +554,9 @@ class AppElement extends FocusElement {
     // the containing queue isn't of the selected queue player.
     const timestampData = track && this.getTimestampData(track)
     if (timestampData && queuePlayer === this.SQP) {
-      this.queueListingElement.expandTimestamps(track)
-    }
-
-    const oldTimestampData = oldTrack && this.getTimestampData(oldTrack)
-    if (oldTimestampData && queuePlayer === this.SQP) {
-      this.queueListingElement.collapseTimestamps(oldTrack)
+      if (this.queueListingElement.currentItem === track) {
+        this.queueListingElement.selectTimestampAtSec(track, startTime)
+      }
     }
 
     if (track && this.enableAutoDJ) {
@@ -573,7 +571,7 @@ class AppElement extends FocusElement {
     }
   }
 
-  handleReceivedTimeData(data, queuePlayer) {
+  handleReceivedTimeData(timeData, oldTimeData, queuePlayer) {
     const PIE = this.getPlaybackInfoElementForQueuePlayer(queuePlayer)
     if (PIE) {
       PIE.updateProgress()
@@ -581,6 +579,7 @@ class AppElement extends FocusElement {
 
     if (queuePlayer === this.SQP) {
       this.updateQueueLengthLabel()
+      this.updateQueueSelection(timeData, oldTimeData)
     }
   }
 
@@ -1920,11 +1919,11 @@ class AppElement extends FocusElement {
     const { playingTrack, timeData } = this.SQP
     const { items } = this.SQP.queueGrouplike
     const {
-      currentInput: selectedInput,
+      currentInput: currentInput,
       currentItem: selectedTrack
     } = this.queueListingElement
 
-    const isTimestamp = (selectedInput instanceof TimestampGrouplikeItemElement)
+    const isTimestamp = (currentInput instanceof TimestampGrouplikeItemElement)
 
     let trackRemainSec = 0
     let trackPassedSec = 0
@@ -1962,7 +1961,7 @@ class AppElement extends FocusElement {
       durationSymbol = ''
     } else if (
       selectedIndex === playingIndex &&
-      (!isTimestamp || selectedInput.isCurrentTimestamp)
+      (!isTimestamp || currentInput.isCurrentTimestamp)
     ) {
       // Remaining length of the queue.
       if (timeData) {
@@ -1975,7 +1974,7 @@ class AppElement extends FocusElement {
       durationSymbol = ''
     } else if (
       selectedIndex < playingIndex ||
-      (isTimestamp && selectedInput.data.timestamp <= trackPassedSec)
+      (isTimestamp && currentInput.data.timestamp <= trackPassedSec)
     ) {
       // Time since the selected track ended.
       durationRange = items.slice(selectedIndex + 1, playingIndex)
@@ -1985,11 +1984,11 @@ class AppElement extends FocusElement {
         if (selectedIndex < playingIndex) {
           durationRange.unshift(items[selectedIndex])
         }
-        durationAdd -= selectedInput.data.timestampEnd
+        durationAdd -= currentInput.data.timestampEnd
       }
     } else if (
       selectedIndex > playingIndex ||
-      (isTimestamp && selectedInput.data.timestamp > trackPassedSec)
+      (isTimestamp && currentInput.data.timestamp > trackPassedSec)
     ) {
       // Time until the selected track begins.
       if (timeData) {
@@ -2005,7 +2004,7 @@ class AppElement extends FocusElement {
         durationAdd = 0
       }
       if (isTimestamp) {
-        durationAdd += selectedInput.data.timestamp
+        durationAdd += currentInput.data.timestamp
       }
       durationSymbol = '+'
     }
@@ -2041,7 +2040,7 @@ class AppElement extends FocusElement {
       let timestampPart
 
       if (isTimestamp && selectedIndex === playingIndex) {
-        const selectedTimestampIndex = timestampData.indexOf(selectedInput.data)
+        const selectedTimestampIndex = timestampData.indexOf(currentInput.data)
 
         const found = timestampData.findIndex(ts => ts.timestamp > trackPassedSec)
         const playingTimestampIndex = (found >= 0 ? found - 1 : 0)
@@ -2081,6 +2080,53 @@ class AppElement extends FocusElement {
     this.queueTimeLabel.y = this.queuePane.contentH - 1
   }
 
+  updateQueueSelection(timeData, oldTimeData) {
+    if (!timeData) {
+      return
+    }
+
+    const { playingTrack } = this.SQP
+    const { form } = this.queueListingElement
+    const { currentInput } = form
+
+    if (!currentInput || currentInput.item !== playingTrack) {
+      return
+    }
+
+    const timestamps = this.getTimestampData(playingTrack)
+
+    if (!timestamps) {
+      return
+    }
+
+    const tsOld = oldTimeData &&
+      this.getTimestampAtSec(playingTrack, oldTimeData.curSecTotal)
+    const tsNew =
+      this.getTimestampAtSec(playingTrack, timeData.curSecTotal)
+
+    if (
+      tsNew !== tsOld &&
+      currentInput instanceof TimestampGrouplikeItemElement &&
+      currentInput.data === tsOld
+    ) {
+      const index = form.inputs.findIndex(el => (
+        el.item === playingTrack &&
+        el instanceof TimestampGrouplikeItemElement &&
+        el.data === tsNew
+      ))
+
+      if (index === -1) {
+        return
+      }
+
+      form.curIndex = index
+      if (form.isSelected) {
+        form.updateSelectedElement()
+      }
+      form.scrollSelectedElementIntoView()
+    }
+  }
+
   get SQP() {
     // Just a convenient shorthand.
     return this.selectedQueuePlayer
@@ -2336,6 +2382,14 @@ class GrouplikeListingElement extends Form {
       if (!ET.includes(item)) {
         this.expandedTimestamps.push(item)
         this.buildTimestampItems()
+
+        if (this.currentItem === item) {
+          if (this.isSelected) {
+            this.form.selectInput(this.form.inputs[this.form.curIndex + 1])
+          } else {
+            this.form.curIndex += 1
+          }
+        }
       }
     }
   }
@@ -2343,8 +2397,20 @@ class GrouplikeListingElement extends Form {
   collapseTimestamps(item) {
     const ET = this.expandedTimestamps // :alien:
     if (ET.includes(item)) {
+      const restore = (this.currentItem === item)
+
       ET.splice(ET.indexOf(item), 1)
       this.buildTimestampItems()
+
+      if (restore) {
+        const { form } = this
+        const index = form.inputs.findIndex(inp => inp.item === item)
+        form.curIndex = index
+        if (form.isSelected) {
+          form.updateSelectedElement()
+        }
+        form.scrollSelectedElementIntoView()
+      }
     }
   }
 
@@ -2361,6 +2427,30 @@ class GrouplikeListingElement extends Form {
     return this.expandedTimestamps.includes(item)
   }
 
+  selectTimestampAtSec(item, sec) {
+    this.expandTimestamps(item)
+
+    const { form } = this
+    let index = form.inputs.findIndex(el => (
+      el.item === item &&
+      el instanceof TimestampGrouplikeItemElement &&
+      el.data.timestamp >= sec
+    ))
+
+    if (index === -1) {
+      index = form.inputs.findIndex(el => el.item === item)
+      if (index === -1) {
+        return
+      }
+    }
+
+    form.curIndex = index
+    if (form.isSelected) {
+      form.updateSelectedElement()
+    }
+    form.scrollSelectedElementIntoView()
+  }
+
   updateTimestamps() {
     const ET = this.expandedTimestamps
     if (ET) {
@@ -2368,13 +2458,43 @@ class GrouplikeListingElement extends Form {
     }
   }
 
-  buildTimestampItems(item) {
-    const form = this.form
+  restoreSelectedInput(restoreInput) {
+    const { form } = this
+    const { inputs, currentInput } = form
+
+    if (currentInput === restoreInput) {
+      return
+    }
+
+    let inputToSelect
+
+    if (inputs.includes(restoreInput)) {
+      inputToSelect = restoreInput
+    } else if (restoreInput instanceof InteractiveGrouplikeItemElement) {
+      inputToSelect = inputs.find(input =>
+        input.item === restoreInput.item &&
+        input instanceof InteractiveGrouplikeItemElement
+      )
+    } else if (restoreInput instanceof TimestampGrouplikeItemElement) {
+      inputToSelect = inputs.find(input =>
+        input.data === restoreInput.data &&
+        input instanceof TimestampGrouplikeItemElement
+      )
+    }
+
+    if (!inputToSelect) {
+      return
+    }
+
+    form.curIndex = inputs.indexOf(inputToSelect)
+    if (form.isSelected) {
+      form.updateSelectedElement()
+    }
+    form.scrollSelectedElementIntoView()
+  }
 
-    // We're going to restore this selection later. It's kinda hacky and won't
-    // work if the selected input was itself a timestamp item, but that
-    // [extremely RFC voice] hopefully won't happen!
-    const selectedInput = this.form.inputs[this.form.curIndex]
+  buildTimestampItems(restoreInput = this.currentInput) {
+    const form = this.form
 
     // Clear up any existing timestamp items, since we're about to generate new
     // ones!
@@ -2428,11 +2548,7 @@ class GrouplikeListingElement extends Form {
       }
     }
 
-    const index = form.inputs.indexOf(selectedInput)
-    if (index >= 0) {
-      form.selectInput(form.inputs.indexOf(selectedInput))
-    }
-
+    this.restoreSelectedInput(restoreInput)
     this.scheduleDrawWithoutPropertyChange()
     this.fixAllLayout()
   }
@@ -2444,6 +2560,7 @@ class GrouplikeListingElement extends Form {
 
     this.commentLabel.text = this.grouplike.comment || ''
 
+    const restoreInput = this.form.currentInput
     const wasSelected = this.isSelected
     const form = this.form
 
@@ -2504,11 +2621,12 @@ class GrouplikeListingElement extends Form {
       }
     }
 
+    this.buildTimestampItems(restoreInput)
+
     // Just to make the selected-track-info bar fill right away (if it wasn't
     // already filled by a previous this.curIndex set).
     form.curIndex = form.curIndex
 
-    this.buildTimestampItems()
     this.fixAllLayout()
   }
 
@@ -2643,7 +2761,7 @@ class GrouplikeListingElement extends Form {
   }
 
   get currentInput() {
-    return this.form.inputs[this.form.curIndex] || null
+    return this.form.currentInput
   }
 }
 
@@ -2685,6 +2803,10 @@ class GrouplikeListingForm extends ListScrollForm {
     return Math.max(0, this.inputs.findIndex(el => el instanceof InteractiveGrouplikeItemElement))
   }
 
+  get currentInput() {
+    return this.inputs[this.curIndex]
+  }
+
   selectAndShow(item) {
     const index = this.inputs.findIndex(inp => inp.item === item)
     if (index >= 0) {