« 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--todo.txt6
-rw-r--r--ui.js100
2 files changed, 100 insertions, 6 deletions
diff --git a/todo.txt b/todo.txt
index f8202c2..fa437df 100644
--- a/todo.txt
+++ b/todo.txt
@@ -607,3 +607,9 @@ TODO: Apparently, seeking to a timestamp under a previous track in the queue
 
 TODO: Next/previous buttons should seek between timestamps if there are more
       within the same track.
+      (Done!)
+
+TODO: Should skipping back to a previous track with timestamps automatically
+      seek to the final timestamp within that track? I'm undecided, but at the
+      moment leaning *slightly* towards "no". I may be biased due to it is
+      harder to code that behavior though! :P
diff --git a/ui.js b/ui.js
index b67fc8d..bc7f68d 100644
--- a/ui.js
+++ b/ui.js
@@ -1117,6 +1117,27 @@ class AppElement extends FocusElement {
     return this.timestampDictionary.get(item) || null
   }
 
+  getTimestampAtSec(item, sec) {
+    const timestampData = this.getTimestampData(item)
+    if (!timestampData) {
+      return null
+    }
+
+    // Just like, start from the end, man.
+    // Why doesn't JavaScript have a findIndexFromEnd function???
+    for (let i = timestampData.length - 1; i >= 0; i--) {
+      const ts = timestampData[i];
+      if (
+        ts.timestamp <= sec &&
+        ts.timestampEnd >= sec
+      ) {
+        return ts
+      }
+    }
+
+    return null
+  }
+
   async readTimestampData(item) {
     const file = this.getTimestampsFile(item)
 
@@ -1217,19 +1238,86 @@ class AppElement extends FocusElement {
     // all are before this position, skip to previous.
 
     let maxCurSec = 0
-    this.forEachQueuePlayerToActOn(({ timeData }) => {
-      if (timeData) {
-        maxCurSec = Math.max(maxCurSec, timeData.curSecTotal)
+    this.forEachQueuePlayerToActOn(qp => {
+      if (qp.timeData) {
+        let effectiveCurSec = qp.timeData.curSecTotal
+
+        const ts = this.getTimestampAtSec(qp.playingTrack, qp.timeData.curSecTotal)
+        if (ts) {
+          effectiveCurSec -= ts.timestamp
+        }
+
+        maxCurSec = Math.max(maxCurSec, effectiveCurSec)
       }
     })
 
     if (Math.floor(maxCurSec) < this.config.seekToStartThreshold) {
-      this.actOnQueuePlayers(qp => qp.playPrevious(qp.playingTrack, true))
+      this.skipBack()
     } else {
-      this.actOnQueuePlayers(qp => qp.seekToStart())
+      this.seekToStart()
     }
   }
 
+  seekToStart() {
+    this.actOnQueuePlayers(qp => qp.seekToStart())
+    this.actOnQueuePlayers(qp => {
+      if (!qp.playingTrack) {
+        return
+      }
+
+      const ts = this.getTimestampAtSec(qp.playingTrack, qp.timeData.curSecTotal)
+      if (ts) {
+        qp.seekTo(ts.timestamp)
+        return
+      }
+
+      qp.seekToStart()
+    })
+  }
+
+  skipBack() {
+    this.actOnQueuePlayers(qp => {
+      if (!qp.playingTrack) {
+        return
+      }
+
+      const ts = this.getTimestampAtSec(qp.playingTrack, qp.timeData.curSecTotal)
+      if (ts) {
+        const timestampData = this.getTimestampData(qp.playingTrack)
+        const playingTimestampIndex = timestampData.indexOf(ts)
+        const previous = timestampData[playingTimestampIndex - 1]
+        if (previous) {
+          qp.seekTo(previous.timestamp)
+          return
+        }
+      }
+
+      qp.playPrevious(qp.playingTrack, true)
+    })
+  }
+
+  skipAhead() {
+    this.actOnQueuePlayers(qp => {
+      if (!qp.playingTrack) {
+        return
+      }
+
+      const ts = this.getTimestampAtSec(qp.playingTrack, qp.timeData.curSecTotal)
+
+      if (ts) {
+        const timestampData = this.getTimestampData(qp.playingTrack)
+        const playingTimestampIndex = timestampData.indexOf(ts)
+        const next = timestampData[playingTimestampIndex + 1]
+        if (next) {
+          qp.seekTo(next.timestamp)
+          return
+        }
+      }
+
+      qp.playNext(qp.playingTrack, true)
+    })
+  }
+
   actOnQueuePlayers(fn) {
     this.forEachQueuePlayerToActOn(queuePlayer => {
       fn(queuePlayer)
@@ -1610,7 +1698,7 @@ class AppElement extends FocusElement {
       } else if (input.isSkipBack(keyBuf)) {
         this.skipBackOrSeekToStart()
       } else if (input.isSkipAhead(keyBuf)) {
-        this.actOnQueuePlayers(qp => qp.playNext(qp.playingTrack, true))
+        this.skipAhead()
       }
     }