@bendyline/squisq-react 2.11.6 → 2.11.8
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-Z64OLGMD.js} +136 -265
- 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 +64 -64
- 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
|
|
@@ -165,10 +243,18 @@ function MediaClipElement({
|
|
|
165
243
|
if (!el) return;
|
|
166
244
|
if (!active) {
|
|
167
245
|
if (!el.paused) el.pause();
|
|
246
|
+
const startsIn = clip.absoluteStart - currentTime;
|
|
247
|
+
if (!renderMode && startsIn > 0 && startsIn <= PRESEEK && !isCaptureDrivenVideo(el) && Math.abs(el.currentTime - clip.sourceIn) > DRIFT / 5) {
|
|
248
|
+
try {
|
|
249
|
+
el.currentTime = clip.sourceIn;
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
}
|
|
168
253
|
return;
|
|
169
254
|
}
|
|
255
|
+
if (!renderMode) applyLevel(el, clipLevelAt(clip, currentTime));
|
|
170
256
|
const target = Math.max(0, clip.sourceIn + (currentTime - clip.absoluteStart));
|
|
171
|
-
if (el
|
|
257
|
+
if (!isCaptureDrivenVideo(el) && (renderMode || !isPlaying || Math.abs(el.currentTime - target) > DRIFT)) {
|
|
172
258
|
try {
|
|
173
259
|
el.currentTime = target;
|
|
174
260
|
} catch {
|
|
@@ -178,10 +264,10 @@ function MediaClipElement({
|
|
|
178
264
|
const p = el.play();
|
|
179
265
|
if (p) p.catch(() => {
|
|
180
266
|
});
|
|
181
|
-
} else if (el
|
|
267
|
+
} else if (!isCaptureDrivenVideo(el)) {
|
|
182
268
|
el.pause();
|
|
183
269
|
}
|
|
184
|
-
}, [active, currentTime, isPlaying, renderMode, clip
|
|
270
|
+
}, [active, currentTime, isPlaying, renderMode, clip, src]);
|
|
185
271
|
const isVideo = clip.kind === "video";
|
|
186
272
|
const common = {
|
|
187
273
|
ref,
|
|
@@ -200,17 +286,21 @@ function MediaClipElement({
|
|
|
200
286
|
...common,
|
|
201
287
|
className: `doc-player__media-video${active ? " doc-player__media-video--active" : ""}`,
|
|
202
288
|
"data-video-placement": clip.placement ?? "default",
|
|
203
|
-
muted: renderMode || muted,
|
|
289
|
+
muted: renderMode || muted || clip.audioMuted === true,
|
|
204
290
|
playsInline: true,
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
291
|
+
...clip.crop ? { "data-crop": serializeMediaCrop(clip.crop) } : {},
|
|
292
|
+
style: {
|
|
293
|
+
...mediaVideoStyle({
|
|
294
|
+
presentation,
|
|
295
|
+
pipSize,
|
|
296
|
+
pipShape,
|
|
297
|
+
pipPosition,
|
|
298
|
+
pipOrientation,
|
|
299
|
+
pipFrameStyle,
|
|
300
|
+
active
|
|
301
|
+
}),
|
|
302
|
+
...cropStyle(clip.crop)
|
|
303
|
+
}
|
|
214
304
|
}
|
|
215
305
|
);
|
|
216
306
|
}
|
|
@@ -2349,247 +2439,6 @@ function buildSegmentTitleMap(doc) {
|
|
|
2349
2439
|
return map;
|
|
2350
2440
|
}
|
|
2351
2441
|
|
|
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
2442
|
// src/DocPlayer.tsx
|
|
2594
2443
|
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2595
2444
|
function isDevEnvironment() {
|
|
@@ -2667,6 +2516,8 @@ function DocPlayerContent({
|
|
|
2667
2516
|
onPlaybackStateChange,
|
|
2668
2517
|
onControlsReady,
|
|
2669
2518
|
onRenderAPIReady,
|
|
2519
|
+
renderVideoFrameSelector,
|
|
2520
|
+
processedAudio,
|
|
2670
2521
|
isFullscreen = false,
|
|
2671
2522
|
onFullscreenToggle,
|
|
2672
2523
|
onBlockMarkers,
|
|
@@ -2759,8 +2610,11 @@ function DocPlayerContent({
|
|
|
2759
2610
|
const rawSchedule = useMemo5(() => resolveMediaSchedule(doc), [doc]);
|
|
2760
2611
|
const clipDurations = useMediaClipDurations(rawSchedule, basePath);
|
|
2761
2612
|
const mediaSchedule = useMemo5(
|
|
2762
|
-
() => resolveMediaSchedule(doc, {
|
|
2763
|
-
|
|
2613
|
+
() => resolveMediaSchedule(doc, {
|
|
2614
|
+
intrinsicDuration: (clip) => clipDurations.get(clip.src),
|
|
2615
|
+
...processedAudio ? { processedAudio } : {}
|
|
2616
|
+
}),
|
|
2617
|
+
[doc, clipDurations, processedAudio]
|
|
2764
2618
|
);
|
|
2765
2619
|
const startActiveTimelineMedia = useCallback5(() => {
|
|
2766
2620
|
if (renderMode) return;
|
|
@@ -2787,6 +2641,9 @@ function DocPlayerContent({
|
|
|
2787
2641
|
currentTimeRef.current = currentTime;
|
|
2788
2642
|
const committedRenderTimeRef = useRef7(currentTime);
|
|
2789
2643
|
const renderCommitWaitersRef = useRef7([]);
|
|
2644
|
+
const renderVideoFrameSelectorRef = useRef7(renderVideoFrameSelector);
|
|
2645
|
+
renderVideoFrameSelectorRef.current = renderVideoFrameSelector;
|
|
2646
|
+
const presentedBlocksRef = useRef7(null);
|
|
2790
2647
|
const totalDurationRef = useRef7(totalDuration);
|
|
2791
2648
|
totalDurationRef.current = totalDuration;
|
|
2792
2649
|
const expandedBlocksLenRef = useRef7(0);
|
|
@@ -3041,6 +2898,11 @@ function DocPlayerContent({
|
|
|
3041
2898
|
}
|
|
3042
2899
|
}
|
|
3043
2900
|
const elapsedMs = (time - blockStartTime) * 1e3;
|
|
2901
|
+
const selectVideoFrame = async (video, targetTime) => {
|
|
2902
|
+
const selector = renderMode ? renderVideoFrameSelectorRef.current : void 0;
|
|
2903
|
+
if (selector && await selector(video, targetTime)) return;
|
|
2904
|
+
await seekVideoToFrame(video, targetTime);
|
|
2905
|
+
};
|
|
3044
2906
|
(root.getAnimations?.() ?? []).forEach((anim) => {
|
|
3045
2907
|
const target = anim.effect?.target;
|
|
3046
2908
|
if (!target) return;
|
|
@@ -3061,7 +2923,7 @@ function DocPlayerContent({
|
|
|
3061
2923
|
const clipEnd = parseFloat(video.dataset.clipEnd || "0");
|
|
3062
2924
|
const startAt = parseFloat(video.dataset.startAt || "0");
|
|
3063
2925
|
const targetTime = Math.min(clipStart + Math.max(0, blockElapsed - startAt), clipEnd);
|
|
3064
|
-
videoSeekPromises.push(
|
|
2926
|
+
videoSeekPromises.push(selectVideoFrame(video, targetTime));
|
|
3065
2927
|
});
|
|
3066
2928
|
}
|
|
3067
2929
|
root.querySelectorAll("video[data-clip-id]").forEach((el) => {
|
|
@@ -3072,10 +2934,19 @@ function DocPlayerContent({
|
|
|
3072
2934
|
video.pause();
|
|
3073
2935
|
if (time < absStart || time >= absEnd) return;
|
|
3074
2936
|
const targetTime = sourceIn + (time - absStart);
|
|
3075
|
-
videoSeekPromises.push(
|
|
2937
|
+
videoSeekPromises.push(selectVideoFrame(video, targetTime));
|
|
3076
2938
|
});
|
|
3077
2939
|
await Promise.all(videoSeekPromises);
|
|
3078
|
-
|
|
2940
|
+
const presentedBlocks = Array.from(
|
|
2941
|
+
root.querySelectorAll(".doc-player__block"),
|
|
2942
|
+
(element) => ({ element, className: element.className })
|
|
2943
|
+
);
|
|
2944
|
+
const previousBlocks = presentedBlocksRef.current;
|
|
2945
|
+
presentedBlocksRef.current = presentedBlocks;
|
|
2946
|
+
const blocksUnchanged = previousBlocks !== null && previousBlocks.length === presentedBlocks.length && presentedBlocks.every(
|
|
2947
|
+
(block, index) => block.element === previousBlocks[index].element && block.className === previousBlocks[index].className
|
|
2948
|
+
);
|
|
2949
|
+
if (!renderMode || !blocksUnchanged) await waitForVisualUpdate();
|
|
3079
2950
|
};
|
|
3080
2951
|
const getDuration = () => {
|
|
3081
2952
|
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-Z64OLGMD.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,
|
package/dist/json-view/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
JsonView,
|
|
3
3
|
createJsonFormFenceRenderer
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-KDTKUE53.js";
|
|
5
5
|
import "../chunk-TT6ENR6T.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-6PUMLLJH.js";
|
|
7
7
|
import "../chunk-WLUZTUNZ.js";
|
|
8
8
|
import "../chunk-LR3AIGDD.js";
|
|
9
9
|
export {
|