0.2.68
This commit is contained in:
@@ -94,8 +94,12 @@ type playbackReportResponse struct {
|
||||
// Emby's own item JSON, forwarded verbatim like every other item the gateway returns, so
|
||||
// the client decodes it into the same BaseItem it uses everywhere else.
|
||||
type nextEpisodeResponse struct {
|
||||
Item json.RawMessage `json:"item"`
|
||||
Title string `json:"title"`
|
||||
Item json.RawMessage `json:"item"`
|
||||
Title string `json:"title"`
|
||||
// StreamReady says whether this answer carries a negotiated stream. A client asking
|
||||
// with `stream=0` gets identity, artwork and runtime and nothing that touches Emby's
|
||||
// PlaybackInfo — see [handleNextEpisode] for why that separation exists.
|
||||
StreamReady bool `json:"streamReady"`
|
||||
URL string `json:"url"`
|
||||
ResumePositionMs int64 `json:"resumePositionMs"`
|
||||
Subtitles []playableSubtitle `json:"subtitles"`
|
||||
@@ -302,6 +306,20 @@ func episodeCode(item emby.Summary) string {
|
||||
//
|
||||
// "Nothing follows this" is a normal answer, not a failure: a movie, a series finale and an
|
||||
// unreadable series all come back as 404 and the client simply shows no banner.
|
||||
//
|
||||
// It answers in two shapes, and the separation is the whole reason auto-advance stopped
|
||||
// showing a viewer the machinery. `stream=0` asks for **identity only** — the episode's
|
||||
// item JSON, its title and its artwork — and touches nothing but the episode list. The
|
||||
// full answer additionally negotiates PlaybackInfo, which mints a play session and, on a
|
||||
// title that has to be transcoded, a transcode session at Emby.
|
||||
//
|
||||
// The television asks for the cheap shape when the episode it is watching *starts*, which
|
||||
// is where the banner's picture and wording come from, and for the full one shortly before
|
||||
// the hand-over. Doing both at once is what the player used to do, and it was wrong twice:
|
||||
// a session negotiated forty minutes early has long since been torn down by the time it is
|
||||
// used — so the transition failed, retried, and narrated all of it to the viewer — and the
|
||||
// transcode it may have started sat there being paid for by a viewer who had not yet
|
||||
// decided to watch it.
|
||||
func (s *Server) handleNextEpisode(w http.ResponseWriter, r *http.Request, sess store.Session) {
|
||||
ctx := r.Context()
|
||||
itemID := r.PathValue("id")
|
||||
@@ -355,6 +373,27 @@ func (s *Server) handleNextEpisode(w http.ResponseWriter, r *http.Request, sess
|
||||
if series := strings.TrimSpace(seriesNameOf(raw)); series != "" && title != "" {
|
||||
title = series + " – " + title
|
||||
}
|
||||
|
||||
// The metadata-only shape. Everything below this point is a PlaybackInfo negotiation,
|
||||
// and a client that said it does not want one yet must not be given one anyway.
|
||||
if !nextEpisodeWantsStream(r) {
|
||||
s.playbackTitles.remember(next.ID, title)
|
||||
s.loggerFor(ctx).Debug("next episode identified",
|
||||
"title", title, "item", next.ID, "after_item", itemID)
|
||||
writeJSON(w, http.StatusOK, nextEpisodeResponse{
|
||||
Item: raw,
|
||||
Title: title,
|
||||
StreamReady: false,
|
||||
ResumePositionMs: max64(next.UserData.PlaybackPositionTicks/ticksPerMillisecond, 0),
|
||||
Subtitles: []playableSubtitle{},
|
||||
SubtitlesEnabled: true,
|
||||
TrickplayAvailable: s.trickplayEnabled(ctx),
|
||||
SkipIntroAvailable: s.skipIntroEnabled(ctx),
|
||||
EndCreditsAvailable: s.endCreditsEnabled(ctx),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
subtitles, mediaSourceID, playSessionID, negotiatedURL, playMethod := s.playbackSubtitles(
|
||||
ctx, cred, next.ID, next.UserData.PlaybackPositionTicks, nil, "", false,
|
||||
s.effectivePlaybackCapabilities(ctx, sess),
|
||||
@@ -380,6 +419,7 @@ func (s *Server) handleNextEpisode(w http.ResponseWriter, r *http.Request, sess
|
||||
writeJSON(w, http.StatusOK, nextEpisodeResponse{
|
||||
Item: raw,
|
||||
Title: title,
|
||||
StreamReady: true,
|
||||
URL: streamURL,
|
||||
ResumePositionMs: max64(next.UserData.PlaybackPositionTicks/ticksPerMillisecond, 0),
|
||||
Subtitles: subtitles,
|
||||
@@ -395,6 +435,19 @@ func (s *Server) handleNextEpisode(w http.ResponseWriter, r *http.Request, sess
|
||||
})
|
||||
}
|
||||
|
||||
// nextEpisodeWantsStream reads the client's `stream` parameter, and **defaults to yes**.
|
||||
// An app built before the two-phase lookup existed sends nothing and expects the whole
|
||||
// answer; reading a missing parameter as "identity only" would hand every one of those
|
||||
// televisions a next-up banner with no stream behind it.
|
||||
func nextEpisodeWantsStream(r *http.Request) bool {
|
||||
switch strings.TrimSpace(strings.ToLower(r.URL.Query().Get("stream"))) {
|
||||
case "0", "false", "no":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) playbackSubtitles(
|
||||
ctx context.Context, cred emby.Credentials, itemID string, startTicks int64,
|
||||
subtitleIndex *int, currentPlaySessionID string, forceTranscode bool,
|
||||
|
||||
Reference in New Issue
Block a user