diff --git a/internal/api/cast_token.go b/internal/api/cast_token.go index 5fdf14f8..37751150 100644 --- a/internal/api/cast_token.go +++ b/internal/api/cast_token.go @@ -56,11 +56,25 @@ func (h *handlers) handleCastStreamToken(w http.ResponseWriter, r *http.Request) exp := time.Now().Add(time.Duration(expSec) * time.Second).Unix() token := SignStreamToken(h.streamSecret, req.TrackID, exp) + // Behind a TLS-terminating reverse proxy, r.TLS is nil even though + // the public-facing URL is https://. UPnP devices (Sonos especially) + // reject SetAVTransportURI with error 714 (IllegalMimeType) when + // they hit an http:// URL that immediately 301s to https:// — the + // MIME probe fails to find an audio body. Honor X-Forwarded-Proto + + // X-Forwarded-Host first so the URL we hand to the speaker reaches + // it on the same scheme/host the client used. Falls back to + // r.TLS-based detection for direct (no-proxy) deployments. scheme := "http" - if r.TLS != nil { + if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" { + scheme = proto + } else if r.TLS != nil { scheme = "https" } - url := scheme + "://" + r.Host + "/api/tracks/" + req.TrackID + + host := r.Host + if h := r.Header.Get("X-Forwarded-Host"); h != "" { + host = h + } + url := scheme + "://" + host + "/api/tracks/" + req.TrackID + "/stream?token=" + token + "&exp=" + strconv.FormatInt(exp, 10) writeJSON(w, http.StatusOK, castTokenResponse{Token: token, Exp: exp, URL: url})