mirror of
https://github.com/movie-web/native-app.git
synced 2025-09-13 15:03:26 +00:00
add back button and header layout to player
This commit is contained in:
BIN
apps/expo/assets/images/icon-transparent.png
Normal file
BIN
apps/expo/assets/images/icon-transparent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 156 KiB |
@@ -42,7 +42,7 @@ export default function Searchbar({
|
||||
ref={inputRef}
|
||||
placeholder="What are you looking for?"
|
||||
placeholderTextColor={Colors.secondary[200]}
|
||||
className="w-full rounded-3xl py-3 pr-5 text-white focus-visible:outline-none"
|
||||
className="w-full rounded-3xl py-3 pr-5 text-white"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
@@ -17,14 +17,19 @@ import {
|
||||
import { fetchMediaDetails } from "@movie-web/tmdb";
|
||||
|
||||
import type { ItemData } from "~/components/item/item";
|
||||
import { usePlayer } from "../hooks/usePlayer";
|
||||
import { Header } from "~/components/player/Header";
|
||||
import { PlayerProvider, usePlayer } from "~/context/player.context";
|
||||
|
||||
export default function VideoPlayerWrapper() {
|
||||
const params = useLocalSearchParams();
|
||||
const data = params.data
|
||||
? (JSON.parse(params.data as string) as ItemData)
|
||||
: null;
|
||||
return <VideoPlayer data={data} />;
|
||||
return (
|
||||
<PlayerProvider>
|
||||
<VideoPlayer data={data} />
|
||||
</PlayerProvider>
|
||||
);
|
||||
}
|
||||
|
||||
interface VideoPlayerProps {
|
||||
@@ -37,7 +42,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({ data }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const router = useRouter();
|
||||
const {
|
||||
videoRef,
|
||||
setVideoRef,
|
||||
unlockOrientation,
|
||||
presentFullscreenPlayer,
|
||||
dismissFullscreenPlayer,
|
||||
@@ -99,6 +104,8 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({ data }) => {
|
||||
: [],
|
||||
);
|
||||
|
||||
console.log("stream", url);
|
||||
|
||||
setVideoSrc({
|
||||
uri: url,
|
||||
headers: {
|
||||
@@ -143,17 +150,19 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({ data }) => {
|
||||
return (
|
||||
<View className="flex-1 items-center justify-center bg-black">
|
||||
<Video
|
||||
ref={videoRef}
|
||||
ref={setVideoRef}
|
||||
source={videoSrc}
|
||||
// textTracks={textTracks} // breaks playback
|
||||
className="absolute inset-0"
|
||||
fullscreen={true}
|
||||
fullscreen
|
||||
paused={false}
|
||||
controls={true}
|
||||
controls
|
||||
useSecureView
|
||||
onLoadStart={onVideoLoadStart}
|
||||
onReadyForDisplay={onReadyForDisplay}
|
||||
/>
|
||||
{isLoading && <ActivityIndicator size="large" color="#0000ff" />}
|
||||
{!isLoading && <Header title="S8 E11 Rocky 8" />}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
29
apps/expo/src/components/player/BackButton.tsx
Normal file
29
apps/expo/src/components/player/BackButton.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useRouter } from "expo-router";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
|
||||
import { usePlayer } from "~/context/player.context";
|
||||
|
||||
export const BackButton = ({
|
||||
className,
|
||||
}: Partial<React.ComponentProps<typeof Ionicons>>) => {
|
||||
const { unlockOrientation } = usePlayer();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Ionicons
|
||||
name="arrow-back"
|
||||
onPress={() => {
|
||||
unlockOrientation()
|
||||
.then(() => {
|
||||
return router.back();
|
||||
})
|
||||
.catch(() => {
|
||||
return router.back();
|
||||
});
|
||||
}}
|
||||
size={36}
|
||||
color="white"
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
};
|
22
apps/expo/src/components/player/Header.tsx
Normal file
22
apps/expo/src/components/player/Header.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Image, View } from "react-native";
|
||||
|
||||
import Icon from "../../../assets/images/icon-transparent.png";
|
||||
import { Text } from "../ui/Text";
|
||||
import { BackButton } from "./BackButton";
|
||||
|
||||
interface HeaderProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const Header = ({ title }: HeaderProps) => {
|
||||
return (
|
||||
<View className="absolute top-0 flex w-full flex-row items-center justify-between px-6 pt-6">
|
||||
<BackButton className="w-36" />
|
||||
<Text className="font-bold">{title}</Text>
|
||||
<View className="flex w-36 flex-row items-center justify-center gap-2 space-x-2 rounded-full bg-secondary-300 px-4 py-2 opacity-50">
|
||||
<Image source={Icon} className="h-6 w-6" />
|
||||
<Text className="font-bold">movie-web</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
77
apps/expo/src/context/player.context.tsx
Normal file
77
apps/expo/src/context/player.context.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { VideoRef } from "react-native-video";
|
||||
import React, { createContext, useCallback, useContext, useState } from "react";
|
||||
import * as ScreenOrientation from "expo-screen-orientation";
|
||||
|
||||
interface PlayerContextProps {
|
||||
videoRef: VideoRef | null;
|
||||
setVideoRef: (ref: VideoRef | null) => void;
|
||||
lockOrientation: () => Promise<void>;
|
||||
unlockOrientation: () => Promise<void>;
|
||||
presentFullscreenPlayer: () => Promise<void>;
|
||||
dismissFullscreenPlayer: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface PlayerProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const PlayerContext = createContext<PlayerContextProps | undefined>(undefined);
|
||||
|
||||
export const PlayerProvider = ({ children }: PlayerProviderProps) => {
|
||||
const [internalVideoRef, setInternalVideoRef] = useState<VideoRef | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const setVideoRef = useCallback((ref: VideoRef | null) => {
|
||||
setInternalVideoRef(ref);
|
||||
}, []);
|
||||
|
||||
const lockOrientation = useCallback(async () => {
|
||||
await ScreenOrientation.lockAsync(
|
||||
ScreenOrientation.OrientationLock.LANDSCAPE,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const unlockOrientation = useCallback(async () => {
|
||||
await ScreenOrientation.lockAsync(
|
||||
ScreenOrientation.OrientationLock.PORTRAIT_UP,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const presentFullscreenPlayer = useCallback(async () => {
|
||||
if (internalVideoRef) {
|
||||
internalVideoRef.presentFullscreenPlayer();
|
||||
await lockOrientation();
|
||||
}
|
||||
}, [internalVideoRef, lockOrientation]);
|
||||
|
||||
const dismissFullscreenPlayer = useCallback(async () => {
|
||||
if (internalVideoRef) {
|
||||
internalVideoRef.dismissFullscreenPlayer();
|
||||
await unlockOrientation();
|
||||
}
|
||||
}, [internalVideoRef, unlockOrientation]);
|
||||
|
||||
const contextValue: PlayerContextProps = {
|
||||
videoRef: internalVideoRef,
|
||||
setVideoRef,
|
||||
lockOrientation,
|
||||
unlockOrientation,
|
||||
presentFullscreenPlayer,
|
||||
dismissFullscreenPlayer,
|
||||
};
|
||||
|
||||
return (
|
||||
<PlayerContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</PlayerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const usePlayer = (): PlayerContextProps => {
|
||||
const context = useContext(PlayerContext);
|
||||
if (!context) {
|
||||
throw new Error("usePlayer must be used within a PlayerProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
@@ -1,41 +0,0 @@
|
||||
import type { VideoRef } from "react-native-video";
|
||||
import { useCallback, useRef } from "react";
|
||||
import * as ScreenOrientation from "expo-screen-orientation";
|
||||
|
||||
export const usePlayer = () => {
|
||||
const ref = useRef<VideoRef>(null);
|
||||
|
||||
const lockOrientation = useCallback(async () => {
|
||||
await ScreenOrientation.lockAsync(
|
||||
ScreenOrientation.OrientationLock.LANDSCAPE,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const unlockOrientation = useCallback(async () => {
|
||||
await ScreenOrientation.lockAsync(
|
||||
ScreenOrientation.OrientationLock.PORTRAIT_UP,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const presentFullscreenPlayer = useCallback(async () => {
|
||||
if (ref.current) {
|
||||
ref.current.presentFullscreenPlayer();
|
||||
await lockOrientation();
|
||||
}
|
||||
}, [lockOrientation]);
|
||||
|
||||
const dismissFullscreenPlayer = useCallback(async () => {
|
||||
if (ref.current) {
|
||||
ref.current.dismissFullscreenPlayer();
|
||||
await unlockOrientation();
|
||||
}
|
||||
}, [unlockOrientation]);
|
||||
|
||||
return {
|
||||
videoRef: ref,
|
||||
lockOrientation,
|
||||
unlockOrientation,
|
||||
presentFullscreenPlayer,
|
||||
dismissFullscreenPlayer,
|
||||
} as const;
|
||||
};
|
11
apps/expo/src/types/globals.d.ts
vendored
Normal file
11
apps/expo/src/types/globals.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
declare module "*.svg" {
|
||||
import type { ImageSourcePropType } from "react-native";
|
||||
const content: ImageSourcePropType;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module "*.png" {
|
||||
import type { ImageSourcePropType } from "react-native";
|
||||
const content: ImageSourcePropType;
|
||||
export default content;
|
||||
}
|
Reference in New Issue
Block a user