From 0054095b202424a35fd10f8186e991c4d3c91766 Mon Sep 17 00:00:00 2001 From: Alex Kim Date: Mon, 25 Nov 2024 15:35:05 +1100 Subject: [PATCH] Fixed bug with dupe subtitle names for transcoded content --- app/(auth)/player/transcoding-player.tsx | 23 +++- components/PlayButton.tsx | 3 - .../video-player/controls/DropdownView.tsx | 41 +++---- .../controls/contexts/VideoContext.tsx | 106 ++++++++++++------ modules/vlc-player/ios/VlcPlayerView.swift | 6 +- 5 files changed, 113 insertions(+), 66 deletions(-) diff --git a/app/(auth)/player/transcoding-player.tsx b/app/(auth)/player/transcoding-player.tsx index d568170d..03dd4a35 100644 --- a/app/(auth)/player/transcoding-player.tsx +++ b/app/(auth)/player/transcoding-player.tsx @@ -20,7 +20,13 @@ import { useQuery } from "@tanstack/react-query"; import * as Haptics from "expo-haptics"; import { useFocusEffect, useLocalSearchParams } from "expo-router"; import { useAtomValue } from "jotai"; -import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { Pressable, useWindowDimensions, View } from "react-native"; import { useSharedValue } from "react-native-reanimated"; import Video, { @@ -123,6 +129,7 @@ const Player = () => { return null; } + console.log("getting med"); const res = await getStreamUrl({ api, item, @@ -259,7 +266,7 @@ const Player = () => { // TODO: Use this when streaming with HLS url, but NOT when direct playing // TODO: since playable duration is always 0 then. - // setIsBuffering(data.playableDuration === 0); + setIsBuffering(data.playableDuration === 0); if (!item?.Id || data.currentTime === 0) { return; @@ -328,19 +335,25 @@ const Player = () => { SelectedTrack | undefined >(undefined); - // Set intial Subtitle Track. // We will only select external tracks if they are are text based. Else it should be burned in already. const textSubs = - stream?.mediaSource.MediaStreams?.filter((sub) => sub.Type === "Subtitle" && sub.IsTextSubtitleStream) || []; + stream?.mediaSource.MediaStreams?.filter( + (sub) => sub.Type === "Subtitle" && sub.IsTextSubtitleStream + ) || []; + + const uniqueTextSubs = Array.from( + new Set(textSubs.map((sub) => sub.DisplayTitle)) + ).map((title) => textSubs.find((sub) => sub.DisplayTitle === title)); const chosenSubtitleTrack = textSubs.find( (sub) => sub.Index === subtitleIndex ); useEffect(() => { if (chosenSubtitleTrack && selectedTextTrack === undefined) { + console.log("Setting selected text track", chosenSubtitleTrack); setSelectedTextTrack({ type: SelectedTrackType.INDEX, - value: textSubs.indexOf(chosenSubtitleTrack), + value: uniqueTextSubs.indexOf(chosenSubtitleTrack), }); } }, [embededTextTracks]); diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx index d41e59fc..748b178d 100644 --- a/components/PlayButton.tsx +++ b/components/PlayButton.tsx @@ -65,9 +65,6 @@ export const PlayButton: React.FC = ({ const colorChangeProgress = useSharedValue(0); const [settings] = useSettings(); - - - // console.log(bitrateValue); const goToPlayer = useCallback( (q: string, bitrateValue: number | undefined) => { if (!bitrateValue) { diff --git a/components/video-player/controls/DropdownView.tsx b/components/video-player/controls/DropdownView.tsx index fba7cef2..4621758a 100644 --- a/components/video-player/controls/DropdownView.tsx +++ b/components/video-player/controls/DropdownView.tsx @@ -12,8 +12,6 @@ import { import { useAtomValue } from "jotai"; import { apiAtom } from "@/providers/JellyfinProvider"; import { useLocalSearchParams, useRouter } from "expo-router"; -import { parse } from "@babel/core"; -import { set } from "lodash"; interface DropdownViewProps { showControls: boolean; @@ -129,23 +127,26 @@ const DropdownView: React.FC = ({ showControls }) => { ); const textSubtitlesMap = new Map(textSubtitles.map((s) => [s.name, s])); - const imageSubtitlesMap = new Map(imageSubtitles.map((s) => [s.name, s])); - const sortedSubtitles = allSubs - .map((sub) => { - const displayTitle = sub.DisplayTitle ?? ""; - if (textSubtitlesMap.has(displayTitle)) { - return textSubtitlesMap.get(displayTitle); - } - if (imageSubtitlesMap.has(displayTitle)) { - return imageSubtitlesMap.get(displayTitle); - } - return null; - }) - .filter( - (subtitle): subtitle is TranscodedSubtitle => subtitle !== null - ); + const sortedSubtitles = Array.from( + new Set( + allSubs + .map((sub) => { + const displayTitle = sub.DisplayTitle ?? ""; + if (textSubtitlesMap.has(displayTitle)) { + return textSubtitlesMap.get(displayTitle); + } + if (imageSubtitlesMap.has(displayTitle)) { + return imageSubtitlesMap.get(displayTitle); + } + return null; + }) + .filter( + (subtitle): subtitle is TranscodedSubtitle => subtitle !== null + ) + ) + ); return [disableSubtitle, ...sortedSubtitles]; } @@ -270,7 +271,9 @@ const DropdownView: React.FC = ({ showControls }) => { value={selectedSubtitleIndex === sub.index} key={`subtitle-item-${idx}`} onValueChange={() => { - if (subtitleIndex === sub?.index.toString()) return; + console.log("Set sub index: ", sub.index); + if (selectedSubtitleIndex === sub?.index.toString()) + return; setSelectedSubtitleIndex(sub.index); if (sub.IsTextSubtitleStream && isOnTextSubtitle) { setSubtitleTrack && setSubtitleTrack(sub.index); @@ -322,7 +325,7 @@ const DropdownView: React.FC = ({ showControls }) => { key={`audio-item-${idx}`} value={selectedAudioIndex === track.index} onValueChange={() => { - if (audioIndex === track.index.toString()) return; + if (selectedAudioIndex === track.index.toString()) return; setSelectedAudioIndex(track.index); ChangeTranscodingAudio(track.index); }} diff --git a/components/video-player/controls/contexts/VideoContext.tsx b/components/video-player/controls/contexts/VideoContext.tsx index 2193301f..7f7b30c1 100644 --- a/components/video-player/controls/contexts/VideoContext.tsx +++ b/components/video-player/controls/contexts/VideoContext.tsx @@ -1,53 +1,89 @@ -import { TrackInfo } from '@/modules/vlc-player'; -import { BaseItemDto, MediaSourceInfo } from '@jellyfin/sdk/lib/generated-client'; -import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react'; -import { useControlContext } from './ControlContext'; +import { TrackInfo } from "@/modules/vlc-player"; +import { + BaseItemDto, + MediaSourceInfo, +} from "@jellyfin/sdk/lib/generated-client"; +import React, { + createContext, + useContext, + useState, + ReactNode, + useEffect, +} from "react"; +import { useControlContext } from "./ControlContext"; interface VideoContextProps { - audioTracks: TrackInfo[] | null; - subtitleTracks: TrackInfo[] | null; - setAudioTrack: ((index: number) => void) | undefined; - setSubtitleTrack: ((index: number) => void) | undefined; - setSubtitleURL: ((url: string, customName: string) => void) | undefined; + audioTracks: TrackInfo[] | null; + subtitleTracks: TrackInfo[] | null; + setAudioTrack: ((index: number) => void) | undefined; + setSubtitleTrack: ((index: number) => void) | undefined; + setSubtitleURL: ((url: string, customName: string) => void) | undefined; } const VideoContext = createContext(undefined); interface VideoProviderProps { children: ReactNode; - getAudioTracks: (() => Promise) | (() => TrackInfo[]) | undefined; - getSubtitleTracks: (() => Promise) | (() => TrackInfo[]) | undefined; + getAudioTracks: + | (() => Promise) + | (() => TrackInfo[]) + | undefined; + getSubtitleTracks: + | (() => Promise) + | (() => TrackInfo[]) + | undefined; setAudioTrack: ((index: number) => void) | undefined; setSubtitleTrack: ((index: number) => void) | undefined; setSubtitleURL: ((url: string, customName: string) => void) | undefined; } -export const VideoProvider: React.FC = ({ children, getSubtitleTracks, getAudioTracks, setSubtitleTrack, setSubtitleURL, setAudioTrack }) => { - const [audioTracks, setAudioTracks] = useState(null); - const [subtitleTracks, setSubtitleTracks] = useState( - null - ); +export const VideoProvider: React.FC = ({ + children, + getSubtitleTracks, + getAudioTracks, + setSubtitleTrack, + setSubtitleURL, + setAudioTrack, +}) => { + const [audioTracks, setAudioTracks] = useState(null); + const [subtitleTracks, setSubtitleTracks] = useState( + null + ); - const ControlContext = useControlContext(); - const isVideoLoaded = ControlContext?.isVideoLoaded; + const ControlContext = useControlContext(); + const isVideoLoaded = ControlContext?.isVideoLoaded; - useEffect(() => { - const fetchTracks = async () => { - if (getSubtitleTracks) { - const subtitles = await getSubtitleTracks(); - console.log("Getting embeded subtitles...", subtitles); - setSubtitleTracks(subtitles); - } - if (getAudioTracks) { - const audio = await getAudioTracks(); - setAudioTracks(audio); - } - }; - fetchTracks(); - }, [isVideoLoaded, getAudioTracks, getSubtitleTracks]); + useEffect(() => { + const fetchTracks = async () => { + if ( + getSubtitleTracks && + (subtitleTracks === null || subtitleTracks.length === 0) + ) { + const subtitles = await getSubtitleTracks(); + console.log("Getting embeded subtitles...", subtitles); + setSubtitleTracks(subtitles); + } + if ( + getAudioTracks && + (audioTracks === null || audioTracks.length === 0) + ) { + const audio = await getAudioTracks(); + setAudioTracks(audio); + } + }; + fetchTracks(); + }, [isVideoLoaded, getAudioTracks, getSubtitleTracks]); return ( - + {children} ); @@ -56,7 +92,7 @@ export const VideoProvider: React.FC = ({ children, getSubti export const useVideoContext = () => { const context = useContext(VideoContext); if (context === undefined) { - throw new Error('useVideoContext must be used within a VideoProvider'); + throw new Error("useVideoContext must be used within a VideoProvider"); } return context; -}; \ No newline at end of file +}; diff --git a/modules/vlc-player/ios/VlcPlayerView.swift b/modules/vlc-player/ios/VlcPlayerView.swift index 5b2b9281..b40e5ef3 100644 --- a/modules/vlc-player/ios/VlcPlayerView.swift +++ b/modules/vlc-player/ios/VlcPlayerView.swift @@ -580,10 +580,8 @@ extension VlcPlayerView: VLCMediaPlayerDelegate { "duration": player.media?.length.intValue ?? 0, "error": false, ] - // Playing and not transcoding, we can let it in no HLS issue. - // We should also mark it as playing when the media is ready. - // Fix HLS issue. - if player.isPlaying && self.isMediaReady { + + if player.isPlaying { stateInfo["isPlaying"] = true stateInfo["isBuffering"] = false stateInfo["state"] = "Playing"