watch: improved overlay opacity animation
continuous-integration/drone/push Build is passing Details

nextui
Guus van Meerveld 1 month ago
parent a2d9a449db
commit 8a6097302b

@ -1,6 +1,7 @@
"use client"; "use client";
import screenfull from "screenfull"; import screenfull from "screenfull";
import { useDebounce } from "use-debounce";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook"; import { useHotkeys } from "react-hotkeys-hook";
@ -10,6 +11,7 @@ import {
FiVolumeX as MutedIcon, FiVolumeX as MutedIcon,
FiPause as PauseIcon, FiPause as PauseIcon,
FiPlay as PlayIcon, FiPlay as PlayIcon,
FiSettings as SettingsIcon,
FiVolume as VolumeIcon, FiVolume as VolumeIcon,
FiVolume1 as VolumeIcon1, FiVolume1 as VolumeIcon1,
FiVolume2 as VolumeIcon2 FiVolume2 as VolumeIcon2
@ -26,15 +28,16 @@ import {
import { Slider } from "@nextui-org/slider"; import { Slider } from "@nextui-org/slider";
import { HlsStream, Stream, StreamType } from "@/client/typings/stream"; import { HlsStream, Stream, StreamType } from "@/client/typings/stream";
import { Video } from "@/client/typings/video";
import formatDuration from "@/utils/formatDuration"; import formatDuration from "@/utils/formatDuration";
import { Component } from "@/typings/component"; import { Component } from "@/typings/component";
export const Player: Component<{ export const Player: Component<{
streams: Stream[]; streams: Stream[];
initialDuration: number; video: Video;
initialTimestamp?: number; initialTimestamp?: number;
}> = ({ streams, initialTimestamp, initialDuration }) => { }> = ({ streams, initialTimestamp, video }) => {
const stream = streams.find((stream) => stream.type === StreamType.Hls); const stream = streams.find((stream) => stream.type === StreamType.Hls);
const playerRef = useRef<ReactPlayer>(null); const playerRef = useRef<ReactPlayer>(null);
@ -65,7 +68,20 @@ export const Player: Component<{
]; ];
}, []); }, []);
const [duration, setDuration] = useState(initialDuration); const [showOverlay, setShowOverlay] = useState(false);
const [mouseOnOverlay, setMouseOnOverlay] = useState([0, 0]);
useEffect(() => {
setShowOverlay(true);
}, [mouseOnOverlay]);
const [mouseLastMoved] = useDebounce(mouseOnOverlay, 2000);
useEffect(() => {
setShowOverlay(false);
}, [mouseLastMoved]);
const [duration, setDuration] = useState(video.duration / 1000);
const [progress, setProgress] = useState(0); const [progress, setProgress] = useState(0);
const [loaded, setLoaded] = useState(0); const [loaded, setLoaded] = useState(0);
const [maximized, setMaximized] = useState(false); const [maximized, setMaximized] = useState(false);
@ -79,7 +95,11 @@ export const Player: Component<{
const seekForward = useCallback( const seekForward = useCallback(
(seconds: number) => { (seconds: number) => {
if (duration >= seconds) { if (duration >= seconds) {
const newProgress = progress + seconds / duration; let newProgress = progress + seconds / duration;
if (newProgress <= 0) newProgress = 0;
if (newProgress >= duration) newProgress = duration;
setUserSetProgress(newProgress); setUserSetProgress(newProgress);
setProgress(newProgress); setProgress(newProgress);
@ -188,11 +208,28 @@ export const Player: Component<{
<> <>
<div className="relative" style={{ paddingTop: `${100 / (16 / 9)}%` }}> <div className="relative" style={{ paddingTop: `${100 / (16 / 9)}%` }}>
<div id={videoPlayerId}> <div id={videoPlayerId}>
<div className="flex flex-col w-full h-full absolute bottom-0 z-10 transition-opacity ease-in duration-[2000ms] opacity-0 hover:opacity-100"> <div
className="flex flex-col w-full h-full absolute bottom-0 z-10 transition-opacity ease-in duration-[2000ms]"
style={{
opacity: showOverlay ? 100 : 0,
cursor: showOverlay ? "initial" : "none"
}}
onMouseMove={(e) => setMouseOnOverlay([e.movementX, e.movementY])}
>
<div
style={{
display: maximized ? "flex" : "none",
background:
"linear-gradient(180deg, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 100%)"
}}
className="flex-row gap-2 p-4"
>
<p className="text-3xl">{video.title}</p>
</div>
<div <div
className="flex-1" className="flex-1"
onClick={() => setPlaying((state) => !state)} onClick={() => setPlaying((state) => !state)}
></div> />
<div <div
className="flex flex-col gap-1 pb-2 px-4" className="flex flex-col gap-1 pb-2 px-4"
style={{ style={{
@ -270,6 +307,27 @@ export const Player: Component<{
</p> </p>
</div> </div>
<div className="flex flex-row gap-2 items-center"> <div className="flex flex-row gap-2 items-center">
<Dropdown>
<DropdownTrigger>
<Button className="text-xl" isIconOnly variant="light">
<SettingsIcon />
</Button>
</DropdownTrigger>
<DropdownMenu
aria-label="Playback rate menu"
onAction={(key) => {
setPlaybackRate(parseFloat(key as string));
}}
items={playbackRateMenuItems}
>
{(item) => (
<DropdownItem key={item.key}>
{item.label}x
</DropdownItem>
)}
</DropdownMenu>
</Dropdown>
<Dropdown> <Dropdown>
<DropdownTrigger> <DropdownTrigger>
<Button className="text-xl" variant="light"> <Button className="text-xl" variant="light">

@ -86,7 +86,7 @@ export const Watch: Component = () => {
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<Player <Player
initialTimestamp={timestamp} initialTimestamp={timestamp}
initialDuration={data.video.duration / 1000} video={data.video}
streams={data.streams} streams={data.streams}
/> />
<div className="flex flex-col xl:flex-row gap-4"> <div className="flex flex-col xl:flex-row gap-4">

Loading…
Cancel
Save