"use client" import * as React from "react" import { MaximizeIcon, PauseIcon, PlayIcon, RotateCcwIcon, Volume1Icon, Volume2Icon, VolumeXIcon } from "lucide-react" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { cn } from "@/lib/utils" export type MediaTrack = { src: string kind?: React.TrackHTMLAttributes["kind"] srcLang?: string label?: string default?: boolean } export type MediaPlayerProps = Omit, "controls" | "children" | "onTimeUpdate" | "onVolumeChange"> & { src: string title?: React.ReactNode description?: React.ReactNode tracks?: MediaTrack[] actions?: React.ReactNode aspectRatio?: "video" | "square" | "wide" | string showMeta?: boolean onTimeChange?: (time: number, duration: number) => void onVolumeValueChange?: (volume: number, muted: boolean) => void } function formatTime(seconds: number) { if (!Number.isFinite(seconds) || seconds < 0) return "0:00" const minutes = Math.floor(seconds / 60) const rest = Math.floor(seconds % 60).toString().padStart(2, "0") return `${minutes}:${rest}` } function getAspectRatioClassName(value: NonNullable) { if (value === "video") return "aspect-video" if (value === "square") return "aspect-square" if (value === "wide") return "aspect-[21/9]" return "" } function MediaPlayer({ src, title, description, tracks = [], actions, aspectRatio = "video", showMeta = true, poster, autoPlay, muted: mutedProp, loop, playsInline = true, preload = "metadata", onTimeChange, onVolumeValueChange, className, ...props }: MediaPlayerProps) { const ref = React.useRef(null) const [playing, setPlaying] = React.useState(Boolean(autoPlay)) const [duration, setDuration] = React.useState(0) const [time, setTime] = React.useState(0) const [volume, setVolume] = React.useState(1) const [muted, setMuted] = React.useState(Boolean(mutedProp)) const [ready, setReady] = React.useState(false) const syncFromElement = React.useCallback(() => { const element = ref.current if (!element) return setPlaying(!element.paused) setDuration(element.duration || 0) setTime(element.currentTime || 0) setVolume(element.volume) setMuted(element.muted) onTimeChange?.(element.currentTime || 0, element.duration || 0) }, [onTimeChange]) const togglePlaying = async () => { const element = ref.current if (!element) return if (element.paused) await element.play() else element.pause() syncFromElement() } const seek = (value: number) => { const element = ref.current if (!element) return element.currentTime = value setTime(value) onTimeChange?.(value, duration) } const updateVolume = (nextVolume: number) => { const element = ref.current const safeVolume = Math.max(0, Math.min(1, nextVolume)) if (element) { element.volume = safeVolume element.muted = safeVolume === 0 ? true : muted } setVolume(safeVolume) const nextMuted = safeVolume === 0 ? true : muted setMuted(nextMuted) onVolumeValueChange?.(safeVolume, nextMuted) } const toggleMuted = () => { const element = ref.current const nextMuted = !muted if (element) element.muted = nextMuted setMuted(nextMuted) onVolumeValueChange?.(volume, nextMuted) } const VolumeIcon = muted || volume === 0 ? VolumeXIcon : volume < 0.6 ? Volume1Icon : Volume2Icon return (
{showMeta && (title || description || actions) ? (
{title ?

{title}

: null} {description ?

{description}

: null}
{!ready ? loading : null} {actions}
) : null}
seek(Number(event.target.value))} className="h-2 w-full cursor-pointer accent-primary" />
{formatTime(time)} / {formatTime(duration)}
updateVolume(Number(event.target.value))} className="h-2 w-24 cursor-pointer accent-primary" />
) } export { MediaPlayer }