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.
This commit is contained in:
2026-08-12 01:17:54 -04:00
parent 55aa3ea5b0
commit df11ef23f4
6 changed files with 516 additions and 16 deletions
+22
View File
@@ -43,6 +43,28 @@ func (q *Queue) emitModeChanged() {
)
}
// emitPlaybackFailed tells the frontend that a track could not be
// played. Before this existed the failure was logged, the index was
// reverted and nothing reached the user: a moved file was a button
// that did nothing, twice, forever (errors.C1).
func (q *Queue) emitPlaybackFailed(track Track, reason error) {
msg := ""
if reason != nil {
msg = reason.Error()
}
events.Emit(
q.ctx,
events.PlaybackFailed,
PlaybackFailure{
FilePath: track.FilePath,
Title: track.Title,
Artist: track.Artist,
Reason: msg,
},
)
}
// emitTracksModified emits a delta update for track list changes.
func (q *Queue) emitTracksModified(
action string,