fix: local playback

This commit is contained in:
Fredrik Burmester
2024-10-13 16:07:03 +02:00
parent 43d64bc3d0
commit eefd1d9d13
4 changed files with 149 additions and 97 deletions

View File

@@ -2,43 +2,44 @@ import { Controls } from "@/components/video-player/Controls";
import { useAndroidNavigationBar } from "@/hooks/useAndroidNavigationBar";
import { useOrientation } from "@/hooks/useOrientation";
import { useOrientationSettings } from "@/hooks/useOrientationSettings";
import { VlcPlayerView } from "@/modules/vlc-player";
import {
PlaybackStatePayload,
ProgressUpdatePayload,
VlcPlayerViewRef,
} from "@/modules/vlc-player/src/VlcPlayer.types";
import { apiAtom } from "@/providers/JellyfinProvider";
import {
PlaybackType,
usePlaySettings,
} from "@/providers/PlaySettingsProvider";
import { useSettings } from "@/utils/atoms/settings";
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
import orientationToOrientationLock from "@/utils/OrientationLockConverter";
import { secondsToTicks } from "@/utils/secondsToTicks";
import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
import { ticksToSeconds } from "@/utils/time";
import { Api } from "@jellyfin/sdk";
import * as Haptics from "expo-haptics";
import * as NavigationBar from "expo-navigation-bar";
import { useFocusEffect } from "expo-router";
import * as ScreenOrientation from "expo-screen-orientation";
import { useAtomValue } from "jotai";
import React, {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react";
import { Dimensions, Platform, Pressable, StatusBar, View } from "react-native";
import { Dimensions, Pressable, StatusBar, View } from "react-native";
import { useSharedValue } from "react-native-reanimated";
import Video, { OnProgressData, VideoRef } from "react-native-video";
import { SelectedTrackType } from "react-native-video";
export default function page() {
const { playSettings, playUrl } = usePlaySettings();
const api = useAtomValue(apiAtom);
const videoRef = useRef<VideoRef | null>(null);
const videoSource = useVideoSource(playSettings, api, playUrl);
const firstTime = useRef(true);
const [settings] = useSettings();
const videoRef = useRef<VlcPlayerViewRef>(null);
const screenDimensions = Dimensions.get("screen");
const [isPlaybackStopped, setIsPlaybackStopped] = useState(false);
const [showControls, setShowControls] = useState(true);
const [ignoreSafeAreas, setIgnoreSafeAreas] = useState(false);
const [isPlaying, setIsPlaying] = useState(false);
@@ -48,25 +49,53 @@ export default function page() {
const isSeeking = useSharedValue(false);
const cacheProgress = useSharedValue(0);
const togglePlay = useCallback(async () => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
if (isPlaying) {
videoRef.current?.pause();
} else {
videoRef.current?.resume();
}
}, [isPlaying]);
const [playbackState, setPlaybackState] = useState<
PlaybackStatePayload["nativeEvent"] | null
>(null);
if (!playSettings || !playUrl || !api || !playSettings.item) return null;
const togglePlay = useCallback(
async (ticks: number) => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
if (isPlaying) {
videoRef.current?.pause();
} else {
videoRef.current?.play();
}
},
[isPlaying, api, playSettings?.item?.Id, videoRef, settings]
);
const play = useCallback(() => {
setIsPlaying(true);
videoRef.current?.resume();
videoRef.current?.play();
}, [videoRef]);
const pause = useCallback(() => {
videoRef.current?.pause();
}, [videoRef]);
const stop = useCallback(() => {
setIsPlaying(false);
videoRef.current?.pause();
setIsPlaybackStopped(true);
videoRef.current?.stop();
}, [videoRef]);
const onProgress = useCallback(
async (data: ProgressUpdatePayload) => {
if (isSeeking.value === true) return;
if (isPlaybackStopped === true) return;
const { currentTime, duration, isBuffering, isPlaying } =
data.nativeEvent;
progress.value = currentTime;
// cacheProgress.value = secondsToTicks(data.playableDuration);
// setIsBuffering(data.playableDuration === 0);
},
[playSettings?.item.Id, isPlaying, api, isPlaybackStopped]
);
useFocusEffect(
useCallback(() => {
play();
@@ -77,19 +106,72 @@ export default function page() {
}, [play, stop])
);
const { orientation } = useOrientation();
useOrientation();
useOrientationSettings();
useAndroidNavigationBar();
const onProgress = useCallback(async (data: OnProgressData) => {
if (isSeeking.value === true) return;
progress.value = secondsToTicks(data.currentTime);
cacheProgress.value = secondsToTicks(data.playableDuration);
setIsBuffering(data.playableDuration === 0);
const selectedSubtitleTrack = useMemo(() => {
const a = playSettings?.mediaSource?.MediaStreams?.find(
(s) => s.Index === playSettings.subtitleIndex
);
console.log(a);
return a;
}, [playSettings]);
const [hlsSubTracks, setHlsSubTracks] = useState<
{
index: number;
language?: string | undefined;
selected?: boolean | undefined;
title?: string | undefined;
type: any;
}[]
>([]);
const selectedTextTrack = useMemo(() => {
for (let st of hlsSubTracks) {
if (st.title === selectedSubtitleTrack?.DisplayTitle) {
return {
type: SelectedTrackType.TITLE,
value: selectedSubtitleTrack?.DisplayTitle ?? "",
};
}
}
return undefined;
}, [hlsSubTracks]);
const onPlaybackStateChanged = (e: PlaybackStatePayload) => {
const { target, state, isBuffering, isPlaying } = e.nativeEvent;
if (state === "Playing") {
setIsPlaying(true);
return;
}
if (state === "Paused") {
setIsPlaying(false);
return;
}
if (isPlaying) {
setIsPlaying(true);
setIsBuffering(false);
} else if (isBuffering) {
setIsBuffering(true);
}
setPlaybackState(e.nativeEvent);
};
useEffect(() => {
return () => {
stop();
};
}, []);
if (!playSettings || !playUrl || !api || !videoSource || !playSettings.item)
return null;
useEffect(() => {
console.log(playUrl);
}, [playUrl]);
return (
<View
@@ -107,29 +189,17 @@ export default function page() {
}}
className="absolute z-0 h-full w-full"
>
<Video
<VlcPlayerView
ref={videoRef}
source={videoSource}
source={{
uri: playUrl,
autoplay: true,
isNetwork: false,
}}
style={{ width: "100%", height: "100%" }}
resizeMode={ignoreSafeAreas ? "cover" : "contain"}
onProgress={onProgress}
onError={() => {}}
onLoad={() => {
if (firstTime.current === true) {
play();
firstTime.current = false;
}
}}
playWhenInactive={true}
allowsExternalPlayback={true}
playInBackground={true}
pictureInPicture={true}
showNotificationControls={true}
ignoreSilentSwitch="ignore"
fullscreen={false}
onPlaybackStateChanged={(state) => {
if (isSeeking.value === false) setIsPlaying(state.isPlaying);
}}
onVideoProgress={onProgress}
progressUpdateInterval={1000}
onVideoStateChange={onPlaybackStateChanged}
/>
</Pressable>
@@ -146,35 +216,9 @@ export default function page() {
setShowControls={setShowControls}
setIgnoreSafeAreas={setIgnoreSafeAreas}
ignoreSafeAreas={ignoreSafeAreas}
enableTrickplay={false}
offline={true}
/>
</View>
);
}
export function useVideoSource(
playSettings: PlaybackType | null,
api: Api | null,
playUrl?: string | null
) {
const videoSource = useMemo(() => {
if (!playSettings || !api || !playUrl) {
return null;
}
const startPosition = 0;
return {
uri: playUrl,
isNetwork: false,
startPosition,
metadata: {
artist: playSettings.item?.AlbumArtist ?? undefined,
title: playSettings.item?.Name || "Unknown",
description: playSettings.item?.Overview ?? undefined,
subtitle: playSettings.item?.Album ?? undefined,
},
};
}, [playSettings, api]);
return videoSource;
}