@hexdn/react 0.1.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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -0
  3. package/dist/player/bounded-surface-position.d.ts +8 -0
  4. package/dist/player/bounded-surface-position.js +8 -0
  5. package/dist/player/hexdn-player-control-popover.module.css +113 -0
  6. package/dist/player/index.d.ts +25 -0
  7. package/dist/player/index.js +32 -0
  8. package/dist/player/media-video-player-control-hold.d.ts +2 -0
  9. package/dist/player/media-video-player-control-hold.js +9 -0
  10. package/dist/player/media-video-player-debug-hud-loader-contract.d.ts +5 -0
  11. package/dist/player/media-video-player-debug-hud-loader-contract.js +1 -0
  12. package/dist/player/media-video-player-keyboard.d.ts +5 -0
  13. package/dist/player/media-video-player-keyboard.js +52 -0
  14. package/dist/player/media-video-player-layout.d.ts +64 -0
  15. package/dist/player/media-video-player-layout.js +110 -0
  16. package/dist/player/media-video-player-scrubber.d.ts +16 -0
  17. package/dist/player/media-video-player-scrubber.js +333 -0
  18. package/dist/player/media-video-player-surface-click.d.ts +8 -0
  19. package/dist/player/media-video-player-surface-click.js +14 -0
  20. package/dist/player/media-video-player-view-state.d.ts +21 -0
  21. package/dist/player/media-video-player-view-state.js +31 -0
  22. package/dist/player/media-video-player.module.css +1254 -0
  23. package/dist/player/playback-landscape-presentation.d.ts +16 -0
  24. package/dist/player/playback-landscape-presentation.js +39 -0
  25. package/dist/player/playback-loading-indicator.d.ts +7 -0
  26. package/dist/player/playback-loading-indicator.js +35 -0
  27. package/dist/player/player-control-popover.d.ts +16 -0
  28. package/dist/player/player-control-popover.js +130 -0
  29. package/dist/player/player-host.d.ts +31 -0
  30. package/dist/player/player-host.js +29 -0
  31. package/dist/player/player-icon.d.ts +19 -0
  32. package/dist/player/player-icon.js +59 -0
  33. package/dist/player/player-labels.d.ts +27 -0
  34. package/dist/player/player-labels.js +35 -0
  35. package/dist/player/player-slots.d.ts +18 -0
  36. package/dist/player/player-slots.js +1 -0
  37. package/dist/player/ready-player.d.ts +34 -0
  38. package/dist/player/ready-player.js +293 -0
  39. package/dist/player/tooltip-surface.d.ts +2 -0
  40. package/dist/player/tooltip-surface.js +8 -0
  41. package/dist/player/ui-primitives.d.ts +17 -0
  42. package/dist/player/ui-primitives.js +15 -0
  43. package/dist/player/ui.module.css +113 -0
  44. package/dist/player/use-media-video-player-controls.d.ts +17 -0
  45. package/dist/player/use-media-video-player-controls.js +87 -0
  46. package/dist/player/use-media-video-player-fullscreen.d.ts +24 -0
  47. package/dist/player/use-media-video-player-fullscreen.js +38 -0
  48. package/dist/player/use-media-video-player-interactions.d.ts +25 -0
  49. package/dist/player/use-media-video-player-interactions.js +163 -0
  50. package/dist/player/use-media-video-player-presentation.d.ts +50 -0
  51. package/dist/player/use-media-video-player-presentation.js +125 -0
  52. package/dist/player/use-media-video-player.d.ts +81 -0
  53. package/dist/player/use-media-video-player.js +263 -0
  54. package/dist/player/use-player-analytics.d.ts +13 -0
  55. package/dist/player/use-player-analytics.js +79 -0
  56. package/dist/player/use-slider-pointer.d.ts +20 -0
  57. package/dist/player/use-slider-pointer.js +85 -0
  58. package/dist/player/utils.d.ts +1 -0
  59. package/dist/player/utils.js +3 -0
  60. package/dist/upload/index.d.ts +37 -0
  61. package/dist/upload/index.js +60 -0
  62. package/dist/upload/upload.module.css +41 -0
  63. package/dist/upload/use-uploader.d.ts +42 -0
  64. package/dist/upload/use-uploader.js +179 -0
  65. package/package.json +51 -0
@@ -0,0 +1,333 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { usePlayerHost } from "./player-host.js";
4
+ import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
5
+ import { TooltipSurface } from "./tooltip-surface.js";
6
+ import { useSliderPointer } from "./use-slider-pointer.js";
7
+ import { findMediaThumbnailPreviewCue, loadMediaThumbnailPreviewCues, materializeMediaThumbnailPreviewCueImage, } from "@hexdn/playback/internal";
8
+ import styles from "./media-video-player.module.css";
9
+ import { areScrubberPreviewStatesEqual, buildPlayerSliderStyle, buildScrubberThumbnailPreviewStyle, emptyScrubberPreviewGeometry, measureScrubberPreviewGeometry, resolveScrubberPointerRatio, syncScrubberPreviewLayout, } from "./media-video-player-layout.js";
10
+ import { formatMediaVideoTime } from "@hexdn/playback/internal";
11
+ const maxScrubberThumbnailObjectUrls = 8;
12
+ const maxScrubberThumbnailRequests = 2;
13
+ function revokeObjectUrls(objectUrls) {
14
+ for (const objectUrl of objectUrls) {
15
+ URL.revokeObjectURL(objectUrl);
16
+ }
17
+ }
18
+ export function MediaVideoPlayerScrubber({ bufferProgress, currentTime, duration, hasSeekableDuration, onSeekChange, onSeekToTime, onShowControls, previewThumbnails, requestAuthorization, seekLabel, seekProgress, seekValueText, }) {
19
+ const { fetchResource } = usePlayerHost();
20
+ const [scrubberPreviewState, setScrubberPreviewState] = useState(null);
21
+ const [scrubberThumbnailCues, setScrubberThumbnailCues] = useState([]);
22
+ const [scrubberThumbnailObjectUrls, setScrubberThumbnailObjectUrls] = useState(() => new Map());
23
+ const [scrubberThumbnailPreviewRequestUrl, setScrubberThumbnailPreviewRequestUrl,] = useState(null);
24
+ const scrubberRef = useRef(null);
25
+ const scrubberPreviewFrameRef = useRef(null);
26
+ const scrubberPreviewGeometryRef = useRef(emptyScrubberPreviewGeometry);
27
+ const scrubberPreviewLatestRef = useRef(null);
28
+ const scrubberPreviewPendingRef = useRef(null);
29
+ const scrubberPreviewSurfaceRef = useRef(null);
30
+ const scrubberThumbnailCuesRef = useRef([]);
31
+ const scrubberThumbnailObjectUrlsRef = useRef(new Map());
32
+ const scrubberThumbnailRequestsRef = useRef(new Map());
33
+ const setScrubberPreviewStateCoalesced = useCallback((nextPreviewState) => {
34
+ scrubberPreviewPendingRef.current = nextPreviewState;
35
+ if (scrubberPreviewFrameRef.current !== null) {
36
+ return;
37
+ }
38
+ scrubberPreviewFrameRef.current = window.requestAnimationFrame(() => {
39
+ scrubberPreviewFrameRef.current = null;
40
+ const pendingPreviewState = scrubberPreviewPendingRef.current;
41
+ scrubberPreviewPendingRef.current = null;
42
+ setScrubberPreviewState((currentPreviewState) => areScrubberPreviewStatesEqual(currentPreviewState, pendingPreviewState)
43
+ ? currentPreviewState
44
+ : pendingPreviewState);
45
+ });
46
+ }, []);
47
+ const clearScrubberPreviewFrame = useCallback(() => {
48
+ if (scrubberPreviewFrameRef.current === null) {
49
+ return;
50
+ }
51
+ window.cancelAnimationFrame(scrubberPreviewFrameRef.current);
52
+ scrubberPreviewFrameRef.current = null;
53
+ scrubberPreviewPendingRef.current = null;
54
+ }, []);
55
+ const clearScrubberThumbnailImageCache = useCallback(() => {
56
+ for (const controller of scrubberThumbnailRequestsRef.current.values()) {
57
+ controller.abort();
58
+ }
59
+ scrubberThumbnailRequestsRef.current.clear();
60
+ revokeObjectUrls(scrubberThumbnailObjectUrlsRef.current.values());
61
+ scrubberThumbnailObjectUrlsRef.current = new Map();
62
+ setScrubberThumbnailObjectUrls(new Map());
63
+ }, []);
64
+ const replaceScrubberThumbnailCues = useCallback((cues) => {
65
+ setScrubberThumbnailCues((currentCues) => {
66
+ if (currentCues === cues) {
67
+ return currentCues;
68
+ }
69
+ scrubberThumbnailCuesRef.current = cues;
70
+ return cues;
71
+ });
72
+ }, []);
73
+ const renderedScrubberPreview = scrubberPreviewState?.preview ?? null;
74
+ const showScrubberPreview = hasSeekableDuration && scrubberPreviewState?.active === true;
75
+ const scrubberPreviewRawCue = useMemo(() => renderedScrubberPreview
76
+ ? findMediaThumbnailPreviewCue(scrubberThumbnailCues, renderedScrubberPreview.time)
77
+ : null, [renderedScrubberPreview, scrubberThumbnailCues]);
78
+ const scrubberPreviewCue = useMemo(() => {
79
+ if (!scrubberPreviewRawCue) {
80
+ return null;
81
+ }
82
+ if (!requestAuthorization) {
83
+ return scrubberPreviewRawCue;
84
+ }
85
+ const objectUrl = scrubberThumbnailObjectUrls.get(scrubberPreviewRawCue.imageUrl);
86
+ return objectUrl
87
+ ? {
88
+ ...scrubberPreviewRawCue,
89
+ imageUrl: objectUrl,
90
+ }
91
+ : null;
92
+ }, [
93
+ requestAuthorization,
94
+ scrubberPreviewRawCue,
95
+ scrubberThumbnailObjectUrls,
96
+ ]);
97
+ const scrubberPreviewFrameCue = scrubberPreviewCue ??
98
+ (requestAuthorization && scrubberPreviewRawCue
99
+ ? { ...scrubberPreviewRawCue, imageUrl: "" }
100
+ : null);
101
+ const scrubberPreviewTimeLabel = formatMediaVideoTime(renderedScrubberPreview?.time ?? currentTime);
102
+ const seekSliderStyle = buildPlayerSliderStyle(bufferProgress, renderedScrubberPreview, seekProgress);
103
+ const thumbnailPreviewUrl = previewThumbnails?.type === "webvtt_sprites" ? previewThumbnails.url : null;
104
+ const thumbnailPreviewCount = previewThumbnails?.type === "webvtt_sprites" ? previewThumbnails.count : 0;
105
+ const requestScrubberThumbnailPreview = useCallback(() => {
106
+ if (thumbnailPreviewUrl) {
107
+ setScrubberThumbnailPreviewRequestUrl(thumbnailPreviewUrl);
108
+ }
109
+ }, [thumbnailPreviewUrl]);
110
+ const applyScrubberPreviewLayout = useCallback((boundary, preview) => {
111
+ scrubberPreviewLatestRef.current = preview;
112
+ let geometry = scrubberPreviewGeometryRef.current;
113
+ const surface = scrubberPreviewSurfaceRef.current;
114
+ if (surface &&
115
+ (geometry.boundaryWidth <= 0 || geometry.surfaceWidth <= 0)) {
116
+ geometry = measureScrubberPreviewGeometry(boundary, surface);
117
+ scrubberPreviewGeometryRef.current = geometry;
118
+ }
119
+ syncScrubberPreviewLayout(boundary, preview, geometry);
120
+ }, []);
121
+ useEffect(() => {
122
+ const boundary = scrubberRef.current;
123
+ const surface = scrubberPreviewSurfaceRef.current;
124
+ if (!boundary || !surface) {
125
+ return;
126
+ }
127
+ let frameId = null;
128
+ const updateLayout = () => {
129
+ frameId = null;
130
+ const geometry = measureScrubberPreviewGeometry(boundary, surface);
131
+ scrubberPreviewGeometryRef.current = geometry;
132
+ if (scrubberPreviewLatestRef.current) {
133
+ syncScrubberPreviewLayout(boundary, scrubberPreviewLatestRef.current, geometry);
134
+ }
135
+ };
136
+ const scheduleLayoutUpdate = () => {
137
+ if (frameId === null) {
138
+ frameId = window.requestAnimationFrame(updateLayout);
139
+ }
140
+ };
141
+ const resizeObserver = typeof ResizeObserver === "undefined"
142
+ ? null
143
+ : new ResizeObserver(scheduleLayoutUpdate);
144
+ resizeObserver?.observe(boundary);
145
+ resizeObserver?.observe(surface);
146
+ window.addEventListener("resize", scheduleLayoutUpdate);
147
+ updateLayout();
148
+ return () => {
149
+ if (frameId !== null) {
150
+ window.cancelAnimationFrame(frameId);
151
+ }
152
+ resizeObserver?.disconnect();
153
+ window.removeEventListener("resize", scheduleLayoutUpdate);
154
+ };
155
+ }, []);
156
+ useEffect(() => {
157
+ if (!hasSeekableDuration || !thumbnailPreviewUrl) {
158
+ return;
159
+ }
160
+ // Request metadata only once the external media reports a seekable duration.
161
+ // eslint-disable-next-line react-hooks/set-state-in-effect
162
+ setScrubberThumbnailPreviewRequestUrl((currentUrl) => currentUrl === thumbnailPreviewUrl ? currentUrl : thumbnailPreviewUrl);
163
+ }, [hasSeekableDuration, thumbnailPreviewUrl]);
164
+ useEffect(() => {
165
+ if (!thumbnailPreviewUrl ||
166
+ scrubberThumbnailPreviewRequestUrl !== thumbnailPreviewUrl) {
167
+ // Invalidate URLs and decoded image state together when the media source changes.
168
+ // eslint-disable-next-line react-hooks/set-state-in-effect
169
+ clearScrubberThumbnailImageCache();
170
+ replaceScrubberThumbnailCues([]);
171
+ return;
172
+ }
173
+ const previewUrl = thumbnailPreviewUrl;
174
+ const previewCueLimit = thumbnailPreviewCount;
175
+ const controller = new AbortController();
176
+ let cancelled = false;
177
+ replaceScrubberThumbnailCues([]);
178
+ clearScrubberThumbnailImageCache();
179
+ async function loadThumbnailPreviewCues() {
180
+ try {
181
+ const cues = await loadMediaThumbnailPreviewCues({
182
+ fetchResource,
183
+ limit: previewCueLimit,
184
+ requestAuthorization,
185
+ signal: controller.signal,
186
+ url: previewUrl,
187
+ });
188
+ if (!cancelled) {
189
+ replaceScrubberThumbnailCues(cues);
190
+ }
191
+ }
192
+ catch {
193
+ if (!controller.signal.aborted && !cancelled) {
194
+ replaceScrubberThumbnailCues([]);
195
+ }
196
+ }
197
+ }
198
+ void loadThumbnailPreviewCues();
199
+ return () => {
200
+ cancelled = true;
201
+ controller.abort();
202
+ };
203
+ }, [
204
+ clearScrubberThumbnailImageCache,
205
+ fetchResource,
206
+ replaceScrubberThumbnailCues,
207
+ requestAuthorization,
208
+ scrubberThumbnailPreviewRequestUrl,
209
+ thumbnailPreviewCount,
210
+ thumbnailPreviewUrl,
211
+ ]);
212
+ useEffect(() => {
213
+ const authorization = requestAuthorization;
214
+ const rawCue = scrubberPreviewRawCue;
215
+ if (!authorization || !rawCue) {
216
+ return;
217
+ }
218
+ if (scrubberThumbnailObjectUrlsRef.current.has(rawCue.imageUrl) ||
219
+ scrubberThumbnailRequestsRef.current.has(rawCue.imageUrl)) {
220
+ return;
221
+ }
222
+ while (scrubberThumbnailRequestsRef.current.size >= maxScrubberThumbnailRequests) {
223
+ const [oldestUrl, oldestController] = scrubberThumbnailRequestsRef.current.entries().next().value ?? [];
224
+ if (!oldestUrl || !oldestController) {
225
+ break;
226
+ }
227
+ oldestController.abort();
228
+ scrubberThumbnailRequestsRef.current.delete(oldestUrl);
229
+ }
230
+ const controller = new AbortController();
231
+ scrubberThumbnailRequestsRef.current.set(rawCue.imageUrl, controller);
232
+ void materializeMediaThumbnailPreviewCueImage({
233
+ fetchResource,
234
+ cue: rawCue,
235
+ requestAuthorization: authorization,
236
+ signal: controller.signal,
237
+ })
238
+ .then((materializedCue) => {
239
+ if (scrubberThumbnailRequestsRef.current.get(rawCue.imageUrl) ===
240
+ controller) {
241
+ scrubberThumbnailRequestsRef.current.delete(rawCue.imageUrl);
242
+ }
243
+ if (!materializedCue) {
244
+ return;
245
+ }
246
+ if (controller.signal.aborted) {
247
+ URL.revokeObjectURL(materializedCue.imageUrl);
248
+ return;
249
+ }
250
+ setScrubberThumbnailObjectUrls((currentObjectUrls) => {
251
+ if (currentObjectUrls.has(rawCue.imageUrl)) {
252
+ URL.revokeObjectURL(materializedCue.imageUrl);
253
+ return currentObjectUrls;
254
+ }
255
+ const nextObjectUrls = new Map(currentObjectUrls);
256
+ while (nextObjectUrls.size >= maxScrubberThumbnailObjectUrls) {
257
+ const [oldestUrl, oldestObjectUrl] = nextObjectUrls.entries().next().value ?? [];
258
+ if (!oldestUrl || !oldestObjectUrl) {
259
+ break;
260
+ }
261
+ URL.revokeObjectURL(oldestObjectUrl);
262
+ nextObjectUrls.delete(oldestUrl);
263
+ }
264
+ nextObjectUrls.set(rawCue.imageUrl, materializedCue.imageUrl);
265
+ scrubberThumbnailObjectUrlsRef.current = nextObjectUrls;
266
+ return nextObjectUrls;
267
+ });
268
+ })
269
+ .catch(() => {
270
+ if (scrubberThumbnailRequestsRef.current.get(rawCue.imageUrl) ===
271
+ controller) {
272
+ scrubberThumbnailRequestsRef.current.delete(rawCue.imageUrl);
273
+ }
274
+ });
275
+ }, [requestAuthorization, scrubberPreviewRawCue, fetchResource]);
276
+ useEffect(() => () => {
277
+ clearScrubberPreviewFrame();
278
+ for (const controller of scrubberThumbnailRequestsRef.current.values()) {
279
+ controller.abort();
280
+ }
281
+ scrubberThumbnailRequestsRef.current.clear();
282
+ revokeObjectUrls(scrubberThumbnailObjectUrlsRef.current.values());
283
+ scrubberThumbnailCuesRef.current = [];
284
+ scrubberThumbnailObjectUrlsRef.current = new Map();
285
+ }, [clearScrubberPreviewFrame]);
286
+ const readScrubberPosition = useCallback((point) => {
287
+ const boundary = scrubberRef.current;
288
+ if (!boundary || !hasSeekableDuration || duration <= 0) {
289
+ return null;
290
+ }
291
+ scrubberPreviewGeometryRef.current = {
292
+ ...scrubberPreviewGeometryRef.current,
293
+ boundaryWidth: boundary.clientWidth,
294
+ };
295
+ const ratio = resolveScrubberPointerRatio(boundary.getBoundingClientRect(), point);
296
+ return ratio === null ? null : ratio * duration;
297
+ }, [duration, hasSeekableDuration]);
298
+ const updateScrubberPreview = useCallback((time) => {
299
+ if (time === null) {
300
+ scrubberPreviewLatestRef.current = null;
301
+ clearScrubberPreviewFrame();
302
+ setScrubberPreviewState((current) => current?.active ? { ...current, active: false } : current);
303
+ return;
304
+ }
305
+ const boundary = scrubberRef.current;
306
+ if (!boundary)
307
+ return;
308
+ onShowControls();
309
+ const nextPreview = { percent: (time / duration) * 100, time };
310
+ requestScrubberThumbnailPreview();
311
+ applyScrubberPreviewLayout(boundary, nextPreview);
312
+ setScrubberPreviewStateCoalesced({
313
+ active: true,
314
+ preview: nextPreview,
315
+ });
316
+ }, [
317
+ applyScrubberPreviewLayout,
318
+ clearScrubberPreviewFrame,
319
+ duration,
320
+ onShowControls,
321
+ requestScrubberThumbnailPreview,
322
+ setScrubberPreviewStateCoalesced,
323
+ ]);
324
+ const { cancel: hideScrubberPreview, pointerHandlers } = useSliderPointer({
325
+ disabled: !hasSeekableDuration,
326
+ readValue: readScrubberPosition,
327
+ onPreviewChange: updateScrubberPreview,
328
+ onCommit: onSeekToTime,
329
+ });
330
+ return (_jsxs("div", { className: styles.scrubber, "data-hexdn-disabled": hasSeekableDuration ? "false" : "true", "data-hexdn-preview-visible": showScrubberPreview ? "true" : "false", ref: scrubberRef, style: seekSliderStyle, ...pointerHandlers, children: [_jsx("span", { className: styles.scrubberBuffer }), _jsx("span", { className: styles.scrubberFill }), _jsx("span", { "aria-hidden": "true", className: styles.scrubberPreviewAnchor, children: _jsx("span", { className: styles.scrubberPreview }) }), _jsx("span", { "aria-hidden": "true", className: styles.scrubberPreviewPositioner, children: _jsxs(TooltipSurface, { className: styles.scrubberPreviewSurface, ref: scrubberPreviewSurfaceRef, children: [scrubberPreviewFrameCue ? (_jsx("span", { className: styles.scrubberPreviewFrame, style: buildScrubberThumbnailPreviewStyle(scrubberPreviewFrameCue) })) : null, _jsx("span", { className: styles.scrubberPreviewTime, children: scrubberPreviewTimeLabel })] }) }), _jsx("span", { className: styles.scrubberHead }), _jsx("input", { "aria-label": seekLabel, "aria-valuetext": seekValueText, className: styles.scrubberInput, disabled: !hasSeekableDuration, max: hasSeekableDuration ? duration : 0, min: 0, onBlur: hideScrubberPreview, onChange: onSeekChange, onFocus: onShowControls, onPointerUp: (event) => {
331
+ event.currentTarget.blur();
332
+ }, step: 0.1, type: "range", value: hasSeekableDuration ? Math.min(Math.max(currentTime, 0), duration) : 0 })] }));
333
+ }
@@ -0,0 +1,8 @@
1
+ export type MediaVideoPlayerSurfaceClickAction = "ignore" | "play-now" | "reveal-controls" | "toggle-after-double-click-window";
2
+ export declare function resolveMediaVideoPlayerSurfaceClickAction(input: {
3
+ readonly clickCount: number;
4
+ readonly controlsVisible: boolean;
5
+ readonly ended: boolean;
6
+ readonly isCoarsePointer: boolean;
7
+ readonly paused: boolean;
8
+ }): MediaVideoPlayerSurfaceClickAction;
@@ -0,0 +1,14 @@
1
+ export function resolveMediaVideoPlayerSurfaceClickAction(input) {
2
+ if (input.clickCount > 1) {
3
+ return "ignore";
4
+ }
5
+ if (input.isCoarsePointer && !input.controlsVisible) {
6
+ return "reveal-controls";
7
+ }
8
+ // Starting playback must remain inside the click's user-activation window.
9
+ // Safari rejects audible play requests deferred through a timer. Pausing can
10
+ // still wait briefly so a double click only enters fullscreen.
11
+ return input.paused || input.ended
12
+ ? "play-now"
13
+ : "toggle-after-double-click-window";
14
+ }
@@ -0,0 +1,21 @@
1
+ export declare function resolveMediaVideoPlayerViewState(input: {
2
+ readonly bufferedEnd: number;
3
+ readonly currentTime: number;
4
+ readonly duration: number;
5
+ readonly hasError: boolean;
6
+ readonly isControlsVisible: boolean;
7
+ readonly isMediaReady: boolean;
8
+ readonly isMediaWaiting: boolean;
9
+ readonly isMuted: boolean;
10
+ readonly isPresentationReady: boolean;
11
+ readonly volume: number;
12
+ }): {
13
+ bufferProgress: string;
14
+ controlsAreVisible: boolean;
15
+ effectiveVolume: number;
16
+ hasSeekableDuration: boolean;
17
+ isBuffering: boolean;
18
+ isLoading: boolean;
19
+ seekProgress: string;
20
+ volumeProgress: string;
21
+ };
@@ -0,0 +1,31 @@
1
+ import { shouldShowMediaLoadingIndicator } from "@hexdn/playback/internal";
2
+ import { toMediaProgressPercent } from "@hexdn/playback/internal";
3
+ export function resolveMediaVideoPlayerViewState(input) {
4
+ const hasSeekableDuration = input.duration > 0;
5
+ const isBuffering = input.isPresentationReady && input.isMediaReady && input.isMediaWaiting;
6
+ const isLoading = shouldShowMediaLoadingIndicator({
7
+ hasError: input.hasError,
8
+ isMediaReady: input.isMediaReady,
9
+ isMediaWaiting: input.isMediaWaiting,
10
+ isPresentationReady: input.isPresentationReady,
11
+ });
12
+ const controlsAreVisible = input.isControlsVisible ||
13
+ !input.isPresentationReady ||
14
+ isLoading ||
15
+ input.hasError;
16
+ const effectiveVolume = input.isMuted ? 0 : input.volume;
17
+ return {
18
+ bufferProgress: hasSeekableDuration
19
+ ? toMediaProgressPercent(input.bufferedEnd / input.duration)
20
+ : "0%",
21
+ controlsAreVisible,
22
+ effectiveVolume,
23
+ hasSeekableDuration,
24
+ isBuffering,
25
+ isLoading,
26
+ seekProgress: hasSeekableDuration
27
+ ? toMediaProgressPercent(input.currentTime / input.duration)
28
+ : "0%",
29
+ volumeProgress: toMediaProgressPercent(effectiveVolume),
30
+ };
31
+ }