fullscreen video

This commit is contained in:
Jelle van Snik 2023-01-08 16:23:42 +01:00
parent f93b9b5b0f
commit 218a14d5f6
7 changed files with 84 additions and 40 deletions

View File

@ -53,8 +53,9 @@ export const VideoPlayerDispatchContext = createContext<
export function VideoPlayerContextProvider(props: {
children: React.ReactNode;
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>(
videoPlayerContextReducer,
initial

View File

@ -9,7 +9,7 @@ const VideoPlayerInternals = forwardRef<HTMLVideoElement>((_, ref) => {
const video = useContext(VideoPlayerContext);
return (
<video controls ref={ref}>
<video ref={ref} className="h-full w-full">
{video.source ? <source src={video.source} type="video/mp4" /> : null}
</video>
);
@ -17,11 +17,17 @@ const VideoPlayerInternals = forwardRef<HTMLVideoElement>((_, ref) => {
export function VideoPlayer(props: VideoPlayerProps) {
const playerRef = useRef<HTMLVideoElement | null>(null);
const playerWrapperRef = useRef<HTMLDivElement | null>(null);
return (
<VideoPlayerContextProvider player={playerRef}>
<VideoPlayerInternals ref={playerRef} />
{props.children}
<VideoPlayerContextProvider player={playerRef} wrapper={playerWrapperRef}>
<div
className="relative aspect-video w-full bg-black"
ref={playerWrapperRef}
>
<VideoPlayerInternals ref={playerRef} />
<div className="absolute inset-0">{props.children}</div>
</div>
</VideoPlayerContextProvider>
);
}

View File

@ -1,27 +1,24 @@
// import { useCallback, useContext } from "react";
// import {
// VideoPlayerContext,
// VideoPlayerDispatchContext,
// } from "../VideoContext";
import { useCallback } from "react";
import { useVideoPlayerState } from "../VideoContext";
export function FullscreenControl() {
return <p>Hello world</p>;
// const dispatch = useContext(VideoPlayerDispatchContext);
// const video = useContext(VideoPlayerContext);
const { videoState } = useVideoPlayerState();
// const handleClick = useCallback(() => {
// dispatch({
// type: "FULLSCREEN",
// do: video.fullscreen ? "EXIT" : "ENTER",
// });
// }, [video, dispatch]);
const handleClick = useCallback(() => {
if (videoState.isFullscreen) videoState.exitFullscreen();
else videoState.enterFullscreen();
}, [videoState]);
// let text = "not fullscreen";
// if (video.fullscreen) text = "in fullscreen";
let text = "not fullscreen";
if (videoState.isFullscreen) text = "in fullscreen";
// return (
// <button type="button" onClick={handleClick}>
// {text}
// </button>
// );
return (
<button
className="m-1 rounded bg-denim-100 p-1 text-white hover:opacity-75"
type="button"
onClick={handleClick}
>
{text}
</button>
);
}

View File

@ -9,11 +9,15 @@ export function PauseControl() {
else videoState.play();
}, [videoState]);
let text = "paused";
if (videoState?.isPlaying) text = "playing";
let text =
videoState.isPlaying || videoState.isSeeking ? "playing" : "paused";
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}
</button>
);

View File

@ -1,14 +1,21 @@
export interface PlayerControls {
play(): void;
pause(): void;
exitFullscreen(): void;
enterFullscreen(): void;
}
export const initialControls: PlayerControls = {
play: () => null,
pause: () => null,
enterFullscreen: () => null,
exitFullscreen: () => null,
};
export function populateControls(player: HTMLVideoElement): PlayerControls {
export function populateControls(
player: HTMLVideoElement,
wrapper: HTMLDivElement
): PlayerControls {
return {
play() {
player.play();
@ -16,5 +23,13 @@ export function populateControls(player: HTMLVideoElement): PlayerControls {
pause() {
player.pause();
},
enterFullscreen() {
if (!document.fullscreenEnabled || document.fullscreenElement) return;
wrapper.requestFullscreen();
},
exitFullscreen() {
if (!document.fullscreenElement) return;
document.exitFullscreen();
},
};
}

View File

@ -8,11 +8,15 @@ import {
export type PlayerState = {
isPlaying: boolean;
isPaused: boolean;
isSeeking: boolean;
isFullscreen: boolean;
} & PlayerControls;
export const initialPlayerState = {
export const initialPlayerState: PlayerState = {
isPlaying: false,
isPaused: true,
isFullscreen: false,
isSeeking: false,
...initialControls,
};
@ -24,6 +28,8 @@ function readState(player: HTMLVideoElement, update: SetPlayer) {
};
state.isPaused = player.paused;
state.isPlaying = !player.paused;
state.isFullscreen = !!document.fullscreenElement;
state.isSeeking = player.seeking;
update(state);
}
@ -35,19 +41,32 @@ function registerListeners(player: HTMLVideoElement, update: SetPlayer) {
player.addEventListener("play", () => {
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);
useEffect(() => {
const player = ref.current;
if (player) {
const wrapper = wrapperRef.current;
if (player && wrapper) {
readState(player, setState);
registerListeners(player, setState);
setState((s) => ({ ...s, ...populateControls(player) }));
setState((s) => ({ ...s, ...populateControls(player, wrapper) }));
}
}, [ref]);
}, [ref, wrapperRef]);
return {
playerState: state,

View File

@ -7,10 +7,12 @@ import { VideoPlayer } from "@/components/video/VideoPlayer";
export function TestView() {
return (
<VideoPlayer>
<PauseControl />
<FullscreenControl />
<SourceControl source="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" />
</VideoPlayer>
<div className="w-[40rem] max-w-full">
<VideoPlayer>
<PauseControl />
<FullscreenControl />
<SourceControl source="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" />
</VideoPlayer>
</div>
);
}