@ttt-productions/media-viewer 0.8.0 → 0.9.0
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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react/audio-viewer.d.ts.map +1 -1
- package/dist/react/audio-viewer.js +6 -3
- package/dist/react/audio-viewer.js.map +1 -1
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/media-viewer.d.ts.map +1 -1
- package/dist/react/media-viewer.js +81 -8
- package/dist/react/media-viewer.js.map +1 -1
- package/dist/react/use-media-playback.d.ts +43 -0
- package/dist/react/use-media-playback.d.ts.map +1 -0
- package/dist/react/use-media-playback.js +189 -0
- package/dist/react/use-media-playback.js.map +1 -0
- package/dist/react/use-media-recovery.d.ts +42 -0
- package/dist/react/use-media-recovery.d.ts.map +1 -0
- package/dist/react/use-media-recovery.js +319 -0
- package/dist/react/use-media-recovery.js.map +1 -0
- package/dist/react/video-viewer.d.ts.map +1 -1
- package/dist/react/video-viewer.js +8 -3
- package/dist/react/video-viewer.js.map +1 -1
- package/dist/recovery.d.ts +94 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +37 -0
- package/dist/recovery.js.map +1 -0
- package/dist/types.d.ts +74 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/styles/media-viewer.css +9 -0
- package/dist/components/media-job-status-list.d.ts +0 -13
- package/dist/components/media-job-status-list.d.ts.map +0 -1
- package/dist/components/media-job-status-list.js +0 -58
- package/dist/components/media-job-status-list.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export type { MediaViewerType, FallbackMode, BaseMediaProps, ImageViewerProps, VideoViewerProps, AudioViewerProps, MediaPreviewProps, MediaViewerProps, } from "./types.js";
|
|
1
|
+
export type { MediaViewerType, FallbackMode, BaseMediaProps, ImageViewerProps, VideoViewerProps, AudioViewerProps, MediaPreviewProps, MediaViewerProps, MediaPlaybackProps, MediaPlaybackControls, MediaDiagnosticAdapter, AssetStatusHint, DiagnosisResult, RecoveryState, } from "./types.js";
|
|
2
|
+
export { BACKOFF_SCHEDULE_MS, MAX_RECOVERY_DURATION_MS, applyJitter, backoffForAttempt, withinBudget, } from "./recovery.js";
|
|
2
3
|
//# 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,eAAe,EACf,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,WAAW,EACX,iBAAiB,EACjB,YAAY,GACb,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export { BACKOFF_SCHEDULE_MS, MAX_RECOVERY_DURATION_MS, applyJitter, backoffForAttempt, withinBudget, } from "./recovery.js";
|
|
2
2
|
//# 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":"AAiBA,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,WAAW,EACX,iBAAiB,EACjB,YAAY,GACb,MAAM,eAAe,CAAC"}
|
|
@@ -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,aAAa,CAAC;
|
|
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,aAAa,CAAC;AAGpD,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,qBAqKlD"}
|
|
@@ -2,12 +2,15 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { useInView } from "react-intersection-observer";
|
|
4
4
|
import { Skeleton } from "@ttt-productions/ui-core/react";
|
|
5
|
+
import { useMediaPlayback } from "./use-media-playback.js";
|
|
5
6
|
export function AudioViewer(props) {
|
|
6
|
-
const { url, className, mediaClassName, controls = true, autoPlay, loop = false, lazy = true, skeleton: showSkeleton = true, priority = false, preload = "metadata", onLoad, onLoadChange, onError, fallback, } = props;
|
|
7
|
+
const { url, className, mediaClassName, controls = true, autoPlay, loop = false, lazy = true, skeleton: showSkeleton = true, priority = false, preload = "metadata", onLoad, onLoadChange, onError, fallback, onEnded, onProgressSample, startAtSeconds, endOverlay, playbackControlsRef, } = props;
|
|
7
8
|
const [isLoaded, setIsLoaded] = React.useState(false);
|
|
8
9
|
const [hasError, setHasError] = React.useState(false);
|
|
9
10
|
const [shouldLoad, setShouldLoad] = React.useState(priority || !lazy);
|
|
10
11
|
const audioRef = React.useRef(null);
|
|
12
|
+
// Additive playback API — derives from element events (no observer, Rule 22).
|
|
13
|
+
const { hasEnded, handlers: playbackHandlers } = useMediaPlayback(audioRef, { onEnded, onProgressSample, startAtSeconds, endOverlay }, playbackControlsRef, url);
|
|
11
14
|
const { ref: inViewRef, inView } = useInView({
|
|
12
15
|
triggerOnce: false,
|
|
13
16
|
threshold: 0.01,
|
|
@@ -71,10 +74,10 @@ export function AudioViewer(props) {
|
|
|
71
74
|
return _jsx(_Fragment, { children: fallback });
|
|
72
75
|
return (_jsx("div", { className: className, children: _jsx("div", { className: "mv-audio-error", children: _jsx("p", { children: "Failed to load audio" }) }) }));
|
|
73
76
|
}
|
|
74
|
-
return (_jsx("div", { ref: inViewRef, className: className, role: controls ? undefined : "button", tabIndex: controls ? undefined : 0, "aria-label": controls ? undefined : "Toggle audio playback", onClick: controls ? undefined : togglePlay, onKeyDown: controls ? undefined : onKeyDown, 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: audioRef, src: url, className: mediaClassName, controls: controls, autoPlay: autoPlay, loop: loop, preload: preload, onLoadedData: handleLoadedData, onCanPlay: handleCanPlay, onError: handleError, style: {
|
|
77
|
+
return (_jsx("div", { ref: inViewRef, className: className, role: controls ? undefined : "button", tabIndex: controls ? undefined : 0, "aria-label": controls ? undefined : "Toggle audio playback", onClick: controls ? undefined : togglePlay, onKeyDown: controls ? undefined : onKeyDown, 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: audioRef, src: url, className: mediaClassName, controls: controls, autoPlay: autoPlay, loop: loop, preload: preload, onLoadedData: handleLoadedData, onCanPlay: handleCanPlay, onError: handleError, onLoadedMetadata: playbackHandlers.onLoadedMetadata, onTimeUpdate: playbackHandlers.onTimeUpdate, onPlay: playbackHandlers.onPlay, onPause: playbackHandlers.onPause, onSeeked: playbackHandlers.onSeeked, onEnded: playbackHandlers.onEnded, style: {
|
|
75
78
|
width: "100%",
|
|
76
79
|
opacity: isLoaded ? 1 : 0,
|
|
77
80
|
transition: "opacity 300ms ease",
|
|
78
|
-
} })] })) }));
|
|
81
|
+
} }), endOverlay != null && hasEnded && (_jsx("div", { className: "mv-end-overlay", style: { position: "absolute", inset: 0, zIndex: 2 }, children: endOverlay }))] })) }));
|
|
79
82
|
}
|
|
80
83
|
//# sourceMappingURL=audio-viewer.js.map
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
1
|
+
{"version":3,"file":"audio-viewer.js","sourceRoot":"","sources":["../../src/react/audio-viewer.tsx"],"names":[],"mappings":"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"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -3,4 +3,8 @@ export { ImageViewer } from "./image-viewer.js";
|
|
|
3
3
|
export { VideoViewer } from "./video-viewer.js";
|
|
4
4
|
export { AudioViewer } from "./audio-viewer.js";
|
|
5
5
|
export { MediaFallbackLink, shouldShowFallback, EmptyFallback, ErrorFallback } from "./fallback.js";
|
|
6
|
+
export { useMediaRecovery } from "./use-media-recovery.js";
|
|
7
|
+
export type { UseMediaRecoveryOptions, UseMediaRecoveryResult } from "./use-media-recovery.js";
|
|
8
|
+
export { useMediaPlayback, PROGRESS_SAMPLE_INTERVAL_MS } from "./use-media-playback.js";
|
|
9
|
+
export type { UseMediaPlaybackResult } from "./use-media-playback.js";
|
|
6
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACxF,YAAY,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/react/index.js
CHANGED
|
@@ -4,4 +4,6 @@ export { ImageViewer } from "./image-viewer.js";
|
|
|
4
4
|
export { VideoViewer } from "./video-viewer.js";
|
|
5
5
|
export { AudioViewer } from "./audio-viewer.js";
|
|
6
6
|
export { MediaFallbackLink, shouldShowFallback, EmptyFallback, ErrorFallback } from "./fallback.js";
|
|
7
|
+
export { useMediaRecovery } from "./use-media-recovery.js";
|
|
8
|
+
export { useMediaPlayback, PROGRESS_SAMPLE_INTERVAL_MS } from "./use-media-playback.js";
|
|
7
9
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -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;AAI/B,OAAO,KAAK,EAAE,iBAAiB,EAAiC,MAAM,aAAa,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;AAI/B,OAAO,KAAK,EAAE,iBAAiB,EAAiC,MAAM,aAAa,CAAC;AA6GpF,wBAAgB,WAAW,CAAC,KAAK,EAAE,iBAAiB,4BAgOnD;AAED,wBAAwB;AACxB,eAAO,MAAM,YAAY,oBAAc,CAAC"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { getSimplifiedMediaType } from "@ttt-productions/media-schemas";
|
|
4
4
|
import { MediaFallbackLink, shouldShowFallback, EmptyFallback, ErrorFallback } from "./fallback.js";
|
|
5
5
|
import { ImageViewer } from "./image-viewer.js";
|
|
6
6
|
import { VideoViewer } from "./video-viewer.js";
|
|
7
7
|
import { AudioViewer } from "./audio-viewer.js";
|
|
8
|
+
import { useMediaRecovery } from "./use-media-recovery.js";
|
|
8
9
|
function inferType(props) {
|
|
9
10
|
if (props.type) {
|
|
10
11
|
// Handle MIME strings like "image/png" -> "image"
|
|
@@ -20,10 +21,61 @@ function inferType(props) {
|
|
|
20
21
|
const simplified = getSimplifiedMediaType(hint);
|
|
21
22
|
return simplified;
|
|
22
23
|
}
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Recovery-phase UI helpers
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
function RecoveryOverlay({ state, onManualRetry, isCircular }) {
|
|
28
|
+
const wrapStyle = {
|
|
29
|
+
display: "flex",
|
|
30
|
+
flexDirection: "column",
|
|
31
|
+
alignItems: "center",
|
|
32
|
+
justifyContent: "center",
|
|
33
|
+
width: "100%",
|
|
34
|
+
height: "100%",
|
|
35
|
+
gap: "0.5rem",
|
|
36
|
+
...(isCircular ? { borderRadius: "50%" } : {}),
|
|
37
|
+
};
|
|
38
|
+
if (state.phase === "processing") {
|
|
39
|
+
return (_jsx("div", { className: "mv-recovery-processing", style: wrapStyle, children: _jsx("span", { className: "mv-recovery-label", children: "Processing media\u2026" }) }));
|
|
40
|
+
}
|
|
41
|
+
if (state.phase === "finalizing") {
|
|
42
|
+
return (_jsx("div", { className: "mv-recovery-finalizing", style: wrapStyle, children: _jsx("span", { className: "mv-recovery-label", children: "Finishing media\u2026" }) }));
|
|
43
|
+
}
|
|
44
|
+
if (state.phase === "transient-retry" || state.phase === "propagating") {
|
|
45
|
+
return (_jsx("div", { className: "mv-recovery-retrying", style: wrapStyle, children: _jsx("span", { className: "mv-recovery-label", children: "Loading\u2026" }) }));
|
|
46
|
+
}
|
|
47
|
+
if (state.phase === "auth-retry") {
|
|
48
|
+
return (_jsx("div", { className: "mv-recovery-auth", style: wrapStyle, children: _jsx("span", { className: "mv-recovery-label", children: "Refreshing access\u2026" }) }));
|
|
49
|
+
}
|
|
50
|
+
if (state.phase === "hard-unavailable") {
|
|
51
|
+
return (_jsxs("div", { className: "mv-recovery-hard", style: wrapStyle, children: [_jsx(ErrorFallback, { isCircular: isCircular }), state.reason === "auth" ? (_jsx("span", { className: "mv-recovery-label", children: "Access denied." })) : (_jsx("span", { className: "mv-recovery-label", children: "Media unavailable." }))] }));
|
|
52
|
+
}
|
|
53
|
+
if (state.phase === "max-wait-fallback") {
|
|
54
|
+
return (_jsxs("div", { className: "mv-recovery-max-wait", style: wrapStyle, children: [_jsx("span", { className: "mv-recovery-label", children: "This media is taking longer than expected." }), _jsx("button", { type: "button", className: "mv-recovery-retry-btn", onClick: onManualRetry, children: "Retry" })] }));
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Main component
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
23
61
|
export function MediaViewer(props) {
|
|
24
|
-
const { url, alt, type, className, unloadOnExit, isCircular = false, priority, skeleton, lazy, controls, autoPlay, muted, loop, posterUrl, preload, autoPlayOnVisible, onLoad, onError, onLoadChange, fallbackMode = "link", fallbackLabel, filename, mime, name, } = props;
|
|
62
|
+
const { url, alt, type, className, unloadOnExit, isCircular = false, priority, skeleton, lazy, controls, autoPlay, muted, loop, posterUrl, preload, autoPlayOnVisible, onLoad, onError, onLoadChange, onEnded, onProgressSample, startAtSeconds, endOverlay, playbackControlsRef, fallbackMode = "link", fallbackLabel, filename, mime, name, recoveryAdapter, assetStatusHint, } = props;
|
|
25
63
|
const [hasError, setHasError] = React.useState(false);
|
|
26
64
|
const [sourceUrl, setSourceUrl] = React.useState(null);
|
|
65
|
+
// Remount key: incrementing this causes child media elements to unmount and
|
|
66
|
+
// re-mount from scratch, re-issuing the HTTP request.
|
|
67
|
+
const [remountKey, setRemountKey] = React.useState(0);
|
|
68
|
+
const handleRemount = React.useCallback(() => {
|
|
69
|
+
setHasError(false);
|
|
70
|
+
setRemountKey((k) => k + 1);
|
|
71
|
+
}, []);
|
|
72
|
+
// Recovery hook (no-op when recoveryAdapter is omitted)
|
|
73
|
+
const { recoveryState, onMediaError, onMediaLoad, manualRetry } = useMediaRecovery({
|
|
74
|
+
url: typeof url === "string" ? url : null,
|
|
75
|
+
adapter: recoveryAdapter,
|
|
76
|
+
statusHint: assetStatusHint,
|
|
77
|
+
onRemount: handleRemount,
|
|
78
|
+
});
|
|
27
79
|
React.useEffect(() => {
|
|
28
80
|
setHasError(false);
|
|
29
81
|
let objectUrl = null;
|
|
@@ -45,7 +97,19 @@ export function MediaViewer(props) {
|
|
|
45
97
|
const handleError = React.useCallback(() => {
|
|
46
98
|
setHasError(true);
|
|
47
99
|
onError?.();
|
|
48
|
-
|
|
100
|
+
// If a recovery adapter is provided, delegate to the recovery hook.
|
|
101
|
+
if (recoveryAdapter) {
|
|
102
|
+
onMediaError();
|
|
103
|
+
}
|
|
104
|
+
}, [onError, recoveryAdapter, onMediaError]);
|
|
105
|
+
const handleLoad = React.useCallback(() => {
|
|
106
|
+
setHasError(false);
|
|
107
|
+
onLoad?.();
|
|
108
|
+
// Signal successful load to the recovery state machine.
|
|
109
|
+
if (recoveryAdapter) {
|
|
110
|
+
onMediaLoad();
|
|
111
|
+
}
|
|
112
|
+
}, [onLoad, recoveryAdapter, onMediaLoad]);
|
|
49
113
|
const circularStyle = isCircular
|
|
50
114
|
? { borderRadius: "50%", overflow: "hidden" }
|
|
51
115
|
: {};
|
|
@@ -55,23 +119,32 @@ export function MediaViewer(props) {
|
|
|
55
119
|
height: "100%",
|
|
56
120
|
...circularStyle,
|
|
57
121
|
};
|
|
122
|
+
// When recovery is active and in a terminal/blocking state, show the overlay.
|
|
123
|
+
const isInRecovery = recoveryAdapter !== undefined &&
|
|
124
|
+
recoveryState.phase !== "idle" &&
|
|
125
|
+
recoveryState.phase !== "loaded" &&
|
|
126
|
+
recoveryState.phase !== "loading";
|
|
58
127
|
// No URL -> empty fallback
|
|
59
128
|
if (!sourceUrl) {
|
|
60
129
|
return (_jsx("div", { className: className, style: wrapperStyle, children: _jsx(EmptyFallback, { isCircular: isCircular }) }));
|
|
61
130
|
}
|
|
62
|
-
//
|
|
63
|
-
if (hasError) {
|
|
131
|
+
// Recovery overlay (for all non-idle/non-loaded recovery states)
|
|
132
|
+
if (isInRecovery && hasError) {
|
|
133
|
+
return (_jsx("div", { className: className, style: wrapperStyle, children: _jsx(RecoveryOverlay, { state: recoveryState, onManualRetry: manualRetry, isCircular: isCircular }) }));
|
|
134
|
+
}
|
|
135
|
+
// Vanilla error (no recovery adapter) -> error fallback
|
|
136
|
+
if (hasError && !recoveryAdapter) {
|
|
64
137
|
return (_jsx("div", { className: className, style: wrapperStyle, children: _jsx(ErrorFallback, { isCircular: isCircular }) }));
|
|
65
138
|
}
|
|
66
139
|
const t = inferType({ type, mime, name, url: sourceUrl });
|
|
67
140
|
if (t === "image") {
|
|
68
|
-
return (_jsx("div", { className: className, style: wrapperStyle, children: _jsx(ImageViewer, { url: sourceUrl, alt: alt, lazy: lazy, priority: priority, skeleton: skeleton, unloadOnExit: unloadOnExit, isCircular: isCircular, onLoad:
|
|
141
|
+
return (_jsx("div", { className: className, style: wrapperStyle, children: _jsx(ImageViewer, { url: sourceUrl, alt: alt, lazy: lazy, priority: priority, skeleton: skeleton, unloadOnExit: unloadOnExit, isCircular: isCircular, onLoad: handleLoad, onError: handleError, fallback: _jsx(ErrorFallback, { isCircular: isCircular }) }, remountKey) }));
|
|
69
142
|
}
|
|
70
143
|
if (t === "video") {
|
|
71
|
-
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, onLoad:
|
|
144
|
+
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, onLoad: handleLoad, onError: handleError, onEnded: onEnded, onProgressSample: onProgressSample, startAtSeconds: startAtSeconds, endOverlay: endOverlay, playbackControlsRef: playbackControlsRef }, remountKey) }));
|
|
72
145
|
}
|
|
73
146
|
if (t === "audio") {
|
|
74
|
-
return (_jsx("div", { className: className, style: wrapperStyle, children: _jsx(AudioViewer, { url: sourceUrl, lazy: lazy, priority: priority, skeleton: skeleton, controls: controls, autoPlay: autoPlay, loop: loop, preload: preload, onLoad:
|
|
147
|
+
return (_jsx("div", { className: className, style: wrapperStyle, children: _jsx(AudioViewer, { url: sourceUrl, lazy: lazy, priority: priority, skeleton: skeleton, controls: controls, autoPlay: autoPlay, loop: loop, preload: preload, onLoad: handleLoad, onLoadChange: onLoadChange, onError: handleError, onEnded: onEnded, onProgressSample: onProgressSample, startAtSeconds: startAtSeconds, endOverlay: endOverlay, playbackControlsRef: playbackControlsRef }, remountKey) }));
|
|
75
148
|
}
|
|
76
149
|
// Unknown type -> fallback link
|
|
77
150
|
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":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAIxE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"media-viewer.js","sourceRoot":"","sources":["../../src/react/media-viewer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { MediaPlaybackControls, MediaPlaybackProps } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Throttle interval for periodic progress samples. A sample fires at most once
|
|
5
|
+
* per this many milliseconds of active (non-paused) playback via `timeupdate`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const PROGRESS_SAMPLE_INTERVAL_MS = 5000;
|
|
8
|
+
export type UseMediaPlaybackResult = {
|
|
9
|
+
/** Whether the media has ended (drives the end overlay). */
|
|
10
|
+
hasEnded: boolean;
|
|
11
|
+
/** Handlers to spread onto the underlying <video>/<audio> element. */
|
|
12
|
+
handlers: {
|
|
13
|
+
onLoadedMetadata: (e: React.SyntheticEvent<HTMLMediaElement>) => void;
|
|
14
|
+
onTimeUpdate: (e: React.SyntheticEvent<HTMLMediaElement>) => void;
|
|
15
|
+
onPlay: (e: React.SyntheticEvent<HTMLMediaElement>) => void;
|
|
16
|
+
onPause: (e: React.SyntheticEvent<HTMLMediaElement>) => void;
|
|
17
|
+
onSeeked: (e: React.SyntheticEvent<HTMLMediaElement>) => void;
|
|
18
|
+
onEnded: (e: React.SyntheticEvent<HTMLMediaElement>) => void;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Shared playback API for VideoViewer and AudioViewer. Owns the additive
|
|
23
|
+
* playback surface (onEnded, onProgressSample, startAtSeconds, endOverlay,
|
|
24
|
+
* imperative controls) so both viewers behave identically. Adds NO observer —
|
|
25
|
+
* everything is derived from native media element events (Rule 22).
|
|
26
|
+
*
|
|
27
|
+
* The element ref is supplied by the viewer (which already owns it for
|
|
28
|
+
* autoplay/fullscreen). When the viewer unloads the element off-screen
|
|
29
|
+
* (`unloadOnExit`) and remounts it on return, the last known playback position
|
|
30
|
+
* is tracked in a component-scoped ref that outlives the element, so playback
|
|
31
|
+
* resumes from where it left off — never from `startAtSeconds` again and never
|
|
32
|
+
* from 0.
|
|
33
|
+
*/
|
|
34
|
+
export declare function useMediaPlayback(elementRef: React.RefObject<HTMLMediaElement | null>, props: MediaPlaybackProps, playbackControlsRef?: React.Ref<MediaPlaybackControls>,
|
|
35
|
+
/**
|
|
36
|
+
* Stable identity for the CURRENT media source (the viewer passes its `url`).
|
|
37
|
+
* When this changes the media is a different asset, so all resume/progress/
|
|
38
|
+
* ended tracking is reset — a stale position from the previous media must NOT
|
|
39
|
+
* carry over onto the new element. Omit when the viewer never reuses one
|
|
40
|
+
* instance across sources.
|
|
41
|
+
*/
|
|
42
|
+
mediaKey?: string): UseMediaPlaybackResult;
|
|
43
|
+
//# sourceMappingURL=use-media-playback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-media-playback.d.ts","sourceRoot":"","sources":["../../src/react/use-media-playback.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE7E;;;GAGG;AACH,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAEhD,MAAM,MAAM,sBAAsB,GAAG;IACnC,4DAA4D;IAC5D,QAAQ,EAAE,OAAO,CAAC;IAClB,sEAAsE;IACtE,QAAQ,EAAE;QACR,gBAAgB,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;QACtE,YAAY,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;QAClE,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;QAC5D,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;QAC7D,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;QAC9D,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;KAC9D,CAAC;CACH,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,EACpD,KAAK,EAAE,kBAAkB,EACzB,mBAAmB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,qBAAqB,CAAC;AACtD;;;;;;GAMG;AACH,QAAQ,CAAC,EAAE,MAAM,GAChB,sBAAsB,CA4LxB"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Throttle interval for periodic progress samples. A sample fires at most once
|
|
5
|
+
* per this many milliseconds of active (non-paused) playback via `timeupdate`.
|
|
6
|
+
*/
|
|
7
|
+
export const PROGRESS_SAMPLE_INTERVAL_MS = 5000;
|
|
8
|
+
/**
|
|
9
|
+
* Shared playback API for VideoViewer and AudioViewer. Owns the additive
|
|
10
|
+
* playback surface (onEnded, onProgressSample, startAtSeconds, endOverlay,
|
|
11
|
+
* imperative controls) so both viewers behave identically. Adds NO observer —
|
|
12
|
+
* everything is derived from native media element events (Rule 22).
|
|
13
|
+
*
|
|
14
|
+
* The element ref is supplied by the viewer (which already owns it for
|
|
15
|
+
* autoplay/fullscreen). When the viewer unloads the element off-screen
|
|
16
|
+
* (`unloadOnExit`) and remounts it on return, the last known playback position
|
|
17
|
+
* is tracked in a component-scoped ref that outlives the element, so playback
|
|
18
|
+
* resumes from where it left off — never from `startAtSeconds` again and never
|
|
19
|
+
* from 0.
|
|
20
|
+
*/
|
|
21
|
+
export function useMediaPlayback(elementRef, props, playbackControlsRef,
|
|
22
|
+
/**
|
|
23
|
+
* Stable identity for the CURRENT media source (the viewer passes its `url`).
|
|
24
|
+
* When this changes the media is a different asset, so all resume/progress/
|
|
25
|
+
* ended tracking is reset — a stale position from the previous media must NOT
|
|
26
|
+
* carry over onto the new element. Omit when the viewer never reuses one
|
|
27
|
+
* instance across sources.
|
|
28
|
+
*/
|
|
29
|
+
mediaKey) {
|
|
30
|
+
const { onEnded, onProgressSample, startAtSeconds, endOverlay } = props;
|
|
31
|
+
const [hasEnded, setHasEnded] = React.useState(false);
|
|
32
|
+
// Latest callbacks/props via refs so handler identities stay stable and the
|
|
33
|
+
// element event handlers always see current values.
|
|
34
|
+
const onEndedRef = React.useRef(onEnded);
|
|
35
|
+
const onProgressSampleRef = React.useRef(onProgressSample);
|
|
36
|
+
onEndedRef.current = onEnded;
|
|
37
|
+
onProgressSampleRef.current = onProgressSample;
|
|
38
|
+
const seedPosition = () => typeof startAtSeconds === "number" && startAtSeconds > 0 ? startAtSeconds : null;
|
|
39
|
+
// Last known playback position. Seeded from startAtSeconds, then updated once
|
|
40
|
+
// playback has actually started. Survives the unloadOnExit unload/remount
|
|
41
|
+
// cycle because it lives on the (still-mounted) component instance.
|
|
42
|
+
const lastPositionRef = React.useRef(seedPosition());
|
|
43
|
+
// True once we have observed real playback progress — after this we resume
|
|
44
|
+
// from lastPositionRef, never re-apply the original startAtSeconds prop.
|
|
45
|
+
const hasStartedRef = React.useRef(false);
|
|
46
|
+
// Throttle bookkeeping for periodic samples (wall-clock, active playback only).
|
|
47
|
+
const lastSampleAtRef = React.useRef(0);
|
|
48
|
+
// Render-phase reset-on-source-change: when the media source (`mediaKey`)
|
|
49
|
+
// changes, the same component/hook instance is being reused for a DIFFERENT
|
|
50
|
+
// asset (e.g. a playlist swapping `url` in place). Reset every piece of
|
|
51
|
+
// per-source tracking so the previous media's position/ended/throttle state
|
|
52
|
+
// cannot bleed onto the new element. This is the sanctioned render-phase
|
|
53
|
+
// reset-on-prop-change pattern (not a set-state-in-effect).
|
|
54
|
+
const prevMediaKeyRef = React.useRef(mediaKey);
|
|
55
|
+
if (prevMediaKeyRef.current !== mediaKey) {
|
|
56
|
+
prevMediaKeyRef.current = mediaKey;
|
|
57
|
+
lastPositionRef.current = seedPosition();
|
|
58
|
+
hasStartedRef.current = false;
|
|
59
|
+
lastSampleAtRef.current = 0;
|
|
60
|
+
if (hasEnded)
|
|
61
|
+
setHasEnded(false);
|
|
62
|
+
}
|
|
63
|
+
const emitSample = React.useCallback((el) => {
|
|
64
|
+
const cb = onProgressSampleRef.current;
|
|
65
|
+
if (!cb)
|
|
66
|
+
return;
|
|
67
|
+
const duration = Number.isFinite(el.duration) ? el.duration : 0;
|
|
68
|
+
cb(el.currentTime, duration);
|
|
69
|
+
}, []);
|
|
70
|
+
const trackPosition = React.useCallback((el) => {
|
|
71
|
+
if (hasStartedRef.current) {
|
|
72
|
+
lastPositionRef.current = el.currentTime;
|
|
73
|
+
}
|
|
74
|
+
}, []);
|
|
75
|
+
const applyInitialSeek = React.useCallback((el) => {
|
|
76
|
+
const target = lastPositionRef.current;
|
|
77
|
+
if (typeof target !== "number" || target <= 0)
|
|
78
|
+
return;
|
|
79
|
+
// Only seek a freshly-loaded element sitting at the start. This makes the
|
|
80
|
+
// handler idempotent (a metadata event on an already-positioned element is
|
|
81
|
+
// a no-op) and, crucially, re-applies the LAST KNOWN position to the new
|
|
82
|
+
// element created by an unloadOnExit remount — resuming, never restarting.
|
|
83
|
+
if (el.currentTime > 0.01)
|
|
84
|
+
return;
|
|
85
|
+
const dur = el.duration;
|
|
86
|
+
const clamped = Number.isFinite(dur) && dur > 0 ? Math.min(target, dur) : target;
|
|
87
|
+
try {
|
|
88
|
+
el.currentTime = clamped;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
/* setting currentTime before ready can throw in some engines; ignore */
|
|
92
|
+
}
|
|
93
|
+
}, []);
|
|
94
|
+
const onLoadedMetadata = React.useCallback((e) => {
|
|
95
|
+
applyInitialSeek(e.currentTarget);
|
|
96
|
+
}, [applyInitialSeek]);
|
|
97
|
+
const onTimeUpdate = React.useCallback((e) => {
|
|
98
|
+
const el = e.currentTarget;
|
|
99
|
+
// Any observed timeupdate while playing means playback has started; from
|
|
100
|
+
// now on we track live position for remount resume.
|
|
101
|
+
if (!el.paused && !el.ended) {
|
|
102
|
+
hasStartedRef.current = true;
|
|
103
|
+
}
|
|
104
|
+
trackPosition(el);
|
|
105
|
+
if (el.paused)
|
|
106
|
+
return; // no periodic samples while paused
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
if (now - lastSampleAtRef.current >= PROGRESS_SAMPLE_INTERVAL_MS) {
|
|
109
|
+
lastSampleAtRef.current = now;
|
|
110
|
+
emitSample(el);
|
|
111
|
+
}
|
|
112
|
+
}, [emitSample, trackPosition]);
|
|
113
|
+
const onPlay = React.useCallback(() => {
|
|
114
|
+
// Playing again after an ended state (native replay) clears the overlay.
|
|
115
|
+
setHasEnded(false);
|
|
116
|
+
}, []);
|
|
117
|
+
const onPause = React.useCallback((e) => {
|
|
118
|
+
const el = e.currentTarget;
|
|
119
|
+
trackPosition(el);
|
|
120
|
+
// Flush a sample immediately on pause (final position for this segment).
|
|
121
|
+
lastSampleAtRef.current = Date.now();
|
|
122
|
+
emitSample(el);
|
|
123
|
+
}, [emitSample, trackPosition]);
|
|
124
|
+
const onSeeked = React.useCallback((e) => {
|
|
125
|
+
const el = e.currentTarget;
|
|
126
|
+
trackPosition(el);
|
|
127
|
+
// Flush a sample on seek completion so the app sees the new position.
|
|
128
|
+
lastSampleAtRef.current = Date.now();
|
|
129
|
+
emitSample(el);
|
|
130
|
+
}, [emitSample, trackPosition]);
|
|
131
|
+
const onEndedHandler = React.useCallback((e) => {
|
|
132
|
+
const el = e.currentTarget;
|
|
133
|
+
// Ended means the play cursor reached duration; record final position.
|
|
134
|
+
hasStartedRef.current = true;
|
|
135
|
+
lastPositionRef.current = el.currentTime;
|
|
136
|
+
// Flush a final sample with the ended position.
|
|
137
|
+
lastSampleAtRef.current = Date.now();
|
|
138
|
+
emitSample(el);
|
|
139
|
+
setHasEnded(true);
|
|
140
|
+
onEndedRef.current?.();
|
|
141
|
+
}, [emitSample]);
|
|
142
|
+
// Imperative controls. restart() seeks to 0 and plays; seekTo(s) seeks.
|
|
143
|
+
React.useImperativeHandle(playbackControlsRef, () => ({
|
|
144
|
+
restart() {
|
|
145
|
+
const el = elementRef.current;
|
|
146
|
+
if (!el)
|
|
147
|
+
return;
|
|
148
|
+
try {
|
|
149
|
+
el.currentTime = 0;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
/* ignore */
|
|
153
|
+
}
|
|
154
|
+
lastPositionRef.current = 0;
|
|
155
|
+
setHasEnded(false);
|
|
156
|
+
void el.play().catch(() => { });
|
|
157
|
+
},
|
|
158
|
+
seekTo(seconds) {
|
|
159
|
+
const el = elementRef.current;
|
|
160
|
+
if (!el)
|
|
161
|
+
return;
|
|
162
|
+
const dur = el.duration;
|
|
163
|
+
const clamped = Number.isFinite(dur) && dur > 0 ? Math.min(Math.max(seconds, 0), dur) : Math.max(seconds, 0);
|
|
164
|
+
try {
|
|
165
|
+
el.currentTime = clamped;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
/* ignore */
|
|
169
|
+
}
|
|
170
|
+
hasStartedRef.current = true;
|
|
171
|
+
lastPositionRef.current = clamped;
|
|
172
|
+
},
|
|
173
|
+
}), [elementRef]);
|
|
174
|
+
// Ended state is cleared by onPlay (native replay / imperative restart).
|
|
175
|
+
// endOverlay itself is rendered by the viewer using `hasEnded`.
|
|
176
|
+
void endOverlay;
|
|
177
|
+
return {
|
|
178
|
+
hasEnded,
|
|
179
|
+
handlers: {
|
|
180
|
+
onLoadedMetadata,
|
|
181
|
+
onTimeUpdate,
|
|
182
|
+
onPlay,
|
|
183
|
+
onPause,
|
|
184
|
+
onSeeked,
|
|
185
|
+
onEnded: onEndedHandler,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=use-media-playback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-media-playback.js","sourceRoot":"","sources":["../../src/react/use-media-playback.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { MediaDiagnosticAdapter, RecoveryState, AssetStatusHint } from "../recovery.js";
|
|
2
|
+
export interface UseMediaRecoveryOptions {
|
|
3
|
+
/** The URL of the media element. Recovery is skipped when falsy. */
|
|
4
|
+
url: string | null | undefined;
|
|
5
|
+
/** Injected diagnostic adapter from the app. */
|
|
6
|
+
adapter?: MediaDiagnosticAdapter;
|
|
7
|
+
/**
|
|
8
|
+
* Optional app hint about the server-side lifecycle of the asset.
|
|
9
|
+
* When "failed" or "rejected" the element error is treated as hard.
|
|
10
|
+
* When "processing" or "finalizing" a loading skeleton/spinner is shown
|
|
11
|
+
* without triggering the recovery probe.
|
|
12
|
+
*/
|
|
13
|
+
statusHint?: AssetStatusHint;
|
|
14
|
+
/**
|
|
15
|
+
* Called when the recovery state machine wants to remount/reload the
|
|
16
|
+
* media element. The parent should increment a remount key or equivalent
|
|
17
|
+
* so the element re-attempts loading from scratch.
|
|
18
|
+
*/
|
|
19
|
+
onRemount?: () => void;
|
|
20
|
+
}
|
|
21
|
+
export interface UseMediaRecoveryResult {
|
|
22
|
+
/** Current recovery phase. */
|
|
23
|
+
recoveryState: RecoveryState;
|
|
24
|
+
/**
|
|
25
|
+
* Call this when the underlying media element fires an error event.
|
|
26
|
+
* If no adapter is provided, or statusHint is "failed"/"rejected", the
|
|
27
|
+
* error is treated as hard immediately.
|
|
28
|
+
*/
|
|
29
|
+
onMediaError: () => void;
|
|
30
|
+
/**
|
|
31
|
+
* Call this when the underlying media element fires its successful
|
|
32
|
+
* load/decode event (onLoad / onLoadedData / onCanPlay).
|
|
33
|
+
*/
|
|
34
|
+
onMediaLoad: () => void;
|
|
35
|
+
/**
|
|
36
|
+
* Manual retry: resets the state machine back to loading and triggers a
|
|
37
|
+
* fresh remount. Usable from a "Retry" button in max-wait-fallback state.
|
|
38
|
+
*/
|
|
39
|
+
manualRetry: () => void;
|
|
40
|
+
}
|
|
41
|
+
export declare function useMediaRecovery({ url, adapter, statusHint, onRemount, }: UseMediaRecoveryOptions): UseMediaRecoveryResult;
|
|
42
|
+
//# sourceMappingURL=use-media-recovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-media-recovery.d.ts","sourceRoot":"","sources":["../../src/react/use-media-recovery.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,sBAAsB,EACtB,aAAa,EACb,eAAe,EAChB,MAAM,gBAAgB,CAAC;AA+CxB,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAE/B,gDAAgD;IAChD,OAAO,CAAC,EAAE,sBAAsB,CAAC;IAEjC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAE7B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,8BAA8B;IAC9B,aAAa,EAAE,aAAa,CAAC;IAE7B;;;;OAIG;IACH,YAAY,EAAE,MAAM,IAAI,CAAC;IAEzB;;;OAGG;IACH,WAAW,EAAE,MAAM,IAAI,CAAC;IAExB;;;OAGG;IACH,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAOD,wBAAgB,gBAAgB,CAAC,EAC/B,GAAG,EACH,OAAO,EACP,UAAU,EACV,SAAS,GACV,EAAE,uBAAuB,GAAG,sBAAsB,CAmUlD"}
|