Mute video by default and allow control of volume

This commit is contained in:
Nicolas Mowen 2024-03-13 15:47:55 -06:00
parent f9ed082e35
commit ed9bf9dce4
2 changed files with 91 additions and 2 deletions

View File

@ -17,8 +17,16 @@ import {
DropdownMenuRadioItem, DropdownMenuRadioItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "../ui/dropdown-menu"; } from "../ui/dropdown-menu";
import { MdForward10, MdReplay10 } from "react-icons/md"; import {
MdForward10,
MdReplay10,
MdVolumeDown,
MdVolumeMute,
MdVolumeOff,
MdVolumeUp,
} from "react-icons/md";
import useKeyboardListener from "@/hooks/use-keyboard-listener"; import useKeyboardListener from "@/hooks/use-keyboard-listener";
import { Slider } from "../ui/slider-volume";
const HLS_MIME_TYPE = "application/vnd.apple.mpegurl" as const; const HLS_MIME_TYPE = "application/vnd.apple.mpegurl" as const;
const unsupportedErrorCodes = [ const unsupportedErrorCodes = [
@ -86,6 +94,7 @@ export default function HlsVideoPlayer({
// controls // controls
const [isPlaying, setIsPlaying] = useState(true); const [isPlaying, setIsPlaying] = useState(true);
const [volume, setVolume] = useState(0.0);
const [mobileCtrlTimeout, setMobileCtrlTimeout] = useState<NodeJS.Timeout>(); const [mobileCtrlTimeout, setMobileCtrlTimeout] = useState<NodeJS.Timeout>();
const [controls, setControls] = useState(isMobile); const [controls, setControls] = useState(isMobile);
const [controlsOpen, setControlsOpen] = useState(false); const [controlsOpen, setControlsOpen] = useState(false);
@ -117,7 +126,11 @@ export default function HlsVideoPlayer({
break; break;
case "m": case "m":
if (down && !repeat && videoRef.current) { if (down && !repeat && videoRef.current) {
videoRef.current.muted = !videoRef.current.muted; if (videoRef.current.muted) {
videoRef.current.volume = 1;
} else {
videoRef.current.volume = 0;
}
} }
break; break;
case " ": case " ":
@ -166,6 +179,8 @@ export default function HlsVideoPlayer({
autoPlay autoPlay
controls={false} controls={false}
playsInline playsInline
muted={volume == 0.0}
onVolumeChange={() => setVolume(videoRef.current?.volume ?? 0.0)}
onPlay={() => { onPlay={() => {
setIsPlaying(true); setIsPlaying(true);
@ -202,6 +217,7 @@ export default function HlsVideoPlayer({
<VideoControls <VideoControls
video={videoRef.current} video={videoRef.current}
isPlaying={isPlaying} isPlaying={isPlaying}
volume={volume}
show={controls} show={controls}
controlsOpen={controlsOpen} controlsOpen={controlsOpen}
setControlsOpen={setControlsOpen} setControlsOpen={setControlsOpen}
@ -214,6 +230,7 @@ export default function HlsVideoPlayer({
type VideoControlsProps = { type VideoControlsProps = {
video: HTMLVideoElement | null; video: HTMLVideoElement | null;
isPlaying: boolean; isPlaying: boolean;
volume: number;
show: boolean; show: boolean;
controlsOpen: boolean; controlsOpen: boolean;
setControlsOpen: (open: boolean) => void; setControlsOpen: (open: boolean) => void;
@ -221,6 +238,7 @@ type VideoControlsProps = {
function VideoControls({ function VideoControls({
video, video,
isPlaying, isPlaying,
volume,
show, show,
controlsOpen, controlsOpen,
setControlsOpen, setControlsOpen,
@ -280,6 +298,21 @@ function VideoControls({
[isPlaying, video], [isPlaying, video],
); );
// volume control
const [showVolume, setShowVolume] = useState(false);
const VolumeIcon = useMemo(() => {
if (volume == 0) {
return MdVolumeOff;
} else if (volume <= 0.33) {
return MdVolumeMute;
} else if (volume <= 0.67) {
return MdVolumeDown;
} else {
return MdVolumeUp;
}
}, [volume]);
if (!video || !show) { if (!video || !show) {
return; return;
} }
@ -288,6 +321,36 @@ function VideoControls({
<div <div
className={`absolute bottom-5 left-1/2 -translate-x-1/2 px-4 py-2 flex justify-between items-center gap-8 text-white z-50 bg-black bg-opacity-60 rounded-lg`} className={`absolute bottom-5 left-1/2 -translate-x-1/2 px-4 py-2 flex justify-between items-center gap-8 text-white z-50 bg-black bg-opacity-60 rounded-lg`}
> >
<div
className="flex justify-normal items-center gap-2"
onMouseOver={isDesktop ? () => setShowVolume(true) : undefined}
onMouseOut={isDesktop ? () => setShowVolume(false) : undefined}
onClick={(e) => {
e.stopPropagation();
if (isDesktop) {
if (video.muted) {
video.volume = 1;
} else {
video.volume = 0;
}
} else {
setShowVolume(!showVolume);
}
}}
>
<VolumeIcon className="size-5" />
{showVolume && (
<Slider
className="w-20"
value={[volume]}
min={0}
max={1}
step={0.05}
onValueChange={(value) => (video.volume = value[0])}
/>
)}
</div>
<MdReplay10 className="size-5 cursor-pointer" onClick={onReplay} /> <MdReplay10 className="size-5 cursor-pointer" onClick={onReplay} />
<div className="cursor-pointer" onClick={onTogglePlay}> <div className="cursor-pointer" onClick={onTogglePlay}>
{isPlaying ? ( {isPlaying ? (

View File

@ -0,0 +1,26 @@
import * as React from "react";
import * as SliderPrimitive from "@radix-ui/react-slider";
import { cn } from "@/lib/utils";
const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => (
<SliderPrimitive.Root
ref={ref}
className={cn(
"relative flex w-full touch-none select-none items-center",
className,
)}
{...props}
>
<SliderPrimitive.Track className="relative h-1 w-full grow overflow-hidden rounded-full bg-muted">
<SliderPrimitive.Range className="absolute h-full bg-white" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-3 w-3 rounded-full bg-white ring-white focus:ring-white disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root>
));
Slider.displayName = SliderPrimitive.Root.displayName;
export { Slider };