mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-21 14:47:41 +01:00
fullscreen video
This commit is contained in:
parent
f93b9b5b0f
commit
218a14d5f6
7 changed files with 84 additions and 40 deletions
|
@ -53,8 +53,9 @@ export const VideoPlayerDispatchContext = createContext<
|
||||||
export function VideoPlayerContextProvider(props: {
|
export function VideoPlayerContextProvider(props: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
player: MutableRefObject<HTMLVideoElement | null>;
|
player: MutableRefObject<HTMLVideoElement | null>;
|
||||||
|
wrapper: MutableRefObject<HTMLDivElement | null>;
|
||||||
}) {
|
}) {
|
||||||
const { playerState } = useVideoPlayer(props.player);
|
const { playerState } = useVideoPlayer(props.player, props.wrapper);
|
||||||
const [videoData, dispatch] = useReducer<typeof videoPlayerContextReducer>(
|
const [videoData, dispatch] = useReducer<typeof videoPlayerContextReducer>(
|
||||||
videoPlayerContextReducer,
|
videoPlayerContextReducer,
|
||||||
initial
|
initial
|
||||||
|
|
|
@ -9,7 +9,7 @@ const VideoPlayerInternals = forwardRef<HTMLVideoElement>((_, ref) => {
|
||||||
const video = useContext(VideoPlayerContext);
|
const video = useContext(VideoPlayerContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video controls ref={ref}>
|
<video ref={ref} className="h-full w-full">
|
||||||
{video.source ? <source src={video.source} type="video/mp4" /> : null}
|
{video.source ? <source src={video.source} type="video/mp4" /> : null}
|
||||||
</video>
|
</video>
|
||||||
);
|
);
|
||||||
|
@ -17,11 +17,17 @@ const VideoPlayerInternals = forwardRef<HTMLVideoElement>((_, ref) => {
|
||||||
|
|
||||||
export function VideoPlayer(props: VideoPlayerProps) {
|
export function VideoPlayer(props: VideoPlayerProps) {
|
||||||
const playerRef = useRef<HTMLVideoElement | null>(null);
|
const playerRef = useRef<HTMLVideoElement | null>(null);
|
||||||
|
const playerWrapperRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VideoPlayerContextProvider player={playerRef}>
|
<VideoPlayerContextProvider player={playerRef} wrapper={playerWrapperRef}>
|
||||||
|
<div
|
||||||
|
className="relative aspect-video w-full bg-black"
|
||||||
|
ref={playerWrapperRef}
|
||||||
|
>
|
||||||
<VideoPlayerInternals ref={playerRef} />
|
<VideoPlayerInternals ref={playerRef} />
|
||||||
{props.children}
|
<div className="absolute inset-0">{props.children}</div>
|
||||||
|
</div>
|
||||||
</VideoPlayerContextProvider>
|
</VideoPlayerContextProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,24 @@
|
||||||
// import { useCallback, useContext } from "react";
|
import { useCallback } from "react";
|
||||||
// import {
|
import { useVideoPlayerState } from "../VideoContext";
|
||||||
// VideoPlayerContext,
|
|
||||||
// VideoPlayerDispatchContext,
|
|
||||||
// } from "../VideoContext";
|
|
||||||
|
|
||||||
export function FullscreenControl() {
|
export function FullscreenControl() {
|
||||||
return <p>Hello world</p>;
|
const { videoState } = useVideoPlayerState();
|
||||||
// const dispatch = useContext(VideoPlayerDispatchContext);
|
|
||||||
// const video = useContext(VideoPlayerContext);
|
|
||||||
|
|
||||||
// const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
// dispatch({
|
if (videoState.isFullscreen) videoState.exitFullscreen();
|
||||||
// type: "FULLSCREEN",
|
else videoState.enterFullscreen();
|
||||||
// do: video.fullscreen ? "EXIT" : "ENTER",
|
}, [videoState]);
|
||||||
// });
|
|
||||||
// }, [video, dispatch]);
|
|
||||||
|
|
||||||
// let text = "not fullscreen";
|
let text = "not fullscreen";
|
||||||
// if (video.fullscreen) text = "in fullscreen";
|
if (videoState.isFullscreen) text = "in fullscreen";
|
||||||
|
|
||||||
// return (
|
return (
|
||||||
// <button type="button" onClick={handleClick}>
|
<button
|
||||||
// {text}
|
className="m-1 rounded bg-denim-100 p-1 text-white hover:opacity-75"
|
||||||
// </button>
|
type="button"
|
||||||
// );
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,15 @@ export function PauseControl() {
|
||||||
else videoState.play();
|
else videoState.play();
|
||||||
}, [videoState]);
|
}, [videoState]);
|
||||||
|
|
||||||
let text = "paused";
|
let text =
|
||||||
if (videoState?.isPlaying) text = "playing";
|
videoState.isPlaying || videoState.isSeeking ? "playing" : "paused";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button type="button" onClick={handleClick}>
|
<button
|
||||||
|
className="m-1 rounded bg-denim-100 p-1 text-white hover:opacity-75"
|
||||||
|
type="button"
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
{text}
|
{text}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
export interface PlayerControls {
|
export interface PlayerControls {
|
||||||
play(): void;
|
play(): void;
|
||||||
pause(): void;
|
pause(): void;
|
||||||
|
exitFullscreen(): void;
|
||||||
|
enterFullscreen(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialControls: PlayerControls = {
|
export const initialControls: PlayerControls = {
|
||||||
play: () => null,
|
play: () => null,
|
||||||
pause: () => null,
|
pause: () => null,
|
||||||
|
enterFullscreen: () => null,
|
||||||
|
exitFullscreen: () => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function populateControls(player: HTMLVideoElement): PlayerControls {
|
export function populateControls(
|
||||||
|
player: HTMLVideoElement,
|
||||||
|
wrapper: HTMLDivElement
|
||||||
|
): PlayerControls {
|
||||||
return {
|
return {
|
||||||
play() {
|
play() {
|
||||||
player.play();
|
player.play();
|
||||||
|
@ -16,5 +23,13 @@ export function populateControls(player: HTMLVideoElement): PlayerControls {
|
||||||
pause() {
|
pause() {
|
||||||
player.pause();
|
player.pause();
|
||||||
},
|
},
|
||||||
|
enterFullscreen() {
|
||||||
|
if (!document.fullscreenEnabled || document.fullscreenElement) return;
|
||||||
|
wrapper.requestFullscreen();
|
||||||
|
},
|
||||||
|
exitFullscreen() {
|
||||||
|
if (!document.fullscreenElement) return;
|
||||||
|
document.exitFullscreen();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,15 @@ import {
|
||||||
export type PlayerState = {
|
export type PlayerState = {
|
||||||
isPlaying: boolean;
|
isPlaying: boolean;
|
||||||
isPaused: boolean;
|
isPaused: boolean;
|
||||||
|
isSeeking: boolean;
|
||||||
|
isFullscreen: boolean;
|
||||||
} & PlayerControls;
|
} & PlayerControls;
|
||||||
|
|
||||||
export const initialPlayerState = {
|
export const initialPlayerState: PlayerState = {
|
||||||
isPlaying: false,
|
isPlaying: false,
|
||||||
isPaused: true,
|
isPaused: true,
|
||||||
|
isFullscreen: false,
|
||||||
|
isSeeking: false,
|
||||||
...initialControls,
|
...initialControls,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,6 +28,8 @@ function readState(player: HTMLVideoElement, update: SetPlayer) {
|
||||||
};
|
};
|
||||||
state.isPaused = player.paused;
|
state.isPaused = player.paused;
|
||||||
state.isPlaying = !player.paused;
|
state.isPlaying = !player.paused;
|
||||||
|
state.isFullscreen = !!document.fullscreenElement;
|
||||||
|
state.isSeeking = player.seeking;
|
||||||
|
|
||||||
update(state);
|
update(state);
|
||||||
}
|
}
|
||||||
|
@ -35,19 +41,32 @@ function registerListeners(player: HTMLVideoElement, update: SetPlayer) {
|
||||||
player.addEventListener("play", () => {
|
player.addEventListener("play", () => {
|
||||||
update((s) => ({ ...s, isPaused: false, isPlaying: true }));
|
update((s) => ({ ...s, isPaused: false, isPlaying: true }));
|
||||||
});
|
});
|
||||||
|
player.addEventListener("seeking", () => {
|
||||||
|
update((s) => ({ ...s, isSeeking: true }));
|
||||||
|
});
|
||||||
|
player.addEventListener("seeked", () => {
|
||||||
|
update((s) => ({ ...s, isSeeking: false }));
|
||||||
|
});
|
||||||
|
document.addEventListener("fullscreenchange", () => {
|
||||||
|
update((s) => ({ ...s, isFullscreen: !!document.fullscreenElement }));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useVideoPlayer(ref: MutableRefObject<HTMLVideoElement | null>) {
|
export function useVideoPlayer(
|
||||||
|
ref: MutableRefObject<HTMLVideoElement | null>,
|
||||||
|
wrapperRef: MutableRefObject<HTMLDivElement | null>
|
||||||
|
) {
|
||||||
const [state, setState] = useState(initialPlayerState);
|
const [state, setState] = useState(initialPlayerState);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const player = ref.current;
|
const player = ref.current;
|
||||||
if (player) {
|
const wrapper = wrapperRef.current;
|
||||||
|
if (player && wrapper) {
|
||||||
readState(player, setState);
|
readState(player, setState);
|
||||||
registerListeners(player, setState);
|
registerListeners(player, setState);
|
||||||
setState((s) => ({ ...s, ...populateControls(player) }));
|
setState((s) => ({ ...s, ...populateControls(player, wrapper) }));
|
||||||
}
|
}
|
||||||
}, [ref]);
|
}, [ref, wrapperRef]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
playerState: state,
|
playerState: state,
|
||||||
|
|
|
@ -7,10 +7,12 @@ import { VideoPlayer } from "@/components/video/VideoPlayer";
|
||||||
|
|
||||||
export function TestView() {
|
export function TestView() {
|
||||||
return (
|
return (
|
||||||
|
<div className="w-[40rem] max-w-full">
|
||||||
<VideoPlayer>
|
<VideoPlayer>
|
||||||
<PauseControl />
|
<PauseControl />
|
||||||
<FullscreenControl />
|
<FullscreenControl />
|
||||||
<SourceControl source="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" />
|
<SourceControl source="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" />
|
||||||
</VideoPlayer>
|
</VideoPlayer>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue