diff --git a/app/(auth)/vlc-player.tsx b/app/(auth)/vlc-player.tsx index e775f68d..69f0213d 100644 --- a/app/(auth)/vlc-player.tsx +++ b/app/(auth)/vlc-player.tsx @@ -303,6 +303,12 @@ export default function page() { onVideoProgress={onProgress} progressUpdateInterval={1000} onVideoStateChange={onPlaybackStateChanged} + onVideoLoadStart={() => { + console.log("onVideoLoadStart"); + }} + onVideoLoadEnd={() => { + console.log("onVideoLoadEnd"); + }} /> diff --git a/components/video-player/Controls.tsx b/components/video-player/Controls.tsx index 079162c5..65e20efd 100644 --- a/components/video-player/Controls.tsx +++ b/components/video-player/Controls.tsx @@ -2,6 +2,10 @@ import { useAdjacentItems } from "@/hooks/useAdjacentEpisodes"; import { useCreditSkipper } from "@/hooks/useCreditSkipper"; import { useIntroSkipper } from "@/hooks/useIntroSkipper"; import { useTrickplay } from "@/hooks/useTrickplay"; +import { + TrackInfo, + VlcPlayerViewRef, +} from "@/modules/vlc-player/src/VlcPlayer.types"; import { usePlaySettings } from "@/providers/PlaySettingsProvider"; import { useSettings } from "@/utils/atoms/settings"; import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings"; @@ -29,22 +33,16 @@ import { View, } from "react-native"; import { Slider } from "react-native-awesome-slider"; -import Animated, { +import { runOnJS, SharedValue, useAnimatedReaction, - useAnimatedStyle, useSharedValue, - withTiming, } from "react-native-reanimated"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { VideoRef } from "react-native-video"; +import * as DropdownMenu from "zeego/dropdown-menu"; import { Text } from "../common/Text"; import { Loader } from "../Loader"; -import { VlcPlayerViewRef } from "@/modules/vlc-player/src/VlcPlayer.types"; -import { secondsToTicks } from "@/utils/secondsToTicks"; -import { VideoDebugInfo } from "../vlc/VideoDebugInfo"; -import * as DropdownMenu from "zeego/dropdown-menu"; interface Props { item: BaseItemDto; @@ -249,10 +247,34 @@ export const Controls: React.FC = ({ MediaStream | undefined >(undefined); - const subtitleTracks = useMemo(() => { - return item.MediaStreams?.filter((stream) => stream.Type === "Subtitle"); + const allSubtitleTracks = useMemo(() => { + const subs = item.MediaStreams?.filter( + (stream) => stream.Type === "Subtitle" + ); + console.log("allSubtitleTracks", subs); + return subs; }, [item]); + const [audioTracks, setAudioTracks] = useState(null); + const [subtitleTracks, setSubtitleTracks] = useState( + null + ); + + useEffect(() => { + const fetchTracks = async () => { + if (videoRef.current) { + const audio = await videoRef.current.getAudioTracks(); + const subtitles = await videoRef.current.getSubtitleTracks(); + setAudioTracks(audio); + setSubtitleTracks(subtitles); + console.log("embedded audio", audio); + console.log("embedded sutitles", subtitles); + } + }; + + fetchTracks(); + }, [videoRef]); + return ( = ({ > - - + + = ({ sideOffset={8} > Subtitle tracks - {subtitleTracks?.map((sub, idx: number) => ( - { - if (!sub.Index !== undefined && sub.Index !== null) - videoRef.current?.setSubtitleTrack(sub.Index!); - }} + + + Subtitle + + - - {sub.DisplayTitle} - - - ))} + { + videoRef.current?.setSubtitleTrack(-1); + }} + > + + + None + + + {subtitleTracks?.map((sub, idx: number) => ( + { + // if(sub. === 'External') { + // videoRef.current?.setSubtitleURL( + // `https://fredflix.se/Providers/Subtitles/Subtitles/` + // ); + // } + + videoRef.current?.setSubtitleTrack(-1); + console.log(sub); + }} + > + + + {sub.name} + + + ))} + + diff --git a/modules/vlc-player/ios/VlcPlayerModule.swift b/modules/vlc-player/ios/VlcPlayerModule.swift index 97a6fd62..7f821aba 100644 --- a/modules/vlc-player/ios/VlcPlayerModule.swift +++ b/modules/vlc-player/ios/VlcPlayerModule.swift @@ -30,8 +30,9 @@ public class VlcPlayerModule: Module { Events( "onPlaybackStateChanged", - "onVideoLoadStart", "onVideoStateChange", + "onVideoLoadStart", + "onVideoLoadEnd", "onVideoProgress" ) @@ -74,6 +75,11 @@ public class VlcPlayerModule: Module { AsyncFunction("getVideoCropGeometry") { (view: VlcPlayerView) -> String? in return view.getVideoCropGeometry() } + + AsyncFunction("setSubtitleURL") { (view: VlcPlayerView, url: String) in + view.setSubtitleURL(url) + } + } } } diff --git a/modules/vlc-player/ios/VlcPlayerView.swift b/modules/vlc-player/ios/VlcPlayerView.swift index 0d56da4c..d6ac186c 100644 --- a/modules/vlc-player/ios/VlcPlayerView.swift +++ b/modules/vlc-player/ios/VlcPlayerView.swift @@ -9,6 +9,8 @@ class VlcPlayerView: ExpoView { private var isPaused: Bool = false private var currentGeometryCString: [CChar]? private var lastReportedState: VLCMediaPlayerState? + private var lastReportedIsPlaying: Bool? + private var isMediaReady: Bool = false // MARK: - Initialization @@ -233,6 +235,7 @@ class VlcPlayerView: ExpoView { // } // } // } + @objc func setSubtitleTrack(_ trackIndex: Int) { print("Debug: Attempting to set subtitle track to index: \(trackIndex)") DispatchQueue.main.async { @@ -255,21 +258,39 @@ class VlcPlayerView: ExpoView { } } + @objc func setSubtitleURL(_ subtitleURL: String) { + DispatchQueue.main.async { [weak self] in + guard let self = self, let url = URL(string: subtitleURL) else { + print("Error: Invalid subtitle URL") + return + } + + let result = self.mediaPlayer?.addPlaybackSlave(url, type: .subtitle, enforce: true) + if let result = result { + print("Subtitle added with result: \(result)") + } else { + print("Failed to add subtitle") + } + } + } + @objc func getSubtitleTracks() -> [[String: Any]]? { guard let mediaPlayer = self.mediaPlayer else { return nil } let count = mediaPlayer.numberOfSubtitlesTracks + + print( + "Debug: Number of subtitle tracks: \(count)" + ) + guard count > 0 else { return nil } var tracks: [[String: Any]] = [] - // Add the "Disabled" track - tracks.append(["name": "Disabled", "index": -1]) - if let names = mediaPlayer.videoSubTitlesNames as? [String], let indexes = mediaPlayer.videoSubTitlesIndexes as? [NSNumber] { @@ -493,6 +514,7 @@ class VlcPlayerView: ExpoView { @objc var onVideoLoadStart: RCTDirectEventBlock? @objc var onVideoStateChange: RCTDirectEventBlock? @objc var onVideoProgress: RCTDirectEventBlock? + @objc var onVideoLoadEnd: RCTDirectEventBlock? // MARK: - Deinitialization @@ -534,30 +556,19 @@ extension VlcPlayerView: VLCMediaPlayerDelegate { stateInfo["state"] = "Buffering" } - // switch currentState { - // case .opening: - // stateInfo["state"] = "Opening" - // case .buffering: - // stateInfo["state"] = "Buffering" - // stateInfo["isBuffering"] = true - // case .playing: - // stateInfo["state"] = "Playing" - // case .paused: - // stateInfo["state"] = "Paused" - // case .stopped: - // stateInfo["state"] = "Stopped" - // case .ended: - // stateInfo["state"] = "Ended" - // case .error: - // stateInfo["state"] = "Error" - // default: - // stateInfo["state"] = "Unknown" - // } + // Dermine if the media has finished loading + if currentState == .buffering && !self.isMediaReady { + self.isMediaReady = true + self.onVideoLoadEnd?(stateInfo) + } - print("State changed: \(stateInfo)") - - self.lastReportedState = currentState - self.onVideoStateChange?(stateInfo) + if self.lastReportedState != currentState + || self.lastReportedIsPlaying != player.isPlaying + { + self.lastReportedState = currentState + self.lastReportedIsPlaying = player.isPlaying + self.onVideoStateChange?(stateInfo) + } } } diff --git a/modules/vlc-player/src/VlcPlayer.types.ts b/modules/vlc-player/src/VlcPlayer.types.ts index a4720647..bba48210 100644 --- a/modules/vlc-player/src/VlcPlayer.types.ts +++ b/modules/vlc-player/src/VlcPlayer.types.ts @@ -67,6 +67,7 @@ export type VlcPlayerViewProps = { onVideoProgress?: (event: ProgressUpdatePayload) => void; onVideoStateChange?: (event: PlaybackStatePayload) => void; onVideoLoadStart?: (event: VideoLoadStartPayload) => void; + onVideoLoadEnd?: (event: VideoLoadStartPayload) => void; }; export interface VlcPlayerViewRef { @@ -87,4 +88,5 @@ export interface VlcPlayerViewRef { getChapters: () => Promise; setVideoCropGeometry: (geometry: string | null) => Promise; getVideoCropGeometry: () => Promise; + setSubtitleURL: (url: string) => Promise; } diff --git a/modules/vlc-player/src/VlcPlayerView.tsx b/modules/vlc-player/src/VlcPlayerView.tsx index 50950693..1893bab5 100644 --- a/modules/vlc-player/src/VlcPlayerView.tsx +++ b/modules/vlc-player/src/VlcPlayerView.tsx @@ -5,8 +5,6 @@ import { VlcPlayerViewProps, VlcPlayerViewRef, VlcPlayerSource, - TrackInfo, - ChapterInfo, } from "./VlcPlayer.types"; interface NativeViewRef extends VlcPlayerViewRef { @@ -80,6 +78,9 @@ const VlcPlayerView = React.forwardRef( const geometry = await nativeRef.current?.getVideoCropGeometry(); return geometry ?? null; }, + setSubtitleURL: async (url: string) => { + await nativeRef.current?.setSubtitleURL(url); + }, })); const { @@ -93,6 +94,7 @@ const VlcPlayerView = React.forwardRef( onVideoLoadStart, onVideoStateChange, onVideoProgress, + onVideoLoadEnd, ...otherProps } = props; @@ -111,6 +113,7 @@ const VlcPlayerView = React.forwardRef( volume={volume} videoAspectRatio={videoAspectRatio} onVideoLoadStart={onVideoLoadStart} + onVideoLoadEnd={onVideoLoadEnd} onVideoStateChange={onVideoStateChange} onVideoProgress={onVideoProgress} /> diff --git a/utils/jellyfin/media/getStreamUrl.ts b/utils/jellyfin/media/getStreamUrl.ts index 09197414..a8a0e414 100644 --- a/utils/jellyfin/media/getStreamUrl.ts +++ b/utils/jellyfin/media/getStreamUrl.ts @@ -7,6 +7,7 @@ import { } from "@jellyfin/sdk/lib/generated-client/models"; import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api"; import { getAuthHeaders } from "../jellyfin"; +import native from "@/utils/profiles/native"; export const getStreamUrl = async ({ api, @@ -83,7 +84,7 @@ export const getStreamUrl = async ({ { method: "POST", data: { - deviceProfile, + deviceProfile: forceDirectPlay ? native : deviceProfile, userId, maxStreamingBitrate, startTimeTicks, diff --git a/utils/profiles/native.js b/utils/profiles/native.js index 9e5f0152..472f8bcc 100644 --- a/utils/profiles/native.js +++ b/utils/profiles/native.js @@ -82,6 +82,12 @@ export default { Type: MediaTypes.Video, VideoCodec: "hevc,h264,mpeg4", }, + { + AudioCodec: "flac,alac,aac,eac3,ac3,opus", + Container: "mkv", + Type: MediaTypes.Video, + VideoCodec: "hevc,h264,mpeg4", + }, { AudioCodec: "alac,aac,ac3", Container: "m4v",