mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-07 13:02:52 +01:00
fix(subtitles): teardown mpv before re-navigation and record the real local-sub index
Destroy the mpv instance before the track-change router.replace in replaceWithTrackSelection and goToPreviousItem, matching goToNextItem — two overlapping decoders/surfaces during the transition OOM low-RAM devices (CodeRabbit). addSubtitleFile now sets the live index to the downloaded local sub's real index (LOCAL_SUBTITLE_INDEX_START - position) instead of a blanket -100, which collided with the first local sub and mis-recorded the selection (Copilot); it still resolves to notFound on the onTracksReady re-apply so it doesn't clobber the freshly selected track.
This commit is contained in:
@@ -931,6 +931,10 @@ export default function DirectPlayerPage() {
|
||||
bitrateValue: bitrateValue?.toString() ?? "",
|
||||
playbackPosition: msToTicks(progress.get()).toString(),
|
||||
}).toString();
|
||||
// Destroy the current mpv instance before re-navigating, same rationale as
|
||||
// goToNextItem: Expo Router briefly holds two players during the
|
||||
// transition and two decoders/surfaces OOM-kill low-RAM devices.
|
||||
videoRef.current?.destroy().catch(() => {});
|
||||
router.replace(`player/direct-player?${queryParams}` as any);
|
||||
},
|
||||
[
|
||||
@@ -1105,6 +1109,10 @@ export default function DirectPlayerPage() {
|
||||
previousItem.UserData?.PlaybackPositionTicks?.toString() ?? "",
|
||||
}).toString();
|
||||
|
||||
// Free the current mpv instance before navigating, matching goToNextItem —
|
||||
// otherwise two decoders/surfaces overlap during the transition and can
|
||||
// OOM-kill low-RAM devices.
|
||||
videoRef.current?.destroy().catch(() => {});
|
||||
router.replace(`player/direct-player?${queryParams}` as any);
|
||||
}, [
|
||||
previousItem,
|
||||
@@ -1117,14 +1125,24 @@ export default function DirectPlayerPage() {
|
||||
]);
|
||||
|
||||
// TV: Add subtitle file to player (for client-side downloaded subtitles)
|
||||
const addSubtitleFile = useCallback(async (path: string) => {
|
||||
// Move the live index off the previous server sub BEFORE the add: the
|
||||
// sub-add fires onTracksReady, whose re-apply would otherwise resolve the
|
||||
// stale index and clobber the freshly selected local sub (disable it, or
|
||||
// re-select the old track). The sentinel resolves to notFound → no-op.
|
||||
setCurrentSubtitleIndex(LOCAL_SUBTITLE_INDEX_START);
|
||||
await videoRef.current?.addSubtitleFile?.(path, true);
|
||||
}, []);
|
||||
const addSubtitleFile = useCallback(
|
||||
async (path: string) => {
|
||||
// Set the live index to the new local sub's REAL index BEFORE the add.
|
||||
// Local subs are keyed LOCAL_SUBTITLE_INDEX_START - position, so use the
|
||||
// downloaded path's position (not a blanket sentinel, which would collide
|
||||
// with the first local sub at -100 and mis-record the selection). Any
|
||||
// local index resolves to notFound on the onTracksReady re-apply, so it
|
||||
// still doesn't clobber the freshly selected track; carry-over now keeps
|
||||
// the correct local sub.
|
||||
const locals = itemId ? getSubtitlesForItem(itemId) : [];
|
||||
const pos = locals.findIndex((s) => s.filePath === path);
|
||||
setCurrentSubtitleIndex(
|
||||
LOCAL_SUBTITLE_INDEX_START - (pos >= 0 ? pos : 0),
|
||||
);
|
||||
await videoRef.current?.addSubtitleFile?.(path, true);
|
||||
},
|
||||
[itemId],
|
||||
);
|
||||
|
||||
// TV: Refresh subtitle tracks after server-side subtitle download
|
||||
// Re-fetches the media source to pick up newly downloaded subtitles
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
type SubtitleSearchResult,
|
||||
useRemoteSubtitles,
|
||||
} from "@/hooks/useRemoteSubtitles";
|
||||
import { compareTracksForMenu } from "@/utils/jellyfin/subtitleUtils";
|
||||
import { COMMON_SUBTITLE_LANGUAGES } from "@/utils/opensubtitles/api";
|
||||
|
||||
interface TVSubtitleSheetProps {
|
||||
@@ -97,19 +96,13 @@ export const TVSubtitleSheet: React.FC<TVSubtitleSheetProps> = ({
|
||||
const overlayOpacity = useRef(new Animated.Value(0)).current;
|
||||
const sheetTranslateY = useRef(new Animated.Value(300)).current;
|
||||
|
||||
// Order like jellyfin-web (embedded first, externals last, forced/default up).
|
||||
const sortedTracks = useMemo(
|
||||
() => [...subtitleTracks].sort(compareTracksForMenu),
|
||||
[subtitleTracks],
|
||||
);
|
||||
|
||||
const initialSelectedTrackIndex = useMemo(() => {
|
||||
if (currentSubtitleIndex === -1) return 0;
|
||||
const trackIdx = sortedTracks.findIndex(
|
||||
const trackIdx = subtitleTracks.findIndex(
|
||||
(t) => t.Index === currentSubtitleIndex,
|
||||
);
|
||||
return trackIdx >= 0 ? trackIdx + 1 : 0;
|
||||
}, [sortedTracks, currentSubtitleIndex]);
|
||||
}, [subtitleTracks, currentSubtitleIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
@@ -222,7 +215,7 @@ export const TVSubtitleSheet: React.FC<TVSubtitleSheetProps> = ({
|
||||
value: -1,
|
||||
selected: currentSubtitleIndex === -1,
|
||||
};
|
||||
const options = sortedTracks.map((track) => ({
|
||||
const options = subtitleTracks.map((track) => ({
|
||||
label:
|
||||
track.DisplayTitle || `${track.Language || "Unknown"} (${track.Codec})`,
|
||||
sublabel: track.Codec?.toUpperCase(),
|
||||
@@ -230,7 +223,7 @@ export const TVSubtitleSheet: React.FC<TVSubtitleSheetProps> = ({
|
||||
selected: track.Index === currentSubtitleIndex,
|
||||
}));
|
||||
return [noneOption, ...options];
|
||||
}, [sortedTracks, currentSubtitleIndex, t]);
|
||||
}, [subtitleTracks, currentSubtitleIndex, t]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user