@ttt-productions/media-viewer 0.10.17 → 0.10.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"audio-player-chrome.d.ts","sourceRoot":"","sources":["../../src/react/audio-player-chrome.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAa/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AA+B5D,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACnD,uEAAuE;IACvE,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAChC,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,cAAuB,EACvB,UAAgC,EAChC,YAAY,GACb,EAAE,sBAAsB,qBAoVxB"}
1
+ {"version":3,"file":"audio-player-chrome.d.ts","sourceRoot":"","sources":["../../src/react/audio-player-chrome.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAa/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AA+C5D,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACnD,uEAAuE;IACvE,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAChC,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,cAAuB,EACvB,UAAgC,EAChC,YAAY,GACb,EAAE,sBAAsB,qBAqVxB"}
@@ -11,7 +11,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  // is best-effort throughout: if anything throws, the player still plays and
12
12
  // only the visualizer goes dark. `createMediaElementSource` is once-per-
13
13
  // element, so the graph is keyed to the element instance and rebuilt only if
14
- // the element itself changes.
14
+ // the element itself changes. The graph is built only for audio Web Audio can
15
+ // hear (`graphCanHear`): a cross-origin file fetched without CORS reaches the
16
+ // graph as silence, and once routed the element's own output is that silence,
17
+ // so such a file plays straight to the speakers with the visualizer idle.
15
18
  import * as React from 'react';
16
19
  import { Pause, Play, Volume2, VolumeX, ChevronDown, ChevronUp, Activity, BarChart3, } from 'lucide-react';
17
20
  import { Spinner } from '@ttt-productions/ui-core/react';
@@ -38,6 +41,24 @@ function writePref(persistKey, suffix, value) {
38
41
  /* storage unavailable (private mode, SSR) — session-only prefs */
39
42
  }
40
43
  }
44
+ /**
45
+ * Whether Web Audio can hear the element's current file: one read with CORS (`crossOrigin` set),
46
+ * a local `blob:` / `data:` URL, or a same-origin file.
47
+ */
48
+ function graphCanHear(el) {
49
+ if (el.crossOrigin)
50
+ return true;
51
+ const src = el.currentSrc || el.src;
52
+ if (!src)
53
+ return false;
54
+ try {
55
+ const u = new URL(src, window.location.href);
56
+ return u.protocol === 'blob:' || u.protocol === 'data:' || u.origin === window.location.origin;
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
41
62
  function formatTime(seconds) {
42
63
  if (!Number.isFinite(seconds) || seconds < 0)
43
64
  return '0:00';
@@ -91,6 +112,8 @@ export function AudioPlayerChrome({ audioRef, visualizerMode = 'bars', persistKe
91
112
  }
92
113
  return;
93
114
  }
115
+ if (!graphCanHear(el))
116
+ return;
94
117
  teardownGraph();
95
118
  const AudioCtx = window.AudioContext ||
96
119
  window.webkitAudioContext;
@@ -1 +1 @@
1
- {"version":3,"file":"audio-player-chrome.js","sourceRoot":"","sources":["../../src/react/audio-player-chrome.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\n// Custom audio player chrome — the one owned control surface used by every\n// AudioViewer. Owns: play/pause, seek, time readout, mute/volume, and\n// the visualizer panel (shared engine in ../visualizer.ts) with minimize and\n// line/bars mode toggles. The media ELEMENT (loading, errors, playback API)\n// stays owned by AudioViewer — this component only reads/drives it.\n//\n// Web Audio wiring is created lazily on the first `play` event (autoplay\n// policies suspend fresh AudioContexts; a user-gesture play resumes them) and\n// is best-effort throughout: if anything throws, the player still plays and\n// only the visualizer goes dark. `createMediaElementSource` is once-per-\n// element, so the graph is keyed to the element instance and rebuilt only if\n// the element itself changes.\n\nimport * as React from 'react';\nimport {\n Pause,\n Play,\n Volume2,\n VolumeX,\n ChevronDown,\n ChevronUp,\n Activity,\n BarChart3,\n} from 'lucide-react';\nimport { Spinner } from '@ttt-productions/ui-core/react';\nimport { startWaveformLoop, drawIdleFrame } from '../visualizer.js';\nimport type { AudioVisualizerMode } from '../visualizer.js';\nimport { resolveInfiniteDuration } from '../media-duration.js';\n\nconst DEFAULT_PERSIST_KEY = 'mv-audio-player';\n\nfunction readPref(persistKey: string | null, suffix: string): string | null {\n if (!persistKey) return null;\n try {\n return window.localStorage.getItem(`${persistKey}:${suffix}`);\n } catch {\n return null;\n }\n}\n\nfunction writePref(persistKey: string | null, suffix: string, value: string): void {\n if (!persistKey) return;\n try {\n window.localStorage.setItem(`${persistKey}:${suffix}`, value);\n } catch {\n /* storage unavailable (private mode, SSR) — session-only prefs */\n }\n}\n\nfunction formatTime(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return '0:00';\n const whole = Math.floor(seconds);\n const m = Math.floor(whole / 60);\n const s = whole % 60;\n return `${m}:${s.toString().padStart(2, '0')}`;\n}\n\nexport type AudioPlayerChromeProps = {\n audioRef: React.RefObject<HTMLAudioElement | null>;\n /** Initial visualizer mode; a persisted user choice wins over this. */\n visualizerMode?: AudioVisualizerMode;\n /** localStorage key base for minimize/mode prefs; `null` disables persistence. */\n persistKey?: string | null;\n /** App-injected action buttons, rendered at the end of the control row. */\n extraActions?: React.ReactNode;\n};\n\nexport function AudioPlayerChrome({\n audioRef,\n visualizerMode = 'bars',\n persistKey = DEFAULT_PERSIST_KEY,\n extraActions,\n}: AudioPlayerChromeProps) {\n const [isPlaying, setIsPlaying] = React.useState(false);\n // Playback is waiting on data (initial buffer, a stall, or a seek landing).\n const [buffering, setBuffering] = React.useState(false);\n const [currentTime, setCurrentTime] = React.useState(0);\n const [duration, setDuration] = React.useState(0);\n const [isMuted, setIsMuted] = React.useState(false);\n const [volume, setVolume] = React.useState(1);\n const [minimized, setMinimized] = React.useState<boolean>(\n () => readPref(persistKey, 'minimized') === '1',\n );\n const [mode, setMode] = React.useState<AudioVisualizerMode>(() => {\n const stored = readPref(persistKey, 'mode');\n return stored === 'line' || stored === 'bars' ? stored : visualizerMode;\n });\n\n // While the user drags the seek slider, timeupdate must not fight the thumb.\n const [scrubTime, setScrubTime] = React.useState<number | null>(null);\n\n const canvasRef = React.useRef<HTMLCanvasElement | null>(null);\n const modeRef = React.useRef(mode);\n modeRef.current = mode;\n\n // Web Audio graph (lazy, keyed to the element instance).\n const audioCtxRef = React.useRef<AudioContext | null>(null);\n const sourceElRef = React.useRef<HTMLAudioElement | null>(null);\n const analyserRef = React.useRef<AnalyserNode | null>(null);\n const stopLoopRef = React.useRef<(() => void) | null>(null);\n\n const stopLoop = React.useCallback(() => {\n stopLoopRef.current?.();\n stopLoopRef.current = null;\n }, []);\n\n const teardownGraph = React.useCallback(() => {\n stopLoop();\n analyserRef.current = null;\n sourceElRef.current = null;\n const ctx = audioCtxRef.current;\n audioCtxRef.current = null;\n if (ctx && ctx.state !== 'closed') {\n ctx.close().catch(() => {});\n }\n }, [stopLoop]);\n\n const ensureGraph = React.useCallback(\n (el: HTMLAudioElement) => {\n try {\n if (sourceElRef.current === el && audioCtxRef.current) {\n if (audioCtxRef.current.state === 'suspended') {\n audioCtxRef.current.resume().catch(() => {});\n }\n return;\n }\n teardownGraph();\n const AudioCtx: typeof AudioContext =\n (window as unknown as { AudioContext?: typeof AudioContext }).AudioContext ||\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext!;\n if (!AudioCtx) return;\n const ctx = new AudioCtx();\n const source = ctx.createMediaElementSource(el);\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 256;\n // Element audio now routes through the graph — destination connection\n // is REQUIRED or playback goes silent.\n source.connect(analyser);\n analyser.connect(ctx.destination);\n audioCtxRef.current = ctx;\n sourceElRef.current = el;\n analyserRef.current = analyser;\n if (ctx.state === 'suspended') {\n ctx.resume().catch(() => {});\n }\n } catch {\n // Best-effort: playback works without the visualizer.\n analyserRef.current = null;\n }\n },\n [teardownGraph],\n );\n\n const startLoop = React.useCallback(() => {\n if (stopLoopRef.current) return;\n const analyser = analyserRef.current;\n if (!analyser) return;\n stopLoopRef.current = startWaveformLoop({\n analyser,\n getCanvas: () => canvasRef.current,\n mode: () => modeRef.current,\n });\n }, []);\n\n // Element event subscriptions. The element mounts in the same commit as this\n // chrome (AudioViewer renders both once loading starts), so the ref is set\n // by the time this effect runs.\n React.useEffect(() => {\n const el = audioRef.current;\n if (!el) return;\n\n // MediaRecorder blobs report duration Infinity until seeked (Chromium webm\n // quirk) — without this the readout sticks at \"0:01 / 0:00\". Learn the real\n // duration via the shared seek workaround; paused-only so an actively\n // playing element is never seek-jumped, and one attempt per element so a\n // workaround-triggered durationchange can never re-enter it.\n let durationReconcileAttempted = false;\n const reconcileInfiniteDuration = () => {\n if (durationReconcileAttempted || Number.isFinite(el.duration) || !el.paused) return;\n durationReconcileAttempted = true;\n void resolveInfiniteDuration(el).then((real) => {\n if (real !== null) setDuration(real);\n });\n };\n\n const syncFromElement = () => {\n setIsPlaying(!el.paused && !el.ended);\n setIsMuted(el.muted);\n setVolume(el.volume);\n if (Number.isFinite(el.duration)) setDuration(el.duration);\n setCurrentTime(el.currentTime);\n };\n syncFromElement();\n if (el.readyState >= 1) reconcileInfiniteDuration();\n\n const onPlay = () => {\n setIsPlaying(true);\n ensureGraph(el);\n startLoop();\n };\n const onPause = () => {\n setIsPlaying(false);\n stopLoop();\n };\n const onEnded = () => {\n setIsPlaying(false);\n stopLoop();\n };\n const onTimeUpdate = () => setCurrentTime(el.currentTime);\n const onDurationChange = () => {\n if (Number.isFinite(el.duration)) setDuration(el.duration);\n else reconcileInfiniteDuration();\n };\n const onVolumeChange = () => {\n setIsMuted(el.muted);\n setVolume(el.volume);\n };\n const onWaiting = () => setBuffering(true);\n const onReady = () => setBuffering(false);\n\n el.addEventListener('play', onPlay);\n el.addEventListener('pause', onPause);\n el.addEventListener('ended', onEnded);\n el.addEventListener('timeupdate', onTimeUpdate);\n el.addEventListener('durationchange', onDurationChange);\n el.addEventListener('loadedmetadata', onDurationChange);\n el.addEventListener('volumechange', onVolumeChange);\n el.addEventListener('waiting', onWaiting);\n el.addEventListener('seeking', onWaiting);\n el.addEventListener('playing', onReady);\n el.addEventListener('seeked', onReady);\n el.addEventListener('canplay', onReady);\n el.addEventListener('pause', onReady);\n el.addEventListener('ended', onReady);\n el.addEventListener('error', onReady);\n return () => {\n el.removeEventListener('play', onPlay);\n el.removeEventListener('pause', onPause);\n el.removeEventListener('ended', onEnded);\n el.removeEventListener('timeupdate', onTimeUpdate);\n el.removeEventListener('durationchange', onDurationChange);\n el.removeEventListener('loadedmetadata', onDurationChange);\n el.removeEventListener('volumechange', onVolumeChange);\n el.removeEventListener('waiting', onWaiting);\n el.removeEventListener('seeking', onWaiting);\n el.removeEventListener('playing', onReady);\n el.removeEventListener('seeked', onReady);\n el.removeEventListener('canplay', onReady);\n el.removeEventListener('pause', onReady);\n el.removeEventListener('ended', onReady);\n el.removeEventListener('error', onReady);\n };\n }, [audioRef, ensureGraph, startLoop, stopLoop]);\n\n // Full teardown when the chrome unmounts.\n React.useEffect(() => teardownGraph, [teardownGraph]);\n\n // Idle frame whenever the visualizer is visible but not animating.\n React.useEffect(() => {\n if (minimized || isPlaying) return;\n const canvas = canvasRef.current;\n if (canvas) drawIdleFrame(canvas, mode);\n }, [minimized, isPlaying, mode]);\n\n const togglePlay = () => {\n const el = audioRef.current;\n if (!el) return;\n if (el.paused) void el.play().catch(() => {});\n else el.pause();\n };\n\n const toggleMute = () => {\n const el = audioRef.current;\n if (!el) return;\n el.muted = !el.muted;\n };\n\n const onVolumeInput = (e: React.ChangeEvent<HTMLInputElement>) => {\n const el = audioRef.current;\n if (!el) return;\n const v = Number(e.target.value);\n el.volume = v;\n if (v > 0 && el.muted) el.muted = false;\n };\n\n const onSeekInput = (e: React.ChangeEvent<HTMLInputElement>) => {\n setScrubTime(Number(e.target.value));\n };\n\n const commitSeek = () => {\n const el = audioRef.current;\n if (el && scrubTime !== null) {\n try {\n el.currentTime = scrubTime;\n } catch {\n /* not seekable yet */\n }\n }\n setScrubTime(null);\n };\n\n const toggleMinimized = () => {\n setMinimized((m) => {\n writePref(persistKey, 'minimized', m ? '0' : '1');\n return !m;\n });\n };\n\n const toggleMode = () => {\n setMode((prev) => {\n const next: AudioVisualizerMode = prev === 'line' ? 'bars' : 'line';\n writePref(persistKey, 'mode', next);\n return next;\n });\n };\n\n const shownTime = scrubTime ?? currentTime;\n\n return (\n <div\n className=\"mv-player\"\n data-mv-player\n data-minimized={minimized ? 'true' : 'false'}\n data-mode={mode}\n >\n {!minimized && (\n <div className=\"mv-player-viz\">\n <canvas\n ref={canvasRef}\n width={600}\n height={96}\n className=\"mv-player-canvas\"\n aria-hidden=\"true\"\n />\n <button\n type=\"button\"\n className=\"mv-player-btn mv-player-mode-btn\"\n onClick={toggleMode}\n aria-label={\n mode === 'line' ? 'Switch visualizer to bars' : 'Switch visualizer to waveform line'\n }\n >\n {mode === 'line' ? <BarChart3 aria-hidden=\"true\" /> : <Activity aria-hidden=\"true\" />}\n </button>\n </div>\n )}\n <div className=\"mv-player-controls\">\n <button\n type=\"button\"\n className=\"mv-player-btn mv-player-play-btn\"\n onClick={togglePlay}\n aria-label={isPlaying ? 'Pause' : 'Play'}\n aria-busy={isPlaying && buffering ? true : undefined}\n >\n {isPlaying && buffering ? (\n <Spinner size=\"xs\" />\n ) : isPlaying ? (\n <Pause aria-hidden=\"true\" />\n ) : (\n <Play aria-hidden=\"true\" />\n )}\n </button>\n <span className=\"mv-player-time\" aria-hidden=\"true\">\n {formatTime(shownTime)}\n </span>\n <input\n type=\"range\"\n className=\"mv-player-seek\"\n min={0}\n max={duration > 0 ? duration : 0}\n step={0.1}\n value={Math.min(shownTime, duration > 0 ? duration : shownTime)}\n onChange={onSeekInput}\n onMouseUp={commitSeek}\n onTouchEnd={commitSeek}\n onKeyUp={commitSeek}\n disabled={duration <= 0}\n aria-label=\"Seek\"\n />\n <span className=\"mv-player-time\" aria-hidden=\"true\">\n {formatTime(duration)}\n </span>\n <button\n type=\"button\"\n className=\"mv-player-btn\"\n onClick={toggleMute}\n aria-label={isMuted ? 'Unmute' : 'Mute'}\n >\n {isMuted ? <VolumeX aria-hidden=\"true\" /> : <Volume2 aria-hidden=\"true\" />}\n </button>\n <input\n type=\"range\"\n className=\"mv-player-volume\"\n min={0}\n max={1}\n step={0.05}\n value={isMuted ? 0 : volume}\n onChange={onVolumeInput}\n aria-label=\"Volume\"\n />\n <button\n type=\"button\"\n className=\"mv-player-btn\"\n onClick={toggleMinimized}\n aria-label={minimized ? 'Show visualizer' : 'Hide visualizer'}\n >\n {minimized ? <ChevronUp aria-hidden=\"true\" /> : <ChevronDown aria-hidden=\"true\" />}\n </button>\n {extraActions != null && <div className=\"mv-player-extra\">{extraActions}</div>}\n </div>\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"audio-player-chrome.js","sourceRoot":"","sources":["../../src/react/audio-player-chrome.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\n// Custom audio player chrome — the one owned control surface used by every\n// AudioViewer. Owns: play/pause, seek, time readout, mute/volume, and\n// the visualizer panel (shared engine in ../visualizer.ts) with minimize and\n// line/bars mode toggles. The media ELEMENT (loading, errors, playback API)\n// stays owned by AudioViewer — this component only reads/drives it.\n//\n// Web Audio wiring is created lazily on the first `play` event (autoplay\n// policies suspend fresh AudioContexts; a user-gesture play resumes them) and\n// is best-effort throughout: if anything throws, the player still plays and\n// only the visualizer goes dark. `createMediaElementSource` is once-per-\n// element, so the graph is keyed to the element instance and rebuilt only if\n// the element itself changes. The graph is built only for audio Web Audio can\n// hear (`graphCanHear`): a cross-origin file fetched without CORS reaches the\n// graph as silence, and once routed the element's own output is that silence,\n// so such a file plays straight to the speakers with the visualizer idle.\n\nimport * as React from 'react';\nimport {\n Pause,\n Play,\n Volume2,\n VolumeX,\n ChevronDown,\n ChevronUp,\n Activity,\n BarChart3,\n} from 'lucide-react';\nimport { Spinner } from '@ttt-productions/ui-core/react';\nimport { startWaveformLoop, drawIdleFrame } from '../visualizer.js';\nimport type { AudioVisualizerMode } from '../visualizer.js';\nimport { resolveInfiniteDuration } from '../media-duration.js';\n\nconst DEFAULT_PERSIST_KEY = 'mv-audio-player';\n\nfunction readPref(persistKey: string | null, suffix: string): string | null {\n if (!persistKey) return null;\n try {\n return window.localStorage.getItem(`${persistKey}:${suffix}`);\n } catch {\n return null;\n }\n}\n\nfunction writePref(persistKey: string | null, suffix: string, value: string): void {\n if (!persistKey) return;\n try {\n window.localStorage.setItem(`${persistKey}:${suffix}`, value);\n } catch {\n /* storage unavailable (private mode, SSR) — session-only prefs */\n }\n}\n\n/**\n * Whether Web Audio can hear the element's current file: one read with CORS (`crossOrigin` set),\n * a local `blob:` / `data:` URL, or a same-origin file.\n */\nfunction graphCanHear(el: HTMLAudioElement): boolean {\n if (el.crossOrigin) return true;\n const src = el.currentSrc || el.src;\n if (!src) return false;\n try {\n const u = new URL(src, window.location.href);\n return u.protocol === 'blob:' || u.protocol === 'data:' || u.origin === window.location.origin;\n } catch {\n return false;\n }\n}\n\nfunction formatTime(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return '0:00';\n const whole = Math.floor(seconds);\n const m = Math.floor(whole / 60);\n const s = whole % 60;\n return `${m}:${s.toString().padStart(2, '0')}`;\n}\n\nexport type AudioPlayerChromeProps = {\n audioRef: React.RefObject<HTMLAudioElement | null>;\n /** Initial visualizer mode; a persisted user choice wins over this. */\n visualizerMode?: AudioVisualizerMode;\n /** localStorage key base for minimize/mode prefs; `null` disables persistence. */\n persistKey?: string | null;\n /** App-injected action buttons, rendered at the end of the control row. */\n extraActions?: React.ReactNode;\n};\n\nexport function AudioPlayerChrome({\n audioRef,\n visualizerMode = 'bars',\n persistKey = DEFAULT_PERSIST_KEY,\n extraActions,\n}: AudioPlayerChromeProps) {\n const [isPlaying, setIsPlaying] = React.useState(false);\n // Playback is waiting on data (initial buffer, a stall, or a seek landing).\n const [buffering, setBuffering] = React.useState(false);\n const [currentTime, setCurrentTime] = React.useState(0);\n const [duration, setDuration] = React.useState(0);\n const [isMuted, setIsMuted] = React.useState(false);\n const [volume, setVolume] = React.useState(1);\n const [minimized, setMinimized] = React.useState<boolean>(\n () => readPref(persistKey, 'minimized') === '1',\n );\n const [mode, setMode] = React.useState<AudioVisualizerMode>(() => {\n const stored = readPref(persistKey, 'mode');\n return stored === 'line' || stored === 'bars' ? stored : visualizerMode;\n });\n\n // While the user drags the seek slider, timeupdate must not fight the thumb.\n const [scrubTime, setScrubTime] = React.useState<number | null>(null);\n\n const canvasRef = React.useRef<HTMLCanvasElement | null>(null);\n const modeRef = React.useRef(mode);\n modeRef.current = mode;\n\n // Web Audio graph (lazy, keyed to the element instance).\n const audioCtxRef = React.useRef<AudioContext | null>(null);\n const sourceElRef = React.useRef<HTMLAudioElement | null>(null);\n const analyserRef = React.useRef<AnalyserNode | null>(null);\n const stopLoopRef = React.useRef<(() => void) | null>(null);\n\n const stopLoop = React.useCallback(() => {\n stopLoopRef.current?.();\n stopLoopRef.current = null;\n }, []);\n\n const teardownGraph = React.useCallback(() => {\n stopLoop();\n analyserRef.current = null;\n sourceElRef.current = null;\n const ctx = audioCtxRef.current;\n audioCtxRef.current = null;\n if (ctx && ctx.state !== 'closed') {\n ctx.close().catch(() => {});\n }\n }, [stopLoop]);\n\n const ensureGraph = React.useCallback(\n (el: HTMLAudioElement) => {\n try {\n if (sourceElRef.current === el && audioCtxRef.current) {\n if (audioCtxRef.current.state === 'suspended') {\n audioCtxRef.current.resume().catch(() => {});\n }\n return;\n }\n if (!graphCanHear(el)) return;\n teardownGraph();\n const AudioCtx: typeof AudioContext =\n (window as unknown as { AudioContext?: typeof AudioContext }).AudioContext ||\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext!;\n if (!AudioCtx) return;\n const ctx = new AudioCtx();\n const source = ctx.createMediaElementSource(el);\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 256;\n // Element audio now routes through the graph — destination connection\n // is REQUIRED or playback goes silent.\n source.connect(analyser);\n analyser.connect(ctx.destination);\n audioCtxRef.current = ctx;\n sourceElRef.current = el;\n analyserRef.current = analyser;\n if (ctx.state === 'suspended') {\n ctx.resume().catch(() => {});\n }\n } catch {\n // Best-effort: playback works without the visualizer.\n analyserRef.current = null;\n }\n },\n [teardownGraph],\n );\n\n const startLoop = React.useCallback(() => {\n if (stopLoopRef.current) return;\n const analyser = analyserRef.current;\n if (!analyser) return;\n stopLoopRef.current = startWaveformLoop({\n analyser,\n getCanvas: () => canvasRef.current,\n mode: () => modeRef.current,\n });\n }, []);\n\n // Element event subscriptions. The element mounts in the same commit as this\n // chrome (AudioViewer renders both once loading starts), so the ref is set\n // by the time this effect runs.\n React.useEffect(() => {\n const el = audioRef.current;\n if (!el) return;\n\n // MediaRecorder blobs report duration Infinity until seeked (Chromium webm\n // quirk) — without this the readout sticks at \"0:01 / 0:00\". Learn the real\n // duration via the shared seek workaround; paused-only so an actively\n // playing element is never seek-jumped, and one attempt per element so a\n // workaround-triggered durationchange can never re-enter it.\n let durationReconcileAttempted = false;\n const reconcileInfiniteDuration = () => {\n if (durationReconcileAttempted || Number.isFinite(el.duration) || !el.paused) return;\n durationReconcileAttempted = true;\n void resolveInfiniteDuration(el).then((real) => {\n if (real !== null) setDuration(real);\n });\n };\n\n const syncFromElement = () => {\n setIsPlaying(!el.paused && !el.ended);\n setIsMuted(el.muted);\n setVolume(el.volume);\n if (Number.isFinite(el.duration)) setDuration(el.duration);\n setCurrentTime(el.currentTime);\n };\n syncFromElement();\n if (el.readyState >= 1) reconcileInfiniteDuration();\n\n const onPlay = () => {\n setIsPlaying(true);\n ensureGraph(el);\n startLoop();\n };\n const onPause = () => {\n setIsPlaying(false);\n stopLoop();\n };\n const onEnded = () => {\n setIsPlaying(false);\n stopLoop();\n };\n const onTimeUpdate = () => setCurrentTime(el.currentTime);\n const onDurationChange = () => {\n if (Number.isFinite(el.duration)) setDuration(el.duration);\n else reconcileInfiniteDuration();\n };\n const onVolumeChange = () => {\n setIsMuted(el.muted);\n setVolume(el.volume);\n };\n const onWaiting = () => setBuffering(true);\n const onReady = () => setBuffering(false);\n\n el.addEventListener('play', onPlay);\n el.addEventListener('pause', onPause);\n el.addEventListener('ended', onEnded);\n el.addEventListener('timeupdate', onTimeUpdate);\n el.addEventListener('durationchange', onDurationChange);\n el.addEventListener('loadedmetadata', onDurationChange);\n el.addEventListener('volumechange', onVolumeChange);\n el.addEventListener('waiting', onWaiting);\n el.addEventListener('seeking', onWaiting);\n el.addEventListener('playing', onReady);\n el.addEventListener('seeked', onReady);\n el.addEventListener('canplay', onReady);\n el.addEventListener('pause', onReady);\n el.addEventListener('ended', onReady);\n el.addEventListener('error', onReady);\n return () => {\n el.removeEventListener('play', onPlay);\n el.removeEventListener('pause', onPause);\n el.removeEventListener('ended', onEnded);\n el.removeEventListener('timeupdate', onTimeUpdate);\n el.removeEventListener('durationchange', onDurationChange);\n el.removeEventListener('loadedmetadata', onDurationChange);\n el.removeEventListener('volumechange', onVolumeChange);\n el.removeEventListener('waiting', onWaiting);\n el.removeEventListener('seeking', onWaiting);\n el.removeEventListener('playing', onReady);\n el.removeEventListener('seeked', onReady);\n el.removeEventListener('canplay', onReady);\n el.removeEventListener('pause', onReady);\n el.removeEventListener('ended', onReady);\n el.removeEventListener('error', onReady);\n };\n }, [audioRef, ensureGraph, startLoop, stopLoop]);\n\n // Full teardown when the chrome unmounts.\n React.useEffect(() => teardownGraph, [teardownGraph]);\n\n // Idle frame whenever the visualizer is visible but not animating.\n React.useEffect(() => {\n if (minimized || isPlaying) return;\n const canvas = canvasRef.current;\n if (canvas) drawIdleFrame(canvas, mode);\n }, [minimized, isPlaying, mode]);\n\n const togglePlay = () => {\n const el = audioRef.current;\n if (!el) return;\n if (el.paused) void el.play().catch(() => {});\n else el.pause();\n };\n\n const toggleMute = () => {\n const el = audioRef.current;\n if (!el) return;\n el.muted = !el.muted;\n };\n\n const onVolumeInput = (e: React.ChangeEvent<HTMLInputElement>) => {\n const el = audioRef.current;\n if (!el) return;\n const v = Number(e.target.value);\n el.volume = v;\n if (v > 0 && el.muted) el.muted = false;\n };\n\n const onSeekInput = (e: React.ChangeEvent<HTMLInputElement>) => {\n setScrubTime(Number(e.target.value));\n };\n\n const commitSeek = () => {\n const el = audioRef.current;\n if (el && scrubTime !== null) {\n try {\n el.currentTime = scrubTime;\n } catch {\n /* not seekable yet */\n }\n }\n setScrubTime(null);\n };\n\n const toggleMinimized = () => {\n setMinimized((m) => {\n writePref(persistKey, 'minimized', m ? '0' : '1');\n return !m;\n });\n };\n\n const toggleMode = () => {\n setMode((prev) => {\n const next: AudioVisualizerMode = prev === 'line' ? 'bars' : 'line';\n writePref(persistKey, 'mode', next);\n return next;\n });\n };\n\n const shownTime = scrubTime ?? currentTime;\n\n return (\n <div\n className=\"mv-player\"\n data-mv-player\n data-minimized={minimized ? 'true' : 'false'}\n data-mode={mode}\n >\n {!minimized && (\n <div className=\"mv-player-viz\">\n <canvas\n ref={canvasRef}\n width={600}\n height={96}\n className=\"mv-player-canvas\"\n aria-hidden=\"true\"\n />\n <button\n type=\"button\"\n className=\"mv-player-btn mv-player-mode-btn\"\n onClick={toggleMode}\n aria-label={\n mode === 'line' ? 'Switch visualizer to bars' : 'Switch visualizer to waveform line'\n }\n >\n {mode === 'line' ? <BarChart3 aria-hidden=\"true\" /> : <Activity aria-hidden=\"true\" />}\n </button>\n </div>\n )}\n <div className=\"mv-player-controls\">\n <button\n type=\"button\"\n className=\"mv-player-btn mv-player-play-btn\"\n onClick={togglePlay}\n aria-label={isPlaying ? 'Pause' : 'Play'}\n aria-busy={isPlaying && buffering ? true : undefined}\n >\n {isPlaying && buffering ? (\n <Spinner size=\"xs\" />\n ) : isPlaying ? (\n <Pause aria-hidden=\"true\" />\n ) : (\n <Play aria-hidden=\"true\" />\n )}\n </button>\n <span className=\"mv-player-time\" aria-hidden=\"true\">\n {formatTime(shownTime)}\n </span>\n <input\n type=\"range\"\n className=\"mv-player-seek\"\n min={0}\n max={duration > 0 ? duration : 0}\n step={0.1}\n value={Math.min(shownTime, duration > 0 ? duration : shownTime)}\n onChange={onSeekInput}\n onMouseUp={commitSeek}\n onTouchEnd={commitSeek}\n onKeyUp={commitSeek}\n disabled={duration <= 0}\n aria-label=\"Seek\"\n />\n <span className=\"mv-player-time\" aria-hidden=\"true\">\n {formatTime(duration)}\n </span>\n <button\n type=\"button\"\n className=\"mv-player-btn\"\n onClick={toggleMute}\n aria-label={isMuted ? 'Unmute' : 'Mute'}\n >\n {isMuted ? <VolumeX aria-hidden=\"true\" /> : <Volume2 aria-hidden=\"true\" />}\n </button>\n <input\n type=\"range\"\n className=\"mv-player-volume\"\n min={0}\n max={1}\n step={0.05}\n value={isMuted ? 0 : volume}\n onChange={onVolumeInput}\n aria-label=\"Volume\"\n />\n <button\n type=\"button\"\n className=\"mv-player-btn\"\n onClick={toggleMinimized}\n aria-label={minimized ? 'Show visualizer' : 'Hide visualizer'}\n >\n {minimized ? <ChevronUp aria-hidden=\"true\" /> : <ChevronDown aria-hidden=\"true\" />}\n </button>\n {extraActions != null && <div className=\"mv-player-extra\">{extraActions}</div>}\n </div>\n </div>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"audio-viewer.d.ts","sourceRoot":"","sources":["../../src/react/audio-viewer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAKnD,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,qBA2LlD"}
1
+ {"version":3,"file":"audio-viewer.d.ts","sourceRoot":"","sources":["../../src/react/audio-viewer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAKnD,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,qBA6LlD"}
@@ -6,7 +6,7 @@ import { useMediaPlayback } from './use-media-playback.js';
6
6
  import { AudioPlayerChrome } from './audio-player-chrome.js';
7
7
  import { useLoadWatchdog } from './use-load-watchdog.js';
8
8
  export function AudioViewer(props) {
9
- const { url, className, mediaClassName, autoPlay, loop = false, lazy = true, skeleton: showSkeleton = true, priority = false, preload = 'metadata', onLoad, onLoadChange, onError, onVisibilityChange, fallback, loadTimeoutMs, onEnded, onProgressSample, startAtSeconds, endOverlay, playbackControlsRef, visualizerMode, persistKey, extraActions, } = props;
9
+ const { url, className, mediaClassName, autoPlay, loop = false, lazy = true, skeleton: showSkeleton = true, priority = false, preload = 'metadata', onLoad, onLoadChange, onError, onVisibilityChange, fallback, loadTimeoutMs, onEnded, onProgressSample, startAtSeconds, endOverlay, playbackControlsRef, visualizerMode, persistKey, extraActions, crossOrigin, } = props;
10
10
  const [isLoaded, setIsLoaded] = React.useState(false);
11
11
  const [hasError, setHasError] = React.useState(false);
12
12
  const [shouldLoad, setShouldLoad] = React.useState(priority || !lazy);
@@ -77,7 +77,7 @@ export function AudioViewer(props) {
77
77
  if (hasError) {
78
78
  return (_jsx("div", { ref: inViewRef, className: className, style: fallback ? { position: 'relative', width: '100%', height: '100%' } : undefined, children: fallback ?? (_jsx("div", { className: "mv-audio-error", children: _jsx("p", { children: "Failed to load audio" }) })) }));
79
79
  }
80
- return (_jsx("div", { ref: inViewRef, className: className, style: { position: 'relative', width: '100%', outline: 'none' }, children: !shouldLoad ? (showSkeleton ? (_jsx(Skeleton, { style: { height: '3.5rem', width: '100%' } })) : null) : (_jsxs(_Fragment, { children: [showSkeleton && !isLoaded && (_jsx(Skeleton, { style: { position: 'absolute', inset: 0, height: '3.5rem', width: '100%' } })), _jsx("audio", { ref: setAudioRef, src: url, className: mediaClassName, controls: false, autoPlay: autoPlay, loop: loop, preload: preload, onLoadedData: reveal, onCanPlay: reveal, onError: handleError, onLoadedMetadata: handleLoadedMetadata, onTimeUpdate: playbackHandlers.onTimeUpdate, onPlay: playbackHandlers.onPlay, onPause: playbackHandlers.onPause, onSeeked: playbackHandlers.onSeeked, onEnded: playbackHandlers.onEnded, style: {
80
+ return (_jsx("div", { ref: inViewRef, className: className, style: { position: 'relative', width: '100%', outline: 'none' }, children: !shouldLoad ? (showSkeleton ? (_jsx(Skeleton, { style: { height: '3.5rem', width: '100%' } })) : null) : (_jsxs(_Fragment, { children: [showSkeleton && !isLoaded && (_jsx(Skeleton, { style: { position: 'absolute', inset: 0, height: '3.5rem', width: '100%' } })), _jsx("audio", { ref: setAudioRef, src: url, crossOrigin: crossOrigin, className: mediaClassName, controls: false, autoPlay: autoPlay, loop: loop, preload: preload, onLoadedData: reveal, onCanPlay: reveal, onError: handleError, onLoadedMetadata: handleLoadedMetadata, onTimeUpdate: playbackHandlers.onTimeUpdate, onPlay: playbackHandlers.onPlay, onPause: playbackHandlers.onPause, onSeeked: playbackHandlers.onSeeked, onEnded: playbackHandlers.onEnded, style: {
81
81
  width: '100%',
82
82
  opacity: isLoaded ? 1 : 0,
83
83
  transition: 'opacity var(--motion-slow) var(--motion-ease)',
@@ -1 +1 @@
1
- {"version":3,"file":"audio-viewer.js","sourceRoot":"","sources":["../../src/react/audio-viewer.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\nimport { useInView } from 'react-intersection-observer';\nimport { Skeleton } from '@ttt-productions/ui-core/react';\nimport type { AudioViewerProps } from './types.js';\nimport { useMediaPlayback } from './use-media-playback.js';\nimport { AudioPlayerChrome } from './audio-player-chrome.js';\nimport { useLoadWatchdog } from './use-load-watchdog.js';\n\nexport function AudioViewer(props: AudioViewerProps) {\n const {\n url,\n className,\n mediaClassName,\n autoPlay,\n loop = false,\n lazy = true,\n skeleton: showSkeleton = true,\n priority = false,\n preload = 'metadata',\n onLoad,\n onLoadChange,\n onError,\n onVisibilityChange,\n fallback,\n loadTimeoutMs,\n onEnded,\n onProgressSample,\n startAtSeconds,\n endOverlay,\n playbackControlsRef,\n visualizerMode,\n persistKey,\n extraActions,\n } = props;\n\n const [isLoaded, setIsLoaded] = React.useState(false);\n const [hasError, setHasError] = React.useState(false);\n const [shouldLoad, setShouldLoad] = React.useState(priority || !lazy);\n const audioRef = React.useRef<HTMLAudioElement>(null);\n const loadReportedRef = React.useRef(false);\n\n // Additive playback API — derives from element events (no observer, MEDIA-102).\n const {\n hasEnded,\n handlers: playbackHandlers,\n attachElement,\n } = useMediaPlayback(\n audioRef,\n { onEnded, onProgressSample, startAtSeconds, endOverlay },\n playbackControlsRef,\n url,\n );\n\n // The element attaches through the playback lifecycle so the hook knows the\n // active generation; detach (unmount) retires it.\n const setAudioRef = React.useCallback(\n (el: HTMLAudioElement | null) => {\n audioRef.current = el;\n attachElement(el);\n },\n [attachElement],\n );\n\n // The viewer's one observer (MEDIA-102): lazy-load gating, and — while an owner listens —\n // the visibility it reports, which is why it keeps running after the load starts then.\n const { ref: inViewRef, inView } = useInView({\n triggerOnce: false,\n threshold: 0.01,\n skip: priority || (shouldLoad && !onVisibilityChange),\n rootMargin: '100px',\n onChange: (visible) => onVisibilityChange?.(visible),\n });\n\n React.useEffect(() => {\n if (inView && !shouldLoad) {\n setShouldLoad(true);\n }\n }, [inView, shouldLoad]);\n\n // Accordion compatibility: delayed auto-load on mount\n React.useEffect(() => {\n if (priority || shouldLoad) return;\n const timer = setTimeout(() => {\n if (!shouldLoad) setShouldLoad(true);\n }, 100);\n return () => clearTimeout(timer);\n }, [url, priority, shouldLoad]);\n\n React.useEffect(() => {\n onLoadChange?.(!isLoaded);\n }, [isLoaded, onLoadChange]);\n\n React.useEffect(() => {\n setHasError(false);\n setIsLoaded(false);\n loadReportedRef.current = false;\n }, [url]);\n\n // A preload=\"metadata\" element may not fire loadeddata or canplay until play, so the\n // first of loadedmetadata / loadeddata / canplay makes it usable: the skeleton goes, the\n // player shows, and onLoad fires once for this load.\n const reveal = React.useCallback(() => {\n setIsLoaded(true);\n if (loadReportedRef.current) return;\n loadReportedRef.current = true;\n onLoad?.();\n }, [onLoad]);\n\n const handleError = React.useCallback(() => {\n setHasError(true);\n setIsLoaded(true); // Stop skeleton on error\n onError?.();\n }, [onError]);\n\n const handleLoadedMetadata = React.useCallback(\n (e: React.SyntheticEvent<HTMLAudioElement>) => {\n reveal();\n playbackHandlers.onLoadedMetadata(e);\n },\n [reveal, playbackHandlers],\n );\n\n useLoadWatchdog(shouldLoad && !isLoaded && !hasError, loadTimeoutMs, handleError);\n\n // The error state stays inside the observed wrapper, so the viewer keeps reporting\n // its visibility while its owner recovers it.\n if (hasError) {\n return (\n <div\n ref={inViewRef}\n className={className}\n style={fallback ? { position: 'relative', width: '100%', height: '100%' } : undefined}\n >\n {fallback ?? (\n <div className=\"mv-audio-error\">\n <p>Failed to load audio</p>\n </div>\n )}\n </div>\n );\n }\n\n return (\n <div\n ref={inViewRef}\n className={className}\n style={{ position: 'relative', width: '100%', outline: 'none' }}\n >\n {!shouldLoad ? (\n showSkeleton ? (\n <Skeleton style={{ height: '3.5rem', width: '100%' }} />\n ) : null\n ) : (\n <>\n {showSkeleton && !isLoaded && (\n <Skeleton style={{ position: 'absolute', inset: 0, height: '3.5rem', width: '100%' }} />\n )}\n <audio\n ref={setAudioRef}\n src={url}\n className={mediaClassName}\n controls={false}\n autoPlay={autoPlay}\n loop={loop}\n preload={preload}\n onLoadedData={reveal}\n onCanPlay={reveal}\n onError={handleError}\n onLoadedMetadata={handleLoadedMetadata}\n onTimeUpdate={playbackHandlers.onTimeUpdate}\n onPlay={playbackHandlers.onPlay}\n onPause={playbackHandlers.onPause}\n onSeeked={playbackHandlers.onSeeked}\n onEnded={playbackHandlers.onEnded}\n style={{\n width: '100%',\n opacity: isLoaded ? 1 : 0,\n transition: 'opacity var(--motion-slow) var(--motion-ease)',\n }}\n />\n <AudioPlayerChrome\n audioRef={audioRef}\n visualizerMode={visualizerMode}\n persistKey={persistKey}\n extraActions={extraActions}\n />\n {endOverlay != null && hasEnded && (\n <div className=\"mv-end-overlay\" style={{ position: 'absolute', inset: 0, zIndex: 2 }}>\n {endOverlay}\n </div>\n )}\n </>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"audio-viewer.js","sourceRoot":"","sources":["../../src/react/audio-viewer.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAE1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,MAAM,EACJ,GAAG,EACH,SAAS,EACT,cAAc,EACd,QAAQ,EACR,IAAI,GAAG,KAAK,EACZ,IAAI,GAAG,IAAI,EACX,QAAQ,EAAE,YAAY,GAAG,IAAI,EAC7B,QAAQ,GAAG,KAAK,EAChB,OAAO,GAAG,UAAU,EACpB,MAAM,EACN,YAAY,EACZ,OAAO,EACP,kBAAkB,EAClB,QAAQ,EACR,aAAa,EACb,OAAO,EACP,gBAAgB,EAChB,cAAc,EACd,UAAU,EACV,mBAAmB,EACnB,cAAc,EACd,UAAU,EACV,YAAY,EACZ,WAAW,GACZ,GAAG,KAAK,CAAC;IAEV,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAmB,IAAI,CAAC,CAAC;IACtD,MAAM,eAAe,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAE5C,gFAAgF;IAChF,MAAM,EACJ,QAAQ,EACR,QAAQ,EAAE,gBAAgB,EAC1B,aAAa,GACd,GAAG,gBAAgB,CAClB,QAAQ,EACR,EAAE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,UAAU,EAAE,EACzD,mBAAmB,EACnB,GAAG,CACJ,CAAC;IAEF,4EAA4E;IAC5E,kDAAkD;IAClD,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CACnC,CAAC,EAA2B,EAAE,EAAE;QAC9B,QAAQ,CAAC,OAAO,GAAG,EAAE,CAAC;QACtB,aAAa,CAAC,EAAE,CAAC,CAAC;IACpB,CAAC,EACD,CAAC,aAAa,CAAC,CAChB,CAAC;IAEF,0FAA0F;IAC1F,uFAAuF;IACvF,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;QAC3C,WAAW,EAAE,KAAK;QAClB,SAAS,EAAE,IAAI;QACf,IAAI,EAAE,QAAQ,IAAI,CAAC,UAAU,IAAI,CAAC,kBAAkB,CAAC;QACrD,UAAU,EAAE,OAAO;QACnB,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,kBAAkB,EAAE,CAAC,OAAO,CAAC;KACrD,CAAC,CAAC;IAEH,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;YAC1B,aAAa,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IAEzB,sDAAsD;IACtD,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,QAAQ,IAAI,UAAU;YAAE,OAAO;QACnC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,UAAU;gBAAE,aAAa,CAAC,IAAI,CAAC,CAAC;QACvC,CAAC,EAAE,GAAG,CAAC,CAAC;QACR,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC;IAEhC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,YAAY,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;IAE7B,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,WAAW,CAAC,KAAK,CAAC,CAAC;QACnB,WAAW,CAAC,KAAK,CAAC,CAAC;QACnB,eAAe,CAAC,OAAO,GAAG,KAAK,CAAC;IAClC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAEV,qFAAqF;IACrF,yFAAyF;IACzF,qDAAqD;IACrD,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QACpC,WAAW,CAAC,IAAI,CAAC,CAAC;QAClB,IAAI,eAAe,CAAC,OAAO;YAAE,OAAO;QACpC,eAAe,CAAC,OAAO,GAAG,IAAI,CAAC;QAC/B,MAAM,EAAE,EAAE,CAAC;IACb,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QACzC,WAAW,CAAC,IAAI,CAAC,CAAC;QAClB,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,yBAAyB;QAC5C,OAAO,EAAE,EAAE,CAAC;IACd,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAEd,MAAM,oBAAoB,GAAG,KAAK,CAAC,WAAW,CAC5C,CAAC,CAAyC,EAAE,EAAE;QAC5C,MAAM,EAAE,CAAC;QACT,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,EACD,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAC3B,CAAC;IAEF,eAAe,CAAC,UAAU,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC;IAElF,mFAAmF;IACnF,8CAA8C;IAC9C,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CACL,cACE,GAAG,EAAE,SAAS,EACd,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,YAEpF,QAAQ,IAAI,CACX,cAAK,SAAS,EAAC,gBAAgB,YAC7B,+CAA2B,GACvB,CACP,GACG,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,cACE,GAAG,EAAE,SAAS,EACd,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,YAE9D,CAAC,UAAU,CAAC,CAAC,CAAC,CACb,YAAY,CAAC,CAAC,CAAC,CACb,KAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAI,CACzD,CAAC,CAAC,CAAC,IAAI,CACT,CAAC,CAAC,CAAC,CACF,8BACG,YAAY,IAAI,CAAC,QAAQ,IAAI,CAC5B,KAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAI,CACzF,EACD,gBACE,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,KAAK,EACf,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,WAAW,EACpB,gBAAgB,EAAE,oBAAoB,EACtC,YAAY,EAAE,gBAAgB,CAAC,YAAY,EAC3C,MAAM,EAAE,gBAAgB,CAAC,MAAM,EAC/B,OAAO,EAAE,gBAAgB,CAAC,OAAO,EACjC,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,EACnC,OAAO,EAAE,gBAAgB,CAAC,OAAO,EACjC,KAAK,EAAE;wBACL,KAAK,EAAE,MAAM;wBACb,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;wBACzB,UAAU,EAAE,+CAA+C;qBAC5D,GACD,EACF,KAAC,iBAAiB,IAChB,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,YAAY,GAC1B,EACD,UAAU,IAAI,IAAI,IAAI,QAAQ,IAAI,CACjC,cAAK,SAAS,EAAC,gBAAgB,EAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,YACjF,UAAU,GACP,CACP,IACA,CACJ,GACG,CACP,CAAC;AACJ,CAAC","sourcesContent":["import * as React from 'react';\nimport { useInView } from 'react-intersection-observer';\nimport { Skeleton } from '@ttt-productions/ui-core/react';\nimport type { AudioViewerProps } from './types.js';\nimport { useMediaPlayback } from './use-media-playback.js';\nimport { AudioPlayerChrome } from './audio-player-chrome.js';\nimport { useLoadWatchdog } from './use-load-watchdog.js';\n\nexport function AudioViewer(props: AudioViewerProps) {\n const {\n url,\n className,\n mediaClassName,\n autoPlay,\n loop = false,\n lazy = true,\n skeleton: showSkeleton = true,\n priority = false,\n preload = 'metadata',\n onLoad,\n onLoadChange,\n onError,\n onVisibilityChange,\n fallback,\n loadTimeoutMs,\n onEnded,\n onProgressSample,\n startAtSeconds,\n endOverlay,\n playbackControlsRef,\n visualizerMode,\n persistKey,\n extraActions,\n crossOrigin,\n } = props;\n\n const [isLoaded, setIsLoaded] = React.useState(false);\n const [hasError, setHasError] = React.useState(false);\n const [shouldLoad, setShouldLoad] = React.useState(priority || !lazy);\n const audioRef = React.useRef<HTMLAudioElement>(null);\n const loadReportedRef = React.useRef(false);\n\n // Additive playback API — derives from element events (no observer, MEDIA-102).\n const {\n hasEnded,\n handlers: playbackHandlers,\n attachElement,\n } = useMediaPlayback(\n audioRef,\n { onEnded, onProgressSample, startAtSeconds, endOverlay },\n playbackControlsRef,\n url,\n );\n\n // The element attaches through the playback lifecycle so the hook knows the\n // active generation; detach (unmount) retires it.\n const setAudioRef = React.useCallback(\n (el: HTMLAudioElement | null) => {\n audioRef.current = el;\n attachElement(el);\n },\n [attachElement],\n );\n\n // The viewer's one observer (MEDIA-102): lazy-load gating, and — while an owner listens —\n // the visibility it reports, which is why it keeps running after the load starts then.\n const { ref: inViewRef, inView } = useInView({\n triggerOnce: false,\n threshold: 0.01,\n skip: priority || (shouldLoad && !onVisibilityChange),\n rootMargin: '100px',\n onChange: (visible) => onVisibilityChange?.(visible),\n });\n\n React.useEffect(() => {\n if (inView && !shouldLoad) {\n setShouldLoad(true);\n }\n }, [inView, shouldLoad]);\n\n // Accordion compatibility: delayed auto-load on mount\n React.useEffect(() => {\n if (priority || shouldLoad) return;\n const timer = setTimeout(() => {\n if (!shouldLoad) setShouldLoad(true);\n }, 100);\n return () => clearTimeout(timer);\n }, [url, priority, shouldLoad]);\n\n React.useEffect(() => {\n onLoadChange?.(!isLoaded);\n }, [isLoaded, onLoadChange]);\n\n React.useEffect(() => {\n setHasError(false);\n setIsLoaded(false);\n loadReportedRef.current = false;\n }, [url]);\n\n // A preload=\"metadata\" element may not fire loadeddata or canplay until play, so the\n // first of loadedmetadata / loadeddata / canplay makes it usable: the skeleton goes, the\n // player shows, and onLoad fires once for this load.\n const reveal = React.useCallback(() => {\n setIsLoaded(true);\n if (loadReportedRef.current) return;\n loadReportedRef.current = true;\n onLoad?.();\n }, [onLoad]);\n\n const handleError = React.useCallback(() => {\n setHasError(true);\n setIsLoaded(true); // Stop skeleton on error\n onError?.();\n }, [onError]);\n\n const handleLoadedMetadata = React.useCallback(\n (e: React.SyntheticEvent<HTMLAudioElement>) => {\n reveal();\n playbackHandlers.onLoadedMetadata(e);\n },\n [reveal, playbackHandlers],\n );\n\n useLoadWatchdog(shouldLoad && !isLoaded && !hasError, loadTimeoutMs, handleError);\n\n // The error state stays inside the observed wrapper, so the viewer keeps reporting\n // its visibility while its owner recovers it.\n if (hasError) {\n return (\n <div\n ref={inViewRef}\n className={className}\n style={fallback ? { position: 'relative', width: '100%', height: '100%' } : undefined}\n >\n {fallback ?? (\n <div className=\"mv-audio-error\">\n <p>Failed to load audio</p>\n </div>\n )}\n </div>\n );\n }\n\n return (\n <div\n ref={inViewRef}\n className={className}\n style={{ position: 'relative', width: '100%', outline: 'none' }}\n >\n {!shouldLoad ? (\n showSkeleton ? (\n <Skeleton style={{ height: '3.5rem', width: '100%' }} />\n ) : null\n ) : (\n <>\n {showSkeleton && !isLoaded && (\n <Skeleton style={{ position: 'absolute', inset: 0, height: '3.5rem', width: '100%' }} />\n )}\n <audio\n ref={setAudioRef}\n src={url}\n crossOrigin={crossOrigin}\n className={mediaClassName}\n controls={false}\n autoPlay={autoPlay}\n loop={loop}\n preload={preload}\n onLoadedData={reveal}\n onCanPlay={reveal}\n onError={handleError}\n onLoadedMetadata={handleLoadedMetadata}\n onTimeUpdate={playbackHandlers.onTimeUpdate}\n onPlay={playbackHandlers.onPlay}\n onPause={playbackHandlers.onPause}\n onSeeked={playbackHandlers.onSeeked}\n onEnded={playbackHandlers.onEnded}\n style={{\n width: '100%',\n opacity: isLoaded ? 1 : 0,\n transition: 'opacity var(--motion-slow) var(--motion-ease)',\n }}\n />\n <AudioPlayerChrome\n audioRef={audioRef}\n visualizerMode={visualizerMode}\n persistKey={persistKey}\n extraActions={extraActions}\n />\n {endOverlay != null && hasEnded && (\n <div className=\"mv-end-overlay\" style={{ position: 'absolute', inset: 0, zIndex: 2 }}>\n {endOverlay}\n </div>\n )}\n </>\n )}\n </div>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"media-viewer.d.ts","sourceRoot":"","sources":["../../src/react/media-viewer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAwHpD,wBAAgB,WAAW,CAAC,KAAK,EAAE,iBAAiB,4BA8PnD;AAED,wBAAwB;AACxB,eAAO,MAAM,YAAY,oBAAc,CAAC"}
1
+ {"version":3,"file":"media-viewer.d.ts","sourceRoot":"","sources":["../../src/react/media-viewer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAwHpD,wBAAgB,WAAW,CAAC,KAAK,EAAE,iBAAiB,4BAgQnD;AAED,wBAAwB;AACxB,eAAO,MAAM,YAAY,oBAAc,CAAC"}
@@ -59,7 +59,7 @@ function RecoveryOverlay({ state, onManualRetry, isCircular, }) {
59
59
  // Main component
60
60
  // ---------------------------------------------------------------------------
61
61
  export function MediaViewer(props) {
62
- const { url, alt, type, className, unloadOnExit, urlPending = false, isCircular = false, enableZoom, priority, skeleton, lazy, controls, autoPlay, muted, loop, posterUrl, preload, autoPlayOnVisible, onLoad, onError, onLoadChange, onEnded, onProgressSample, startAtSeconds, endOverlay, playbackControlsRef, audioVisualizerMode, audioPersistKey, audioExtraActions, fallbackMode = 'link', fallbackLabel, filename, mime, name, recoveryAdapter, assetStatusHint, loadTimeoutMs, } = props;
62
+ const { url, alt, type, className, unloadOnExit, urlPending = false, isCircular = false, enableZoom, priority, skeleton, lazy, controls, autoPlay, muted, loop, posterUrl, preload, autoPlayOnVisible, onLoad, onError, onLoadChange, onEnded, onProgressSample, startAtSeconds, endOverlay, playbackControlsRef, audioVisualizerMode, audioPersistKey, audioExtraActions, audioCrossOrigin, fallbackMode = 'link', fallbackLabel, filename, mime, name, recoveryAdapter, assetStatusHint, loadTimeoutMs, } = props;
63
63
  const [hasError, setHasError] = React.useState(false);
64
64
  const [sourceUrl, setSourceUrl] = React.useState(null);
65
65
  // Remount key: incrementing this causes child media elements to unmount and
@@ -158,7 +158,7 @@ export function MediaViewer(props) {
158
158
  return (_jsx("div", { className: className, style: wrapperStyle, children: _jsx(VideoViewer, { url: sourceUrl, lazy: lazy, priority: priority, skeleton: skeleton, unloadOnExit: unloadOnExit, controls: controls, autoPlay: autoPlay, muted: muted, loop: loop, posterUrl: posterUrl, preload: preload, autoPlayOnVisible: autoPlayOnVisible, loadTimeoutMs: loadTimeoutMs, onLoad: handleLoad, onError: handleError, onVisibilityChange: onVisibilityChange, fallback: recoveryOverlay ?? undefined, onEnded: onEnded, onProgressSample: onProgressSample, startAtSeconds: startAtSeconds, endOverlay: endOverlay, playbackControlsRef: playbackControlsRef }, remountKey) }));
159
159
  }
160
160
  if (t === 'audio') {
161
- return (_jsx("div", { className: className, style: wrapperStyle, children: _jsx(AudioViewer, { url: sourceUrl, lazy: lazy, priority: priority, skeleton: skeleton, autoPlay: autoPlay, loop: loop, preload: preload, visualizerMode: audioVisualizerMode, persistKey: audioPersistKey, extraActions: audioExtraActions, loadTimeoutMs: loadTimeoutMs, onLoad: handleLoad, onLoadChange: onLoadChange, onError: handleError, onVisibilityChange: onVisibilityChange, fallback: recoveryOverlay ?? undefined, onEnded: onEnded, onProgressSample: onProgressSample, startAtSeconds: startAtSeconds, endOverlay: endOverlay, playbackControlsRef: playbackControlsRef }, remountKey) }));
161
+ return (_jsx("div", { className: className, style: wrapperStyle, children: _jsx(AudioViewer, { url: sourceUrl, lazy: lazy, priority: priority, skeleton: skeleton, autoPlay: autoPlay, loop: loop, preload: preload, visualizerMode: audioVisualizerMode, persistKey: audioPersistKey, extraActions: audioExtraActions, crossOrigin: audioCrossOrigin, loadTimeoutMs: loadTimeoutMs, onLoad: handleLoad, onLoadChange: onLoadChange, onError: handleError, onVisibilityChange: onVisibilityChange, fallback: recoveryOverlay ?? undefined, onEnded: onEnded, onProgressSample: onProgressSample, startAtSeconds: startAtSeconds, endOverlay: endOverlay, playbackControlsRef: playbackControlsRef }, remountKey) }));
162
162
  }
163
163
  // Unknown type -> fallback link
164
164
  return shouldShowFallback(fallbackMode) ? (_jsx("div", { className: className, style: wrapperStyle, children: _jsx(MediaFallbackLink, { url: sourceUrl, filename: filename, label: fallbackLabel ?? 'Download' }) })) : null;
@@ -1 +1 @@
1
- {"version":3,"file":"media-viewer.js","sourceRoot":"","sources":["../../src/react/media-viewer.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\nimport { getSimplifiedMediaType } from '@ttt-productions/media-schemas';\nimport type { SimplifiedMediaType } from '@ttt-productions/media-schemas';\n\nimport type { MediaViewerType, FallbackMode } from '../types.js';\nimport type { MediaPreviewProps } from './types.js';\nimport {\n MediaFallbackLink,\n shouldShowFallback,\n EmptyFallback,\n ErrorFallback,\n PendingFallback,\n} from './fallback.js';\nimport { ImageViewer } from './image-viewer.js';\nimport { VideoViewer } from './video-viewer.js';\nimport { AudioViewer } from './audio-viewer.js';\nimport { useMediaRecovery } from './use-media-recovery.js';\nimport type { RecoveryState } from '../recovery.js';\n\nfunction inferType(props: {\n type?: MediaPreviewProps['type'];\n mime?: string;\n name?: string;\n url?: string;\n}): MediaViewerType {\n if (props.type) {\n // Handle MIME strings like \"image/png\" -> \"image\"\n const primary = typeof props.type === 'string' ? props.type.split('/')[0] : props.type;\n if (primary === 'image' || primary === 'video' || primary === 'audio') return primary;\n if (primary === 'other') return 'other';\n }\n\n const hint = props.mime ?? props.name ?? props.url;\n if (!hint) return 'other';\n const simplified: SimplifiedMediaType = getSimplifiedMediaType(hint);\n return simplified;\n}\n\n// ---------------------------------------------------------------------------\n// Recovery-phase UI helpers\n// ---------------------------------------------------------------------------\n\nfunction RecoveryOverlay({\n state,\n onManualRetry,\n isCircular,\n}: {\n state: RecoveryState;\n onManualRetry: () => void;\n isCircular?: boolean;\n}) {\n const wrapStyle: React.CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n width: '100%',\n height: '100%',\n gap: '0.5rem',\n ...(isCircular ? { borderRadius: '50%' } : {}),\n };\n\n if (state.phase === 'processing') {\n return (\n <div className=\"mv-recovery-processing\" style={wrapStyle}>\n <span className=\"mv-recovery-label\">Processing media&hellip;</span>\n </div>\n );\n }\n\n if (state.phase === 'finalizing') {\n return (\n <div className=\"mv-recovery-finalizing\" style={wrapStyle}>\n <span className=\"mv-recovery-label\">Finishing media&hellip;</span>\n </div>\n );\n }\n\n if (state.phase === 'transient-retry' || state.phase === 'propagating') {\n return (\n <div className=\"mv-recovery-retrying\" style={wrapStyle}>\n <span className=\"mv-recovery-label\">Loading&hellip;</span>\n </div>\n );\n }\n\n if (state.phase === 'auth-retry') {\n return (\n <div className=\"mv-recovery-auth\" style={wrapStyle}>\n <span className=\"mv-recovery-label\">Refreshing access&hellip;</span>\n </div>\n );\n }\n\n if (state.phase === 'hard-unavailable') {\n return (\n <div className=\"mv-recovery-hard\" style={wrapStyle}>\n <ErrorFallback isCircular={isCircular} />\n {state.reason === 'auth' ? (\n <span className=\"mv-recovery-label\">Access denied.</span>\n ) : (\n <span className=\"mv-recovery-label\">Media unavailable.</span>\n )}\n </div>\n );\n }\n\n if (state.phase === 'max-wait-fallback') {\n return (\n <div className=\"mv-recovery-max-wait\" style={wrapStyle}>\n <span className=\"mv-recovery-label\">This media is taking longer than expected.</span>\n <button type=\"button\" className=\"mv-recovery-retry-btn\" onClick={onManualRetry}>\n Retry\n </button>\n </div>\n );\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Main component\n// ---------------------------------------------------------------------------\n\nexport function MediaViewer(props: MediaPreviewProps) {\n const {\n url,\n alt,\n type,\n className,\n unloadOnExit,\n urlPending = false,\n isCircular = false,\n enableZoom,\n priority,\n skeleton,\n lazy,\n controls,\n autoPlay,\n muted,\n loop,\n posterUrl,\n preload,\n autoPlayOnVisible,\n onLoad,\n onError,\n onLoadChange,\n onEnded,\n onProgressSample,\n startAtSeconds,\n endOverlay,\n playbackControlsRef,\n audioVisualizerMode,\n audioPersistKey,\n audioExtraActions,\n fallbackMode = 'link',\n fallbackLabel,\n filename,\n mime,\n name,\n recoveryAdapter,\n assetStatusHint,\n loadTimeoutMs,\n } = props;\n\n const [hasError, setHasError] = React.useState(false);\n const [sourceUrl, setSourceUrl] = React.useState<string | null>(null);\n\n // Remount key: incrementing this causes child media elements to unmount and\n // re-mount from scratch, re-issuing the HTTP request.\n const [remountKey, setRemountKey] = React.useState(0);\n\n const handleRemount = React.useCallback(() => {\n setHasError(false);\n setRemountKey((k) => k + 1);\n }, []);\n\n // The viewer's visibility as its own observer reports it (MEDIA-102 — this router adds\n // none). Only a recovering viewer listens — one whose element failed, the only time a\n // retry is scheduled — and retries wait while it is off screen. A healthy viewer does\n // not listen, so its observer keeps its own lazy-load lifetime. Each failure starts from\n // \"visible\", so a report from an earlier failure never outlives it.\n const listensForVisibility = recoveryAdapter !== undefined && hasError;\n const [elementVisible, setElementVisible] = React.useState(true);\n const [listenedLastRender, setListenedLastRender] = React.useState(listensForVisibility);\n if (listenedLastRender !== listensForVisibility) {\n setListenedLastRender(listensForVisibility);\n setElementVisible(true);\n }\n\n // Recovery hook (no-op when recoveryAdapter is omitted)\n const { recoveryState, onMediaError, onMediaLoad, manualRetry } = useMediaRecovery({\n url: typeof url === 'string' ? url : null,\n adapter: recoveryAdapter,\n isElementVisible: elementVisible,\n statusHint: assetStatusHint,\n onRemount: handleRemount,\n });\n const onVisibilityChange = listensForVisibility ? setElementVisible : undefined;\n\n React.useEffect(() => {\n setHasError(false);\n let objectUrl: string | null = null;\n\n if (typeof url === 'string') {\n setSourceUrl(url);\n } else if (url instanceof Blob) {\n objectUrl = URL.createObjectURL(url);\n setSourceUrl(objectUrl);\n } else {\n setSourceUrl(null);\n }\n\n return () => {\n if (objectUrl) URL.revokeObjectURL(objectUrl);\n };\n }, [url]);\n\n const handleError = React.useCallback(() => {\n setHasError(true);\n onError?.();\n // If a recovery adapter is provided, delegate to the recovery hook.\n if (recoveryAdapter) {\n onMediaError();\n }\n }, [onError, recoveryAdapter, onMediaError]);\n\n const handleLoad = React.useCallback(() => {\n setHasError(false);\n onLoad?.();\n // Signal successful load to the recovery state machine.\n if (recoveryAdapter) {\n onMediaLoad();\n }\n }, [onLoad, recoveryAdapter, onMediaLoad]);\n\n const circularStyle: React.CSSProperties = isCircular\n ? { borderRadius: '50%', overflow: 'hidden' }\n : {};\n\n const wrapperStyle: React.CSSProperties = {\n position: 'relative',\n width: '100%',\n height: '100%',\n ...circularStyle,\n };\n\n // When recovery is active and in a terminal/blocking state, show the overlay.\n const isInRecovery =\n recoveryAdapter !== undefined &&\n recoveryState.phase !== 'idle' &&\n recoveryState.phase !== 'loaded' &&\n recoveryState.phase !== 'loading';\n\n // No URL -> empty fallback\n if (!sourceUrl) {\n return (\n <div className={className} style={wrapperStyle}>\n {urlPending ? (\n <PendingFallback isCircular={isCircular} />\n ) : (\n <EmptyFallback isCircular={isCircular} />\n )}\n </div>\n );\n }\n\n // Recovery overlay (for all non-idle/non-loaded recovery states). It renders as the\n // failed viewer's fallback, inside that viewer's observed wrapper, so the viewer keeps\n // reporting its visibility while it recovers.\n const recoveryOverlay =\n isInRecovery && hasError ? (\n <RecoveryOverlay state={recoveryState} onManualRetry={manualRetry} isCircular={isCircular} />\n ) : null;\n\n // Vanilla error (no recovery adapter) -> error fallback\n if (hasError && !recoveryAdapter) {\n return (\n <div className={className} style={wrapperStyle}>\n <ErrorFallback isCircular={isCircular} />\n </div>\n );\n }\n\n const t = inferType({ type, mime, name, url: sourceUrl });\n\n if (t === 'image') {\n return (\n <div className={className} style={wrapperStyle}>\n <ImageViewer\n key={remountKey}\n url={sourceUrl}\n alt={alt}\n lazy={lazy}\n priority={priority}\n skeleton={skeleton}\n unloadOnExit={unloadOnExit}\n isCircular={isCircular}\n enableZoom={enableZoom}\n loadTimeoutMs={loadTimeoutMs}\n onLoad={handleLoad}\n onError={handleError}\n onVisibilityChange={onVisibilityChange}\n fallback={recoveryOverlay ?? <ErrorFallback isCircular={isCircular} />}\n />\n </div>\n );\n }\n\n if (t === 'video') {\n return (\n <div className={className} style={wrapperStyle}>\n <VideoViewer\n key={remountKey}\n url={sourceUrl}\n lazy={lazy}\n priority={priority}\n skeleton={skeleton}\n unloadOnExit={unloadOnExit}\n controls={controls}\n autoPlay={autoPlay}\n muted={muted}\n loop={loop}\n posterUrl={posterUrl}\n preload={preload}\n autoPlayOnVisible={autoPlayOnVisible}\n loadTimeoutMs={loadTimeoutMs}\n onLoad={handleLoad}\n onError={handleError}\n onVisibilityChange={onVisibilityChange}\n fallback={recoveryOverlay ?? undefined}\n onEnded={onEnded}\n onProgressSample={onProgressSample}\n startAtSeconds={startAtSeconds}\n endOverlay={endOverlay}\n playbackControlsRef={playbackControlsRef}\n />\n </div>\n );\n }\n\n if (t === 'audio') {\n return (\n <div className={className} style={wrapperStyle}>\n <AudioViewer\n key={remountKey}\n url={sourceUrl}\n lazy={lazy}\n priority={priority}\n skeleton={skeleton}\n autoPlay={autoPlay}\n loop={loop}\n preload={preload}\n visualizerMode={audioVisualizerMode}\n persistKey={audioPersistKey}\n extraActions={audioExtraActions}\n loadTimeoutMs={loadTimeoutMs}\n onLoad={handleLoad}\n onLoadChange={onLoadChange}\n onError={handleError}\n onVisibilityChange={onVisibilityChange}\n fallback={recoveryOverlay ?? undefined}\n onEnded={onEnded}\n onProgressSample={onProgressSample}\n startAtSeconds={startAtSeconds}\n endOverlay={endOverlay}\n playbackControlsRef={playbackControlsRef}\n />\n </div>\n );\n }\n\n // Unknown type -> fallback link\n return shouldShowFallback(fallbackMode as FallbackMode) ? (\n <div className={className} style={wrapperStyle}>\n <MediaFallbackLink url={sourceUrl} filename={filename} label={fallbackLabel ?? 'Download'} />\n </div>\n ) : null;\n}\n\n/** Back-compat alias */\nexport const MediaPreview = MediaViewer;\n"]}
1
+ {"version":3,"file":"media-viewer.js","sourceRoot":"","sources":["../../src/react/media-viewer.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\nimport { getSimplifiedMediaType } from '@ttt-productions/media-schemas';\nimport type { SimplifiedMediaType } from '@ttt-productions/media-schemas';\n\nimport type { MediaViewerType, FallbackMode } from '../types.js';\nimport type { MediaPreviewProps } from './types.js';\nimport {\n MediaFallbackLink,\n shouldShowFallback,\n EmptyFallback,\n ErrorFallback,\n PendingFallback,\n} from './fallback.js';\nimport { ImageViewer } from './image-viewer.js';\nimport { VideoViewer } from './video-viewer.js';\nimport { AudioViewer } from './audio-viewer.js';\nimport { useMediaRecovery } from './use-media-recovery.js';\nimport type { RecoveryState } from '../recovery.js';\n\nfunction inferType(props: {\n type?: MediaPreviewProps['type'];\n mime?: string;\n name?: string;\n url?: string;\n}): MediaViewerType {\n if (props.type) {\n // Handle MIME strings like \"image/png\" -> \"image\"\n const primary = typeof props.type === 'string' ? props.type.split('/')[0] : props.type;\n if (primary === 'image' || primary === 'video' || primary === 'audio') return primary;\n if (primary === 'other') return 'other';\n }\n\n const hint = props.mime ?? props.name ?? props.url;\n if (!hint) return 'other';\n const simplified: SimplifiedMediaType = getSimplifiedMediaType(hint);\n return simplified;\n}\n\n// ---------------------------------------------------------------------------\n// Recovery-phase UI helpers\n// ---------------------------------------------------------------------------\n\nfunction RecoveryOverlay({\n state,\n onManualRetry,\n isCircular,\n}: {\n state: RecoveryState;\n onManualRetry: () => void;\n isCircular?: boolean;\n}) {\n const wrapStyle: React.CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n width: '100%',\n height: '100%',\n gap: '0.5rem',\n ...(isCircular ? { borderRadius: '50%' } : {}),\n };\n\n if (state.phase === 'processing') {\n return (\n <div className=\"mv-recovery-processing\" style={wrapStyle}>\n <span className=\"mv-recovery-label\">Processing media&hellip;</span>\n </div>\n );\n }\n\n if (state.phase === 'finalizing') {\n return (\n <div className=\"mv-recovery-finalizing\" style={wrapStyle}>\n <span className=\"mv-recovery-label\">Finishing media&hellip;</span>\n </div>\n );\n }\n\n if (state.phase === 'transient-retry' || state.phase === 'propagating') {\n return (\n <div className=\"mv-recovery-retrying\" style={wrapStyle}>\n <span className=\"mv-recovery-label\">Loading&hellip;</span>\n </div>\n );\n }\n\n if (state.phase === 'auth-retry') {\n return (\n <div className=\"mv-recovery-auth\" style={wrapStyle}>\n <span className=\"mv-recovery-label\">Refreshing access&hellip;</span>\n </div>\n );\n }\n\n if (state.phase === 'hard-unavailable') {\n return (\n <div className=\"mv-recovery-hard\" style={wrapStyle}>\n <ErrorFallback isCircular={isCircular} />\n {state.reason === 'auth' ? (\n <span className=\"mv-recovery-label\">Access denied.</span>\n ) : (\n <span className=\"mv-recovery-label\">Media unavailable.</span>\n )}\n </div>\n );\n }\n\n if (state.phase === 'max-wait-fallback') {\n return (\n <div className=\"mv-recovery-max-wait\" style={wrapStyle}>\n <span className=\"mv-recovery-label\">This media is taking longer than expected.</span>\n <button type=\"button\" className=\"mv-recovery-retry-btn\" onClick={onManualRetry}>\n Retry\n </button>\n </div>\n );\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Main component\n// ---------------------------------------------------------------------------\n\nexport function MediaViewer(props: MediaPreviewProps) {\n const {\n url,\n alt,\n type,\n className,\n unloadOnExit,\n urlPending = false,\n isCircular = false,\n enableZoom,\n priority,\n skeleton,\n lazy,\n controls,\n autoPlay,\n muted,\n loop,\n posterUrl,\n preload,\n autoPlayOnVisible,\n onLoad,\n onError,\n onLoadChange,\n onEnded,\n onProgressSample,\n startAtSeconds,\n endOverlay,\n playbackControlsRef,\n audioVisualizerMode,\n audioPersistKey,\n audioExtraActions,\n audioCrossOrigin,\n fallbackMode = 'link',\n fallbackLabel,\n filename,\n mime,\n name,\n recoveryAdapter,\n assetStatusHint,\n loadTimeoutMs,\n } = props;\n\n const [hasError, setHasError] = React.useState(false);\n const [sourceUrl, setSourceUrl] = React.useState<string | null>(null);\n\n // Remount key: incrementing this causes child media elements to unmount and\n // re-mount from scratch, re-issuing the HTTP request.\n const [remountKey, setRemountKey] = React.useState(0);\n\n const handleRemount = React.useCallback(() => {\n setHasError(false);\n setRemountKey((k) => k + 1);\n }, []);\n\n // The viewer's visibility as its own observer reports it (MEDIA-102 — this router adds\n // none). Only a recovering viewer listens — one whose element failed, the only time a\n // retry is scheduled — and retries wait while it is off screen. A healthy viewer does\n // not listen, so its observer keeps its own lazy-load lifetime. Each failure starts from\n // \"visible\", so a report from an earlier failure never outlives it.\n const listensForVisibility = recoveryAdapter !== undefined && hasError;\n const [elementVisible, setElementVisible] = React.useState(true);\n const [listenedLastRender, setListenedLastRender] = React.useState(listensForVisibility);\n if (listenedLastRender !== listensForVisibility) {\n setListenedLastRender(listensForVisibility);\n setElementVisible(true);\n }\n\n // Recovery hook (no-op when recoveryAdapter is omitted)\n const { recoveryState, onMediaError, onMediaLoad, manualRetry } = useMediaRecovery({\n url: typeof url === 'string' ? url : null,\n adapter: recoveryAdapter,\n isElementVisible: elementVisible,\n statusHint: assetStatusHint,\n onRemount: handleRemount,\n });\n const onVisibilityChange = listensForVisibility ? setElementVisible : undefined;\n\n React.useEffect(() => {\n setHasError(false);\n let objectUrl: string | null = null;\n\n if (typeof url === 'string') {\n setSourceUrl(url);\n } else if (url instanceof Blob) {\n objectUrl = URL.createObjectURL(url);\n setSourceUrl(objectUrl);\n } else {\n setSourceUrl(null);\n }\n\n return () => {\n if (objectUrl) URL.revokeObjectURL(objectUrl);\n };\n }, [url]);\n\n const handleError = React.useCallback(() => {\n setHasError(true);\n onError?.();\n // If a recovery adapter is provided, delegate to the recovery hook.\n if (recoveryAdapter) {\n onMediaError();\n }\n }, [onError, recoveryAdapter, onMediaError]);\n\n const handleLoad = React.useCallback(() => {\n setHasError(false);\n onLoad?.();\n // Signal successful load to the recovery state machine.\n if (recoveryAdapter) {\n onMediaLoad();\n }\n }, [onLoad, recoveryAdapter, onMediaLoad]);\n\n const circularStyle: React.CSSProperties = isCircular\n ? { borderRadius: '50%', overflow: 'hidden' }\n : {};\n\n const wrapperStyle: React.CSSProperties = {\n position: 'relative',\n width: '100%',\n height: '100%',\n ...circularStyle,\n };\n\n // When recovery is active and in a terminal/blocking state, show the overlay.\n const isInRecovery =\n recoveryAdapter !== undefined &&\n recoveryState.phase !== 'idle' &&\n recoveryState.phase !== 'loaded' &&\n recoveryState.phase !== 'loading';\n\n // No URL -> empty fallback\n if (!sourceUrl) {\n return (\n <div className={className} style={wrapperStyle}>\n {urlPending ? (\n <PendingFallback isCircular={isCircular} />\n ) : (\n <EmptyFallback isCircular={isCircular} />\n )}\n </div>\n );\n }\n\n // Recovery overlay (for all non-idle/non-loaded recovery states). It renders as the\n // failed viewer's fallback, inside that viewer's observed wrapper, so the viewer keeps\n // reporting its visibility while it recovers.\n const recoveryOverlay =\n isInRecovery && hasError ? (\n <RecoveryOverlay state={recoveryState} onManualRetry={manualRetry} isCircular={isCircular} />\n ) : null;\n\n // Vanilla error (no recovery adapter) -> error fallback\n if (hasError && !recoveryAdapter) {\n return (\n <div className={className} style={wrapperStyle}>\n <ErrorFallback isCircular={isCircular} />\n </div>\n );\n }\n\n const t = inferType({ type, mime, name, url: sourceUrl });\n\n if (t === 'image') {\n return (\n <div className={className} style={wrapperStyle}>\n <ImageViewer\n key={remountKey}\n url={sourceUrl}\n alt={alt}\n lazy={lazy}\n priority={priority}\n skeleton={skeleton}\n unloadOnExit={unloadOnExit}\n isCircular={isCircular}\n enableZoom={enableZoom}\n loadTimeoutMs={loadTimeoutMs}\n onLoad={handleLoad}\n onError={handleError}\n onVisibilityChange={onVisibilityChange}\n fallback={recoveryOverlay ?? <ErrorFallback isCircular={isCircular} />}\n />\n </div>\n );\n }\n\n if (t === 'video') {\n return (\n <div className={className} style={wrapperStyle}>\n <VideoViewer\n key={remountKey}\n url={sourceUrl}\n lazy={lazy}\n priority={priority}\n skeleton={skeleton}\n unloadOnExit={unloadOnExit}\n controls={controls}\n autoPlay={autoPlay}\n muted={muted}\n loop={loop}\n posterUrl={posterUrl}\n preload={preload}\n autoPlayOnVisible={autoPlayOnVisible}\n loadTimeoutMs={loadTimeoutMs}\n onLoad={handleLoad}\n onError={handleError}\n onVisibilityChange={onVisibilityChange}\n fallback={recoveryOverlay ?? undefined}\n onEnded={onEnded}\n onProgressSample={onProgressSample}\n startAtSeconds={startAtSeconds}\n endOverlay={endOverlay}\n playbackControlsRef={playbackControlsRef}\n />\n </div>\n );\n }\n\n if (t === 'audio') {\n return (\n <div className={className} style={wrapperStyle}>\n <AudioViewer\n key={remountKey}\n url={sourceUrl}\n lazy={lazy}\n priority={priority}\n skeleton={skeleton}\n autoPlay={autoPlay}\n loop={loop}\n preload={preload}\n visualizerMode={audioVisualizerMode}\n persistKey={audioPersistKey}\n extraActions={audioExtraActions}\n crossOrigin={audioCrossOrigin}\n loadTimeoutMs={loadTimeoutMs}\n onLoad={handleLoad}\n onLoadChange={onLoadChange}\n onError={handleError}\n onVisibilityChange={onVisibilityChange}\n fallback={recoveryOverlay ?? undefined}\n onEnded={onEnded}\n onProgressSample={onProgressSample}\n startAtSeconds={startAtSeconds}\n endOverlay={endOverlay}\n playbackControlsRef={playbackControlsRef}\n />\n </div>\n );\n }\n\n // Unknown type -> fallback link\n return shouldShowFallback(fallbackMode as FallbackMode) ? (\n <div className={className} style={wrapperStyle}>\n <MediaFallbackLink url={sourceUrl} filename={filename} label={fallbackLabel ?? 'Download'} />\n </div>\n ) : null;\n}\n\n/** Back-compat alias */\nexport const MediaPreview = MediaViewer;\n"]}
@@ -123,6 +123,14 @@ export type AudioViewerProps = BaseMediaProps & MediaPlaybackProps & {
123
123
  persistKey?: string | null;
124
124
  /** App-injected action buttons rendered at the end of the player control row. */
125
125
  extraActions?: React.ReactNode;
126
+ /**
127
+ * CORS mode of the audio element. The visualizer reads the audio through Web Audio, which
128
+ * hears only silence from a cross-origin file fetched without CORS — and, once routed, so does
129
+ * the listener. Set it whenever the file is served from another origin that sends CORS
130
+ * (`use-credentials` when that origin authorizes by cookie). Without it a cross-origin file
131
+ * still plays, with the visualizer idle.
132
+ */
133
+ crossOrigin?: 'anonymous' | 'use-credentials';
126
134
  };
127
135
  export type MediaPreviewProps = {
128
136
  url?: string | File | Blob | null;
@@ -164,6 +172,8 @@ export type MediaPreviewProps = {
164
172
  audioVisualizerMode?: AudioVisualizerMode;
165
173
  audioPersistKey?: string | null;
166
174
  audioExtraActions?: React.ReactNode;
175
+ /** Audio type only — forwarded to AudioViewer's `crossOrigin` (see AudioViewerProps). */
176
+ audioCrossOrigin?: AudioViewerProps['crossOrigin'];
167
177
  fallbackMode?: FallbackMode;
168
178
  fallbackLabel?: string;
169
179
  filename?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"","sourcesContent":["import type * as React from 'react';\nimport type { MediaDiagnosticAdapter, AssetStatusHint } from '../recovery.js';\nimport type { AudioVisualizerMode } from '../visualizer.js';\nimport type {\n MediaViewerType,\n FallbackMode,\n MediaPlaybackControls,\n MediaProgressSampleReason,\n} from '../types.js';\n\n/**\n * Additive, fully optional playback API shared by `VideoViewer` and\n * `AudioViewer`. Every field is optional; when all are absent the viewers\n * behave exactly as before.\n */\nexport type MediaPlaybackProps = {\n /**\n * Fires when the media element ends (native `ended` event) with the play\n * cursor at duration. NOTE: when `loop` is set the browser restarts natively\n * and emits no `ended` event, so `onEnded` never fires in that mode.\n */\n onEnded?: () => void;\n\n /**\n * Playback-position reporting. Every sample carries the `reason` it was\n * emitted for ({@link MediaProgressSampleReason}) as an additive third\n * argument, so a consumer can throttle `periodic` reports while committing\n * the exact-moment ones. Contract:\n * - `periodic` — while playing, fires at most once every ~5 seconds of\n * playback (throttled off `timeupdate`). Never fires while paused, and is\n * SUPPRESSED near the start until the resume decision is settled: while no\n * `startAtSeconds` seed has been adopted and the element is still within\n * the late-resume adoption window, an autoplaying element reports nothing,\n * so a position of ~0 can never overwrite a saved one.\n * - `pause` / `seeked` / `ended` — fires once immediately on that event with\n * the exact position, never suppressed. The programmatic resume seek that\n * applies `startAtSeconds` reports as `seeked` with the resumed position.\n * `durationSeconds` is `0` until the element's duration is known.\n *\n * Active-element retirement: samples come only from the ACTIVE media-element\n * generation. When the viewer tears an element down (unload off-screen,\n * unmount, source replacement), it retires the element first — the still-live\n * cursor is committed once as a final `pause` sample (skipped when the last\n * commit already reported that exact position, when playback never started,\n * or while still inside the pre-seed suppression window) — and the reset\n * events the teardown itself produces (browsers snap the cursor to 0 and can\n * fire pause/seeked/timeupdate) are ignored. A genuine user seek or restart\n * to exactly 0 on the active element still reports normally.\n */\n onProgressSample?: (\n currentTimeSeconds: number,\n durationSeconds: number,\n reason: MediaProgressSampleReason,\n ) => void;\n\n /**\n * Initial playback position (seconds), applied once the element has metadata.\n * May arrive late (e.g. from an async prefs read that resolves after the\n * viewer mounted): a value that appears while playback has not yet begun is\n * adopted and applied to the already-loaded element. Once real playback has\n * begun a later value is ignored — it never yanks playing media.\n * Survives the `unloadOnExit` unload/remount cycle: once playback has started\n * the viewer tracks the live position internally and resumes from the LAST\n * KNOWN position on remount — it does not re-apply this prop and does not\n * restart at 0.\n */\n startAtSeconds?: number;\n\n /**\n * Rendered as an overlay covering the media area when the media has ended.\n * Removed when playback restarts (native replay or imperative `restart()`).\n * The overlay only intercepts pointer events while shown and renders above\n * the element. In a native-fullscreen exit state the overlay reappears over\n * the inline element (it is part of normal document flow, not the fullscreen\n * layer).\n */\n endOverlay?: React.ReactNode;\n\n /** Imperative controls handle — see {@link MediaPlaybackControls}. */\n playbackControlsRef?: React.Ref<MediaPlaybackControls>;\n};\n\nexport type BaseMediaProps = {\n url: string;\n className?: string;\n mediaClassName?: string;\n lazy?: boolean;\n priority?: boolean;\n skeleton?: boolean;\n /**\n * Fires once per load of the element, when it becomes usable: an image's `load`; a video's or\n * audio's first `loadedmetadata`, `loadeddata`, or `canplay` (a `preload=\"metadata\"` element may\n * never reach `loadeddata` before play).\n */\n onLoad?: () => void;\n onError?: () => void;\n /**\n * The viewer's own visibility, as its one IntersectionObserver reports it (MEDIA-102): called\n * with each observation while the observer runs. Not called while the observer is skipped\n * (a priority or non-lazy element that needs no visibility), which a consumer reads as visible.\n */\n onVisibilityChange?: (visible: boolean) => void;\n fallback?: React.ReactNode;\n /**\n * Load-watchdog budget in ms (default LOAD_WATCHDOG_MS). A visible, loading\n * element that produces neither a load/metadata nor an error event within\n * this budget synthesizes an error so the asset resolves to a bounded\n * terminal state instead of an eternal skeleton. `0` disables.\n */\n loadTimeoutMs?: number;\n};\n\nexport type ImageViewerProps = BaseMediaProps & {\n alt?: string;\n enableZoom?: boolean;\n unloadOnExit?: boolean;\n isCircular?: boolean;\n preventGestures?: boolean;\n};\n\nexport type VideoViewerProps = BaseMediaProps &\n MediaPlaybackProps & {\n controls?: boolean;\n autoPlay?: boolean;\n muted?: boolean;\n loop?: boolean;\n posterUrl?: string;\n enableFullscreen?: boolean;\n unloadOnExit?: boolean;\n preload?: 'auto' | 'metadata' | 'none';\n autoPlayOnVisible?: boolean;\n };\n\nexport type AudioViewerProps = BaseMediaProps &\n MediaPlaybackProps & {\n autoPlay?: boolean;\n loop?: boolean;\n preload?: 'auto' | 'metadata' | 'none';\n onLoadChange?: (isLoading: boolean) => void;\n\n // AudioViewer always uses the package-owned control surface. Native browser\n // audio controls are intentionally not part of the public contract.\n /** Initial visualizer mode (\"line\" oscilloscope | \"bars\" EQ). A persisted user choice wins. */\n visualizerMode?: AudioVisualizerMode;\n /** localStorage key base for the minimize/mode prefs. `null` disables persistence. */\n persistKey?: string | null;\n /** App-injected action buttons rendered at the end of the player control row. */\n extraActions?: React.ReactNode;\n };\n\nexport type MediaPreviewProps = {\n url?: string | File | Blob | null;\n /**\n * The URL is still being prepared (for example a short-lived access grant is\n * being minted). While true and no `url` is available, the viewer renders a\n * loading state instead of the empty fallback, so \"not yet\" never reads as\n * \"nothing here\".\n */\n urlPending?: boolean;\n alt?: string;\n type?: MediaViewerType | string;\n className?: string;\n unloadOnExit?: boolean;\n isCircular?: boolean;\n /** Image type only — forwarded to ImageViewer's click-to-zoom toggle (which\n * defaults ON). Pass `false` to render a plain, non-zooming image surface.\n * Ignored for video/audio/other types. */\n enableZoom?: ImageViewerProps['enableZoom'];\n priority?: boolean;\n skeleton?: boolean;\n lazy?: boolean;\n /** Video controls only. Audio always renders the canonical package-owned controls. */\n controls?: boolean;\n autoPlay?: boolean;\n muted?: boolean;\n loop?: boolean;\n posterUrl?: string;\n preload?: 'auto' | 'metadata' | 'none';\n autoPlayOnVisible?: boolean;\n onLoad?: () => void;\n onError?: () => void;\n onLoadChange?: (isLoading: boolean) => void;\n\n // -------------------------------------------------------------------------\n // Playback API (additive, optional) — forwarded to the video/audio viewer.\n // Ignored for image/other types. See MediaPlaybackProps for the contract.\n // -------------------------------------------------------------------------\n onEnded?: MediaPlaybackProps['onEnded'];\n onProgressSample?: MediaPlaybackProps['onProgressSample'];\n startAtSeconds?: MediaPlaybackProps['startAtSeconds'];\n endOverlay?: MediaPlaybackProps['endOverlay'];\n playbackControlsRef?: MediaPlaybackProps['playbackControlsRef'];\n\n // Audio always uses the package-owned player. These props customize that\n // single control surface and are ignored for every other media type.\n audioVisualizerMode?: AudioVisualizerMode;\n audioPersistKey?: string | null;\n audioExtraActions?: React.ReactNode;\n\n fallbackMode?: FallbackMode;\n fallbackLabel?: string;\n filename?: string;\n mime?: string;\n name?: string;\n\n // -------------------------------------------------------------------------\n // Recovery adapter (additive, optional — backward-compatible)\n // -------------------------------------------------------------------------\n /**\n * Injected diagnostic adapter. When provided, element load errors for\n * assets the app believes are live will trigger the bounded recovery state\n * machine. When omitted, the existing error fallback behavior is unchanged.\n */\n recoveryAdapter?: MediaDiagnosticAdapter;\n\n /**\n * Optional app hint about the server-side lifecycle state of the asset.\n * Drives which recovery phase is shown (e.g. processing → skeleton,\n * finalizing → spinner, failed/rejected → hard error immediately).\n */\n assetStatusHint?: AssetStatusHint;\n\n /** Load-watchdog budget forwarded to the underlying viewer — see BaseMediaProps. */\n loadTimeoutMs?: number;\n};\n\n/** Back-compat alias */\nexport type MediaViewerProps = MediaPreviewProps;\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"","sourcesContent":["import type * as React from 'react';\nimport type { MediaDiagnosticAdapter, AssetStatusHint } from '../recovery.js';\nimport type { AudioVisualizerMode } from '../visualizer.js';\nimport type {\n MediaViewerType,\n FallbackMode,\n MediaPlaybackControls,\n MediaProgressSampleReason,\n} from '../types.js';\n\n/**\n * Additive, fully optional playback API shared by `VideoViewer` and\n * `AudioViewer`. Every field is optional; when all are absent the viewers\n * behave exactly as before.\n */\nexport type MediaPlaybackProps = {\n /**\n * Fires when the media element ends (native `ended` event) with the play\n * cursor at duration. NOTE: when `loop` is set the browser restarts natively\n * and emits no `ended` event, so `onEnded` never fires in that mode.\n */\n onEnded?: () => void;\n\n /**\n * Playback-position reporting. Every sample carries the `reason` it was\n * emitted for ({@link MediaProgressSampleReason}) as an additive third\n * argument, so a consumer can throttle `periodic` reports while committing\n * the exact-moment ones. Contract:\n * - `periodic` — while playing, fires at most once every ~5 seconds of\n * playback (throttled off `timeupdate`). Never fires while paused, and is\n * SUPPRESSED near the start until the resume decision is settled: while no\n * `startAtSeconds` seed has been adopted and the element is still within\n * the late-resume adoption window, an autoplaying element reports nothing,\n * so a position of ~0 can never overwrite a saved one.\n * - `pause` / `seeked` / `ended` — fires once immediately on that event with\n * the exact position, never suppressed. The programmatic resume seek that\n * applies `startAtSeconds` reports as `seeked` with the resumed position.\n * `durationSeconds` is `0` until the element's duration is known.\n *\n * Active-element retirement: samples come only from the ACTIVE media-element\n * generation. When the viewer tears an element down (unload off-screen,\n * unmount, source replacement), it retires the element first — the still-live\n * cursor is committed once as a final `pause` sample (skipped when the last\n * commit already reported that exact position, when playback never started,\n * or while still inside the pre-seed suppression window) — and the reset\n * events the teardown itself produces (browsers snap the cursor to 0 and can\n * fire pause/seeked/timeupdate) are ignored. A genuine user seek or restart\n * to exactly 0 on the active element still reports normally.\n */\n onProgressSample?: (\n currentTimeSeconds: number,\n durationSeconds: number,\n reason: MediaProgressSampleReason,\n ) => void;\n\n /**\n * Initial playback position (seconds), applied once the element has metadata.\n * May arrive late (e.g. from an async prefs read that resolves after the\n * viewer mounted): a value that appears while playback has not yet begun is\n * adopted and applied to the already-loaded element. Once real playback has\n * begun a later value is ignored — it never yanks playing media.\n * Survives the `unloadOnExit` unload/remount cycle: once playback has started\n * the viewer tracks the live position internally and resumes from the LAST\n * KNOWN position on remount — it does not re-apply this prop and does not\n * restart at 0.\n */\n startAtSeconds?: number;\n\n /**\n * Rendered as an overlay covering the media area when the media has ended.\n * Removed when playback restarts (native replay or imperative `restart()`).\n * The overlay only intercepts pointer events while shown and renders above\n * the element. In a native-fullscreen exit state the overlay reappears over\n * the inline element (it is part of normal document flow, not the fullscreen\n * layer).\n */\n endOverlay?: React.ReactNode;\n\n /** Imperative controls handle — see {@link MediaPlaybackControls}. */\n playbackControlsRef?: React.Ref<MediaPlaybackControls>;\n};\n\nexport type BaseMediaProps = {\n url: string;\n className?: string;\n mediaClassName?: string;\n lazy?: boolean;\n priority?: boolean;\n skeleton?: boolean;\n /**\n * Fires once per load of the element, when it becomes usable: an image's `load`; a video's or\n * audio's first `loadedmetadata`, `loadeddata`, or `canplay` (a `preload=\"metadata\"` element may\n * never reach `loadeddata` before play).\n */\n onLoad?: () => void;\n onError?: () => void;\n /**\n * The viewer's own visibility, as its one IntersectionObserver reports it (MEDIA-102): called\n * with each observation while the observer runs. Not called while the observer is skipped\n * (a priority or non-lazy element that needs no visibility), which a consumer reads as visible.\n */\n onVisibilityChange?: (visible: boolean) => void;\n fallback?: React.ReactNode;\n /**\n * Load-watchdog budget in ms (default LOAD_WATCHDOG_MS). A visible, loading\n * element that produces neither a load/metadata nor an error event within\n * this budget synthesizes an error so the asset resolves to a bounded\n * terminal state instead of an eternal skeleton. `0` disables.\n */\n loadTimeoutMs?: number;\n};\n\nexport type ImageViewerProps = BaseMediaProps & {\n alt?: string;\n enableZoom?: boolean;\n unloadOnExit?: boolean;\n isCircular?: boolean;\n preventGestures?: boolean;\n};\n\nexport type VideoViewerProps = BaseMediaProps &\n MediaPlaybackProps & {\n controls?: boolean;\n autoPlay?: boolean;\n muted?: boolean;\n loop?: boolean;\n posterUrl?: string;\n enableFullscreen?: boolean;\n unloadOnExit?: boolean;\n preload?: 'auto' | 'metadata' | 'none';\n autoPlayOnVisible?: boolean;\n };\n\nexport type AudioViewerProps = BaseMediaProps &\n MediaPlaybackProps & {\n autoPlay?: boolean;\n loop?: boolean;\n preload?: 'auto' | 'metadata' | 'none';\n onLoadChange?: (isLoading: boolean) => void;\n\n // AudioViewer always uses the package-owned control surface. Native browser\n // audio controls are intentionally not part of the public contract.\n /** Initial visualizer mode (\"line\" oscilloscope | \"bars\" EQ). A persisted user choice wins. */\n visualizerMode?: AudioVisualizerMode;\n /** localStorage key base for the minimize/mode prefs. `null` disables persistence. */\n persistKey?: string | null;\n /** App-injected action buttons rendered at the end of the player control row. */\n extraActions?: React.ReactNode;\n /**\n * CORS mode of the audio element. The visualizer reads the audio through Web Audio, which\n * hears only silence from a cross-origin file fetched without CORS — and, once routed, so does\n * the listener. Set it whenever the file is served from another origin that sends CORS\n * (`use-credentials` when that origin authorizes by cookie). Without it a cross-origin file\n * still plays, with the visualizer idle.\n */\n crossOrigin?: 'anonymous' | 'use-credentials';\n };\n\nexport type MediaPreviewProps = {\n url?: string | File | Blob | null;\n /**\n * The URL is still being prepared (for example a short-lived access grant is\n * being minted). While true and no `url` is available, the viewer renders a\n * loading state instead of the empty fallback, so \"not yet\" never reads as\n * \"nothing here\".\n */\n urlPending?: boolean;\n alt?: string;\n type?: MediaViewerType | string;\n className?: string;\n unloadOnExit?: boolean;\n isCircular?: boolean;\n /** Image type only — forwarded to ImageViewer's click-to-zoom toggle (which\n * defaults ON). Pass `false` to render a plain, non-zooming image surface.\n * Ignored for video/audio/other types. */\n enableZoom?: ImageViewerProps['enableZoom'];\n priority?: boolean;\n skeleton?: boolean;\n lazy?: boolean;\n /** Video controls only. Audio always renders the canonical package-owned controls. */\n controls?: boolean;\n autoPlay?: boolean;\n muted?: boolean;\n loop?: boolean;\n posterUrl?: string;\n preload?: 'auto' | 'metadata' | 'none';\n autoPlayOnVisible?: boolean;\n onLoad?: () => void;\n onError?: () => void;\n onLoadChange?: (isLoading: boolean) => void;\n\n // -------------------------------------------------------------------------\n // Playback API (additive, optional) — forwarded to the video/audio viewer.\n // Ignored for image/other types. See MediaPlaybackProps for the contract.\n // -------------------------------------------------------------------------\n onEnded?: MediaPlaybackProps['onEnded'];\n onProgressSample?: MediaPlaybackProps['onProgressSample'];\n startAtSeconds?: MediaPlaybackProps['startAtSeconds'];\n endOverlay?: MediaPlaybackProps['endOverlay'];\n playbackControlsRef?: MediaPlaybackProps['playbackControlsRef'];\n\n // Audio always uses the package-owned player. These props customize that\n // single control surface and are ignored for every other media type.\n audioVisualizerMode?: AudioVisualizerMode;\n audioPersistKey?: string | null;\n audioExtraActions?: React.ReactNode;\n /** Audio type only — forwarded to AudioViewer's `crossOrigin` (see AudioViewerProps). */\n audioCrossOrigin?: AudioViewerProps['crossOrigin'];\n\n fallbackMode?: FallbackMode;\n fallbackLabel?: string;\n filename?: string;\n mime?: string;\n name?: string;\n\n // -------------------------------------------------------------------------\n // Recovery adapter (additive, optional — backward-compatible)\n // -------------------------------------------------------------------------\n /**\n * Injected diagnostic adapter. When provided, element load errors for\n * assets the app believes are live will trigger the bounded recovery state\n * machine. When omitted, the existing error fallback behavior is unchanged.\n */\n recoveryAdapter?: MediaDiagnosticAdapter;\n\n /**\n * Optional app hint about the server-side lifecycle state of the asset.\n * Drives which recovery phase is shown (e.g. processing → skeleton,\n * finalizing → spinner, failed/rejected → hard error immediately).\n */\n assetStatusHint?: AssetStatusHint;\n\n /** Load-watchdog budget forwarded to the underlying viewer — see BaseMediaProps. */\n loadTimeoutMs?: number;\n};\n\n/** Back-compat alias */\nexport type MediaViewerProps = MediaPreviewProps;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/media-viewer",
3
- "version": "0.10.17",
3
+ "version": "0.10.18",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ttt-productions/ttt-packages.git",