@ttt-productions/media-viewer 0.10.16 → 0.10.17
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.
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/media-duration.js +2 -2
- package/dist/media-duration.js.map +1 -1
- package/dist/react/audio-player-chrome.d.ts +2 -2
- package/dist/react/audio-player-chrome.d.ts.map +1 -1
- package/dist/react/audio-player-chrome.js +50 -50
- package/dist/react/audio-player-chrome.js.map +1 -1
- package/dist/react/audio-player.d.ts +1 -1
- package/dist/react/audio-player.d.ts.map +1 -1
- package/dist/react/audio-player.js +2 -2
- package/dist/react/audio-player.js.map +1 -1
- package/dist/react/audio-viewer.d.ts +2 -2
- package/dist/react/audio-viewer.d.ts.map +1 -1
- package/dist/react/audio-viewer.js +14 -14
- package/dist/react/audio-viewer.js.map +1 -1
- package/dist/react/fallback.d.ts +1 -1
- package/dist/react/fallback.d.ts.map +1 -1
- package/dist/react/fallback.js +7 -7
- package/dist/react/fallback.js.map +1 -1
- package/dist/react/image-viewer.d.ts +2 -2
- package/dist/react/image-viewer.d.ts.map +1 -1
- package/dist/react/image-viewer.js +28 -22
- package/dist/react/image-viewer.js.map +1 -1
- package/dist/react/index.d.ts +12 -11
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +9 -9
- package/dist/react/index.js.map +1 -1
- package/dist/react/media-viewer.d.ts +2 -2
- package/dist/react/media-viewer.d.ts.map +1 -1
- package/dist/react/media-viewer.js +43 -43
- package/dist/react/media-viewer.js.map +1 -1
- package/dist/react/types.d.ts +189 -0
- package/dist/react/types.d.ts.map +1 -0
- package/dist/react/types.js +2 -0
- package/dist/react/types.js.map +1 -0
- package/dist/react/use-load-watchdog.d.ts.map +1 -1
- package/dist/react/use-load-watchdog.js +4 -5
- package/dist/react/use-load-watchdog.js.map +1 -1
- package/dist/react/use-media-playback.d.ts +3 -2
- package/dist/react/use-media-playback.d.ts.map +1 -1
- package/dist/react/use-media-playback.js +13 -11
- package/dist/react/use-media-playback.js.map +1 -1
- package/dist/react/use-media-recovery.d.ts +1 -1
- package/dist/react/use-media-recovery.d.ts.map +1 -1
- package/dist/react/use-media-recovery.js +37 -37
- package/dist/react/use-media-recovery.js.map +1 -1
- package/dist/react/video-viewer.d.ts +2 -2
- package/dist/react/video-viewer.d.ts.map +1 -1
- package/dist/react/video-viewer.js +31 -23
- package/dist/react/video-viewer.js.map +1 -1
- package/dist/recovery.d.ts +14 -14
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js.map +1 -1
- package/dist/types.d.ts +3 -192
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/visualizer.d.ts +1 -1
- package/dist/visualizer.d.ts.map +1 -1
- package/dist/visualizer.js +10 -8
- package/dist/visualizer.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
export { BACKOFF_SCHEDULE_MS, MAX_RECOVERY_DURATION_MS, LOAD_WATCHDOG_MS, PHASE_MAX_WAIT_MS, applyJitter, backoffForAttempt, withinBudget, } from
|
|
3
|
-
export {
|
|
4
|
-
export
|
|
5
|
-
export {
|
|
1
|
+
export type { MediaViewerType, FallbackMode, MediaPlaybackControls, MediaProgressSampleReason, } from './types.js';
|
|
2
|
+
export { BACKOFF_SCHEDULE_MS, MAX_RECOVERY_DURATION_MS, LOAD_WATCHDOG_MS, PHASE_MAX_WAIT_MS, applyJitter, backoffForAttempt, withinBudget, } from './recovery.js';
|
|
3
|
+
export type { MediaDiagnosticAdapter, AssetStatusHint, DiagnosisResult, RecoveryState, } from './recovery.js';
|
|
4
|
+
export { startWaveformLoop, drawIdleFrame } from './visualizer.js';
|
|
5
|
+
export type { StartWaveformLoopOptions, AudioVisualizerMode } from './visualizer.js';
|
|
6
|
+
export { resolveInfiniteDuration } from './media-duration.js';
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,YAAY,EACZ,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,YAAY,GACb,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,aAAa,GACd,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnE,YAAY,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAErF,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { BACKOFF_SCHEDULE_MS, MAX_RECOVERY_DURATION_MS, LOAD_WATCHDOG_MS, PHASE_MAX_WAIT_MS, applyJitter, backoffForAttempt, withinBudget, } from
|
|
2
|
-
export { startWaveformLoop, drawIdleFrame } from
|
|
3
|
-
export { resolveInfiniteDuration } from
|
|
1
|
+
export { BACKOFF_SCHEDULE_MS, MAX_RECOVERY_DURATION_MS, LOAD_WATCHDOG_MS, PHASE_MAX_WAIT_MS, applyJitter, backoffForAttempt, withinBudget, } from './recovery.js';
|
|
2
|
+
export { startWaveformLoop, drawIdleFrame } from './visualizer.js';
|
|
3
|
+
export { resolveInfiniteDuration } from './media-duration.js';
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,YAAY,GACb,MAAM,eAAe,CAAC;AAQvB,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["export type {\n MediaViewerType,\n FallbackMode,\n MediaPlaybackControls,\n MediaProgressSampleReason,\n} from './types.js';\n\nexport {\n BACKOFF_SCHEDULE_MS,\n MAX_RECOVERY_DURATION_MS,\n LOAD_WATCHDOG_MS,\n PHASE_MAX_WAIT_MS,\n applyJitter,\n backoffForAttempt,\n withinBudget,\n} from './recovery.js';\nexport type {\n MediaDiagnosticAdapter,\n AssetStatusHint,\n DiagnosisResult,\n RecoveryState,\n} from './recovery.js';\n\nexport { startWaveformLoop, drawIdleFrame } from './visualizer.js';\nexport type { StartWaveformLoopOptions, AudioVisualizerMode } from './visualizer.js';\n\nexport { resolveInfiniteDuration } from './media-duration.js';\n"]}
|
package/dist/media-duration.js
CHANGED
|
@@ -21,7 +21,7 @@ export function resolveInfiniteDuration(el, timeoutMs = 3000) {
|
|
|
21
21
|
if (done)
|
|
22
22
|
return;
|
|
23
23
|
done = true;
|
|
24
|
-
el.removeEventListener(
|
|
24
|
+
el.removeEventListener('durationchange', onDurationChange);
|
|
25
25
|
clearTimeout(timer);
|
|
26
26
|
try {
|
|
27
27
|
el.currentTime = 0; // undo the workaround seek
|
|
@@ -36,7 +36,7 @@ export function resolveInfiniteDuration(el, timeoutMs = 3000) {
|
|
|
36
36
|
finish(el.duration);
|
|
37
37
|
};
|
|
38
38
|
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
39
|
-
el.addEventListener(
|
|
39
|
+
el.addEventListener('durationchange', onDurationChange);
|
|
40
40
|
try {
|
|
41
41
|
el.currentTime = Number.MAX_SAFE_INTEGER;
|
|
42
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-duration.js","sourceRoot":"","sources":["../src/media-duration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CACrC,EAAoB,EACpB,SAAS,GAAG,IAAI;IAEhB,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;IACtE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,MAAM,MAAM,GAAG,CAAC,KAAoB,EAAE,EAAE;YACtC,IAAI,IAAI;gBAAE,OAAO;YACjB,IAAI,GAAG,IAAI,CAAC;YACZ,EAAE,CAAC,mBAAmB,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC;YAC3D,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,CAAC;gBACH,EAAE,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,2BAA2B;YACjD,CAAC;YAAC,MAAM,CAAC;gBACP,6DAA6D;YAC/D,CAAC;YACD,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,CAAC;QACF,MAAM,gBAAgB,GAAG,GAAG,EAAE;YAC5B,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC;gBAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QACxD,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;QACxD,EAAE,CAAC,gBAAgB,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC;QACxD,IAAI,CAAC;YACH,EAAE,CAAC,WAAW,GAAG,MAAM,CAAC,gBAAgB,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,CAAC;QACf,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Chromium MediaRecorder blobs (webm/opus audio, webm video) report\n * `duration: Infinity` (or NaN) at `loadedmetadata` — the webm header carries\n * no duration until the engine has scanned the stream. The standard workaround:\n * seek far past the end; the engine then computes the real duration and fires\n * `durationchange` with a finite value.\n *\n * Resolves the element's REAL finite duration in seconds, or `null` when it\n * cannot be determined within `timeoutMs` — callers decide their own fallback\n * (the file-input duration validator fails OPEN on null; the audio player\n * chrome keeps its placeholder). When the workaround seek actually ran,\n * `currentTime` is restored to 0 before resolving, so a paused just-mounted\n * preview is left visually untouched.\n */\nexport function resolveInfiniteDuration(\n el: HTMLMediaElement,\n timeoutMs = 3000,\n): Promise<number | null> {\n if (Number.isFinite(el.duration)) return Promise.resolve(el.duration);\n return new Promise((resolve) => {\n let done = false;\n const finish = (value: number | null) => {\n if (done) return;\n done = true;\n el.removeEventListener(
|
|
1
|
+
{"version":3,"file":"media-duration.js","sourceRoot":"","sources":["../src/media-duration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CACrC,EAAoB,EACpB,SAAS,GAAG,IAAI;IAEhB,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;IACtE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,MAAM,MAAM,GAAG,CAAC,KAAoB,EAAE,EAAE;YACtC,IAAI,IAAI;gBAAE,OAAO;YACjB,IAAI,GAAG,IAAI,CAAC;YACZ,EAAE,CAAC,mBAAmB,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC;YAC3D,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,CAAC;gBACH,EAAE,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,2BAA2B;YACjD,CAAC;YAAC,MAAM,CAAC;gBACP,6DAA6D;YAC/D,CAAC;YACD,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,CAAC;QACF,MAAM,gBAAgB,GAAG,GAAG,EAAE;YAC5B,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC;gBAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QACxD,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;QACxD,EAAE,CAAC,gBAAgB,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC;QACxD,IAAI,CAAC;YACH,EAAE,CAAC,WAAW,GAAG,MAAM,CAAC,gBAAgB,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,CAAC;QACf,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Chromium MediaRecorder blobs (webm/opus audio, webm video) report\n * `duration: Infinity` (or NaN) at `loadedmetadata` — the webm header carries\n * no duration until the engine has scanned the stream. The standard workaround:\n * seek far past the end; the engine then computes the real duration and fires\n * `durationchange` with a finite value.\n *\n * Resolves the element's REAL finite duration in seconds, or `null` when it\n * cannot be determined within `timeoutMs` — callers decide their own fallback\n * (the file-input duration validator fails OPEN on null; the audio player\n * chrome keeps its placeholder). When the workaround seek actually ran,\n * `currentTime` is restored to 0 before resolving, so a paused just-mounted\n * preview is left visually untouched.\n */\nexport function resolveInfiniteDuration(\n el: HTMLMediaElement,\n timeoutMs = 3000,\n): Promise<number | null> {\n if (Number.isFinite(el.duration)) return Promise.resolve(el.duration);\n return new Promise((resolve) => {\n let done = false;\n const finish = (value: number | null) => {\n if (done) return;\n done = true;\n el.removeEventListener('durationchange', onDurationChange);\n clearTimeout(timer);\n try {\n el.currentTime = 0; // undo the workaround seek\n } catch {\n // detached/errored element — the resolved value still stands\n }\n resolve(value);\n };\n const onDurationChange = () => {\n if (Number.isFinite(el.duration)) finish(el.duration);\n };\n const timer = setTimeout(() => finish(null), timeoutMs);\n el.addEventListener('durationchange', onDurationChange);\n try {\n el.currentTime = Number.MAX_SAFE_INTEGER;\n } catch {\n finish(null);\n }\n });\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import type { AudioVisualizerMode } from
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { AudioVisualizerMode } from '../visualizer.js';
|
|
3
3
|
export type AudioPlayerChromeProps = {
|
|
4
4
|
audioRef: React.RefObject<HTMLAudioElement | null>;
|
|
5
5
|
/** Initial visualizer mode; a persisted user choice wins over this. */
|
|
@@ -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;
|
|
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,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
// Custom audio player chrome — the one owned control surface used by every
|
|
4
4
|
// AudioViewer. Owns: play/pause, seek, time readout, mute/volume, and
|
|
@@ -12,12 +12,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
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
14
|
// the element itself changes.
|
|
15
|
-
import * as React from
|
|
16
|
-
import { Pause, Play, Volume2, VolumeX, ChevronDown, ChevronUp, Activity, BarChart3 } from
|
|
17
|
-
import { Spinner } from
|
|
18
|
-
import { startWaveformLoop, drawIdleFrame } from
|
|
19
|
-
import { resolveInfiniteDuration } from
|
|
20
|
-
const DEFAULT_PERSIST_KEY =
|
|
15
|
+
import * as React from 'react';
|
|
16
|
+
import { Pause, Play, Volume2, VolumeX, ChevronDown, ChevronUp, Activity, BarChart3, } from 'lucide-react';
|
|
17
|
+
import { Spinner } from '@ttt-productions/ui-core/react';
|
|
18
|
+
import { startWaveformLoop, drawIdleFrame } from '../visualizer.js';
|
|
19
|
+
import { resolveInfiniteDuration } from '../media-duration.js';
|
|
20
|
+
const DEFAULT_PERSIST_KEY = 'mv-audio-player';
|
|
21
21
|
function readPref(persistKey, suffix) {
|
|
22
22
|
if (!persistKey)
|
|
23
23
|
return null;
|
|
@@ -40,13 +40,13 @@ function writePref(persistKey, suffix, value) {
|
|
|
40
40
|
}
|
|
41
41
|
function formatTime(seconds) {
|
|
42
42
|
if (!Number.isFinite(seconds) || seconds < 0)
|
|
43
|
-
return
|
|
43
|
+
return '0:00';
|
|
44
44
|
const whole = Math.floor(seconds);
|
|
45
45
|
const m = Math.floor(whole / 60);
|
|
46
46
|
const s = whole % 60;
|
|
47
|
-
return `${m}:${s.toString().padStart(2,
|
|
47
|
+
return `${m}:${s.toString().padStart(2, '0')}`;
|
|
48
48
|
}
|
|
49
|
-
export function AudioPlayerChrome({ audioRef, visualizerMode =
|
|
49
|
+
export function AudioPlayerChrome({ audioRef, visualizerMode = 'bars', persistKey = DEFAULT_PERSIST_KEY, extraActions, }) {
|
|
50
50
|
const [isPlaying, setIsPlaying] = React.useState(false);
|
|
51
51
|
// Playback is waiting on data (initial buffer, a stall, or a seek landing).
|
|
52
52
|
const [buffering, setBuffering] = React.useState(false);
|
|
@@ -54,10 +54,10 @@ export function AudioPlayerChrome({ audioRef, visualizerMode = "bars", persistKe
|
|
|
54
54
|
const [duration, setDuration] = React.useState(0);
|
|
55
55
|
const [isMuted, setIsMuted] = React.useState(false);
|
|
56
56
|
const [volume, setVolume] = React.useState(1);
|
|
57
|
-
const [minimized, setMinimized] = React.useState(() => readPref(persistKey,
|
|
57
|
+
const [minimized, setMinimized] = React.useState(() => readPref(persistKey, 'minimized') === '1');
|
|
58
58
|
const [mode, setMode] = React.useState(() => {
|
|
59
|
-
const stored = readPref(persistKey,
|
|
60
|
-
return stored ===
|
|
59
|
+
const stored = readPref(persistKey, 'mode');
|
|
60
|
+
return stored === 'line' || stored === 'bars' ? stored : visualizerMode;
|
|
61
61
|
});
|
|
62
62
|
// While the user drags the seek slider, timeupdate must not fight the thumb.
|
|
63
63
|
const [scrubTime, setScrubTime] = React.useState(null);
|
|
@@ -79,14 +79,14 @@ export function AudioPlayerChrome({ audioRef, visualizerMode = "bars", persistKe
|
|
|
79
79
|
sourceElRef.current = null;
|
|
80
80
|
const ctx = audioCtxRef.current;
|
|
81
81
|
audioCtxRef.current = null;
|
|
82
|
-
if (ctx && ctx.state !==
|
|
82
|
+
if (ctx && ctx.state !== 'closed') {
|
|
83
83
|
ctx.close().catch(() => { });
|
|
84
84
|
}
|
|
85
85
|
}, [stopLoop]);
|
|
86
86
|
const ensureGraph = React.useCallback((el) => {
|
|
87
87
|
try {
|
|
88
88
|
if (sourceElRef.current === el && audioCtxRef.current) {
|
|
89
|
-
if (audioCtxRef.current.state ===
|
|
89
|
+
if (audioCtxRef.current.state === 'suspended') {
|
|
90
90
|
audioCtxRef.current.resume().catch(() => { });
|
|
91
91
|
}
|
|
92
92
|
return;
|
|
@@ -107,7 +107,7 @@ export function AudioPlayerChrome({ audioRef, visualizerMode = "bars", persistKe
|
|
|
107
107
|
audioCtxRef.current = ctx;
|
|
108
108
|
sourceElRef.current = el;
|
|
109
109
|
analyserRef.current = analyser;
|
|
110
|
-
if (ctx.state ===
|
|
110
|
+
if (ctx.state === 'suspended') {
|
|
111
111
|
ctx.resume().catch(() => { });
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -187,37 +187,37 @@ export function AudioPlayerChrome({ audioRef, visualizerMode = "bars", persistKe
|
|
|
187
187
|
};
|
|
188
188
|
const onWaiting = () => setBuffering(true);
|
|
189
189
|
const onReady = () => setBuffering(false);
|
|
190
|
-
el.addEventListener(
|
|
191
|
-
el.addEventListener(
|
|
192
|
-
el.addEventListener(
|
|
193
|
-
el.addEventListener(
|
|
194
|
-
el.addEventListener(
|
|
195
|
-
el.addEventListener(
|
|
196
|
-
el.addEventListener(
|
|
197
|
-
el.addEventListener(
|
|
198
|
-
el.addEventListener(
|
|
199
|
-
el.addEventListener(
|
|
200
|
-
el.addEventListener(
|
|
201
|
-
el.addEventListener(
|
|
202
|
-
el.addEventListener(
|
|
203
|
-
el.addEventListener(
|
|
204
|
-
el.addEventListener(
|
|
190
|
+
el.addEventListener('play', onPlay);
|
|
191
|
+
el.addEventListener('pause', onPause);
|
|
192
|
+
el.addEventListener('ended', onEnded);
|
|
193
|
+
el.addEventListener('timeupdate', onTimeUpdate);
|
|
194
|
+
el.addEventListener('durationchange', onDurationChange);
|
|
195
|
+
el.addEventListener('loadedmetadata', onDurationChange);
|
|
196
|
+
el.addEventListener('volumechange', onVolumeChange);
|
|
197
|
+
el.addEventListener('waiting', onWaiting);
|
|
198
|
+
el.addEventListener('seeking', onWaiting);
|
|
199
|
+
el.addEventListener('playing', onReady);
|
|
200
|
+
el.addEventListener('seeked', onReady);
|
|
201
|
+
el.addEventListener('canplay', onReady);
|
|
202
|
+
el.addEventListener('pause', onReady);
|
|
203
|
+
el.addEventListener('ended', onReady);
|
|
204
|
+
el.addEventListener('error', onReady);
|
|
205
205
|
return () => {
|
|
206
|
-
el.removeEventListener(
|
|
207
|
-
el.removeEventListener(
|
|
208
|
-
el.removeEventListener(
|
|
209
|
-
el.removeEventListener(
|
|
210
|
-
el.removeEventListener(
|
|
211
|
-
el.removeEventListener(
|
|
212
|
-
el.removeEventListener(
|
|
213
|
-
el.removeEventListener(
|
|
214
|
-
el.removeEventListener(
|
|
215
|
-
el.removeEventListener(
|
|
216
|
-
el.removeEventListener(
|
|
217
|
-
el.removeEventListener(
|
|
218
|
-
el.removeEventListener(
|
|
219
|
-
el.removeEventListener(
|
|
220
|
-
el.removeEventListener(
|
|
206
|
+
el.removeEventListener('play', onPlay);
|
|
207
|
+
el.removeEventListener('pause', onPause);
|
|
208
|
+
el.removeEventListener('ended', onEnded);
|
|
209
|
+
el.removeEventListener('timeupdate', onTimeUpdate);
|
|
210
|
+
el.removeEventListener('durationchange', onDurationChange);
|
|
211
|
+
el.removeEventListener('loadedmetadata', onDurationChange);
|
|
212
|
+
el.removeEventListener('volumechange', onVolumeChange);
|
|
213
|
+
el.removeEventListener('waiting', onWaiting);
|
|
214
|
+
el.removeEventListener('seeking', onWaiting);
|
|
215
|
+
el.removeEventListener('playing', onReady);
|
|
216
|
+
el.removeEventListener('seeked', onReady);
|
|
217
|
+
el.removeEventListener('canplay', onReady);
|
|
218
|
+
el.removeEventListener('pause', onReady);
|
|
219
|
+
el.removeEventListener('ended', onReady);
|
|
220
|
+
el.removeEventListener('error', onReady);
|
|
221
221
|
};
|
|
222
222
|
}, [audioRef, ensureGraph, startLoop, stopLoop]);
|
|
223
223
|
// Full teardown when the chrome unmounts.
|
|
@@ -271,18 +271,18 @@ export function AudioPlayerChrome({ audioRef, visualizerMode = "bars", persistKe
|
|
|
271
271
|
};
|
|
272
272
|
const toggleMinimized = () => {
|
|
273
273
|
setMinimized((m) => {
|
|
274
|
-
writePref(persistKey,
|
|
274
|
+
writePref(persistKey, 'minimized', m ? '0' : '1');
|
|
275
275
|
return !m;
|
|
276
276
|
});
|
|
277
277
|
};
|
|
278
278
|
const toggleMode = () => {
|
|
279
279
|
setMode((prev) => {
|
|
280
|
-
const next = prev ===
|
|
281
|
-
writePref(persistKey,
|
|
280
|
+
const next = prev === 'line' ? 'bars' : 'line';
|
|
281
|
+
writePref(persistKey, 'mode', next);
|
|
282
282
|
return next;
|
|
283
283
|
});
|
|
284
284
|
};
|
|
285
285
|
const shownTime = scrubTime ?? currentTime;
|
|
286
|
-
return (_jsxs("div", { className: "mv-player", "data-mv-player": true, "data-minimized": minimized ?
|
|
286
|
+
return (_jsxs("div", { className: "mv-player", "data-mv-player": true, "data-minimized": minimized ? 'true' : 'false', "data-mode": mode, children: [!minimized && (_jsxs("div", { className: "mv-player-viz", children: [_jsx("canvas", { ref: canvasRef, width: 600, height: 96, className: "mv-player-canvas", "aria-hidden": "true" }), _jsx("button", { type: "button", className: "mv-player-btn mv-player-mode-btn", onClick: toggleMode, "aria-label": mode === 'line' ? 'Switch visualizer to bars' : 'Switch visualizer to waveform line', children: mode === 'line' ? _jsx(BarChart3, { "aria-hidden": "true" }) : _jsx(Activity, { "aria-hidden": "true" }) })] })), _jsxs("div", { className: "mv-player-controls", children: [_jsx("button", { type: "button", className: "mv-player-btn mv-player-play-btn", onClick: togglePlay, "aria-label": isPlaying ? 'Pause' : 'Play', "aria-busy": isPlaying && buffering ? true : undefined, children: isPlaying && buffering ? (_jsx(Spinner, { size: "xs" })) : isPlaying ? (_jsx(Pause, { "aria-hidden": "true" })) : (_jsx(Play, { "aria-hidden": "true" })) }), _jsx("span", { className: "mv-player-time", "aria-hidden": "true", children: formatTime(shownTime) }), _jsx("input", { type: "range", className: "mv-player-seek", min: 0, max: duration > 0 ? duration : 0, step: 0.1, value: Math.min(shownTime, duration > 0 ? duration : shownTime), onChange: onSeekInput, onMouseUp: commitSeek, onTouchEnd: commitSeek, onKeyUp: commitSeek, disabled: duration <= 0, "aria-label": "Seek" }), _jsx("span", { className: "mv-player-time", "aria-hidden": "true", children: formatTime(duration) }), _jsx("button", { type: "button", className: "mv-player-btn", onClick: toggleMute, "aria-label": isMuted ? 'Unmute' : 'Mute', children: isMuted ? _jsx(VolumeX, { "aria-hidden": "true" }) : _jsx(Volume2, { "aria-hidden": "true" }) }), _jsx("input", { type: "range", className: "mv-player-volume", min: 0, max: 1, step: 0.05, value: isMuted ? 0 : volume, onChange: onVolumeInput, "aria-label": "Volume" }), _jsx("button", { type: "button", className: "mv-player-btn", onClick: toggleMinimized, "aria-label": minimized ? 'Show visualizer' : 'Hide visualizer', children: minimized ? _jsx(ChevronUp, { "aria-hidden": "true" }) : _jsx(ChevronDown, { "aria-hidden": "true" }) }), extraActions != null && _jsx("div", { className: "mv-player-extra", children: extraActions })] })] }));
|
|
287
287
|
}
|
|
288
288
|
//# sourceMappingURL=audio-player-chrome.js.map
|
|
@@ -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 { Pause, Play, Volume2, VolumeX, ChevronDown, ChevronUp, Activity, BarChart3 } 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>(() => readPref(persistKey, \"minimized\") === \"1\");\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 className=\"mv-player\" data-mv-player data-minimized={minimized ? \"true\" : \"false\"} data-mode={mode}>\n {!minimized && (\n <div className=\"mv-player-viz\">\n <canvas ref={canvasRef} width={600} height={96} className=\"mv-player-canvas\" aria-hidden=\"true\" />\n <button\n type=\"button\"\n className=\"mv-player-btn mv-player-mode-btn\"\n onClick={toggleMode}\n aria-label={mode === \"line\" ? \"Switch visualizer to bars\" : \"Switch visualizer to waveform line\"}\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 ? <Spinner size=\"xs\" /> : isPlaying ? <Pause aria-hidden=\"true\" /> : <Play aria-hidden=\"true\" />}\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.\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,4 +1,4 @@
|
|
|
1
|
-
import type { AudioViewerProps } from
|
|
1
|
+
import type { AudioViewerProps } from './types.js';
|
|
2
2
|
export type AudioPlayerProps = AudioViewerProps;
|
|
3
3
|
export declare function AudioPlayer(props: AudioPlayerProps): import("react").JSX.Element;
|
|
4
4
|
//# sourceMappingURL=audio-player.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio-player.d.ts","sourceRoot":"","sources":["../../src/react/audio-player.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"audio-player.d.ts","sourceRoot":"","sources":["../../src/react/audio-player.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,MAAM,gBAAgB,GAAG,gBAAgB,CAAC;AAEhD,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BAElD"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
// Semantic named entry point for the canonical AudioViewer. Both exports use
|
|
4
4
|
// the same package-owned control surface; native browser chrome is unavailable.
|
|
5
|
-
import { AudioViewer } from
|
|
5
|
+
import { AudioViewer } from './audio-viewer.js';
|
|
6
6
|
export function AudioPlayer(props) {
|
|
7
7
|
return _jsx(AudioViewer, { ...props });
|
|
8
8
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio-player.js","sourceRoot":"","sources":["../../src/react/audio-player.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,6EAA6E;AAC7E,gFAAgF;AAEhF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKhD,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,OAAO,KAAC,WAAW,OAAK,KAAK,GAAI,CAAC;AACpC,CAAC","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"audio-player.js","sourceRoot":"","sources":["../../src/react/audio-player.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,6EAA6E;AAC7E,gFAAgF;AAEhF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKhD,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,OAAO,KAAC,WAAW,OAAK,KAAK,GAAI,CAAC;AACpC,CAAC","sourcesContent":["'use client';\n\n// Semantic named entry point for the canonical AudioViewer. Both exports use\n// the same package-owned control surface; native browser chrome is unavailable.\n\nimport { AudioViewer } from './audio-viewer.js';\nimport type { AudioViewerProps } from './types.js';\n\nexport type AudioPlayerProps = AudioViewerProps;\n\nexport function AudioPlayer(props: AudioPlayerProps) {\n return <AudioViewer {...props} />;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import type { AudioViewerProps } from
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { AudioViewerProps } from './types.js';
|
|
3
3
|
export declare function AudioViewer(props: AudioViewerProps): React.JSX.Element;
|
|
4
4
|
//# sourceMappingURL=audio-viewer.d.ts.map
|
|
@@ -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,
|
|
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,19 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import * as React from
|
|
3
|
-
import { useInView } from
|
|
4
|
-
import { Skeleton } from
|
|
5
|
-
import { useMediaPlayback } from
|
|
6
|
-
import { AudioPlayerChrome } from
|
|
7
|
-
import { useLoadWatchdog } from
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { useInView } from 'react-intersection-observer';
|
|
4
|
+
import { Skeleton } from '@ttt-productions/ui-core/react';
|
|
5
|
+
import { useMediaPlayback } from './use-media-playback.js';
|
|
6
|
+
import { AudioPlayerChrome } from './audio-player-chrome.js';
|
|
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 =
|
|
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;
|
|
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);
|
|
13
13
|
const audioRef = React.useRef(null);
|
|
14
14
|
const loadReportedRef = React.useRef(false);
|
|
15
15
|
// Additive playback API — derives from element events (no observer, MEDIA-102).
|
|
16
|
-
const { hasEnded, handlers: playbackHandlers, attachElement } = useMediaPlayback(audioRef, { onEnded, onProgressSample, startAtSeconds, endOverlay }, playbackControlsRef, url);
|
|
16
|
+
const { hasEnded, handlers: playbackHandlers, attachElement, } = useMediaPlayback(audioRef, { onEnded, onProgressSample, startAtSeconds, endOverlay }, playbackControlsRef, url);
|
|
17
17
|
// The element attaches through the playback lifecycle so the hook knows the
|
|
18
18
|
// active generation; detach (unmount) retires it.
|
|
19
19
|
const setAudioRef = React.useCallback((el) => {
|
|
@@ -26,7 +26,7 @@ export function AudioViewer(props) {
|
|
|
26
26
|
triggerOnce: false,
|
|
27
27
|
threshold: 0.01,
|
|
28
28
|
skip: priority || (shouldLoad && !onVisibilityChange),
|
|
29
|
-
rootMargin:
|
|
29
|
+
rootMargin: '100px',
|
|
30
30
|
onChange: (visible) => onVisibilityChange?.(visible),
|
|
31
31
|
});
|
|
32
32
|
React.useEffect(() => {
|
|
@@ -75,12 +75,12 @@ export function AudioViewer(props) {
|
|
|
75
75
|
// The error state stays inside the observed wrapper, so the viewer keeps reporting
|
|
76
76
|
// its visibility while its owner recovers it.
|
|
77
77
|
if (hasError) {
|
|
78
|
-
return (_jsx("div", { ref: inViewRef, className: className, style: fallback ? { position:
|
|
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:
|
|
81
|
-
width:
|
|
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: {
|
|
81
|
+
width: '100%',
|
|
82
82
|
opacity: isLoaded ? 1 : 0,
|
|
83
|
-
transition:
|
|
84
|
-
} }), _jsx(AudioPlayerChrome, { audioRef: audioRef, visualizerMode: visualizerMode, persistKey: persistKey, extraActions: extraActions }), endOverlay != null && hasEnded && (_jsx("div", { className: "mv-end-overlay", style: { position:
|
|
83
|
+
transition: 'opacity var(--motion-slow) var(--motion-ease)',
|
|
84
|
+
} }), _jsx(AudioPlayerChrome, { audioRef: audioRef, visualizerMode: visualizerMode, persistKey: persistKey, extraActions: extraActions }), endOverlay != null && hasEnded && (_jsx("div", { className: "mv-end-overlay", style: { position: 'absolute', inset: 0, zIndex: 2 }, children: endOverlay }))] })) }));
|
|
85
85
|
}
|
|
86
86
|
//# sourceMappingURL=audio-viewer.js.map
|
|
@@ -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 { hasEnded, handlers: playbackHandlers, attachElement } = 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 ? <Skeleton style={{ height: \"3.5rem\", width: \"100%\" }} /> : 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\n className=\"mv-end-overlay\"\n style={{ position: \"absolute\", inset: 0, zIndex: 2 }}\n >\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,GACb,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,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 } = 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"]}
|
package/dist/react/fallback.d.ts
CHANGED