From 7135be198a0a697a8bf77745f4119ee6257cbf3e Mon Sep 17 00:00:00 2001 From: Alex Kim Date: Sun, 7 Dec 2025 01:43:47 +1100 Subject: [PATCH] Done? --- app/(auth)/player/direct-player.tsx | 10 ++-- .../controls/contexts/PlayerContext.tsx | 10 ++-- .../controls/contexts/VideoContext.tsx | 46 +++++-------------- .../mpv-player/ios/MPVSoftwareRenderer.swift | 4 +- modules/mpv-player/ios/MpvPlayerView.swift | 4 +- modules/mpv-player/src/MpvPlayer.types.ts | 4 +- 6 files changed, 26 insertions(+), 52 deletions(-) diff --git a/app/(auth)/player/direct-player.tsx b/app/(auth)/player/direct-player.tsx index 406c1ce4..daf1ed20 100644 --- a/app/(auth)/player/direct-player.tsx +++ b/app/(auth)/player/direct-player.tsx @@ -69,7 +69,7 @@ export default function page() { const [isMuted, setIsMuted] = useState(false); const [isBuffering, setIsBuffering] = useState(true); const [isVideoLoaded, setIsVideoLoaded] = useState(false); - const [trackCount, setTrackCount] = useState(0); + const [tracksReady, setTracksReady] = useState(false); const progress = useSharedValue(0); const isSeeking = useSharedValue(false); @@ -338,6 +338,7 @@ export default function page() { const currentPlayStateInfo = useCallback(() => { if (!stream || !item?.Id) return; + console.log("subtitle"); return { itemId: item.Id, audioStreamIndex: audioIndex ? audioIndex : undefined, @@ -674,7 +675,7 @@ export default function page() { item={item} mediaSource={stream?.mediaSource} isVideoLoaded={isVideoLoaded} - trackCount={trackCount} + tracksReady={tracksReady} > { - console.log("[Player] Tracks ready:", e.nativeEvent.trackCount); - setTrackCount(e.nativeEvent.trackCount); + onTracksReady={() => { + setTracksReady(true); }} /> diff --git a/components/video-player/controls/contexts/PlayerContext.tsx b/components/video-player/controls/contexts/PlayerContext.tsx index 07f72818..a5586feb 100644 --- a/components/video-player/controls/contexts/PlayerContext.tsx +++ b/components/video-player/controls/contexts/PlayerContext.tsx @@ -16,7 +16,7 @@ interface PlayerContextProps { item: BaseItemDto; mediaSource: MediaSourceInfo | null | undefined; isVideoLoaded: boolean; - trackCount: number; + tracksReady: boolean; } const PlayerContext = createContext(undefined); @@ -27,7 +27,7 @@ interface PlayerProviderProps { item: BaseItemDto; mediaSource: MediaSourceInfo | null | undefined; isVideoLoaded: boolean; - trackCount: number; + tracksReady: boolean; } export const PlayerProvider: React.FC = ({ @@ -36,11 +36,11 @@ export const PlayerProvider: React.FC = ({ item, mediaSource, isVideoLoaded, - trackCount, + tracksReady, }) => { const value = useMemo( - () => ({ playerRef, item, mediaSource, isVideoLoaded, trackCount }), - [playerRef, item, mediaSource, isVideoLoaded, trackCount], + () => ({ playerRef, item, mediaSource, isVideoLoaded, tracksReady }), + [playerRef, item, mediaSource, isVideoLoaded, tracksReady], ); return ( diff --git a/components/video-player/controls/contexts/VideoContext.tsx b/components/video-player/controls/contexts/VideoContext.tsx index 73068ccc..72ea4b4f 100644 --- a/components/video-player/controls/contexts/VideoContext.tsx +++ b/components/video-player/controls/contexts/VideoContext.tsx @@ -64,10 +64,6 @@ * The order of subtitles in Jellyfin's MediaStreams matches the order in MPV. */ -import { - type MediaStream, - SubtitleDeliveryMethod, -} from "@jellyfin/sdk/lib/generated-client"; import { router, useLocalSearchParams } from "expo-router"; import type React from "react"; import { @@ -79,6 +75,10 @@ import { useState, } from "react"; import type { AudioTrack, SubtitleTrack } from "@/modules"; +import { + isImageBasedSubtitle, + isSubtitleInMpv, +} from "@/utils/jellyfin/subtitleUtils"; import type { Track } from "../types"; import { usePlayerContext, usePlayerControls } from "./PlayerContext"; @@ -95,7 +95,7 @@ export const VideoProvider: React.FC<{ children: ReactNode }> = ({ const [subtitleTracks, setSubtitleTracks] = useState(null); const [audioTracks, setAudioTracks] = useState(null); - const { trackCount, mediaSource } = usePlayerContext(); + const { tracksReady, mediaSource } = usePlayerContext(); const playerControls = usePlayerControls(); const { itemId, audioIndex, bitrateValue, subtitleIndex, playbackPosition } = @@ -115,10 +115,6 @@ export const VideoProvider: React.FC<{ children: ReactNode }> = ({ const isTranscoding = Boolean(mediaSource?.TranscodingUrl); - /** Check if subtitle is image-based (PGS, VOBSUB, etc.) */ - const isImageBased = (sub: MediaStream): boolean => - sub.IsTextSubtitleStream === false; - /** * Check if the currently selected subtitle is image-based. * Used to determine if we need to refresh the player when changing subs. @@ -128,7 +124,7 @@ export const VideoProvider: React.FC<{ children: ReactNode }> = ({ const currentSub = allSubs.find( (s) => s.Index?.toString() === subtitleIndex, ); - return currentSub ? isImageBased(currentSub) : false; + return currentSub ? isImageBasedSubtitle(currentSub) : false; }, [allSubs, subtitleIndex]); /** @@ -150,29 +146,9 @@ export const VideoProvider: React.FC<{ children: ReactNode }> = ({ router.replace(`player/direct-player?${queryParams}` as any); }; - /** - * Determine if a subtitle is available in MPV's track list. - * - * A subtitle is in MPV if: - * - Delivery is Embed/Hls/External AND not an image-based sub during transcode - */ - const isSubtitleInMpv = (sub: MediaStream): boolean => { - // During transcoding, image-based subs are burned in, not in MPV - if (isTranscoding && isImageBased(sub)) { - return false; - } - - // Embed/Hls/External methods mean the sub is loaded into MPV - return ( - sub.DeliveryMethod === SubtitleDeliveryMethod.Embed || - sub.DeliveryMethod === SubtitleDeliveryMethod.Hls || - sub.DeliveryMethod === SubtitleDeliveryMethod.External - ); - }; - - // Fetch tracks when track count changes + // Fetch tracks when ready useEffect(() => { - if (trackCount === 0) return; + if (!tracksReady) return; const fetchTracks = async () => { const [subtitleData, audioData] = await Promise.all([ @@ -184,7 +160,7 @@ export const VideoProvider: React.FC<{ children: ReactNode }> = ({ let mpvIndex = 0; // MPV track index counter (only incremented for subs in MPV) const subs: Track[] = allSubs.map((sub) => { - const inMpv = isSubtitleInMpv(sub); + const inMpv = isSubtitleInMpv(sub, isTranscoding); // Get MPV track ID: only if this sub is actually in MPV's track list const mpvId = inMpv @@ -200,7 +176,7 @@ export const VideoProvider: React.FC<{ children: ReactNode }> = ({ // Need to refresh player so Jellyfin burns in the new sub if ( isTranscoding && - (isImageBased(sub) || isCurrentSubImageBased) + (isImageBasedSubtitle(sub) || isCurrentSubImageBased) ) { replacePlayer({ subtitleIndex: String(sub.Index) }); return; @@ -257,7 +233,7 @@ export const VideoProvider: React.FC<{ children: ReactNode }> = ({ }; fetchTracks(); - }, [trackCount, mediaSource]); + }, [tracksReady, mediaSource]); return ( diff --git a/modules/mpv-player/ios/MPVSoftwareRenderer.swift b/modules/mpv-player/ios/MPVSoftwareRenderer.swift index a5ebc2dd..398b48ee 100644 --- a/modules/mpv-player/ios/MPVSoftwareRenderer.swift +++ b/modules/mpv-player/ios/MPVSoftwareRenderer.swift @@ -9,7 +9,7 @@ protocol MPVSoftwareRendererDelegate: AnyObject { func renderer(_ renderer: MPVSoftwareRenderer, didChangePause isPaused: Bool) func renderer(_ renderer: MPVSoftwareRenderer, didChangeLoading isLoading: Bool) func renderer(_ renderer: MPVSoftwareRenderer, didBecomeReadyToSeek: Bool) - func renderer(_ renderer: MPVSoftwareRenderer, didUpdateTrackList trackCount: Int) + func renderer(_ renderer: MPVSoftwareRenderer, didBecomeTracksReady: Bool) } final class MPVSoftwareRenderer { @@ -961,7 +961,7 @@ final class MPVSoftwareRenderer { Logger.shared.log("Track list updated: \(trackCount) tracks available", type: "Info") DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.delegate?.renderer(self, didUpdateTrackList: Int(trackCount)) + self.delegate?.renderer(self, didBecomeTracksReady: true) } } default: diff --git a/modules/mpv-player/ios/MpvPlayerView.swift b/modules/mpv-player/ios/MpvPlayerView.swift index 470be207..440e1d2d 100644 --- a/modules/mpv-player/ios/MpvPlayerView.swift +++ b/modules/mpv-player/ios/MpvPlayerView.swift @@ -317,10 +317,10 @@ extension MpvPlayerView: MPVSoftwareRendererDelegate { } } - func renderer(_: MPVSoftwareRenderer, didUpdateTrackList trackCount: Int) { + func renderer(_: MPVSoftwareRenderer, didBecomeTracksReady: Bool) { DispatchQueue.main.async { [weak self] in guard let self else { return } - self.onTracksReady(["trackCount": trackCount]) + self.onTracksReady([:]) } } } diff --git a/modules/mpv-player/src/MpvPlayer.types.ts b/modules/mpv-player/src/MpvPlayer.types.ts index 0e4dddce..4359f515 100644 --- a/modules/mpv-player/src/MpvPlayer.types.ts +++ b/modules/mpv-player/src/MpvPlayer.types.ts @@ -21,9 +21,7 @@ export type OnErrorEventPayload = { error: string; }; -export type OnTracksReadyEventPayload = { - trackCount: number; -}; +export type OnTracksReadyEventPayload = Record; export type MpvPlayerModuleEvents = { onChange: (params: ChangeEventPayload) => void;