@bendyline/squisq-react 2.11.7 → 2.11.9
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 +19 -18
- package/dist/{chunk-ZY5N3JPM.js → chunk-6PUMLLJH.js} +281 -68
- package/dist/{chunk-ITQ4K5N6.js → chunk-KDTKUE53.js} +1 -1
- package/dist/{chunk-5UGIE2QO.js → chunk-LDXH4FOY.js} +2 -2
- package/dist/{chunk-7QA6EW6Y.js → chunk-SESRQ7XH.js} +1 -1
- package/dist/{chunk-XSOMFBH2.js → chunk-W6MKRWL6.js} +250 -2
- package/dist/{chunk-KGWT454C.js → chunk-Y2ANJRT3.js} +155 -266
- package/dist/index.d.ts +36 -4
- package/dist/index.js +10 -6
- package/dist/json-view/index.js +2 -2
- package/dist/layers/index.js +1 -1
- package/dist/markdown/index.js +1 -1
- package/dist/page/index.js +4 -4
- package/dist/player/index.d.ts +1 -1
- package/dist/player/index.js +4 -4
- package/dist/{player-BlD8zREs.d.ts → player-5j10MhBq.d.ts} +24 -2
- package/dist/squisq-player.full.global.js +437 -437
- package/dist/squisq-player.global.js +66 -66
- package/dist/standalone-source.js +1 -1
- package/dist/styles/index.css +30 -0
- package/package.json +2 -2
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BlockRenderer,
|
|
3
3
|
LinearDocView
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-LDXH4FOY.js";
|
|
5
|
+
import {
|
|
6
|
+
isCaptureDrivenVideo,
|
|
7
|
+
seekVideoToFrame
|
|
8
|
+
} from "./chunk-W6MKRWL6.js";
|
|
5
9
|
import {
|
|
6
10
|
useAudioSync,
|
|
7
11
|
useDocPlayback,
|
|
@@ -13,7 +17,7 @@ import {
|
|
|
13
17
|
} from "./chunk-TT6ENR6T.js";
|
|
14
18
|
import {
|
|
15
19
|
MarkdownRenderer
|
|
16
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-6PUMLLJH.js";
|
|
17
21
|
import {
|
|
18
22
|
useMediaProvider,
|
|
19
23
|
useMediaUrl,
|
|
@@ -22,8 +26,74 @@ import {
|
|
|
22
26
|
|
|
23
27
|
// src/MediaClipLayer.tsx
|
|
24
28
|
import { useEffect, useRef } from "react";
|
|
29
|
+
import { serializeMediaCrop } from "@bendyline/squisq/mediaEdit";
|
|
30
|
+
|
|
31
|
+
// src/mediaClipPlayback.ts
|
|
32
|
+
function clipLevelAt(clip, time) {
|
|
33
|
+
let level = clip.gain ? Math.pow(10, clip.gain / 20) : 1;
|
|
34
|
+
if (clip.fadeIn && time < clip.absoluteStart + clip.fadeIn) {
|
|
35
|
+
level *= Math.max(0, (time - clip.absoluteStart) / clip.fadeIn);
|
|
36
|
+
}
|
|
37
|
+
if (clip.fadeOut && time > clip.absoluteEnd - clip.fadeOut) {
|
|
38
|
+
level *= Math.max(0, (clip.absoluteEnd - time) / clip.fadeOut);
|
|
39
|
+
}
|
|
40
|
+
return level;
|
|
41
|
+
}
|
|
42
|
+
function mountedClips(clips, time) {
|
|
43
|
+
const out = [];
|
|
44
|
+
const groups = /* @__PURE__ */ new Map();
|
|
45
|
+
for (const clip of clips) {
|
|
46
|
+
if (!clip.segment) {
|
|
47
|
+
out.push({ key: clip.id, clip });
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const group = groups.get(clip.segment.groupId) ?? [];
|
|
51
|
+
group.push(clip);
|
|
52
|
+
groups.set(clip.segment.groupId, group);
|
|
53
|
+
}
|
|
54
|
+
for (const [groupId, segments] of groups) {
|
|
55
|
+
segments.sort((a, b) => (a.segment?.index ?? 0) - (b.segment?.index ?? 0));
|
|
56
|
+
let current = segments.findIndex((s) => time < s.absoluteEnd);
|
|
57
|
+
if (current === -1) current = segments.length - 1;
|
|
58
|
+
for (const index of [current, current + 1]) {
|
|
59
|
+
const clip = segments[index];
|
|
60
|
+
if (clip) out.push({ key: `${groupId}#slot${index % 2}`, clip });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/MediaClipLayer.tsx
|
|
25
67
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
26
68
|
var DRIFT = 0.25;
|
|
69
|
+
var PRESEEK = 0.75;
|
|
70
|
+
var boostContext = null;
|
|
71
|
+
var boostNodes = /* @__PURE__ */ new WeakMap();
|
|
72
|
+
function boostNodeFor(el) {
|
|
73
|
+
const existing = boostNodes.get(el);
|
|
74
|
+
if (existing) return existing;
|
|
75
|
+
if (typeof AudioContext === "undefined") return null;
|
|
76
|
+
try {
|
|
77
|
+
boostContext ?? (boostContext = new AudioContext());
|
|
78
|
+
const gain = boostContext.createGain();
|
|
79
|
+
boostContext.createMediaElementSource(el).connect(gain).connect(boostContext.destination);
|
|
80
|
+
boostNodes.set(el, gain);
|
|
81
|
+
return gain;
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function applyLevel(el, level) {
|
|
87
|
+
const boost = level > 1 ? boostNodeFor(el) : boostNodes.get(el);
|
|
88
|
+
if (boost) {
|
|
89
|
+
el.volume = 1;
|
|
90
|
+
boost.gain.value = level;
|
|
91
|
+
if (boostContext?.state === "suspended") void boostContext.resume().catch(() => {
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
el.volume = Math.min(1, Math.max(0, level));
|
|
96
|
+
}
|
|
27
97
|
function mediaGroupStyle(presentation) {
|
|
28
98
|
return {
|
|
29
99
|
position: "absolute",
|
|
@@ -76,6 +146,12 @@ function mediaVideoStyle({
|
|
|
76
146
|
...pipCornerStyle(pipPosition)
|
|
77
147
|
};
|
|
78
148
|
}
|
|
149
|
+
function cropStyle(crop) {
|
|
150
|
+
if (!crop) return {};
|
|
151
|
+
const pct = (n) => `${(n * 100).toFixed(3)}%`;
|
|
152
|
+
const inset = `inset(${pct(crop.y)} ${pct(1 - crop.x - crop.w)} ${pct(1 - crop.y - crop.h)} ${pct(crop.x)})`;
|
|
153
|
+
return { objectViewBox: inset };
|
|
154
|
+
}
|
|
79
155
|
function MediaClipLayer({
|
|
80
156
|
schedule,
|
|
81
157
|
currentTime,
|
|
@@ -93,8 +169,10 @@ function MediaClipLayer({
|
|
|
93
169
|
}) {
|
|
94
170
|
const { renderClips, activeIds } = useMediaSchedule(schedule, currentTime);
|
|
95
171
|
if (renderClips.length === 0) return null;
|
|
172
|
+
const mounted = mountedClips(renderClips, currentTime);
|
|
96
173
|
const groups = /* @__PURE__ */ new Map();
|
|
97
|
-
for (const
|
|
174
|
+
for (const entry of mounted) {
|
|
175
|
+
const { clip } = entry;
|
|
98
176
|
const clipPresentation = honorClipPresentation && clip.kind === "video" ? clip.placement === "picture-in-picture" ? "picture-in-picture" : clip.placement === "overlay" ? "full-frame" : presentation : presentation;
|
|
99
177
|
const clipPipSize = clip.pipSize ?? pipSize;
|
|
100
178
|
const clipPipShape = clip.pipShape ?? pipShape;
|
|
@@ -107,7 +185,7 @@ function MediaClipLayer({
|
|
|
107
185
|
pipPosition: clipPipPosition,
|
|
108
186
|
clips: []
|
|
109
187
|
};
|
|
110
|
-
group.clips.push(
|
|
188
|
+
group.clips.push(entry);
|
|
111
189
|
groups.set(key, group);
|
|
112
190
|
}
|
|
113
191
|
return /* @__PURE__ */ jsx(Fragment, { children: [...groups].map(([key, group]) => /* @__PURE__ */ jsx(
|
|
@@ -120,7 +198,7 @@ function MediaClipLayer({
|
|
|
120
198
|
"data-pip-position": group.pipPosition,
|
|
121
199
|
"aria-hidden": true,
|
|
122
200
|
style: mediaGroupStyle(group.presentation),
|
|
123
|
-
children: group.clips.map((clip) => /* @__PURE__ */ jsx(
|
|
201
|
+
children: group.clips.map(({ key: elementKey, clip }) => /* @__PURE__ */ jsx(
|
|
124
202
|
MediaClipElement,
|
|
125
203
|
{
|
|
126
204
|
clip,
|
|
@@ -137,7 +215,7 @@ function MediaClipLayer({
|
|
|
137
215
|
pipOrientation,
|
|
138
216
|
pipFrameStyle
|
|
139
217
|
},
|
|
140
|
-
|
|
218
|
+
elementKey
|
|
141
219
|
))
|
|
142
220
|
},
|
|
143
221
|
key
|
|
@@ -160,15 +238,29 @@ function MediaClipElement({
|
|
|
160
238
|
}) {
|
|
161
239
|
const ref = useRef(null);
|
|
162
240
|
const src = useMediaUrl(clip.src, basePath);
|
|
241
|
+
const { sourceIn, absoluteStart, absoluteEnd, gain, fadeIn, fadeOut } = clip;
|
|
163
242
|
useEffect(() => {
|
|
164
243
|
const el = ref.current;
|
|
165
244
|
if (!el) return;
|
|
166
245
|
if (!active) {
|
|
167
246
|
if (!el.paused) el.pause();
|
|
247
|
+
const startsIn = absoluteStart - currentTime;
|
|
248
|
+
if (!renderMode && startsIn > 0 && startsIn <= PRESEEK && !isCaptureDrivenVideo(el) && Math.abs(el.currentTime - sourceIn) > DRIFT / 5) {
|
|
249
|
+
try {
|
|
250
|
+
el.currentTime = sourceIn;
|
|
251
|
+
} catch {
|
|
252
|
+
}
|
|
253
|
+
}
|
|
168
254
|
return;
|
|
169
255
|
}
|
|
170
|
-
|
|
171
|
-
|
|
256
|
+
if (!renderMode) {
|
|
257
|
+
applyLevel(
|
|
258
|
+
el,
|
|
259
|
+
clipLevelAt({ absoluteStart, absoluteEnd, gain, fadeIn, fadeOut }, currentTime)
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
const target = Math.max(0, sourceIn + (currentTime - absoluteStart));
|
|
263
|
+
if (!isCaptureDrivenVideo(el) && (renderMode || !isPlaying || Math.abs(el.currentTime - target) > DRIFT)) {
|
|
172
264
|
try {
|
|
173
265
|
el.currentTime = target;
|
|
174
266
|
} catch {
|
|
@@ -178,10 +270,22 @@ function MediaClipElement({
|
|
|
178
270
|
const p = el.play();
|
|
179
271
|
if (p) p.catch(() => {
|
|
180
272
|
});
|
|
181
|
-
} else if (el
|
|
273
|
+
} else if (!isCaptureDrivenVideo(el)) {
|
|
182
274
|
el.pause();
|
|
183
275
|
}
|
|
184
|
-
}, [
|
|
276
|
+
}, [
|
|
277
|
+
active,
|
|
278
|
+
currentTime,
|
|
279
|
+
isPlaying,
|
|
280
|
+
renderMode,
|
|
281
|
+
sourceIn,
|
|
282
|
+
absoluteStart,
|
|
283
|
+
absoluteEnd,
|
|
284
|
+
gain,
|
|
285
|
+
fadeIn,
|
|
286
|
+
fadeOut,
|
|
287
|
+
src
|
|
288
|
+
]);
|
|
185
289
|
const isVideo = clip.kind === "video";
|
|
186
290
|
const common = {
|
|
187
291
|
ref,
|
|
@@ -200,17 +304,21 @@ function MediaClipElement({
|
|
|
200
304
|
...common,
|
|
201
305
|
className: `doc-player__media-video${active ? " doc-player__media-video--active" : ""}`,
|
|
202
306
|
"data-video-placement": clip.placement ?? "default",
|
|
203
|
-
muted: renderMode || muted,
|
|
307
|
+
muted: renderMode || muted || clip.audioMuted === true,
|
|
204
308
|
playsInline: true,
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
309
|
+
...clip.crop ? { "data-crop": serializeMediaCrop(clip.crop) } : {},
|
|
310
|
+
style: {
|
|
311
|
+
...mediaVideoStyle({
|
|
312
|
+
presentation,
|
|
313
|
+
pipSize,
|
|
314
|
+
pipShape,
|
|
315
|
+
pipPosition,
|
|
316
|
+
pipOrientation,
|
|
317
|
+
pipFrameStyle,
|
|
318
|
+
active
|
|
319
|
+
}),
|
|
320
|
+
...cropStyle(clip.crop)
|
|
321
|
+
}
|
|
214
322
|
}
|
|
215
323
|
);
|
|
216
324
|
}
|
|
@@ -2349,247 +2457,6 @@ function buildSegmentTitleMap(doc) {
|
|
|
2349
2457
|
return map;
|
|
2350
2458
|
}
|
|
2351
2459
|
|
|
2352
|
-
// src/docPlayer/renderReadiness.ts
|
|
2353
|
-
var DEFAULT_VIDEO_FRAME_TIMEOUT_MS = 2e3;
|
|
2354
|
-
var VIDEO_FRAME_READINESS_POLL_MS = 16;
|
|
2355
|
-
var VIDEO_TIME_TOLERANCE_SECONDS = 0.01;
|
|
2356
|
-
var MAX_SEQUENTIAL_CAPTURE_STEP_SECONDS = 0.5;
|
|
2357
|
-
var SEQUENTIAL_CAPTURE_PLAYBACK_RATE = 1;
|
|
2358
|
-
function formatMediaTime(time) {
|
|
2359
|
-
return Number.isFinite(time) ? `${time.toFixed(3)}s` : String(time);
|
|
2360
|
-
}
|
|
2361
|
-
function reachableMediaTime(video, targetTime) {
|
|
2362
|
-
const duration = video.duration;
|
|
2363
|
-
if (!Number.isFinite(duration)) return targetTime;
|
|
2364
|
-
return Math.max(0, Math.min(targetTime, duration));
|
|
2365
|
-
}
|
|
2366
|
-
function isAtReachableMediaTime(video, targetTime) {
|
|
2367
|
-
return Math.abs(video.currentTime - reachableMediaTime(video, targetTime)) <= VIDEO_TIME_TOLERANCE_SECONDS;
|
|
2368
|
-
}
|
|
2369
|
-
function isVisiblyPresented(video) {
|
|
2370
|
-
if (!video.isConnected) return false;
|
|
2371
|
-
const view = video.ownerDocument.defaultView;
|
|
2372
|
-
if (!view) return false;
|
|
2373
|
-
for (let element = video; element; element = element.parentElement) {
|
|
2374
|
-
const style = view.getComputedStyle(element);
|
|
2375
|
-
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse" || Number.parseFloat(style.opacity || "1") <= 0) {
|
|
2376
|
-
return false;
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
return true;
|
|
2380
|
-
}
|
|
2381
|
-
function createCaptureWatchdog(ownerDocument, timeoutMs, expire) {
|
|
2382
|
-
let timeout = null;
|
|
2383
|
-
const clear = () => {
|
|
2384
|
-
if (timeout === null) return;
|
|
2385
|
-
clearTimeout(timeout);
|
|
2386
|
-
timeout = null;
|
|
2387
|
-
};
|
|
2388
|
-
return {
|
|
2389
|
-
arm: () => {
|
|
2390
|
-
clear();
|
|
2391
|
-
if (ownerDocument.visibilityState !== "visible") return;
|
|
2392
|
-
timeout = setTimeout(expire, timeoutMs);
|
|
2393
|
-
},
|
|
2394
|
-
clear
|
|
2395
|
-
};
|
|
2396
|
-
}
|
|
2397
|
-
function seekVideoByAssignment(video, targetTime, timeoutMs = DEFAULT_VIDEO_FRAME_TIMEOUT_MS) {
|
|
2398
|
-
video.pause();
|
|
2399
|
-
return new Promise((resolve, reject) => {
|
|
2400
|
-
let settled = false;
|
|
2401
|
-
let videoFrameRequest = null;
|
|
2402
|
-
const ownerDocument = video.ownerDocument;
|
|
2403
|
-
const isInactive = () => ownerDocument.visibilityState !== "visible";
|
|
2404
|
-
const cleanup = () => {
|
|
2405
|
-
watchdog.clear();
|
|
2406
|
-
clearInterval(readinessPoll);
|
|
2407
|
-
video.removeEventListener("seeked", handleMediaReady);
|
|
2408
|
-
video.removeEventListener("loadeddata", handleMediaReady);
|
|
2409
|
-
video.removeEventListener("canplay", handleMediaReady);
|
|
2410
|
-
ownerDocument.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
2411
|
-
if (videoFrameRequest !== null && typeof video.cancelVideoFrameCallback === "function") {
|
|
2412
|
-
video.cancelVideoFrameCallback(videoFrameRequest);
|
|
2413
|
-
}
|
|
2414
|
-
};
|
|
2415
|
-
const finish = () => {
|
|
2416
|
-
if (settled) return;
|
|
2417
|
-
settled = true;
|
|
2418
|
-
cleanup();
|
|
2419
|
-
resolve();
|
|
2420
|
-
};
|
|
2421
|
-
const fail = () => {
|
|
2422
|
-
if (settled) return;
|
|
2423
|
-
settled = true;
|
|
2424
|
-
cleanup();
|
|
2425
|
-
reject(
|
|
2426
|
-
new Error(
|
|
2427
|
-
`Video frame did not become ready at ${formatMediaTime(targetTime)} within ${timeoutMs}ms (currentTime=${formatMediaTime(video.currentTime)}, reachableTime=${formatMediaTime(reachableMediaTime(video, targetTime))}, duration=${formatMediaTime(video.duration)}, readyState=${video.readyState}, seeking=${String(video.seeking)}, visible=${String(isVisiblyPresented(video))}).`
|
|
2428
|
-
)
|
|
2429
|
-
);
|
|
2430
|
-
};
|
|
2431
|
-
const requestPresentedFrame = () => {
|
|
2432
|
-
if (settled || video.seeking || video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA || !isAtReachableMediaTime(video, targetTime)) {
|
|
2433
|
-
return;
|
|
2434
|
-
}
|
|
2435
|
-
if (typeof video.requestVideoFrameCallback !== "function" || !isVisiblyPresented(video)) {
|
|
2436
|
-
finish();
|
|
2437
|
-
return;
|
|
2438
|
-
}
|
|
2439
|
-
if (videoFrameRequest !== null) return;
|
|
2440
|
-
videoFrameRequest = video.requestVideoFrameCallback(() => {
|
|
2441
|
-
videoFrameRequest = null;
|
|
2442
|
-
finish();
|
|
2443
|
-
});
|
|
2444
|
-
};
|
|
2445
|
-
function handleMediaReady() {
|
|
2446
|
-
requestPresentedFrame();
|
|
2447
|
-
}
|
|
2448
|
-
const watchdog = createCaptureWatchdog(ownerDocument, timeoutMs, fail);
|
|
2449
|
-
const assignTargetTime = () => {
|
|
2450
|
-
try {
|
|
2451
|
-
video.currentTime = reachableMediaTime(video, targetTime);
|
|
2452
|
-
queueMicrotask(requestPresentedFrame);
|
|
2453
|
-
return true;
|
|
2454
|
-
} catch (error) {
|
|
2455
|
-
settled = true;
|
|
2456
|
-
cleanup();
|
|
2457
|
-
reject(error);
|
|
2458
|
-
return false;
|
|
2459
|
-
}
|
|
2460
|
-
};
|
|
2461
|
-
function handleVisibilityChange() {
|
|
2462
|
-
if (settled) return;
|
|
2463
|
-
if (isInactive()) {
|
|
2464
|
-
watchdog.clear();
|
|
2465
|
-
return;
|
|
2466
|
-
}
|
|
2467
|
-
requestPresentedFrame();
|
|
2468
|
-
if (settled) return;
|
|
2469
|
-
if (!video.seeking && !isAtReachableMediaTime(video, targetTime) && !assignTargetTime()) {
|
|
2470
|
-
return;
|
|
2471
|
-
}
|
|
2472
|
-
watchdog.arm();
|
|
2473
|
-
}
|
|
2474
|
-
const readinessPoll = setInterval(requestPresentedFrame, VIDEO_FRAME_READINESS_POLL_MS);
|
|
2475
|
-
video.addEventListener("seeked", handleMediaReady);
|
|
2476
|
-
video.addEventListener("loadeddata", handleMediaReady);
|
|
2477
|
-
video.addEventListener("canplay", handleMediaReady);
|
|
2478
|
-
ownerDocument.addEventListener("visibilitychange", handleVisibilityChange);
|
|
2479
|
-
watchdog.arm();
|
|
2480
|
-
assignTargetTime();
|
|
2481
|
-
});
|
|
2482
|
-
}
|
|
2483
|
-
function advanceSequentialCaptureVideo(video, targetTime, timeoutMs) {
|
|
2484
|
-
return new Promise((resolve, reject) => {
|
|
2485
|
-
let settled = false;
|
|
2486
|
-
const ownerDocument = video.ownerDocument;
|
|
2487
|
-
const isInactive = () => ownerDocument.visibilityState !== "visible";
|
|
2488
|
-
const cleanup = () => {
|
|
2489
|
-
watchdog.clear();
|
|
2490
|
-
clearInterval(readinessPoll);
|
|
2491
|
-
video.removeEventListener("timeupdate", check);
|
|
2492
|
-
video.removeEventListener("loadeddata", check);
|
|
2493
|
-
video.removeEventListener("canplay", check);
|
|
2494
|
-
video.removeEventListener("error", fail);
|
|
2495
|
-
ownerDocument.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
2496
|
-
};
|
|
2497
|
-
const finish = () => {
|
|
2498
|
-
if (!settled) {
|
|
2499
|
-
settled = true;
|
|
2500
|
-
cleanup();
|
|
2501
|
-
resolve();
|
|
2502
|
-
}
|
|
2503
|
-
};
|
|
2504
|
-
const fallbackToSeek = () => {
|
|
2505
|
-
if (settled) return;
|
|
2506
|
-
settled = true;
|
|
2507
|
-
video.pause();
|
|
2508
|
-
cleanup();
|
|
2509
|
-
void seekVideoByAssignment(video, targetTime, timeoutMs).then(resolve, reject);
|
|
2510
|
-
};
|
|
2511
|
-
const fail = () => {
|
|
2512
|
-
if (settled) return;
|
|
2513
|
-
settled = true;
|
|
2514
|
-
video.pause();
|
|
2515
|
-
cleanup();
|
|
2516
|
-
reject(
|
|
2517
|
-
new Error(
|
|
2518
|
-
`Video frame did not advance to ${formatMediaTime(targetTime)} within ${timeoutMs}ms (currentTime=${formatMediaTime(video.currentTime)}, readyState=${video.readyState}).`
|
|
2519
|
-
)
|
|
2520
|
-
);
|
|
2521
|
-
};
|
|
2522
|
-
function check() {
|
|
2523
|
-
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && video.currentTime >= reachableMediaTime(video, targetTime)) {
|
|
2524
|
-
video.pause();
|
|
2525
|
-
finish();
|
|
2526
|
-
}
|
|
2527
|
-
}
|
|
2528
|
-
const handlePlaybackFailure = () => {
|
|
2529
|
-
if (settled || isInactive()) return;
|
|
2530
|
-
fallbackToSeek();
|
|
2531
|
-
};
|
|
2532
|
-
const startPlayback = () => {
|
|
2533
|
-
if (settled || isInactive()) return;
|
|
2534
|
-
try {
|
|
2535
|
-
video.playbackRate = SEQUENTIAL_CAPTURE_PLAYBACK_RATE;
|
|
2536
|
-
if (video.paused) {
|
|
2537
|
-
void video.play().catch(handlePlaybackFailure);
|
|
2538
|
-
}
|
|
2539
|
-
} catch {
|
|
2540
|
-
handlePlaybackFailure();
|
|
2541
|
-
}
|
|
2542
|
-
};
|
|
2543
|
-
const watchdog = createCaptureWatchdog(ownerDocument, timeoutMs, fail);
|
|
2544
|
-
const pauseWhileInactive = () => {
|
|
2545
|
-
video.pause();
|
|
2546
|
-
watchdog.clear();
|
|
2547
|
-
};
|
|
2548
|
-
const resumeWhenActive = () => {
|
|
2549
|
-
if (settled || isInactive()) return;
|
|
2550
|
-
check();
|
|
2551
|
-
if (settled) return;
|
|
2552
|
-
startPlayback();
|
|
2553
|
-
watchdog.arm();
|
|
2554
|
-
};
|
|
2555
|
-
function handleVisibilityChange() {
|
|
2556
|
-
if (ownerDocument.visibilityState !== "visible") {
|
|
2557
|
-
pauseWhileInactive();
|
|
2558
|
-
return;
|
|
2559
|
-
}
|
|
2560
|
-
resumeWhenActive();
|
|
2561
|
-
}
|
|
2562
|
-
const readinessPoll = setInterval(check, VIDEO_FRAME_READINESS_POLL_MS);
|
|
2563
|
-
video.addEventListener("timeupdate", check);
|
|
2564
|
-
video.addEventListener("loadeddata", check);
|
|
2565
|
-
video.addEventListener("canplay", check);
|
|
2566
|
-
video.addEventListener("error", fail, { once: true });
|
|
2567
|
-
ownerDocument.addEventListener("visibilitychange", handleVisibilityChange);
|
|
2568
|
-
resumeWhenActive();
|
|
2569
|
-
});
|
|
2570
|
-
}
|
|
2571
|
-
function seekVideoToFrame(video, targetTime, timeoutMs = DEFAULT_VIDEO_FRAME_TIMEOUT_MS) {
|
|
2572
|
-
if (video.readyState >= HTMLMediaElement.HAVE_METADATA && video.videoWidth <= 0 && video.videoHeight <= 0) {
|
|
2573
|
-
video.pause();
|
|
2574
|
-
return Promise.resolve();
|
|
2575
|
-
}
|
|
2576
|
-
const reachableTargetTime = reachableMediaTime(video, targetTime);
|
|
2577
|
-
const step = reachableTargetTime - video.currentTime;
|
|
2578
|
-
if (video.dataset.captureSequential === "true") {
|
|
2579
|
-
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && step <= VIDEO_TIME_TOLERANCE_SECONDS) {
|
|
2580
|
-
if (step < -VIDEO_TIME_TOLERANCE_SECONDS) video.pause();
|
|
2581
|
-
return Promise.resolve();
|
|
2582
|
-
}
|
|
2583
|
-
if (step > 0 && step <= MAX_SEQUENTIAL_CAPTURE_STEP_SECONDS && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
2584
|
-
return advanceSequentialCaptureVideo(video, targetTime, timeoutMs);
|
|
2585
|
-
}
|
|
2586
|
-
}
|
|
2587
|
-
video.pause();
|
|
2588
|
-
const alreadyReady = !video.seeking && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && isAtReachableMediaTime(video, targetTime);
|
|
2589
|
-
if (alreadyReady) return Promise.resolve();
|
|
2590
|
-
return seekVideoByAssignment(video, targetTime, timeoutMs);
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
2460
|
// src/DocPlayer.tsx
|
|
2594
2461
|
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2595
2462
|
function isDevEnvironment() {
|
|
@@ -2667,6 +2534,8 @@ function DocPlayerContent({
|
|
|
2667
2534
|
onPlaybackStateChange,
|
|
2668
2535
|
onControlsReady,
|
|
2669
2536
|
onRenderAPIReady,
|
|
2537
|
+
renderVideoFrameSelector,
|
|
2538
|
+
processedAudio,
|
|
2670
2539
|
isFullscreen = false,
|
|
2671
2540
|
onFullscreenToggle,
|
|
2672
2541
|
onBlockMarkers,
|
|
@@ -2759,8 +2628,11 @@ function DocPlayerContent({
|
|
|
2759
2628
|
const rawSchedule = useMemo5(() => resolveMediaSchedule(doc), [doc]);
|
|
2760
2629
|
const clipDurations = useMediaClipDurations(rawSchedule, basePath);
|
|
2761
2630
|
const mediaSchedule = useMemo5(
|
|
2762
|
-
() => resolveMediaSchedule(doc, {
|
|
2763
|
-
|
|
2631
|
+
() => resolveMediaSchedule(doc, {
|
|
2632
|
+
intrinsicDuration: (clip) => clipDurations.get(clip.src),
|
|
2633
|
+
...processedAudio ? { processedAudio } : {}
|
|
2634
|
+
}),
|
|
2635
|
+
[doc, clipDurations, processedAudio]
|
|
2764
2636
|
);
|
|
2765
2637
|
const startActiveTimelineMedia = useCallback5(() => {
|
|
2766
2638
|
if (renderMode) return;
|
|
@@ -2787,6 +2659,9 @@ function DocPlayerContent({
|
|
|
2787
2659
|
currentTimeRef.current = currentTime;
|
|
2788
2660
|
const committedRenderTimeRef = useRef7(currentTime);
|
|
2789
2661
|
const renderCommitWaitersRef = useRef7([]);
|
|
2662
|
+
const renderVideoFrameSelectorRef = useRef7(renderVideoFrameSelector);
|
|
2663
|
+
renderVideoFrameSelectorRef.current = renderVideoFrameSelector;
|
|
2664
|
+
const presentedBlocksRef = useRef7(null);
|
|
2790
2665
|
const totalDurationRef = useRef7(totalDuration);
|
|
2791
2666
|
totalDurationRef.current = totalDuration;
|
|
2792
2667
|
const expandedBlocksLenRef = useRef7(0);
|
|
@@ -3041,6 +2916,11 @@ function DocPlayerContent({
|
|
|
3041
2916
|
}
|
|
3042
2917
|
}
|
|
3043
2918
|
const elapsedMs = (time - blockStartTime) * 1e3;
|
|
2919
|
+
const selectVideoFrame = async (video, targetTime) => {
|
|
2920
|
+
const selector = renderMode ? renderVideoFrameSelectorRef.current : void 0;
|
|
2921
|
+
if (selector && await selector(video, targetTime)) return;
|
|
2922
|
+
await seekVideoToFrame(video, targetTime);
|
|
2923
|
+
};
|
|
3044
2924
|
(root.getAnimations?.() ?? []).forEach((anim) => {
|
|
3045
2925
|
const target = anim.effect?.target;
|
|
3046
2926
|
if (!target) return;
|
|
@@ -3061,7 +2941,7 @@ function DocPlayerContent({
|
|
|
3061
2941
|
const clipEnd = parseFloat(video.dataset.clipEnd || "0");
|
|
3062
2942
|
const startAt = parseFloat(video.dataset.startAt || "0");
|
|
3063
2943
|
const targetTime = Math.min(clipStart + Math.max(0, blockElapsed - startAt), clipEnd);
|
|
3064
|
-
videoSeekPromises.push(
|
|
2944
|
+
videoSeekPromises.push(selectVideoFrame(video, targetTime));
|
|
3065
2945
|
});
|
|
3066
2946
|
}
|
|
3067
2947
|
root.querySelectorAll("video[data-clip-id]").forEach((el) => {
|
|
@@ -3072,10 +2952,19 @@ function DocPlayerContent({
|
|
|
3072
2952
|
video.pause();
|
|
3073
2953
|
if (time < absStart || time >= absEnd) return;
|
|
3074
2954
|
const targetTime = sourceIn + (time - absStart);
|
|
3075
|
-
videoSeekPromises.push(
|
|
2955
|
+
videoSeekPromises.push(selectVideoFrame(video, targetTime));
|
|
3076
2956
|
});
|
|
3077
2957
|
await Promise.all(videoSeekPromises);
|
|
3078
|
-
|
|
2958
|
+
const presentedBlocks = Array.from(
|
|
2959
|
+
root.querySelectorAll(".doc-player__block"),
|
|
2960
|
+
(element) => ({ element, className: element.className })
|
|
2961
|
+
);
|
|
2962
|
+
const previousBlocks = presentedBlocksRef.current;
|
|
2963
|
+
presentedBlocksRef.current = presentedBlocks;
|
|
2964
|
+
const blocksUnchanged = previousBlocks !== null && previousBlocks.length === presentedBlocks.length && presentedBlocks.every(
|
|
2965
|
+
(block, index) => block.element === previousBlocks[index].element && block.className === previousBlocks[index].className
|
|
2966
|
+
);
|
|
2967
|
+
if (!renderMode || !blocksUnchanged) await waitForVisualUpdate();
|
|
3079
2968
|
};
|
|
3080
2969
|
const getDuration = () => {
|
|
3081
2970
|
const mediaDuration = getDocPlaybackDuration(doc);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SquisqRenderAPI, V as VideoPresentation$1, P as PipSize, a as PipShape, b as PipPosition } from './player-
|
|
2
|
-
export { B as BlockMarker, c as BlockRenderer, C as CaptionMode, d as CaptionOverlay, e as CaptionPosition, f as CaptionStyle, g as ControlsLayout, D as DisplayMode, h as DocControlsBottom, i as DocControlsOverlay, j as DocControlsSidebar, k as DocControlsSlideshow, l as DocPlayer, m as DocPlayerProps, n as DocPlayerWithSidebar, o as DocProgressBar, F as FlashcardFaceView, p as FlashcardFaceViewProps, q as FlashcardView, r as FlashcardViewProps, I as InlineAudioPlayer, s as InlineAudioPlayerProps, t as InlineVideoPlayer, u as InlineVideoPlayerProps, M as MediaClipLayer, v as MediaClipLayerProps, w as PlaybackActions, x as PlaybackState, R as RenderAudioSegmentInfo, y as RenderBlockInfo, z as RenderCaptionInfo, A as RenderChapterInfo, E as
|
|
1
|
+
import { S as SquisqRenderAPI, V as VideoPresentation$1, P as PipSize, a as PipShape, b as PipPosition } from './player-5j10MhBq.js';
|
|
2
|
+
export { B as BlockMarker, c as BlockRenderer, C as CaptionMode, d as CaptionOverlay, e as CaptionPosition, f as CaptionStyle, g as ControlsLayout, D as DisplayMode, h as DocControlsBottom, i as DocControlsOverlay, j as DocControlsSidebar, k as DocControlsSlideshow, l as DocPlayer, m as DocPlayerProps, n as DocPlayerWithSidebar, o as DocProgressBar, F as FlashcardFaceView, p as FlashcardFaceViewProps, q as FlashcardView, r as FlashcardViewProps, I as InlineAudioPlayer, s as InlineAudioPlayerProps, t as InlineVideoPlayer, u as InlineVideoPlayerProps, M as MediaClipLayer, v as MediaClipLayerProps, w as PlaybackActions, x as PlaybackState, R as RenderAudioSegmentInfo, y as RenderBlockInfo, z as RenderCaptionInfo, A as RenderChapterInfo, E as RenderVideoFrameSelector, G as SlideNavActions, H as SocialCaptionOverlay, J as formatTime } from './player-5j10MhBq.js';
|
|
3
3
|
import { Theme, VideoPresentation, VideoPipSize, VideoPipShape, VideoPipPosition, Doc, ScheduledClip, MediaProvider } from '@bendyline/squisq/schemas';
|
|
4
4
|
import { DashboardStyleId, ViewportConfig, CoverSlideTemplate, CoverSlidePlayback } from '@bendyline/squisq/doc';
|
|
5
5
|
export { getAnimationStyle, getTransitionClass } from '@bendyline/squisq/doc';
|
|
@@ -8,13 +8,14 @@ export { a as CodeBlockCopyContext, M as MarkdownRenderer, b as MarkdownRenderer
|
|
|
8
8
|
export { FenceRendererContext, MermaidDiagram, MermaidDiagramProps, useFenceRenderers } from './markdown/index.js';
|
|
9
9
|
export { CanvasSection, CanvasSectionProps, ImageDisplayMode, LinearDocView, LinearDocViewProps, PageSectionView, PageSectionViewProps, PageViewContext, PageViewContextValue, PaginatedDocPage, PaginatedDocView, PaginatedDocViewProps, usePageView } from './page/index.js';
|
|
10
10
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
11
|
+
import { RefObject } from 'react';
|
|
12
|
+
import { AnimatedImageInfo } from '@bendyline/squisq/imageEdit';
|
|
11
13
|
export { ImageLayer, MapLayer, MermaidLayer, PathLayer, ShapeLayer, TextLayer, TreeLayer, VideoLayer } from './layers/index.js';
|
|
12
14
|
export { T as TableLayer, a as TableLayerContentRenderer, b as TableLayerContentRendererProps } from './TableLayer-B7ADuUGc.js';
|
|
13
15
|
export { MediaContext, MediaScheduleController, ModalDialogOptions, ResourcePolicyContext, UseDocPlaybackOptions, useAudioSync, useAutoSurface, useDocPlayback, useMediaProvider, useMediaSchedule, useMediaUrl, useModalDialog, useResourcePolicy, useViewportOrientation } from './hooks/index.js';
|
|
14
16
|
export { FenceRenderContext, FenceRenderer, FenceRendererMap } from '@bendyline/squisq/fence';
|
|
15
17
|
export { A as AudioActions, a as AudioController, b as AudioState } from './AudioController-DwMsPe38.js';
|
|
16
18
|
export { JsonFormFenceAction, JsonFormFenceRendererOptions, JsonView, JsonViewProps, createJsonFormFenceRenderer } from './json-view/index.js';
|
|
17
|
-
import 'react';
|
|
18
19
|
import '@bendyline/squisq/markdown';
|
|
19
20
|
import '@bendyline/squisq/jsonForm';
|
|
20
21
|
|
|
@@ -156,6 +157,37 @@ interface DashboardViewProps {
|
|
|
156
157
|
}
|
|
157
158
|
declare function DashboardView({ doc, theme, viewport, layout, showTitle, style, documentTitle, basePath, animationsEnabled, muted, renderMode, onRenderAPIReady, className, }: DashboardViewProps): react_jsx_runtime.JSX.Element;
|
|
158
159
|
|
|
160
|
+
type AnimatedImageStatus = 'playing' | 'paused' | 'ended';
|
|
161
|
+
interface AnimatedImagePlayback {
|
|
162
|
+
/** Inspection result: null while pending, for sources that cannot animate,
|
|
163
|
+
* and when the bytes cannot be read. */
|
|
164
|
+
info: AnimatedImageInfo | null;
|
|
165
|
+
/** True once the image is known to animate. */
|
|
166
|
+
animated: boolean;
|
|
167
|
+
/** The URL to render — `resolvedUrl`, or a private copy after a replay. */
|
|
168
|
+
displaySrc: string;
|
|
169
|
+
status: AnimatedImageStatus;
|
|
170
|
+
/** Play from the first frame. */
|
|
171
|
+
play: () => void;
|
|
172
|
+
/** Freeze on a still frame. */
|
|
173
|
+
pause: () => void;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* @param src The authored source (a relative path, URL or data URL). Its
|
|
177
|
+
* extension or MIME decides whether the image could animate at all.
|
|
178
|
+
* @param resolvedUrl The URL the image is actually loaded from, e.g. a blob
|
|
179
|
+
* URL from a MediaProvider. Pass '' while it is still resolving.
|
|
180
|
+
*/
|
|
181
|
+
declare function useAnimatedImage(src: string, resolvedUrl: string): AnimatedImagePlayback;
|
|
182
|
+
|
|
183
|
+
interface AnimatedImageControlsProps {
|
|
184
|
+
playback: AnimatedImagePlayback;
|
|
185
|
+
/** The `<img>` showing `playback.displaySrc`. Its parent must be the
|
|
186
|
+
* positioned box the image fills. */
|
|
187
|
+
imageRef: RefObject<HTMLImageElement | null>;
|
|
188
|
+
}
|
|
189
|
+
declare function AnimatedImageControls({ playback, imageRef }: AnimatedImageControlsProps): react_jsx_runtime.JSX.Element | null;
|
|
190
|
+
|
|
159
191
|
interface DocPlayerAppearanceOverrides {
|
|
160
192
|
theme?: Theme;
|
|
161
193
|
videoPresentation?: VideoPresentation$1;
|
|
@@ -206,4 +238,4 @@ declare function resolveDocPlayerAppearance(doc: Doc, overrides?: DocPlayerAppea
|
|
|
206
238
|
*/
|
|
207
239
|
declare function useMediaClipDurations(schedule: ScheduledClip[], basePath: string, mediaProviderOverride?: MediaProvider | null): Map<string, number>;
|
|
208
240
|
|
|
209
|
-
export { CodeBlockCopyHandler, DashboardView, type DashboardViewProps, type DocPlayerAppearanceOverrides, type MountOptions, PipPosition, PipShape, PipSize, type ResolvedDocPlayerAppearance, type SquisqPlayerHandle, SquisqRenderAPI, VideoPresentation$1 as VideoPresentation, resolveDocPlayerAppearance, useMediaClipDurations };
|
|
241
|
+
export { AnimatedImageControls, type AnimatedImageControlsProps, type AnimatedImagePlayback, type AnimatedImageStatus, CodeBlockCopyHandler, DashboardView, type DashboardViewProps, type DocPlayerAppearanceOverrides, type MountOptions, PipPosition, PipShape, PipSize, type ResolvedDocPlayerAppearance, type SquisqPlayerHandle, SquisqRenderAPI, VideoPresentation$1 as VideoPresentation, resolveDocPlayerAppearance, useAnimatedImage, useMediaClipDurations };
|
package/dist/index.js
CHANGED
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
formatTime,
|
|
16
16
|
resolveDocPlayerAppearance,
|
|
17
17
|
useMediaClipDurations
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-Y2ANJRT3.js";
|
|
19
19
|
import {
|
|
20
20
|
PaginatedDocView
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-SESRQ7XH.js";
|
|
22
22
|
import {
|
|
23
23
|
BlockRenderer,
|
|
24
24
|
CanvasSection,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
PageSectionView,
|
|
27
27
|
PageViewContext,
|
|
28
28
|
usePageView
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-LDXH4FOY.js";
|
|
30
30
|
import {
|
|
31
31
|
ImageLayer,
|
|
32
32
|
MapLayer,
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
VideoLayer,
|
|
40
40
|
getAnimationStyle,
|
|
41
41
|
getTransitionClass
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-W6MKRWL6.js";
|
|
43
43
|
import {
|
|
44
44
|
useModalDialog
|
|
45
45
|
} from "./chunk-65POMKHR.js";
|
|
@@ -52,17 +52,19 @@ import {
|
|
|
52
52
|
import {
|
|
53
53
|
JsonView,
|
|
54
54
|
createJsonFormFenceRenderer
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-KDTKUE53.js";
|
|
56
56
|
import {
|
|
57
57
|
useAutoSurface
|
|
58
58
|
} from "./chunk-TT6ENR6T.js";
|
|
59
59
|
import {
|
|
60
|
+
AnimatedImageControls,
|
|
60
61
|
FenceRendererContext,
|
|
61
62
|
InlineAudioPlayer,
|
|
62
63
|
InlineVideoPlayer,
|
|
63
64
|
MarkdownRenderer,
|
|
65
|
+
useAnimatedImage,
|
|
64
66
|
useFenceRenderers
|
|
65
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-6PUMLLJH.js";
|
|
66
68
|
import {
|
|
67
69
|
MermaidDiagram
|
|
68
70
|
} from "./chunk-WLUZTUNZ.js";
|
|
@@ -74,6 +76,7 @@ import {
|
|
|
74
76
|
useResourcePolicy
|
|
75
77
|
} from "./chunk-LR3AIGDD.js";
|
|
76
78
|
export {
|
|
79
|
+
AnimatedImageControls,
|
|
77
80
|
BlockRenderer,
|
|
78
81
|
CanvasSection,
|
|
79
82
|
CaptionOverlay,
|
|
@@ -115,6 +118,7 @@ export {
|
|
|
115
118
|
getAnimationStyle,
|
|
116
119
|
getTransitionClass,
|
|
117
120
|
resolveDocPlayerAppearance,
|
|
121
|
+
useAnimatedImage,
|
|
118
122
|
useAudioSync,
|
|
119
123
|
useAutoSurface,
|
|
120
124
|
useDocPlayback,
|