diff --git a/internal/server/server.go b/internal/server/server.go index df62fc5..f87106d 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -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()) }