mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-24 19:11:49 +01:00
Backdrop + improved seeking
This commit is contained in:
parent
b43b8b19e4
commit
098f6af0ae
24
src/components/video/DecoratedVideoPlayer.tsx
Normal file
24
src/components/video/DecoratedVideoPlayer.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { BackdropControl } from "./controls/BackdropControl";
|
||||
import { FullscreenControl } from "./controls/FullscreenControl";
|
||||
import { LoadingControl } from "./controls/LoadingControl";
|
||||
import { PauseControl } from "./controls/PauseControl";
|
||||
import { ProgressControl } from "./controls/ProgressControl";
|
||||
import { TimeControl } from "./controls/TimeControl";
|
||||
import { VolumeControl } from "./controls/VolumeControl";
|
||||
import { VideoPlayer, VideoPlayerProps } from "./VideoPlayer";
|
||||
|
||||
export function DecoratedVideoPlayer(props: VideoPlayerProps) {
|
||||
return (
|
||||
<VideoPlayer autoPlay={props.autoPlay}>
|
||||
<BackdropControl>
|
||||
<PauseControl />
|
||||
<FullscreenControl />
|
||||
<ProgressControl />
|
||||
<VolumeControl />
|
||||
<LoadingControl />
|
||||
<TimeControl />
|
||||
</BackdropControl>
|
||||
{props.children}
|
||||
</VideoPlayer>
|
||||
);
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { forwardRef, useContext, useRef } from "react";
|
||||
import { VideoPlayerContext, VideoPlayerContextProvider } from "./VideoContext";
|
||||
|
||||
interface VideoPlayerProps {
|
||||
export interface VideoPlayerProps {
|
||||
autoPlay?: boolean;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
53
src/components/video/controls/BackdropControl.tsx
Normal file
53
src/components/video/controls/BackdropControl.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { useVideoPlayerState } from "../VideoContext";
|
||||
|
||||
interface BackdropControlProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function BackdropControl(props: BackdropControlProps) {
|
||||
const { videoState } = useVideoPlayerState();
|
||||
const [moved, setMoved] = useState(false);
|
||||
const timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const handleMouseMove = useCallback(() => {
|
||||
setMoved(true);
|
||||
if (timeout.current) clearTimeout(timeout.current);
|
||||
timeout.current = setTimeout(() => {
|
||||
setMoved(false);
|
||||
timeout.current = null;
|
||||
}, 3000);
|
||||
}, [timeout, setMoved]);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (videoState.isPlaying) videoState.pause();
|
||||
else videoState.play();
|
||||
}, [videoState]);
|
||||
|
||||
const showUI = moved || videoState.isPaused;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute inset-0 ${!showUI ? "cursor-none" : ""}`}
|
||||
onMouseMove={handleMouseMove}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div
|
||||
className={`absolute inset-0 bg-black bg-opacity-20 transition-opacity duration-200 ${
|
||||
!showUI ? "!opacity-0" : ""
|
||||
}`}
|
||||
/>
|
||||
<div
|
||||
className={`absolute inset-x-0 bottom-0 h-[30%] bg-gradient-to-t from-black to-transparent opacity-75 transition-opacity duration-200 ${
|
||||
!showUI ? "!opacity-0" : ""
|
||||
}`}
|
||||
/>
|
||||
<div
|
||||
className={`absolute inset-x-0 top-0 h-[30%] bg-gradient-to-b from-black to-transparent opacity-75 transition-opacity duration-200 ${
|
||||
!showUI ? "!opacity-0" : ""
|
||||
}`}
|
||||
/>
|
||||
<div className="absolute inset-0">{showUI ? props.children : null}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,34 +1,61 @@
|
||||
import { useCallback, useRef } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useVideoPlayerState } from "../VideoContext";
|
||||
|
||||
export function ProgressControl() {
|
||||
const { videoState } = useVideoPlayerState();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [mouseDown, setMouseDown] = useState<boolean>(false);
|
||||
const [progress, setProgress] = useState<number>(0);
|
||||
|
||||
const watchProgress = `${(
|
||||
let watchProgress = `${(
|
||||
(videoState.time / videoState.duration) *
|
||||
100
|
||||
).toFixed(2)}%`;
|
||||
if (mouseDown) watchProgress = `${progress}%`;
|
||||
|
||||
const bufferProgress = `${(
|
||||
(videoState.buffered / videoState.duration) *
|
||||
100
|
||||
).toFixed(2)}%`;
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e: React.MouseEvent<HTMLElement>) => {
|
||||
useEffect(() => {
|
||||
function mouseMove(ev: MouseEvent) {
|
||||
if (!mouseDown || !ref.current) return;
|
||||
const rect = ref.current.getBoundingClientRect();
|
||||
const pos = ((ev.pageX - rect.left) / ref.current.offsetWidth) * 100;
|
||||
setProgress(pos);
|
||||
}
|
||||
|
||||
function mouseUp(ev: MouseEvent) {
|
||||
if (!mouseDown) return;
|
||||
setMouseDown(false);
|
||||
document.body.removeAttribute("data-no-select");
|
||||
|
||||
if (!ref.current) return;
|
||||
const rect = ref.current.getBoundingClientRect();
|
||||
const pos = (e.pageX - rect.left) / ref.current.offsetWidth;
|
||||
const pos = (ev.pageX - rect.left) / ref.current.offsetWidth;
|
||||
videoState.setTime(pos * videoState.duration);
|
||||
},
|
||||
[videoState, ref]
|
||||
);
|
||||
}
|
||||
|
||||
document.addEventListener("mousemove", mouseMove);
|
||||
document.addEventListener("mouseup", mouseUp);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousemove", mouseMove);
|
||||
document.removeEventListener("mouseup", mouseUp);
|
||||
};
|
||||
}, [mouseDown, videoState]);
|
||||
|
||||
const handleMouseDown = useCallback(() => {
|
||||
setMouseDown(true);
|
||||
document.body.setAttribute("data-no-select", "true");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="relative m-1 my-4 h-4 w-48 overflow-hidden rounded-full border border-white bg-denim-100"
|
||||
onClick={handleClick}
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-y-0 left-0 bg-denim-700 opacity-50"
|
||||
|
@ -12,3 +12,7 @@ body {
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body[data-no-select] {
|
||||
user-select: none;
|
||||
}
|
||||
|
@ -1,28 +1,22 @@
|
||||
import { FullscreenControl } from "@/components/video/controls/FullscreenControl";
|
||||
import { LoadingControl } from "@/components/video/controls/LoadingControl";
|
||||
import { PauseControl } from "@/components/video/controls/PauseControl";
|
||||
import { ProgressControl } from "@/components/video/controls/ProgressControl";
|
||||
import { SourceControl } from "@/components/video/controls/SourceControl";
|
||||
import { TimeControl } from "@/components/video/controls/TimeControl";
|
||||
import { VolumeControl } from "@/components/video/controls/VolumeControl";
|
||||
import { VideoPlayer } from "@/components/video/VideoPlayer";
|
||||
import { DecoratedVideoPlayer } from "@/components/video/DecoratedVideoPlayer";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
// test videos: https://gist.github.com/jsturgis/3b19447b304616f18657
|
||||
|
||||
// TODO video todos:
|
||||
// - make pretty
|
||||
// - better seeking
|
||||
// - improve seekables
|
||||
// - error handling
|
||||
// - middle pause button + click to pause
|
||||
// - middle pause button
|
||||
// - improve pausing while seeking/buffering
|
||||
// - captions
|
||||
// - backdrop better click handling
|
||||
// - IOS support: (no volume, fullscreen video element instead of wrapper)
|
||||
// - IpadOS support: (fullscreen video wrapper should work, see (lookmovie.io) )
|
||||
// - HLS support: feature detection otherwise use HLS.js
|
||||
export function TestView() {
|
||||
const [show, setShow] = useState(false);
|
||||
const [show, setShow] = useState(true);
|
||||
const handleClick = useCallback(() => {
|
||||
setShow((v) => !v);
|
||||
}, [setShow]);
|
||||
@ -33,15 +27,9 @@ export function TestView() {
|
||||
|
||||
return (
|
||||
<div className="w-[40rem] max-w-full">
|
||||
<VideoPlayer autoPlay>
|
||||
<PauseControl />
|
||||
<FullscreenControl />
|
||||
<ProgressControl />
|
||||
<VolumeControl />
|
||||
<LoadingControl />
|
||||
<TimeControl />
|
||||
<DecoratedVideoPlayer>
|
||||
<SourceControl source="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4" />
|
||||
</VideoPlayer>
|
||||
</DecoratedVideoPlayer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user