@bendyline/squisq-react 2.3.3 → 2.4.1
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/README.md +4 -0
- package/dist/{chunk-NWZQGZIJ.js → chunk-BOZJ655L.js} +10 -4
- package/dist/{chunk-IUFWLNXS.js → chunk-EKD6JMJN.js} +627 -164
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.js +1 -1
- package/dist/index.d.ts +56 -4
- package/dist/index.js +7 -3
- package/dist/player/index.d.ts +50 -4
- package/dist/player/index.js +2 -2
- package/dist/squisq-player.full.global.js +463 -461
- package/dist/squisq-player.global.js +65 -63
- package/dist/standalone-source.js +1 -1
- package/dist/styles/index.css +68 -0
- package/package.json +3 -3
|
@@ -7,40 +7,137 @@ import {
|
|
|
7
7
|
useDocPlayback,
|
|
8
8
|
useMediaSchedule,
|
|
9
9
|
useViewportOrientation
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BOZJ655L.js";
|
|
11
11
|
import {
|
|
12
12
|
useAutoSurface
|
|
13
13
|
} from "./chunk-TT6ENR6T.js";
|
|
14
14
|
import {
|
|
15
|
-
|
|
15
|
+
useMediaProvider,
|
|
16
|
+
useMediaUrl,
|
|
17
|
+
useResourcePolicy
|
|
16
18
|
} from "./chunk-LR3AIGDD.js";
|
|
17
19
|
|
|
18
20
|
// src/MediaClipLayer.tsx
|
|
19
21
|
import { useEffect, useRef } from "react";
|
|
20
|
-
import { jsx } from "react/jsx-runtime";
|
|
22
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
21
23
|
var DRIFT = 0.25;
|
|
24
|
+
function mediaGroupStyle(presentation) {
|
|
25
|
+
return {
|
|
26
|
+
position: "absolute",
|
|
27
|
+
inset: 0,
|
|
28
|
+
zIndex: presentation === "background" ? 0 : 10,
|
|
29
|
+
pointerEvents: "none"
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function pipCornerStyle(position) {
|
|
33
|
+
switch (position) {
|
|
34
|
+
case "top-left":
|
|
35
|
+
return { top: "4%", left: "3%" };
|
|
36
|
+
case "top-right":
|
|
37
|
+
return { top: "4%", right: "3%" };
|
|
38
|
+
case "bottom-left":
|
|
39
|
+
return { bottom: "6%", left: "3%" };
|
|
40
|
+
case "bottom-right":
|
|
41
|
+
return { right: "3%", bottom: "6%" };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function pipWidth(size, orientation) {
|
|
45
|
+
if (orientation === "portrait") return size === "large" ? "23%" : "15%";
|
|
46
|
+
return size === "large" ? "15%" : "9%";
|
|
47
|
+
}
|
|
48
|
+
function mediaVideoStyle({
|
|
49
|
+
presentation,
|
|
50
|
+
pipSize,
|
|
51
|
+
pipShape,
|
|
52
|
+
pipPosition,
|
|
53
|
+
pipOrientation,
|
|
54
|
+
pipFrameStyle,
|
|
55
|
+
active
|
|
56
|
+
}) {
|
|
57
|
+
const base = {
|
|
58
|
+
position: "absolute",
|
|
59
|
+
display: "block",
|
|
60
|
+
objectFit: "cover",
|
|
61
|
+
opacity: active ? 1 : 0,
|
|
62
|
+
visibility: active ? "visible" : "hidden",
|
|
63
|
+
pointerEvents: "none"
|
|
64
|
+
};
|
|
65
|
+
if (presentation !== "picture-in-picture") {
|
|
66
|
+
return { ...base, inset: 0, width: "100%", height: "100%" };
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
...base,
|
|
70
|
+
width: pipWidth(pipSize, pipOrientation),
|
|
71
|
+
aspectRatio: pipShape === "wide" ? "16 / 9" : "1",
|
|
72
|
+
...pipFrameStyle,
|
|
73
|
+
...pipCornerStyle(pipPosition)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
22
76
|
function MediaClipLayer({
|
|
23
77
|
schedule,
|
|
24
78
|
currentTime,
|
|
25
79
|
isPlaying,
|
|
26
80
|
basePath,
|
|
27
81
|
renderMode = false,
|
|
28
|
-
muted = false
|
|
82
|
+
muted = false,
|
|
83
|
+
presentation = "background",
|
|
84
|
+
pipSize = "small",
|
|
85
|
+
pipShape = "square",
|
|
86
|
+
pipPosition = "bottom-right",
|
|
87
|
+
pipOrientation = "landscape",
|
|
88
|
+
pipFrameStyle,
|
|
89
|
+
honorClipPresentation = true
|
|
29
90
|
}) {
|
|
30
91
|
const { renderClips, activeIds } = useMediaSchedule(schedule, currentTime);
|
|
31
92
|
if (renderClips.length === 0) return null;
|
|
32
|
-
|
|
33
|
-
|
|
93
|
+
const groups = /* @__PURE__ */ new Map();
|
|
94
|
+
for (const clip of renderClips) {
|
|
95
|
+
const clipPresentation = honorClipPresentation && clip.kind === "video" ? clip.placement === "picture-in-picture" ? "picture-in-picture" : clip.placement === "overlay" ? "full-frame" : presentation : presentation;
|
|
96
|
+
const clipPipSize = clip.pipSize ?? pipSize;
|
|
97
|
+
const clipPipShape = clip.pipShape ?? pipShape;
|
|
98
|
+
const clipPipPosition = clip.pipPosition ?? pipPosition;
|
|
99
|
+
const key = `${clipPresentation}:${clipPipSize}:${clipPipShape}:${clipPipPosition}`;
|
|
100
|
+
const group = groups.get(key) ?? {
|
|
101
|
+
presentation: clipPresentation,
|
|
102
|
+
pipSize: clipPipSize,
|
|
103
|
+
pipShape: clipPipShape,
|
|
104
|
+
pipPosition: clipPipPosition,
|
|
105
|
+
clips: []
|
|
106
|
+
};
|
|
107
|
+
group.clips.push(clip);
|
|
108
|
+
groups.set(key, group);
|
|
109
|
+
}
|
|
110
|
+
return /* @__PURE__ */ jsx(Fragment, { children: [...groups].map(([key, group]) => /* @__PURE__ */ jsx(
|
|
111
|
+
"div",
|
|
34
112
|
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
113
|
+
className: `doc-player__media-clips doc-player__media-clips--${group.presentation}`,
|
|
114
|
+
"data-presentation": group.presentation,
|
|
115
|
+
"data-pip-size": group.pipSize,
|
|
116
|
+
"data-pip-shape": group.pipShape,
|
|
117
|
+
"data-pip-position": group.pipPosition,
|
|
118
|
+
"aria-hidden": true,
|
|
119
|
+
style: mediaGroupStyle(group.presentation),
|
|
120
|
+
children: group.clips.map((clip) => /* @__PURE__ */ jsx(
|
|
121
|
+
MediaClipElement,
|
|
122
|
+
{
|
|
123
|
+
clip,
|
|
124
|
+
active: activeIds.has(clip.id),
|
|
125
|
+
currentTime,
|
|
126
|
+
isPlaying,
|
|
127
|
+
basePath,
|
|
128
|
+
renderMode,
|
|
129
|
+
muted,
|
|
130
|
+
presentation: group.presentation,
|
|
131
|
+
pipSize: group.pipSize,
|
|
132
|
+
pipShape: group.pipShape,
|
|
133
|
+
pipPosition: group.pipPosition,
|
|
134
|
+
pipOrientation,
|
|
135
|
+
pipFrameStyle
|
|
136
|
+
},
|
|
137
|
+
clip.id
|
|
138
|
+
))
|
|
42
139
|
},
|
|
43
|
-
|
|
140
|
+
key
|
|
44
141
|
)) });
|
|
45
142
|
}
|
|
46
143
|
function MediaClipElement({
|
|
@@ -50,7 +147,13 @@ function MediaClipElement({
|
|
|
50
147
|
isPlaying,
|
|
51
148
|
basePath,
|
|
52
149
|
renderMode,
|
|
53
|
-
muted
|
|
150
|
+
muted,
|
|
151
|
+
presentation,
|
|
152
|
+
pipSize,
|
|
153
|
+
pipShape,
|
|
154
|
+
pipPosition,
|
|
155
|
+
pipOrientation,
|
|
156
|
+
pipFrameStyle
|
|
54
157
|
}) {
|
|
55
158
|
const ref = useRef(null);
|
|
56
159
|
const src = useMediaUrl(clip.src, basePath);
|
|
@@ -62,7 +165,7 @@ function MediaClipElement({
|
|
|
62
165
|
return;
|
|
63
166
|
}
|
|
64
167
|
const target = Math.max(0, clip.sourceIn + (currentTime - clip.absoluteStart));
|
|
65
|
-
if (renderMode || Math.abs(el.currentTime - target) > DRIFT) {
|
|
168
|
+
if (renderMode || !isPlaying || Math.abs(el.currentTime - target) > DRIFT) {
|
|
66
169
|
try {
|
|
67
170
|
el.currentTime = target;
|
|
68
171
|
} catch {
|
|
@@ -91,17 +194,20 @@ function MediaClipElement({
|
|
|
91
194
|
"video",
|
|
92
195
|
{
|
|
93
196
|
...common,
|
|
94
|
-
|
|
197
|
+
className: `doc-player__media-video${active ? " doc-player__media-video--active" : ""}`,
|
|
198
|
+
"data-active": active ? "true" : "false",
|
|
199
|
+
"data-video-placement": clip.placement ?? "default",
|
|
200
|
+
muted: renderMode || muted,
|
|
95
201
|
playsInline: true,
|
|
96
|
-
style: {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
202
|
+
style: mediaVideoStyle({
|
|
203
|
+
presentation,
|
|
204
|
+
pipSize,
|
|
205
|
+
pipShape,
|
|
206
|
+
pipPosition,
|
|
207
|
+
pipOrientation,
|
|
208
|
+
pipFrameStyle,
|
|
209
|
+
active
|
|
210
|
+
})
|
|
105
211
|
}
|
|
106
212
|
);
|
|
107
213
|
}
|
|
@@ -115,8 +221,123 @@ function MediaClipElement({
|
|
|
115
221
|
);
|
|
116
222
|
}
|
|
117
223
|
|
|
224
|
+
// src/hooks/useMediaClipDurations.ts
|
|
225
|
+
import { useEffect as useEffect2, useMemo, useState } from "react";
|
|
226
|
+
import { isResourceUrlAllowed } from "@bendyline/squisq/markdown";
|
|
227
|
+
var durationCache = /* @__PURE__ */ new Map();
|
|
228
|
+
var PROBE_TIMEOUT_MS = 8e3;
|
|
229
|
+
async function resolveMediaUrl(src, provider, basePath, policy) {
|
|
230
|
+
const isAbsolute = !src || /^(?:[a-z][a-z0-9+.-]*:|\/\/|\/)/i.test(src);
|
|
231
|
+
const fallback = isAbsolute ? src : `${basePath.replace(/\/$/, "")}/${src}`;
|
|
232
|
+
const gate = (url) => isResourceUrlAllowed(url, policy) ? url : "";
|
|
233
|
+
if (isAbsolute || !provider) return gate(fallback);
|
|
234
|
+
try {
|
|
235
|
+
return gate(await provider.resolveUrl(src));
|
|
236
|
+
} catch {
|
|
237
|
+
return gate(fallback);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function probeVideoDuration(url) {
|
|
241
|
+
if (typeof document === "undefined") return Promise.resolve(null);
|
|
242
|
+
return new Promise((resolve) => {
|
|
243
|
+
const el = document.createElement("video");
|
|
244
|
+
el.preload = "metadata";
|
|
245
|
+
el.muted = true;
|
|
246
|
+
el.setAttribute("playsinline", "");
|
|
247
|
+
let settled = false;
|
|
248
|
+
const timeout = {};
|
|
249
|
+
const finish = (seconds) => {
|
|
250
|
+
if (settled) return;
|
|
251
|
+
settled = true;
|
|
252
|
+
if (timeout.id !== void 0) clearTimeout(timeout.id);
|
|
253
|
+
el.removeEventListener("durationchange", onDurationProbe);
|
|
254
|
+
el.removeEventListener("timeupdate", onDurationProbe);
|
|
255
|
+
el.removeAttribute("src");
|
|
256
|
+
try {
|
|
257
|
+
el.load();
|
|
258
|
+
} catch {
|
|
259
|
+
}
|
|
260
|
+
resolve(seconds);
|
|
261
|
+
};
|
|
262
|
+
const readFinite = () => {
|
|
263
|
+
const d = el.duration;
|
|
264
|
+
if (Number.isFinite(d) && d > 0) {
|
|
265
|
+
finish(d);
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
};
|
|
270
|
+
const onDurationProbe = () => {
|
|
271
|
+
readFinite();
|
|
272
|
+
};
|
|
273
|
+
el.addEventListener(
|
|
274
|
+
"loadedmetadata",
|
|
275
|
+
() => {
|
|
276
|
+
if (readFinite()) return;
|
|
277
|
+
el.addEventListener("durationchange", onDurationProbe);
|
|
278
|
+
el.addEventListener("timeupdate", onDurationProbe);
|
|
279
|
+
try {
|
|
280
|
+
el.currentTime = 1e101;
|
|
281
|
+
} catch {
|
|
282
|
+
finish(null);
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
{ once: true }
|
|
286
|
+
);
|
|
287
|
+
el.addEventListener("error", () => finish(null), { once: true });
|
|
288
|
+
timeout.id = setTimeout(() => finish(null), PROBE_TIMEOUT_MS);
|
|
289
|
+
el.src = url;
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
function useMediaClipDurations(schedule, basePath, mediaProviderOverride) {
|
|
293
|
+
const contextProvider = useMediaProvider();
|
|
294
|
+
const provider = mediaProviderOverride !== void 0 ? mediaProviderOverride : contextProvider;
|
|
295
|
+
const resourcePolicy = useResourcePolicy();
|
|
296
|
+
const srcKey = useMemo(() => {
|
|
297
|
+
const set = /* @__PURE__ */ new Set();
|
|
298
|
+
for (const clip of schedule) {
|
|
299
|
+
if (clip.kind === "video" && clip.src) set.add(clip.src);
|
|
300
|
+
}
|
|
301
|
+
return Array.from(set).sort().join("\n");
|
|
302
|
+
}, [schedule]);
|
|
303
|
+
const [durations, setDurations] = useState(() => /* @__PURE__ */ new Map());
|
|
304
|
+
useEffect2(() => {
|
|
305
|
+
const srcs = srcKey ? srcKey.split("\n") : [];
|
|
306
|
+
if (srcs.length === 0) {
|
|
307
|
+
setDurations((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
let cancelled = false;
|
|
311
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
312
|
+
const commit = () => {
|
|
313
|
+
if (!cancelled) setDurations(new Map(resolved));
|
|
314
|
+
};
|
|
315
|
+
for (const src of srcs) {
|
|
316
|
+
resolveMediaUrl(src, provider, basePath, resourcePolicy).then((url) => {
|
|
317
|
+
if (cancelled || !url) return;
|
|
318
|
+
const cached = durationCache.get(url);
|
|
319
|
+
if (cached != null) {
|
|
320
|
+
resolved.set(src, cached);
|
|
321
|
+
commit();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
probeVideoDuration(url).then((seconds) => {
|
|
325
|
+
if (cancelled || seconds == null) return;
|
|
326
|
+
durationCache.set(url, seconds);
|
|
327
|
+
resolved.set(src, seconds);
|
|
328
|
+
commit();
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return () => {
|
|
333
|
+
cancelled = true;
|
|
334
|
+
};
|
|
335
|
+
}, [srcKey, provider, basePath, resourcePolicy]);
|
|
336
|
+
return durations;
|
|
337
|
+
}
|
|
338
|
+
|
|
118
339
|
// src/SocialCaptionOverlay.tsx
|
|
119
|
-
import { useMemo } from "react";
|
|
340
|
+
import { useMemo as useMemo2 } from "react";
|
|
120
341
|
import { resolveFontFamily } from "@bendyline/squisq/schemas";
|
|
121
342
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
122
343
|
var TARGET_CHUNK_SIZE = 4;
|
|
@@ -172,7 +393,7 @@ function SocialCaptionOverlay({
|
|
|
172
393
|
theme,
|
|
173
394
|
viewport
|
|
174
395
|
}) {
|
|
175
|
-
const { chunks } =
|
|
396
|
+
const { chunks } = useMemo2(
|
|
176
397
|
() => captions ? buildWordStream(captions) : { words: [], chunks: [] },
|
|
177
398
|
[captions]
|
|
178
399
|
);
|
|
@@ -374,7 +595,7 @@ function formatTime(seconds) {
|
|
|
374
595
|
}
|
|
375
596
|
|
|
376
597
|
// src/DocProgressBar.tsx
|
|
377
|
-
import { useRef as useRef2, useState, useCallback } from "react";
|
|
598
|
+
import { useRef as useRef2, useState as useState2, useCallback } from "react";
|
|
378
599
|
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
379
600
|
function DocProgressBar({
|
|
380
601
|
state,
|
|
@@ -384,7 +605,7 @@ function DocProgressBar({
|
|
|
384
605
|
getBlockTitle
|
|
385
606
|
}) {
|
|
386
607
|
const progressBarRef = useRef2(null);
|
|
387
|
-
const [hoverPosition, setHoverPosition] =
|
|
608
|
+
const [hoverPosition, setHoverPosition] = useState2(null);
|
|
388
609
|
const playProgress = state.totalDuration > 0 ? Math.max(0, Math.min(1, state.currentTime / state.totalDuration)) : 0;
|
|
389
610
|
const handleProgressHover = useCallback((e) => {
|
|
390
611
|
const bar = progressBarRef.current;
|
|
@@ -711,7 +932,7 @@ function DocControlsOverlay({
|
|
|
711
932
|
}
|
|
712
933
|
|
|
713
934
|
// src/DocControlsSlideshow.tsx
|
|
714
|
-
import { useCallback as useCallback2, useEffect as
|
|
935
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useId, useLayoutEffect, useRef as useRef3, useState as useState3 } from "react";
|
|
715
936
|
import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
716
937
|
function DocControlsSlideshow({
|
|
717
938
|
state,
|
|
@@ -720,7 +941,7 @@ function DocControlsSlideshow({
|
|
|
720
941
|
pickerOpen,
|
|
721
942
|
onPickerOpenChange
|
|
722
943
|
}) {
|
|
723
|
-
const [uncontrolledPickerOpen, setUncontrolledPickerOpen] =
|
|
944
|
+
const [uncontrolledPickerOpen, setUncontrolledPickerOpen] = useState3(false);
|
|
724
945
|
const isPickerOpen = pickerOpen ?? uncontrolledPickerOpen;
|
|
725
946
|
const setPickerOpen = useCallback2(
|
|
726
947
|
(open) => {
|
|
@@ -729,7 +950,7 @@ function DocControlsSlideshow({
|
|
|
729
950
|
},
|
|
730
951
|
[pickerOpen, onPickerOpenChange]
|
|
731
952
|
);
|
|
732
|
-
const [pickerMaxHeight, setPickerMaxHeight] =
|
|
953
|
+
const [pickerMaxHeight, setPickerMaxHeight] = useState3(280);
|
|
733
954
|
const controlsRef = useRef3(null);
|
|
734
955
|
const triggerRef = useRef3(null);
|
|
735
956
|
const menuRef = useRef3(null);
|
|
@@ -744,7 +965,7 @@ function DocControlsSlideshow({
|
|
|
744
965
|
const isFirst = currentBlockIndex <= 0;
|
|
745
966
|
const isLast = currentBlockIndex >= totalBlocks - 1;
|
|
746
967
|
const counterText = totalBlocks > 0 ? currentSlideLabel ?? `${currentSlideNumber ?? currentBlockIndex + 1} / ${totalSlideNumber ?? totalBlocks}` : "\u2014";
|
|
747
|
-
|
|
968
|
+
useEffect3(() => {
|
|
748
969
|
if (!isPickerOpen) return;
|
|
749
970
|
const handlePointerDown = (event) => {
|
|
750
971
|
if (!controlsRef.current?.contains(event.target)) setPickerOpen(false);
|
|
@@ -783,7 +1004,7 @@ function DocControlsSlideshow({
|
|
|
783
1004
|
window.removeEventListener("resize", updateMaxHeight);
|
|
784
1005
|
};
|
|
785
1006
|
}, [isPickerOpen]);
|
|
786
|
-
|
|
1007
|
+
useEffect3(() => {
|
|
787
1008
|
if (!isPickerOpen) return;
|
|
788
1009
|
menuRef.current?.querySelector('[aria-current="true"]')?.focus();
|
|
789
1010
|
}, [isPickerOpen]);
|
|
@@ -1021,18 +1242,105 @@ function DocControlsSlideshow({
|
|
|
1021
1242
|
);
|
|
1022
1243
|
}
|
|
1023
1244
|
|
|
1245
|
+
// src/docPlayer/playerAppearance.ts
|
|
1246
|
+
import { resolveThemeForDoc } from "@bendyline/squisq/doc";
|
|
1247
|
+
function readFrontmatterSetting(frontmatter, canonical, legacy) {
|
|
1248
|
+
if (!frontmatter) return void 0;
|
|
1249
|
+
return Object.prototype.hasOwnProperty.call(frontmatter, canonical) ? frontmatter[canonical] : frontmatter[legacy];
|
|
1250
|
+
}
|
|
1251
|
+
function resolveVideoPresentation(value) {
|
|
1252
|
+
if (typeof value !== "string") return void 0;
|
|
1253
|
+
const normalized = value.trim().toLowerCase();
|
|
1254
|
+
if (normalized === "background" || normalized === "behind") return "background";
|
|
1255
|
+
if (normalized === "full-frame" || normalized === "fullscreen" || normalized === "full") {
|
|
1256
|
+
return "full-frame";
|
|
1257
|
+
}
|
|
1258
|
+
if (normalized === "picture-in-picture" || normalized === "pip" || normalized === "overlay") {
|
|
1259
|
+
return "picture-in-picture";
|
|
1260
|
+
}
|
|
1261
|
+
return void 0;
|
|
1262
|
+
}
|
|
1263
|
+
function resolvePipSize(value) {
|
|
1264
|
+
if (typeof value !== "string") return void 0;
|
|
1265
|
+
const normalized = value.trim().toLowerCase();
|
|
1266
|
+
if (normalized === "small" || normalized === "sm" || normalized === "pip-small") return "small";
|
|
1267
|
+
if (normalized === "large" || normalized === "lg" || normalized === "big" || normalized === "pip-large") {
|
|
1268
|
+
return "large";
|
|
1269
|
+
}
|
|
1270
|
+
return void 0;
|
|
1271
|
+
}
|
|
1272
|
+
function resolvePipShape(value) {
|
|
1273
|
+
if (typeof value !== "string") return void 0;
|
|
1274
|
+
const normalized = value.trim().toLowerCase();
|
|
1275
|
+
if (normalized === "square" || normalized === "1:1") return "square";
|
|
1276
|
+
if (normalized === "wide" || normalized === "16:9" || normalized === "widescreen") return "wide";
|
|
1277
|
+
return void 0;
|
|
1278
|
+
}
|
|
1279
|
+
function resolvePipPosition(value) {
|
|
1280
|
+
if (typeof value !== "string") return void 0;
|
|
1281
|
+
const normalized = value.trim().toLowerCase().replace(/[_\s]+/g, "-");
|
|
1282
|
+
const aliases = {
|
|
1283
|
+
"top-left": "top-left",
|
|
1284
|
+
"upper-left": "top-left",
|
|
1285
|
+
"top-right": "top-right",
|
|
1286
|
+
"upper-right": "top-right",
|
|
1287
|
+
"bottom-left": "bottom-left",
|
|
1288
|
+
"lower-left": "bottom-left",
|
|
1289
|
+
"bottom-right": "bottom-right",
|
|
1290
|
+
"lower-right": "bottom-right"
|
|
1291
|
+
};
|
|
1292
|
+
return aliases[normalized];
|
|
1293
|
+
}
|
|
1294
|
+
function resolveBoolean(value) {
|
|
1295
|
+
if (typeof value === "boolean") return value;
|
|
1296
|
+
if (typeof value !== "string") return void 0;
|
|
1297
|
+
const normalized = value.trim().toLowerCase();
|
|
1298
|
+
if (normalized === "true" || normalized === "yes" || normalized === "on" || normalized === "show" || normalized === "visible") {
|
|
1299
|
+
return true;
|
|
1300
|
+
}
|
|
1301
|
+
if (normalized === "false" || normalized === "no" || normalized === "off" || normalized === "hide" || normalized === "hidden") {
|
|
1302
|
+
return false;
|
|
1303
|
+
}
|
|
1304
|
+
return void 0;
|
|
1305
|
+
}
|
|
1306
|
+
function resolveDocPlayerAppearance(doc, overrides = {}) {
|
|
1307
|
+
const frontmatter = doc.frontmatter;
|
|
1308
|
+
return {
|
|
1309
|
+
theme: overrides.theme ?? resolveThemeForDoc(doc),
|
|
1310
|
+
videoPresentation: overrides.videoPresentation ?? resolveVideoPresentation(
|
|
1311
|
+
readFrontmatterSetting(frontmatter, "squisq-video-presentation", "video-presentation")
|
|
1312
|
+
) ?? "background",
|
|
1313
|
+
pipSize: overrides.pipSize ?? resolvePipSize(readFrontmatterSetting(frontmatter, "squisq-pip-size", "pip-size")) ?? "small",
|
|
1314
|
+
pipShape: overrides.pipShape ?? resolvePipShape(readFrontmatterSetting(frontmatter, "squisq-pip-shape", "pip-shape")) ?? "square",
|
|
1315
|
+
pipPosition: overrides.pipPosition ?? resolvePipPosition(
|
|
1316
|
+
readFrontmatterSetting(frontmatter, "squisq-pip-position", "pip-position")
|
|
1317
|
+
) ?? "bottom-right",
|
|
1318
|
+
showCoverSlide: overrides.showCoverSlide ?? resolveBoolean(readFrontmatterSetting(frontmatter, "squisq-cover-slide", "cover-slide")) ?? true
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1024
1322
|
// src/DocPlayer.tsx
|
|
1025
|
-
import {
|
|
1323
|
+
import {
|
|
1324
|
+
Fragment as Fragment2,
|
|
1325
|
+
useId as useId2,
|
|
1326
|
+
useRef as useRef5,
|
|
1327
|
+
useState as useState5,
|
|
1328
|
+
useEffect as useEffect5,
|
|
1329
|
+
useLayoutEffect as useLayoutEffect2,
|
|
1330
|
+
useCallback as useCallback4,
|
|
1331
|
+
useMemo as useMemo3
|
|
1332
|
+
} from "react";
|
|
1333
|
+
import { flushSync } from "react-dom";
|
|
1026
1334
|
import {
|
|
1027
1335
|
isTemplateBlock as isTemplateBlock2,
|
|
1028
1336
|
getCaptionAtTime as getCaptionAtTime2,
|
|
1029
1337
|
resolveMediaSchedule,
|
|
1030
1338
|
getDocPlaybackDuration
|
|
1031
1339
|
} from "@bendyline/squisq/schemas";
|
|
1032
|
-
import { applySurface } from "@bendyline/squisq/schemas";
|
|
1340
|
+
import { applySurface, pipStyleVars } from "@bendyline/squisq/schemas";
|
|
1033
1341
|
|
|
1034
1342
|
// src/hooks/useSlideSwipe.ts
|
|
1035
|
-
import { useCallback as useCallback3, useEffect as
|
|
1343
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
1036
1344
|
var DISTANCE_RATIO = 0.3;
|
|
1037
1345
|
var FLICK_VELOCITY = 0.5;
|
|
1038
1346
|
var MIN_FLICK_DISTANCE = 12;
|
|
@@ -1056,8 +1364,8 @@ function decideSwipe({
|
|
|
1056
1364
|
}
|
|
1057
1365
|
function useSlideSwipe(opts) {
|
|
1058
1366
|
const settleMs = opts.settleMs ?? DEFAULT_SETTLE_MS;
|
|
1059
|
-
const [offsetPx, setOffsetPx] =
|
|
1060
|
-
const [phase, setPhase] =
|
|
1367
|
+
const [offsetPx, setOffsetPx] = useState4(0);
|
|
1368
|
+
const [phase, setPhase] = useState4("idle");
|
|
1061
1369
|
const optsRef = useRef4(opts);
|
|
1062
1370
|
optsRef.current = opts;
|
|
1063
1371
|
const dragRef = useRef4(null);
|
|
@@ -1095,7 +1403,7 @@ function useSlideSwipe(opts) {
|
|
|
1095
1403
|
setPhase("dragging");
|
|
1096
1404
|
setOffsetPx(0);
|
|
1097
1405
|
}, []);
|
|
1098
|
-
|
|
1406
|
+
useEffect4(() => {
|
|
1099
1407
|
function currentWidth() {
|
|
1100
1408
|
return optsRef.current.containerRef.current?.getBoundingClientRect().width ?? 0;
|
|
1101
1409
|
}
|
|
@@ -1165,7 +1473,7 @@ function useSlideSwipe(opts) {
|
|
|
1165
1473
|
window.removeEventListener("pointercancel", onCancel);
|
|
1166
1474
|
};
|
|
1167
1475
|
}, [settleMs]);
|
|
1168
|
-
|
|
1476
|
+
useEffect4(() => {
|
|
1169
1477
|
if (!opts.enabled) {
|
|
1170
1478
|
dragRef.current = null;
|
|
1171
1479
|
clearPending();
|
|
@@ -1173,7 +1481,7 @@ function useSlideSwipe(opts) {
|
|
|
1173
1481
|
setOffsetPx(0);
|
|
1174
1482
|
}
|
|
1175
1483
|
}, [opts.enabled, clearPending]);
|
|
1176
|
-
|
|
1484
|
+
useEffect4(() => clearPending, [clearPending]);
|
|
1177
1485
|
return { offsetPx, phase, onPointerDown };
|
|
1178
1486
|
}
|
|
1179
1487
|
|
|
@@ -1182,7 +1490,6 @@ import {
|
|
|
1182
1490
|
expandCoverBlock,
|
|
1183
1491
|
createTemplateContext,
|
|
1184
1492
|
markdownToDoc,
|
|
1185
|
-
DEFAULT_THEME,
|
|
1186
1493
|
VIEWPORT_PRESETS
|
|
1187
1494
|
} from "@bendyline/squisq/doc";
|
|
1188
1495
|
import { parseMarkdown } from "@bendyline/squisq/markdown";
|
|
@@ -1231,6 +1538,86 @@ function buildSegmentTitleMap(doc) {
|
|
|
1231
1538
|
return map;
|
|
1232
1539
|
}
|
|
1233
1540
|
|
|
1541
|
+
// src/docPlayer/renderReadiness.ts
|
|
1542
|
+
var DEFAULT_VIDEO_FRAME_TIMEOUT_MS = 2e3;
|
|
1543
|
+
var VIDEO_TIME_TOLERANCE_SECONDS = 0.01;
|
|
1544
|
+
function formatMediaTime(time) {
|
|
1545
|
+
return Number.isFinite(time) ? `${time.toFixed(3)}s` : String(time);
|
|
1546
|
+
}
|
|
1547
|
+
function isVisiblyPresented(video) {
|
|
1548
|
+
if (!video.isConnected) return false;
|
|
1549
|
+
const view = video.ownerDocument.defaultView;
|
|
1550
|
+
if (!view) return false;
|
|
1551
|
+
for (let element = video; element; element = element.parentElement) {
|
|
1552
|
+
const style = view.getComputedStyle(element);
|
|
1553
|
+
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse" || Number.parseFloat(style.opacity || "1") <= 0) {
|
|
1554
|
+
return false;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
return true;
|
|
1558
|
+
}
|
|
1559
|
+
function seekVideoToFrame(video, targetTime, timeoutMs = DEFAULT_VIDEO_FRAME_TIMEOUT_MS) {
|
|
1560
|
+
video.pause();
|
|
1561
|
+
const alreadyReady = !video.seeking && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && Math.abs(video.currentTime - targetTime) <= VIDEO_TIME_TOLERANCE_SECONDS;
|
|
1562
|
+
if (alreadyReady) return Promise.resolve();
|
|
1563
|
+
return new Promise((resolve, reject) => {
|
|
1564
|
+
let settled = false;
|
|
1565
|
+
let videoFrameRequest = null;
|
|
1566
|
+
const cleanup = () => {
|
|
1567
|
+
clearTimeout(timeout);
|
|
1568
|
+
video.removeEventListener("seeked", handleMediaReady);
|
|
1569
|
+
video.removeEventListener("loadeddata", handleMediaReady);
|
|
1570
|
+
video.removeEventListener("canplay", handleMediaReady);
|
|
1571
|
+
if (videoFrameRequest !== null && typeof video.cancelVideoFrameCallback === "function") {
|
|
1572
|
+
video.cancelVideoFrameCallback(videoFrameRequest);
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
const finish = () => {
|
|
1576
|
+
if (settled) return;
|
|
1577
|
+
settled = true;
|
|
1578
|
+
cleanup();
|
|
1579
|
+
resolve();
|
|
1580
|
+
};
|
|
1581
|
+
const fail = () => {
|
|
1582
|
+
if (settled) return;
|
|
1583
|
+
settled = true;
|
|
1584
|
+
cleanup();
|
|
1585
|
+
reject(
|
|
1586
|
+
new Error(
|
|
1587
|
+
`Video frame did not become ready at ${formatMediaTime(targetTime)} within ${timeoutMs}ms (currentTime=${formatMediaTime(video.currentTime)}, readyState=${video.readyState}, seeking=${String(video.seeking)}, visible=${String(isVisiblyPresented(video))}).`
|
|
1588
|
+
)
|
|
1589
|
+
);
|
|
1590
|
+
};
|
|
1591
|
+
const requestPresentedFrame = () => {
|
|
1592
|
+
if (settled || video.seeking || video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) return;
|
|
1593
|
+
if (typeof video.requestVideoFrameCallback !== "function" || !isVisiblyPresented(video)) {
|
|
1594
|
+
finish();
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
if (videoFrameRequest !== null) return;
|
|
1598
|
+
videoFrameRequest = video.requestVideoFrameCallback(() => {
|
|
1599
|
+
videoFrameRequest = null;
|
|
1600
|
+
finish();
|
|
1601
|
+
});
|
|
1602
|
+
};
|
|
1603
|
+
function handleMediaReady() {
|
|
1604
|
+
requestPresentedFrame();
|
|
1605
|
+
}
|
|
1606
|
+
const timeout = setTimeout(fail, timeoutMs);
|
|
1607
|
+
video.addEventListener("seeked", handleMediaReady);
|
|
1608
|
+
video.addEventListener("loadeddata", handleMediaReady);
|
|
1609
|
+
video.addEventListener("canplay", handleMediaReady);
|
|
1610
|
+
try {
|
|
1611
|
+
video.currentTime = targetTime;
|
|
1612
|
+
queueMicrotask(requestPresentedFrame);
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
settled = true;
|
|
1615
|
+
cleanup();
|
|
1616
|
+
reject(error);
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1234
1621
|
// src/DocPlayer.tsx
|
|
1235
1622
|
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1236
1623
|
function isDevEnvironment() {
|
|
@@ -1242,6 +1629,7 @@ function isDevEnvironment() {
|
|
|
1242
1629
|
}
|
|
1243
1630
|
var warnedMissingStyles = false;
|
|
1244
1631
|
var VISUAL_UPDATE_FALLBACK_MS = 100;
|
|
1632
|
+
var RENDER_TIME_EPSILON_SECONDS = 1e-6;
|
|
1245
1633
|
function waitForVisualUpdate() {
|
|
1246
1634
|
return new Promise((resolve) => {
|
|
1247
1635
|
let settled = false;
|
|
@@ -1263,7 +1651,7 @@ function waitForVisualUpdate() {
|
|
|
1263
1651
|
}
|
|
1264
1652
|
function DocPlayer(props) {
|
|
1265
1653
|
const { doc, markdown } = props;
|
|
1266
|
-
const markdownDoc =
|
|
1654
|
+
const markdownDoc = useMemo3(
|
|
1267
1655
|
() => !doc && markdown !== void 0 ? markdownToDoc(parseMarkdown(markdown)) : void 0,
|
|
1268
1656
|
[doc, markdown]
|
|
1269
1657
|
);
|
|
@@ -1279,6 +1667,7 @@ function DocPlayerContent({
|
|
|
1279
1667
|
renderMode = false,
|
|
1280
1668
|
animationsEnabled = true,
|
|
1281
1669
|
autoPlay = false,
|
|
1670
|
+
loop = false,
|
|
1282
1671
|
onEnded,
|
|
1283
1672
|
onTimeUpdate,
|
|
1284
1673
|
audioController: externalAudioController,
|
|
@@ -1296,11 +1685,15 @@ function DocPlayerContent({
|
|
|
1296
1685
|
onBlockMarkers,
|
|
1297
1686
|
forceViewport,
|
|
1298
1687
|
displayMode = "video",
|
|
1299
|
-
showCoverSlide
|
|
1688
|
+
showCoverSlide,
|
|
1300
1689
|
coverVisible,
|
|
1301
1690
|
theme,
|
|
1302
1691
|
surface,
|
|
1303
1692
|
captionStyle = "standard",
|
|
1693
|
+
videoPresentation,
|
|
1694
|
+
pipSize,
|
|
1695
|
+
pipShape,
|
|
1696
|
+
pipPosition,
|
|
1304
1697
|
enableSwipe = true,
|
|
1305
1698
|
globalKeyboardShortcuts = false
|
|
1306
1699
|
}) {
|
|
@@ -1309,11 +1702,12 @@ function DocPlayerContent({
|
|
|
1309
1702
|
const audioRef = useRef5(null);
|
|
1310
1703
|
const containerRef = useRef5(null);
|
|
1311
1704
|
const playerId = `squisq-player-${useId2().replace(/:/g, "")}`;
|
|
1312
|
-
const [tapFeedback, setTapFeedback] =
|
|
1705
|
+
const [tapFeedback, setTapFeedback] = useState5(null);
|
|
1313
1706
|
const tapFeedbackTimer = useRef5();
|
|
1314
|
-
const { viewport
|
|
1707
|
+
const { viewport } = useViewportOrientation();
|
|
1315
1708
|
const activeViewport = forceViewport || (renderMode ? VIEWPORT_PRESETS.landscape : viewport);
|
|
1316
|
-
const
|
|
1709
|
+
const activeOrientation = activeViewport.height > activeViewport.width ? "portrait" : "landscape";
|
|
1710
|
+
const isDebugMode = useMemo3(() => {
|
|
1317
1711
|
if (typeof window === "undefined") return false;
|
|
1318
1712
|
const params = new URLSearchParams(window.location.search);
|
|
1319
1713
|
return params.get("debug") === "true";
|
|
@@ -1326,7 +1720,7 @@ function DocPlayerContent({
|
|
|
1326
1720
|
audioMode
|
|
1327
1721
|
);
|
|
1328
1722
|
const audio = externalAudioController || internalAudio;
|
|
1329
|
-
|
|
1723
|
+
useEffect5(() => {
|
|
1330
1724
|
if (warnedMissingStyles || !isDevEnvironment()) return;
|
|
1331
1725
|
const el = containerRef.current;
|
|
1332
1726
|
if (!el || typeof getComputedStyle !== "function") return;
|
|
@@ -1339,7 +1733,7 @@ function DocPlayerContent({
|
|
|
1339
1733
|
}
|
|
1340
1734
|
}, []);
|
|
1341
1735
|
const {
|
|
1342
|
-
currentTime,
|
|
1736
|
+
currentTime: audioCurrentTime,
|
|
1343
1737
|
isPlaying,
|
|
1344
1738
|
currentSegment,
|
|
1345
1739
|
totalDuration,
|
|
@@ -1354,12 +1748,46 @@ function DocPlayerContent({
|
|
|
1354
1748
|
skipToSegment: _skipToSegment,
|
|
1355
1749
|
restart
|
|
1356
1750
|
} = audio;
|
|
1357
|
-
const
|
|
1751
|
+
const [renderClock, setRenderClock] = useState5(null);
|
|
1752
|
+
const renderTimeOverride = renderClock?.doc === doc ? renderClock.time : null;
|
|
1753
|
+
const currentTime = renderMode ? renderTimeOverride ?? audioCurrentTime : audioCurrentTime;
|
|
1754
|
+
const rawSchedule = useMemo3(() => resolveMediaSchedule(doc), [doc]);
|
|
1755
|
+
const clipDurations = useMediaClipDurations(rawSchedule, basePath);
|
|
1756
|
+
const mediaSchedule = useMemo3(
|
|
1757
|
+
() => resolveMediaSchedule(doc, { intrinsicDuration: (clip) => clipDurations.get(clip.src) }),
|
|
1758
|
+
[doc, clipDurations]
|
|
1759
|
+
);
|
|
1358
1760
|
const currentTimeRef = useRef5(currentTime);
|
|
1359
1761
|
currentTimeRef.current = currentTime;
|
|
1762
|
+
const committedRenderTimeRef = useRef5(currentTime);
|
|
1763
|
+
const renderCommitWaitersRef = useRef5([]);
|
|
1360
1764
|
const totalDurationRef = useRef5(totalDuration);
|
|
1361
1765
|
totalDurationRef.current = totalDuration;
|
|
1362
1766
|
const expandedBlocksLenRef = useRef5(0);
|
|
1767
|
+
useLayoutEffect2(() => {
|
|
1768
|
+
committedRenderTimeRef.current = currentTime;
|
|
1769
|
+
const pending = renderCommitWaitersRef.current;
|
|
1770
|
+
renderCommitWaitersRef.current = pending.filter((waiter) => {
|
|
1771
|
+
if (Math.abs(waiter.time - currentTime) > RENDER_TIME_EPSILON_SECONDS) return true;
|
|
1772
|
+
waiter.resolve();
|
|
1773
|
+
return false;
|
|
1774
|
+
});
|
|
1775
|
+
}, [currentTime]);
|
|
1776
|
+
useEffect5(
|
|
1777
|
+
() => () => {
|
|
1778
|
+
const error = new Error("DocPlayer unmounted before the requested frame committed.");
|
|
1779
|
+
renderCommitWaitersRef.current.splice(0).forEach((waiter) => waiter.reject(error));
|
|
1780
|
+
},
|
|
1781
|
+
[]
|
|
1782
|
+
);
|
|
1783
|
+
const waitForRenderCommit = useCallback4((time) => {
|
|
1784
|
+
if (Math.abs(committedRenderTimeRef.current - time) <= RENDER_TIME_EPSILON_SECONDS) {
|
|
1785
|
+
return Promise.resolve();
|
|
1786
|
+
}
|
|
1787
|
+
return new Promise((resolve, reject) => {
|
|
1788
|
+
renderCommitWaitersRef.current.push({ time, resolve, reject });
|
|
1789
|
+
});
|
|
1790
|
+
}, []);
|
|
1363
1791
|
const handleContainerClick = useCallback4(
|
|
1364
1792
|
(e) => {
|
|
1365
1793
|
if (renderMode || isLinearMode) return;
|
|
@@ -1385,10 +1813,31 @@ function DocPlayerContent({
|
|
|
1385
1813
|
);
|
|
1386
1814
|
const autoSurface = useAutoSurface(surface === "auto");
|
|
1387
1815
|
const resolvedSurface = surface === "auto" ? autoSurface : surface;
|
|
1388
|
-
const
|
|
1389
|
-
|
|
1816
|
+
const appearance = useMemo3(
|
|
1817
|
+
() => resolveDocPlayerAppearance(doc, {
|
|
1818
|
+
theme,
|
|
1819
|
+
videoPresentation,
|
|
1820
|
+
pipSize,
|
|
1821
|
+
pipShape,
|
|
1822
|
+
pipPosition,
|
|
1823
|
+
showCoverSlide
|
|
1824
|
+
}),
|
|
1825
|
+
[doc, theme, videoPresentation, pipSize, pipShape, pipPosition, showCoverSlide]
|
|
1826
|
+
);
|
|
1827
|
+
const effectiveTheme = useMemo3(() => {
|
|
1828
|
+
const base = appearance.theme;
|
|
1390
1829
|
return resolvedSurface ? applySurface(base, resolvedSurface) : base;
|
|
1391
|
-
}, [theme, resolvedSurface]);
|
|
1830
|
+
}, [appearance.theme, resolvedSurface]);
|
|
1831
|
+
const resolvedPipStyle = useMemo3(() => pipStyleVars(effectiveTheme), [effectiveTheme]);
|
|
1832
|
+
const pipVars = resolvedPipStyle;
|
|
1833
|
+
const pipFrameStyle = useMemo3(
|
|
1834
|
+
() => ({
|
|
1835
|
+
border: resolvedPipStyle["--squisq-pip-border"],
|
|
1836
|
+
borderRadius: resolvedPipStyle["--squisq-pip-radius"],
|
|
1837
|
+
boxShadow: resolvedPipStyle["--squisq-pip-shadow"]
|
|
1838
|
+
}),
|
|
1839
|
+
[resolvedPipStyle]
|
|
1840
|
+
);
|
|
1392
1841
|
const {
|
|
1393
1842
|
currentBlock,
|
|
1394
1843
|
currentBlockIndex,
|
|
@@ -1405,11 +1854,15 @@ function DocPlayerContent({
|
|
|
1405
1854
|
} = useDocPlayback(doc, currentTime, {
|
|
1406
1855
|
viewport: activeViewport,
|
|
1407
1856
|
theme: effectiveTheme,
|
|
1408
|
-
onSeek: seekTo
|
|
1857
|
+
onSeek: seekTo,
|
|
1858
|
+
// A synthetic track is only the timer used by an unnarrated preview.
|
|
1859
|
+
// Narration pacing may compact short visual beats, but it must never
|
|
1860
|
+
// remove authored slides from the default loss-averse projection.
|
|
1861
|
+
useAudioSegmentTiming: audioMode !== "synthetic"
|
|
1409
1862
|
});
|
|
1410
|
-
const coverBlock =
|
|
1863
|
+
const coverBlock = useMemo3(() => {
|
|
1411
1864
|
const startBlockConfig = doc.startBlock;
|
|
1412
|
-
if (!showCoverSlide) return null;
|
|
1865
|
+
if (!appearance.showCoverSlide) return null;
|
|
1413
1866
|
if (!startBlockConfig) return null;
|
|
1414
1867
|
const context = createTemplateContext(effectiveTheme, 0, 1, activeViewport);
|
|
1415
1868
|
const layers = expandCoverBlock(startBlockConfig, context);
|
|
@@ -1422,15 +1875,15 @@ function DocPlayerContent({
|
|
|
1422
1875
|
audioSegment: -1,
|
|
1423
1876
|
layers
|
|
1424
1877
|
};
|
|
1425
|
-
}, [doc.startBlock, activeViewport, effectiveTheme, showCoverSlide]);
|
|
1878
|
+
}, [doc.startBlock, activeViewport, effectiveTheme, appearance.showCoverSlide]);
|
|
1426
1879
|
const hasManagedCover = !!coverBlock;
|
|
1427
|
-
const [slideshowCoverVisible, setSlideshowCoverVisible] =
|
|
1428
|
-
const [isSlideshowPickerOpen, setIsSlideshowPickerOpen] =
|
|
1880
|
+
const [slideshowCoverVisible, setSlideshowCoverVisible] = useState5(false);
|
|
1881
|
+
const [isSlideshowPickerOpen, setIsSlideshowPickerOpen] = useState5(false);
|
|
1429
1882
|
const slideshowCoverInitKeyRef = useRef5("");
|
|
1430
|
-
|
|
1883
|
+
useEffect5(() => {
|
|
1431
1884
|
slideshowCoverInitKeyRef.current = "";
|
|
1432
1885
|
}, [doc]);
|
|
1433
|
-
|
|
1886
|
+
useEffect5(() => {
|
|
1434
1887
|
const initKey = `${isSlideshowMode}:${hasManagedCover}:${renderMode}`;
|
|
1435
1888
|
if (slideshowCoverInitKeyRef.current === initKey) return;
|
|
1436
1889
|
slideshowCoverInitKeyRef.current = initKey;
|
|
@@ -1441,12 +1894,12 @@ function DocPlayerContent({
|
|
|
1441
1894
|
setSlideshowCoverVisible(false);
|
|
1442
1895
|
}
|
|
1443
1896
|
}, [isSlideshowMode, hasManagedCover, renderMode, pause]);
|
|
1444
|
-
const [coverForced, setCoverForced] =
|
|
1445
|
-
const [coverGraceActive, setCoverGraceActive] =
|
|
1897
|
+
const [coverForced, setCoverForced] = useState5(false);
|
|
1898
|
+
const [coverGraceActive, setCoverGraceActive] = useState5(false);
|
|
1446
1899
|
const coverGraceTimer = useRef5();
|
|
1447
1900
|
const coverWasShowing = useRef5(false);
|
|
1448
1901
|
const hasPlayedOnce = useRef5(false);
|
|
1449
|
-
|
|
1902
|
+
useEffect5(() => {
|
|
1450
1903
|
hasPlayedOnce.current = false;
|
|
1451
1904
|
coverWasShowing.current = false;
|
|
1452
1905
|
clearTimeout(coverGraceTimer.current);
|
|
@@ -1455,7 +1908,7 @@ function DocPlayerContent({
|
|
|
1455
1908
|
}, [doc]);
|
|
1456
1909
|
const atRest = !!(coverBlock && !isSlideshowMode && !isPlaying && currentTime === 0 && !hasPlayedOnce.current && !renderMode && !autoPlay);
|
|
1457
1910
|
if (atRest) coverWasShowing.current = true;
|
|
1458
|
-
|
|
1911
|
+
useEffect5(() => {
|
|
1459
1912
|
if (isPlaying && coverWasShowing.current && coverBlock && !renderMode && !isSlideshowMode) {
|
|
1460
1913
|
coverWasShowing.current = false;
|
|
1461
1914
|
hasPlayedOnce.current = true;
|
|
@@ -1463,8 +1916,8 @@ function DocPlayerContent({
|
|
|
1463
1916
|
coverGraceTimer.current = setTimeout(() => setCoverGraceActive(false), 3e3);
|
|
1464
1917
|
}
|
|
1465
1918
|
}, [isPlaying, coverBlock, renderMode, isSlideshowMode]);
|
|
1466
|
-
|
|
1467
|
-
|
|
1919
|
+
useEffect5(() => () => clearTimeout(coverGraceTimer.current), []);
|
|
1920
|
+
useEffect5(() => () => clearTimeout(tapFeedbackTimer.current), []);
|
|
1468
1921
|
const showVideoCoverBlock = !isSlideshowMode && !isLinearMode && !!coverBlock && (coverForced || coverGraceActive || !isPlaying && currentTime === 0 && !hasPlayedOnce.current && !renderMode && !autoPlay);
|
|
1469
1922
|
const effectiveSlideshowCoverVisible = coverVisible ?? slideshowCoverVisible;
|
|
1470
1923
|
const showSlideshowCover = !!(isSlideshowMode && !isLinearMode && !renderMode && coverBlock && effectiveSlideshowCoverVisible);
|
|
@@ -1473,23 +1926,26 @@ function DocPlayerContent({
|
|
|
1473
1926
|
const slideshowSlideIndex = slideshowHasCover ? effectiveSlideshowCoverVisible ? 0 : currentBlockIndex + 1 : currentBlockIndex;
|
|
1474
1927
|
const slideshowTotalSlides = slideshowHasCover ? expandedBlocks.length + 1 : expandedBlocks.length;
|
|
1475
1928
|
const hasAutoPlayed = useRef5(false);
|
|
1476
|
-
|
|
1929
|
+
useEffect5(() => {
|
|
1477
1930
|
hasAutoPlayed.current = false;
|
|
1478
1931
|
}, [doc]);
|
|
1479
|
-
|
|
1932
|
+
useEffect5(() => {
|
|
1480
1933
|
if (isAudioReady && autoPlay && !hasAutoPlayed.current) {
|
|
1481
1934
|
hasAutoPlayed.current = true;
|
|
1482
1935
|
play();
|
|
1483
1936
|
}
|
|
1484
1937
|
}, [isAudioReady, autoPlay, play]);
|
|
1485
|
-
|
|
1938
|
+
useEffect5(() => {
|
|
1486
1939
|
onTimeUpdate?.(currentTime);
|
|
1487
1940
|
}, [currentTime, onTimeUpdate]);
|
|
1488
|
-
|
|
1941
|
+
useEffect5(() => {
|
|
1489
1942
|
if (isEnded) {
|
|
1490
1943
|
onEnded?.();
|
|
1944
|
+
if (loop && !isSlideshowMode && !isLinearMode) {
|
|
1945
|
+
void restart();
|
|
1946
|
+
}
|
|
1491
1947
|
}
|
|
1492
|
-
}, [isEnded, onEnded]);
|
|
1948
|
+
}, [isEnded, isLinearMode, isSlideshowMode, loop, onEnded, restart]);
|
|
1493
1949
|
const liveRenderAPIRef = useRef5(null);
|
|
1494
1950
|
const stableRenderAPIRef = useRef5(null);
|
|
1495
1951
|
if (!stableRenderAPIRef.current) {
|
|
@@ -1500,6 +1956,7 @@ function DocPlayerContent({
|
|
|
1500
1956
|
};
|
|
1501
1957
|
stableRenderAPIRef.current = {
|
|
1502
1958
|
seekTo: (time) => current().seekTo(time),
|
|
1959
|
+
getRenderedTime: () => current().getRenderedTime(),
|
|
1503
1960
|
getDuration: () => current().getDuration(),
|
|
1504
1961
|
getBlocks: () => current().getBlocks(),
|
|
1505
1962
|
getAudioSegments: () => current().getAudioSegments(),
|
|
@@ -1511,7 +1968,7 @@ function DocPlayerContent({
|
|
|
1511
1968
|
};
|
|
1512
1969
|
}
|
|
1513
1970
|
const stableRenderAPI = stableRenderAPIRef.current;
|
|
1514
|
-
|
|
1971
|
+
useEffect5(() => {
|
|
1515
1972
|
if (!renderMode && !isDebugMode) {
|
|
1516
1973
|
liveRenderAPIRef.current = null;
|
|
1517
1974
|
return;
|
|
@@ -1521,77 +1978,58 @@ function DocPlayerContent({
|
|
|
1521
1978
|
liveRenderAPIRef.current = null;
|
|
1522
1979
|
return;
|
|
1523
1980
|
}
|
|
1524
|
-
const renderSeekTo = (time) => {
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
} else {
|
|
1563
|
-
video.addEventListener("seeked", () => r(), { once: true });
|
|
1564
|
-
setTimeout(r, 200);
|
|
1565
|
-
}
|
|
1566
|
-
})
|
|
1567
|
-
);
|
|
1568
|
-
});
|
|
1569
|
-
}
|
|
1570
|
-
root.querySelectorAll("video[data-clip-id]").forEach((el) => {
|
|
1571
|
-
const video = el;
|
|
1572
|
-
const absStart = parseFloat(video.dataset.absStart || "0");
|
|
1573
|
-
const absEnd = parseFloat(video.dataset.absEnd || "0");
|
|
1574
|
-
const sourceIn = parseFloat(video.dataset.sourceIn || "0");
|
|
1575
|
-
video.pause();
|
|
1576
|
-
if (time < absStart || time >= absEnd) return;
|
|
1577
|
-
const targetTime = sourceIn + (time - absStart);
|
|
1578
|
-
video.currentTime = targetTime;
|
|
1579
|
-
videoSeekPromises.push(
|
|
1580
|
-
new Promise((r) => {
|
|
1581
|
-
if (Math.abs(video.currentTime - targetTime) < 0.1) {
|
|
1582
|
-
r();
|
|
1583
|
-
} else {
|
|
1584
|
-
video.addEventListener("seeked", () => r(), { once: true });
|
|
1585
|
-
setTimeout(r, 200);
|
|
1586
|
-
}
|
|
1587
|
-
})
|
|
1588
|
-
);
|
|
1589
|
-
});
|
|
1590
|
-
Promise.all(videoSeekPromises).then(() => {
|
|
1591
|
-
void waitForVisualUpdate().then(resolve);
|
|
1592
|
-
});
|
|
1981
|
+
const renderSeekTo = async (time) => {
|
|
1982
|
+
if (renderMode) {
|
|
1983
|
+
const committed = waitForRenderCommit(time);
|
|
1984
|
+
flushSync(() => setRenderClock({ doc, time }));
|
|
1985
|
+
if (externalAudioController) void seekTo(time).catch(() => void 0);
|
|
1986
|
+
await committed;
|
|
1987
|
+
} else {
|
|
1988
|
+
await seekTo(time);
|
|
1989
|
+
}
|
|
1990
|
+
let blockStartTime = 0;
|
|
1991
|
+
for (let i = expandedBlocks.length - 1; i >= 0; i--) {
|
|
1992
|
+
if (time >= expandedBlocks[i].startTime) {
|
|
1993
|
+
blockStartTime = expandedBlocks[i].startTime;
|
|
1994
|
+
break;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
const elapsedMs = (time - blockStartTime) * 1e3;
|
|
1998
|
+
(root.getAnimations?.() ?? []).forEach((anim) => {
|
|
1999
|
+
const target = anim.effect?.target;
|
|
2000
|
+
if (!target) return;
|
|
2001
|
+
if (target.closest(".doc-player__block--active")) {
|
|
2002
|
+
anim.currentTime = Math.max(0, elapsedMs);
|
|
2003
|
+
} else if (target.closest(".doc-player__block--previous")) {
|
|
2004
|
+
anim.currentTime = Math.max(0, elapsedMs);
|
|
2005
|
+
}
|
|
2006
|
+
});
|
|
2007
|
+
const blockElapsed = time - blockStartTime;
|
|
2008
|
+
const videoSeekPromises = [];
|
|
2009
|
+
const activeBlockEl = root.querySelector(".doc-player__block--active");
|
|
2010
|
+
if (activeBlockEl) {
|
|
2011
|
+
const videos = activeBlockEl.querySelectorAll("video[data-clip-start]");
|
|
2012
|
+
videos.forEach((el) => {
|
|
2013
|
+
const video = el;
|
|
2014
|
+
const clipStart = parseFloat(video.dataset.clipStart || "0");
|
|
2015
|
+
const clipEnd = parseFloat(video.dataset.clipEnd || "0");
|
|
2016
|
+
const startAt = parseFloat(video.dataset.startAt || "0");
|
|
2017
|
+
const targetTime = Math.min(clipStart + Math.max(0, blockElapsed - startAt), clipEnd);
|
|
2018
|
+
videoSeekPromises.push(seekVideoToFrame(video, targetTime));
|
|
1593
2019
|
});
|
|
2020
|
+
}
|
|
2021
|
+
root.querySelectorAll("video[data-clip-id]").forEach((el) => {
|
|
2022
|
+
const video = el;
|
|
2023
|
+
const absStart = parseFloat(video.dataset.absStart || "0");
|
|
2024
|
+
const absEnd = parseFloat(video.dataset.absEnd || "0");
|
|
2025
|
+
const sourceIn = parseFloat(video.dataset.sourceIn || "0");
|
|
2026
|
+
video.pause();
|
|
2027
|
+
if (time < absStart || time >= absEnd) return;
|
|
2028
|
+
const targetTime = sourceIn + (time - absStart);
|
|
2029
|
+
videoSeekPromises.push(seekVideoToFrame(video, targetTime));
|
|
1594
2030
|
});
|
|
2031
|
+
await Promise.all(videoSeekPromises);
|
|
2032
|
+
await waitForVisualUpdate();
|
|
1595
2033
|
};
|
|
1596
2034
|
const getDuration = () => {
|
|
1597
2035
|
const mediaDuration = getDocPlaybackDuration(doc);
|
|
@@ -1634,6 +2072,7 @@ function DocPlayerContent({
|
|
|
1634
2072
|
const hasCoverBlock = () => !!coverBlock;
|
|
1635
2073
|
const api = {
|
|
1636
2074
|
seekTo: renderSeekTo,
|
|
2075
|
+
getRenderedTime: () => committedRenderTimeRef.current,
|
|
1637
2076
|
getDuration,
|
|
1638
2077
|
getBlocks,
|
|
1639
2078
|
getAudioSegments,
|
|
@@ -1647,8 +2086,18 @@ function DocPlayerContent({
|
|
|
1647
2086
|
return () => {
|
|
1648
2087
|
if (liveRenderAPIRef.current === api) liveRenderAPIRef.current = null;
|
|
1649
2088
|
};
|
|
1650
|
-
}, [
|
|
1651
|
-
|
|
2089
|
+
}, [
|
|
2090
|
+
renderMode,
|
|
2091
|
+
isDebugMode,
|
|
2092
|
+
seekTo,
|
|
2093
|
+
totalDuration,
|
|
2094
|
+
expandedBlocks,
|
|
2095
|
+
coverBlock,
|
|
2096
|
+
doc,
|
|
2097
|
+
externalAudioController,
|
|
2098
|
+
waitForRenderCommit
|
|
2099
|
+
]);
|
|
2100
|
+
useEffect5(() => {
|
|
1652
2101
|
if (!renderMode && !isDebugMode || !containerRef.current) {
|
|
1653
2102
|
onRenderAPIReady?.(null);
|
|
1654
2103
|
return;
|
|
@@ -1657,8 +2106,8 @@ function DocPlayerContent({
|
|
|
1657
2106
|
return () => onRenderAPIReady?.(null);
|
|
1658
2107
|
}, [renderMode, isDebugMode, onRenderAPIReady, stableRenderAPI]);
|
|
1659
2108
|
const defaultMode = captionsEnabledProp === false ? "off" : captionStyle || "standard";
|
|
1660
|
-
const [captionMode, setCaptionMode] =
|
|
1661
|
-
|
|
2109
|
+
const [captionMode, setCaptionMode] = useState5(defaultMode);
|
|
2110
|
+
useEffect5(() => {
|
|
1662
2111
|
setCaptionMode(defaultMode);
|
|
1663
2112
|
}, [defaultMode]);
|
|
1664
2113
|
const captionsEnabled = captionMode !== "off";
|
|
@@ -1678,8 +2127,8 @@ function DocPlayerContent({
|
|
|
1678
2127
|
});
|
|
1679
2128
|
}, [onCaptionsToggle]);
|
|
1680
2129
|
const hasCaptions = doc.captions && doc.captions.phrases.length > 0;
|
|
1681
|
-
const segmentTitleMap =
|
|
1682
|
-
const playbackState =
|
|
2130
|
+
const segmentTitleMap = useMemo3(() => buildSegmentTitleMap(doc), [doc]);
|
|
2131
|
+
const playbackState = useMemo3(
|
|
1683
2132
|
() => ({
|
|
1684
2133
|
isPlaying,
|
|
1685
2134
|
currentTime,
|
|
@@ -1722,7 +2171,7 @@ function DocPlayerContent({
|
|
|
1722
2171
|
expandedBlocks.length
|
|
1723
2172
|
]
|
|
1724
2173
|
);
|
|
1725
|
-
const playbackActions =
|
|
2174
|
+
const playbackActions = useMemo3(
|
|
1726
2175
|
() => ({
|
|
1727
2176
|
toggle,
|
|
1728
2177
|
restart,
|
|
@@ -1733,7 +2182,7 @@ function DocPlayerContent({
|
|
|
1733
2182
|
}),
|
|
1734
2183
|
[toggle, restart, seekTo, setCaptionsEnabled, cycleCaptionMode, onFullscreenToggle]
|
|
1735
2184
|
);
|
|
1736
|
-
const slideNavActions =
|
|
2185
|
+
const slideNavActions = useMemo3(
|
|
1737
2186
|
() => ({
|
|
1738
2187
|
nextSlide: () => {
|
|
1739
2188
|
if (slideshowHasCover && slideshowCoverVisible) {
|
|
@@ -1822,10 +2271,10 @@ function DocPlayerContent({
|
|
|
1822
2271
|
onNext: handleSwipeNext,
|
|
1823
2272
|
onPrev: handleSwipePrev
|
|
1824
2273
|
});
|
|
1825
|
-
|
|
2274
|
+
useEffect5(() => {
|
|
1826
2275
|
onPlaybackStateChange?.(playbackState);
|
|
1827
2276
|
}, [playbackState, onPlaybackStateChange]);
|
|
1828
|
-
|
|
2277
|
+
useEffect5(() => {
|
|
1829
2278
|
onControlsReady?.({ play, pause, ...playbackActions });
|
|
1830
2279
|
}, [play, pause, playbackActions, onControlsReady]);
|
|
1831
2280
|
const getBlockTitle = useCallback4((block) => {
|
|
@@ -1852,7 +2301,7 @@ function DocPlayerContent({
|
|
|
1852
2301
|
}
|
|
1853
2302
|
return block.id.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
1854
2303
|
}, []);
|
|
1855
|
-
const slideshowPickerItems =
|
|
2304
|
+
const slideshowPickerItems = useMemo3(() => {
|
|
1856
2305
|
const blockItems = expandedBlocks.map((block, index) => ({
|
|
1857
2306
|
id: block.id,
|
|
1858
2307
|
label: String(index + 1),
|
|
@@ -1868,7 +2317,7 @@ function DocPlayerContent({
|
|
|
1868
2317
|
...blockItems
|
|
1869
2318
|
];
|
|
1870
2319
|
}, [coverBlock, expandedBlocks, getBlockTitle, slideshowHasCover]);
|
|
1871
|
-
const blockMarkers =
|
|
2320
|
+
const blockMarkers = useMemo3(() => {
|
|
1872
2321
|
if (!totalDuration || !expandedBlocks.length) return [];
|
|
1873
2322
|
let prevSegment = -1;
|
|
1874
2323
|
return expandedBlocks.map((block, index) => {
|
|
@@ -1883,7 +2332,7 @@ function DocPlayerContent({
|
|
|
1883
2332
|
};
|
|
1884
2333
|
});
|
|
1885
2334
|
}, [expandedBlocks, totalDuration, getBlockTitle]);
|
|
1886
|
-
|
|
2335
|
+
useEffect5(() => {
|
|
1887
2336
|
if (blockMarkers.length > 0) {
|
|
1888
2337
|
onBlockMarkers?.(blockMarkers);
|
|
1889
2338
|
}
|
|
@@ -1960,7 +2409,7 @@ function DocPlayerContent({
|
|
|
1960
2409
|
(e) => handleKeyboardShortcut(e, false),
|
|
1961
2410
|
[handleKeyboardShortcut]
|
|
1962
2411
|
);
|
|
1963
|
-
|
|
2412
|
+
useEffect5(() => {
|
|
1964
2413
|
if (!globalKeyboardShortcuts || renderMode || isLinearMode) return;
|
|
1965
2414
|
const handleDocumentKeyDown = (event) => {
|
|
1966
2415
|
handleKeyboardShortcut(event, true);
|
|
@@ -2000,6 +2449,9 @@ function DocPlayerContent({
|
|
|
2000
2449
|
{
|
|
2001
2450
|
ref: containerRef,
|
|
2002
2451
|
"data-player-id": playerId,
|
|
2452
|
+
"data-orientation": activeOrientation,
|
|
2453
|
+
"data-playback-state": isPlaying ? "playing" : "paused",
|
|
2454
|
+
"data-swipe-phase": swipe.phase,
|
|
2003
2455
|
tabIndex: renderMode ? -1 : 0,
|
|
2004
2456
|
"aria-label": "Document player",
|
|
2005
2457
|
onKeyDown: renderMode ? void 0 : handleKeyDown,
|
|
@@ -2007,6 +2459,7 @@ function DocPlayerContent({
|
|
|
2007
2459
|
onClick: handleContainerClick,
|
|
2008
2460
|
onPointerDown: swipe.onPointerDown,
|
|
2009
2461
|
style: {
|
|
2462
|
+
...pipVars,
|
|
2010
2463
|
position: "relative",
|
|
2011
2464
|
width: "100%",
|
|
2012
2465
|
aspectRatio: `${activeViewport.width} / ${activeViewport.height}`,
|
|
@@ -2027,7 +2480,13 @@ function DocPlayerContent({
|
|
|
2027
2480
|
isPlaying,
|
|
2028
2481
|
basePath,
|
|
2029
2482
|
renderMode,
|
|
2030
|
-
muted
|
|
2483
|
+
muted,
|
|
2484
|
+
presentation: appearance.videoPresentation,
|
|
2485
|
+
pipSize: appearance.pipSize,
|
|
2486
|
+
pipShape: appearance.pipShape,
|
|
2487
|
+
pipPosition: appearance.pipPosition,
|
|
2488
|
+
pipOrientation: activeOrientation,
|
|
2489
|
+
pipFrameStyle
|
|
2031
2490
|
}
|
|
2032
2491
|
),
|
|
2033
2492
|
/* @__PURE__ */ jsxs4("div", { className: "doc-player__viewport", children: [
|
|
@@ -2179,7 +2638,7 @@ function DocPlayerContent({
|
|
|
2179
2638
|
" ",
|
|
2180
2639
|
/* @__PURE__ */ jsxs4("span", { style: { color: "#666" }, children: [
|
|
2181
2640
|
"(",
|
|
2182
|
-
|
|
2641
|
+
activeOrientation,
|
|
2183
2642
|
")"
|
|
2184
2643
|
] })
|
|
2185
2644
|
] }),
|
|
@@ -2192,7 +2651,7 @@ function DocPlayerContent({
|
|
|
2192
2651
|
hasCaptions && (() => {
|
|
2193
2652
|
const debugPhrase = getCaptionAtTime2(doc.captions, currentTime);
|
|
2194
2653
|
const debugEnabled = captionsEnabled && (isPlaying || currentTime > 0);
|
|
2195
|
-
return /* @__PURE__ */ jsxs4(
|
|
2654
|
+
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
2196
2655
|
/* @__PURE__ */ jsxs4("div", { children: [
|
|
2197
2656
|
/* @__PURE__ */ jsx7("span", { style: { color: "#888" }, children: "captions:" }),
|
|
2198
2657
|
" ",
|
|
@@ -2445,7 +2904,7 @@ function DocControlsSidebar({ state, actions }) {
|
|
|
2445
2904
|
}
|
|
2446
2905
|
|
|
2447
2906
|
// src/DocPlayerWithSidebar.tsx
|
|
2448
|
-
import { useRef as useRef6, useState as
|
|
2907
|
+
import { useRef as useRef6, useState as useState6, useCallback as useCallback5, useEffect as useEffect6 } from "react";
|
|
2449
2908
|
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2450
2909
|
var DEFAULT_STATE = {
|
|
2451
2910
|
isPlaying: false,
|
|
@@ -2465,6 +2924,7 @@ function DocPlayerWithSidebar({
|
|
|
2465
2924
|
doc,
|
|
2466
2925
|
basePath,
|
|
2467
2926
|
autoPlay = false,
|
|
2927
|
+
loop = false,
|
|
2468
2928
|
onEnded,
|
|
2469
2929
|
onTimeUpdate,
|
|
2470
2930
|
audioController,
|
|
@@ -2480,7 +2940,7 @@ function DocPlayerWithSidebar({
|
|
|
2480
2940
|
const stateRef = useRef6(DEFAULT_STATE);
|
|
2481
2941
|
const actionsRef = useRef6(null);
|
|
2482
2942
|
const wasPlayingRef = useRef6(false);
|
|
2483
|
-
const [, setTick] =
|
|
2943
|
+
const [, setTick] = useState6(0);
|
|
2484
2944
|
const handleStateChange = useCallback5(
|
|
2485
2945
|
(state) => {
|
|
2486
2946
|
stateRef.current = state;
|
|
@@ -2500,7 +2960,7 @@ function DocPlayerWithSidebar({
|
|
|
2500
2960
|
},
|
|
2501
2961
|
[]
|
|
2502
2962
|
);
|
|
2503
|
-
|
|
2963
|
+
useEffect6(() => {
|
|
2504
2964
|
const interval = setInterval(() => {
|
|
2505
2965
|
if (!stateRef.current.isPlaying) return;
|
|
2506
2966
|
setTick((t) => t + 1);
|
|
@@ -2515,6 +2975,7 @@ function DocPlayerWithSidebar({
|
|
|
2515
2975
|
theme,
|
|
2516
2976
|
basePath,
|
|
2517
2977
|
autoPlay,
|
|
2978
|
+
loop,
|
|
2518
2979
|
onEnded,
|
|
2519
2980
|
onTimeUpdate,
|
|
2520
2981
|
audioController,
|
|
@@ -2536,12 +2997,14 @@ function DocPlayerWithSidebar({
|
|
|
2536
2997
|
|
|
2537
2998
|
export {
|
|
2538
2999
|
MediaClipLayer,
|
|
3000
|
+
useMediaClipDurations,
|
|
2539
3001
|
SocialCaptionOverlay,
|
|
2540
3002
|
CaptionOverlay,
|
|
2541
3003
|
formatTime,
|
|
2542
3004
|
DocProgressBar,
|
|
2543
3005
|
DocControlsOverlay,
|
|
2544
3006
|
DocControlsSlideshow,
|
|
3007
|
+
resolveDocPlayerAppearance,
|
|
2545
3008
|
DocPlayer,
|
|
2546
3009
|
DocControlsBottom,
|
|
2547
3010
|
DocControlsSidebar,
|