{"version":3,"file":"VideoPlayer.cjs","names":[],"sources":["../../../src/components/VideoPlayer/VideoPlayer.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — `src` takes a URL or a Blob, and\n * every other prop is a facet of playing it: durationMs (a MediaRecorder WebM may\n * carry none), poster, captions, aspect ratio, the rate control and its presets,\n * pitch, fullscreen, autoPlay/loop/muted, onEnded/onError, plus the actions slot and\n * locale. The body is the transport state machine — object-URL lifecycle, seek, rate,\n * volume — around one video element ref.\n */\nimport { Maximize, Minimize, Pause, Play, Volume2, VolumeX } from \"lucide-react\";\nimport { useEffect, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { formatDuration } from \"@/audio/duration\";\nimport { useFullscreen } from \"@/hooks/use-fullscreen\";\nimport { useObjectUrl } from \"@/hooks/use-object-url\";\nimport { cn } from \"@/utils/cn\";\n\nimport { DEFAULT_PLAYBACK_RATES } from \"./playback-rates\";\nimport styles from \"./VideoPlayer.module.css\";\n\n/**\n * DOM attributes this component redefines.\n *\n * `onRateChange` is a real collision, not tidiness: React types it on every\n * element as the media `ratechange` handler, `ReactEventHandler`. Ours carries\n * the rate the viewer picked, and a `(rate: number) => void` is not assignable\n * to a handler that receives an event — same shape of clash as `Input`'s `size`\n * against `HTMLInputElement.size`. The DOM one is useless on a `<div>` anyway:\n * the event fires on the media element inside, which this component owns.\n */\ntype OverriddenDomProps = \"children\" | \"onRateChange\";\n\n/** One caption or subtitle file, rendered as a `<track>`. */\nexport interface VideoTextTrack {\n    /** URL of the WebVTT file. */\n    src: string;\n    /** BCP 47 language tag, e.g. `\"pt-BR\"`. */\n    srcLang: string;\n    /** Name shown in the browser's own track menu. */\n    label: string;\n    /** Track kind. Default `\"captions\"`. */\n    kind?: \"subtitles\" | \"captions\" | \"descriptions\" | \"chapters\" | \"metadata\";\n    /** Whether this track is on unless the viewer picks another. */\n    default?: boolean;\n}\n\nexport interface VideoPlayerProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n    /**\n     * What to play — a URL, or a `Blob`/`File` straight from a recorder.\n     *\n     * A `Blob` is wrapped in an object URL that is revoked when it changes or the\n     * component unmounts, so a session that records twenty clips does not leak\n     * twenty URLs for the lifetime of the tab.\n     */\n    src: string | Blob | null;\n    /**\n     * Known length in milliseconds.\n     *\n     * Pass it whenever you have it — a recording from `useVideoRecorder` always\n     * does. Without it the element's own `duration` is used, and a fresh\n     * `MediaRecorder` blob may report `Infinity` until it is probed for one.\n     */\n    durationMs?: number;\n    /** Still shown before playback. Pair it with `captureFrame` from `/imaging`. */\n    poster?: string;\n    /** Caption and subtitle files. */\n    tracks?: readonly VideoTextTrack[];\n    /** Frame shape while the video has no intrinsic size yet. Default `16 / 9`. */\n    aspectRatio?: number;\n    /**\n     * Playback rate. Controlled when passed with `onRateChange`.\n     *\n     * Written to both `playbackRate` and `defaultPlaybackRate`, because the\n     * element resets the first to the second when a source loads — a player that\n     * only writes `playbackRate` silently drops back to 1× on every clip change.\n     */\n    rate?: number;\n    /** Presets the control offers. `[]` hides it. Default {@link DEFAULT_PLAYBACK_RATES}. */\n    rates?: readonly number[];\n    /** Called with the rate the viewer picked. */\n    onRateChange?: (rate: number) => void;\n    /** Let the pitch rise with the rate (`preservesPitch = false`). Default `false`. */\n    shiftPitch?: boolean;\n    /** Offer a fullscreen button where the browser supports it. Default `true`. */\n    fullscreen?: boolean;\n    /** Start playing as soon as `src` is ready. Default `false`. */\n    autoPlay?: boolean;\n    /** Loop. Default `false`. */\n    loop?: boolean;\n    /** Start muted. Autoplay without a gesture needs this. Default `false`. */\n    muted?: boolean;\n    /** Locale for the labels. Default `\"pt-BR\"`. */\n    locale?: \"pt-BR\" | \"en\";\n    /** Rendered at the end of the control row — a download button, a delete button. */\n    actions?: ReactNode;\n    /** Fired when playback reaches the end. */\n    onEnded?: () => void;\n    /** Fired when the element reports a decode/network error. */\n    onError?: (error: unknown) => void;\n    /** No `src` yet, or playback not allowed. */\n    disabled?: boolean;\n}\n\nconst STRINGS = {\n    \"pt-BR\": {\n        play: \"Tocar\",\n        pause: \"Pausar\",\n        seek: \"Posição\",\n        mute: \"Silenciar\",\n        unmute: \"Ativar som\",\n        volume: \"Volume\",\n        rate: \"Velocidade\",\n        enterFullscreen: \"Tela cheia\",\n        exitFullscreen: \"Sair da tela cheia\",\n    },\n    en: {\n        play: \"Play\",\n        pause: \"Pause\",\n        seek: \"Seek\",\n        mute: \"Mute\",\n        unmute: \"Unmute\",\n        volume: \"Volume\",\n        rate: \"Speed\",\n        enterFullscreen: \"Full screen\",\n        exitFullscreen: \"Exit full screen\",\n    },\n} as const;\n\n/**\n * Label a rate the way a viewer reads it.\n *\n * @param rate The multiplier.\n * @param locale Which decimal separator to use.\n * @returns e.g. `\"1,5×\"` in pt-BR, `\"1.5×\"` in English.\n */\nfunction rateLabel(rate: number, locale: \"pt-BR\" | \"en\"): string {\n    return `${rate.toLocaleString(locale === \"en\" ? \"en-US\" : \"pt-BR\")}×`;\n}\n\n/**\n * Playback transport for one video: play/pause, seek, volume, speed, fullscreen.\n *\n * Built on a real `<video>` element, and deliberately **not** an overlay: the\n * controls sit in a bar under the frame rather than on top of it. Controls drawn\n * over video have to earn their contrast against arbitrary pixels, which no\n * `--tempest-*` token can promise — every text token in this SDK is resolved\n * against a known surface, and the two contrast defects this repo shipped were\n * both a text token used over a surface it was never checked against. A bar with\n * its own surface inherits the theme and is legible by construction.\n *\n * The seek bar, the volume slider and the speed picker are bare `<input>` and\n * `<select>` elements rather than the SDK's `Slider` and `Select`: those are form\n * fields, with a label row and a value badge, and a transport wants neither. The\n * native elements keep the keyboard and screen-reader behaviour that matters here\n * for free.\n *\n * @example\n * const rec = useVideoRecorder(screen.stream);\n * {rec.recording && (\n *     <VideoPlayer src={rec.recording.blob} durationMs={rec.recording.durationMs} />\n * )}\n */\nexport function VideoPlayer({\n    src,\n    durationMs,\n    poster,\n    tracks,\n    aspectRatio = 16 / 9,\n    rate,\n    rates = DEFAULT_PLAYBACK_RATES,\n    onRateChange,\n    shiftPitch = false,\n    fullscreen = true,\n    autoPlay = false,\n    loop = false,\n    muted = false,\n    locale = \"pt-BR\",\n    actions,\n    onEnded,\n    onError,\n    disabled = false,\n    className,\n    ...rest\n}: VideoPlayerProps) {\n    const strings = STRINGS[locale];\n    const shell = useRef<HTMLDivElement | null>(null);\n    const video = useRef<HTMLVideoElement | null>(null);\n    const probed = useRef(false);\n\n    const [playing, setPlaying] = useState(false);\n    const [currentMs, setCurrentMs] = useState(0);\n    const [elementMs, setElementMs] = useState(Number.POSITIVE_INFINITY);\n    const [ownRate, setOwnRate] = useState(rate ?? 1);\n    const [volume, setVolume] = useState(1);\n    const [silent, setSilent] = useState(muted);\n\n    const immersive = useFullscreen(shell);\n    const blobUrl = useObjectUrl(typeof src === \"string\" ? null : src);\n    const url = typeof src === \"string\" ? src : blobUrl;\n\n    const effectiveRate = rate ?? ownRate;\n    const totalMs = Number.isFinite(durationMs) ? (durationMs as number) : elementMs;\n    const seekable = Number.isFinite(totalMs) && totalMs > 0;\n\n    useEffect(() => {\n        setCurrentMs(0);\n        setPlaying(false);\n        setElementMs(Number.POSITIVE_INFINITY);\n        probed.current = false;\n    }, [url]);\n\n    /**\n     * Keep the rate on the element, and keep it across sources.\n     *\n     * `defaultPlaybackRate` is written alongside `playbackRate` because the load\n     * algorithm copies the former onto the latter: writing only `playbackRate`\n     * means the rate the viewer chose is silently lost on the next clip.\n     */\n    useEffect(() => {\n        const node = video.current;\n        if (!node) return;\n        node.defaultPlaybackRate = effectiveRate;\n        node.playbackRate = effectiveRate;\n        node.preservesPitch = !shiftPitch;\n    }, [effectiveRate, shiftPitch, url]);\n\n    useEffect(() => {\n        const node = video.current;\n        if (!node) return;\n        node.volume = volume;\n        node.muted = silent;\n    }, [volume, silent, url]);\n\n    /**\n     * Coax a duration out of an element that reports `Infinity`.\n     *\n     * Seeking past the end forces the browser to demux to the last frame, after\n     * which it knows the real length. It runs at most once per source, and only\n     * when the caller gave us no `durationMs`.\n     */\n    const handleMetadata = (): void => {\n        const node = video.current;\n        if (!node) return;\n        if (Number.isFinite(node.duration)) {\n            setElementMs(node.duration * 1000);\n            return;\n        }\n        if (probed.current || Number.isFinite(durationMs)) return;\n        probed.current = true;\n        const onProbed = (): void => {\n            node.removeEventListener(\"timeupdate\", onProbed);\n            if (Number.isFinite(node.duration)) setElementMs(node.duration * 1000);\n            node.currentTime = 0;\n        };\n        node.addEventListener(\"timeupdate\", onProbed);\n        node.currentTime = 1e101;\n    };\n\n    const toggle = (): void => {\n        const node = video.current;\n        if (!node || !url) return;\n        if (node.paused) {\n            void Promise.resolve(node.play())\n                .then(() => setPlaying(true))\n                .catch((error: unknown) => onError?.(error));\n            return;\n        }\n        node.pause();\n        setPlaying(false);\n    };\n\n    const seek = (ms: number): void => {\n        const node = video.current;\n        if (!node || !seekable) return;\n        node.currentTime = ms / 1000;\n        setCurrentMs(ms);\n    };\n\n    const changeRate = (next: number): void => {\n        if (rate === undefined) setOwnRate(next);\n        onRateChange?.(next);\n    };\n\n    return (\n        <div\n            ref={shell}\n            className={cn(styles.player, immersive.isFullscreen && styles.immersive, className)}\n            {...rest}\n        >\n            <div className={styles.frame} style={{ aspectRatio }}>\n                <video\n                    ref={video}\n                    className={styles.video}\n                    src={url ?? undefined}\n                    poster={poster}\n                    loop={loop}\n                    autoPlay={autoPlay}\n                    playsInline\n                    preload=\"metadata\"\n                    onLoadedMetadata={handleMetadata}\n                    onDurationChange={handleMetadata}\n                    onTimeUpdate={() => {\n                        const node = video.current;\n                        if (node) setCurrentMs(node.currentTime * 1000);\n                    }}\n                    onPlay={() => setPlaying(true)}\n                    onPause={() => setPlaying(false)}\n                    onVolumeChange={() => {\n                        const node = video.current;\n                        if (!node) return;\n                        setVolume(node.volume);\n                        setSilent(node.muted);\n                    }}\n                    onEnded={() => {\n                        setPlaying(false);\n                        onEnded?.();\n                    }}\n                    onError={(event) => onError?.(event)}\n                >\n                    {tracks?.map((track) => (\n                        <track\n                            key={`${track.kind ?? \"captions\"}-${track.srcLang}-${track.src}`}\n                            kind={track.kind ?? \"captions\"}\n                            src={track.src}\n                            srcLang={track.srcLang}\n                            label={track.label}\n                            default={track.default}\n                        />\n                    ))}\n                </video>\n            </div>\n\n            <div className={styles.chrome}>\n                <button\n                    type=\"button\"\n                    className={styles.transport}\n                    onClick={toggle}\n                    disabled={disabled || !url}\n                    aria-label={playing ? strings.pause : strings.play}\n                    title={playing ? strings.pause : strings.play}\n                >\n                    {playing ? <Pause size={16} aria-hidden /> : <Play size={16} aria-hidden />}\n                </button>\n\n                <input\n                    type=\"range\"\n                    className={styles.seek}\n                    min={0}\n                    max={seekable ? Math.round(totalMs) : 0}\n                    step={100}\n                    value={Math.min(Math.round(currentMs), seekable ? Math.round(totalMs) : 0)}\n                    onChange={(event) => seek(Number(event.target.value))}\n                    disabled={disabled || !seekable}\n                    aria-label={strings.seek}\n                    aria-valuetext={`${formatDuration(currentMs)} / ${formatDuration(totalMs)}`}\n                />\n\n                <span className={styles.time}>\n                    <span>{formatDuration(currentMs)}</span>\n                    <span aria-hidden=\"true\">/</span>\n                    <span>{formatDuration(totalMs)}</span>\n                </span>\n\n                <button\n                    type=\"button\"\n                    className={styles.icon}\n                    onClick={() => setSilent((was) => !was)}\n                    disabled={disabled}\n                    aria-label={silent ? strings.unmute : strings.mute}\n                    title={silent ? strings.unmute : strings.mute}\n                >\n                    {silent ? <VolumeX size={16} aria-hidden /> : <Volume2 size={16} aria-hidden />}\n                </button>\n\n                <input\n                    type=\"range\"\n                    className={styles.volume}\n                    min={0}\n                    max={1}\n                    step={0.05}\n                    value={silent ? 0 : volume}\n                    onChange={(event) => {\n                        const next = Number(event.target.value);\n                        setVolume(next);\n                        setSilent(next === 0);\n                    }}\n                    disabled={disabled}\n                    aria-label={strings.volume}\n                />\n\n                {rates.length > 0 && (\n                    <select\n                        className={styles.rate}\n                        value={effectiveRate}\n                        onChange={(event) => changeRate(Number(event.target.value))}\n                        disabled={disabled}\n                        aria-label={strings.rate}\n                        title={strings.rate}\n                    >\n                        {rates.map((option) => (\n                            <option key={option} value={option}>\n                                {rateLabel(option, locale)}\n                            </option>\n                        ))}\n                    </select>\n                )}\n\n                {fullscreen && immersive.supported && (\n                    <button\n                        type=\"button\"\n                        className={styles.icon}\n                        onClick={() => {\n                            void immersive.toggle().catch((error: unknown) => onError?.(error));\n                        }}\n                        disabled={disabled}\n                        aria-label={\n                            immersive.isFullscreen\n                                ? strings.exitFullscreen\n                                : strings.enterFullscreen\n                        }\n                        title={\n                            immersive.isFullscreen\n                                ? strings.exitFullscreen\n                                : strings.enterFullscreen\n                        }\n                    >\n                        {immersive.isFullscreen ? (\n                            <Minimize size={16} aria-hidden />\n                        ) : (\n                            <Maximize size={16} aria-hidden />\n                        )}\n                    </button>\n                )}\n\n                {actions && <span className={styles.actions}>{actions}</span>}\n            </div>\n        </div>\n    );\n}\n"],"mappings":"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"}