mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-17 08:38:04 +00:00
Compare commits
1 Commits
develop
...
feat/expo-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c4bc68566 |
5
app.json
5
app.json
@@ -82,9 +82,11 @@
|
|||||||
"expo-build-properties",
|
"expo-build-properties",
|
||||||
{
|
{
|
||||||
"ios": {
|
"ios": {
|
||||||
|
"newArchEnabled": true,
|
||||||
"deploymentTarget": "14.0"
|
"deploymentTarget": "14.0"
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
|
"newArchEnabled": true,
|
||||||
"android": {
|
"android": {
|
||||||
"compileSdkVersion": 34,
|
"compileSdkVersion": 34,
|
||||||
"targetSdkVersion": 34,
|
"targetSdkVersion": 34,
|
||||||
@@ -111,7 +113,8 @@
|
|||||||
{
|
{
|
||||||
"motionPermission": "Allow Streamyfin to access your device motion for landscape video watching."
|
"motionPermission": "Allow Streamyfin to access your device motion for landscape video watching."
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"expo-video"
|
||||||
],
|
],
|
||||||
"experiments": {
|
"experiments": {
|
||||||
"typedRoutes": true
|
"typedRoutes": true
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { apiAtom } from "@/providers/JellyfinProvider";
|
|||||||
import { usePlayback } from "@/providers/PlaybackProvider";
|
import { usePlayback } from "@/providers/PlaybackProvider";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
||||||
import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
|
import { getAuthHeaders, isBaseItemDto } from "@/utils/jellyfin/jellyfin";
|
||||||
import { writeToLog } from "@/utils/log";
|
import { writeToLog } from "@/utils/log";
|
||||||
import orientationToOrientationLock from "@/utils/OrientationLockConverter";
|
import orientationToOrientationLock from "@/utils/OrientationLockConverter";
|
||||||
import { secondsToTicks } from "@/utils/secondsToTicks";
|
import { secondsToTicks } from "@/utils/secondsToTicks";
|
||||||
@@ -32,6 +32,7 @@ import "react-native-gesture-handler";
|
|||||||
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||||
import Animated, {
|
import Animated, {
|
||||||
runOnJS,
|
runOnJS,
|
||||||
|
useAnimatedReaction,
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
useSharedValue,
|
useSharedValue,
|
||||||
withTiming,
|
withTiming,
|
||||||
@@ -41,6 +42,7 @@ import Video, { OnProgressData } from "react-native-video";
|
|||||||
import { Text } from "./common/Text";
|
import { Text } from "./common/Text";
|
||||||
import { itemRouter } from "./common/TouchableItemRouter";
|
import { itemRouter } from "./common/TouchableItemRouter";
|
||||||
import { Loader } from "./Loader";
|
import { Loader } from "./Loader";
|
||||||
|
import { useVideoPlayer, VideoView } from "expo-video";
|
||||||
|
|
||||||
async function lockOrientation(orientation: ScreenOrientation.OrientationLock) {
|
async function lockOrientation(orientation: ScreenOrientation.OrientationLock) {
|
||||||
await ScreenOrientation.lockAsync(orientation);
|
await ScreenOrientation.lockAsync(orientation);
|
||||||
@@ -59,10 +61,10 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
|||||||
setVolume,
|
setVolume,
|
||||||
setIsPlaying,
|
setIsPlaying,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
videoRef,
|
|
||||||
onProgress,
|
onProgress,
|
||||||
isBuffering: _isBuffering,
|
isBuffering,
|
||||||
setIsBuffering,
|
setIsBuffering,
|
||||||
|
player,
|
||||||
} = usePlayback();
|
} = usePlayback();
|
||||||
|
|
||||||
const [settings] = useSettings();
|
const [settings] = useSettings();
|
||||||
@@ -71,6 +73,8 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
|||||||
const segments = useSegments();
|
const segments = useSegments();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const firstLoad = useRef(true);
|
||||||
|
|
||||||
const { trickPlayUrl, calculateTrickplayUrl, trickplayInfo } =
|
const { trickPlayUrl, calculateTrickplayUrl, trickplayInfo } =
|
||||||
useTrickplay(currentlyPlaying);
|
useTrickplay(currentlyPlaying);
|
||||||
const { previousItem, nextItem } = useAdjacentEpisodes({ currentlyPlaying });
|
const { previousItem, nextItem } = useAdjacentEpisodes({ currentlyPlaying });
|
||||||
@@ -90,6 +94,24 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
|||||||
const localIsBuffering = useSharedValue(true);
|
const localIsBuffering = useSharedValue(true);
|
||||||
const cacheProgress = useSharedValue(0);
|
const cacheProgress = useSharedValue(0);
|
||||||
const [isStatusBarHidden, setIsStatusBarHidden] = useState(false);
|
const [isStatusBarHidden, setIsStatusBarHidden] = useState(false);
|
||||||
|
const [progressState, _setProgressState] = useState(0);
|
||||||
|
|
||||||
|
const setProgressState = useCallback(
|
||||||
|
(value: number) => {
|
||||||
|
if (sliding.current === true) return;
|
||||||
|
_setProgressState(value);
|
||||||
|
},
|
||||||
|
[sliding.current]
|
||||||
|
);
|
||||||
|
|
||||||
|
useAnimatedReaction(
|
||||||
|
() => {
|
||||||
|
return progress.value;
|
||||||
|
},
|
||||||
|
(progress) => {
|
||||||
|
runOnJS(setProgressState)(progress);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const hideControls = useCallback(() => {
|
const hideControls = useCallback(() => {
|
||||||
"worklet";
|
"worklet";
|
||||||
@@ -104,8 +126,6 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const backAction = () => {
|
const backAction = () => {
|
||||||
if (currentlyPlaying) {
|
if (currentlyPlaying) {
|
||||||
// Your custom back action here
|
|
||||||
console.log("onback");
|
|
||||||
Alert.alert("Hold on!", "Are you sure you want to exit?", [
|
Alert.alert("Hold on!", "Are you sure you want to exit?", [
|
||||||
{
|
{
|
||||||
text: "Cancel",
|
text: "Cancel",
|
||||||
@@ -162,10 +182,62 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
|||||||
};
|
};
|
||||||
}, [currentlyPlaying, api, poster]);
|
}, [currentlyPlaying, api, poster]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const subscription = player.addListener("playingChange", (isPlaying) => {
|
||||||
|
setIsPlaying(isPlaying);
|
||||||
|
});
|
||||||
|
|
||||||
|
const subscription2 = player.addListener("statusChange", (status) => {
|
||||||
|
if (status === "error") {
|
||||||
|
console.log("player.addListener ~ error");
|
||||||
|
Alert.alert("Error", "An error occurred while playing the video.");
|
||||||
|
}
|
||||||
|
if (status === "readyToPlay") {
|
||||||
|
console.log("player.addListener ~ readyToPlay");
|
||||||
|
localIsBuffering.value = false;
|
||||||
|
setIsBuffering(false);
|
||||||
|
if (firstLoad.current === true) {
|
||||||
|
playVideo();
|
||||||
|
firstLoad.current = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (status === "loading") {
|
||||||
|
localIsBuffering.value = true;
|
||||||
|
setIsBuffering(true);
|
||||||
|
}
|
||||||
|
if (status === "idle") {
|
||||||
|
console.log("player.addListener ~ idle");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subscription.remove();
|
||||||
|
subscription2.remove();
|
||||||
|
};
|
||||||
|
}, [player, setIsBuffering]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
max.value = currentlyPlaying?.item.RunTimeTicks || 0;
|
max.value = currentlyPlaying?.item.RunTimeTicks || 0;
|
||||||
}, [currentlyPlaying?.item.RunTimeTicks]);
|
}, [currentlyPlaying?.item.RunTimeTicks]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
if (sliding.current === true) return;
|
||||||
|
if (player.playing === true) {
|
||||||
|
const time = secondsToTicks(player.currentTime);
|
||||||
|
progress.value = time;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error getting current time:", error);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [player, sliding.current]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentlyPlaying) {
|
if (!currentlyPlaying) {
|
||||||
resetOrientation();
|
resetOrientation();
|
||||||
@@ -173,14 +245,11 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
|||||||
min.value = 0;
|
min.value = 0;
|
||||||
max.value = 0;
|
max.value = 0;
|
||||||
cacheProgress.value = 0;
|
cacheProgress.value = 0;
|
||||||
localIsBuffering.value = false;
|
|
||||||
sliding.current = false;
|
sliding.current = false;
|
||||||
hideControls();
|
hideControls();
|
||||||
setStatusBarHidden(false);
|
setStatusBarHidden(false);
|
||||||
// NavigationBar.setVisibilityAsync("visible")
|
|
||||||
} else {
|
} else {
|
||||||
setStatusBarHidden(true);
|
setStatusBarHidden(true);
|
||||||
// NavigationBar.setVisibilityAsync("hidden")
|
|
||||||
lockOrientation(
|
lockOrientation(
|
||||||
settings?.defaultVideoOrientation ||
|
settings?.defaultVideoOrientation ||
|
||||||
ScreenOrientation.OrientationLock.DEFAULT
|
ScreenOrientation.OrientationLock.DEFAULT
|
||||||
@@ -229,10 +298,9 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
|||||||
),
|
),
|
||||||
})),
|
})),
|
||||||
loader: useAnimatedStyle(() => ({
|
loader: useAnimatedStyle(() => ({
|
||||||
opacity: withTiming(
|
opacity: withTiming(localIsBuffering.value === true ? 1 : 0, {
|
||||||
localIsBuffering.value === true || progress.value === 0 ? 1 : 0,
|
duration: 300,
|
||||||
{ duration: 300 }
|
}),
|
||||||
),
|
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -303,32 +371,14 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
|||||||
}, [opacity.value, hideControls, showControls]);
|
}, [opacity.value, hideControls, showControls]);
|
||||||
|
|
||||||
const skipIntro = useCallback(async () => {
|
const skipIntro = useCallback(async () => {
|
||||||
if (!introTimestamps || !videoRef.current) return;
|
if (!introTimestamps || !player) return;
|
||||||
try {
|
try {
|
||||||
videoRef.current.seek(introTimestamps.IntroEnd);
|
player.currentTime = introTimestamps.IntroEnd;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeToLog("ERROR", "Error skipping intro", error);
|
writeToLog("ERROR", "Error skipping intro", error);
|
||||||
}
|
}
|
||||||
}, [introTimestamps]);
|
}, [introTimestamps]);
|
||||||
|
|
||||||
const handleVideoProgress = useCallback(
|
|
||||||
(e: OnProgressData) => {
|
|
||||||
if (e.playableDuration === 0) {
|
|
||||||
setIsBuffering(true);
|
|
||||||
localIsBuffering.value = true;
|
|
||||||
} else {
|
|
||||||
setIsBuffering(false);
|
|
||||||
localIsBuffering.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sliding.current) return;
|
|
||||||
onProgress(e);
|
|
||||||
progress.value = secondsToTicks(e.currentTime);
|
|
||||||
cacheProgress.value = secondsToTicks(e.playableDuration);
|
|
||||||
},
|
|
||||||
[onProgress, setIsBuffering]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleVideoError = useCallback(
|
const handleVideoError = useCallback(
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
@@ -341,27 +391,27 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
|||||||
|
|
||||||
const handleSkipBackward = useCallback(async () => {
|
const handleSkipBackward = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const curr = await videoRef.current?.getCurrentPosition();
|
const curr = player.currentTime;
|
||||||
if (curr !== undefined) {
|
if (curr !== undefined) {
|
||||||
videoRef.current?.seek(Math.max(0, curr - 15));
|
player.currentTime = Math.max(0, curr - 15);
|
||||||
showControls();
|
showControls();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeToLog("ERROR", "Error seeking video backwards", error);
|
writeToLog("ERROR", "Error seeking video backwards", error);
|
||||||
}
|
}
|
||||||
}, [videoRef, showControls]);
|
}, [player, showControls]);
|
||||||
|
|
||||||
const handleSkipForward = useCallback(async () => {
|
const handleSkipForward = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const curr = await videoRef.current?.getCurrentPosition();
|
const curr = player.currentTime;
|
||||||
if (curr !== undefined) {
|
if (curr !== undefined) {
|
||||||
videoRef.current?.seek(Math.max(0, curr + 15));
|
player.currentTime = Math.max(0, curr + 15);
|
||||||
showControls();
|
showControls();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeToLog("ERROR", "Error seeking video forwards", error);
|
writeToLog("ERROR", "Error seeking video forwards", error);
|
||||||
}
|
}
|
||||||
}, [videoRef, showControls]);
|
}, [player, showControls]);
|
||||||
|
|
||||||
const handlePlayPause = useCallback(() => {
|
const handlePlayPause = useCallback(() => {
|
||||||
console.log("handlePlayPause");
|
console.log("handlePlayPause");
|
||||||
@@ -379,19 +429,19 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
|||||||
(val: number) => {
|
(val: number) => {
|
||||||
if (opacity.value === 0) return;
|
if (opacity.value === 0) return;
|
||||||
const tick = Math.floor(val);
|
const tick = Math.floor(val);
|
||||||
videoRef.current?.seek(tick / 10000000);
|
player.currentTime = tick / 10000000;
|
||||||
sliding.current = false;
|
sliding.current = false;
|
||||||
},
|
},
|
||||||
[videoRef]
|
[player]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSliderChange = useCallback(
|
const handleSliderChange = useCallback(
|
||||||
(val: number) => {
|
(val: number) => {
|
||||||
if (opacity.value === 0) return;
|
if (opacity.value === 0) return;
|
||||||
|
sliding.current = true;
|
||||||
const tick = Math.floor(val);
|
const tick = Math.floor(val);
|
||||||
progress.value = tick;
|
progress.value = tick;
|
||||||
calculateTrickplayUrl(progress);
|
calculateTrickplayUrl(progress);
|
||||||
showControls();
|
|
||||||
},
|
},
|
||||||
[progress, calculateTrickplayUrl, showControls]
|
[progress, calculateTrickplayUrl, showControls]
|
||||||
);
|
);
|
||||||
@@ -495,55 +545,16 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{videoSource && (
|
{videoSource && (
|
||||||
<Video
|
<VideoView
|
||||||
ref={videoRef}
|
|
||||||
allowsExternalPlayback
|
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
}}
|
}}
|
||||||
resizeMode={ignoreSafeArea ? "cover" : "contain"}
|
player={player}
|
||||||
playWhenInactive={true}
|
allowsFullscreen
|
||||||
playInBackground={true}
|
nativeControls={false}
|
||||||
showNotificationControls={true}
|
allowsPictureInPicture
|
||||||
ignoreSilentSwitch="ignore"
|
contentFit={ignoreSafeArea ? "cover" : "contain"}
|
||||||
controls={false}
|
|
||||||
pictureInPicture={true}
|
|
||||||
onProgress={handleVideoProgress}
|
|
||||||
subtitleStyle={{
|
|
||||||
fontSize: 16,
|
|
||||||
}}
|
|
||||||
source={videoSource}
|
|
||||||
onPlaybackStateChanged={(e) => {
|
|
||||||
if (e.isPlaying === true) {
|
|
||||||
playVideo(false);
|
|
||||||
} else if (e.isPlaying === false) {
|
|
||||||
pauseVideo(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onBuffer={(e) => {
|
|
||||||
if (e.isBuffering) {
|
|
||||||
console.log("Buffering...");
|
|
||||||
setIsBuffering(true);
|
|
||||||
localIsBuffering.value = true;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onRestoreUserInterfaceForPictureInPictureStop={() => {
|
|
||||||
showControls();
|
|
||||||
}}
|
|
||||||
onVolumeChange={(e) => {
|
|
||||||
setVolume(e.volume);
|
|
||||||
}}
|
|
||||||
fullscreen={false}
|
|
||||||
onLoadStart={() => {
|
|
||||||
localIsBuffering.value = true;
|
|
||||||
}}
|
|
||||||
onLoad={() => {
|
|
||||||
localIsBuffering.value = true
|
|
||||||
}}
|
|
||||||
|
|
||||||
progressUpdateInterval={1000}
|
|
||||||
onError={handleVideoError}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -778,10 +789,13 @@ export const FullScreenVideoPlayer: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<View className="flex flex-row items-center justify-between mt-0.5">
|
<View className="flex flex-row items-center justify-between mt-0.5">
|
||||||
<Text className="text-[12px] text-neutral-400">
|
<Text className="text-[12px] text-neutral-400">
|
||||||
{runtimeTicksToSeconds(progress.value)}
|
{runtimeTicksToSeconds(progressState)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className="text-[12px] text-neutral-400">
|
<Text className="text-[12px] text-neutral-400">
|
||||||
-{runtimeTicksToSeconds(max.value - progress.value)}
|
-
|
||||||
|
{runtimeTicksToSeconds(
|
||||||
|
(currentlyPlaying?.item.RunTimeTicks || 0) - progressState
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -50,6 +50,7 @@
|
|||||||
"expo-status-bar": "~1.12.1",
|
"expo-status-bar": "~1.12.1",
|
||||||
"expo-system-ui": "~3.0.7",
|
"expo-system-ui": "~3.0.7",
|
||||||
"expo-updates": "~0.25.24",
|
"expo-updates": "~0.25.24",
|
||||||
|
"expo-video": "^1.2.6",
|
||||||
"expo-web-browser": "~13.0.3",
|
"expo-web-browser": "~13.0.3",
|
||||||
"ffmpeg-kit-react-native": "^6.0.2",
|
"ffmpeg-kit-react-native": "^6.0.2",
|
||||||
"jotai": "^2.9.3",
|
"jotai": "^2.9.3",
|
||||||
@@ -93,5 +94,12 @@
|
|||||||
"react-test-renderer": "18.2.0",
|
"react-test-renderer": "18.2.0",
|
||||||
"typescript": "~5.3.3"
|
"typescript": "~5.3.3"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true,
|
||||||
|
"expo": {
|
||||||
|
"doctor": {
|
||||||
|
"reactNativeDirectoryCheck": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
parseM3U8ForSubtitles,
|
parseM3U8ForSubtitles,
|
||||||
SubtitleTrack,
|
SubtitleTrack,
|
||||||
} from "@/utils/hls/parseM3U8ForSubtitles";
|
} from "@/utils/hls/parseM3U8ForSubtitles";
|
||||||
|
import { useVideoPlayer, VideoPlayer } from "expo-video";
|
||||||
|
|
||||||
export type CurrentlyPlayingState = {
|
export type CurrentlyPlayingState = {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -40,14 +41,10 @@ interface PlaybackContextType {
|
|||||||
currentlyPlaying: CurrentlyPlayingState | null;
|
currentlyPlaying: CurrentlyPlayingState | null;
|
||||||
videoRef: React.MutableRefObject<VideoRef | null>;
|
videoRef: React.MutableRefObject<VideoRef | null>;
|
||||||
isPlaying: boolean;
|
isPlaying: boolean;
|
||||||
isFullscreen: boolean;
|
|
||||||
progressTicks: number | null;
|
progressTicks: number | null;
|
||||||
playVideo: (triggerRef?: boolean) => void;
|
playVideo: (triggerRef?: boolean) => void;
|
||||||
pauseVideo: (triggerRef?: boolean) => void;
|
pauseVideo: (triggerRef?: boolean) => void;
|
||||||
stopPlayback: () => void;
|
stopPlayback: () => void;
|
||||||
presentFullscreenPlayer: () => void;
|
|
||||||
dismissFullscreenPlayer: () => void;
|
|
||||||
setIsFullscreen: (isFullscreen: boolean) => void;
|
|
||||||
setIsPlaying: (isPlaying: boolean) => void;
|
setIsPlaying: (isPlaying: boolean) => void;
|
||||||
isBuffering: boolean;
|
isBuffering: boolean;
|
||||||
setIsBuffering: (val: boolean) => void;
|
setIsBuffering: (val: boolean) => void;
|
||||||
@@ -60,6 +57,7 @@ interface PlaybackContextType {
|
|||||||
currentlyPlaying: CurrentlyPlayingState | null
|
currentlyPlaying: CurrentlyPlayingState | null
|
||||||
) => void;
|
) => void;
|
||||||
subtitles: SubtitleTrack[];
|
subtitles: SubtitleTrack[];
|
||||||
|
player: VideoPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlaybackContext = createContext<PlaybackContextType | null>(null);
|
const PlaybackContext = createContext<PlaybackContextType | null>(null);
|
||||||
@@ -71,13 +69,12 @@ export const PlaybackProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
|
|
||||||
const videoRef = useRef<VideoRef | null>(null);
|
const videoRef = useRef<VideoRef | null>(null);
|
||||||
|
const previousVolume = useRef<number | null>(null);
|
||||||
|
|
||||||
const [settings] = useSettings();
|
const [settings] = useSettings();
|
||||||
|
|
||||||
const previousVolume = useRef<number | null>(null);
|
|
||||||
|
|
||||||
const [isPlaying, _setIsPlaying] = useState<boolean>(false);
|
const [isPlaying, _setIsPlaying] = useState<boolean>(false);
|
||||||
const [isBuffering, setIsBuffering] = useState<boolean>(false);
|
const [isBuffering, setIsBuffering] = useState<boolean>(true);
|
||||||
const [isFullscreen, setIsFullscreen] = useState<boolean>(false);
|
const [isFullscreen, setIsFullscreen] = useState<boolean>(false);
|
||||||
const [progressTicks, setProgressTicks] = useState<number | null>(0);
|
const [progressTicks, setProgressTicks] = useState<number | null>(0);
|
||||||
const [volume, _setVolume] = useState<number | null>(null);
|
const [volume, _setVolume] = useState<number | null>(null);
|
||||||
@@ -99,6 +96,10 @@ export const PlaybackProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
[_setVolume]
|
[_setVolume]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const player = useVideoPlayer(currentlyPlaying?.url || null, () => {
|
||||||
|
if (player) player.play();
|
||||||
|
});
|
||||||
|
|
||||||
const { data: deviceId } = useQuery({
|
const { data: deviceId } = useQuery({
|
||||||
queryKey: ["deviceId", api],
|
queryKey: ["deviceId", api],
|
||||||
queryFn: getDeviceId,
|
queryFn: getDeviceId,
|
||||||
@@ -175,7 +176,7 @@ export const PlaybackProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
const playVideo = useCallback(
|
const playVideo = useCallback(
|
||||||
(triggerRef: boolean = true) => {
|
(triggerRef: boolean = true) => {
|
||||||
if (triggerRef === true) {
|
if (triggerRef === true) {
|
||||||
videoRef.current?.resume();
|
player.play();
|
||||||
}
|
}
|
||||||
_setIsPlaying(true);
|
_setIsPlaying(true);
|
||||||
reportPlaybackProgress({
|
reportPlaybackProgress({
|
||||||
@@ -192,7 +193,7 @@ export const PlaybackProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
const pauseVideo = useCallback(
|
const pauseVideo = useCallback(
|
||||||
(triggerRef: boolean = true) => {
|
(triggerRef: boolean = true) => {
|
||||||
if (triggerRef === true) {
|
if (triggerRef === true) {
|
||||||
videoRef.current?.pause();
|
player.pause();
|
||||||
}
|
}
|
||||||
_setIsPlaying(false);
|
_setIsPlaying(false);
|
||||||
reportPlaybackProgress({
|
reportPlaybackProgress({
|
||||||
@@ -253,16 +254,6 @@ export const PlaybackProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
[_onProgress]
|
[_onProgress]
|
||||||
);
|
);
|
||||||
|
|
||||||
const presentFullscreenPlayer = useCallback(() => {
|
|
||||||
videoRef.current?.presentFullscreenPlayer();
|
|
||||||
setIsFullscreen(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const dismissFullscreenPlayer = useCallback(() => {
|
|
||||||
videoRef.current?.dismissFullscreenPlayer();
|
|
||||||
setIsFullscreen(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!deviceId || !api?.accessToken) return;
|
if (!deviceId || !api?.accessToken) return;
|
||||||
|
|
||||||
@@ -346,14 +337,13 @@ export const PlaybackProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
return (
|
return (
|
||||||
<PlaybackContext.Provider
|
<PlaybackContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
player,
|
||||||
onProgress,
|
onProgress,
|
||||||
isBuffering,
|
isBuffering,
|
||||||
setIsBuffering,
|
setIsBuffering,
|
||||||
progressTicks,
|
progressTicks,
|
||||||
setVolume,
|
setVolume,
|
||||||
setIsPlaying,
|
setIsPlaying,
|
||||||
setIsFullscreen,
|
|
||||||
isFullscreen,
|
|
||||||
isPlaying,
|
isPlaying,
|
||||||
currentlyPlaying,
|
currentlyPlaying,
|
||||||
sessionData: session,
|
sessionData: session,
|
||||||
@@ -362,8 +352,6 @@ export const PlaybackProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
setCurrentlyPlayingState,
|
setCurrentlyPlayingState,
|
||||||
pauseVideo,
|
pauseVideo,
|
||||||
stopPlayback,
|
stopPlayback,
|
||||||
presentFullscreenPlayer,
|
|
||||||
dismissFullscreenPlayer,
|
|
||||||
startDownloadedFilePlayback,
|
startDownloadedFilePlayback,
|
||||||
subtitles,
|
subtitles,
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user