mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
* refactor: migrate eslint to v9 * chore: lint * chore: update eslint command * chore: fix lint warnings * chore: separate lint and lint:fix * chore: exclude contentlayer generated code * fix(scripts): add missing await
151 lines
4.1 KiB
TypeScript
151 lines
4.1 KiB
TypeScript
"use client";
|
|
|
|
import {useInView} from "framer-motion";
|
|
import {useRef, FC, useEffect, useState, useCallback} from "react";
|
|
import {Button, cn, Spinner, Tooltip} from "@heroui/react";
|
|
|
|
import {PlayBoldIcon, PauseBoldIcon} from "@/components/icons";
|
|
import {RotateLeftLinearIcon} from "@/components/icons";
|
|
|
|
interface VideoInViewProps {
|
|
src: string;
|
|
playMode?: "auto" | "manual";
|
|
autoPlay?: boolean;
|
|
poster?: string;
|
|
width?: number;
|
|
height?: number;
|
|
controls?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
export const VideoInView: FC<VideoInViewProps> = ({
|
|
src,
|
|
width,
|
|
height,
|
|
poster,
|
|
autoPlay = true,
|
|
playMode = "manual",
|
|
controls = false,
|
|
className,
|
|
}) => {
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
|
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
|
|
const isVisible = useInView(videoRef);
|
|
|
|
// play video when it is visible and playMode is auto
|
|
useEffect(() => {
|
|
if (playMode !== "auto") {
|
|
return;
|
|
}
|
|
|
|
if (isVisible) {
|
|
videoRef.current?.play();
|
|
} else {
|
|
videoRef.current?.pause();
|
|
}
|
|
}, [isVisible]);
|
|
|
|
const handleCanPlay = useCallback(() => {
|
|
setIsLoading(false);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const videoEl = videoRef.current;
|
|
|
|
if (videoEl) {
|
|
if (videoEl.readyState > 3) {
|
|
// HAVE_FUTURE_DATA: enough data to start playing
|
|
handleCanPlay();
|
|
} else {
|
|
videoEl.addEventListener("canplaythrough", handleCanPlay);
|
|
}
|
|
|
|
// Cleanup the event listener
|
|
return () => {
|
|
videoEl.removeEventListener("canplaythrough", handleCanPlay);
|
|
};
|
|
}
|
|
}, []);
|
|
|
|
const onRestart = useCallback(() => {
|
|
if (videoRef.current) {
|
|
videoRef.current.currentTime = 0;
|
|
videoRef.current.play();
|
|
setIsPlaying(true);
|
|
}
|
|
}, []);
|
|
|
|
const onTogglePlay = useCallback(() => {
|
|
if (videoRef.current) {
|
|
if (!isPlaying) {
|
|
videoRef.current.play();
|
|
} else {
|
|
videoRef.current.pause();
|
|
}
|
|
|
|
setIsPlaying((v) => !v);
|
|
}
|
|
}, [isPlaying]);
|
|
|
|
return (
|
|
<div
|
|
className="relative data-[playing=true]:after:opacity-0 data-[playing=true]:after:z-[-1] after:content-[''] after:absolute after:inset-0 after:bg-black/30 after:z-20 after:transition-opacity"
|
|
data-playing={isPlaying}
|
|
>
|
|
{isLoading && (
|
|
<Spinner
|
|
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
|
|
color="secondary"
|
|
size="lg"
|
|
/>
|
|
)}
|
|
<Tooltip content={isPlaying ? "Pause" : "Play"} delay={1000}>
|
|
<Button
|
|
isIconOnly
|
|
className="absolute z-50 right-12 top-3 border-1 border-transparent bg-transparent before:bg-white/10 before:content-[''] before:block before:z-[-1] before:absolute before:inset-0 before:backdrop-blur-md before:backdrop-saturate-100 before:rounded-lg"
|
|
size="sm"
|
|
variant="bordered"
|
|
onPress={onTogglePlay}
|
|
>
|
|
{isPlaying ? (
|
|
<PauseBoldIcon className="text-white" size={16} />
|
|
) : (
|
|
<PlayBoldIcon className="text-white" size={16} />
|
|
)}
|
|
</Button>
|
|
</Tooltip>
|
|
|
|
<Tooltip content="Restart" delay={1000}>
|
|
<Button
|
|
isIconOnly
|
|
className="absolute z-50 right-3 top-3 border-1 border-transparent bg-transparent before:bg-white/10 before:content-[''] before:block before:z-[-1] before:absolute before:inset-0 before:backdrop-blur-md before:backdrop-saturate-100 before:rounded-lg"
|
|
size="sm"
|
|
variant="bordered"
|
|
onPress={onRestart}
|
|
>
|
|
<RotateLeftLinearIcon className="text-white" size={16} />
|
|
</Button>
|
|
</Tooltip>
|
|
<video
|
|
ref={videoRef}
|
|
loop
|
|
muted
|
|
autoPlay={autoPlay && playMode === "auto"}
|
|
className={cn(
|
|
"w-full border border-transparent dark:border-default-200/50 object-fit rounded-xl shadow-lg",
|
|
className,
|
|
)}
|
|
controls={controls}
|
|
height={height}
|
|
poster={poster}
|
|
src={src}
|
|
width={width}
|
|
onCanPlay={handleCanPlay}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|