feat(server): stream URL gets file extension so Sonos can probe duration
test-go / test (push) Successful in 37s
test-go / integration (push) Failing after 10m34s

This commit is contained in:
2026-06-04 07:29:28 -04:00
parent aa23a72693
commit 27bd38e005
2 changed files with 36 additions and 1 deletions
+32 -1
View File
@@ -57,6 +57,33 @@ func mimeForFormat(format string) string {
}
}
// extForFormat maps the tracks.file_format column to a path-safe file
// extension. Sonos firmware gates duration probing on the URL path
// extension (Content-Type header alone is insufficient) -- without a
// recognizable extension, Sonos reports TrackDuration=0 and seeks
// trigger auto-advance because every position past 0 looks past-the-
// end. Defaults to "mp3" for unknown formats. See task #610.
func extForFormat(format string) string {
switch strings.ToLower(strings.TrimSpace(format)) {
case "mp3", "mpeg":
return "mp3"
case "flac":
return "flac"
case "aac":
return "aac"
case "m4a", "mp4":
return "m4a"
case "ogg", "vorbis":
return "ogg"
case "opus":
return "opus"
case "wav", "wave":
return "wav"
default:
return "mp3"
}
}
// handleCastStreamToken issues a short-lived HMAC stream token for the
// given trackId. Authenticated via the standard session cookie / bearer.
//
@@ -114,8 +141,12 @@ func (h *handlers) handleCastStreamToken(w http.ResponseWriter, r *http.Request)
if h := r.Header.Get("X-Forwarded-Host"); h != "" {
host = h
}
// Include the file extension in the path so Sonos's URL probe sees a
// recognizable audio file. Without it, Sonos reports TrackDuration=0
// and seeks past 0s land "after the end" -> early track-skip.
url := scheme + "://" + host + "/api/tracks/" + req.TrackID +
"/stream?token=" + token + "&exp=" + strconv.FormatInt(exp, 10)
"/stream." + extForFormat(track.FileFormat) +
"?token=" + token + "&exp=" + strconv.FormatInt(exp, 10)
writeJSON(w, http.StatusOK, castTokenResponse{
Token: token,