Files
yellowjacket/backend/queue/handlers.go
T
logan df11ef23f4 feat(player): report the real position, and skip an unplayable track
The seek bar was a setInterval counter reconciled only on track
change: measured 3 s behind during steady playback and 30 s behind
after four keyboard seeks, because the seek shortcut never told it.
And `loadCurrentTrack`/`playCurrentTrack` logged, returned false and
emitted nothing, so double-clicking a moved file did nothing, twice,
forever — while auto-advance onto a bad file stopped playback dead.

- A 1 Hz position ticker while playing, plus an immediate report on
  load, play, pause, seek and natural finish. The payload carries a
  `trackChangeId` (the store is a singleton, so a bar mounting later
  must not adopt a report about the previous track) and a `seq` (the
  same second reported twice still has to reset interpolation).
- `PlaybackFailed` from both failure paths, and `playCurrentOrSkip`
  steps over tracks that will not load — bounded by the queue length,
  so a disconnected drive stops after one pass instead of spinning
  through a RepeatAll wrap. `PlayIndex` still reverts: the user picked
  that track.
- `SeekFailed` is emitted when the seek itself fails, not only when
  nothing is loaded, and is followed by a position report so the
  optimistic move is taken back by the mechanism that fixed the drift.
- A queue that simply ran out no longer unloads the player, so the
  finished track stays on the bar at 0:00.
2026-08-12 01:17:54 -04:00

56 lines
1.2 KiB
Go

package queue
// OnPlaybackFinished is called when a track finishes playing naturally.
// This drives the auto-advance behavior and records the play.
func (q *Queue) OnPlaybackFinished() {
q.mu.Lock()
if len(q.tracks) == 0 {
q.mu.Unlock()
return
}
// Capture the track that just finished before advancing.
finishedID := q.tracks[q.currentIndex].AudioFileID
// Repeat One: replay the current track.
if q.repeatMode == RepeatOne {
if q.playCurrentTrack() {
q.emitIndexChanged()
}
q.mu.Unlock()
q.recordPlay(finishedID)
return
}
nextIdx := q.nextIndex()
if nextIdx == -1 {
// Queue exhausted — this is the extension point for a future fallback playlist.
q.onQueueExhausted(false)
q.mu.Unlock()
q.recordPlay(finishedID)
return
}
q.currentIndex = nextIdx
// Skip over tracks that cannot be played instead of reverting.
// Reverting stopped playback dead on the first moved file and left
// Next unable to get past it, since Next hit the same track.
if !q.playCurrentOrSkip(true, q.nextIndex) {
q.onQueueExhausted(false)
q.mu.Unlock()
q.recordPlay(finishedID)
return
}
q.emitIndexChanged()
q.mu.Unlock()
q.recordPlay(finishedID)
}