package jellyfin import ( "errors" "net/http" "os" "path" "path/filepath" "strings" "github.com/mithral/vellum/internal/auth" "github.com/mithral/vellum/internal/catalog" "github.com/mithral/vellum/internal/httpx" "github.com/mithral/vellum/internal/stream" ) // handlePlaybackInfo answers GET /Items/{itemId}/PlaybackInfo. // // The response always describes the one thing Vellum can do — direct play — and // reports SupportsTranscoding=false. If the client has disabled both direct // play and direct stream it is asking for a transcode; that is refused with // ErrorCode "NoCompatibleStream" and a warning, rather than by handing back a // TranscodingUrl that would not exist. func (h *Handlers) handlePlaybackInfo(w http.ResponseWriter, r *http.Request, u auth.User) { id, isLib, ok := decodeID(r.PathValue("itemId")) if !ok || isLib { httpx.WriteError(w, http.StatusNotFound, "item not found") return } it, ok := h.authorizedItem(w, r, u, id) if !ok { return } f, err := h.firstFile(r.Context(), id) if err != nil { h.fail(w, "load file facts", err) return } q := r.URL.Query() directPlay := queryBoolDefault(q.Get("EnableDirectPlay"), true) directStream := queryBoolDefault(q.Get("EnableDirectStream"), true) src := buildMediaSource(it, f, tokenFromRequest(r)) resp := PlaybackInfoResponse{MediaSources: []MediaSourceInfo{src}} if !directPlay && !directStream { resp.ErrorCode = "NoCompatibleStream" h.log.Warn("jellyfin client requires transcoding; refusing", "item", it.ID, "kind", it.Kind) } httpx.WriteJSON(w, http.StatusOK, resp) } // handleVideoStream serves a video item's file. func (h *Handlers) handleVideoStream(w http.ResponseWriter, r *http.Request, u auth.User) { h.serveStream(w, r, u) } // handleAudioStream serves an audio (or other non-video) item's file. Jellyfin // clients fetch audiobook and music tracks here. func (h *Handlers) handleAudioStream(w http.ResponseWriter, r *http.Request, u auth.User) { h.serveStream(w, r, u) } // serveStream streams the item's first file through internal/stream, so Range // requests (seeking) work exactly as they do for the web app. Auth already came // from tokenFromRequest, which includes the ?api_key= form that