diff --git a/app/(auth)/vlc-player.tsx b/app/(auth)/vlc-player.tsx index 682e6881..bdaf4536 100644 --- a/app/(auth)/vlc-player.tsx +++ b/app/(auth)/vlc-player.tsx @@ -15,14 +15,21 @@ import { usePlaySettings, } from "@/providers/PlaySettingsProvider"; import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl"; +import { writeToLog } from "@/utils/log"; import { msToTicks, ticksToMs } from "@/utils/time"; import { Api } from "@jellyfin/sdk"; import { getPlaystateApi } from "@jellyfin/sdk/lib/utils/api"; import * as Haptics from "expo-haptics"; -import { useFocusEffect } from "expo-router"; +import { useFocusEffect, useRouter } from "expo-router"; import { useAtomValue } from "jotai"; -import React, { useCallback, useMemo, useRef, useState } from "react"; -import { Dimensions, Pressable, StatusBar, View } from "react-native"; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { Alert, Dimensions, Pressable, StatusBar, View } from "react-native"; import { useSharedValue } from "react-native-reanimated"; export default function page() { @@ -33,6 +40,8 @@ export default function page() { // const poster = usePoster(playSettings, api); // const user = useAtomValue(userAtom); + const router = useRouter(); + const screenDimensions = Dimensions.get("screen"); const [isPlaybackStopped, setIsPlaybackStopped] = useState(false); @@ -46,8 +55,11 @@ export default function page() { const isSeeking = useSharedValue(false); const cacheProgress = useSharedValue(0); - if (!playSettings || !playUrl || !api || !playSettings.item || !mediaSource) + if (!playSettings || !playUrl || !api || !playSettings.item || !mediaSource) { + Alert.alert("Error", "Invalid play settings"); + router.back(); return null; + } const togglePlay = useCallback( async (ticks: number) => { @@ -136,6 +148,7 @@ export default function page() { const { currentTime, isPlaying } = data.nativeEvent; + progress.value = currentTime; const currentTimeInTicks = msToTicks(currentTime); await getPlaystateApi(api).onPlaybackProgress({ @@ -198,6 +211,13 @@ export default function page() { } }; + useEffect(() => { + console.log( + "PlaybackPositionTicks", + playSettings.item?.UserData?.PlaybackPositionTicks + ); + }, [playSettings.item]); + return ( { setIsVideoLoaded(true); }} + onVideoError={(e) => { + console.error("Video Error:", e.nativeEvent); + Alert.alert( + "Error", + "An error occurred while playing the video. Check logs in settings." + ); + writeToLog("ERROR", "Video Error", e.nativeEvent); + }} /> diff --git a/modules/vlc-player/ios/VlcPlayerModule.swift b/modules/vlc-player/ios/VlcPlayerModule.swift index 7f821aba..0d65f6ba 100644 --- a/modules/vlc-player/ios/VlcPlayerModule.swift +++ b/modules/vlc-player/ios/VlcPlayerModule.swift @@ -33,7 +33,8 @@ public class VlcPlayerModule: Module { "onVideoStateChange", "onVideoLoadStart", "onVideoLoadEnd", - "onVideoProgress" + "onVideoProgress", + "onVideoError" ) AsyncFunction("play") { (view: VlcPlayerView) in diff --git a/modules/vlc-player/ios/VlcPlayerView.swift b/modules/vlc-player/ios/VlcPlayerView.swift index bb465a0d..08d22013 100644 --- a/modules/vlc-player/ios/VlcPlayerView.swift +++ b/modules/vlc-player/ios/VlcPlayerView.swift @@ -99,7 +99,13 @@ class VlcPlayerView: ExpoView { let isNetwork = source["isNetwork"] as? Bool ?? false let startPosition = source["startPosition"] as? Int32 ?? 0 - guard let uri = uri, !uri.isEmpty else { return } + guard let uri = uri, !uri.isEmpty else { + print("Error: Invalid or empty URI") + self.onVideoError?(["error": "Invalid or empty URI"]) + return + } + + self.onVideoLoadStart?(["target": self.reactTag ?? NSNull()]) if initType == 2, let options = initOptions { self.mediaPlayer = VLCMediaPlayer(options: options) @@ -128,6 +134,7 @@ class VlcPlayerView: ExpoView { media = VLCMedia(path: uri) } } + // Apply subtitle options let subtitleOptions = self.getSubtitleOptions() media.addOptions(subtitleOptions) @@ -141,6 +148,7 @@ class VlcPlayerView: ExpoView { print("Debug: No additional media options provided") } + // Apply subtitle options let subtitleTrackIndex = source["subtitleTrackIndex"] as? Int ?? -1 print("Debug: Subtitle track index from source: \(subtitleTrackIndex)") @@ -154,14 +162,25 @@ class VlcPlayerView: ExpoView { self.mediaPlayer?.media = media if startPosition > 0 { - self.mediaPlayer?.time = VLCTime(int: startPosition) + // Wait for the media to be ready before setting the start position + NotificationCenter.default.addObserver( + forName: NSNotification.Name(rawValue: VLCMediaPlayerStateChanged), object: nil, + queue: nil + ) { [weak self] notification in + guard let self = self, let player = self.mediaPlayer, + player.isPlaying == false + else { return } + + self.mediaPlayer?.time = VLCTime(int: startPosition) + NotificationCenter.default.removeObserver( + self, name: NSNotification.Name(rawValue: VLCMediaPlayerStateChanged), + object: nil) + } } if autoplay { self.play() } - - self.onVideoLoadStart?(["target": self.reactTag ?? NSNull()]) } } @@ -520,6 +539,7 @@ class VlcPlayerView: ExpoView { @objc var onVideoStateChange: RCTDirectEventBlock? @objc var onVideoProgress: RCTDirectEventBlock? @objc var onVideoLoadEnd: RCTDirectEventBlock? + @objc var onVideoError: RCTDirectEventBlock? // MARK: - Deinitialization @@ -545,6 +565,7 @@ extension VlcPlayerView: VLCMediaPlayerDelegate { "target": self.reactTag ?? NSNull(), "currentTime": player.time.intValue, "duration": player.media?.length.intValue ?? 0, + "error": false, ] if player.isPlaying { @@ -556,9 +577,16 @@ extension VlcPlayerView: VLCMediaPlayerDelegate { stateInfo["state"] = "Paused" } - if player.state == .buffering { + if player.state == VLCMediaPlayerState.buffering { stateInfo["isBuffering"] = true stateInfo["state"] = "Buffering" + } else if player.state == VLCMediaPlayerState.error { + print("player.state ~ error") + stateInfo["state"] = "Error" + self.onVideoLoadEnd?(stateInfo) + } else if player.state == VLCMediaPlayerState.opening { + print("player.state ~ opening") + stateInfo["state"] = "Opening" } // Dermine if the media has finished loading diff --git a/modules/vlc-player/src/VlcPlayer.types.ts b/modules/vlc-player/src/VlcPlayer.types.ts index 1edeb2a8..3bd61fbe 100644 --- a/modules/vlc-player/src/VlcPlayer.types.ts +++ b/modules/vlc-player/src/VlcPlayer.types.ts @@ -1,15 +1,7 @@ export type PlaybackStatePayload = { nativeEvent: { target: number; - state: - | "Opening" - | "Buffering" - | "Playing" - | "Paused" - | "Stopped" - | "Ended" - | "Error" - | "Unknown"; + state: "Opening" | "Buffering" | "Playing" | "Paused" | "Error"; currentTime: number; duration: number; isBuffering: boolean; @@ -69,6 +61,7 @@ export type VlcPlayerViewProps = { onVideoStateChange?: (event: PlaybackStatePayload) => void; onVideoLoadStart?: (event: VideoLoadStartPayload) => void; onVideoLoadEnd?: (event: VideoLoadStartPayload) => void; + onVideoError?: (event: PlaybackStatePayload) => void; }; export interface VlcPlayerViewRef { diff --git a/modules/vlc-player/src/VlcPlayerView.tsx b/modules/vlc-player/src/VlcPlayerView.tsx index 9393a92a..fb08496a 100644 --- a/modules/vlc-player/src/VlcPlayerView.tsx +++ b/modules/vlc-player/src/VlcPlayerView.tsx @@ -95,6 +95,7 @@ const VlcPlayerView = React.forwardRef( onVideoStateChange, onVideoProgress, onVideoLoadEnd, + onVideoError, ...otherProps } = props; @@ -120,6 +121,7 @@ const VlcPlayerView = React.forwardRef( onVideoLoadEnd={onVideoLoadEnd} onVideoStateChange={onVideoStateChange} onVideoProgress={onVideoProgress} + onVideoError={onVideoError} /> ); }