Przeglądaj źródła

fix: resume — save on stop-while-playing, restore to correct track

- Extract saveResumeState helper: saves when playing OR paused (not just
  paused). Both WS and REST stop handlers now call it.
- restoreResume now jumps to saved PlaylistPos before seeking, so the
  correct track is loaded even when playlist order differs.
- Title validation: if the loaded title does not match the window title
  after jumping, abort restore and delete stale resume file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
master
Jan Svabenik 1 miesiąc temu
rodzic
commit
f1007d4540
1 zmienionych plików z 42 dodań i 18 usunięć
  1. +42
    -18
      internal/server/server.go

+ 42
- 18
internal/server/server.go Wyświetl plik

@@ -200,14 +200,7 @@ func (s *Server) handleCommand(raw []byte) {
case "pause":
s.wa.Pause()
case "stop":
if s.wa.IsPaused() {
_ = resume.Save(s.cfg.ResumeFile, resume.State{
PlaylistLength: s.wa.GetPlaylistLength(),
PlaylistPos: s.wa.GetPlaylistPosition(),
OffsetSeconds: s.wa.GetPosition(),
TrackTitle: s.wa.GetTitle(),
})
}
s.saveResumeState()
s.wa.Stop()
case "next":
s.wa.NextTrack()
@@ -313,6 +306,25 @@ func (s *Server) killChecker() {
}
}

// saveResumeState persists the current playback position when a track is
// active (playing or paused). Safe to call unconditionally before Stop.
func (s *Server) saveResumeState() {
state := s.wa.PlayState()
if state != 1 && state != 3 { // not playing and not paused
return
}
title := s.wa.GetTitle()
if title == "" {
return
}
_ = resume.Save(s.cfg.ResumeFile, resume.State{
PlaylistLength: s.wa.GetPlaylistLength(),
PlaylistPos: s.wa.GetPlaylistPosition(),
OffsetSeconds: s.wa.GetPosition(),
TrackTitle: title,
})
}

func (s *Server) restoreResume() {
deadline := time.Now().Add(30 * time.Second)
for time.Now().Before(deadline) {
@@ -326,14 +338,33 @@ func (s *Server) restoreResume() {
return
}
if s.wa.GetPlaylistLength() != st.PlaylistLength {
log.Printf("resume: playlist length mismatch (saved %d, got %d) — aborting",
st.PlaylistLength, s.wa.GetPlaylistLength())
_ = resume.Delete(s.cfg.ResumeFile)
return
}
s.wa.Play()
// Jump to the saved track (JumpToTrack is 0-based, also starts playback).
if st.PlaylistPos > 0 {
s.wa.JumpToTrack(st.PlaylistPos - 1)
time.Sleep(300 * time.Millisecond) // give Winamp a moment to load
// Validate title to catch same-length playlists with different content.
if st.TrackTitle != "" {
if got := s.wa.GetTitle(); got != "" && got != st.TrackTitle {
log.Printf("resume: title mismatch (expected %q, got %q) — aborting",
st.TrackTitle, got)
s.wa.Stop()
_ = resume.Delete(s.cfg.ResumeFile)
return
}
}
} else {
s.wa.Play()
}
s.wa.Seek(st.OffsetSeconds)
s.wa.Pause()
_ = resume.Delete(s.cfg.ResumeFile)
log.Printf("resume: restored %q at %ds", st.TrackTitle, st.OffsetSeconds)
log.Printf("resume: restored %q at %ds (playlist pos %d)",
st.TrackTitle, st.OffsetSeconds, st.PlaylistPos)
}

// ── REST handlers (for curl/debug) ───────────────────────────────────────────
@@ -354,14 +385,7 @@ func (s *Server) handleNext(w http.ResponseWriter, r *http.Request) { jsonOK(w,
func (s *Server) handlePrev(w http.ResponseWriter, r *http.Request) { jsonOK(w, s.wa.PrevTrack()) }

func (s *Server) handleStop(w http.ResponseWriter, r *http.Request) {
if s.wa.IsPaused() {
_ = resume.Save(s.cfg.ResumeFile, resume.State{
PlaylistLength: s.wa.GetPlaylistLength(),
PlaylistPos: s.wa.GetPlaylistPosition(),
OffsetSeconds: s.wa.GetPosition(),
TrackTitle: s.wa.GetTitle(),
})
}
s.saveResumeState()
jsonOK(w, s.wa.Stop())
}



Ładowanie…
Anuluj
Zapisz