@vanillaskyai/video 0.10.6 → 0.10.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/{chapter-title-YRLIQY3V.js → chapter-title-2JVDU62E.js} +1 -1
- package/dist/check-runtime.js +2 -2
- package/dist/{chunk-EWWCQFTI.js → chunk-7AA2JWHZ.js} +3 -2
- package/dist/{chunk-DD4ZSYKG.js → chunk-J7EGPURK.js} +309 -129
- package/dist/{chunk-VWJBIYBM.js → chunk-KUCEOG2L.js} +53 -132
- package/dist/{chunk-XBIKTSFY.js → chunk-Q3LPOPHL.js} +1 -1
- package/dist/{chunk-JNZRVC45.js → chunk-R5ZX2LJV.js} +1 -1
- package/dist/{cinema-media-AYKCBXRI.js → cinema-media-WUS7CKPC.js} +3 -4
- package/dist/{comparison-74B2XEH4.js → comparison-T4OYCOH3.js} +3 -4
- package/dist/{editorial-timeline-DUW7C75H.js → editorial-timeline-JHQZIPNE.js} +3 -4
- package/dist/{key-figure-OEWIMPGS.js → key-figure-YWN2OBQU.js} +3 -4
- package/dist/{mobile-message-KBLF3WUR.js → mobile-message-CDKOERRZ.js} +3 -4
- package/dist/{quote-QCGR2HOU.js → quote-JKABOMIG.js} +3 -4
- package/dist/react.js +36 -22
- package/dist/server.js +15 -5
- package/package.json +1 -1
- package/registry/items/backgrounds.json +2 -2
- package/registry/items/chapterTitle.json +1 -1
- package/starters/video-chat/README.md +4 -8
- package/starters/video-chat/package.json +1 -1
- package/starters/video-chat/server.ts +0 -5
- package/dist/chunk-MGCD3ZJ3.js +0 -207
- package/dist/scene-video-backdrop-MUGIXE2Q.js +0 -7
- package/dist/{chunk-OOBT4X46.js → chunk-5JBMYQP6.js} +34 -34
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@ VanillaSky follows semantic versioning. This changelog begins with the 0.1 beta.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.10.8
|
|
8
|
+
|
|
9
|
+
- Reassert a requested pause if native video playback starts late, preserving footage during delayed narration and keeping viewer pauses in place.
|
|
10
|
+
|
|
11
|
+
- Distinguish decode errors, frame-readiness timeouts and stalled footage in the local chat diagnostic log without retaining media URLs or user content.
|
|
12
|
+
|
|
13
|
+
- Correct starter guidance for separate footage modes and remove obsolete planner logs that included raw model output.
|
|
14
|
+
- Prepare the next compatible mobile video while the current scene plays, retaining its decoded element across cuts and limiting mounted footage to the active and next scenes.
|
|
15
|
+
- Observe cached video frames on mount so a missed loading event cannot delay genuine stall recovery.
|
|
16
|
+
|
|
17
|
+
## 0.10.7
|
|
18
|
+
|
|
19
|
+
- Reuse the first presented video frame at scene handoffs so a second readiness observation cannot briefly interrupt continuous narration.
|
|
20
|
+
|
|
21
|
+
- Keep an already-speaking paragraph uninterrupted across brief visual handoffs, while genuinely late footage still pauses narration until it can play.
|
|
22
|
+
|
|
23
|
+
- Keep the final chapter visible through answer completion, including recovery from failed footage.
|
|
24
|
+
- Derive recovery titles from authored titles or subject excerpts instead of a generic placeholder.
|
|
25
|
+
|
|
26
|
+
- Let cold footage finish its bounded initial load before treating it as stalled. Require the actual new source to present a frame before narration starts, and keep source changes from cancelling their own native load.
|
|
27
|
+
|
|
7
28
|
## 0.10.6
|
|
8
29
|
|
|
9
30
|
- Initialize the reusable narration audio element during the existing user gesture so delayed first speech can play on Safari.
|
package/dist/check-runtime.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VideoFrame
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KUCEOG2L.js";
|
|
4
|
+
import "./chunk-5JBMYQP6.js";
|
|
4
5
|
import "./chunk-224QNWRA.js";
|
|
5
|
-
import "./chunk-OOBT4X46.js";
|
|
6
6
|
import "./chunk-SPVTJH3F.js";
|
|
7
7
|
import "./chunk-R3XAOMKP.js";
|
|
8
8
|
import "./chunk-73NTSFFI.js";
|
|
@@ -5,10 +5,11 @@ import {
|
|
|
5
5
|
|
|
6
6
|
// src/visual-system/scene-templates/chapter-title.tsx
|
|
7
7
|
import { jsx } from "react/jsx-runtime";
|
|
8
|
-
function TitleScene({ variables, width, height, progress }) {
|
|
8
|
+
function TitleScene({ variables, width, height, progress, motionProgress }) {
|
|
9
9
|
const title = String(variables.title ?? "A different perspective");
|
|
10
10
|
const unit = Math.min(width, height);
|
|
11
|
-
const
|
|
11
|
+
const presentation = motionProgress ?? progress;
|
|
12
|
+
const opacity = fade(presentation / 0.22) * (1 - fade((presentation - 0.76) / 0.24));
|
|
12
13
|
return /* @__PURE__ */ jsx("div", { "data-template": "title", "data-title-treatment": "quiet-fade", style: { position: "absolute", inset: 0, background: "#000", color: "#fff", display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx("div", { "data-title-composition": "centered", style: { width: "76%", textAlign: "center", opacity, fontFamily: editorialFont, fontWeight: 500, fontSize: unit * 0.068, lineHeight: 1.18, letterSpacing: "-.025em", textWrap: "balance", overflowWrap: "anywhere" }, children: title }) });
|
|
13
14
|
}
|
|
14
15
|
var TitleSceneTemplate = TitleScene;
|
|
@@ -1,13 +1,60 @@
|
|
|
1
1
|
import {
|
|
2
2
|
darken,
|
|
3
3
|
resolveTokens,
|
|
4
|
+
useExternalVideoBackdrop,
|
|
4
5
|
useMediaAudio,
|
|
5
6
|
useMediaFailure,
|
|
6
7
|
useNarrationPreroll
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-5JBMYQP6.js";
|
|
9
|
+
import {
|
|
10
|
+
resolveMediaType
|
|
11
|
+
} from "./chunk-224QNWRA.js";
|
|
8
12
|
|
|
9
|
-
// src/visual-system/
|
|
10
|
-
|
|
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";
|
|
11
58
|
|
|
12
59
|
// src/visual-system/scene-templates/color-utils.ts
|
|
13
60
|
import React from "react";
|
|
@@ -83,49 +130,6 @@ var BrandGradientOverlay = ({ style: globalStyle, progress, sceneDuration, seed
|
|
|
83
130
|
});
|
|
84
131
|
};
|
|
85
132
|
|
|
86
|
-
// src/visual-system/motion/curves.ts
|
|
87
|
-
function interpolate(value, inputRange, outputRange, options) {
|
|
88
|
-
const { easing, extrapolateLeft = "extend", extrapolateRight = "extend" } = options ?? {};
|
|
89
|
-
let i = 0;
|
|
90
|
-
for (; i < inputRange.length - 2; i++) {
|
|
91
|
-
if (value < inputRange[i + 1]) break;
|
|
92
|
-
}
|
|
93
|
-
const inputMin = inputRange[i];
|
|
94
|
-
const inputMax = inputRange[i + 1];
|
|
95
|
-
const outputMin = outputRange[i];
|
|
96
|
-
const outputMax = outputRange[i + 1];
|
|
97
|
-
let t = inputMax === inputMin ? 0 : (value - inputMin) / (inputMax - inputMin);
|
|
98
|
-
if (t < 0 && extrapolateLeft === "clamp") t = 0;
|
|
99
|
-
if (t > 1 && extrapolateRight === "clamp") t = 1;
|
|
100
|
-
if (easing && t >= 0 && t <= 1) {
|
|
101
|
-
t = easing(t);
|
|
102
|
-
}
|
|
103
|
-
return outputMin + t * (outputMax - outputMin);
|
|
104
|
-
}
|
|
105
|
-
function spring(progress, config) {
|
|
106
|
-
if (progress <= 0) return 0;
|
|
107
|
-
if (progress >= 1) {
|
|
108
|
-
const { damping: damping2 = 26 } = config ?? {};
|
|
109
|
-
if (damping2 >= 20) return 1;
|
|
110
|
-
}
|
|
111
|
-
const { damping = 26, stiffness = 170, mass = 1 } = config ?? {};
|
|
112
|
-
const omega = Math.sqrt(stiffness / mass);
|
|
113
|
-
const zeta = damping / (2 * Math.sqrt(stiffness * mass));
|
|
114
|
-
const t = progress * 3.5;
|
|
115
|
-
let value;
|
|
116
|
-
if (zeta < 1) {
|
|
117
|
-
const omegaD = omega * Math.sqrt(1 - zeta * zeta);
|
|
118
|
-
value = 1 - Math.exp(-zeta * omega * t) * (Math.cos(omegaD * t) + zeta * omega / omegaD * Math.sin(omegaD * t));
|
|
119
|
-
} else if (zeta === 1) {
|
|
120
|
-
value = 1 - Math.exp(-omega * t) * (1 + omega * t);
|
|
121
|
-
} else {
|
|
122
|
-
const s1 = -omega * (zeta + Math.sqrt(zeta * zeta - 1));
|
|
123
|
-
const s2 = -omega * (zeta - Math.sqrt(zeta * zeta - 1));
|
|
124
|
-
value = 1 + (s1 * Math.exp(s2 * t) - s2 * Math.exp(s1 * t)) / (s2 - s1);
|
|
125
|
-
}
|
|
126
|
-
return value;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
133
|
// src/visual-system/scene-templates/background-effect.ts
|
|
130
134
|
var DIRECTIONS = ["right", "left", "up", "down"];
|
|
131
135
|
function getKenBurnsTransform(progress, direction) {
|
|
@@ -223,6 +227,7 @@ function resolveMediaPosition(value) {
|
|
|
223
227
|
}
|
|
224
228
|
|
|
225
229
|
// src/visual-system/scene-templates/scene-video-backdrop.tsx
|
|
230
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
226
231
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
227
232
|
var SceneVideoBackdrop = ({
|
|
228
233
|
mediaUrl,
|
|
@@ -237,9 +242,6 @@ var SceneVideoBackdrop = ({
|
|
|
237
242
|
muted,
|
|
238
243
|
volume,
|
|
239
244
|
playbackId = mediaUrl,
|
|
240
|
-
retainPoster = false,
|
|
241
|
-
persistent = false,
|
|
242
|
-
preparedPoster,
|
|
243
245
|
onReady,
|
|
244
246
|
onError
|
|
245
247
|
}) => {
|
|
@@ -255,16 +257,17 @@ var SceneVideoBackdrop = ({
|
|
|
255
257
|
const [waitingKey, setWaitingKey] = useState();
|
|
256
258
|
const [exhaustedKey, setExhaustedKey] = useState();
|
|
257
259
|
const videoRef = useRef(null);
|
|
260
|
+
const presentedVideoUrl = useRef(void 0);
|
|
258
261
|
const startedVideoUrl = useRef(void 0);
|
|
259
262
|
const startedPlaybackId = useRef(void 0);
|
|
260
263
|
const videoPresentationKey = `${playbackId}\0${mediaUrl}`;
|
|
261
264
|
const presentationRef = useRef({ key: videoPresentationKey, playing: isPlaying });
|
|
262
265
|
presentationRef.current = { key: videoPresentationKey, playing: isPlaying };
|
|
263
|
-
const unavailable = () => {
|
|
266
|
+
const unavailable = (reason = "playback-error") => {
|
|
264
267
|
if (presentationRef.current.key === videoPresentationKey && presentationRef.current.playing) {
|
|
265
268
|
setExhaustedKey(videoPresentationKey);
|
|
266
269
|
onError?.();
|
|
267
|
-
reportMediaFailure?.();
|
|
270
|
+
reportMediaFailure?.(reason);
|
|
268
271
|
}
|
|
269
272
|
};
|
|
270
273
|
useEffect(() => {
|
|
@@ -274,6 +277,8 @@ var SceneVideoBackdrop = ({
|
|
|
274
277
|
}
|
|
275
278
|
const video = videoRef.current;
|
|
276
279
|
if (!video) return;
|
|
280
|
+
const expectedSource = video.getAttribute("src") === mediaUrl ? video.src : void 0;
|
|
281
|
+
let awaitingFirstFrame = presentedVideoUrl.current !== mediaUrl || video.currentSrc !== expectedSource;
|
|
277
282
|
let previousTime = video.currentTime;
|
|
278
283
|
let forwardFrames = 0;
|
|
279
284
|
let stopped = false;
|
|
@@ -281,8 +286,14 @@ var SceneVideoBackdrop = ({
|
|
|
281
286
|
let poll;
|
|
282
287
|
const observe = (_now, metadata) => {
|
|
283
288
|
if (stopped) return;
|
|
289
|
+
const currentSource = video.currentSrc === expectedSource;
|
|
290
|
+
if (currentSource && awaitingFirstFrame && (metadata || presentedVideoUrl.current === mediaUrl)) {
|
|
291
|
+
awaitingFirstFrame = false;
|
|
292
|
+
clearTimeout(deadline);
|
|
293
|
+
deadline = setTimeout(fail, 1e3);
|
|
294
|
+
}
|
|
284
295
|
const time = metadata?.mediaTime ?? video.currentTime;
|
|
285
|
-
if (video.seeking || time < previousTime) forwardFrames = 0;
|
|
296
|
+
if (!currentSource || video.seeking || time < previousTime) forwardFrames = 0;
|
|
286
297
|
else if (time > previousTime + 1e-3) forwardFrames++;
|
|
287
298
|
previousTime = time;
|
|
288
299
|
if (forwardFrames >= 2) {
|
|
@@ -294,12 +305,13 @@ var SceneVideoBackdrop = ({
|
|
|
294
305
|
if (video.requestVideoFrameCallback) frame = video.requestVideoFrameCallback(observe);
|
|
295
306
|
else poll = setTimeout(observe, 50);
|
|
296
307
|
};
|
|
297
|
-
const
|
|
308
|
+
const fail = () => {
|
|
298
309
|
if (!stopped) {
|
|
299
310
|
stopped = true;
|
|
300
|
-
unavailable();
|
|
311
|
+
unavailable(awaitingFirstFrame ? "frame-readiness-timeout" : "stalled-media");
|
|
301
312
|
}
|
|
302
|
-
}
|
|
313
|
+
};
|
|
314
|
+
let deadline = setTimeout(fail, awaitingFirstFrame ? 8e3 : 1e3);
|
|
303
315
|
observe();
|
|
304
316
|
return () => {
|
|
305
317
|
stopped = true;
|
|
@@ -308,6 +320,39 @@ var SceneVideoBackdrop = ({
|
|
|
308
320
|
if (frame !== void 0) video.cancelVideoFrameCallback?.(frame);
|
|
309
321
|
};
|
|
310
322
|
}, [waitingKey, videoPresentationKey, isPlaying]);
|
|
323
|
+
const onReadyRef = useRef(onReady);
|
|
324
|
+
onReadyRef.current = onReady;
|
|
325
|
+
useEffect(() => {
|
|
326
|
+
const video = videoRef.current;
|
|
327
|
+
if (!video) return;
|
|
328
|
+
let stopped = false;
|
|
329
|
+
let frame;
|
|
330
|
+
const markPresented = () => {
|
|
331
|
+
if (stopped || !video.isConnected || presentationRef.current.key !== videoPresentationKey || video.getAttribute("src") !== mediaUrl || video.currentSrc !== video.src) return false;
|
|
332
|
+
presentedVideoUrl.current = mediaUrl;
|
|
333
|
+
video.dispatchEvent(new Event("vanillasky:video-frame-presented", { bubbles: true }));
|
|
334
|
+
onReadyRef.current?.();
|
|
335
|
+
setDecodedVideoUrl(mediaUrl);
|
|
336
|
+
stopped = true;
|
|
337
|
+
return true;
|
|
338
|
+
};
|
|
339
|
+
const observe = () => {
|
|
340
|
+
if (stopped || frame !== void 0) return;
|
|
341
|
+
if (video.requestVideoFrameCallback) {
|
|
342
|
+
frame = video.requestVideoFrameCallback(() => {
|
|
343
|
+
frame = void 0;
|
|
344
|
+
if (!markPresented()) observe();
|
|
345
|
+
});
|
|
346
|
+
} else if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) markPresented();
|
|
347
|
+
};
|
|
348
|
+
video.addEventListener("loadeddata", observe);
|
|
349
|
+
observe();
|
|
350
|
+
return () => {
|
|
351
|
+
stopped = true;
|
|
352
|
+
video.removeEventListener("loadeddata", observe);
|
|
353
|
+
if (frame !== void 0) video.cancelVideoFrameCallback?.(frame);
|
|
354
|
+
};
|
|
355
|
+
}, [mediaUrl, videoPresentationKey]);
|
|
311
356
|
const fitDuration = useCallback((video) => {
|
|
312
357
|
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;
|
|
313
358
|
}, [resolvedMuted, sceneDuration]);
|
|
@@ -321,7 +366,7 @@ var SceneVideoBackdrop = ({
|
|
|
321
366
|
return;
|
|
322
367
|
}
|
|
323
368
|
video.currentTime = 0;
|
|
324
|
-
void video.play().catch(unavailable);
|
|
369
|
+
void video.play().catch(() => unavailable());
|
|
325
370
|
};
|
|
326
371
|
useEffect(() => {
|
|
327
372
|
const video = videoRef.current;
|
|
@@ -330,6 +375,10 @@ var SceneVideoBackdrop = ({
|
|
|
330
375
|
video.setAttribute("src", mediaUrl);
|
|
331
376
|
video.load();
|
|
332
377
|
}
|
|
378
|
+
}, [mediaUrl]);
|
|
379
|
+
useEffect(() => {
|
|
380
|
+
const video = videoRef.current;
|
|
381
|
+
if (!video) return;
|
|
333
382
|
return () => {
|
|
334
383
|
video.pause();
|
|
335
384
|
video.removeAttribute("src");
|
|
@@ -337,7 +386,7 @@ var SceneVideoBackdrop = ({
|
|
|
337
386
|
startedVideoUrl.current = void 0;
|
|
338
387
|
startedPlaybackId.current = void 0;
|
|
339
388
|
};
|
|
340
|
-
}, [
|
|
389
|
+
}, []);
|
|
341
390
|
useEffect(() => {
|
|
342
391
|
const video = videoRef.current;
|
|
343
392
|
if (video) video.volume = resolvedVolume;
|
|
@@ -352,16 +401,21 @@ var SceneVideoBackdrop = ({
|
|
|
352
401
|
}
|
|
353
402
|
if (startedPlaybackId.current === playbackId) {
|
|
354
403
|
if (video.ended) continueMotion(video);
|
|
355
|
-
else void video.play().catch(unavailable);
|
|
404
|
+
else void video.play().catch(() => unavailable());
|
|
356
405
|
return;
|
|
357
406
|
}
|
|
358
407
|
const changingSource = startedVideoUrl.current !== void 0 && startedVideoUrl.current !== mediaUrl;
|
|
359
408
|
fitDuration(video);
|
|
360
409
|
if (!changingSource && video.currentTime > 0) video.currentTime = 0;
|
|
361
|
-
video.play().catch(unavailable);
|
|
410
|
+
video.play().catch(() => unavailable());
|
|
362
411
|
startedVideoUrl.current = mediaUrl;
|
|
363
412
|
startedPlaybackId.current = playbackId;
|
|
364
413
|
}, [isPlaying, mediaUrl, playbackId, rewindPreroll]);
|
|
414
|
+
const enforceRequestedPause = (event) => {
|
|
415
|
+
if (presentationRef.current.playing) return;
|
|
416
|
+
event.currentTarget.pause();
|
|
417
|
+
if (rewindPreroll && event.currentTarget.currentTime > 0) event.currentTarget.currentTime = 0;
|
|
418
|
+
};
|
|
365
419
|
const mediaStyle = {
|
|
366
420
|
position: "absolute",
|
|
367
421
|
inset: 0,
|
|
@@ -370,59 +424,9 @@ var SceneVideoBackdrop = ({
|
|
|
370
424
|
objectFit: "cover",
|
|
371
425
|
objectPosition: resolvedPosition,
|
|
372
426
|
transform: bgTransform.transform,
|
|
373
|
-
transformOrigin: bgTransform.transformOrigin
|
|
374
|
-
zIndex: persistent ? 1 : void 0
|
|
427
|
+
transformOrigin: bgTransform.transformOrigin
|
|
375
428
|
};
|
|
376
|
-
const preparedPosition = preparedPoster ? resolveMediaPosition(preparedPoster.mediaPosition) : resolvedPosition;
|
|
377
|
-
const preparedTransform = getBackgroundTransform(preparedPoster?.backgroundEffect, 0, 0);
|
|
378
|
-
const posterPlanes = [
|
|
379
|
-
...persistent && mediaPoster ? [{
|
|
380
|
-
presentationKey: videoPresentationKey,
|
|
381
|
-
mediaPoster,
|
|
382
|
-
mediaPosition: resolvedPosition,
|
|
383
|
-
transform: bgTransform.transform,
|
|
384
|
-
transformOrigin: bgTransform.transformOrigin,
|
|
385
|
-
opacity: 1,
|
|
386
|
-
zIndex: 0,
|
|
387
|
-
role: "current"
|
|
388
|
-
}] : [],
|
|
389
|
-
...preparedPoster && preparedPoster.presentationKey !== videoPresentationKey ? [{
|
|
390
|
-
presentationKey: preparedPoster.presentationKey,
|
|
391
|
-
mediaPoster: preparedPoster.mediaPoster,
|
|
392
|
-
mediaPosition: preparedPosition,
|
|
393
|
-
transform: preparedTransform.transform,
|
|
394
|
-
transformOrigin: preparedTransform.transformOrigin,
|
|
395
|
-
opacity: preparedPoster.opacity ?? 0,
|
|
396
|
-
zIndex: 2,
|
|
397
|
-
role: "prepared"
|
|
398
|
-
}] : []
|
|
399
|
-
];
|
|
400
429
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
401
|
-
posterPlanes.map((posterPlane) => /* @__PURE__ */ jsx(
|
|
402
|
-
"img",
|
|
403
|
-
{
|
|
404
|
-
src: posterPlane.mediaPoster,
|
|
405
|
-
alt: "",
|
|
406
|
-
"aria-hidden": "true",
|
|
407
|
-
draggable: false,
|
|
408
|
-
"data-video-poster-plane": posterPlane.role,
|
|
409
|
-
"data-video-poster-visible": posterPlane.opacity > 0 ? "true" : "false",
|
|
410
|
-
style: {
|
|
411
|
-
position: "absolute",
|
|
412
|
-
inset: 0,
|
|
413
|
-
width: "100%",
|
|
414
|
-
height: "100%",
|
|
415
|
-
objectFit: "cover",
|
|
416
|
-
objectPosition: posterPlane.mediaPosition,
|
|
417
|
-
transform: posterPlane.transform,
|
|
418
|
-
transformOrigin: posterPlane.transformOrigin,
|
|
419
|
-
zIndex: posterPlane.zIndex,
|
|
420
|
-
opacity: posterPlane.opacity,
|
|
421
|
-
pointerEvents: "none"
|
|
422
|
-
}
|
|
423
|
-
},
|
|
424
|
-
posterPlane.presentationKey
|
|
425
|
-
)),
|
|
426
430
|
exhaustedKey === videoPresentationKey && /* @__PURE__ */ jsx(
|
|
427
431
|
"div",
|
|
428
432
|
{
|
|
@@ -437,42 +441,218 @@ var SceneVideoBackdrop = ({
|
|
|
437
441
|
{
|
|
438
442
|
ref: videoRef,
|
|
439
443
|
src: mediaUrl,
|
|
440
|
-
poster:
|
|
444
|
+
poster: decodedVideoUrl !== mediaUrl ? mediaPoster || void 0 : void 0,
|
|
441
445
|
muted: resolvedMuted,
|
|
442
446
|
loop: false,
|
|
443
447
|
playsInline: true,
|
|
444
448
|
preload: "auto",
|
|
445
449
|
onLoadedMetadata: (event) => fitDuration(event.currentTarget),
|
|
446
450
|
onEnded: (event) => continueMotion(event.currentTarget),
|
|
451
|
+
onPlay: enforceRequestedPause,
|
|
452
|
+
onPlaying: enforceRequestedPause,
|
|
447
453
|
onWaiting: () => {
|
|
448
454
|
if (isPlaying) setWaitingKey(videoPresentationKey);
|
|
449
455
|
},
|
|
450
|
-
onLoadedData: (event) => {
|
|
451
|
-
const video = event.currentTarget;
|
|
452
|
-
const markPresented = () => {
|
|
453
|
-
if (!video.isConnected) return;
|
|
454
|
-
onReady?.();
|
|
455
|
-
if (!retainPoster) setDecodedVideoUrl(mediaUrl);
|
|
456
|
-
};
|
|
457
|
-
if (video.requestVideoFrameCallback) {
|
|
458
|
-
video.requestVideoFrameCallback(markPresented);
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
markPresented();
|
|
462
|
-
},
|
|
463
456
|
onError,
|
|
464
457
|
"data-media-position": mediaPosition,
|
|
465
|
-
"data-video-backdrop":
|
|
458
|
+
"data-video-backdrop": "scene",
|
|
466
459
|
style: { ...mediaStyle, visibility: exhaustedKey === videoPresentationKey ? "hidden" : void 0 }
|
|
467
460
|
}
|
|
468
461
|
)
|
|
469
462
|
] });
|
|
470
463
|
};
|
|
471
464
|
|
|
465
|
+
// src/visual-system/scene-templates/scene-background.tsx
|
|
466
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
467
|
+
function resolveMediaTreatment(value) {
|
|
468
|
+
return value === "none" || value === "subtle" || value === "text-safe" ? value : "cinematic";
|
|
469
|
+
}
|
|
470
|
+
var SCRIM_STOP_COUNT = 7;
|
|
471
|
+
function smoothstep(t) {
|
|
472
|
+
return t * t * (3 - 2 * t);
|
|
473
|
+
}
|
|
474
|
+
function easedStops(peakAlpha, start, end, direction) {
|
|
475
|
+
const alphaAt = (t) => {
|
|
476
|
+
const eased = direction === "fade-out" ? 1 - smoothstep(t) : smoothstep(t);
|
|
477
|
+
return `rgba(0,0,0,${Number((peakAlpha * eased).toFixed(3))})`;
|
|
478
|
+
};
|
|
479
|
+
const stops = [];
|
|
480
|
+
if (start > 0) stops.push(`${alphaAt(0)} 0%`);
|
|
481
|
+
for (let i = 0; i < SCRIM_STOP_COUNT; i += 1) {
|
|
482
|
+
const t = i / (SCRIM_STOP_COUNT - 1);
|
|
483
|
+
const position = Number((start + (end - start) * t).toFixed(2));
|
|
484
|
+
stops.push(`${alphaAt(t)} ${position}%`);
|
|
485
|
+
}
|
|
486
|
+
if (end < 100) stops.push(`${alphaAt(1)} 100%`);
|
|
487
|
+
return stops.join(", ");
|
|
488
|
+
}
|
|
489
|
+
function getMediaTreatmentLayers(value, anchor = "full") {
|
|
490
|
+
const treatment = resolveMediaTreatment(value);
|
|
491
|
+
if (treatment === "none") return [];
|
|
492
|
+
const vignette = {
|
|
493
|
+
id: "vignette",
|
|
494
|
+
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")})`
|
|
495
|
+
};
|
|
496
|
+
if (treatment === "subtle") return [vignette];
|
|
497
|
+
const textSafe = treatment === "text-safe";
|
|
498
|
+
const layers = [vignette];
|
|
499
|
+
if (anchor !== "bottom") {
|
|
500
|
+
layers.push({
|
|
501
|
+
id: "center-scrim",
|
|
502
|
+
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")})`
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
if (anchor !== "center") {
|
|
506
|
+
layers.push({
|
|
507
|
+
id: "bottom-scrim",
|
|
508
|
+
background: `linear-gradient(to top, ${easedStops(textSafe ? 0.64 : 0.5, 8, 100, "fade-out")})`,
|
|
509
|
+
style: { top: "55%" }
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
return layers;
|
|
513
|
+
}
|
|
514
|
+
function initialMediaPaint(wantsMedia, resolved, mediaUrl, mediaPoster) {
|
|
515
|
+
if (typeof window === "undefined") return "ready";
|
|
516
|
+
if (!wantsMedia) return "ready";
|
|
517
|
+
if (resolved === "video") return mediaPoster ? "ready" : "pending";
|
|
518
|
+
if (typeof Image === "undefined") return "ready";
|
|
519
|
+
const cached = new Image();
|
|
520
|
+
cached.src = mediaUrl;
|
|
521
|
+
return cached.complete && cached.naturalWidth > 0 ? "ready" : "pending";
|
|
522
|
+
}
|
|
523
|
+
function getMediaBackgroundProps(variables) {
|
|
524
|
+
return {
|
|
525
|
+
mediaUrl: String(variables.mediaUrl || ""),
|
|
526
|
+
mediaType: String(variables.mediaType || "auto"),
|
|
527
|
+
mediaPoster: String(variables.mediaPoster || ""),
|
|
528
|
+
mediaPosition: String(variables.mediaPosition || "center"),
|
|
529
|
+
mediaTreatment: String(variables.mediaTreatment || "cinematic")
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
var SceneBackground = ({
|
|
533
|
+
style,
|
|
534
|
+
progress,
|
|
535
|
+
sceneDuration,
|
|
536
|
+
width: _width,
|
|
537
|
+
// accepted for symmetry; not currently used in render
|
|
538
|
+
height: _height,
|
|
539
|
+
mediaUrl = "",
|
|
540
|
+
mediaType = "auto",
|
|
541
|
+
mediaPoster,
|
|
542
|
+
mediaPosition = "center",
|
|
543
|
+
mediaTreatment = "cinematic",
|
|
544
|
+
textAnchor = "full",
|
|
545
|
+
backgroundEffect,
|
|
546
|
+
seed,
|
|
547
|
+
isPlaying = true,
|
|
548
|
+
beatIntensity = 0
|
|
549
|
+
}) => {
|
|
550
|
+
void _width;
|
|
551
|
+
void _height;
|
|
552
|
+
const resolved = resolveMediaType(mediaType, mediaUrl);
|
|
553
|
+
const wantsMedia = resolved !== "gradient" && !!mediaUrl;
|
|
554
|
+
const externalVideoBackdrop = useExternalVideoBackdrop();
|
|
555
|
+
const hasExternalVideoBackdrop = externalVideoBackdrop !== false && resolved === "video";
|
|
556
|
+
const externalVideoFailed = externalVideoBackdrop === "fallback" && resolved === "video";
|
|
557
|
+
const externalVideoReady = externalVideoBackdrop === "ready" && resolved === "video";
|
|
558
|
+
const [mediaPaint, setMediaPaint] = useState2(
|
|
559
|
+
() => initialMediaPaint(wantsMedia, resolved, mediaUrl, mediaPoster)
|
|
560
|
+
);
|
|
561
|
+
useEffect2(() => {
|
|
562
|
+
setMediaPaint(initialMediaPaint(wantsMedia, resolved, mediaUrl, mediaPoster));
|
|
563
|
+
if (!wantsMedia || resolved !== "photo") return;
|
|
564
|
+
if (typeof Image === "undefined") return;
|
|
565
|
+
let cancelled = false;
|
|
566
|
+
const probe = new Image();
|
|
567
|
+
probe.onload = () => {
|
|
568
|
+
if (!cancelled) setMediaPaint("ready");
|
|
569
|
+
};
|
|
570
|
+
probe.onerror = () => {
|
|
571
|
+
if (!cancelled) setMediaPaint("failed");
|
|
572
|
+
};
|
|
573
|
+
probe.src = mediaUrl;
|
|
574
|
+
if (probe.complete) setMediaPaint(probe.naturalWidth > 0 ? "ready" : "failed");
|
|
575
|
+
return () => {
|
|
576
|
+
cancelled = true;
|
|
577
|
+
probe.onload = null;
|
|
578
|
+
probe.onerror = null;
|
|
579
|
+
};
|
|
580
|
+
}, [mediaUrl, mediaPoster, resolved, wantsMedia]);
|
|
581
|
+
const showMedia = wantsMedia && mediaPaint !== "failed";
|
|
582
|
+
const showTreatment = wantsMedia && mediaPaint === "ready";
|
|
583
|
+
const resolvedPosition = resolveMediaPosition(mediaPosition);
|
|
584
|
+
const resolvedTreatment = resolveMediaTreatment(mediaTreatment);
|
|
585
|
+
const treatmentLayers = getMediaTreatmentLayers(resolvedTreatment, textAnchor);
|
|
586
|
+
const gradSeed = typeof seed === "number" ? seed : typeof seed === "string" ? seed.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0) : 0;
|
|
587
|
+
const bgTransform = getBackgroundTransform(
|
|
588
|
+
backgroundEffect,
|
|
589
|
+
progress,
|
|
590
|
+
beatIntensity
|
|
591
|
+
);
|
|
592
|
+
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
593
|
+
(!hasExternalVideoBackdrop || externalVideoFailed) && /* @__PURE__ */ jsx2(
|
|
594
|
+
BrandGradientOverlay,
|
|
595
|
+
{
|
|
596
|
+
style,
|
|
597
|
+
progress,
|
|
598
|
+
sceneDuration,
|
|
599
|
+
seed: gradSeed
|
|
600
|
+
}
|
|
601
|
+
),
|
|
602
|
+
showMedia && !hasExternalVideoBackdrop && (resolved === "video" ? /* @__PURE__ */ jsx2(
|
|
603
|
+
SceneVideoBackdrop,
|
|
604
|
+
{
|
|
605
|
+
mediaUrl,
|
|
606
|
+
mediaPoster,
|
|
607
|
+
mediaPosition,
|
|
608
|
+
backgroundEffect,
|
|
609
|
+
progress,
|
|
610
|
+
sceneDuration,
|
|
611
|
+
beatIntensity,
|
|
612
|
+
isPlaying,
|
|
613
|
+
onReady: () => setMediaPaint("ready"),
|
|
614
|
+
onError: () => setMediaPaint("failed")
|
|
615
|
+
}
|
|
616
|
+
) : /* @__PURE__ */ jsx2(
|
|
617
|
+
"img",
|
|
618
|
+
{
|
|
619
|
+
src: mediaUrl,
|
|
620
|
+
alt: "",
|
|
621
|
+
"aria-hidden": "true",
|
|
622
|
+
draggable: false,
|
|
623
|
+
"data-media-position": mediaPosition,
|
|
624
|
+
style: {
|
|
625
|
+
position: "absolute",
|
|
626
|
+
inset: 0,
|
|
627
|
+
transform: bgTransform.transform,
|
|
628
|
+
transformOrigin: bgTransform.transformOrigin,
|
|
629
|
+
width: "100%",
|
|
630
|
+
height: "100%",
|
|
631
|
+
objectFit: "cover",
|
|
632
|
+
objectPosition: resolvedPosition
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
)),
|
|
636
|
+
(hasExternalVideoBackdrop ? externalVideoReady : showTreatment) && !externalVideoFailed && treatmentLayers.map((layer) => /* @__PURE__ */ jsx2(
|
|
637
|
+
"div",
|
|
638
|
+
{
|
|
639
|
+
"data-media-treatment": resolvedTreatment,
|
|
640
|
+
"data-media-overlay": layer.id,
|
|
641
|
+
style: {
|
|
642
|
+
position: "absolute",
|
|
643
|
+
inset: 0,
|
|
644
|
+
background: layer.background,
|
|
645
|
+
pointerEvents: "none",
|
|
646
|
+
...layer.style
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
layer.id
|
|
650
|
+
))
|
|
651
|
+
] });
|
|
652
|
+
};
|
|
653
|
+
|
|
472
654
|
export {
|
|
473
|
-
BrandGradientOverlay,
|
|
474
655
|
spring,
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
SceneVideoBackdrop
|
|
656
|
+
getMediaBackgroundProps,
|
|
657
|
+
SceneBackground
|
|
478
658
|
};
|