@vanillaskyai/video 0.10.22 → 0.11.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 (128) hide show
  1. package/CHANGELOG.md +74 -1
  2. package/PUBLIC-API.md +45 -438
  3. package/README.md +57 -96
  4. package/dist/builtin-metadata-OT6V7TB4.js +8 -0
  5. package/dist/{chapter-title-2JVDU62E.js → chapter-title-2RCXX7SN.js} +1 -2
  6. package/dist/{chunk-PDQFQIQW.js → chunk-3EQ6PVWL.js} +1 -1
  7. package/dist/chunk-4G4JBMCM.js +37 -0
  8. package/dist/{chunk-7AA2JWHZ.js → chunk-5DOQTIMD.js} +6 -6
  9. package/dist/chunk-666HGTVZ.js +41 -0
  10. package/dist/{chunk-7M56IUUX.js → chunk-6Z3ID54H.js} +0 -18
  11. package/dist/{chunk-5BSJLT6H.js → chunk-E7FHBLAC.js} +16 -29
  12. package/dist/chunk-HIQM4J3U.js +30 -0
  13. package/dist/chunk-ISWQ6T5X.js +36 -0
  14. package/dist/{chunk-224QNWRA.js → chunk-K5J7ESRO.js} +1 -10
  15. package/dist/chunk-KMVRBUL5.js +50 -0
  16. package/dist/chunk-NM4CXXZY.js +25 -0
  17. package/dist/{chunk-44WND2VP.js → chunk-V2CP7PVY.js} +19 -63
  18. package/dist/{chunk-3O7OMMMF.js → chunk-WZESNEPT.js} +0 -1
  19. package/dist/{chunk-RXTN2CW6.js → chunk-Z3DLSLAJ.js} +1 -1
  20. package/dist/cinema-media-YZ2UANJG.js +365 -0
  21. package/dist/cli.js +154 -1753
  22. package/dist/{compose-video-BH5K6XWT.js → compose-video-H2YUQDUY.js} +4 -4
  23. package/dist/{events-B4YCc4vc.d.ts → events-BZAfl0Dm.d.ts} +1 -1
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.js +4 -6
  26. package/dist/preload-media-23JXUFCF.js +77 -0
  27. package/dist/react.d.ts +39 -15
  28. package/dist/react.js +1350 -739
  29. package/dist/scene-validation-SGLLLFY5.js +9 -0
  30. package/dist/server.d.ts +67 -117
  31. package/dist/server.js +262 -614
  32. package/dist/test.d.ts +2 -2
  33. package/dist/test.js +21 -21
  34. package/dist/{text-stream-LD364KBI.js → text-stream-SOVLYR2L.js} +2 -2
  35. package/dist/{types-BqB8zC9u.d.ts → types-CG-kPI81.d.ts} +3 -1
  36. package/dist/{types-DdZw4GRQ.d.ts → types-ht-Zw3Wv.d.ts} +1 -1
  37. package/docs/agent-integration.md +19 -64
  38. package/docs/architecture.md +82 -87
  39. package/docs/customization.md +49 -66
  40. package/docs/development.md +18 -17
  41. package/docs/errors.md +4 -4
  42. package/docs/getting-started.md +74 -93
  43. package/docs/media-and-audio.md +9 -7
  44. package/docs/performance.md +11 -6
  45. package/docs/persistence.md +3 -7
  46. package/docs/production.md +4 -15
  47. package/docs/prompt-and-input.md +1 -2
  48. package/docs/provider-integration.md +134 -181
  49. package/docs/reference/protocol.md +5 -14
  50. package/docs/reference/provider-adapters.md +21 -13
  51. package/docs/security.md +1 -14
  52. package/docs/testing.md +56 -123
  53. package/package.json +5 -27
  54. package/starters/video-chat/.env.example +12 -4
  55. package/starters/video-chat/.env.native.example +13 -0
  56. package/starters/video-chat/README.md +72 -11
  57. package/starters/video-chat/package.json +1 -1
  58. package/starters/video-chat/providers/text-native.ts +94 -0
  59. package/starters/video-chat/providers/text.ts +19 -0
  60. package/starters/video-chat/providers/transcription.ts +32 -0
  61. package/starters/video-chat/providers/video-custom.ts +45 -0
  62. package/starters/video-chat/providers/video-delivery.ts +57 -0
  63. package/starters/video-chat/providers/video-google.ts +48 -0
  64. package/starters/video-chat/providers/video-job.ts +103 -0
  65. package/starters/video-chat/providers/video-runway.ts +45 -0
  66. package/starters/video-chat/providers/video.ts +39 -63
  67. package/starters/video-chat/server.ts +3 -29
  68. package/starters/video-chat/vite.config.ts +2 -8
  69. package/styles/video-chat.css +8 -105
  70. package/dist/builtin-server-W4PLJUZ5.js +0 -8
  71. package/dist/catalog-types-WTbLP6Jh.d.ts +0 -78
  72. package/dist/check-runtime.d.ts +0 -15
  73. package/dist/check-runtime.js +0 -96
  74. package/dist/chunk-2E6T633S.js +0 -27
  75. package/dist/chunk-4YM2M62S.js +0 -13
  76. package/dist/chunk-4ZJLPHBV.js +0 -684
  77. package/dist/chunk-5JBMYQP6.js +0 -156
  78. package/dist/chunk-73NTSFFI.js +0 -81
  79. package/dist/chunk-EGVQODKU.js +0 -83
  80. package/dist/chunk-HFVNAPHZ.js +0 -35
  81. package/dist/chunk-IIN5M5HW.js +0 -697
  82. package/dist/chunk-IQMYK5DX.js +0 -133
  83. package/dist/chunk-IR44XKBI.js +0 -46
  84. package/dist/chunk-JKVOBTRO.js +0 -145
  85. package/dist/chunk-LVM5Q2DL.js +0 -68
  86. package/dist/chunk-M4QTEJTK.js +0 -709
  87. package/dist/chunk-QSBDB4J2.js +0 -16
  88. package/dist/chunk-R3XAOMKP.js +0 -29
  89. package/dist/chunk-SPVTJH3F.js +0 -24
  90. package/dist/chunk-YAHT3LST.js +0 -663
  91. package/dist/chunk-ZD2VTUYR.js +0 -28
  92. package/dist/cinema-media-HYCDG65Z.js +0 -11
  93. package/dist/comparison-XXAP4S4J.js +0 -36
  94. package/dist/editorial-timeline-VUQC6KPA.js +0 -42
  95. package/dist/key-figure-4TJK7HLT.js +0 -29
  96. package/dist/kit-DrRpdn0p.d.ts +0 -79
  97. package/dist/mobile-message-FIHAO6XC.js +0 -49
  98. package/dist/preload-media-LJXWKTWG.js +0 -57
  99. package/dist/quote-GRPIYKQJ.js +0 -32
  100. package/dist/system-prompt-AG26KJAA.js +0 -12
  101. package/dist/template-catalog.d.ts +0 -400
  102. package/dist/template-catalog.js +0 -6
  103. package/dist/templates.d.ts +0 -25
  104. package/dist/templates.js +0 -102
  105. package/dist/validate-4OUD2CLX.js +0 -10
  106. package/docs/concepts.md +0 -107
  107. package/docs/custom-templates.md +0 -346
  108. package/docs/immersive-interface.md +0 -86
  109. package/docs/maintainers/cinematic-migration.md +0 -38
  110. package/docs/motion-and-effects.md +0 -109
  111. package/docs/reference/design-system.html +0 -125
  112. package/docs/responsive-orientation.md +0 -37
  113. package/docs/streaming-protocol.md +0 -15
  114. package/examples/custom-template/README.md +0 -19
  115. package/examples/custom-template/minimal-text.tsx +0 -82
  116. package/examples/custom-template/structured-data.tsx +0 -104
  117. package/registry/items/backgrounds.json +0 -70
  118. package/registry/items/chapterTitle.json +0 -80
  119. package/registry/items/cinemaMedia.json +0 -136
  120. package/registry/items/comparison.json +0 -168
  121. package/registry/items/editorialTimeline.json +0 -172
  122. package/registry/items/keyFigure.json +0 -162
  123. package/registry/items/mobileMessage.json +0 -153
  124. package/registry/items/motion.json +0 -45
  125. package/registry/items/quote.json +0 -161
  126. package/registry/items/template-context.json +0 -31
  127. package/registry/items/theme.json +0 -47
  128. package/registry/items/typography.json +0 -47
@@ -1,709 +0,0 @@
1
- import {
2
- ExternalVideoBackdropProvider,
3
- resolveDensity
4
- } from "./chunk-5JBMYQP6.js";
5
- import {
6
- limitsConcurrentVideoDecoders,
7
- resolveMediaType
8
- } from "./chunk-224QNWRA.js";
9
- import {
10
- resolveVideoTimeline
11
- } from "./chunk-SPVTJH3F.js";
12
- import {
13
- supportsExternalVideoBackdrop
14
- } from "./chunk-R3XAOMKP.js";
15
- import {
16
- getTemplateDefaults
17
- } from "./chunk-73NTSFFI.js";
18
-
19
- // src/player/mounted-scene-readiness.tsx
20
- import { createContext, useContext, useEffect, useRef } from "react";
21
- import { jsx } from "react/jsx-runtime";
22
- var MountedReadinessContext = createContext(void 0);
23
- var sceneReadinessKey = (scene) => `${scene.id}\0${String(scene.variables.mediaUrl || "")}`;
24
- function MountedSceneReadiness({
25
- scene,
26
- playing,
27
- fallback = false,
28
- onFailure,
29
- onReady,
30
- observeIncoming = false,
31
- timeoutMs = 8e3,
32
- preparedProof
33
- }) {
34
- const marker = useRef(null);
35
- const report = useContext(MountedReadinessContext);
36
- const key = sceneReadinessKey(scene);
37
- const onFailureRef = useRef(onFailure);
38
- onFailureRef.current = onFailure;
39
- const onReadyRef = useRef(onReady);
40
- onReadyRef.current = onReady;
41
- useEffect(() => {
42
- if (!report && !onReadyRef.current || !playing) return;
43
- let stopped = false;
44
- let frame = 0;
45
- let callback;
46
- let observed;
47
- let presented;
48
- let previousMediaTime;
49
- let forwardFrames = 0;
50
- let motionRevision = 0;
51
- const root = marker.current?.closest("[data-video-frame]");
52
- const start = performance.now();
53
- const finish = (error, actualVideoFrame = false) => {
54
- if (stopped) return;
55
- stopped = true;
56
- if (callback !== void 0) observed?.cancelVideoFrameCallback?.(callback);
57
- if (error && onFailureRef.current) onFailureRef.current();
58
- else if (onReadyRef.current && !error) {
59
- const video = presented;
60
- const mediaTime = video?.currentTime ?? 0;
61
- const confirmedAt = performance.now();
62
- const source = video?.currentSrc;
63
- const revision = motionRevision;
64
- let consumed = false;
65
- onReadyRef.current(actualVideoFrame && video ? { consume: (candidate) => {
66
- const valid = !consumed && candidate === video && video.isConnected && motionRevision === revision && !video.paused && !video.seeking && video.currentSrc === source && video.currentSrc === video.src && performance.now() - confirmedAt <= 200 && video.currentTime >= mediaTime && video.currentTime - mediaTime <= 0.2;
67
- consumed = true;
68
- return valid;
69
- } } : void 0);
70
- } else report?.(key, error, actualVideoFrame);
71
- };
72
- const check = () => {
73
- if (stopped) return;
74
- if (fallback && root?.querySelector("[data-scene-fallback]") && !root.querySelector("[data-template-loading]") && document.fonts?.status !== "loading") {
75
- finish();
76
- return;
77
- }
78
- const layer = observeIncoming ? [...root?.querySelectorAll("[data-layer-scene-id]") ?? []].find((node) => node.getAttribute("data-layer-scene-id") === scene.id) : root?.querySelector('[data-scene-layer="active"]');
79
- const loading = layer?.querySelector("[data-template-loading]");
80
- const mediaUrl = String(scene.variables.mediaUrl || "");
81
- const isVideo = scene.variables.mediaType === "video" || /\.(mp4|webm|mov)(?:[?#]|$)/i.test(mediaUrl);
82
- if (root && layer && !loading && document.fonts?.status !== "loading") {
83
- if (!mediaUrl || scene.variables.mediaType === "gradient") {
84
- finish();
85
- return;
86
- }
87
- if (isVideo) {
88
- const video = layer.querySelector("video");
89
- if (video && video.getAttribute("src") === mediaUrl && video.currentSrc === video.src && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
90
- if (preparedProof?.consume(video)) {
91
- finish(void 0, true);
92
- return;
93
- }
94
- if (observed !== video) {
95
- previousMediaTime = void 0;
96
- forwardFrames = 0;
97
- }
98
- if (presented === video && (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA || forwardFrames >= 2)) {
99
- finish(void 0, true);
100
- return;
101
- }
102
- observed = video;
103
- if (video.requestVideoFrameCallback) {
104
- callback = video.requestVideoFrameCallback((_now, metadata) => {
105
- callback = void 0;
106
- if (stopped) return;
107
- const sameSource = video.isConnected && video.getAttribute("src") === mediaUrl && video.currentSrc === video.src;
108
- const mediaTime = metadata.mediaTime;
109
- if (!sameSource || video.paused || video.seeking || !Number.isFinite(mediaTime) || previousMediaTime !== void 0 && mediaTime <= previousMediaTime + 1e-3) forwardFrames = 0;
110
- else if (previousMediaTime !== void 0 && mediaTime > previousMediaTime + 1e-3) forwardFrames++;
111
- previousMediaTime = sameSource && !video.paused && !video.seeking ? mediaTime : void 0;
112
- if (sameSource) presented = video;
113
- check();
114
- });
115
- return;
116
- }
117
- if (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) {
118
- finish(void 0, true);
119
- return;
120
- }
121
- }
122
- } else {
123
- const image = [...layer.querySelectorAll("img") ?? []].find((element) => element.getAttribute("src") === mediaUrl);
124
- if (image?.complete && image.naturalWidth > 0) {
125
- finish();
126
- return;
127
- }
128
- }
129
- }
130
- if (timeoutMs !== null && performance.now() - start >= timeoutMs) {
131
- finish(new Error("Scene media did not become ready"));
132
- return;
133
- }
134
- frame = requestAnimationFrame(check);
135
- };
136
- const onPresented = (event) => {
137
- const video = event.target;
138
- if (!(video instanceof HTMLVideoElement) || stopped || video.getAttribute("src") !== String(scene.variables.mediaUrl || "") || video.currentSrc !== video.src) return;
139
- presented = video;
140
- cancelAnimationFrame(frame);
141
- if (callback !== void 0) observed?.cancelVideoFrameCallback?.(callback);
142
- callback = void 0;
143
- check();
144
- };
145
- const resetMotion = (event) => {
146
- if (event.target === observed || event.target === presented) {
147
- motionRevision++;
148
- previousMediaTime = void 0;
149
- forwardFrames = 0;
150
- }
151
- };
152
- for (const type of ["pause", "waiting", "seeking"]) root?.addEventListener(type, resetMotion, true);
153
- root?.addEventListener("vanillasky:video-frame-presented", onPresented);
154
- check();
155
- const timeout = timeoutMs === null ? void 0 : setTimeout(() => finish(new Error("Scene media did not become ready")), timeoutMs);
156
- return () => {
157
- root?.removeEventListener("vanillasky:video-frame-presented", onPresented);
158
- for (const type of ["pause", "waiting", "seeking"]) root?.removeEventListener(type, resetMotion, true);
159
- stopped = true;
160
- clearTimeout(timeout);
161
- cancelAnimationFrame(frame);
162
- if (callback !== void 0) observed?.cancelVideoFrameCallback?.(callback);
163
- };
164
- }, [key, report, scene, playing, fallback, observeIncoming, timeoutMs, preparedProof]);
165
- return /* @__PURE__ */ jsx("span", { ref: marker, hidden: true });
166
- }
167
-
168
- // src/player/video-frame.tsx
169
- import {
170
- createElement,
171
- Component,
172
- Suspense,
173
- useState,
174
- useRef as useRef2,
175
- useCallback,
176
- useEffect as useEffect2,
177
- useSyncExternalStore
178
- } from "react";
179
-
180
- // src/visual-system/layout.ts
181
- function getDimensions(orientation = "portrait") {
182
- return orientation === "landscape" ? { width: 1920, height: 1080, aspectRatio: "16 / 9" } : { width: 1080, height: 1920, aspectRatio: "9 / 16" };
183
- }
184
- function getSafeZone(orientation = "portrait") {
185
- return orientation === "landscape" ? { top: 108, bottom: 108, left: 192, right: 192 } : { top: 100, bottom: 100, left: 60, right: 60 };
186
- }
187
- function scaleSafeZone(zone, factor) {
188
- if (factor === 1) return zone;
189
- return {
190
- top: Math.round(zone.top * factor),
191
- bottom: Math.round(zone.bottom * factor),
192
- left: Math.round(zone.left * factor),
193
- right: Math.round(zone.right * factor)
194
- };
195
- }
196
-
197
- // src/player/video-frame.tsx
198
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
199
- function PresentedScene({ notify }) {
200
- useEffect2(() => {
201
- if (!notify) return;
202
- const frame = requestAnimationFrame(() => {
203
- try {
204
- void Promise.resolve(notify()).catch(() => void 0);
205
- } catch {
206
- }
207
- });
208
- return () => cancelAnimationFrame(frame);
209
- }, [notify]);
210
- return null;
211
- }
212
- function SafeScene({ scene, onFramePresented }) {
213
- const copy = scene.narration?.trim() || Object.values(scene.variables).filter((value) => typeof value === "string" && !/^https?:/i.test(value)).join(" ");
214
- return /* @__PURE__ */ jsxs(
215
- "div",
216
- {
217
- "data-scene-fallback": "true",
218
- style: {
219
- width: "100%",
220
- height: "100%",
221
- display: "grid",
222
- placeContent: "center",
223
- boxSizing: "border-box",
224
- padding: "8%",
225
- background: "#090712",
226
- color: "#fff",
227
- font: "500 clamp(20px, 4vw, 64px)/1.3 system-ui",
228
- overflow: "hidden"
229
- },
230
- children: [
231
- /* @__PURE__ */ jsx2(MountedSceneReadiness, { scene, playing: true, fallback: true }),
232
- /* @__PURE__ */ jsx2(PresentedScene, { notify: onFramePresented }),
233
- /* @__PURE__ */ jsx2("p", { children: copy.slice(0, 600) || "Your response continues." }),
234
- /* @__PURE__ */ jsx2("small", { style: { fontSize: "0.3em" }, role: "status", children: "This scene uses a simpler layout." })
235
- ]
236
- }
237
- );
238
- }
239
- var SceneBoundary = class extends Component {
240
- state = { failed: false };
241
- static getDerivedStateFromError() {
242
- return { failed: true };
243
- }
244
- render() {
245
- return this.state.failed ? /* @__PURE__ */ jsx2(SafeScene, { scene: this.props.scene, onFramePresented: this.props.onFramePresented }) : this.props.children;
246
- }
247
- };
248
- var SCENE_TRANSITION_SECONDS = 0.3;
249
- var MEDIA_PREROLL_SECONDS = 1.2;
250
- var CONTIGUITY_ULP_FACTOR = 4;
251
- var subscribeToDecoderPolicy = () => () => {
252
- };
253
- function useDecoderConstraint() {
254
- return useSyncExternalStore(
255
- subscribeToDecoderPolicy,
256
- limitsConcurrentVideoDecoders,
257
- () => false
258
- );
259
- }
260
- function clamp01(value) {
261
- return Math.max(0, Math.min(1, value));
262
- }
263
- function supportsContinuousTransition(value) {
264
- return value === "crossfade" || value === "fade";
265
- }
266
- function rangesAreContiguous(left, right) {
267
- const scale = Math.max(1, Math.abs(left.end), Math.abs(right.start));
268
- const ulpTolerance = Number.EPSILON * scale * CONTIGUITY_ULP_FACTOR;
269
- return Math.abs(left.end - right.start) <= ulpTolerance;
270
- }
271
- function sceneHasBackdrop(range) {
272
- return String(range.scene.variables.mediaType || "auto") !== "gradient" && String(range.scene.variables.mediaUrl || "").trim() !== "";
273
- }
274
- function sceneHasVideoBackdrop(range) {
275
- const mediaUrl = String(range.scene.variables.mediaUrl || "").trim();
276
- return mediaUrl !== "" && resolveMediaType(
277
- String(range.scene.variables.mediaType || "auto"),
278
- mediaUrl
279
- ) === "video";
280
- }
281
- function registrySupportsExternalVideoBackdrop(kit) {
282
- return kit.templates.every((template) => {
283
- const properties = template.schema?.properties;
284
- if (!properties?.mediaUrl) return true;
285
- const mediaTypes = properties.mediaType?.enum;
286
- const canRenderVideo = mediaTypes == null || mediaTypes.includes("video") || mediaTypes.includes("auto");
287
- return !canRenderVideo || supportsExternalVideoBackdrop(template);
288
- });
289
- }
290
- function sceneBackgroundChanges(left, right) {
291
- const mediaUrl = (range) => String(range.scene.variables.mediaType || "auto") === "gradient" ? "" : String(range.scene.variables.mediaUrl || "");
292
- return mediaUrl(left) !== mediaUrl(right);
293
- }
294
- function SceneLayer({
295
- onFramePresented,
296
- onMediaError,
297
- kit,
298
- config,
299
- range,
300
- progress,
301
- motionProgress,
302
- width,
303
- height,
304
- playing,
305
- preparingNarration,
306
- mediaAudioMuted,
307
- mediaAudioVolume,
308
- layer,
309
- opacity,
310
- interactive,
311
- zIndex,
312
- externalVideoBackdrop
313
- }) {
314
- const mediaFailed = externalVideoBackdrop === "fallback" || range.scene.templateId === "cinemaMedia" && !String(range.scene.variables.mediaUrl || "").trim();
315
- const recoveryTitle = mediaFailed && range.scene.templateId === "cinemaMedia" && typeof range.scene.variables.fallbackText === "string" ? range.scene.variables.fallbackText : void 0;
316
- const template = kit.getTemplate(recoveryTitle ? "chapterTitle" : range.scene.templateId);
317
- const duration = range.end - range.start;
318
- return /* @__PURE__ */ jsx2(
319
- ExternalVideoBackdropProvider,
320
- {
321
- mode: externalVideoBackdrop,
322
- audioMuted: mediaAudioMuted || !playing,
323
- audioVolume: mediaAudioVolume,
324
- preparingNarration,
325
- onMediaError,
326
- children: /* @__PURE__ */ jsxs(
327
- "div",
328
- {
329
- "data-scene-layer": layer,
330
- "data-scene-fallback": mediaFailed ? "true" : void 0,
331
- "data-layer-scene-id": range.scene.id,
332
- "data-layer-template-id": range.scene.templateId,
333
- "aria-hidden": interactive ? void 0 : true,
334
- ...interactive ? {} : { inert: "inert" },
335
- style: {
336
- position: "absolute",
337
- inset: 0,
338
- opacity,
339
- zIndex,
340
- pointerEvents: interactive ? "auto" : "none",
341
- // A changed-media transition can expose an incoming template's true
342
- // initial frame. Values that would be false placeholders at progress
343
- // zero opt into this inherited guard.
344
- "--vanillasky-transition-semantic-visibility": layer === "incoming" ? "hidden" : "visible",
345
- "--vanillasky-template-surface": externalVideoBackdrop !== false ? "transparent" : void 0
346
- },
347
- children: [
348
- range.scene.templateId === "cinemaMedia" && !recoveryTitle && (mediaFailed || !String(range.scene.variables.mediaUrl || "").trim()) && /* @__PURE__ */ jsx2(
349
- "div",
350
- {
351
- role: "status",
352
- "data-media-unavailable": "true",
353
- style: { position: "absolute", inset: 0, zIndex: 2, display: "grid", placeContent: "center", color: "#bbb", font: "14px system-ui", background: "#000" },
354
- children: "Visual unavailable"
355
- }
356
- ),
357
- template ? /* @__PURE__ */ jsx2(SceneBoundary, { scene: range.scene, onFramePresented, children: /* @__PURE__ */ jsxs(Suspense, { fallback: /* @__PURE__ */ jsx2(
358
- "div",
359
- {
360
- "data-template-loading": range.scene.templateId,
361
- style: { position: "absolute", inset: 0 }
362
- }
363
- ), children: [
364
- /* @__PURE__ */ jsx2(PresentedScene, { notify: onFramePresented }),
365
- createElement(template.component, {
366
- variables: {
367
- ...template.defaults ?? getTemplateDefaults(template.schema),
368
- ...range.scene.variables,
369
- ...mediaFailed ? { mediaUrl: "", mediaPoster: "", mediaType: "gradient" } : {},
370
- ...recoveryTitle ? { title: recoveryTitle } : {}
371
- },
372
- style: config.style,
373
- progress,
374
- motionProgress,
375
- beatIntensity: 0,
376
- width,
377
- height,
378
- textArchetype: range.scene.textArchetype ?? config.style.defaultTextArchetype,
379
- backgroundEffect: range.scene.backgroundEffect ?? config.style.defaultBackgroundEffect,
380
- safeZone: scaleSafeZone(
381
- getSafeZone(config.orientation),
382
- resolveDensity(config.style.density).safeZoneScale
383
- ),
384
- sceneDuration: duration,
385
- isPlaying: playing
386
- })
387
- ] }) }, range.scene.id) : /* @__PURE__ */ jsx2(SafeScene, { scene: range.scene, onFramePresented })
388
- ]
389
- }
390
- )
391
- }
392
- );
393
- }
394
- function VideoFrame({
395
- onFramePresented,
396
- kit,
397
- config,
398
- time,
399
- width,
400
- height,
401
- playing = false,
402
- preparingNarration = false,
403
- mediaAudioMuted = true,
404
- mediaAudioVolume = 1,
405
- className,
406
- style
407
- }) {
408
- const recoveryRoot = useRef2(null);
409
- const displayedKey = useRef2(void 0);
410
- const displayedWasPlaying = useRef2(false);
411
- const handoffProof = useRef2(void 0);
412
- const confirmedHandoff = useRef2(void 0);
413
- const [preparedMedia, setPreparedMedia] = useState(() => /* @__PURE__ */ new Set());
414
- const markPrepared = useCallback((key) => setPreparedMedia((previous2) => /* @__PURE__ */ new Set([...previous2, key])), []);
415
- const reportedFailures = useRef2(/* @__PURE__ */ new Set());
416
- const [failedMedia, setFailedMedia] = useState(() => /* @__PURE__ */ new Set());
417
- const markMediaFailed = useCallback((key, reason = "playback-error") => {
418
- if (!key || !config.scenes.some((scene) => sceneReadinessKey(scene) === key)) return;
419
- if (!reportedFailures.current.has(key)) {
420
- reportedFailures.current.add(key);
421
- 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" } }));
422
- }
423
- setFailedMedia((previous2) => previous2.has(key) ? previous2 : /* @__PURE__ */ new Set([...previous2, key]));
424
- }, [config.scenes]);
425
- useEffect2(() => {
426
- const currentKeys = new Set(config.scenes.map(sceneReadinessKey));
427
- setPreparedMedia((previous2) => {
428
- const retained = new Set([...previous2].filter((key) => currentKeys.has(key)));
429
- return retained.size === previous2.size ? previous2 : retained;
430
- });
431
- for (const key of reportedFailures.current) if (!currentKeys.has(key)) reportedFailures.current.delete(key);
432
- setFailedMedia((previous2) => {
433
- const retained = new Set([...previous2].filter((key) => currentKeys.has(key)));
434
- return retained.size === previous2.size ? previous2 : retained;
435
- });
436
- }, [config.scenes]);
437
- const decoderConstrainedDevice = useDecoderConstraint();
438
- const timeline = resolveVideoTimeline(config);
439
- const lastRange = timeline.at(-1);
440
- const foundIndex = timeline.findIndex((range) => time >= range.start && time < range.end);
441
- const afterEnd = lastRange && time >= lastRange.end;
442
- const targetIndex = foundIndex >= 0 ? foundIndex : afterEnd ? timeline.length - 1 : -1;
443
- const target = timeline[targetIndex];
444
- const targetKey = target ? sceneReadinessKey(target.scene) : void 0;
445
- if (confirmedHandoff.current !== targetKey || !(playing || preparingNarration)) {
446
- confirmedHandoff.current = void 0;
447
- handoffProof.current = void 0;
448
- }
449
- const previousIndex = timeline.findIndex((range) => sceneReadinessKey(range.scene) === displayedKey.current);
450
- const previous = timeline[previousIndex];
451
- const canPrepare = (range) => Boolean(range && (mediaAudioMuted || !sceneHasVideoBackdrop(range)) && sceneHasBackdrop(range) && supportsExternalVideoBackdrop(kit.getTemplate(range.scene.templateId)));
452
- const canRetain = (range) => canPrepare(range) || range?.scene.templateId === "chapterTitle";
453
- const hasPlayableMedia = (range) => {
454
- if (!range || !preparedMedia.has(sceneReadinessKey(range.scene))) return false;
455
- const layer = [...recoveryRoot.current?.querySelectorAll("[data-layer-scene-id]") ?? []].find((node) => node.getAttribute("data-layer-scene-id") === range.scene.id);
456
- if (!sceneHasVideoBackdrop(range)) {
457
- const image = [...layer?.querySelectorAll("img") ?? []].find((element) => element.getAttribute("src") === range.scene.variables.mediaUrl);
458
- return Boolean(image && image.getAttribute("src") === range.scene.variables.mediaUrl && image.complete && image.naturalWidth > 0);
459
- }
460
- const video = layer?.querySelector("video");
461
- return Boolean(video && video.getAttribute("src") === range.scene.variables.mediaUrl && video.currentSrc === video.src && video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA);
462
- };
463
- const handoffPending = Boolean(displayedWasPlaying.current && target && previous && targetIndex === previousIndex + 1 && time <= target.start + 1e-3 && rangesAreContiguous(previous, target) && canRetain(previous) && canPrepare(target) && !failedMedia.has(sceneReadinessKey(target.scene)) && !hasPlayableMedia(target) && confirmedHandoff.current !== targetKey);
464
- const activeIndex = handoffPending ? previousIndex : targetIndex;
465
- const active = timeline[activeIndex];
466
- const displayKey = active ? sceneReadinessKey(active.scene) : void 0;
467
- displayedWasPlaying.current = displayedKey.current === displayKey && displayedWasPlaying.current || playing || preparingNarration;
468
- displayedKey.current = displayKey;
469
- if (!active) {
470
- return /* @__PURE__ */ jsx2(
471
- "div",
472
- {
473
- "data-video-frame": timeline.length === 0 ? "empty" : "gap",
474
- className,
475
- style: { width, height, background: "#000", ...style }
476
- }
477
- );
478
- }
479
- if (!kit.getTemplate(active.scene.templateId)) {
480
- return /* @__PURE__ */ jsx2(
481
- "div",
482
- {
483
- "data-video-frame": "unsupported",
484
- "data-template-id": active.scene.templateId,
485
- className,
486
- style: { width, height, background: "#090712", color: "#fff", ...style },
487
- children: /* @__PURE__ */ jsx2(SafeScene, { scene: active.scene, onFramePresented })
488
- }
489
- );
490
- }
491
- const duration = active.end - active.start;
492
- const rawProgress = clamp01((time - active.start) / duration);
493
- const transitionEnabled = supportsContinuousTransition(config.style.defaultTransition);
494
- const next = activeIndex < timeline.length - 1 ? timeline[activeIndex + 1] : void 0;
495
- const activeTemplate = kit.getTemplate(active.scene.templateId);
496
- const nextTemplate = next ? kit.getTemplate(next.scene.templateId) : void 0;
497
- const contiguousNext = next && rangesAreContiguous(active, next) ? next : void 0;
498
- const transitionableNext = contiguousNext && activeTemplate.usesGlobalTransition && activeTemplate.transitionTiming && nextTemplate?.usesGlobalTransition && nextTemplate.transitionTiming ? contiguousNext : void 0;
499
- const activeTiming = activeTemplate.transitionTiming;
500
- const nextTiming = nextTemplate?.transitionTiming;
501
- const eligibleNextTransition = Boolean(
502
- transitionEnabled && transitionableNext && activeTiming && nextTiming && sceneBackgroundChanges(active, transitionableNext)
503
- );
504
- const blendDuration = Math.min(
505
- SCENE_TRANSITION_SECONDS,
506
- Math.max(0, duration)
507
- );
508
- const blendStart = active.end - blendDuration;
509
- const blendEnd = blendStart + blendDuration;
510
- const prerollsNext = Boolean(
511
- contiguousNext && sceneHasBackdrop(contiguousNext) && sceneBackgroundChanges(active, contiguousNext)
512
- );
513
- const prerollDuration = prerollsNext ? Math.min(Math.max(MEDIA_PREROLL_SECONDS, blendDuration), Math.max(0, duration)) : blendDuration;
514
- const prerollStart = active.end - prerollDuration;
515
- const boundedPreparation = decoderConstrainedDevice && registrySupportsExternalVideoBackdrop(kit);
516
- const decoderConstrainedTransition = Boolean(
517
- contiguousNext && decoderConstrainedDevice && !boundedPreparation && sceneHasVideoBackdrop(active) && sceneHasVideoBackdrop(contiguousNext)
518
- );
519
- const activeMediaFailed = failedMedia.has(sceneReadinessKey(active.scene));
520
- const preparingNext = canRetain(active) && canPrepare(contiguousNext);
521
- const nextPlayable = hasPlayableMedia(contiguousNext);
522
- const mountingNext = handoffPending || Boolean(
523
- contiguousNext && !decoderConstrainedTransition && (boundedPreparation || time >= prerollStart) && time < blendEnd && (eligibleNextTransition || prerollsNext || boundedPreparation && sceneHasVideoBackdrop(contiguousNext))
524
- );
525
- const previewingNext = Boolean(
526
- eligibleNextTransition && time >= blendStart && time < blendEnd && (!preparingNext || nextPlayable)
527
- );
528
- const blendProgress = previewingNext && blendDuration > 0 ? Math.round(clamp01((time - blendStart) / blendDuration) * 1e6) / 1e6 : 0;
529
- const progress = rawProgress;
530
- const isFinalScene = activeIndex === timeline.length - 1;
531
- const presentsChapter = active.scene.templateId === "chapterTitle" || active.scene.templateId === "cinemaMedia" && (activeMediaFailed || !String(active.scene.variables.mediaUrl || "").trim());
532
- const finalHold = presentsChapter ? 0.76 : activeTiming?.holdProgress;
533
- const holdChapter = presentsChapter && preparingNext && !nextPlayable;
534
- const motionProgress = (isFinalScene || holdChapter) && finalHold !== void 0 ? Math.min(rawProgress, finalHold) : rawProgress;
535
- const canvas = getDimensions(config.orientation);
536
- const scale = Math.min(width / canvas.width, height / canvas.height);
537
- const canvasLeft = (width - canvas.width * scale) / 2;
538
- const canvasTop = (height - canvas.height * scale) / 2;
539
- return /* @__PURE__ */ jsxs(
540
- "div",
541
- {
542
- ref: recoveryRoot,
543
- onErrorCapture: (event) => {
544
- const target2 = event.target;
545
- if (!(target2 instanceof HTMLVideoElement || target2 instanceof HTMLImageElement)) return;
546
- const ownerId = target2.closest("[data-layer-scene-id]")?.getAttribute("data-layer-scene-id");
547
- const owner = [active, contiguousNext].find((range) => range?.scene.id === ownerId);
548
- const template = owner && kit.getTemplate(owner.scene.templateId);
549
- if (owner && template && supportsExternalVideoBackdrop(template) && target2.getAttribute("src") === owner.scene.variables.mediaUrl) {
550
- markMediaFailed(sceneReadinessKey(owner.scene), "decode-error");
551
- }
552
- },
553
- "data-video-frame": "ready",
554
- "data-scene-id": active.scene.id,
555
- "data-template-id": active.scene.templateId,
556
- className,
557
- style: {
558
- width,
559
- height,
560
- position: "relative",
561
- overflow: "hidden",
562
- background: "#000",
563
- ...style
564
- },
565
- children: [
566
- /* @__PURE__ */ jsx2(
567
- MountedSceneReadiness,
568
- {
569
- scene: active.scene,
570
- playing: playing && !handoffPending,
571
- fallback: activeMediaFailed,
572
- preparedProof: confirmedHandoff.current === sceneReadinessKey(active.scene) ? handoffProof.current : void 0,
573
- onFailure: sceneHasBackdrop(active) && supportsExternalVideoBackdrop(activeTemplate) && !activeMediaFailed ? () => markMediaFailed(sceneReadinessKey(active.scene), "frame-readiness-timeout") : void 0
574
- }
575
- ),
576
- mountingNext && contiguousNext && preparingNext && /* @__PURE__ */ jsx2(
577
- MountedSceneReadiness,
578
- {
579
- scene: contiguousNext.scene,
580
- playing: playing || preparingNarration,
581
- observeIncoming: true,
582
- timeoutMs: handoffPending ? 8e3 : null,
583
- onReady: (proof) => {
584
- const key = sceneReadinessKey(contiguousNext.scene);
585
- if (handoffPending) {
586
- confirmedHandoff.current = key;
587
- handoffProof.current = proof;
588
- }
589
- markPrepared(key);
590
- },
591
- onFailure: () => markMediaFailed(sceneReadinessKey(contiguousNext.scene), "frame-readiness-timeout")
592
- }
593
- ),
594
- /* @__PURE__ */ jsxs(
595
- "div",
596
- {
597
- "data-video-canvas": "true",
598
- style: {
599
- position: "absolute",
600
- left: canvasLeft,
601
- top: canvasTop,
602
- width: canvas.width,
603
- height: canvas.height,
604
- transform: `scale(${scale})`,
605
- transformOrigin: "top left"
606
- },
607
- children: [
608
- /* @__PURE__ */ jsx2(
609
- "div",
610
- {
611
- "data-player-background": "black",
612
- "aria-hidden": "true",
613
- style: {
614
- position: "absolute",
615
- inset: 0,
616
- background: "#000",
617
- backgroundColor: "#000",
618
- pointerEvents: "none"
619
- }
620
- }
621
- ),
622
- mountingNext && contiguousNext ? [
623
- /* @__PURE__ */ jsx2(
624
- SceneLayer,
625
- {
626
- onFramePresented,
627
- kit,
628
- config,
629
- range: active,
630
- onMediaError: (reason) => markMediaFailed(sceneReadinessKey(active.scene), reason),
631
- progress,
632
- motionProgress,
633
- width: canvas.width,
634
- height: canvas.height,
635
- playing: handoffPending ? playing || preparingNarration : playing,
636
- preparingNarration: handoffPending ? false : preparingNarration,
637
- mediaAudioMuted,
638
- mediaAudioVolume,
639
- layer: previewingNext ? "outgoing" : "active",
640
- opacity: 1 - blendProgress,
641
- interactive: true,
642
- zIndex: 1,
643
- externalVideoBackdrop: activeMediaFailed ? "fallback" : false
644
- },
645
- active.scene.id
646
- ),
647
- /* @__PURE__ */ jsx2(
648
- SceneLayer,
649
- {
650
- kit,
651
- config,
652
- range: contiguousNext,
653
- progress: 0,
654
- motionProgress: 0,
655
- width: canvas.width,
656
- height: canvas.height,
657
- playing: Boolean(preparingNext && (!preparedMedia.has(sceneReadinessKey(contiguousNext.scene)) || handoffPending) && (playing || preparingNarration)),
658
- preparingNarration: preparingNext,
659
- mediaAudioMuted: true,
660
- mediaAudioVolume,
661
- layer: "incoming",
662
- opacity: blendProgress,
663
- interactive: false,
664
- zIndex: 2,
665
- onMediaError: (reason) => markMediaFailed(sceneReadinessKey(contiguousNext.scene), reason),
666
- externalVideoBackdrop: failedMedia.has(sceneReadinessKey(contiguousNext.scene)) ? "fallback" : false
667
- },
668
- contiguousNext.scene.id
669
- )
670
- ] : [
671
- /* @__PURE__ */ jsx2(
672
- SceneLayer,
673
- {
674
- onFramePresented,
675
- kit,
676
- config,
677
- range: active,
678
- onMediaError: (reason) => markMediaFailed(sceneReadinessKey(active.scene), reason),
679
- progress,
680
- motionProgress,
681
- width: canvas.width,
682
- height: canvas.height,
683
- playing,
684
- preparingNarration,
685
- mediaAudioMuted,
686
- mediaAudioVolume,
687
- layer: "active",
688
- opacity: 1,
689
- interactive: true,
690
- zIndex: 1,
691
- externalVideoBackdrop: activeMediaFailed ? "fallback" : false
692
- },
693
- active.scene.id
694
- )
695
- ]
696
- ]
697
- }
698
- )
699
- ]
700
- }
701
- );
702
- }
703
-
704
- export {
705
- MountedReadinessContext,
706
- sceneReadinessKey,
707
- getDimensions,
708
- VideoFrame
709
- };