@bendyline/squisq-react 2.11.7 → 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.
@@ -9,14 +9,14 @@ import {
9
9
  TreeLayer,
10
10
  VideoLayer,
11
11
  getTransitionClass
12
- } from "./chunk-XSOMFBH2.js";
12
+ } from "./chunk-W6MKRWL6.js";
13
13
  import {
14
14
  useAutoSurface
15
15
  } from "./chunk-TT6ENR6T.js";
16
16
  import {
17
17
  InlineVideoPlayer,
18
18
  MarkdownRenderer
19
- } from "./chunk-ZY5N3JPM.js";
19
+ } from "./chunk-6PUMLLJH.js";
20
20
  import {
21
21
  useMediaUrl
22
22
  } from "./chunk-LR3AIGDD.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  LinearDocView
3
- } from "./chunk-5UGIE2QO.js";
3
+ } from "./chunk-LDXH4FOY.js";
4
4
 
5
5
  // src/PaginatedDocView.tsx
6
6
  import {
@@ -1336,6 +1336,252 @@ function MapLayer({ layer, basePath, viewport, blockTime }) {
1336
1336
 
1337
1337
  // src/layers/VideoLayer.tsx
1338
1338
  import { useRef as useRef2, useEffect as useEffect2 } from "react";
1339
+
1340
+ // src/docPlayer/renderReadiness.ts
1341
+ var DEFAULT_VIDEO_FRAME_TIMEOUT_MS = 2e3;
1342
+ var VIDEO_FRAME_READINESS_POLL_MS = 16;
1343
+ var VIDEO_TIME_TOLERANCE_SECONDS = 0.01;
1344
+ var MAX_SEQUENTIAL_CAPTURE_STEP_SECONDS = 0.5;
1345
+ var SEQUENTIAL_CAPTURE_PLAYBACK_RATE = 1;
1346
+ function isCaptureDrivenVideo(video) {
1347
+ return video.dataset.captureSequential === "true" || video.dataset.captureDecoded === "true";
1348
+ }
1349
+ function formatMediaTime(time) {
1350
+ return Number.isFinite(time) ? `${time.toFixed(3)}s` : String(time);
1351
+ }
1352
+ function reachableMediaTime(video, targetTime) {
1353
+ const duration = video.duration;
1354
+ if (!Number.isFinite(duration)) return targetTime;
1355
+ return Math.max(0, Math.min(targetTime, duration));
1356
+ }
1357
+ function isAtReachableMediaTime(video, targetTime) {
1358
+ return Math.abs(video.currentTime - reachableMediaTime(video, targetTime)) <= VIDEO_TIME_TOLERANCE_SECONDS;
1359
+ }
1360
+ function isVisiblyPresented(video) {
1361
+ if (!video.isConnected) return false;
1362
+ const view = video.ownerDocument.defaultView;
1363
+ if (!view) return false;
1364
+ for (let element = video; element; element = element.parentElement) {
1365
+ const style = view.getComputedStyle(element);
1366
+ if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse" || Number.parseFloat(style.opacity || "1") <= 0) {
1367
+ return false;
1368
+ }
1369
+ }
1370
+ return true;
1371
+ }
1372
+ function createCaptureWatchdog(ownerDocument, timeoutMs, expire) {
1373
+ let timeout = null;
1374
+ const clear = () => {
1375
+ if (timeout === null) return;
1376
+ clearTimeout(timeout);
1377
+ timeout = null;
1378
+ };
1379
+ return {
1380
+ arm: () => {
1381
+ clear();
1382
+ if (ownerDocument.visibilityState !== "visible") return;
1383
+ timeout = setTimeout(expire, timeoutMs);
1384
+ },
1385
+ clear
1386
+ };
1387
+ }
1388
+ function seekVideoByAssignment(video, targetTime, timeoutMs = DEFAULT_VIDEO_FRAME_TIMEOUT_MS) {
1389
+ video.pause();
1390
+ return new Promise((resolve, reject) => {
1391
+ let settled = false;
1392
+ let videoFrameRequest = null;
1393
+ const ownerDocument = video.ownerDocument;
1394
+ const isInactive = () => ownerDocument.visibilityState !== "visible";
1395
+ const cleanup = () => {
1396
+ watchdog.clear();
1397
+ clearInterval(readinessPoll);
1398
+ video.removeEventListener("seeked", handleMediaReady);
1399
+ video.removeEventListener("loadeddata", handleMediaReady);
1400
+ video.removeEventListener("canplay", handleMediaReady);
1401
+ ownerDocument.removeEventListener("visibilitychange", handleVisibilityChange);
1402
+ if (videoFrameRequest !== null && typeof video.cancelVideoFrameCallback === "function") {
1403
+ video.cancelVideoFrameCallback(videoFrameRequest);
1404
+ }
1405
+ };
1406
+ const finish = () => {
1407
+ if (settled) return;
1408
+ settled = true;
1409
+ cleanup();
1410
+ resolve();
1411
+ };
1412
+ const fail = () => {
1413
+ if (settled) return;
1414
+ settled = true;
1415
+ cleanup();
1416
+ reject(
1417
+ new Error(
1418
+ `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))}).`
1419
+ )
1420
+ );
1421
+ };
1422
+ const requestPresentedFrame = () => {
1423
+ if (settled || video.seeking || video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA || !isAtReachableMediaTime(video, targetTime)) {
1424
+ return;
1425
+ }
1426
+ if (typeof video.requestVideoFrameCallback !== "function" || !isVisiblyPresented(video)) {
1427
+ finish();
1428
+ return;
1429
+ }
1430
+ if (videoFrameRequest !== null) return;
1431
+ videoFrameRequest = video.requestVideoFrameCallback(() => {
1432
+ videoFrameRequest = null;
1433
+ finish();
1434
+ });
1435
+ };
1436
+ function handleMediaReady() {
1437
+ requestPresentedFrame();
1438
+ }
1439
+ const watchdog = createCaptureWatchdog(ownerDocument, timeoutMs, fail);
1440
+ const assignTargetTime = () => {
1441
+ try {
1442
+ video.currentTime = reachableMediaTime(video, targetTime);
1443
+ queueMicrotask(requestPresentedFrame);
1444
+ return true;
1445
+ } catch (error) {
1446
+ settled = true;
1447
+ cleanup();
1448
+ reject(error);
1449
+ return false;
1450
+ }
1451
+ };
1452
+ function handleVisibilityChange() {
1453
+ if (settled) return;
1454
+ if (isInactive()) {
1455
+ watchdog.clear();
1456
+ return;
1457
+ }
1458
+ requestPresentedFrame();
1459
+ if (settled) return;
1460
+ if (!video.seeking && !isAtReachableMediaTime(video, targetTime) && !assignTargetTime()) {
1461
+ return;
1462
+ }
1463
+ watchdog.arm();
1464
+ }
1465
+ const readinessPoll = setInterval(requestPresentedFrame, VIDEO_FRAME_READINESS_POLL_MS);
1466
+ video.addEventListener("seeked", handleMediaReady);
1467
+ video.addEventListener("loadeddata", handleMediaReady);
1468
+ video.addEventListener("canplay", handleMediaReady);
1469
+ ownerDocument.addEventListener("visibilitychange", handleVisibilityChange);
1470
+ watchdog.arm();
1471
+ assignTargetTime();
1472
+ });
1473
+ }
1474
+ function advanceSequentialCaptureVideo(video, targetTime, timeoutMs) {
1475
+ return new Promise((resolve, reject) => {
1476
+ let settled = false;
1477
+ const ownerDocument = video.ownerDocument;
1478
+ const isInactive = () => ownerDocument.visibilityState !== "visible";
1479
+ const cleanup = () => {
1480
+ watchdog.clear();
1481
+ clearInterval(readinessPoll);
1482
+ video.removeEventListener("timeupdate", check);
1483
+ video.removeEventListener("loadeddata", check);
1484
+ video.removeEventListener("canplay", check);
1485
+ video.removeEventListener("error", fail);
1486
+ ownerDocument.removeEventListener("visibilitychange", handleVisibilityChange);
1487
+ };
1488
+ const finish = () => {
1489
+ if (!settled) {
1490
+ settled = true;
1491
+ cleanup();
1492
+ resolve();
1493
+ }
1494
+ };
1495
+ const fallbackToSeek = () => {
1496
+ if (settled) return;
1497
+ settled = true;
1498
+ video.pause();
1499
+ cleanup();
1500
+ void seekVideoByAssignment(video, targetTime, timeoutMs).then(resolve, reject);
1501
+ };
1502
+ const fail = () => {
1503
+ if (settled) return;
1504
+ settled = true;
1505
+ video.pause();
1506
+ cleanup();
1507
+ reject(
1508
+ new Error(
1509
+ `Video frame did not advance to ${formatMediaTime(targetTime)} within ${timeoutMs}ms (currentTime=${formatMediaTime(video.currentTime)}, readyState=${video.readyState}).`
1510
+ )
1511
+ );
1512
+ };
1513
+ function check() {
1514
+ if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && video.currentTime >= reachableMediaTime(video, targetTime)) {
1515
+ video.pause();
1516
+ finish();
1517
+ }
1518
+ }
1519
+ const handlePlaybackFailure = () => {
1520
+ if (settled || isInactive()) return;
1521
+ fallbackToSeek();
1522
+ };
1523
+ const startPlayback = () => {
1524
+ if (settled || isInactive()) return;
1525
+ try {
1526
+ video.playbackRate = SEQUENTIAL_CAPTURE_PLAYBACK_RATE;
1527
+ if (video.paused) {
1528
+ void video.play().catch(handlePlaybackFailure);
1529
+ }
1530
+ } catch {
1531
+ handlePlaybackFailure();
1532
+ }
1533
+ };
1534
+ const watchdog = createCaptureWatchdog(ownerDocument, timeoutMs, fail);
1535
+ const pauseWhileInactive = () => {
1536
+ video.pause();
1537
+ watchdog.clear();
1538
+ };
1539
+ const resumeWhenActive = () => {
1540
+ if (settled || isInactive()) return;
1541
+ check();
1542
+ if (settled) return;
1543
+ startPlayback();
1544
+ watchdog.arm();
1545
+ };
1546
+ function handleVisibilityChange() {
1547
+ if (ownerDocument.visibilityState !== "visible") {
1548
+ pauseWhileInactive();
1549
+ return;
1550
+ }
1551
+ resumeWhenActive();
1552
+ }
1553
+ const readinessPoll = setInterval(check, VIDEO_FRAME_READINESS_POLL_MS);
1554
+ video.addEventListener("timeupdate", check);
1555
+ video.addEventListener("loadeddata", check);
1556
+ video.addEventListener("canplay", check);
1557
+ video.addEventListener("error", fail, { once: true });
1558
+ ownerDocument.addEventListener("visibilitychange", handleVisibilityChange);
1559
+ resumeWhenActive();
1560
+ });
1561
+ }
1562
+ function seekVideoToFrame(video, targetTime, timeoutMs = DEFAULT_VIDEO_FRAME_TIMEOUT_MS) {
1563
+ if (video.readyState >= HTMLMediaElement.HAVE_METADATA && video.videoWidth <= 0 && video.videoHeight <= 0) {
1564
+ video.pause();
1565
+ return Promise.resolve();
1566
+ }
1567
+ const reachableTargetTime = reachableMediaTime(video, targetTime);
1568
+ const step = reachableTargetTime - video.currentTime;
1569
+ if (video.dataset.captureSequential === "true") {
1570
+ if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && step <= VIDEO_TIME_TOLERANCE_SECONDS) {
1571
+ if (step < -VIDEO_TIME_TOLERANCE_SECONDS) video.pause();
1572
+ return Promise.resolve();
1573
+ }
1574
+ if (step > 0 && step <= MAX_SEQUENTIAL_CAPTURE_STEP_SECONDS && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
1575
+ return advanceSequentialCaptureVideo(video, targetTime, timeoutMs);
1576
+ }
1577
+ }
1578
+ video.pause();
1579
+ const alreadyReady = !video.seeking && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && isAtReachableMediaTime(video, targetTime);
1580
+ if (alreadyReady) return Promise.resolve();
1581
+ return seekVideoByAssignment(video, targetTime, timeoutMs);
1582
+ }
1583
+
1584
+ // src/layers/VideoLayer.tsx
1339
1585
  import { jsx as jsx7 } from "react/jsx-runtime";
1340
1586
  var VIDEO_SYNC_DRIFT_SECONDS = 0.2;
1341
1587
  function VideoLayer({
@@ -1389,7 +1635,7 @@ function VideoLayer({
1389
1635
  const video = videoRef.current;
1390
1636
  if (!video || !hasStartedRef.current) return;
1391
1637
  const targetTime = gated ? content.clipStart : Math.min(content.clipEnd, content.clipStart + Math.max(0, blockTime - startAt));
1392
- if (video.dataset.captureSequential !== "true" && Math.abs(video.currentTime - targetTime) > VIDEO_SYNC_DRIFT_SECONDS) {
1638
+ if (!isCaptureDrivenVideo(video) && Math.abs(video.currentTime - targetTime) > VIDEO_SYNC_DRIFT_SECONDS) {
1393
1639
  video.currentTime = targetTime;
1394
1640
  }
1395
1641
  if (gated) {
@@ -1406,7 +1652,7 @@ function VideoLayer({
1406
1652
  playPromise.catch(() => {
1407
1653
  });
1408
1654
  }
1409
- } else if (video.dataset.captureSequential !== "true") {
1655
+ } else if (!isCaptureDrivenVideo(video)) {
1410
1656
  video.pause();
1411
1657
  }
1412
1658
  }, [isPlaying, gated, blockTime, startAt, src, content.clipStart, content.clipEnd]);
@@ -1741,6 +1987,8 @@ function MermaidLayer({ layer, viewport, blockTime, theme }) {
1741
1987
  }
1742
1988
 
1743
1989
  export {
1990
+ isCaptureDrivenVideo,
1991
+ seekVideoToFrame,
1744
1992
  getAnimationStyle,
1745
1993
  getTransitionClass,
1746
1994
  ImageLayer,