This commit is contained in:
ponzischeme89
2026-08-26 21:31:05 +12:00
parent a5f9a91832
commit 3d42c98947
33 changed files with 1172 additions and 69 deletions
+54 -9
View File
@@ -62,6 +62,15 @@ type playbackResponse struct {
// separate features with separate switches, and a house that has turned the skip button
// off has not asked to lose the credits pane with it.
EndCreditsAvailable bool `json:"endCreditsAvailable"`
// Whether Sonarr can name when this continuing show's next episode airs. Rides here for
// the same reason the other availability flags do: the player asks once at the start of
// playback and the notice itself takes no further request. Absent (all fields blank)
// means either the show isn't Sonarr-tracked, isn't continuing, or has no scheduled
// airing to report — the client shows nothing in every one of those cases.
NextAiringAvailable bool `json:"nextAiringAvailable"`
NextAiringLabel string `json:"nextAiringLabel,omitempty"`
NextAiringDayLabel string `json:"nextAiringDayLabel,omitempty"`
NextAiringEpisodeCode string `json:"nextAiringEpisodeCode,omitempty"`
}
type playableSubtitle struct {
@@ -114,6 +123,10 @@ type nextEpisodeResponse struct {
TrickplayAvailable bool `json:"trickplayAvailable"`
SkipIntroAvailable bool `json:"skipIntroAvailable"`
EndCreditsAvailable bool `json:"endCreditsAvailable"`
NextAiringAvailable bool `json:"nextAiringAvailable"`
NextAiringLabel string `json:"nextAiringLabel,omitempty"`
NextAiringDayLabel string `json:"nextAiringDayLabel,omitempty"`
NextAiringEpisodeCode string `json:"nextAiringEpisodeCode,omitempty"`
}
// handlePlayback resolves what to actually play.
@@ -235,6 +248,8 @@ func (s *Server) handlePlayback(w http.ResponseWriter, r *http.Request, sess sto
"negotiation_duration", time.Since(negotiationStarted).Round(time.Millisecond),
)
nextAiringAvailable, nextAiringLabel, nextAiringDayLabel, nextAiringCode :=
s.nextAiringFieldsFor(ctx, target)
writeJSON(w, http.StatusOK, playbackResponse{
ItemID: target.ID,
Title: title,
@@ -257,6 +272,10 @@ func (s *Server) handlePlayback(w http.ResponseWriter, r *http.Request, sess sto
TrickplayAvailable: s.trickplayEnabled(ctx),
SkipIntroAvailable: s.skipIntroEnabled(ctx),
EndCreditsAvailable: s.endCreditsEnabled(ctx),
NextAiringAvailable: nextAiringAvailable,
NextAiringLabel: nextAiringLabel,
NextAiringDayLabel: nextAiringDayLabel,
NextAiringEpisodeCode: nextAiringCode,
})
}
@@ -336,6 +355,22 @@ func (s *Server) firstPlayableEpisode(
return &summary, nil
}
// nextAiringFieldsFor answers the current episode's own show's next airing, never the
// episode being played itself. It only ever asks Sonarr about a series that is actually
// known — a hinted playback response that skipped the Emby lookup and so carries no
// SeriesName simply gets nothing, the same silent degradation every other availability
// flag here takes.
func (s *Server) nextAiringFieldsFor(ctx context.Context, item emby.Summary) (available bool, label, dayLabel, code string) {
if !strings.EqualFold(item.Type, "Episode") {
return false, "", "", ""
}
info, ok := s.nextAiringInfoFor(ctx, item.SeriesName)
if !ok {
return false, "", "", ""
}
return true, info.Label, info.DayLabel, info.EpisodeCode
}
func episodeCode(item emby.Summary) string {
if !strings.EqualFold(item.Type, "Episode") || item.ParentIndexNumber < 0 || item.IndexNumber <= 0 {
return ""
@@ -435,20 +470,26 @@ func (s *Server) handleNextEpisode(w http.ResponseWriter, r *http.Request, sess
// 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.
nextAiringAvailable, nextAiringLabel, nextAiringDayLabel, nextAiringCode :=
s.nextAiringFieldsFor(ctx, next)
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),
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),
NextAiringAvailable: nextAiringAvailable,
NextAiringLabel: nextAiringLabel,
NextAiringDayLabel: nextAiringDayLabel,
NextAiringEpisodeCode: nextAiringCode,
})
return
}
@@ -491,6 +532,10 @@ func (s *Server) handleNextEpisode(w http.ResponseWriter, r *http.Request, sess
TrickplayAvailable: s.trickplayEnabled(ctx),
SkipIntroAvailable: s.skipIntroEnabled(ctx),
EndCreditsAvailable: s.endCreditsEnabled(ctx),
NextAiringAvailable: nextAiringAvailable,
NextAiringLabel: nextAiringLabel,
NextAiringDayLabel: nextAiringDayLabel,
NextAiringEpisodeCode: nextAiringCode,
})
}