@vanillaskyai/video 0.1.1 → 0.3.0

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 (52) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/PUBLIC-API.md +20 -2
  3. package/README.md +14 -6
  4. package/dist/{builtin-server-F7YXRVCH.js → builtin-server-BRTZN4Q7.js} +2 -2
  5. package/dist/check-runtime.js +1 -1
  6. package/dist/{chunk-EVG2ZYIY.js → chunk-3O7OMMMF.js} +7 -4
  7. package/dist/{chunk-GRMUV7QY.js → chunk-BKF3A357.js} +1 -1
  8. package/dist/{chunk-YC5MFXA5.js → chunk-GR4I3MN2.js} +37 -15
  9. package/dist/{chunk-A2X45FET.js → chunk-I5YWVQ5R.js} +1 -1
  10. package/dist/{chunk-ERY4S6QV.js → chunk-JW47XCRL.js} +6 -1
  11. package/dist/{chunk-XB7LH4J2.js → chunk-QTMHS7JD.js} +2 -1
  12. package/dist/{chunk-W2XJKG6C.js → chunk-RBQN3VSY.js} +2 -2
  13. package/dist/{chunk-OOPIETLW.js → chunk-S5ZU5WUU.js} +1 -1
  14. package/dist/{chunk-ABPKM4GK.js → chunk-STIFILQG.js} +1 -1
  15. package/dist/chunk-WHPZED7X.js +57 -0
  16. package/dist/{chunk-CHLGW22F.js → chunk-XAMUOSX7.js} +3 -3
  17. package/dist/{chunk-IXCUY3B3.js → chunk-XYZOJ5NZ.js} +195 -52
  18. package/dist/cli.js +5 -5
  19. package/dist/{compose-video-CN3VSWST.js → compose-video-TQOHDXNW.js} +4 -4
  20. package/dist/{events-_wfjbmEp.d.ts → events-CTIsANzz.d.ts} +2 -2
  21. package/dist/index.d.ts +2 -2
  22. package/dist/{kit-BMNcHqa9.d.ts → kit-DA2cfJ96.d.ts} +1 -1
  23. package/dist/react.d.ts +7 -4
  24. package/dist/react.js +226 -30
  25. package/dist/server.d.ts +21 -4
  26. package/dist/server.js +205 -18
  27. package/dist/system-prompt-NMYHLAMJ.js +10 -0
  28. package/dist/template-catalog.js +1 -1
  29. package/dist/templates.d.ts +3 -3
  30. package/dist/test.d.ts +3 -2
  31. package/dist/test.js +3 -3
  32. package/dist/{text-stream-6XHC7J47.js → text-stream-UPGUD2TD.js} +2 -2
  33. package/dist/{types-B-PZR6G_.d.ts → types-CkO2EYr4.d.ts} +8 -4
  34. package/docs/concepts.md +11 -1
  35. package/docs/custom-templates.md +34 -6
  36. package/docs/customization.md +2 -1
  37. package/docs/errors.md +3 -1
  38. package/docs/getting-started.md +13 -10
  39. package/docs/input-and-first-scene.md +17 -6
  40. package/docs/integrate-nextjs.md +6 -3
  41. package/docs/media-and-audio.md +73 -14
  42. package/docs/prompt-and-input.md +27 -5
  43. package/docs/provider-integration.md +45 -2
  44. package/docs/reference/protocol.md +9 -0
  45. package/examples/nextjs-quickstart/README.md +3 -1
  46. package/examples/nextjs-quickstart/package.json +3 -2
  47. package/examples/nextjs-quickstart/src/app/api/video/route.ts +1 -1
  48. package/package.json +1 -1
  49. package/registry/items/brandMessage.json +1 -2
  50. package/registry/items/media.json +2 -1
  51. package/dist/chunk-FQ47WXAJ.js +0 -38
  52. package/dist/system-prompt-A6UXNTPK.js +0 -8
package/dist/react.js CHANGED
@@ -2,14 +2,14 @@ import {
2
2
  VIDEO_STREAM_CONTENT_TYPE,
3
3
  VIDEO_STREAM_HEADER,
4
4
  decodeVideoSse
5
- } from "./chunk-OOPIETLW.js";
5
+ } from "./chunk-S5ZU5WUU.js";
6
6
  import {
7
7
  applyVideoEvent,
8
8
  createVideoState
9
- } from "./chunk-ABPKM4GK.js";
9
+ } from "./chunk-STIFILQG.js";
10
10
  import {
11
11
  safePublicDiagnostic
12
- } from "./chunk-ERY4S6QV.js";
12
+ } from "./chunk-JW47XCRL.js";
13
13
  import {
14
14
  parseVideo
15
15
  } from "./chunk-GHJEAP4O.js";
@@ -24,12 +24,12 @@ import {
24
24
  BUILTIN_TEMPLATE_CAPABILITIES,
25
25
  BUILTIN_TEMPLATE_CATALOG,
26
26
  getBuiltinTemplateMetadata
27
- } from "./chunk-CHLGW22F.js";
27
+ } from "./chunk-XAMUOSX7.js";
28
28
  import "./chunk-2E6T633S.js";
29
29
  import {
30
30
  VideoFrame,
31
31
  getDimensions
32
- } from "./chunk-XB7LH4J2.js";
32
+ } from "./chunk-QTMHS7JD.js";
33
33
  import {
34
34
  fontStack
35
35
  } from "./chunk-FNAEQ7QO.js";
@@ -144,6 +144,7 @@ function preloadBuiltinTemplate(id) {
144
144
 
145
145
  // src/player/video-player.tsx
146
146
  import { jsx, jsxs } from "react/jsx-runtime";
147
+ var MINIMUM_GENERATION_INTRO_MS = 3e3;
147
148
  function savedVideoState(video) {
148
149
  return {
149
150
  ...createVideoState(),
@@ -155,6 +156,7 @@ function VideoPlayerRuntime({
155
156
  kit,
156
157
  stream,
157
158
  video,
159
+ playbackMode,
158
160
  autoPlay = true,
159
161
  startMuted = true,
160
162
  width,
@@ -167,33 +169,62 @@ function VideoPlayerRuntime({
167
169
  onError,
168
170
  onStateChange
169
171
  }) {
170
- const [reducedMotion] = useState(
172
+ const [reducedMotion, setReducedMotion] = useState(
171
173
  () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
172
174
  );
173
- const [isPlaying, setIsPlaying] = useState(() => autoPlay && !reducedMotion);
174
- const [isMuted, setIsMuted] = useState(startMuted);
175
+ const [audioUnlocked, setAudioUnlocked] = useState(false);
176
+ const resolvedStartMuted = playbackMode === "muted-autoplay" ? true : playbackMode ? false : startMuted;
177
+ const shouldAutoPlay = playbackMode === "manual" ? false : playbackMode === "autoplay-after-interaction" ? audioUnlocked : playbackMode === "muted-autoplay" || playbackMode === "autoplay-with-sound" ? true : autoPlay;
178
+ const autoStartGeneration = Boolean(playbackMode && stream && shouldAutoPlay && !reducedMotion);
179
+ const [isPlaying, setIsPlaying] = useState(() => shouldAutoPlay && !reducedMotion && !autoStartGeneration);
180
+ const [isMuted, setIsMuted] = useState(resolvedStartMuted);
175
181
  const [isFullscreen, setIsFullscreen] = useState(false);
176
182
  const [state, setState] = useState(() => video ? savedVideoState(video) : createVideoState());
177
183
  const [currentTime, setCurrentTime] = useState(0);
184
+ const [activeStream, setActiveStream] = useState(stream);
185
+ const [replacementPending, setReplacementPending] = useState(false);
186
+ const [startRequested, setStartRequested] = useState(autoStartGeneration);
187
+ const [introPlaying, setIntroPlaying] = useState(autoStartGeneration);
188
+ const [generationIntroComplete, setGenerationIntroComplete] = useState(() => !playbackMode || !stream);
178
189
  const [observedWidth, setObservedWidth] = useState(0);
179
190
  const containerRef = useRef(null);
180
191
  const stateRef = useRef(state);
181
192
  const timeRef = useRef(currentTime);
182
193
  const audioRef = useRef(null);
194
+ const introStartedAtRef = useRef(autoStartGeneration ? performance.now() : null);
183
195
  const callbacksRef = useRef({ onComplete, onError, onStateChange });
184
196
  stateRef.current = state;
185
197
  timeRef.current = currentTime;
186
198
  callbacksRef.current = { onComplete, onError, onStateChange };
199
+ if (stream !== activeStream) {
200
+ const autoStartReplacement = Boolean(playbackMode && stream && shouldAutoPlay && !reducedMotion);
201
+ setActiveStream(stream);
202
+ setReplacementPending(stream != null);
203
+ setState(video ? savedVideoState(video) : createVideoState());
204
+ setCurrentTime(0);
205
+ setIsMuted(resolvedStartMuted);
206
+ setIsPlaying(shouldAutoPlay && !reducedMotion && !autoStartReplacement);
207
+ setStartRequested(autoStartReplacement);
208
+ setIntroPlaying(autoStartReplacement);
209
+ setGenerationIntroComplete(!playbackMode || !stream);
210
+ introStartedAtRef.current = autoStartReplacement ? performance.now() : null;
211
+ }
187
212
  useEffect(() => {
188
213
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
189
214
  const preference = window.matchMedia("(prefers-reduced-motion: reduce)");
190
215
  const handleChange = (event) => {
191
- if (event.matches) setIsPlaying(false);
216
+ setReducedMotion(event.matches);
217
+ if (event.matches) {
218
+ introStartedAtRef.current = null;
219
+ setStartRequested(false);
220
+ setIntroPlaying(false);
221
+ setIsPlaying(false);
222
+ }
192
223
  };
193
224
  preference.addEventListener?.("change", handleChange);
194
225
  return () => preference.removeEventListener?.("change", handleChange);
195
226
  }, []);
196
- useEffect(() => setIsMuted(startMuted), [startMuted]);
227
+ useEffect(() => setIsMuted(resolvedStartMuted), [resolvedStartMuted]);
197
228
  useEffect(() => {
198
229
  const updateFullscreen = () => setIsFullscreen(document.fullscreenElement === containerRef.current);
199
230
  document.addEventListener("fullscreenchange", updateFullscreen);
@@ -224,6 +255,7 @@ function VideoPlayerRuntime({
224
255
  ...current.config ? { mutableSceneIds } : {}
225
256
  });
226
257
  stateRef.current = next;
258
+ setReplacementPending(false);
227
259
  setState(next);
228
260
  callbacksRef.current.onStateChange?.(next);
229
261
  if (next.status === "complete" && current.status !== "complete") {
@@ -302,14 +334,65 @@ function VideoPlayerRuntime({
302
334
  useEffect(() => {
303
335
  const audio = audioRef.current;
304
336
  if (!audio) return;
305
- if (isPlaying) {
306
- void audio.play().catch(() => {
337
+ if (isPlaying || introPlaying) {
338
+ void audio.play().then(() => {
339
+ if (!audio.muted) setAudioUnlocked(true);
340
+ }).catch(() => {
341
+ audio.currentTime = 0;
342
+ timeRef.current = 0;
343
+ setCurrentTime(0);
344
+ setStartRequested(false);
345
+ setIntroPlaying(false);
307
346
  setIsPlaying(false);
308
347
  });
309
348
  } else {
310
349
  audio.pause();
311
350
  }
312
- }, [isPlaying, state.config?.audio?.audioUrl]);
351
+ }, [introPlaying, isPlaying, state.config?.audio?.audioUrl]);
352
+ useEffect(() => {
353
+ if (!state.config?.scenes.length) {
354
+ const terminalWithoutVideo = state.status === "complete" || state.status === "error" || state.status === "aborted";
355
+ if (terminalWithoutVideo) {
356
+ audioRef.current?.pause();
357
+ introStartedAtRef.current = null;
358
+ setStartRequested(false);
359
+ setIntroPlaying(false);
360
+ setGenerationIntroComplete(true);
361
+ }
362
+ return;
363
+ }
364
+ if (!startRequested) return;
365
+ if (state.config.scenes[0]?.id === "supplied-opening") {
366
+ timeRef.current = 0;
367
+ setCurrentTime(0);
368
+ if (audioRef.current) audioRef.current.volume = state.config.audio?.volume ?? 1;
369
+ introStartedAtRef.current = null;
370
+ setStartRequested(false);
371
+ setIntroPlaying(false);
372
+ setGenerationIntroComplete(true);
373
+ setIsPlaying(true);
374
+ return;
375
+ }
376
+ const startedAt = introStartedAtRef.current ?? performance.now();
377
+ introStartedAtRef.current = startedAt;
378
+ const remaining = Math.max(0, MINIMUM_GENERATION_INTRO_MS - (performance.now() - startedAt));
379
+ const startGeneratedVideo = () => {
380
+ timeRef.current = 0;
381
+ setCurrentTime(0);
382
+ if (audioRef.current) audioRef.current.volume = state.config?.audio?.volume ?? 1;
383
+ introStartedAtRef.current = null;
384
+ setStartRequested(false);
385
+ setIntroPlaying(false);
386
+ setGenerationIntroComplete(true);
387
+ setIsPlaying(true);
388
+ };
389
+ if (remaining <= 0) {
390
+ startGeneratedVideo();
391
+ return;
392
+ }
393
+ const timer = setTimeout(startGeneratedVideo, remaining);
394
+ return () => clearTimeout(timer);
395
+ }, [startRequested, state.config?.audio?.volume, state.config?.scenes.length, state.status]);
313
396
  const streamOrientation = state.config?.orientation ?? "portrait";
314
397
  const responsiveWidth = width ?? observedWidth;
315
398
  const orientation = orientationOverride === "auto" ? responsiveWidth > 0 ? responsiveWidth <= responsiveBreakpoint ? "portrait" : "landscape" : streamOrientation : orientationOverride ?? streamOrientation;
@@ -322,15 +405,63 @@ function VideoPlayerRuntime({
322
405
  const duration = config ? getVideoDuration(config) : 0;
323
406
  const terminal = state.status === "complete" || state.status === "error" || state.status === "aborted";
324
407
  const ended = terminal && duration > 0 && currentTime >= duration - 1e-3;
408
+ const generationIntroWaiting = Boolean(playbackMode && stream && !generationIntroComplete);
409
+ const firstSceneRange = config ? resolveVideoTimeline(config)[0] : void 0;
410
+ const hasSuppliedOpening = firstSceneRange?.scene.id === "supplied-opening";
411
+ const showStartPoster = (!generationIntroWaiting || hasSuppliedOpening) && !startRequested && !isPlaying && !ended && currentTime <= 1e-3 && Boolean(config?.scenes.length);
412
+ const firstSceneHoldProgress = firstSceneRange ? kit.getTemplateMetadata(firstSceneRange.scene.templateId)?.transitionTiming?.holdProgress ?? 0.7 : 0;
413
+ const posterTime = firstSceneRange ? firstSceneRange.start + Math.max(0, firstSceneRange.end - firstSceneRange.start) * firstSceneHoldProgress : currentTime;
414
+ const startPlayback = () => {
415
+ setGenerationIntroComplete(true);
416
+ const audio = audioRef.current;
417
+ if (audio) {
418
+ void audio.play().then(() => {
419
+ if (!audio.muted) setAudioUnlocked(true);
420
+ }).catch(() => {
421
+ audio.currentTime = 0;
422
+ timeRef.current = 0;
423
+ setCurrentTime(0);
424
+ setIsPlaying(false);
425
+ });
426
+ }
427
+ setIsPlaying(true);
428
+ };
429
+ const armPlayback = () => {
430
+ introStartedAtRef.current = performance.now();
431
+ setStartRequested(true);
432
+ setIntroPlaying(true);
433
+ const audio = audioRef.current;
434
+ if (!audio) return;
435
+ const volume = stateRef.current.config?.audio?.volume ?? 1;
436
+ audio.volume = volume;
437
+ void audio.play().then(() => {
438
+ if (!audio.muted) setAudioUnlocked(true);
439
+ }).catch(() => {
440
+ audio.volume = volume;
441
+ audio.currentTime = 0;
442
+ timeRef.current = 0;
443
+ setCurrentTime(0);
444
+ introStartedAtRef.current = null;
445
+ setStartRequested(false);
446
+ setIntroPlaying(false);
447
+ setIsPlaying(false);
448
+ });
449
+ };
325
450
  const togglePlayback = () => {
451
+ if (startRequested) return;
452
+ if (!stateRef.current.config?.scenes.length) {
453
+ armPlayback();
454
+ return;
455
+ }
326
456
  if (!isPlaying && ended) {
327
457
  timeRef.current = 0;
328
458
  setCurrentTime(0);
329
459
  if (audioRef.current) audioRef.current.currentTime = 0;
330
- setIsPlaying(true);
460
+ startPlayback();
331
461
  return;
332
462
  }
333
- setIsPlaying((playing) => !playing);
463
+ if (isPlaying) setIsPlaying(false);
464
+ else startPlayback();
334
465
  };
335
466
  const toggleFullscreen = async () => {
336
467
  const container = containerRef.current;
@@ -352,7 +483,31 @@ function VideoPlayerRuntime({
352
483
  font: "600 13px/1 system-ui, sans-serif",
353
484
  cursor: "pointer"
354
485
  };
355
- const generationCoverVisible = state.status === "streaming" && !config?.scenes.length;
486
+ const startButtonStyle = {
487
+ display: "inline-flex",
488
+ alignItems: "center",
489
+ gap: 10,
490
+ minHeight: 54,
491
+ padding: "12px 18px",
492
+ border: "1px solid rgba(255, 255, 255, 0.3)",
493
+ borderRadius: 999,
494
+ backgroundColor: "rgba(9, 7, 18, 0.88)",
495
+ color: "#ffffff",
496
+ boxShadow: "0 8px 28px rgba(0, 0, 0, 0.4)",
497
+ backdropFilter: "blur(12px)",
498
+ font: "700 15px/1 system-ui, sans-serif",
499
+ cursor: "pointer"
500
+ };
501
+ const startButtonPositionStyle = {
502
+ position: "absolute",
503
+ left: "50%",
504
+ top: "50%",
505
+ zIndex: 3,
506
+ transform: "translate(-50%, -50%)"
507
+ };
508
+ const pendingReplacement = replacementPending && state.status === "idle";
509
+ const displayedStatus = pendingReplacement ? "streaming" : state.status;
510
+ const generationCoverVisible = generationIntroWaiting && !hasSuppliedOpening || displayedStatus === "streaming" && !config?.scenes.length;
356
511
  const coverBackground = config?.style.brand.background;
357
512
  const coverText = config?.style.brand.colors.foreground ?? "#ffffff";
358
513
  const coverBackgroundCss = coverBackground?.type === "solid" ? coverBackground.color : `linear-gradient(135deg, ${coverBackground?.colors[0] ?? "#8711C1"}, ${coverBackground?.colors[1] ?? "#2167E3"})`;
@@ -370,13 +525,19 @@ function VideoPlayerRuntime({
370
525
  }
371
526
  },
372
527
  "data-testid": "video-player",
373
- "data-status": state.status,
528
+ "data-status": displayedStatus,
374
529
  "data-finish-reason": state.finishReason,
375
530
  "data-scenes": config?.scenes.length ?? 0,
376
531
  "data-orientation": orientation,
377
532
  "data-current-time": currentTime.toFixed(3),
378
- "data-playing": isPlaying,
533
+ "data-playing": isPlaying || introPlaying,
379
534
  "data-ended": ended,
535
+ "data-start-poster": showStartPoster,
536
+ "data-start-requested": startRequested,
537
+ "data-intro-playing": introPlaying,
538
+ "data-generation-intro-complete": generationIntroComplete,
539
+ "data-audio-unlocked": audioUnlocked,
540
+ "data-playback-mode": playbackMode ?? "custom",
380
541
  className,
381
542
  style: {
382
543
  width: width ?? "100%",
@@ -387,7 +548,7 @@ function VideoPlayerRuntime({
387
548
  ...style
388
549
  },
389
550
  children: [
390
- generationCoverVisible ? /* @__PURE__ */ jsx(
551
+ generationCoverVisible ? /* @__PURE__ */ jsxs(
391
552
  "div",
392
553
  {
393
554
  "data-testid": "video-generation-cover",
@@ -404,18 +565,33 @@ function VideoPlayerRuntime({
404
565
  background: coverBackgroundCss,
405
566
  fontFamily: fontStack(config?.style.brand.font)
406
567
  },
407
- children: /* @__PURE__ */ jsxs("div", { children: [
408
- /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { fontSize: "clamp(28px, 8vw, 72px)", opacity: 0.9 }, children: "\u2726" }),
409
- /* @__PURE__ */ jsx("strong", { style: { display: "block", marginTop: 18, fontSize: "clamp(24px, 5vw, 56px)", lineHeight: 1.08 }, children: "Creating your video\u2026" }),
410
- /* @__PURE__ */ jsx("span", { style: { display: "block", marginTop: 14, fontSize: "clamp(14px, 2vw, 22px)", lineHeight: 1.4, opacity: 0.78 }, children: "Choosing the best scenes for your content." })
411
- ] })
568
+ children: [
569
+ /* @__PURE__ */ jsxs("div", { style: { position: "absolute", top: "28%", left: "10%", right: "10%" }, children: [
570
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { fontSize: "clamp(28px, 8vw, 72px)", opacity: 0.9 }, children: "\u2726" }),
571
+ /* @__PURE__ */ jsx("strong", { style: { display: "block", marginTop: 18, fontSize: "clamp(24px, 5vw, 56px)", lineHeight: 1.08 }, children: "Creating your video\u2026" }),
572
+ /* @__PURE__ */ jsx("span", { style: { display: "block", marginTop: 14, fontSize: "clamp(14px, 2vw, 22px)", lineHeight: 1.4, opacity: 0.78 }, children: "Choosing the best scenes for your content." })
573
+ ] }),
574
+ !startRequested && !isPlaying ? /* @__PURE__ */ jsxs(
575
+ "button",
576
+ {
577
+ type: "button",
578
+ "aria-label": config?.audio && !isMuted ? "Play video with sound" : "Play video response",
579
+ onClick: armPlayback,
580
+ style: { ...startButtonStyle, ...startButtonPositionStyle },
581
+ children: [
582
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u25B6" }),
583
+ config?.audio && !isMuted ? "Play with sound" : "Play video"
584
+ ]
585
+ }
586
+ ) : null
587
+ ]
412
588
  }
413
589
  ) : config?.scenes.length ? /* @__PURE__ */ jsx(
414
590
  VideoFrame,
415
591
  {
416
592
  kit,
417
593
  config: displayConfig,
418
- time: currentTime,
594
+ time: showStartPoster ? posterTime : currentTime,
419
595
  width: dimensions.width,
420
596
  height: dimensions.height,
421
597
  playing: isPlaying,
@@ -427,6 +603,23 @@ function VideoPlayerRuntime({
427
603
  }
428
604
  }
429
605
  ) : null,
606
+ showStartPoster ? /* @__PURE__ */ jsxs(
607
+ "button",
608
+ {
609
+ type: "button",
610
+ "data-testid": "video-start-button",
611
+ "aria-label": config?.audio && !isMuted ? "Play video with sound" : "Play video response",
612
+ onClick: startPlayback,
613
+ style: {
614
+ ...startButtonStyle,
615
+ ...startButtonPositionStyle
616
+ },
617
+ children: [
618
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u25B6" }),
619
+ config?.audio && !isMuted ? "Play with sound" : "Play video"
620
+ ]
621
+ }
622
+ ) : null,
430
623
  config?.audio ? /* @__PURE__ */ jsx(
431
624
  "audio",
432
625
  {
@@ -435,7 +628,7 @@ function VideoPlayerRuntime({
435
628
  autoPlay: isPlaying,
436
629
  muted: isMuted,
437
630
  preload: "auto",
438
- loop: state.status === "streaming"
631
+ loop: state.status === "streaming" || introPlaying
439
632
  }
440
633
  ) : null,
441
634
  !generationCoverVisible ? /* @__PURE__ */ jsxs(
@@ -460,23 +653,26 @@ function VideoPlayerRuntime({
460
653
  boxShadow: "0 4px 20px rgba(0, 0, 0, 0.35)"
461
654
  },
462
655
  children: [
463
- /* @__PURE__ */ jsx(
656
+ !showStartPoster ? /* @__PURE__ */ jsx(
464
657
  "button",
465
658
  {
466
659
  type: "button",
467
660
  "aria-label": ended ? "Replay video response" : isPlaying ? "Pause video response" : "Play video response",
468
661
  onClick: togglePlayback,
469
662
  style: controlButtonStyle,
470
- children: ended ? "\u21BB" : isPlaying ? "\u2161" : "\u25B6"
663
+ children: ended ? "\u21BB Replay" : isPlaying ? "\u2161" : "\u25B6"
471
664
  }
472
- ),
665
+ ) : null,
473
666
  config?.audio ? /* @__PURE__ */ jsx(
474
667
  "button",
475
668
  {
476
669
  type: "button",
477
670
  "aria-label": isMuted ? "Unmute video response" : "Mute video response",
478
671
  "aria-pressed": !isMuted,
479
- onClick: () => setIsMuted((muted) => !muted),
672
+ onClick: () => setIsMuted((muted) => {
673
+ if (muted) setAudioUnlocked(true);
674
+ return !muted;
675
+ }),
480
676
  style: controlButtonStyle,
481
677
  children: isMuted ? "\u{1F507}" : "\u{1F50A}"
482
678
  }
package/dist/server.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { l as VideoGenerationContext, m as VideoPlanner, n as VideoRequest, j as VideoCapabilities, d as VideoInput, a as VideoAudio, o as VideoSceneValidator, p as VideoTemplatePacing, q as VideoSnapshotRetention, r as VideoResumeCursor, s as VideoSceneValidationContext, f as VideoScene } from './types-B-PZR6G_.js';
2
- import { b as VideoFinishReason, a as VideoWarning, V as VideoEvent } from './events-_wfjbmEp.js';
3
- export { c as VideoWarningCategory } from './events-_wfjbmEp.js';
1
+ import { m as VideoGenerationContext, n as VideoPlanner, o as VideoRequest, k as VideoCapabilities, d as VideoInput, a as VideoAudio, p as VideoSceneValidator, q as VideoTemplatePacing, r as VideoSnapshotRetention, s as VideoResumeCursor, t as VideoSceneValidationContext, g as VideoScene } from './types-CkO2EYr4.js';
2
+ import { b as VideoFinishReason, a as VideoWarning, V as VideoEvent } from './events-CTIsANzz.js';
3
+ export { c as VideoWarningCategory } from './events-CTIsANzz.js';
4
4
  import { S as SceneTemplateMetadata } from './catalog-types-BIhSpOWK.js';
5
5
 
6
6
  interface VideoProviderUsage {
@@ -84,6 +84,8 @@ interface VideoStreamHandlerOptions {
84
84
  onComplete?: (summary: VideoGenerationSummary) => unknown;
85
85
  /** Drop malformed generated parts by default, or preserve fail-fast behavior. */
86
86
  invalidPartBehavior?: "drop" | "fail";
87
+ /** Require one explicit closer before a standard generated plan is considered complete. */
88
+ requireCloser?: boolean;
87
89
  /** Allow credentialed cross-origin requests from explicit allowedOrigins. */
88
90
  allowCredentials?: boolean;
89
91
  /** Templates and extensions implemented by this customer deployment. */
@@ -120,6 +122,19 @@ declare function createTemplateSceneValidator(options: {
120
122
  }) => boolean;
121
123
  }): VideoSceneValidator;
122
124
 
125
+ interface ResolvedMedia {
126
+ url: string;
127
+ type: "image" | "video";
128
+ posterUrl?: string;
129
+ }
130
+ interface MediaResolverContext {
131
+ input: VideoInput;
132
+ templateId: string;
133
+ preferredType: "image" | "video" | "any";
134
+ signal: AbortSignal;
135
+ }
136
+ type MediaResolver = (query: string, context: MediaResolverContext) => ResolvedMedia | null | Promise<ResolvedMedia | null>;
137
+
123
138
  interface VideoHandlerOptions extends Omit<VideoStreamHandlerOptions, "generate" | "systemPrompt" | "supportedCapabilities" | "validateScene" | "getTemplatePacing"> {
124
139
  /** Customer-owned templates that replace matching built-ins and add new IDs. */
125
140
  templates?: ServerTemplateRegistry;
@@ -131,6 +146,8 @@ interface VideoHandlerOptions extends Omit<VideoStreamHandlerOptions, "generate"
131
146
  basePrompt?: string;
132
147
  /** Authorize an app-approved media URL in addition to URLs supplied in the request. */
133
148
  allowMediaUrl?: Parameters<typeof createTemplateSceneValidator>[0]["allowMediaUrl"];
149
+ /** Resolve bounded semantic media intent through an application-owned provider. */
150
+ resolveMedia?: MediaResolver;
134
151
  }
135
152
  /**
136
153
  * Build the secure route for a template kit while leaving provider, model,
@@ -138,4 +155,4 @@ interface VideoHandlerOptions extends Omit<VideoStreamHandlerOptions, "generate"
138
155
  */
139
156
  declare function createVideoHandler(options: VideoHandlerOptions): VideoStreamHandler;
140
157
 
141
- export { SceneTemplateMetadata as ServerTemplateMetadata, type ServerTemplateRegistry, VideoFinishReason, type VideoGenerationSummary, type VideoHandlerOptions, type VideoProviderUsage, VideoWarning, createServerTemplateRegistry, createVideoHandler };
158
+ export { type MediaResolver, type MediaResolverContext, type ResolvedMedia, SceneTemplateMetadata as ServerTemplateMetadata, type ServerTemplateRegistry, VideoFinishReason, type VideoGenerationSummary, type VideoHandlerOptions, type VideoProviderUsage, VideoWarning, createServerTemplateRegistry, createVideoHandler };