|
|
|
@@ -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()) |
|
|
|
} |
|
|
|
|
|
|
|
|