From b59759638c1ecd86b13b2284906e4e65a7b5292c Mon Sep 17 00:00:00 2001 From: Gauvain Date: Wed, 8 Jul 2026 18:14:07 +0200 Subject: [PATCH] fix(subtitles): normalize local subtitle sentinel to -1 in stream re-negotiation URLs --- app/(auth)/player/direct-player.tsx | 13 ++++++++++--- .../video-player/controls/contexts/VideoContext.tsx | 9 ++++++++- components/video-player/controls/types.ts | 8 ++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/(auth)/player/direct-player.tsx b/app/(auth)/player/direct-player.tsx index 27d9bf0c7..b68bac4c3 100644 --- a/app/(auth)/player/direct-player.tsx +++ b/app/(auth)/player/direct-player.tsx @@ -25,7 +25,10 @@ import { Controls } from "@/components/video-player/controls/Controls"; import { Controls as TVControls } from "@/components/video-player/controls/Controls.tv"; import { PlayerProvider } from "@/components/video-player/controls/contexts/PlayerContext"; import { VideoProvider } from "@/components/video-player/controls/contexts/VideoContext"; -import { LOCAL_SUBTITLE_INDEX_START } from "@/components/video-player/controls/types"; +import { + LOCAL_SUBTITLE_INDEX_START, + toServerSubtitleIndex, +} from "@/components/video-player/controls/types"; import { PlaybackSpeedScope, updatePlaybackSpeedSettings, @@ -888,7 +891,9 @@ export default function DirectPlayerPage() { const queryParams = new URLSearchParams({ itemId: item?.Id ?? "", audioIndex: String(index), - subtitleIndex: String(currentSubtitleIndex), + // A local (client-downloaded) sub only exists in the dying mpv + // instance — the server must be asked for "none" (-1) instead. + subtitleIndex: String(toServerSubtitleIndex(currentSubtitleIndex)), mediaSourceId: stream?.mediaSource?.Id ?? "", bitrateValue: bitrateValue?.toString() ?? "", playbackPosition: msToTicks(progress.get()).toString(), @@ -954,7 +959,9 @@ export default function DirectPlayerPage() { const queryParams = new URLSearchParams({ itemId: item?.Id ?? "", audioIndex: params.audioIndex ?? String(currentAudioIndex ?? ""), - subtitleIndex: params.subtitleIndex ?? String(currentSubtitleIndex), + subtitleIndex: + params.subtitleIndex ?? + String(toServerSubtitleIndex(currentSubtitleIndex)), mediaSourceId: stream?.mediaSource?.Id ?? "", bitrateValue: bitrateValue?.toString() ?? "", playbackPosition: msToTicks(progress.get()).toString(), diff --git a/components/video-player/controls/contexts/VideoContext.tsx b/components/video-player/controls/contexts/VideoContext.tsx index 4aee779f8..c42c7dca0 100644 --- a/components/video-player/controls/contexts/VideoContext.tsx +++ b/components/video-player/controls/contexts/VideoContext.tsx @@ -126,10 +126,17 @@ export const VideoProvider: React.FC<{ children: ReactNode }> = ({ audioIndex?: string; subtitleIndex?: string; }) => { + // The URL param can hold a local-sub sentinel (selected via setParams) that + // only exists in the dying player — the server must get "none" (-1) instead. + // NaN (missing/blank param) fails the comparison and passes through as-is. + const fallbackSubtitleIndex = + Number.parseInt(subtitleIndex, 10) <= LOCAL_SUBTITLE_INDEX_START + ? "-1" + : subtitleIndex; const queryParams = new URLSearchParams({ itemId: itemId ?? "", audioIndex: params.audioIndex ?? audioIndex, - subtitleIndex: params.subtitleIndex ?? subtitleIndex, + subtitleIndex: params.subtitleIndex ?? fallbackSubtitleIndex, mediaSourceId: mediaSource?.Id ?? "", bitrateValue: bitrateValue, playbackPosition: playbackPosition, diff --git a/components/video-player/controls/types.ts b/components/video-player/controls/types.ts index 470b0f084..ff6ddeab0 100644 --- a/components/video-player/controls/types.ts +++ b/components/video-player/controls/types.ts @@ -36,4 +36,12 @@ type Track = { */ export const LOCAL_SUBTITLE_INDEX_START = -100; +/** + * Map a client-side subtitle index to what the Jellyfin stream API understands. + * Local sentinel indexes have no matching MediaStream on the server, which + * expects -1 ("no subtitles") when re-negotiating a stream. + */ +export const toServerSubtitleIndex = (index: number): number => + index <= LOCAL_SUBTITLE_INDEX_START ? -1 : index; + export type { EmbeddedSubtitle, ExternalSubtitle, Track, TranscodedSubtitle };