@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,663 +0,0 @@
1
- import {
2
- darken,
3
- resolveTokens,
4
- useExternalVideoBackdrop,
5
- useMediaAudio,
6
- useMediaFailure,
7
- useNarrationPreroll
8
- } from "./chunk-5JBMYQP6.js";
9
- import {
10
- resolveMediaType
11
- } from "./chunk-224QNWRA.js";
12
-
13
- // src/visual-system/motion/curves.ts
14
- function interpolate(value, inputRange, outputRange, options) {
15
- const { easing, extrapolateLeft = "extend", extrapolateRight = "extend" } = options ?? {};
16
- let i = 0;
17
- for (; i < inputRange.length - 2; i++) {
18
- if (value < inputRange[i + 1]) break;
19
- }
20
- const inputMin = inputRange[i];
21
- const inputMax = inputRange[i + 1];
22
- const outputMin = outputRange[i];
23
- const outputMax = outputRange[i + 1];
24
- let t = inputMax === inputMin ? 0 : (value - inputMin) / (inputMax - inputMin);
25
- if (t < 0 && extrapolateLeft === "clamp") t = 0;
26
- if (t > 1 && extrapolateRight === "clamp") t = 1;
27
- if (easing && t >= 0 && t <= 1) {
28
- t = easing(t);
29
- }
30
- return outputMin + t * (outputMax - outputMin);
31
- }
32
- function spring(progress, config) {
33
- if (progress <= 0) return 0;
34
- if (progress >= 1) {
35
- const { damping: damping2 = 26 } = config ?? {};
36
- if (damping2 >= 20) return 1;
37
- }
38
- const { damping = 26, stiffness = 170, mass = 1 } = config ?? {};
39
- const omega = Math.sqrt(stiffness / mass);
40
- const zeta = damping / (2 * Math.sqrt(stiffness * mass));
41
- const t = progress * 3.5;
42
- let value;
43
- if (zeta < 1) {
44
- const omegaD = omega * Math.sqrt(1 - zeta * zeta);
45
- value = 1 - Math.exp(-zeta * omega * t) * (Math.cos(omegaD * t) + zeta * omega / omegaD * Math.sin(omegaD * t));
46
- } else if (zeta === 1) {
47
- value = 1 - Math.exp(-omega * t) * (1 + omega * t);
48
- } else {
49
- const s1 = -omega * (zeta + Math.sqrt(zeta * zeta - 1));
50
- const s2 = -omega * (zeta - Math.sqrt(zeta * zeta - 1));
51
- value = 1 + (s1 * Math.exp(s2 * t) - s2 * Math.exp(s1 * t)) / (s2 - s1);
52
- }
53
- return value;
54
- }
55
-
56
- // src/visual-system/scene-templates/scene-background.tsx
57
- import { useEffect as useEffect2, useState as useState2 } from "react";
58
-
59
- // src/visual-system/scene-templates/color-utils.ts
60
- import React from "react";
61
-
62
- // src/visual-system/theme/colors.ts
63
- function orderDarkToLight(a, b) {
64
- const luminance = (hex) => {
65
- const match = hex.match(/^#([0-9a-f]{6})$/i);
66
- if (!match) return 0;
67
- const h = match[1];
68
- return 0.299 * parseInt(h.slice(0, 2), 16) + 0.587 * parseInt(h.slice(2, 4), 16) + 0.114 * parseInt(h.slice(4, 6), 16);
69
- };
70
- return luminance(a) <= luminance(b) ? [a, b] : [b, a];
71
- }
72
-
73
- // src/visual-system/scene-templates/color-utils.ts
74
- function gradientBackground({
75
- colorA,
76
- colorB,
77
- solidBg,
78
- progress,
79
- sceneDuration,
80
- seed,
81
- family
82
- }) {
83
- if (solidBg) return solidBg;
84
- const normalizedProgress = Math.max(0, Math.min(1, progress));
85
- const atBoundary = normalizedProgress === 0 || normalizedProgress === 1;
86
- const easedLoop = normalizedProgress * normalizedProgress * (3 - 2 * normalizedProgress);
87
- const seedEnvelope = atBoundary ? 0 : Math.sin(normalizedProgress * Math.PI) ** 2;
88
- void sceneDuration;
89
- const seededRandom = (s) => {
90
- const x = Math.sin(s * 12.9898 + s * 78.233) * 43758.5453;
91
- return x - Math.floor(x);
92
- };
93
- const phase = seededRandom(seed) * Math.PI * 2 * seedEnvelope;
94
- const t = (atBoundary ? 0 : easedLoop * Math.PI * 2) + phase;
95
- const [topColor, bottomColor] = orderDarkToLight(colorA, colorB);
96
- if (family === "wash") {
97
- const shift = Math.sin(t) * 6;
98
- return `linear-gradient(${175 + shift}deg, ${topColor} 0%, ${bottomColor} 100%)`;
99
- }
100
- if (family === "spotlight") {
101
- const x = 50 + Math.sin(t) * 8;
102
- const y = 28 + Math.cos(t + 1) * 5;
103
- return `
104
- radial-gradient(ellipse 70% 55% at ${x}% ${y}%, ${colorA}66 0%, ${colorA}1a 45%, transparent 72%),
105
- linear-gradient(180deg, ${darken(topColor, 0.55)} 0%, ${darken(bottomColor, 0.7)} 100%)
106
- `;
107
- }
108
- const x1 = 25 + Math.sin(t) * 25;
109
- const y1 = 30 + Math.cos(t + 1) * 25;
110
- const x2 = 75 + Math.sin(t + 2) * 25;
111
- const y2 = 70 + Math.cos(t + 3.5) * 25;
112
- return `
113
- radial-gradient(ellipse 60% 60% at ${x1}% ${y1}%, ${colorA}4d 0%, transparent 65%),
114
- radial-gradient(ellipse 60% 60% at ${x2}% ${y2}%, ${colorB}4d 0%, transparent 65%),
115
- linear-gradient(180deg, ${topColor} 0%, ${bottomColor} 100%)
116
- `;
117
- }
118
- var BrandGradientOverlay = ({ style: globalStyle, progress, sceneDuration, seed }) => {
119
- const tokens = resolveTokens(globalStyle);
120
- const [first, second] = tokens.background.type === "gradient" ? tokens.background.colors : [tokens.background.color, tokens.background.color];
121
- const solidBg = tokens.background.type === "solid" ? tokens.background.color : void 0;
122
- return React.createElement("div", {
123
- "data-brand-gradient": "true",
124
- style: {
125
- position: "absolute",
126
- inset: 0,
127
- background: gradientBackground({ colorA: first, colorB: second, solidBg, progress, sceneDuration, seed, family: tokens.preset.background }),
128
- pointerEvents: "none"
129
- }
130
- });
131
- };
132
-
133
- // src/visual-system/scene-templates/background-effect.ts
134
- var DIRECTIONS = ["right", "left", "up", "down"];
135
- function getKenBurnsTransform(progress, direction) {
136
- const scale = interpolate(progress, [0, 1], [1.05, 1.15]);
137
- const mainAxis = direction === "right" || direction === "left";
138
- const sign = direction === "right" || direction === "down" ? 1 : -1;
139
- const primary = interpolate(progress, [0, 1], [-2 * sign, 2 * sign]);
140
- const secondary = interpolate(progress, [0, 1], [-0.5, 0.5]);
141
- const x = mainAxis ? primary : secondary;
142
- const y = mainAxis ? secondary : primary;
143
- return {
144
- transform: `scale(${scale}) translate(${x}%, ${y}%)`,
145
- transformOrigin: "center"
146
- };
147
- }
148
- function getDriftTransform(progress, direction) {
149
- const horizontal = direction === "right" || direction === "left";
150
- const sign = direction === "right" || direction === "down" ? 1 : -1;
151
- const offset = interpolate(progress, [0, 1], [-2 * sign, 2 * sign]);
152
- return {
153
- transform: horizontal ? `scale(1.1) translateX(${offset}%)` : `scale(1.1) translateY(${offset}%)`,
154
- transformOrigin: "center"
155
- };
156
- }
157
- function getBackgroundTransform(effect, progress, beatIntensity = 0, sceneIndex = 0) {
158
- const dir = DIRECTIONS[sceneIndex % DIRECTIONS.length];
159
- const resolved = effect ?? "static";
160
- switch (resolved) {
161
- case "slow-zoom-in":
162
- return {
163
- transform: `scale(${interpolate(progress, [0, 1], [1, 1.12])})`,
164
- transformOrigin: "center"
165
- };
166
- case "slow-zoom-out":
167
- return {
168
- transform: `scale(${interpolate(progress, [0, 1], [1.12, 1])})`,
169
- transformOrigin: "center"
170
- };
171
- case "ken-burns":
172
- return getKenBurnsTransform(progress, dir);
173
- case "drift":
174
- return getDriftTransform(progress, dir);
175
- case "pulse": {
176
- const base = 1 + beatIntensity * 0.04;
177
- const breathe = 1 + Math.sin(progress * Math.PI * 2) * 0.02;
178
- return {
179
- transform: `scale(${base * breathe})`,
180
- transformOrigin: "center"
181
- };
182
- }
183
- case "breathe": {
184
- const s = 1 + Math.sin(progress * Math.PI) * 0.03;
185
- const y = Math.sin(progress * Math.PI) * 0.5;
186
- return {
187
- transform: `scale(${s}) translateY(${y}%)`,
188
- transformOrigin: "center"
189
- };
190
- }
191
- case "slow-tilt": {
192
- const angle = interpolate(progress, [0, 1], [0, 2.5]);
193
- return {
194
- transform: `scale(1.08) rotate(${angle}deg)`,
195
- transformOrigin: "center"
196
- };
197
- }
198
- case "camera-shake": {
199
- const t = progress * 40;
200
- const sx = Math.sin(t * 2.3) * 0.4 + Math.sin(t * 5.7) * 0.2;
201
- const sy = Math.cos(t * 3.1) * 0.3 + Math.cos(t * 4.9) * 0.15;
202
- const sr = Math.sin(t * 1.7) * 0.3;
203
- return {
204
- transform: `scale(1.05) translate(${sx}%, ${sy}%) rotate(${sr}deg)`,
205
- transformOrigin: "center"
206
- };
207
- }
208
- case "static":
209
- default:
210
- return {
211
- transform: "none",
212
- transformOrigin: "center"
213
- };
214
- }
215
- }
216
-
217
- // src/visual-system/scene-templates/media-position.ts
218
- var MEDIA_POSITIONS = {
219
- center: "center center",
220
- top: "center top",
221
- bottom: "center bottom",
222
- left: "left center",
223
- right: "right center"
224
- };
225
- function resolveMediaPosition(value) {
226
- return MEDIA_POSITIONS[value] ?? MEDIA_POSITIONS.center;
227
- }
228
-
229
- // src/visual-system/scene-templates/scene-video-backdrop.tsx
230
- import { useCallback, useEffect, useRef, useState } from "react";
231
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
232
- var SceneVideoBackdrop = ({
233
- mediaUrl,
234
- mediaPoster,
235
- mediaPosition = "center",
236
- backgroundEffect,
237
- progress,
238
- sceneDuration,
239
- preparingNarration = false,
240
- beatIntensity = 0,
241
- isPlaying,
242
- muted,
243
- volume,
244
- playbackId = mediaUrl,
245
- onReady,
246
- onError
247
- }) => {
248
- const inheritedAudio = useMediaAudio();
249
- const reportMediaFailure = useMediaFailure();
250
- const inheritedPreroll = useNarrationPreroll();
251
- const rewindPreroll = preparingNarration || inheritedPreroll;
252
- const resolvedMuted = muted ?? inheritedAudio.muted;
253
- const resolvedVolume = volume ?? inheritedAudio.volume;
254
- const resolvedPosition = resolveMediaPosition(mediaPosition);
255
- const bgTransform = getBackgroundTransform(backgroundEffect, progress, beatIntensity);
256
- const [decodedVideoUrl, setDecodedVideoUrl] = useState();
257
- const [waitingKey, setWaitingKey] = useState();
258
- const [exhaustedKey, setExhaustedKey] = useState();
259
- const videoRef = useRef(null);
260
- const playableVideoUrl = useRef(void 0);
261
- const startedVideoUrl = useRef(void 0);
262
- const startedPlaybackId = useRef(void 0);
263
- const videoPresentationKey = `${playbackId}\0${mediaUrl}`;
264
- const presentationRef = useRef({ key: videoPresentationKey, playing: isPlaying });
265
- presentationRef.current = { key: videoPresentationKey, playing: isPlaying };
266
- const unavailable = (reason = "playback-error") => {
267
- if (presentationRef.current.key === videoPresentationKey && presentationRef.current.playing) {
268
- setExhaustedKey(videoPresentationKey);
269
- onError?.();
270
- reportMediaFailure?.(reason);
271
- }
272
- };
273
- useEffect(() => {
274
- if (!isPlaying || rewindPreroll || waitingKey !== videoPresentationKey) {
275
- if (waitingKey) setWaitingKey(void 0);
276
- return;
277
- }
278
- const video = videoRef.current;
279
- if (!video) return;
280
- const expectedSource = video.getAttribute("src") === mediaUrl ? video.src : void 0;
281
- let awaitingPlayback = playableVideoUrl.current !== mediaUrl || video.currentSrc !== expectedSource;
282
- let previousTime = video.currentTime;
283
- let forwardFrames = 0;
284
- let stopped = false;
285
- let frame;
286
- let poll;
287
- const observe = (_now, metadata) => {
288
- if (stopped) return;
289
- const currentSource = video.currentSrc === expectedSource;
290
- if (currentSource && awaitingPlayback && video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) {
291
- playableVideoUrl.current = mediaUrl;
292
- awaitingPlayback = false;
293
- clearTimeout(deadline);
294
- deadline = setTimeout(fail, 1e3);
295
- }
296
- const time = metadata?.mediaTime ?? video.currentTime;
297
- if (!currentSource || video.seeking || time < previousTime) forwardFrames = 0;
298
- else if (time > previousTime + 1e-3) forwardFrames++;
299
- previousTime = time;
300
- if (forwardFrames >= 2) {
301
- playableVideoUrl.current = mediaUrl;
302
- stopped = true;
303
- clearTimeout(deadline);
304
- setWaitingKey(void 0);
305
- return;
306
- }
307
- if (video.requestVideoFrameCallback) frame = video.requestVideoFrameCallback(observe);
308
- else poll = setTimeout(observe, 50);
309
- };
310
- const fail = () => {
311
- if (!stopped) {
312
- stopped = true;
313
- unavailable(awaitingPlayback ? "frame-readiness-timeout" : "stalled-media");
314
- }
315
- };
316
- let deadline = setTimeout(fail, awaitingPlayback ? 8e3 : 1e3);
317
- observe();
318
- return () => {
319
- stopped = true;
320
- clearTimeout(deadline);
321
- clearTimeout(poll);
322
- if (frame !== void 0) video.cancelVideoFrameCallback?.(frame);
323
- };
324
- }, [waitingKey, videoPresentationKey, isPlaying, rewindPreroll]);
325
- const onReadyRef = useRef(onReady);
326
- onReadyRef.current = onReady;
327
- useEffect(() => {
328
- const video = videoRef.current;
329
- if (!video) return;
330
- let stopped = false;
331
- let frame;
332
- const markPresented = () => {
333
- if (stopped || !video.isConnected || presentationRef.current.key !== videoPresentationKey || video.getAttribute("src") !== mediaUrl || video.currentSrc !== video.src) return false;
334
- if (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) playableVideoUrl.current = mediaUrl;
335
- video.dispatchEvent(new Event("vanillasky:video-frame-presented", { bubbles: true }));
336
- onReadyRef.current?.();
337
- setDecodedVideoUrl(mediaUrl);
338
- stopped = true;
339
- return true;
340
- };
341
- const observe = () => {
342
- if (stopped || frame !== void 0) return;
343
- if (video.requestVideoFrameCallback) {
344
- frame = video.requestVideoFrameCallback(() => {
345
- frame = void 0;
346
- if (!markPresented()) observe();
347
- });
348
- } else if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) markPresented();
349
- };
350
- video.addEventListener("loadeddata", observe);
351
- observe();
352
- return () => {
353
- stopped = true;
354
- video.removeEventListener("loadeddata", observe);
355
- if (frame !== void 0) video.cancelVideoFrameCallback?.(frame);
356
- };
357
- }, [mediaUrl, videoPresentationKey]);
358
- const fitDuration = useCallback((video) => {
359
- 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;
360
- }, [resolvedMuted, sceneDuration]);
361
- useEffect(() => {
362
- if (videoRef.current) fitDuration(videoRef.current);
363
- }, [fitDuration]);
364
- const continueMotion = (video) => {
365
- if (!isPlaying) return;
366
- if (!resolvedMuted) {
367
- unavailable();
368
- return;
369
- }
370
- video.currentTime = 0;
371
- void video.play().catch(() => unavailable());
372
- };
373
- useEffect(() => {
374
- const video = videoRef.current;
375
- if (!video) return;
376
- if (video.getAttribute("src") !== mediaUrl) {
377
- video.setAttribute("src", mediaUrl);
378
- video.load();
379
- }
380
- }, [mediaUrl]);
381
- useEffect(() => {
382
- const video = videoRef.current;
383
- if (!video) return;
384
- return () => {
385
- video.pause();
386
- video.removeAttribute("src");
387
- video.load();
388
- startedVideoUrl.current = void 0;
389
- startedPlaybackId.current = void 0;
390
- };
391
- }, []);
392
- useEffect(() => {
393
- const video = videoRef.current;
394
- if (video) video.volume = resolvedVolume;
395
- }, [resolvedVolume]);
396
- useEffect(() => {
397
- const video = videoRef.current;
398
- if (!video) return;
399
- if (!isPlaying) {
400
- video.pause();
401
- if (rewindPreroll && !resolvedMuted && video.currentTime > 0) video.currentTime = 0;
402
- return;
403
- }
404
- if (startedPlaybackId.current === playbackId) {
405
- if (video.ended) continueMotion(video);
406
- else void video.play().catch(() => unavailable());
407
- return;
408
- }
409
- const changingSource = startedVideoUrl.current !== void 0 && startedVideoUrl.current !== mediaUrl;
410
- fitDuration(video);
411
- if (!changingSource && video.currentTime > 0) video.currentTime = 0;
412
- video.play().catch(() => unavailable());
413
- startedVideoUrl.current = mediaUrl;
414
- startedPlaybackId.current = playbackId;
415
- }, [isPlaying, mediaUrl, playbackId, rewindPreroll]);
416
- const enforceRequestedPause = (event) => {
417
- if (presentationRef.current.playing) return;
418
- event.currentTarget.pause();
419
- if (rewindPreroll && !resolvedMuted && event.currentTarget.currentTime > 0) event.currentTarget.currentTime = 0;
420
- };
421
- const mediaStyle = {
422
- position: "absolute",
423
- inset: 0,
424
- width: "100%",
425
- height: "100%",
426
- objectFit: "cover",
427
- objectPosition: resolvedPosition,
428
- transform: bgTransform.transform,
429
- transformOrigin: bgTransform.transformOrigin
430
- };
431
- return /* @__PURE__ */ jsxs(Fragment, { children: [
432
- exhaustedKey === videoPresentationKey && /* @__PURE__ */ jsx(
433
- "div",
434
- {
435
- role: "status",
436
- "data-media-continuity": "exhausted",
437
- style: { position: "absolute", inset: 0, zIndex: 3, background: "#000", color: "#bbb", display: "grid", placeContent: "center", font: "14px system-ui" },
438
- children: "Visual unavailable"
439
- }
440
- ),
441
- /* @__PURE__ */ jsx(
442
- "video",
443
- {
444
- ref: videoRef,
445
- src: mediaUrl,
446
- poster: decodedVideoUrl !== mediaUrl ? mediaPoster || void 0 : void 0,
447
- muted: resolvedMuted,
448
- loop: resolvedMuted && isPlaying && Number.isFinite(sceneDuration) && Number(sceneDuration) > 0,
449
- playsInline: true,
450
- preload: "auto",
451
- onLoadedMetadata: (event) => fitDuration(event.currentTarget),
452
- onEnded: (event) => continueMotion(event.currentTarget),
453
- onPlay: enforceRequestedPause,
454
- onPlaying: (event) => {
455
- if (event.currentTarget.currentSrc === event.currentTarget.src && event.currentTarget.getAttribute("src") === mediaUrl && event.currentTarget.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) playableVideoUrl.current = mediaUrl;
456
- enforceRequestedPause(event);
457
- },
458
- onWaiting: () => {
459
- if (isPlaying) setWaitingKey(videoPresentationKey);
460
- },
461
- onError,
462
- "data-media-position": mediaPosition,
463
- "data-video-backdrop": "scene",
464
- style: { ...mediaStyle, visibility: exhaustedKey === videoPresentationKey ? "hidden" : void 0 }
465
- }
466
- )
467
- ] });
468
- };
469
-
470
- // src/visual-system/scene-templates/scene-background.tsx
471
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
472
- function resolveMediaTreatment(value) {
473
- return value === "none" || value === "subtle" || value === "text-safe" ? value : "cinematic";
474
- }
475
- var SCRIM_STOP_COUNT = 7;
476
- function smoothstep(t) {
477
- return t * t * (3 - 2 * t);
478
- }
479
- function easedStops(peakAlpha, start, end, direction) {
480
- const alphaAt = (t) => {
481
- const eased = direction === "fade-out" ? 1 - smoothstep(t) : smoothstep(t);
482
- return `rgba(0,0,0,${Number((peakAlpha * eased).toFixed(3))})`;
483
- };
484
- const stops = [];
485
- if (start > 0) stops.push(`${alphaAt(0)} 0%`);
486
- for (let i = 0; i < SCRIM_STOP_COUNT; i += 1) {
487
- const t = i / (SCRIM_STOP_COUNT - 1);
488
- const position = Number((start + (end - start) * t).toFixed(2));
489
- stops.push(`${alphaAt(t)} ${position}%`);
490
- }
491
- if (end < 100) stops.push(`${alphaAt(1)} 100%`);
492
- return stops.join(", ");
493
- }
494
- function getMediaTreatmentLayers(value, anchor = "full") {
495
- const treatment = resolveMediaTreatment(value);
496
- if (treatment === "none") return [];
497
- const vignette = {
498
- id: "vignette",
499
- background: treatment === "subtle" ? `radial-gradient(ellipse at center, ${easedStops(0.28, 45, 100, "fade-in")})` : `radial-gradient(ellipse at center, ${easedStops(0.72, 32, 100, "fade-in")})`
500
- };
501
- if (treatment === "subtle") return [vignette];
502
- const textSafe = treatment === "text-safe";
503
- const layers = [vignette];
504
- if (anchor !== "bottom") {
505
- layers.push({
506
- id: "center-scrim",
507
- background: textSafe ? `radial-gradient(ellipse 92% 58% at 50% 50%, ${easedStops(0.46, 34, 90, "fade-out")})` : `radial-gradient(ellipse 88% 52% at 50% 50%, ${easedStops(0.26, 30, 88, "fade-out")})`
508
- });
509
- }
510
- if (anchor !== "center") {
511
- layers.push({
512
- id: "bottom-scrim",
513
- background: `linear-gradient(to top, ${easedStops(textSafe ? 0.64 : 0.5, 8, 100, "fade-out")})`,
514
- style: { top: "55%" }
515
- });
516
- }
517
- return layers;
518
- }
519
- function initialMediaPaint(wantsMedia, resolved, mediaUrl, mediaPoster) {
520
- if (typeof window === "undefined") return "ready";
521
- if (!wantsMedia) return "ready";
522
- if (resolved === "video") return mediaPoster ? "ready" : "pending";
523
- if (typeof Image === "undefined") return "ready";
524
- const cached = new Image();
525
- cached.src = mediaUrl;
526
- return cached.complete && cached.naturalWidth > 0 ? "ready" : "pending";
527
- }
528
- function getMediaBackgroundProps(variables) {
529
- return {
530
- mediaUrl: String(variables.mediaUrl || ""),
531
- mediaType: String(variables.mediaType || "auto"),
532
- mediaPoster: String(variables.mediaPoster || ""),
533
- mediaPosition: String(variables.mediaPosition || "center"),
534
- mediaTreatment: String(variables.mediaTreatment || "cinematic")
535
- };
536
- }
537
- var SceneBackground = ({
538
- style,
539
- progress,
540
- sceneDuration,
541
- width: _width,
542
- // accepted for symmetry; not currently used in render
543
- height: _height,
544
- mediaUrl = "",
545
- mediaType = "auto",
546
- mediaPoster,
547
- mediaPosition = "center",
548
- mediaTreatment = "cinematic",
549
- textAnchor = "full",
550
- backgroundEffect,
551
- seed,
552
- isPlaying = true,
553
- beatIntensity = 0
554
- }) => {
555
- void _width;
556
- void _height;
557
- const resolved = resolveMediaType(mediaType, mediaUrl);
558
- const wantsMedia = resolved !== "gradient" && !!mediaUrl;
559
- const externalVideoBackdrop = useExternalVideoBackdrop();
560
- const hasExternalVideoBackdrop = externalVideoBackdrop !== false && resolved === "video";
561
- const externalVideoFailed = externalVideoBackdrop === "fallback" && resolved === "video";
562
- const externalVideoReady = externalVideoBackdrop === "ready" && resolved === "video";
563
- const [mediaPaint, setMediaPaint] = useState2(
564
- () => initialMediaPaint(wantsMedia, resolved, mediaUrl, mediaPoster)
565
- );
566
- useEffect2(() => {
567
- setMediaPaint(initialMediaPaint(wantsMedia, resolved, mediaUrl, mediaPoster));
568
- if (!wantsMedia || resolved !== "photo") return;
569
- if (typeof Image === "undefined") return;
570
- let cancelled = false;
571
- const probe = new Image();
572
- probe.onload = () => {
573
- if (!cancelled) setMediaPaint("ready");
574
- };
575
- probe.onerror = () => {
576
- if (!cancelled) setMediaPaint("failed");
577
- };
578
- probe.src = mediaUrl;
579
- if (probe.complete) setMediaPaint(probe.naturalWidth > 0 ? "ready" : "failed");
580
- return () => {
581
- cancelled = true;
582
- probe.onload = null;
583
- probe.onerror = null;
584
- };
585
- }, [mediaUrl, mediaPoster, resolved, wantsMedia]);
586
- const showMedia = wantsMedia && mediaPaint !== "failed";
587
- const showTreatment = wantsMedia && mediaPaint === "ready";
588
- const resolvedPosition = resolveMediaPosition(mediaPosition);
589
- const resolvedTreatment = resolveMediaTreatment(mediaTreatment);
590
- const treatmentLayers = getMediaTreatmentLayers(resolvedTreatment, textAnchor);
591
- const gradSeed = typeof seed === "number" ? seed : typeof seed === "string" ? seed.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0) : 0;
592
- const bgTransform = getBackgroundTransform(
593
- backgroundEffect,
594
- progress,
595
- beatIntensity
596
- );
597
- return /* @__PURE__ */ jsxs2(Fragment2, { children: [
598
- (!hasExternalVideoBackdrop || externalVideoFailed) && /* @__PURE__ */ jsx2(
599
- BrandGradientOverlay,
600
- {
601
- style,
602
- progress,
603
- sceneDuration,
604
- seed: gradSeed
605
- }
606
- ),
607
- showMedia && !hasExternalVideoBackdrop && (resolved === "video" ? /* @__PURE__ */ jsx2(
608
- SceneVideoBackdrop,
609
- {
610
- mediaUrl,
611
- mediaPoster,
612
- mediaPosition,
613
- backgroundEffect,
614
- progress,
615
- sceneDuration,
616
- beatIntensity,
617
- isPlaying,
618
- onReady: () => setMediaPaint("ready"),
619
- onError: () => setMediaPaint("failed")
620
- }
621
- ) : /* @__PURE__ */ jsx2(
622
- "img",
623
- {
624
- src: mediaUrl,
625
- alt: "",
626
- "aria-hidden": "true",
627
- draggable: false,
628
- "data-media-position": mediaPosition,
629
- style: {
630
- position: "absolute",
631
- inset: 0,
632
- transform: bgTransform.transform,
633
- transformOrigin: bgTransform.transformOrigin,
634
- width: "100%",
635
- height: "100%",
636
- objectFit: "cover",
637
- objectPosition: resolvedPosition
638
- }
639
- }
640
- )),
641
- (hasExternalVideoBackdrop ? externalVideoReady : showTreatment) && !externalVideoFailed && treatmentLayers.map((layer) => /* @__PURE__ */ jsx2(
642
- "div",
643
- {
644
- "data-media-treatment": resolvedTreatment,
645
- "data-media-overlay": layer.id,
646
- style: {
647
- position: "absolute",
648
- inset: 0,
649
- background: layer.background,
650
- pointerEvents: "none",
651
- ...layer.style
652
- }
653
- },
654
- layer.id
655
- ))
656
- ] });
657
- };
658
-
659
- export {
660
- spring,
661
- getMediaBackgroundProps,
662
- SceneBackground
663
- };
@@ -1,28 +0,0 @@
1
- import {
2
- getReadableSceneDuration
3
- } from "./chunk-3O7OMMMF.js";
4
-
5
- // src/protocol/scene-duration.ts
6
- var NARRATION_WORDS_PER_SECOND = 2.6;
7
- var NARRATION_TAIL_SECONDS = 0.6;
8
- function getSceneDurationBounds(scene, metadata) {
9
- return {
10
- minimum: Math.max(1e-3, metadata?.minDuration ?? 1),
11
- readable: getReadableSceneDuration(scene, metadata)
12
- };
13
- }
14
- function getSpokenDuration(narration) {
15
- const words = narration.trim().split(/\s+/u).filter(Boolean).length;
16
- if (words === 0) return 0;
17
- return words / NARRATION_WORDS_PER_SECOND + NARRATION_TAIL_SECONDS;
18
- }
19
- function getSceneDuration(scene, metadata) {
20
- const { readable } = getSceneDurationBounds(scene, metadata);
21
- return Math.max(readable, scene.narration ? getSpokenDuration(scene.narration) : 0);
22
- }
23
-
24
- export {
25
- getSceneDurationBounds,
26
- getSpokenDuration,
27
- getSceneDuration
28
- };
@@ -1,11 +0,0 @@
1
- import {
2
- MediaScene,
3
- MediaSceneTemplate
4
- } from "./chunk-QSBDB4J2.js";
5
- import "./chunk-YAHT3LST.js";
6
- import "./chunk-5JBMYQP6.js";
7
- import "./chunk-224QNWRA.js";
8
- export {
9
- MediaScene,
10
- MediaSceneTemplate
11
- };