@vanillaskyai/video 0.10.2 → 0.10.4
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 +17 -0
- package/PUBLIC-API.md +43 -31
- package/README.md +18 -9
- package/dist/check-runtime.js +2 -2
- package/dist/{chunk-KHLO5OHT.js → chunk-G5TYLTL5.js} +1 -1
- package/dist/{chunk-77VY4O7A.js → chunk-O6FBIB4L.js} +58 -10
- package/dist/{chunk-TVIU23OM.js → chunk-OOBT4X46.js} +13 -3
- package/dist/{chunk-5C6HZNHY.js → chunk-QZAZT44G.js} +3 -2
- package/dist/{chunk-6KZGF63O.js → chunk-WZFLEPLM.js} +58 -35
- package/dist/{chunk-VQH3JTQC.js → chunk-Z2ZI5G7O.js} +1 -1
- package/dist/{cinema-media-APDJS7SC.js → cinema-media-3SIGVVCY.js} +4 -4
- package/dist/{comparison-ZVE2DE7U.js → comparison-VDGRWPB5.js} +4 -4
- package/dist/{editorial-timeline-YIXXVJUV.js → editorial-timeline-QJU4JJWB.js} +4 -4
- package/dist/{key-figure-I2C37FWV.js → key-figure-YUAQW6OE.js} +4 -4
- package/dist/{mobile-message-JSGBU6EX.js → mobile-message-ZMMTUDUQ.js} +4 -4
- package/dist/{quote-XH54G3FS.js → quote-BB6UJT6L.js} +4 -4
- package/dist/react.d.ts +3 -1
- package/dist/react.js +94 -84
- package/dist/{scene-video-backdrop-PCTARAAJ.js → scene-video-backdrop-4EZVVQY7.js} +2 -2
- package/dist/server.d.ts +15 -3
- package/dist/server.js +416 -275
- package/dist/{types-CdBmNTGD.d.ts → types-BqB8zC9u.d.ts} +1 -1
- package/docs/agent-integration.md +5 -4
- package/docs/concepts.md +2 -2
- package/docs/development.md +23 -0
- package/docs/getting-started.md +7 -5
- package/docs/media-and-audio.md +37 -18
- package/docs/performance.md +21 -10
- package/docs/production.md +11 -10
- package/docs/prompt-and-input.md +24 -15
- package/docs/provider-integration.md +11 -13
- package/docs/reference/protocol.md +7 -1
- package/docs/reference/provider-adapters.md +7 -4
- package/docs/testing.md +7 -2
- package/package.json +5 -2
- package/registry/items/backgrounds.json +2 -2
- package/registry/items/cinemaMedia.json +1 -1
- package/registry/items/comparison.json +1 -1
- package/registry/items/editorialTimeline.json +1 -1
- package/registry/items/keyFigure.json +1 -1
- package/registry/items/mobileMessage.json +1 -1
- package/registry/items/quote.json +1 -1
- package/starters/video-chat/README.md +25 -26
- package/starters/video-chat/package.json +1 -1
- package/starters/video-chat/providers/video.ts +10 -6
- package/starters/video-chat/server.ts +1 -1
- package/starters/video-chat/stock.ts +59 -31
- package/styles/video-chat.css +6 -61
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ VanillaSky follows semantic versioning. This changelog begins with the 0.1 beta.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.10.4
|
|
8
|
+
|
|
9
|
+
- Show the canonical chapter immediately after submitting a prompt; remove the separate gradient loading placeholder and opening stock requests.
|
|
10
|
+
- Separate AI video and Pexels in Settings. AI mode uses generated footage only; Pexels performs full bounded video search without AI-video calls. Missing or late footage becomes an authored chapter with complete narration.
|
|
11
|
+
- Prepare speech while footage generates, reuse prepared lines, and limit concurrent speech requests so an answer does not flood host admission. Describe HTTP throttling as a retryable request limit.
|
|
12
|
+
- Keep silent footage looping for the finite narrated scene, and recover missing URLs or rejected playback to a chapter rather than an unavailable screen. Preserve user pause, cancellation and replay.
|
|
13
|
+
- Allow hosts to configure the actual generated clip duration (default five seconds) and apply scene-position deadlines to media preparation.
|
|
14
|
+
- Add optional host-only phase diagnostics with bounded timing and fixed recovery reasons, without retaining prompt or provider content.
|
|
15
|
+
|
|
16
|
+
## 0.10.3
|
|
17
|
+
|
|
18
|
+
- Remove the implicit documentary visual style from the chat interface; preserve explicitly supplied application styles and let the answer direct its own look.
|
|
19
|
+
- Default chat now plans an answer brief and narrated shots, with an immediate introduction followed by AI-first footage and relevant stock fallback. Creative direction adapts to the request; the model no longer chooses body templates, media sources, scene IDs, or completion commands. Explicit custom template registries retain their existing composition contract.
|
|
20
|
+
- Reserve the answer's ending before streaming body shots, retain valid narration when visuals are unavailable, and skip exact repeated opening or ending lines before requesting media.
|
|
21
|
+
- Fit short clips to measured narration within a bounded playback rate. If moving coverage runs out, use bounded recovery and an explicit waiting state instead of holding a finished frame while speech continues. Preserve deliberate pauses and audio activation across scenes.
|
|
22
|
+
- The optional starter video adapter uses Fal Turbo with five-second clips and intent-specific shot direction; providers, credentials, admission and spending limits remain application-owned.
|
|
23
|
+
|
|
7
24
|
## 0.10.2
|
|
8
25
|
|
|
9
26
|
- Keep generated narration playing in Safari after a delayed opening-to-body handoff by reusing the permitted audio element; isolate late playback failures from subsequent lines.
|
package/PUBLIC-API.md
CHANGED
|
@@ -116,6 +116,19 @@ provider-neutral text-delta escape hatch.
|
|
|
116
116
|
|
|
117
117
|
### Handler contract
|
|
118
118
|
|
|
119
|
+
The chat contract includes two footage modes: `"cinematic"` (labelled AI video)
|
|
120
|
+
and `"pexels"`. AI mode does not fall back to stock; either mode recovers missing
|
|
121
|
+
footage to an authored chapter. Existing `"cinematic"` requests remain valid.
|
|
122
|
+
`generatedClipDurationSec?: number` on the handler defaults to 5 and accepts 2–20;
|
|
123
|
+
it must match the host adapter's requested duration. `VideoChatTurn.mode` is
|
|
124
|
+
optional so older in-memory saved turns remain readable.
|
|
125
|
+
|
|
126
|
+
A declared `data.video-chat-preparation` extension carries
|
|
127
|
+
`{ sceneId: string, narration: string }` before media resolves. It permits bounded
|
|
128
|
+
speech preparation, not playback of an unvalidated scene. The client limits
|
|
129
|
+
speech preparation to two concurrent jobs and reuses it when `scene.add` arrives.
|
|
130
|
+
The six public exports, persisted video schema and protocol version are unchanged.
|
|
131
|
+
|
|
119
132
|
`createVideoChatHandler` is the opinionated general-purpose video-chat route.
|
|
120
133
|
Mount it once and use its bounded `action` query parameter for capabilities,
|
|
121
134
|
responses, opening media, narration, suggestions, speech,
|
|
@@ -124,33 +137,28 @@ application-configured visual-mode attempt limits, capability fallbacks, and aux
|
|
|
124
137
|
shapes. The application supplies provider-neutral `streamText`, `generateText`,
|
|
125
138
|
`generateSpeech`, `transcribe`, `searchMedia`, and `generateVideo` callbacks.
|
|
126
139
|
Only the two text callbacks are required. Missing optional callbacks remove
|
|
127
|
-
their capability; templates and browser speech remain available.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
their capability; templates and browser speech remain available. Failed or late
|
|
141
|
+
footage becomes an authored chapter with complete narration. The default handler
|
|
142
|
+
skips invalid planner parts and preserves playable scenes on an interrupted plan,
|
|
143
|
+
emitting non-fatal warnings. Explicit `invalidPartBehavior: "fail"` retains strict
|
|
144
|
+
generation semantics.
|
|
132
145
|
|
|
133
146
|
- A response accepts `prompt`, `mode`, `orientation`, optional bounded
|
|
134
147
|
`conversation`, `opening`, and `style`. `opening` is an optional
|
|
135
148
|
prewritten hook from a selected suggestion. The response returns protocol
|
|
136
|
-
`0.6` SSE and negotiates `data.video-chat-opening
|
|
137
|
-
|
|
138
|
-
- The planner produces
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
the limit. With zero, full mode uses stock only. The planner receives this
|
|
150
|
-
budget, and the server enforces it independently. If providers fail, an
|
|
151
|
-
already completed video with the identical search query and visual look may
|
|
152
|
-
be reused once in the same response before a readable template fallback. Without `generateVideo`, only `templates` is exposed
|
|
153
|
-
and forged generated-mode requests degrade to it.
|
|
149
|
+
`0.6` SSE and negotiates `data.video-chat-opening` and
|
|
150
|
+
`data.video-chat-preparation` before the associated scene is ready.
|
|
151
|
+
- The planner produces the opening and shots in one model stream, reserving the
|
|
152
|
+
intended ending. Each shot supplies an authored recovery title, complete
|
|
153
|
+
narration and visual direction. The default UI uses a chapter opening; the
|
|
154
|
+
separate opening-media action remains available to custom consumers.
|
|
155
|
+
- `cinematic` uses the application-owned `maxGeneratedVideos` limit (default
|
|
156
|
+
five, nonnegative safe integer). It counts generation attempts, including
|
|
157
|
+
failures. Zero skips generation and keeps authored chapters. Expired scene
|
|
158
|
+
deadlines also skip generation without consuming an attempt. This limit is
|
|
159
|
+
neither a currency nor duration cap. Pexels mode uses only `searchMedia` and
|
|
160
|
+
never consumes this allowance. Capabilities expose Pexels when stock search
|
|
161
|
+
is configured; AI mode remains usable with chapter recovery without video.
|
|
154
162
|
- The response planner writes narration on each scene. The narration action is
|
|
155
163
|
retained as a compatibility fallback for missing lines, not used in the
|
|
156
164
|
normal path.
|
|
@@ -251,9 +259,9 @@ playable output; an error is shown only when no playable response remains.
|
|
|
251
259
|
`ask(prompt, { opening, openingMedia })` lets a custom interface start a
|
|
252
260
|
prewritten suggestion hook immediately and reuse its image or video without
|
|
253
261
|
another model or media lookup. The hook otherwise reads the opening from the
|
|
254
|
-
response stream
|
|
255
|
-
|
|
256
|
-
|
|
262
|
+
response stream and starts the planned timeline only when opening speech and
|
|
263
|
+
the contiguous prepared scenes are ready. It no longer requests opening stock
|
|
264
|
+
automatically. `UseVideoChatOptions.onFirstFrame` receives a
|
|
257
265
|
`VideoChatFirstFrameMetric` once after its first active scene commits and reaches
|
|
258
266
|
an animation-frame opportunity. This measures presentation readiness, not physical
|
|
259
267
|
screen paint or media decoding. Each `VideoChatTurn` exposes `openingMedia` and `completed`, so
|
|
@@ -428,6 +436,7 @@ from `/server` without crossing a React type boundary.
|
|
|
428
436
|
and nonnegative `elapsedMs` since the prompt was submitted:
|
|
429
437
|
|
|
430
438
|
- `first-frame`: first committed active scene at an animation-frame opportunity;
|
|
439
|
+
- `first-media-frame`: first decoded frame reported by the mounted footage surface;
|
|
431
440
|
- `first-speech`: actual speech playback onset, with `source` equal to `browser`,
|
|
432
441
|
`generated`, or `custom`;
|
|
433
442
|
- `stall`: a finished wait for the next prepared scene, with `durationMs` and
|
|
@@ -455,8 +464,11 @@ resolver deadline (integer 1–120000 milliseconds, default 15000). The host sti
|
|
|
455
464
|
provider cancellation, authorization and spend limits; a timeout does not prove the
|
|
456
465
|
provider avoided a charge. `maxGeneratedVideos` continues to count attempted clips.
|
|
457
466
|
|
|
458
|
-
`VideoChatProps.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
467
|
+
`VideoChatProps.showRecoveryNotice` opts into a dismissible, fixed SDK
|
|
468
|
+
media-recovery notice; it never displays arbitrary provider messages.
|
|
469
|
+
|
|
470
|
+
`VideoChatHandlerOptions.onDiagnostic` is an optional host-only observer for
|
|
471
|
+
request, authoring and media phase timings with fixed recovery reason codes.
|
|
472
|
+
It does not retain prompt, narration, query, asset URL or provider error text,
|
|
473
|
+
and cannot affect response delivery. No new entry points or named exports are
|
|
474
|
+
introduced; its additive signature is checked in the packed consumer contract.
|
package/README.md
CHANGED
|
@@ -25,9 +25,9 @@ npx vanillasky doctor
|
|
|
25
25
|
npm run dev
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Open the reported localhost URL. One text key gives you the
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
Open the reported localhost URL. One text key gives you the chat, an immediate introduction, and browser voice.
|
|
29
|
+
Add the video adapter for AI footage, or a Pexels key for the lower-cost stock mode.
|
|
30
|
+
Without footage, authored chapter scenes carry the narration and subtitles. If installation is interrupted, rerun the init command.
|
|
31
31
|
|
|
32
32
|
Optional upgrades keep the same client:
|
|
33
33
|
|
|
@@ -35,7 +35,7 @@ Optional upgrades keep the same client:
|
|
|
35
35
|
| --- | --- | --- |
|
|
36
36
|
| Generated speech | `npx vanillasky providers add speech` | `XAI_API_KEY` |
|
|
37
37
|
| Generated video and voice transcription | `npx vanillasky providers add video` | `FAL_KEY` |
|
|
38
|
-
|
|
|
38
|
+
| Pexels video search | No install needed | `PEXELS_API_KEY` |
|
|
39
39
|
|
|
40
40
|
Run the selected command, add its key to `.env.local`, and restart the server.
|
|
41
41
|
Only selected provider packages are installed. `npx vanillasky doctor` reports
|
|
@@ -80,17 +80,25 @@ conversation and playback lifecycle. Edit the generated server when you want a
|
|
|
80
80
|
different provider. The [provider guide](docs/provider-integration.md) explains
|
|
81
81
|
both boundaries.
|
|
82
82
|
|
|
83
|
-
##
|
|
83
|
+
## An immediate intro, then moving footage
|
|
84
84
|
|
|
85
|
-
The
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
The default chat uses a template introduction while its first shot prepares,
|
|
86
|
+
then footage with narration and subtitles. Choose AI video or Pexels in Settings.
|
|
87
|
+
AI mode never substitutes stock, and Pexels mode never calls the AI-video provider.
|
|
88
|
+
Missing or late footage becomes a useful chapter scene. The planner adapts its
|
|
89
|
+
visible actions and spoken beats to explanations, stories, comedy, imagination,
|
|
90
|
+
and practical requests. It does not choose body templates.
|
|
91
|
+
|
|
92
|
+
Speech and footage prepare together. Silent clips loop through the remaining
|
|
93
|
+
narration when necessary, and every answer preserves its intended ending.
|
|
94
|
+
|
|
95
|
+
Packaged templates remain available for custom compositions and source ownership.
|
|
88
96
|
|
|
89
97
|
Copy template source only when you want to own and edit it:
|
|
90
98
|
|
|
91
99
|
```bash
|
|
92
100
|
npm install --save-dev tsx
|
|
93
|
-
npx vanillasky templates add
|
|
101
|
+
npx vanillasky templates add chapterTitle
|
|
94
102
|
```
|
|
95
103
|
|
|
96
104
|
That compiler is needed only for source-owned templates. See
|
|
@@ -113,6 +121,7 @@ MP4/WebM export remains application-owned.
|
|
|
113
121
|
| Add media or voice | [Media and voice](docs/media-and-audio.md) |
|
|
114
122
|
| Persist and replay results | [Performance measurements](docs/performance.md) · [Persistence and replay](docs/persistence.md) |
|
|
115
123
|
| Test routes and streams | [Test integrations](docs/testing.md) |
|
|
124
|
+
| Iterate on SDK chat | [Development](docs/development.md): `dev:chat`, `check:chat`, `verify:release` |
|
|
116
125
|
| Deploy securely | [Production](docs/production.md) · [Security](docs/security.md) |
|
|
117
126
|
| Inspect the API contract | [Public API](PUBLIC-API.md) · [Protocol](docs/reference/protocol.md) |
|
|
118
127
|
|
package/dist/check-runtime.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VideoFrame
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-WZFLEPLM.js";
|
|
4
4
|
import "./chunk-224QNWRA.js";
|
|
5
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-OOBT4X46.js";
|
|
6
6
|
import "./chunk-SPVTJH3F.js";
|
|
7
7
|
import "./chunk-R3XAOMKP.js";
|
|
8
8
|
import "./chunk-73NTSFFI.js";
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
darken,
|
|
3
3
|
resolveTokens,
|
|
4
|
-
useMediaAudio
|
|
5
|
-
|
|
4
|
+
useMediaAudio,
|
|
5
|
+
useMediaFailure,
|
|
6
|
+
useNarrationPreroll
|
|
7
|
+
} from "./chunk-OOBT4X46.js";
|
|
6
8
|
|
|
7
9
|
// src/visual-system/scene-templates/scene-video-backdrop.tsx
|
|
8
|
-
import { useEffect, useRef, useState } from "react";
|
|
10
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
9
11
|
|
|
10
12
|
// src/visual-system/scene-templates/color-utils.ts
|
|
11
13
|
import React from "react";
|
|
@@ -228,6 +230,8 @@ var SceneVideoBackdrop = ({
|
|
|
228
230
|
mediaPosition = "center",
|
|
229
231
|
backgroundEffect,
|
|
230
232
|
progress,
|
|
233
|
+
sceneDuration,
|
|
234
|
+
preparingNarration = false,
|
|
231
235
|
beatIntensity = 0,
|
|
232
236
|
isPlaying,
|
|
233
237
|
muted,
|
|
@@ -240,15 +244,44 @@ var SceneVideoBackdrop = ({
|
|
|
240
244
|
onError
|
|
241
245
|
}) => {
|
|
242
246
|
const inheritedAudio = useMediaAudio();
|
|
247
|
+
const reportMediaFailure = useMediaFailure();
|
|
248
|
+
const inheritedPreroll = useNarrationPreroll();
|
|
249
|
+
const rewindPreroll = preparingNarration || inheritedPreroll;
|
|
243
250
|
const resolvedMuted = muted ?? inheritedAudio.muted;
|
|
244
251
|
const resolvedVolume = volume ?? inheritedAudio.volume;
|
|
245
252
|
const resolvedPosition = resolveMediaPosition(mediaPosition);
|
|
246
253
|
const bgTransform = getBackgroundTransform(backgroundEffect, progress, beatIntensity);
|
|
247
254
|
const [decodedVideoUrl, setDecodedVideoUrl] = useState();
|
|
255
|
+
const [waitingKey, setWaitingKey] = useState();
|
|
256
|
+
const [exhaustedKey, setExhaustedKey] = useState();
|
|
248
257
|
const videoRef = useRef(null);
|
|
249
258
|
const startedVideoUrl = useRef(void 0);
|
|
250
259
|
const startedPlaybackId = useRef(void 0);
|
|
251
260
|
const videoPresentationKey = `${playbackId}\0${mediaUrl}`;
|
|
261
|
+
const presentationRef = useRef({ key: videoPresentationKey, playing: isPlaying });
|
|
262
|
+
presentationRef.current = { key: videoPresentationKey, playing: isPlaying };
|
|
263
|
+
const unavailable = () => {
|
|
264
|
+
if (presentationRef.current.key === videoPresentationKey && presentationRef.current.playing) {
|
|
265
|
+
setExhaustedKey(videoPresentationKey);
|
|
266
|
+
onError?.();
|
|
267
|
+
reportMediaFailure?.();
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
const fitDuration = useCallback((video) => {
|
|
271
|
+
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;
|
|
272
|
+
}, [resolvedMuted, sceneDuration]);
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
if (videoRef.current) fitDuration(videoRef.current);
|
|
275
|
+
}, [fitDuration]);
|
|
276
|
+
const continueMotion = (video) => {
|
|
277
|
+
if (!isPlaying) return;
|
|
278
|
+
if (!resolvedMuted) {
|
|
279
|
+
unavailable();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
video.currentTime = 0;
|
|
283
|
+
void video.play().catch(unavailable);
|
|
284
|
+
};
|
|
252
285
|
useEffect(() => {
|
|
253
286
|
const video = videoRef.current;
|
|
254
287
|
if (!video) return;
|
|
@@ -273,21 +306,21 @@ var SceneVideoBackdrop = ({
|
|
|
273
306
|
if (!video) return;
|
|
274
307
|
if (!isPlaying) {
|
|
275
308
|
video.pause();
|
|
309
|
+
if (rewindPreroll && video.currentTime > 0) video.currentTime = 0;
|
|
276
310
|
return;
|
|
277
311
|
}
|
|
278
312
|
if (startedPlaybackId.current === playbackId) {
|
|
279
|
-
if (
|
|
280
|
-
|
|
313
|
+
if (video.ended) continueMotion(video);
|
|
314
|
+
else void video.play().catch(unavailable);
|
|
281
315
|
return;
|
|
282
316
|
}
|
|
283
317
|
const changingSource = startedVideoUrl.current !== void 0 && startedVideoUrl.current !== mediaUrl;
|
|
284
|
-
video
|
|
318
|
+
fitDuration(video);
|
|
285
319
|
if (!changingSource && video.currentTime > 0) video.currentTime = 0;
|
|
286
|
-
video.play().catch(
|
|
287
|
-
});
|
|
320
|
+
video.play().catch(unavailable);
|
|
288
321
|
startedVideoUrl.current = mediaUrl;
|
|
289
322
|
startedPlaybackId.current = playbackId;
|
|
290
|
-
}, [isPlaying, mediaUrl, playbackId]);
|
|
323
|
+
}, [isPlaying, mediaUrl, playbackId, rewindPreroll]);
|
|
291
324
|
const mediaStyle = {
|
|
292
325
|
position: "absolute",
|
|
293
326
|
inset: 0,
|
|
@@ -349,6 +382,15 @@ var SceneVideoBackdrop = ({
|
|
|
349
382
|
},
|
|
350
383
|
posterPlane.presentationKey
|
|
351
384
|
)),
|
|
385
|
+
(exhaustedKey === videoPresentationKey || waitingKey === videoPresentationKey) && /* @__PURE__ */ jsx(
|
|
386
|
+
"div",
|
|
387
|
+
{
|
|
388
|
+
role: "status",
|
|
389
|
+
"data-media-continuity": exhaustedKey === videoPresentationKey ? "exhausted" : "waiting",
|
|
390
|
+
style: { position: "absolute", inset: 0, zIndex: 3, background: "#000", color: "#bbb", display: "grid", placeContent: "center", font: "14px system-ui" },
|
|
391
|
+
children: exhaustedKey === videoPresentationKey ? "Visual unavailable" : "Loading visual"
|
|
392
|
+
}
|
|
393
|
+
),
|
|
352
394
|
/* @__PURE__ */ jsx(
|
|
353
395
|
"video",
|
|
354
396
|
{
|
|
@@ -359,6 +401,12 @@ var SceneVideoBackdrop = ({
|
|
|
359
401
|
loop: false,
|
|
360
402
|
playsInline: true,
|
|
361
403
|
preload: "auto",
|
|
404
|
+
onLoadedMetadata: (event) => fitDuration(event.currentTarget),
|
|
405
|
+
onEnded: (event) => continueMotion(event.currentTarget),
|
|
406
|
+
onWaiting: () => {
|
|
407
|
+
if (isPlaying) setWaitingKey(videoPresentationKey);
|
|
408
|
+
},
|
|
409
|
+
onPlaying: () => setWaitingKey(void 0),
|
|
362
410
|
onLoadedData: (event) => {
|
|
363
411
|
const video = event.currentTarget;
|
|
364
412
|
const markPresented = () => {
|
|
@@ -375,7 +423,7 @@ var SceneVideoBackdrop = ({
|
|
|
375
423
|
onError,
|
|
376
424
|
"data-media-position": mediaPosition,
|
|
377
425
|
"data-video-backdrop": persistent ? "persistent" : "scene",
|
|
378
|
-
style: mediaStyle
|
|
426
|
+
style: { ...mediaStyle, visibility: exhaustedKey === videoPresentationKey || waitingKey === videoPresentationKey ? "hidden" : void 0 }
|
|
379
427
|
}
|
|
380
428
|
)
|
|
381
429
|
] });
|
|
@@ -8,11 +8,13 @@ function ExternalVideoBackdropProvider({
|
|
|
8
8
|
mode,
|
|
9
9
|
audioMuted = true,
|
|
10
10
|
audioVolume = 1,
|
|
11
|
+
preparingNarration = false,
|
|
12
|
+
onMediaError,
|
|
11
13
|
children
|
|
12
14
|
}) {
|
|
13
15
|
const value = React.useMemo(
|
|
14
|
-
() => ({ mode, audioMuted, audioVolume }),
|
|
15
|
-
[mode, audioMuted, audioVolume]
|
|
16
|
+
() => ({ mode, audioMuted, audioVolume, preparingNarration, onMediaError }),
|
|
17
|
+
[mode, audioMuted, audioVolume, preparingNarration, onMediaError]
|
|
16
18
|
);
|
|
17
19
|
return /* @__PURE__ */ jsx(BackdropContext.Provider, { value, children });
|
|
18
20
|
}
|
|
@@ -23,6 +25,12 @@ function useMediaAudio() {
|
|
|
23
25
|
const { audioMuted, audioVolume } = React.useContext(BackdropContext);
|
|
24
26
|
return { muted: audioMuted, volume: audioVolume };
|
|
25
27
|
}
|
|
28
|
+
function useNarrationPreroll() {
|
|
29
|
+
return React.useContext(BackdropContext).preparingNarration === true;
|
|
30
|
+
}
|
|
31
|
+
function useMediaFailure() {
|
|
32
|
+
return React.useContext(BackdropContext).onMediaError;
|
|
33
|
+
}
|
|
26
34
|
|
|
27
35
|
// src/visual-system/scene-templates/tokens.ts
|
|
28
36
|
var STYLE_PRESETS = {
|
|
@@ -142,5 +150,7 @@ export {
|
|
|
142
150
|
darken,
|
|
143
151
|
ExternalVideoBackdropProvider,
|
|
144
152
|
useExternalVideoBackdrop,
|
|
145
|
-
useMediaAudio
|
|
153
|
+
useMediaAudio,
|
|
154
|
+
useNarrationPreroll,
|
|
155
|
+
useMediaFailure
|
|
146
156
|
};
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
SceneVideoBackdrop,
|
|
4
4
|
getBackgroundTransform,
|
|
5
5
|
resolveMediaPosition
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-O6FBIB4L.js";
|
|
7
7
|
import {
|
|
8
8
|
resolveMediaType
|
|
9
9
|
} from "./chunk-224QNWRA.js";
|
|
10
10
|
import {
|
|
11
11
|
useExternalVideoBackdrop
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-OOBT4X46.js";
|
|
13
13
|
|
|
14
14
|
// src/visual-system/scene-templates/scene-background.tsx
|
|
15
15
|
import { useEffect, useState } from "react";
|
|
@@ -157,6 +157,7 @@ var SceneBackground = ({
|
|
|
157
157
|
mediaPosition,
|
|
158
158
|
backgroundEffect,
|
|
159
159
|
progress,
|
|
160
|
+
sceneDuration,
|
|
160
161
|
beatIntensity,
|
|
161
162
|
isPlaying,
|
|
162
163
|
onReady: () => setMediaPaint("ready"),
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
ExternalVideoBackdropProvider,
|
|
7
7
|
resolveDensity
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OOBT4X46.js";
|
|
9
9
|
import {
|
|
10
10
|
resolveVideoTimeline
|
|
11
11
|
} from "./chunk-SPVTJH3F.js";
|
|
@@ -209,7 +209,7 @@ var SceneBoundary = class extends Component {
|
|
|
209
209
|
}
|
|
210
210
|
};
|
|
211
211
|
var SCENE_TRANSITION_SECONDS = 0.3;
|
|
212
|
-
var SceneVideoBackdrop = lazy(() => import("./scene-video-backdrop-
|
|
212
|
+
var SceneVideoBackdrop = lazy(() => import("./scene-video-backdrop-4EZVVQY7.js").then((module) => ({ default: module.SceneVideoBackdrop })));
|
|
213
213
|
var MEDIA_PREROLL_SECONDS = 1.2;
|
|
214
214
|
var CONTIGUITY_ULP_FACTOR = 4;
|
|
215
215
|
var subscribeToDecoderPolicy = () => () => {
|
|
@@ -257,6 +257,7 @@ function sceneBackgroundChanges(left, right) {
|
|
|
257
257
|
}
|
|
258
258
|
function SceneLayer({
|
|
259
259
|
onFramePresented,
|
|
260
|
+
onMediaError,
|
|
260
261
|
kit,
|
|
261
262
|
config,
|
|
262
263
|
range,
|
|
@@ -265,6 +266,7 @@ function SceneLayer({
|
|
|
265
266
|
width,
|
|
266
267
|
height,
|
|
267
268
|
playing,
|
|
269
|
+
preparingNarration,
|
|
268
270
|
mediaAudioMuted,
|
|
269
271
|
mediaAudioVolume,
|
|
270
272
|
layer,
|
|
@@ -273,7 +275,7 @@ function SceneLayer({
|
|
|
273
275
|
zIndex,
|
|
274
276
|
externalVideoBackdrop
|
|
275
277
|
}) {
|
|
276
|
-
const mediaFailed = externalVideoBackdrop === "fallback";
|
|
278
|
+
const mediaFailed = externalVideoBackdrop === "fallback" || range.scene.templateId === "cinemaMedia" && !String(range.scene.variables.mediaUrl || "").trim();
|
|
277
279
|
const recoveryTitle = mediaFailed && range.scene.templateId === "cinemaMedia" && typeof range.scene.variables.fallbackText === "string" ? range.scene.variables.fallbackText : void 0;
|
|
278
280
|
const template = kit.getTemplate(recoveryTitle ? "chapterTitle" : range.scene.templateId);
|
|
279
281
|
const duration = range.end - range.start;
|
|
@@ -283,7 +285,9 @@ function SceneLayer({
|
|
|
283
285
|
mode: externalVideoBackdrop,
|
|
284
286
|
audioMuted: mediaAudioMuted || !playing,
|
|
285
287
|
audioVolume: mediaAudioVolume,
|
|
286
|
-
|
|
288
|
+
preparingNarration,
|
|
289
|
+
onMediaError,
|
|
290
|
+
children: /* @__PURE__ */ jsxs(
|
|
287
291
|
"div",
|
|
288
292
|
{
|
|
289
293
|
"data-scene-layer": layer,
|
|
@@ -304,37 +308,48 @@ function SceneLayer({
|
|
|
304
308
|
"--vanillasky-transition-semantic-visibility": layer === "incoming" ? "hidden" : "visible",
|
|
305
309
|
"--vanillasky-template-surface": externalVideoBackdrop !== false ? "transparent" : void 0
|
|
306
310
|
},
|
|
307
|
-
children:
|
|
308
|
-
"
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
311
|
+
children: [
|
|
312
|
+
range.scene.templateId === "cinemaMedia" && !recoveryTitle && (mediaFailed || !String(range.scene.variables.mediaUrl || "").trim()) && /* @__PURE__ */ jsx2(
|
|
313
|
+
"div",
|
|
314
|
+
{
|
|
315
|
+
role: "status",
|
|
316
|
+
"data-media-unavailable": "true",
|
|
317
|
+
style: { position: "absolute", inset: 0, zIndex: 2, display: "grid", placeContent: "center", color: "#bbb", font: "14px system-ui", background: "#000" },
|
|
318
|
+
children: "Visual unavailable"
|
|
319
|
+
}
|
|
320
|
+
),
|
|
321
|
+
template ? /* @__PURE__ */ jsx2(SceneBoundary, { scene: range.scene, onFramePresented, children: /* @__PURE__ */ jsxs(Suspense, { fallback: /* @__PURE__ */ jsx2(
|
|
322
|
+
"div",
|
|
323
|
+
{
|
|
324
|
+
"data-template-loading": range.scene.templateId,
|
|
325
|
+
style: { position: "absolute", inset: 0 }
|
|
326
|
+
}
|
|
327
|
+
), children: [
|
|
328
|
+
/* @__PURE__ */ jsx2(PresentedScene, { notify: onFramePresented }),
|
|
329
|
+
createElement(template.component, {
|
|
330
|
+
variables: {
|
|
331
|
+
...template.defaults ?? getTemplateDefaults(template.schema),
|
|
332
|
+
...range.scene.variables,
|
|
333
|
+
...mediaFailed ? { mediaUrl: "", mediaPoster: "", mediaType: "gradient" } : {},
|
|
334
|
+
...recoveryTitle ? { title: recoveryTitle } : {}
|
|
335
|
+
},
|
|
336
|
+
style: config.style,
|
|
337
|
+
progress,
|
|
338
|
+
motionProgress,
|
|
339
|
+
beatIntensity: 0,
|
|
340
|
+
width,
|
|
341
|
+
height,
|
|
342
|
+
textArchetype: range.scene.textArchetype ?? config.style.defaultTextArchetype,
|
|
343
|
+
backgroundEffect: range.scene.backgroundEffect ?? config.style.defaultBackgroundEffect,
|
|
344
|
+
safeZone: scaleSafeZone(
|
|
345
|
+
getSafeZone(config.orientation),
|
|
346
|
+
resolveDensity(config.style.density).safeZoneScale
|
|
347
|
+
),
|
|
348
|
+
sceneDuration: duration,
|
|
349
|
+
isPlaying: playing
|
|
350
|
+
})
|
|
351
|
+
] }) }, range.scene.id) : /* @__PURE__ */ jsx2(SafeScene, { scene: range.scene, onFramePresented })
|
|
352
|
+
]
|
|
338
353
|
}
|
|
339
354
|
)
|
|
340
355
|
}
|
|
@@ -348,6 +363,7 @@ function VideoFrame({
|
|
|
348
363
|
width,
|
|
349
364
|
height,
|
|
350
365
|
playing = false,
|
|
366
|
+
preparingNarration = false,
|
|
351
367
|
mediaAudioMuted = true,
|
|
352
368
|
mediaAudioVolume = 1,
|
|
353
369
|
className,
|
|
@@ -550,7 +566,9 @@ function VideoFrame({
|
|
|
550
566
|
mediaPosition: String(persistentVideoRange.scene.variables.mediaPosition || "center"),
|
|
551
567
|
backgroundEffect: persistentVideoRange.scene.backgroundEffect ?? config.style.defaultBackgroundEffect,
|
|
552
568
|
progress: persistentVideoRange.scene.id === active.scene.id ? rawProgress : 0,
|
|
569
|
+
sceneDuration: persistentVideoRange.end - persistentVideoRange.start,
|
|
553
570
|
isPlaying: persistentVideoRange.scene.id === active.scene.id && playing,
|
|
571
|
+
preparingNarration,
|
|
554
572
|
muted: mediaAudioMuted || persistentVideoRange.scene.id !== active.scene.id || !playing,
|
|
555
573
|
volume: mediaAudioVolume,
|
|
556
574
|
playbackId: persistentVideoRange.scene.id,
|
|
@@ -578,11 +596,13 @@ function VideoFrame({
|
|
|
578
596
|
kit,
|
|
579
597
|
config,
|
|
580
598
|
range: active,
|
|
599
|
+
onMediaError: () => markMediaFailed(sceneReadinessKey(active.scene)),
|
|
581
600
|
progress,
|
|
582
601
|
motionProgress,
|
|
583
602
|
width: canvas.width,
|
|
584
603
|
height: canvas.height,
|
|
585
604
|
playing,
|
|
605
|
+
preparingNarration,
|
|
586
606
|
mediaAudioMuted,
|
|
587
607
|
mediaAudioVolume,
|
|
588
608
|
layer: previewingNext ? "outgoing" : "active",
|
|
@@ -604,6 +624,7 @@ function VideoFrame({
|
|
|
604
624
|
width: canvas.width,
|
|
605
625
|
height: canvas.height,
|
|
606
626
|
playing: false,
|
|
627
|
+
preparingNarration: false,
|
|
607
628
|
mediaAudioMuted,
|
|
608
629
|
mediaAudioVolume,
|
|
609
630
|
layer: "incoming",
|
|
@@ -622,11 +643,13 @@ function VideoFrame({
|
|
|
622
643
|
kit,
|
|
623
644
|
config,
|
|
624
645
|
range: active,
|
|
646
|
+
onMediaError: () => markMediaFailed(sceneReadinessKey(active.scene)),
|
|
625
647
|
progress,
|
|
626
648
|
motionProgress,
|
|
627
649
|
width: canvas.width,
|
|
628
650
|
height: canvas.height,
|
|
629
651
|
playing,
|
|
652
|
+
preparingNarration,
|
|
630
653
|
mediaAudioMuted,
|
|
631
654
|
mediaAudioVolume,
|
|
632
655
|
layer: "active",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MediaScene,
|
|
3
3
|
MediaSceneTemplate
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-Z2ZI5G7O.js";
|
|
5
|
+
import "./chunk-QZAZT44G.js";
|
|
6
|
+
import "./chunk-O6FBIB4L.js";
|
|
7
7
|
import "./chunk-224QNWRA.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-OOBT4X46.js";
|
|
9
9
|
export {
|
|
10
10
|
MediaScene,
|
|
11
11
|
MediaSceneTemplate
|