@vanillaskyai/video 0.10.2 → 0.10.3

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +12 -7
  3. package/dist/check-runtime.js +2 -2
  4. package/dist/{chunk-6KZGF63O.js → chunk-3U2F7C7C.js} +53 -34
  5. package/dist/{chunk-5C6HZNHY.js → chunk-LJR5VLEI.js} +3 -2
  6. package/dist/{chunk-77VY4O7A.js → chunk-SI6CFFAA.js} +57 -10
  7. package/dist/{chunk-KHLO5OHT.js → chunk-UCXQORDZ.js} +1 -1
  8. package/dist/{chunk-VQH3JTQC.js → chunk-VVNZCY2U.js} +1 -1
  9. package/dist/{chunk-TVIU23OM.js → chunk-YZZSWGC3.js} +8 -3
  10. package/dist/{cinema-media-APDJS7SC.js → cinema-media-4JUYKPQI.js} +4 -4
  11. package/dist/{comparison-ZVE2DE7U.js → comparison-3QLMUC5V.js} +4 -4
  12. package/dist/{editorial-timeline-YIXXVJUV.js → editorial-timeline-IVE2P25N.js} +4 -4
  13. package/dist/{key-figure-I2C37FWV.js → key-figure-APD56LCO.js} +4 -4
  14. package/dist/{mobile-message-JSGBU6EX.js → mobile-message-WLK3WY5B.js} +4 -4
  15. package/dist/{quote-XH54G3FS.js → quote-Z6YET5BO.js} +4 -4
  16. package/dist/react.js +25 -13
  17. package/dist/{scene-video-backdrop-PCTARAAJ.js → scene-video-backdrop-OWUXPXHZ.js} +2 -2
  18. package/dist/server.js +302 -257
  19. package/docs/agent-integration.md +5 -4
  20. package/docs/getting-started.md +7 -5
  21. package/docs/media-and-audio.md +23 -17
  22. package/docs/production.md +7 -4
  23. package/docs/prompt-and-input.md +24 -15
  24. package/docs/provider-integration.md +2 -2
  25. package/docs/reference/protocol.md +7 -1
  26. package/docs/reference/provider-adapters.md +5 -3
  27. package/docs/testing.md +7 -2
  28. package/package.json +1 -1
  29. package/registry/items/backgrounds.json +2 -2
  30. package/registry/items/cinemaMedia.json +1 -1
  31. package/registry/items/comparison.json +1 -1
  32. package/registry/items/editorialTimeline.json +1 -1
  33. package/registry/items/keyFigure.json +1 -1
  34. package/registry/items/mobileMessage.json +1 -1
  35. package/registry/items/quote.json +1 -1
  36. package/starters/video-chat/README.md +18 -17
  37. package/starters/video-chat/package.json +1 -1
  38. package/starters/video-chat/providers/video.ts +5 -4
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ VanillaSky follows semantic versioning. This changelog begins with the 0.1 beta.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.10.3
8
+
9
+ - Remove the implicit documentary visual style from the chat interface; preserve explicitly supplied application styles and let the answer direct its own look.
10
+ - Default chat now plans an answer brief and narrated shots, with an immediate introduction followed by AI-first footage and relevant stock fallback. Creative direction adapts to the request; the model no longer chooses body templates, media sources, scene IDs, or completion commands. Explicit custom template registries retain their existing composition contract.
11
+ - Reserve the answer's ending before streaming body shots, retain valid narration when visuals are unavailable, and skip exact repeated opening or ending lines before requesting media.
12
+ - Fit short clips to measured narration within a bounded playback rate. If moving coverage runs out, use bounded recovery and an explicit waiting state instead of holding a finished frame while speech continues. Preserve deliberate pauses and audio activation across scenes.
13
+ - The optional starter video adapter uses Fal Turbo with five-second clips and intent-specific shot direction; providers, credentials, admission and spending limits remain application-owned.
14
+
7
15
  ## 0.10.2
8
16
 
9
17
  - Keep generated narration playing in Safari after a delayed opening-to-body handoff by reusing the permitted audio element; isolate late playback failures from subsequent lines.
package/README.md CHANGED
@@ -25,9 +25,10 @@ npx vanillasky doctor
25
25
  npm run dev
26
26
  ```
27
27
 
28
- Open the reported localhost URL. One text key gives you the complete chat with
29
- packaged templates and browser voice. No template setup or optional provider
30
- packages are required. If installation is interrupted, rerun the init command.
28
+ Open the reported localhost URL. One text key gives you the chat, an immediate introduction, and browser voice.
29
+ Add the video adapter for generated footage and optional stock for fallback.
30
+ Without a media provider, answers retain narration and subtitles with an
31
+ unavailable-visual state. If installation is interrupted, rerun the init command.
31
32
 
32
33
  Optional upgrades keep the same client:
33
34
 
@@ -80,11 +81,15 @@ conversation and playback lifecycle. Edit the generated server when you want a
80
81
  different provider. The [provider guide](docs/provider-integration.md) explains
81
82
  both boundaries.
82
83
 
83
- ## Templates are the built-in fallback
84
+ ## An immediate intro, then moving footage
84
85
 
85
- The packaged visual templates are the fast, inexpensive default and require no
86
- copied source tree. Add a video provider when you want generated footage; the
87
- same conversation can mix both modes.
86
+ The default chat uses a template introduction while its first shot prepares,
87
+ then generated footage with narration and subtitles. Relevant stock is the
88
+ fallback when generation is unavailable or fails. The planner adapts its
89
+ visible actions and spoken beats to explanations, stories, comedy, imagination,
90
+ and practical requests. It does not choose body templates.
91
+
92
+ Packaged templates remain available for custom compositions and source ownership.
88
93
 
89
94
  Copy template source only when you want to own and edit it:
90
95
 
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  VideoFrame
3
- } from "./chunk-6KZGF63O.js";
3
+ } from "./chunk-3U2F7C7C.js";
4
4
  import "./chunk-224QNWRA.js";
5
- import "./chunk-TVIU23OM.js";
5
+ import "./chunk-YZZSWGC3.js";
6
6
  import "./chunk-SPVTJH3F.js";
7
7
  import "./chunk-R3XAOMKP.js";
8
8
  import "./chunk-73NTSFFI.js";
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  ExternalVideoBackdropProvider,
7
7
  resolveDensity
8
- } from "./chunk-TVIU23OM.js";
8
+ } from "./chunk-YZZSWGC3.js";
9
9
  import {
10
10
  resolveVideoTimeline
11
11
  } from "./chunk-SPVTJH3F.js";
@@ -209,7 +209,7 @@ var SceneBoundary = class extends Component {
209
209
  }
210
210
  };
211
211
  var SCENE_TRANSITION_SECONDS = 0.3;
212
- var SceneVideoBackdrop = lazy(() => import("./scene-video-backdrop-PCTARAAJ.js").then((module) => ({ default: module.SceneVideoBackdrop })));
212
+ var SceneVideoBackdrop = lazy(() => import("./scene-video-backdrop-OWUXPXHZ.js").then((module) => ({ default: module.SceneVideoBackdrop })));
213
213
  var MEDIA_PREROLL_SECONDS = 1.2;
214
214
  var CONTIGUITY_ULP_FACTOR = 4;
215
215
  var subscribeToDecoderPolicy = () => () => {
@@ -265,6 +265,7 @@ function SceneLayer({
265
265
  width,
266
266
  height,
267
267
  playing,
268
+ preparingNarration,
268
269
  mediaAudioMuted,
269
270
  mediaAudioVolume,
270
271
  layer,
@@ -283,7 +284,8 @@ function SceneLayer({
283
284
  mode: externalVideoBackdrop,
284
285
  audioMuted: mediaAudioMuted || !playing,
285
286
  audioVolume: mediaAudioVolume,
286
- children: /* @__PURE__ */ jsx2(
287
+ preparingNarration,
288
+ children: /* @__PURE__ */ jsxs(
287
289
  "div",
288
290
  {
289
291
  "data-scene-layer": layer,
@@ -304,37 +306,48 @@ function SceneLayer({
304
306
  "--vanillasky-transition-semantic-visibility": layer === "incoming" ? "hidden" : "visible",
305
307
  "--vanillasky-template-surface": externalVideoBackdrop !== false ? "transparent" : void 0
306
308
  },
307
- children: template ? /* @__PURE__ */ jsx2(SceneBoundary, { scene: range.scene, onFramePresented, children: /* @__PURE__ */ jsxs(Suspense, { fallback: /* @__PURE__ */ jsx2(
308
- "div",
309
- {
310
- "data-template-loading": range.scene.templateId,
311
- style: { position: "absolute", inset: 0 }
312
- }
313
- ), children: [
314
- /* @__PURE__ */ jsx2(PresentedScene, { notify: onFramePresented }),
315
- createElement(template.component, {
316
- variables: {
317
- ...template.defaults ?? getTemplateDefaults(template.schema),
318
- ...range.scene.variables,
319
- ...mediaFailed ? { mediaUrl: "", mediaPoster: "", mediaType: "gradient" } : {},
320
- ...recoveryTitle ? { title: recoveryTitle } : {}
321
- },
322
- style: config.style,
323
- progress,
324
- motionProgress,
325
- beatIntensity: 0,
326
- width,
327
- height,
328
- textArchetype: range.scene.textArchetype ?? config.style.defaultTextArchetype,
329
- backgroundEffect: range.scene.backgroundEffect ?? config.style.defaultBackgroundEffect,
330
- safeZone: scaleSafeZone(
331
- getSafeZone(config.orientation),
332
- resolveDensity(config.style.density).safeZoneScale
333
- ),
334
- sceneDuration: duration,
335
- isPlaying: playing
336
- })
337
- ] }) }, range.scene.id) : /* @__PURE__ */ jsx2(SafeScene, { scene: range.scene, onFramePresented })
309
+ children: [
310
+ range.scene.templateId === "cinemaMedia" && !recoveryTitle && (mediaFailed || !String(range.scene.variables.mediaUrl || "").trim()) && /* @__PURE__ */ jsx2(
311
+ "div",
312
+ {
313
+ role: "status",
314
+ "data-media-unavailable": "true",
315
+ style: { position: "absolute", inset: 0, zIndex: 2, display: "grid", placeContent: "center", color: "#bbb", font: "14px system-ui", background: "#000" },
316
+ children: "Visual unavailable"
317
+ }
318
+ ),
319
+ template ? /* @__PURE__ */ jsx2(SceneBoundary, { scene: range.scene, onFramePresented, children: /* @__PURE__ */ jsxs(Suspense, { fallback: /* @__PURE__ */ jsx2(
320
+ "div",
321
+ {
322
+ "data-template-loading": range.scene.templateId,
323
+ style: { position: "absolute", inset: 0 }
324
+ }
325
+ ), children: [
326
+ /* @__PURE__ */ jsx2(PresentedScene, { notify: onFramePresented }),
327
+ createElement(template.component, {
328
+ variables: {
329
+ ...template.defaults ?? getTemplateDefaults(template.schema),
330
+ ...range.scene.variables,
331
+ ...mediaFailed ? { mediaUrl: "", mediaPoster: "", mediaType: "gradient" } : {},
332
+ ...recoveryTitle ? { title: recoveryTitle } : {}
333
+ },
334
+ style: config.style,
335
+ progress,
336
+ motionProgress,
337
+ beatIntensity: 0,
338
+ width,
339
+ height,
340
+ textArchetype: range.scene.textArchetype ?? config.style.defaultTextArchetype,
341
+ backgroundEffect: range.scene.backgroundEffect ?? config.style.defaultBackgroundEffect,
342
+ safeZone: scaleSafeZone(
343
+ getSafeZone(config.orientation),
344
+ resolveDensity(config.style.density).safeZoneScale
345
+ ),
346
+ sceneDuration: duration,
347
+ isPlaying: playing
348
+ })
349
+ ] }) }, range.scene.id) : /* @__PURE__ */ jsx2(SafeScene, { scene: range.scene, onFramePresented })
350
+ ]
338
351
  }
339
352
  )
340
353
  }
@@ -348,6 +361,7 @@ function VideoFrame({
348
361
  width,
349
362
  height,
350
363
  playing = false,
364
+ preparingNarration = false,
351
365
  mediaAudioMuted = true,
352
366
  mediaAudioVolume = 1,
353
367
  className,
@@ -550,7 +564,9 @@ function VideoFrame({
550
564
  mediaPosition: String(persistentVideoRange.scene.variables.mediaPosition || "center"),
551
565
  backgroundEffect: persistentVideoRange.scene.backgroundEffect ?? config.style.defaultBackgroundEffect,
552
566
  progress: persistentVideoRange.scene.id === active.scene.id ? rawProgress : 0,
567
+ sceneDuration: persistentVideoRange.end - persistentVideoRange.start,
553
568
  isPlaying: persistentVideoRange.scene.id === active.scene.id && playing,
569
+ preparingNarration,
554
570
  muted: mediaAudioMuted || persistentVideoRange.scene.id !== active.scene.id || !playing,
555
571
  volume: mediaAudioVolume,
556
572
  playbackId: persistentVideoRange.scene.id,
@@ -583,6 +599,7 @@ function VideoFrame({
583
599
  width: canvas.width,
584
600
  height: canvas.height,
585
601
  playing,
602
+ preparingNarration,
586
603
  mediaAudioMuted,
587
604
  mediaAudioVolume,
588
605
  layer: previewingNext ? "outgoing" : "active",
@@ -604,6 +621,7 @@ function VideoFrame({
604
621
  width: canvas.width,
605
622
  height: canvas.height,
606
623
  playing: false,
624
+ preparingNarration: false,
607
625
  mediaAudioMuted,
608
626
  mediaAudioVolume,
609
627
  layer: "incoming",
@@ -627,6 +645,7 @@ function VideoFrame({
627
645
  width: canvas.width,
628
646
  height: canvas.height,
629
647
  playing,
648
+ preparingNarration,
630
649
  mediaAudioMuted,
631
650
  mediaAudioVolume,
632
651
  layer: "active",
@@ -3,13 +3,13 @@ import {
3
3
  SceneVideoBackdrop,
4
4
  getBackgroundTransform,
5
5
  resolveMediaPosition
6
- } from "./chunk-77VY4O7A.js";
6
+ } from "./chunk-SI6CFFAA.js";
7
7
  import {
8
8
  resolveMediaType
9
9
  } from "./chunk-224QNWRA.js";
10
10
  import {
11
11
  useExternalVideoBackdrop
12
- } from "./chunk-TVIU23OM.js";
12
+ } from "./chunk-YZZSWGC3.js";
13
13
 
14
14
  // src/visual-system/scene-templates/scene-background.tsx
15
15
  import { useEffect, useState } from "react";
@@ -157,6 +157,7 @@ var SceneBackground = ({
157
157
  mediaPosition,
158
158
  backgroundEffect,
159
159
  progress,
160
+ sceneDuration,
160
161
  beatIntensity,
161
162
  isPlaying,
162
163
  onReady: () => setMediaPaint("ready"),
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  darken,
3
3
  resolveTokens,
4
- useMediaAudio
5
- } from "./chunk-TVIU23OM.js";
4
+ useMediaAudio,
5
+ useNarrationPreroll
6
+ } from "./chunk-YZZSWGC3.js";
6
7
 
7
8
  // src/visual-system/scene-templates/scene-video-backdrop.tsx
8
- import { useEffect, useRef, useState } from "react";
9
+ import { useCallback, useEffect, useRef, useState } from "react";
9
10
 
10
11
  // src/visual-system/scene-templates/color-utils.ts
11
12
  import React from "react";
@@ -228,6 +229,8 @@ var SceneVideoBackdrop = ({
228
229
  mediaPosition = "center",
229
230
  backgroundEffect,
230
231
  progress,
232
+ sceneDuration,
233
+ preparingNarration = false,
231
234
  beatIntensity = 0,
232
235
  isPlaying,
233
236
  muted,
@@ -240,15 +243,44 @@ var SceneVideoBackdrop = ({
240
243
  onError
241
244
  }) => {
242
245
  const inheritedAudio = useMediaAudio();
246
+ const inheritedPreroll = useNarrationPreroll();
247
+ const rewindPreroll = preparingNarration || inheritedPreroll;
243
248
  const resolvedMuted = muted ?? inheritedAudio.muted;
244
249
  const resolvedVolume = volume ?? inheritedAudio.volume;
245
250
  const resolvedPosition = resolveMediaPosition(mediaPosition);
246
251
  const bgTransform = getBackgroundTransform(backgroundEffect, progress, beatIntensity);
247
252
  const [decodedVideoUrl, setDecodedVideoUrl] = useState();
253
+ const [waitingKey, setWaitingKey] = useState();
254
+ const [exhaustedKey, setExhaustedKey] = useState();
255
+ const continuityReplay = useRef(void 0);
248
256
  const videoRef = useRef(null);
249
257
  const startedVideoUrl = useRef(void 0);
250
258
  const startedPlaybackId = useRef(void 0);
251
259
  const videoPresentationKey = `${playbackId}\0${mediaUrl}`;
260
+ const presentationRef = useRef({ key: videoPresentationKey, playing: isPlaying });
261
+ presentationRef.current = { key: videoPresentationKey, playing: isPlaying };
262
+ const unavailable = () => {
263
+ if (presentationRef.current.key === videoPresentationKey && presentationRef.current.playing) {
264
+ setExhaustedKey(videoPresentationKey);
265
+ onError?.();
266
+ }
267
+ };
268
+ const fitDuration = useCallback((video) => {
269
+ video.playbackRate = (resolvedMuted || video.preservesPitch === true) && sceneDuration && Number.isFinite(video.duration) && video.duration > 0 ? Math.max(0.75, Math.min(1, video.duration / (sceneDuration + 0.2))) : 1;
270
+ }, [resolvedMuted, sceneDuration]);
271
+ useEffect(() => {
272
+ if (videoRef.current) fitDuration(videoRef.current);
273
+ }, [fitDuration]);
274
+ const continueMotion = (video) => {
275
+ if (!isPlaying) return;
276
+ if (!resolvedMuted || continuityReplay.current === videoPresentationKey) {
277
+ setExhaustedKey(videoPresentationKey);
278
+ return;
279
+ }
280
+ continuityReplay.current = videoPresentationKey;
281
+ video.currentTime = 0;
282
+ void video.play().catch(unavailable);
283
+ };
252
284
  useEffect(() => {
253
285
  const video = videoRef.current;
254
286
  if (!video) return;
@@ -273,21 +305,21 @@ var SceneVideoBackdrop = ({
273
305
  if (!video) return;
274
306
  if (!isPlaying) {
275
307
  video.pause();
308
+ if (rewindPreroll && video.currentTime > 0) video.currentTime = 0;
276
309
  return;
277
310
  }
278
311
  if (startedPlaybackId.current === playbackId) {
279
- if (!video.ended) void video.play().catch(() => {
280
- });
312
+ if (video.ended) continueMotion(video);
313
+ else void video.play().catch(unavailable);
281
314
  return;
282
315
  }
283
316
  const changingSource = startedVideoUrl.current !== void 0 && startedVideoUrl.current !== mediaUrl;
284
- video.playbackRate = 1;
317
+ fitDuration(video);
285
318
  if (!changingSource && video.currentTime > 0) video.currentTime = 0;
286
- video.play().catch(() => {
287
- });
319
+ video.play().catch(unavailable);
288
320
  startedVideoUrl.current = mediaUrl;
289
321
  startedPlaybackId.current = playbackId;
290
- }, [isPlaying, mediaUrl, playbackId]);
322
+ }, [isPlaying, mediaUrl, playbackId, rewindPreroll]);
291
323
  const mediaStyle = {
292
324
  position: "absolute",
293
325
  inset: 0,
@@ -349,6 +381,15 @@ var SceneVideoBackdrop = ({
349
381
  },
350
382
  posterPlane.presentationKey
351
383
  )),
384
+ (exhaustedKey === videoPresentationKey || waitingKey === videoPresentationKey) && /* @__PURE__ */ jsx(
385
+ "div",
386
+ {
387
+ role: "status",
388
+ "data-media-continuity": exhaustedKey === videoPresentationKey ? "exhausted" : "waiting",
389
+ style: { position: "absolute", inset: 0, zIndex: 3, background: "#000", color: "#bbb", display: "grid", placeContent: "center", font: "14px system-ui" },
390
+ children: exhaustedKey === videoPresentationKey ? "Visual unavailable" : "Loading visual"
391
+ }
392
+ ),
352
393
  /* @__PURE__ */ jsx(
353
394
  "video",
354
395
  {
@@ -359,6 +400,12 @@ var SceneVideoBackdrop = ({
359
400
  loop: false,
360
401
  playsInline: true,
361
402
  preload: "auto",
403
+ onLoadedMetadata: (event) => fitDuration(event.currentTarget),
404
+ onEnded: (event) => continueMotion(event.currentTarget),
405
+ onWaiting: () => {
406
+ if (isPlaying) setWaitingKey(videoPresentationKey);
407
+ },
408
+ onPlaying: () => setWaitingKey(void 0),
362
409
  onLoadedData: (event) => {
363
410
  const video = event.currentTarget;
364
411
  const markPresented = () => {
@@ -375,7 +422,7 @@ var SceneVideoBackdrop = ({
375
422
  onError,
376
423
  "data-media-position": mediaPosition,
377
424
  "data-video-backdrop": persistent ? "persistent" : "scene",
378
- style: mediaStyle
425
+ style: { ...mediaStyle, visibility: exhaustedKey === videoPresentationKey || waitingKey === videoPresentationKey ? "hidden" : void 0 }
379
426
  }
380
427
  )
381
428
  ] });
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SceneBackground,
3
3
  getMediaBackgroundProps
4
- } from "./chunk-5C6HZNHY.js";
4
+ } from "./chunk-LJR5VLEI.js";
5
5
  import {
6
6
  editorialFont
7
7
  } from "./chunk-4YM2M62S.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SceneBackground,
3
3
  getMediaBackgroundProps
4
- } from "./chunk-5C6HZNHY.js";
4
+ } from "./chunk-LJR5VLEI.js";
5
5
 
6
6
  // src/visual-system/scene-templates/cinema-media.tsx
7
7
  import { jsx } from "react/jsx-runtime";
@@ -8,11 +8,12 @@ function ExternalVideoBackdropProvider({
8
8
  mode,
9
9
  audioMuted = true,
10
10
  audioVolume = 1,
11
+ preparingNarration = false,
11
12
  children
12
13
  }) {
13
14
  const value = React.useMemo(
14
- () => ({ mode, audioMuted, audioVolume }),
15
- [mode, audioMuted, audioVolume]
15
+ () => ({ mode, audioMuted, audioVolume, preparingNarration }),
16
+ [mode, audioMuted, audioVolume, preparingNarration]
16
17
  );
17
18
  return /* @__PURE__ */ jsx(BackdropContext.Provider, { value, children });
18
19
  }
@@ -23,6 +24,9 @@ function useMediaAudio() {
23
24
  const { audioMuted, audioVolume } = React.useContext(BackdropContext);
24
25
  return { muted: audioMuted, volume: audioVolume };
25
26
  }
27
+ function useNarrationPreroll() {
28
+ return React.useContext(BackdropContext).preparingNarration === true;
29
+ }
26
30
 
27
31
  // src/visual-system/scene-templates/tokens.ts
28
32
  var STYLE_PRESETS = {
@@ -142,5 +146,6 @@ export {
142
146
  darken,
143
147
  ExternalVideoBackdropProvider,
144
148
  useExternalVideoBackdrop,
145
- useMediaAudio
149
+ useMediaAudio,
150
+ useNarrationPreroll
146
151
  };
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  MediaScene,
3
3
  MediaSceneTemplate
4
- } from "./chunk-VQH3JTQC.js";
5
- import "./chunk-5C6HZNHY.js";
6
- import "./chunk-77VY4O7A.js";
4
+ } from "./chunk-VVNZCY2U.js";
5
+ import "./chunk-LJR5VLEI.js";
6
+ import "./chunk-SI6CFFAA.js";
7
7
  import "./chunk-224QNWRA.js";
8
- import "./chunk-TVIU23OM.js";
8
+ import "./chunk-YZZSWGC3.js";
9
9
  export {
10
10
  MediaScene,
11
11
  MediaSceneTemplate
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  EditorialSurface
3
- } from "./chunk-KHLO5OHT.js";
4
- import "./chunk-5C6HZNHY.js";
5
- import "./chunk-77VY4O7A.js";
3
+ } from "./chunk-UCXQORDZ.js";
4
+ import "./chunk-LJR5VLEI.js";
5
+ import "./chunk-SI6CFFAA.js";
6
6
  import {
7
7
  editorialLabel,
8
8
  fade
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  hasSceneMedia
12
12
  } from "./chunk-224QNWRA.js";
13
- import "./chunk-TVIU23OM.js";
13
+ import "./chunk-YZZSWGC3.js";
14
14
 
15
15
  // src/visual-system/scene-templates/comparison.tsx
16
16
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  EditorialSurface
3
- } from "./chunk-KHLO5OHT.js";
4
- import "./chunk-5C6HZNHY.js";
5
- import "./chunk-77VY4O7A.js";
3
+ } from "./chunk-UCXQORDZ.js";
4
+ import "./chunk-LJR5VLEI.js";
5
+ import "./chunk-SI6CFFAA.js";
6
6
  import {
7
7
  fade
8
8
  } from "./chunk-4YM2M62S.js";
9
9
  import "./chunk-224QNWRA.js";
10
- import "./chunk-TVIU23OM.js";
10
+ import "./chunk-YZZSWGC3.js";
11
11
 
12
12
  // src/visual-system/scene-templates/editorial-timeline.tsx
13
13
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  EditorialSurface
3
- } from "./chunk-KHLO5OHT.js";
4
- import "./chunk-5C6HZNHY.js";
5
- import "./chunk-77VY4O7A.js";
3
+ } from "./chunk-UCXQORDZ.js";
4
+ import "./chunk-LJR5VLEI.js";
5
+ import "./chunk-SI6CFFAA.js";
6
6
  import {
7
7
  editorialLabel,
8
8
  fade
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  hasSceneMedia
12
12
  } from "./chunk-224QNWRA.js";
13
- import "./chunk-TVIU23OM.js";
13
+ import "./chunk-YZZSWGC3.js";
14
14
 
15
15
  // src/visual-system/scene-templates/key-figure.tsx
16
16
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  MediaScene
3
- } from "./chunk-VQH3JTQC.js";
4
- import "./chunk-5C6HZNHY.js";
3
+ } from "./chunk-VVNZCY2U.js";
4
+ import "./chunk-LJR5VLEI.js";
5
5
  import {
6
6
  spring
7
- } from "./chunk-77VY4O7A.js";
7
+ } from "./chunk-SI6CFFAA.js";
8
8
  import {
9
9
  editorialFont
10
10
  } from "./chunk-4YM2M62S.js";
11
11
  import "./chunk-224QNWRA.js";
12
- import "./chunk-TVIU23OM.js";
12
+ import "./chunk-YZZSWGC3.js";
13
13
 
14
14
  // src/visual-system/scene-templates/mobile-message.tsx
15
15
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  EditorialSurface
3
- } from "./chunk-KHLO5OHT.js";
4
- import "./chunk-5C6HZNHY.js";
5
- import "./chunk-77VY4O7A.js";
3
+ } from "./chunk-UCXQORDZ.js";
4
+ import "./chunk-LJR5VLEI.js";
5
+ import "./chunk-SI6CFFAA.js";
6
6
  import {
7
7
  fade
8
8
  } from "./chunk-4YM2M62S.js";
9
9
  import {
10
10
  hasSceneMedia
11
11
  } from "./chunk-224QNWRA.js";
12
- import "./chunk-TVIU23OM.js";
12
+ import "./chunk-YZZSWGC3.js";
13
13
 
14
14
  // src/visual-system/scene-templates/quote.tsx
15
15
  import { jsx, jsxs } from "react/jsx-runtime";
package/dist/react.js CHANGED
@@ -40,9 +40,9 @@ import {
40
40
  VideoFrame,
41
41
  getDimensions,
42
42
  sceneReadinessKey
43
- } from "./chunk-6KZGF63O.js";
43
+ } from "./chunk-3U2F7C7C.js";
44
44
  import "./chunk-224QNWRA.js";
45
- import "./chunk-TVIU23OM.js";
45
+ import "./chunk-YZZSWGC3.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-APDJS7SC.js").then((module) => ({ default: module.MediaSceneTemplate })),
97
+ "cinemaMedia": () => import("./cinema-media-4JUYKPQI.js").then((module) => ({ default: module.MediaSceneTemplate })),
98
98
  "chapterTitle": () => import("./chapter-title-YRLIQY3V.js").then((module) => ({ default: module.TitleSceneTemplate })),
99
- "editorialTimeline": () => import("./editorial-timeline-YIXXVJUV.js").then((module) => ({ default: module.TimelineSceneTemplate })),
100
- "mobileMessage": () => import("./mobile-message-JSGBU6EX.js").then((module) => ({ default: module.NotificationSceneTemplate })),
101
- "comparison": () => import("./comparison-ZVE2DE7U.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
102
- "quote": () => import("./quote-XH54G3FS.js").then((module) => ({ default: module.QuoteSceneTemplate })),
103
- "keyFigure": () => import("./key-figure-I2C37FWV.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
99
+ "editorialTimeline": () => import("./editorial-timeline-IVE2P25N.js").then((module) => ({ default: module.TimelineSceneTemplate })),
100
+ "mobileMessage": () => import("./mobile-message-WLK3WY5B.js").then((module) => ({ default: module.NotificationSceneTemplate })),
101
+ "comparison": () => import("./comparison-3QLMUC5V.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
102
+ "quote": () => import("./quote-Z6YET5BO.js").then((module) => ({ default: module.QuoteSceneTemplate })),
103
+ "keyFigure": () => import("./key-figure-APD56LCO.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
104
104
  };
105
105
 
106
106
  // src/visual-system/catalog/builtin-player.generated.ts
@@ -754,6 +754,14 @@ function VideoPlayerRuntime({
754
754
  }
755
755
  }, []);
756
756
  const playbackEndedRef = useRef(false);
757
+ let mediaPlaying = isPlaying;
758
+ if (sceneIndexRef.current >= 0) {
759
+ try {
760
+ mediaPlaying = isPlaying && narrationReady?.() !== false;
761
+ } catch {
762
+ mediaPlaying = false;
763
+ }
764
+ }
757
765
  stateRef.current = state;
758
766
  timeRef.current = currentTime;
759
767
  callbacksRef.current = { onComplete, onPlaybackEnd, onError, onSceneChange, narrationReady, narrationTime, onFramePresented, onMediaFramePresented, onStallChange, onStateChange };
@@ -1131,7 +1139,8 @@ function VideoPlayerRuntime({
1131
1139
  time: showStartPoster ? posterTime : currentTime,
1132
1140
  width: dimensions.width,
1133
1141
  height: dimensions.height,
1134
- playing: isPlaying,
1142
+ playing: mediaPlaying,
1143
+ preparingNarration: isPlaying && !mediaPlaying,
1135
1144
  mediaAudioMuted: !nativeMediaAudio || isMuted,
1136
1145
  mediaAudioVolume: nativeMediaVolume,
1137
1146
  style: {
@@ -1243,7 +1252,7 @@ var VideoError = class extends Error {
1243
1252
  function recoverSceneMedia(scene) {
1244
1253
  if (scene.templateId === "cinemaMedia") {
1245
1254
  const title = scene.variables.fallbackText;
1246
- if (typeof title !== "string" || !title.trim() || [...title].length > 65) return void 0;
1255
+ if (typeof title !== "string" || !title.trim() || [...title].length > 65) return { ...scene, variables: { mediaType: "video", mediaUrl: "" } };
1247
1256
  return { ...scene, templateId: "chapterTitle", variables: { title: title.trim() } };
1248
1257
  }
1249
1258
  if (!["comparison", "editorialTimeline", "quote", "keyFigure"].includes(scene.templateId)) return void 0;
@@ -1397,7 +1406,11 @@ function useNarration(options) {
1397
1406
  if (!optionsRef.current.voice.getCurrentTime) return void 0;
1398
1407
  const time = currentRef.current ? optionsRef.current.voice.getCurrentTime() : void 0;
1399
1408
  if (currentRef.current && readyRef.current && time === void 0) return void 0;
1400
- if (time !== void 0 && Number.isFinite(time)) clockRef.current = Math.min(group?.totalSeconds ?? Infinity, Math.max(group?.offsetSeconds ?? 0, time));
1409
+ if (time !== void 0 && Number.isFinite(time)) {
1410
+ const end = group?.totalSeconds ?? scene.timing.fixedDuration ?? Infinity;
1411
+ const limit = currentRef.current ? Math.max(0, end - 0.01) : end;
1412
+ clockRef.current = Math.min(limit, Math.max(group?.offsetSeconds ?? 0, time));
1413
+ }
1401
1414
  return clockRef.current;
1402
1415
  }, []);
1403
1416
  const isReady = useCallback(() => optionsRef.current.enabled === false || readyRef.current || clockRef.current !== void 0 && clockRef.current >= (groupRef.current?.offsetSeconds ?? 0) + 0.04, []);
@@ -3367,8 +3380,7 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3367
3380
  const sessionOrientation = options.orientation ?? viewportOrientation;
3368
3381
  const { chat, restoreSession } = useVideoChatSession({
3369
3382
  ...options,
3370
- orientation: sessionOrientation,
3371
- style: { generatedLook: "Natural light, restrained camera movement, documentary realism, consistent natural color. No embedded text.", ...options.style }
3383
+ orientation: sessionOrientation
3372
3384
  });
3373
3385
  const instanceId = useId();
3374
3386
  const historyId = `${instanceId}-history`;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SceneVideoBackdrop
3
- } from "./chunk-77VY4O7A.js";
4
- import "./chunk-TVIU23OM.js";
3
+ } from "./chunk-SI6CFFAA.js";
4
+ import "./chunk-YZZSWGC3.js";
5
5
  export {
6
6
  SceneVideoBackdrop
7
7
  };