@vanillaskyai/video 0.10.6 → 0.10.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.
@@ -1,11 +1,11 @@
1
+ import {
2
+ ExternalVideoBackdropProvider,
3
+ resolveDensity
4
+ } from "./chunk-5JBMYQP6.js";
1
5
  import {
2
6
  limitsConcurrentVideoDecoders,
3
7
  resolveMediaType
4
8
  } from "./chunk-224QNWRA.js";
5
- import {
6
- ExternalVideoBackdropProvider,
7
- resolveDensity
8
- } from "./chunk-OOBT4X46.js";
9
9
  import {
10
10
  resolveVideoTimeline
11
11
  } from "./chunk-SPVTJH3F.js";
@@ -33,17 +33,19 @@ function MountedSceneReadiness({ scene, playing, fallback = false, onFailure })
33
33
  let frame = 0;
34
34
  let callback;
35
35
  let observed;
36
+ let presented;
37
+ const root = marker.current?.closest("[data-video-frame]");
36
38
  const start = performance.now();
37
39
  const finish = (error, actualVideoFrame = false) => {
38
40
  if (!stopped) {
39
41
  stopped = true;
42
+ if (callback !== void 0) observed?.cancelVideoFrameCallback?.(callback);
40
43
  if (error && onFailureRef.current) onFailureRef.current();
41
44
  else report(key, error, actualVideoFrame);
42
45
  }
43
46
  };
44
47
  const check = () => {
45
48
  if (stopped) return;
46
- const root = marker.current?.closest("[data-video-frame]");
47
49
  if (fallback && root?.querySelector("[data-scene-fallback]") && !root.querySelector("[data-template-loading]") && document.fonts?.status !== "loading") {
48
50
  finish();
49
51
  return;
@@ -58,9 +60,12 @@ function MountedSceneReadiness({ scene, playing, fallback = false, onFailure })
58
60
  return;
59
61
  }
60
62
  if (isVideo) {
61
- const persistent = root.querySelector("[data-persistent-video-scene-id]");
62
- const video = (persistent?.getAttribute("data-persistent-video-scene-id") === scene.id ? persistent : layer)?.querySelector("video");
63
- if (video && video.getAttribute("src") === mediaUrl && video.readyState >= 2) {
63
+ const video = layer.querySelector("video");
64
+ if (video && video.getAttribute("src") === mediaUrl && video.currentSrc === video.src && video.readyState >= 2) {
65
+ if (presented === video) {
66
+ finish(void 0, true);
67
+ return;
68
+ }
64
69
  observed = video;
65
70
  if (video.requestVideoFrameCallback) {
66
71
  callback = video.requestVideoFrameCallback(() => finish(void 0, true));
@@ -83,9 +88,20 @@ function MountedSceneReadiness({ scene, playing, fallback = false, onFailure })
83
88
  }
84
89
  frame = requestAnimationFrame(check);
85
90
  };
91
+ const onPresented = (event) => {
92
+ const video = event.target;
93
+ if (!(video instanceof HTMLVideoElement) || stopped || video.getAttribute("src") !== String(scene.variables.mediaUrl || "") || video.currentSrc !== video.src) return;
94
+ presented = video;
95
+ cancelAnimationFrame(frame);
96
+ if (callback !== void 0) observed?.cancelVideoFrameCallback?.(callback);
97
+ callback = void 0;
98
+ check();
99
+ };
100
+ root?.addEventListener("vanillasky:video-frame-presented", onPresented);
86
101
  check();
87
102
  const timeout = setTimeout(() => finish(new Error("Scene media did not become ready")), 8e3);
88
103
  return () => {
104
+ root?.removeEventListener("vanillasky:video-frame-presented", onPresented);
89
105
  stopped = true;
90
106
  clearTimeout(timeout);
91
107
  cancelAnimationFrame(frame);
@@ -94,47 +110,14 @@ function MountedSceneReadiness({ scene, playing, fallback = false, onFailure })
94
110
  }, [key, report, scene, playing, fallback]);
95
111
  return /* @__PURE__ */ jsx("span", { ref: marker, hidden: true });
96
112
  }
97
- function PreparedSceneReadiness({ scene }) {
98
- const marker = useRef(null);
99
- const report = useContext(MountedReadinessContext);
100
- useEffect(() => {
101
- if (!report) return;
102
- let frame = 0;
103
- let stopped = false;
104
- const check = () => {
105
- if (stopped) return;
106
- const root = marker.current?.closest("[data-video-frame]");
107
- const incoming = root?.querySelector("[data-scene-layer='incoming']");
108
- const video = incoming?.getAttribute("data-layer-scene-id") === scene.id ? incoming.querySelector("video") : void 0;
109
- if (video && video.getAttribute("src") === scene.variables.mediaUrl && video.readyState >= 2) {
110
- report(sceneReadinessKey(scene), void 0, false, true);
111
- return;
112
- }
113
- const image = [...root?.querySelectorAll("img[data-video-poster-plane='prepared']") ?? []].find((image2) => image2.getAttribute("src") === scene.variables.mediaPoster);
114
- if (image?.complete && image.naturalWidth > 0) {
115
- void image.decode().then(() => {
116
- if (!stopped && image.isConnected) report(sceneReadinessKey(scene), void 0, false, true);
117
- }).catch(() => void 0);
118
- return;
119
- }
120
- frame = requestAnimationFrame(check);
121
- };
122
- check();
123
- return () => {
124
- stopped = true;
125
- cancelAnimationFrame(frame);
126
- };
127
- }, [scene, report]);
128
- return /* @__PURE__ */ jsx("span", { ref: marker, hidden: true });
129
- }
130
113
 
131
114
  // src/player/video-frame.tsx
132
115
  import {
133
116
  createElement,
134
117
  Component,
135
- lazy,
136
118
  Suspense,
137
119
  useState,
120
+ useRef as useRef2,
138
121
  useCallback,
139
122
  useEffect as useEffect2,
140
123
  useSyncExternalStore
@@ -209,7 +192,6 @@ var SceneBoundary = class extends Component {
209
192
  }
210
193
  };
211
194
  var SCENE_TRANSITION_SECONDS = 0.3;
212
- var SceneVideoBackdrop = lazy(() => import("./scene-video-backdrop-MUGIXE2Q.js").then((module) => ({ default: module.SceneVideoBackdrop })));
213
195
  var MEDIA_PREROLL_SECONDS = 1.2;
214
196
  var CONTIGUITY_ULP_FACTOR = 4;
215
197
  var subscribeToDecoderPolicy = () => () => {
@@ -369,19 +351,25 @@ function VideoFrame({
369
351
  className,
370
352
  style
371
353
  }) {
354
+ const recoveryRoot = useRef2(null);
355
+ const reportedFailures = useRef2(/* @__PURE__ */ new Set());
372
356
  const [failedMedia, setFailedMedia] = useState(() => /* @__PURE__ */ new Set());
373
- const markMediaFailed = useCallback((key) => {
357
+ const markMediaFailed = useCallback((key, reason = "playback-error") => {
374
358
  if (!key || !config.scenes.some((scene) => sceneReadinessKey(scene) === key)) return;
359
+ if (!reportedFailures.current.has(key)) {
360
+ reportedFailures.current.add(key);
361
+ recoveryRoot.current?.dispatchEvent(new CustomEvent("vanillasky:media-recovery", { bubbles: true, detail: { reason: ["decode-error", "frame-readiness-timeout", "stalled-media", "playback-error"].includes(reason) ? reason : "playback-error" } }));
362
+ }
375
363
  setFailedMedia((previous) => previous.has(key) ? previous : /* @__PURE__ */ new Set([...previous, key]));
376
364
  }, [config.scenes]);
377
365
  useEffect2(() => {
378
366
  const currentKeys = new Set(config.scenes.map(sceneReadinessKey));
367
+ for (const key of reportedFailures.current) if (!currentKeys.has(key)) reportedFailures.current.delete(key);
379
368
  setFailedMedia((previous) => {
380
369
  const retained = new Set([...previous].filter((key) => currentKeys.has(key)));
381
370
  return retained.size === previous.size ? previous : retained;
382
371
  });
383
372
  }, [config.scenes]);
384
- const [readyPersistentVideo, setReadyPersistentVideo] = useState();
385
373
  const decoderConstrainedDevice = useDecoderConstraint();
386
374
  const timeline = resolveVideoTimeline(config);
387
375
  const lastRange = timeline.at(-1);
@@ -435,35 +423,13 @@ function VideoFrame({
435
423
  );
436
424
  const prerollDuration = prerollsNext ? Math.min(Math.max(MEDIA_PREROLL_SECONDS, blendDuration), Math.max(0, duration)) : blendDuration;
437
425
  const prerollStart = active.end - prerollDuration;
426
+ const boundedPreparation = decoderConstrainedDevice && registrySupportsExternalVideoBackdrop(kit);
438
427
  const decoderConstrainedTransition = Boolean(
439
- contiguousNext && decoderConstrainedDevice && sceneHasVideoBackdrop(active) && sceneHasVideoBackdrop(contiguousNext)
428
+ contiguousNext && decoderConstrainedDevice && !boundedPreparation && sceneHasVideoBackdrop(active) && sceneHasVideoBackdrop(contiguousNext)
440
429
  );
441
- const persistentVideoEnabled = decoderConstrainedDevice && registrySupportsExternalVideoBackdrop(kit);
442
- const activeUsesPersistentVideo = persistentVideoEnabled && sceneHasVideoBackdrop(active);
443
- const incomingUsesPersistentVideo = Boolean(
444
- persistentVideoEnabled && contiguousNext && !sceneHasVideoBackdrop(active) && sceneHasVideoBackdrop(contiguousNext) && time >= prerollStart && time < blendEnd
445
- );
446
- const persistentVideoRange = activeUsesPersistentVideo ? active : incomingUsesPersistentVideo && contiguousNext ? contiguousNext : void 0;
447
- const persistentVideoKey = persistentVideoRange ? `${persistentVideoRange.scene.id}\0${String(persistentVideoRange.scene.variables.mediaUrl || "")}` : void 0;
448
- const firstVideoRange = timeline.find(sceneHasVideoBackdrop);
449
- const posterPreparationRange = activeUsesPersistentVideo ? contiguousNext && sceneHasVideoBackdrop(contiguousNext) ? contiguousNext : activeIndex === timeline.length - 1 && firstVideoRange?.scene.id !== active.scene.id ? firstVideoRange : void 0 : void 0;
450
- const preparedReadinessRange = posterPreparationRange ?? (contiguousNext && sceneHasVideoBackdrop(contiguousNext) ? contiguousNext : void 0);
451
- const preparedPoster = posterPreparationRange && String(
452
- posterPreparationRange.scene.variables.mediaPoster || ""
453
- ) ? {
454
- presentationKey: `${posterPreparationRange.scene.id}\0${String(
455
- posterPreparationRange.scene.variables.mediaUrl || ""
456
- )}`,
457
- mediaPoster: String(posterPreparationRange.scene.variables.mediaPoster),
458
- mediaPosition: String(posterPreparationRange.scene.variables.mediaPosition || "center"),
459
- backgroundEffect: posterPreparationRange.scene.backgroundEffect ?? config.style.defaultBackgroundEffect
460
- } : void 0;
461
430
  const activeMediaFailed = failedMedia.has(sceneReadinessKey(active.scene));
462
- const persistentVideoFailed = persistentVideoKey !== void 0 && failedMedia.has(persistentVideoKey);
463
- const persistentVideoReady = persistentVideoRange !== void 0 && (String(persistentVideoRange.scene.variables.mediaPoster || "") !== "" || readyPersistentVideo === persistentVideoKey);
464
- const persistentVideoMode = persistentVideoFailed ? "fallback" : persistentVideoReady ? "ready" : "pending";
465
431
  const mountingNext = Boolean(
466
- contiguousNext && !decoderConstrainedTransition && time >= prerollStart && time < blendEnd && (eligibleNextTransition || prerollsNext)
432
+ contiguousNext && !decoderConstrainedTransition && (boundedPreparation || time >= prerollStart) && time < blendEnd && (eligibleNextTransition || prerollsNext || boundedPreparation && sceneHasVideoBackdrop(contiguousNext))
467
433
  );
468
434
  const previewingNext = Boolean(
469
435
  eligibleNextTransition && time >= blendStart && time < blendEnd
@@ -471,7 +437,9 @@ function VideoFrame({
471
437
  const blendProgress = previewingNext && blendDuration > 0 ? Math.round(clamp01((time - blendStart) / blendDuration) * 1e6) / 1e6 : 0;
472
438
  const progress = rawProgress;
473
439
  const isFinalScene = activeIndex === timeline.length - 1;
474
- const motionProgress = isFinalScene && activeTiming ? Math.min(rawProgress, activeTiming.holdProgress) : rawProgress;
440
+ const presentsChapter = active.scene.templateId === "chapterTitle" || active.scene.templateId === "cinemaMedia" && (activeMediaFailed || !String(active.scene.variables.mediaUrl || "").trim());
441
+ const finalHold = presentsChapter ? 0.76 : activeTiming?.holdProgress;
442
+ const motionProgress = isFinalScene && finalHold !== void 0 ? Math.min(rawProgress, finalHold) : rawProgress;
475
443
  const canvas = getDimensions(config.orientation);
476
444
  const scale = Math.min(width / canvas.width, height / canvas.height);
477
445
  const canvasLeft = (width - canvas.width * scale) / 2;
@@ -479,10 +447,15 @@ function VideoFrame({
479
447
  return /* @__PURE__ */ jsxs(
480
448
  "div",
481
449
  {
450
+ ref: recoveryRoot,
482
451
  onErrorCapture: (event) => {
483
452
  const target = event.target;
484
- if ((target instanceof HTMLVideoElement || target instanceof HTMLImageElement) && target.getAttribute("src") === active.scene.variables.mediaUrl && supportsExternalVideoBackdrop(activeTemplate) && (target.closest("[data-layer-scene-id]")?.getAttribute("data-layer-scene-id") === active.scene.id || target.closest("[data-persistent-video-scene-id]")?.getAttribute("data-persistent-video-scene-id") === active.scene.id)) {
485
- markMediaFailed(sceneReadinessKey(active.scene));
453
+ if (!(target instanceof HTMLVideoElement || target instanceof HTMLImageElement)) return;
454
+ const ownerId = target.closest("[data-layer-scene-id]")?.getAttribute("data-layer-scene-id");
455
+ const owner = [active, contiguousNext].find((range) => range?.scene.id === ownerId);
456
+ const template = owner && kit.getTemplate(owner.scene.templateId);
457
+ if (owner && template && supportsExternalVideoBackdrop(template) && target.getAttribute("src") === owner.scene.variables.mediaUrl) {
458
+ markMediaFailed(sceneReadinessKey(owner.scene), "decode-error");
486
459
  }
487
460
  },
488
461
  "data-video-frame": "ready",
@@ -504,10 +477,9 @@ function VideoFrame({
504
477
  scene: active.scene,
505
478
  playing,
506
479
  fallback: activeMediaFailed,
507
- onFailure: sceneHasBackdrop(active) && supportsExternalVideoBackdrop(activeTemplate) && !activeMediaFailed ? () => markMediaFailed(sceneReadinessKey(active.scene)) : void 0
480
+ onFailure: sceneHasBackdrop(active) && supportsExternalVideoBackdrop(activeTemplate) && !activeMediaFailed ? () => markMediaFailed(sceneReadinessKey(active.scene), "frame-readiness-timeout") : void 0
508
481
  }
509
482
  ),
510
- preparedReadinessRange && /* @__PURE__ */ jsx2(PreparedSceneReadiness, { scene: preparedReadinessRange.scene }),
511
483
  /* @__PURE__ */ jsxs(
512
484
  "div",
513
485
  {
@@ -536,58 +508,6 @@ function VideoFrame({
536
508
  }
537
509
  }
538
510
  ),
539
- persistentVideoRange && !persistentVideoFailed && /* @__PURE__ */ jsx2(
540
- "div",
541
- {
542
- "data-persistent-video-scene-id": persistentVideoRange.scene.id,
543
- "aria-hidden": "true",
544
- style: { position: "absolute", inset: 0, zIndex: 0 },
545
- children: /* @__PURE__ */ jsx2(Suspense, { fallback: String(persistentVideoRange.scene.variables.mediaPoster || "") ? /* @__PURE__ */ jsx2(
546
- "img",
547
- {
548
- src: String(persistentVideoRange.scene.variables.mediaPoster),
549
- alt: "",
550
- "data-video-poster-plane": "loading",
551
- "data-video-poster-visible": "true",
552
- style: {
553
- position: "absolute",
554
- inset: 0,
555
- width: "100%",
556
- height: "100%",
557
- objectFit: "cover",
558
- objectPosition: String(persistentVideoRange.scene.variables.mediaPosition || "center")
559
- }
560
- }
561
- ) : null, children: /* @__PURE__ */ jsx2(
562
- SceneVideoBackdrop,
563
- {
564
- mediaUrl: String(persistentVideoRange.scene.variables.mediaUrl || ""),
565
- mediaPoster: String(persistentVideoRange.scene.variables.mediaPoster || "") || void 0,
566
- mediaPosition: String(persistentVideoRange.scene.variables.mediaPosition || "center"),
567
- backgroundEffect: persistentVideoRange.scene.backgroundEffect ?? config.style.defaultBackgroundEffect,
568
- progress: persistentVideoRange.scene.id === active.scene.id ? rawProgress : 0,
569
- sceneDuration: persistentVideoRange.end - persistentVideoRange.start,
570
- isPlaying: persistentVideoRange.scene.id === active.scene.id && playing,
571
- preparingNarration,
572
- muted: mediaAudioMuted || persistentVideoRange.scene.id !== active.scene.id || !playing,
573
- volume: mediaAudioVolume,
574
- playbackId: persistentVideoRange.scene.id,
575
- retainPoster: true,
576
- persistent: true,
577
- preparedPoster: preparedPoster ? {
578
- ...preparedPoster,
579
- // The image is already decoded and costs no second video
580
- // decoder. Fade it above the outgoing video using the same
581
- // authored transition clock, then reuse that exact DOM image
582
- // as the new source's underlay at the cut.
583
- opacity: decoderConstrainedTransition ? blendProgress : 0
584
- } : void 0,
585
- onReady: () => setReadyPersistentVideo(persistentVideoKey),
586
- onError: () => markMediaFailed(persistentVideoKey)
587
- }
588
- ) })
589
- }
590
- ),
591
511
  mountingNext && contiguousNext ? [
592
512
  /* @__PURE__ */ jsx2(
593
513
  SceneLayer,
@@ -596,7 +516,7 @@ function VideoFrame({
596
516
  kit,
597
517
  config,
598
518
  range: active,
599
- onMediaError: () => markMediaFailed(sceneReadinessKey(active.scene)),
519
+ onMediaError: (reason) => markMediaFailed(sceneReadinessKey(active.scene), reason),
600
520
  progress,
601
521
  motionProgress,
602
522
  width: canvas.width,
@@ -609,7 +529,7 @@ function VideoFrame({
609
529
  opacity: 1 - blendProgress,
610
530
  interactive: true,
611
531
  zIndex: 1,
612
- externalVideoBackdrop: activeMediaFailed ? "fallback" : persistentVideoRange?.scene.id === active.scene.id ? persistentVideoMode : false
532
+ externalVideoBackdrop: activeMediaFailed ? "fallback" : false
613
533
  },
614
534
  active.scene.id
615
535
  ),
@@ -631,7 +551,8 @@ function VideoFrame({
631
551
  opacity: blendProgress,
632
552
  interactive: false,
633
553
  zIndex: 2,
634
- externalVideoBackdrop: persistentVideoRange?.scene.id === contiguousNext.scene.id ? persistentVideoMode : false
554
+ onMediaError: (reason) => markMediaFailed(sceneReadinessKey(contiguousNext.scene), reason),
555
+ externalVideoBackdrop: failedMedia.has(sceneReadinessKey(contiguousNext.scene)) ? "fallback" : false
635
556
  },
636
557
  contiguousNext.scene.id
637
558
  )
@@ -643,7 +564,7 @@ function VideoFrame({
643
564
  kit,
644
565
  config,
645
566
  range: active,
646
- onMediaError: () => markMediaFailed(sceneReadinessKey(active.scene)),
567
+ onMediaError: (reason) => markMediaFailed(sceneReadinessKey(active.scene), reason),
647
568
  progress,
648
569
  motionProgress,
649
570
  width: canvas.width,
@@ -656,7 +577,7 @@ function VideoFrame({
656
577
  opacity: 1,
657
578
  interactive: true,
658
579
  zIndex: 1,
659
- externalVideoBackdrop: activeMediaFailed ? "fallback" : persistentVideoRange?.scene.id === active.scene.id ? persistentVideoMode : false
580
+ externalVideoBackdrop: activeMediaFailed ? "fallback" : false
660
581
  },
661
582
  active.scene.id
662
583
  )
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SceneBackground,
3
3
  getMediaBackgroundProps
4
- } from "./chunk-MGCD3ZJ3.js";
4
+ } from "./chunk-J7EGPURK.js";
5
5
 
6
6
  // src/visual-system/scene-templates/cinema-media.tsx
7
7
  import { jsx } from "react/jsx-runtime";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SceneBackground,
3
3
  getMediaBackgroundProps
4
- } from "./chunk-MGCD3ZJ3.js";
4
+ } from "./chunk-J7EGPURK.js";
5
5
  import {
6
6
  editorialFont
7
7
  } from "./chunk-4YM2M62S.js";
@@ -1,11 +1,10 @@
1
1
  import {
2
2
  MediaScene,
3
3
  MediaSceneTemplate
4
- } from "./chunk-XBIKTSFY.js";
5
- import "./chunk-MGCD3ZJ3.js";
6
- import "./chunk-DD4ZSYKG.js";
4
+ } from "./chunk-Q3LPOPHL.js";
5
+ import "./chunk-J7EGPURK.js";
6
+ import "./chunk-5JBMYQP6.js";
7
7
  import "./chunk-224QNWRA.js";
8
- import "./chunk-OOBT4X46.js";
9
8
  export {
10
9
  MediaScene,
11
10
  MediaSceneTemplate
@@ -1,16 +1,15 @@
1
1
  import {
2
2
  EditorialSurface
3
- } from "./chunk-JNZRVC45.js";
4
- import "./chunk-MGCD3ZJ3.js";
5
- import "./chunk-DD4ZSYKG.js";
3
+ } from "./chunk-R5ZX2LJV.js";
4
+ import "./chunk-J7EGPURK.js";
6
5
  import {
7
6
  editorialLabel,
8
7
  fade
9
8
  } from "./chunk-4YM2M62S.js";
9
+ import "./chunk-5JBMYQP6.js";
10
10
  import {
11
11
  hasSceneMedia
12
12
  } from "./chunk-224QNWRA.js";
13
- import "./chunk-OOBT4X46.js";
14
13
 
15
14
  // src/visual-system/scene-templates/comparison.tsx
16
15
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,13 +1,12 @@
1
1
  import {
2
2
  EditorialSurface
3
- } from "./chunk-JNZRVC45.js";
4
- import "./chunk-MGCD3ZJ3.js";
5
- import "./chunk-DD4ZSYKG.js";
3
+ } from "./chunk-R5ZX2LJV.js";
4
+ import "./chunk-J7EGPURK.js";
6
5
  import {
7
6
  fade
8
7
  } from "./chunk-4YM2M62S.js";
8
+ import "./chunk-5JBMYQP6.js";
9
9
  import "./chunk-224QNWRA.js";
10
- import "./chunk-OOBT4X46.js";
11
10
 
12
11
  // src/visual-system/scene-templates/editorial-timeline.tsx
13
12
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,16 +1,15 @@
1
1
  import {
2
2
  EditorialSurface
3
- } from "./chunk-JNZRVC45.js";
4
- import "./chunk-MGCD3ZJ3.js";
5
- import "./chunk-DD4ZSYKG.js";
3
+ } from "./chunk-R5ZX2LJV.js";
4
+ import "./chunk-J7EGPURK.js";
6
5
  import {
7
6
  editorialLabel,
8
7
  fade
9
8
  } from "./chunk-4YM2M62S.js";
9
+ import "./chunk-5JBMYQP6.js";
10
10
  import {
11
11
  hasSceneMedia
12
12
  } from "./chunk-224QNWRA.js";
13
- import "./chunk-OOBT4X46.js";
14
13
 
15
14
  // src/visual-system/scene-templates/key-figure.tsx
16
15
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,15 +1,14 @@
1
1
  import {
2
2
  MediaScene
3
- } from "./chunk-XBIKTSFY.js";
4
- import "./chunk-MGCD3ZJ3.js";
3
+ } from "./chunk-Q3LPOPHL.js";
5
4
  import {
6
5
  spring
7
- } from "./chunk-DD4ZSYKG.js";
6
+ } from "./chunk-J7EGPURK.js";
8
7
  import {
9
8
  editorialFont
10
9
  } from "./chunk-4YM2M62S.js";
10
+ import "./chunk-5JBMYQP6.js";
11
11
  import "./chunk-224QNWRA.js";
12
- import "./chunk-OOBT4X46.js";
13
12
 
14
13
  // src/visual-system/scene-templates/mobile-message.tsx
15
14
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,15 +1,14 @@
1
1
  import {
2
2
  EditorialSurface
3
- } from "./chunk-JNZRVC45.js";
4
- import "./chunk-MGCD3ZJ3.js";
5
- import "./chunk-DD4ZSYKG.js";
3
+ } from "./chunk-R5ZX2LJV.js";
4
+ import "./chunk-J7EGPURK.js";
6
5
  import {
7
6
  fade
8
7
  } from "./chunk-4YM2M62S.js";
8
+ import "./chunk-5JBMYQP6.js";
9
9
  import {
10
10
  hasSceneMedia
11
11
  } from "./chunk-224QNWRA.js";
12
- import "./chunk-OOBT4X46.js";
13
12
 
14
13
  // src/visual-system/scene-templates/quote.tsx
15
14
  import { jsx, jsxs } from "react/jsx-runtime";
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  TitleSceneTemplate
3
- } from "./chunk-EWWCQFTI.js";
3
+ } from "./chunk-7AA2JWHZ.js";
4
4
  import "./chunk-4YM2M62S.js";
5
5
  import {
6
6
  getSceneDuration,
@@ -40,9 +40,9 @@ import {
40
40
  VideoFrame,
41
41
  getDimensions,
42
42
  sceneReadinessKey
43
- } from "./chunk-VWJBIYBM.js";
43
+ } from "./chunk-KUCEOG2L.js";
44
+ import "./chunk-5JBMYQP6.js";
44
45
  import "./chunk-224QNWRA.js";
45
- import "./chunk-OOBT4X46.js";
46
46
  import {
47
47
  getVideoDuration,
48
48
  resolveVideoTimeline
@@ -94,13 +94,13 @@ import { createElement } from "react";
94
94
 
95
95
  // src/visual-system/catalog/builtin-loaders.generated.ts
96
96
  var GENERATED_BUILTIN_TEMPLATE_LOADERS = {
97
- "cinemaMedia": () => import("./cinema-media-AYKCBXRI.js").then((module) => ({ default: module.MediaSceneTemplate })),
98
- "chapterTitle": () => import("./chapter-title-YRLIQY3V.js").then((module) => ({ default: module.TitleSceneTemplate })),
99
- "editorialTimeline": () => import("./editorial-timeline-DUW7C75H.js").then((module) => ({ default: module.TimelineSceneTemplate })),
100
- "mobileMessage": () => import("./mobile-message-KBLF3WUR.js").then((module) => ({ default: module.NotificationSceneTemplate })),
101
- "comparison": () => import("./comparison-74B2XEH4.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
102
- "quote": () => import("./quote-QCGR2HOU.js").then((module) => ({ default: module.QuoteSceneTemplate })),
103
- "keyFigure": () => import("./key-figure-OEWIMPGS.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
97
+ "cinemaMedia": () => import("./cinema-media-WUS7CKPC.js").then((module) => ({ default: module.MediaSceneTemplate })),
98
+ "chapterTitle": () => import("./chapter-title-2JVDU62E.js").then((module) => ({ default: module.TitleSceneTemplate })),
99
+ "editorialTimeline": () => import("./editorial-timeline-JHQZIPNE.js").then((module) => ({ default: module.TimelineSceneTemplate })),
100
+ "mobileMessage": () => import("./mobile-message-CDKOERRZ.js").then((module) => ({ default: module.NotificationSceneTemplate })),
101
+ "comparison": () => import("./comparison-T4OYCOH3.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
102
+ "quote": () => import("./quote-JKABOMIG.js").then((module) => ({ default: module.QuoteSceneTemplate })),
103
+ "keyFigure": () => import("./key-figure-YWN2OBQU.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
104
104
  };
105
105
 
106
106
  // src/visual-system/catalog/builtin-player.generated.ts
@@ -534,7 +534,6 @@ function usePlaybackClock({
534
534
  loopRef,
535
535
  sceneIndexRef,
536
536
  visualReadyRef,
537
- posterBridgeKeysRef,
538
537
  callbacksRef,
539
538
  setCurrentTime,
540
539
  setIsPlaying
@@ -545,6 +544,10 @@ function usePlaybackClock({
545
544
  let onsetWaitSeconds = 0;
546
545
  let clockWaitSeconds = 0;
547
546
  let lastNarrationTime;
547
+ let committedTime = timeRef.current;
548
+ let groupHandoff;
549
+ let requestId = stateRef.current.requestId;
550
+ let runId = stateRef.current.runId;
548
551
  const failNarration = (error, state) => {
549
552
  setIsPlaying(false);
550
553
  try {
@@ -565,6 +568,12 @@ function usePlaybackClock({
565
568
  const tick = (now) => {
566
569
  const current = stateRef.current;
567
570
  const config = current.config;
571
+ const externallySeeked = timeRef.current !== committedTime;
572
+ const replaced = current.requestId !== requestId || current.runId !== runId;
573
+ requestId = current.requestId;
574
+ runId = current.runId;
575
+ if (externallySeeked || replaced) groupHandoff = void 0;
576
+ let deferGroupStall = false;
568
577
  const elapsed = Math.max(0, (now - previous) / 1e3);
569
578
  let narrationReady = true;
570
579
  try {
@@ -599,6 +608,7 @@ function usePlaybackClock({
599
608
  return;
600
609
  }
601
610
  clockWaitSeconds = narrationTime !== void 0 && narrationTime === lastNarrationTime && narrationReady && !stalled ? clockWaitSeconds + elapsed : 0;
611
+ const audioMovedBackwards = narrationTime !== void 0 && lastNarrationTime !== void 0 && narrationTime < lastNarrationTime;
602
612
  lastNarrationTime = narrationTime;
603
613
  if (clockWaitSeconds >= 8) {
604
614
  failNarration(new Error("Narration audio clock did not advance within eight seconds"), current);
@@ -613,8 +623,18 @@ function usePlaybackClock({
613
623
  nextTime = Math.min(raw, duration2);
614
624
  }
615
625
  const target = ranges.find((range) => nextTime >= range.start && nextTime < range.end) ?? ranges.at(-1);
616
- const waitingForVisual = Boolean(target && visualReadyRef && visualReadyRef.current !== sceneReadinessKey(target.scene) && !posterBridgeKeysRef?.current.has(sceneReadinessKey(target.scene)));
617
- if (waitingForVisual && target) nextTime = target.start;
626
+ const waitingForVisual = Boolean(target && visualReadyRef && visualReadyRef.current !== sceneReadinessKey(target.scene));
627
+ if (waitingForVisual && target) {
628
+ nextTime = target.start;
629
+ const fromGroup = cued?.scene.narrationGroup;
630
+ const toGroup = target.scene.narrationGroup;
631
+ const continuesParagraph = !externallySeeked && !replaced && !audioMovedBackwards && narrationReady && narrationTime !== void 0 && fromGroup && toGroup && fromGroup.id === toGroup.id && fromGroup.text === toGroup.text && target === ranges[sceneIndexRef.current + 1] && narrationTime > fromGroup.offsetSeconds + 0.04;
632
+ if (continuesParagraph) {
633
+ const key = `${sceneReadinessKey(cued.scene)}\0${sceneReadinessKey(target.scene)}\0${fromGroup.id}\0${fromGroup.text}`;
634
+ if (groupHandoff?.key !== key) groupHandoff = { key, startedAt: now };
635
+ deferGroupStall = !stalled && now - groupHandoff.startedAt < 200;
636
+ } else groupHandoff = void 0;
637
+ } else groupHandoff = void 0;
618
638
  if (nextTime !== timeRef.current) {
619
639
  timeRef.current = nextTime;
620
640
  setCurrentTime(nextTime);
@@ -645,7 +665,8 @@ function usePlaybackClock({
645
665
  }
646
666
  const duration = current.config ? getVideoDuration(current.config) : 0;
647
667
  const active = current.config ? resolveVideoTimeline(current.config).find((range) => timeRef.current >= range.start && timeRef.current < range.end) : void 0;
648
- reportStall(Boolean(active && visualReadyRef && visualReadyRef.current !== sceneReadinessKey(active.scene) && !posterBridgeKeysRef?.current.has(sceneReadinessKey(active.scene))) || !settled && Boolean(current.config?.scenes.length) && duration > 0 && timeRef.current >= duration);
668
+ reportStall(Boolean(active && visualReadyRef && visualReadyRef.current !== sceneReadinessKey(active.scene) && !deferGroupStall) || !settled && Boolean(current.config?.scenes.length) && duration > 0 && timeRef.current >= duration);
669
+ committedTime = timeRef.current;
649
670
  if (!settled || looping || timeRef.current < duration) frame = requestAnimationFrame(tick);
650
671
  else setIsPlaying(false);
651
672
  };
@@ -732,13 +753,8 @@ function VideoPlayerRuntime({
732
753
  const loopRef = useRef(loop);
733
754
  const sceneIndexRef = useRef(-1);
734
755
  const mediaFrameReportedRef = useRef(false);
735
- const posterBridgeKeysRef = useRef(/* @__PURE__ */ new Set());
736
756
  const visualReadyRef = useRef(void 0);
737
- const reportVisualReady = useMemo(() => (key, error, actualVideoFrame = false, posterBridge = false) => {
738
- if (posterBridge) {
739
- posterBridgeKeysRef.current.add(key);
740
- return;
741
- }
757
+ const reportVisualReady = useMemo(() => (key, error, actualVideoFrame = false) => {
742
758
  if (error) {
743
759
  setIsPlaying(false);
744
760
  callbacksRef.current.onError?.(error, stateRef.current);
@@ -798,7 +814,6 @@ function VideoPlayerRuntime({
798
814
  setActiveStream(stream);
799
815
  setActiveSavedVideo(video);
800
816
  mediaFrameReportedRef.current = false;
801
- posterBridgeKeysRef.current.clear();
802
817
  sceneIndexRef.current = -1;
803
818
  setReplacementPending(stream != null);
804
819
  setState(video ? savedVideoState(video) : createVideoState());
@@ -917,7 +932,6 @@ function VideoPlayerRuntime({
917
932
  loopRef,
918
933
  sceneIndexRef,
919
934
  visualReadyRef,
920
- posterBridgeKeysRef,
921
935
  callbacksRef,
922
936
  setCurrentTime,
923
937
  setIsPlaying