@vanillaskyai/video 0.10.1 → 0.10.2
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 +6 -0
- package/dist/react.js +27 -6
- package/dist/server.js +32 -20
- package/package.json +1 -1
- package/starters/video-chat/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ VanillaSky follows semantic versioning. This changelog begins with the 0.1 beta.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.10.2
|
|
8
|
+
|
|
9
|
+
- 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.
|
|
10
|
+
- In drop mode, a media miss without usable authored fallback skips only that scene and preserves later valid scenes and the ending. Strict mode still fails. The skipped beat is reported; its content cannot be reconstructed without a usable asset or fallback.
|
|
11
|
+
- Guide first-shot narration to fit its five-second footage budget and develop the answer in later scenes, without imposing a fixed sentence length on the rest of the video.
|
|
12
|
+
|
|
7
13
|
## 0.10.1
|
|
8
14
|
|
|
9
15
|
- Continue narration through failed built-in media using the authored fallback or the existing graphic on black. Keep the recovery anchor after URL resolution, and observe actual mounted images for photo readiness.
|
package/dist/react.js
CHANGED
|
@@ -1550,6 +1550,8 @@ function createVideoChatVoice(options = {}) {
|
|
|
1550
1550
|
const lines = /* @__PURE__ */ new Map();
|
|
1551
1551
|
const pendingLoads = /* @__PURE__ */ new Set();
|
|
1552
1552
|
let sounding;
|
|
1553
|
+
let generatedElement;
|
|
1554
|
+
let stopGenerated;
|
|
1553
1555
|
let browserFinish;
|
|
1554
1556
|
let held = false;
|
|
1555
1557
|
let silent = false;
|
|
@@ -1674,7 +1676,12 @@ function createVideoChatVoice(options = {}) {
|
|
|
1674
1676
|
},
|
|
1675
1677
|
resume() {
|
|
1676
1678
|
held = false;
|
|
1677
|
-
if (sounding)
|
|
1679
|
+
if (sounding) {
|
|
1680
|
+
const fail = playbackFailure;
|
|
1681
|
+
void sounding.play().catch(() => {
|
|
1682
|
+
if (!held) fail?.();
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1678
1685
|
if (!silent) globalThis.speechSynthesis?.resume();
|
|
1679
1686
|
},
|
|
1680
1687
|
setMuted(muted) {
|
|
@@ -1753,9 +1760,11 @@ function createVideoChatVoice(options = {}) {
|
|
|
1753
1760
|
}
|
|
1754
1761
|
let playbackFailed = false;
|
|
1755
1762
|
try {
|
|
1756
|
-
|
|
1763
|
+
stopGenerated?.();
|
|
1764
|
+
const element = generatedElement ??= new Audio();
|
|
1765
|
+
element.src = line.src;
|
|
1757
1766
|
element.muted = silent;
|
|
1758
|
-
|
|
1767
|
+
element.currentTime = offsetSeconds ?? 0;
|
|
1759
1768
|
sounding = element;
|
|
1760
1769
|
await new Promise((resolve) => {
|
|
1761
1770
|
let finished = false;
|
|
@@ -1771,6 +1780,7 @@ function createVideoChatVoice(options = {}) {
|
|
|
1771
1780
|
element.onplaying = null;
|
|
1772
1781
|
element.onended = null;
|
|
1773
1782
|
element.onerror = null;
|
|
1783
|
+
if (stopGenerated === stop) stopGenerated = void 0;
|
|
1774
1784
|
if (sounding === element) {
|
|
1775
1785
|
sounding = void 0;
|
|
1776
1786
|
playbackFailure = void 0;
|
|
@@ -1778,13 +1788,17 @@ function createVideoChatVoice(options = {}) {
|
|
|
1778
1788
|
resolve();
|
|
1779
1789
|
};
|
|
1780
1790
|
const stop = () => {
|
|
1791
|
+
if (finished) return;
|
|
1781
1792
|
element.pause();
|
|
1782
1793
|
finish();
|
|
1783
1794
|
};
|
|
1784
1795
|
const fail = () => {
|
|
1785
|
-
|
|
1786
|
-
|
|
1796
|
+
if (!finished) {
|
|
1797
|
+
playbackFailed = true;
|
|
1798
|
+
stop();
|
|
1799
|
+
}
|
|
1787
1800
|
};
|
|
1801
|
+
stopGenerated = stop;
|
|
1788
1802
|
const clearWatchdog = watchSpeech(Math.max(line.seconds, estimatedBrowserSeconds(text)), fail);
|
|
1789
1803
|
speechStops.add(stop);
|
|
1790
1804
|
playbackFailure = fail;
|
|
@@ -1799,7 +1813,9 @@ function createVideoChatVoice(options = {}) {
|
|
|
1799
1813
|
element.onerror = fail;
|
|
1800
1814
|
signal.addEventListener("abort", stop, { once: true });
|
|
1801
1815
|
try {
|
|
1802
|
-
if (!held) void element.play().catch(
|
|
1816
|
+
if (!held) void element.play().catch(() => {
|
|
1817
|
+
if (!held) fail();
|
|
1818
|
+
});
|
|
1803
1819
|
} catch {
|
|
1804
1820
|
fail();
|
|
1805
1821
|
}
|
|
@@ -1825,6 +1841,11 @@ function createVideoChatVoice(options = {}) {
|
|
|
1825
1841
|
speechStops.clear();
|
|
1826
1842
|
sounding?.pause();
|
|
1827
1843
|
sounding = void 0;
|
|
1844
|
+
generatedElement?.pause();
|
|
1845
|
+
generatedElement?.removeAttribute?.("src");
|
|
1846
|
+
generatedElement?.load?.();
|
|
1847
|
+
generatedElement = void 0;
|
|
1848
|
+
stopGenerated = void 0;
|
|
1828
1849
|
stopBrowser();
|
|
1829
1850
|
for (const line of lines.values()) {
|
|
1830
1851
|
if (line.source === "generated") URL.revokeObjectURL(line.src);
|
package/dist/server.js
CHANGED
|
@@ -670,27 +670,38 @@ function createMediaResolvingPlanner(options) {
|
|
|
670
670
|
return false;
|
|
671
671
|
}
|
|
672
672
|
};
|
|
673
|
-
const resolveOne = (part, openingReady2) =>
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
673
|
+
const resolveOne = async (part, openingReady2) => {
|
|
674
|
+
try {
|
|
675
|
+
return await resolvePartVariables({
|
|
676
|
+
part,
|
|
677
|
+
requestId: context.request.requestId,
|
|
678
|
+
templateId: part.type === "scene.add" ? part.scene.templateId : void 0,
|
|
679
|
+
input: context.request.input,
|
|
680
|
+
signal: context.signal,
|
|
681
|
+
templates: options.templates,
|
|
682
|
+
resolveMedia: options.resolveMedia,
|
|
683
|
+
approveUrl: options.approveUrl,
|
|
684
|
+
openingReady: openingReady2 ?? options.isOpeningReady(context.request.input),
|
|
685
|
+
claimBudget,
|
|
686
|
+
onFallback: () => getGenerationLifecycleSink(context)?.reportWarning?.({
|
|
687
|
+
code: "provider_warning",
|
|
688
|
+
category: "provider",
|
|
689
|
+
message: "Some visuals use a simple background.",
|
|
690
|
+
recoverable: true
|
|
691
|
+
})
|
|
692
|
+
});
|
|
693
|
+
} catch (cause) {
|
|
694
|
+
if (context.signal.aborted || part.type !== "scene.add") throw cause;
|
|
695
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
696
|
+
if (!getGenerationLifecycleSink(context)?.rejectPart?.(error)) throw error;
|
|
697
|
+
return void 0;
|
|
698
|
+
}
|
|
699
|
+
};
|
|
691
700
|
if (limit === 1) {
|
|
692
701
|
for await (const part of options.planner(context)) {
|
|
693
|
-
if (preflight(part))
|
|
702
|
+
if (!preflight(part)) continue;
|
|
703
|
+
const resolved = await resolveOne(part);
|
|
704
|
+
if (resolved) yield resolved;
|
|
694
705
|
}
|
|
695
706
|
return;
|
|
696
707
|
}
|
|
@@ -748,7 +759,7 @@ function createMediaResolvingPlanner(options) {
|
|
|
748
759
|
pending.shift();
|
|
749
760
|
notifyProducer();
|
|
750
761
|
if ("cause" in result) throw result.cause;
|
|
751
|
-
yield result.part;
|
|
762
|
+
if (result.part) yield result.part;
|
|
752
763
|
}
|
|
753
764
|
await producer;
|
|
754
765
|
if (producerError !== void 0) throw producerError;
|
|
@@ -931,6 +942,7 @@ function createVideoChatResponseInstructions(generatedVideoAvailable, openingAlr
|
|
|
931
942
|
generatedVideoAvailable ? 'First emit one host-consumed opening JSON object: {"type":"video-chat.opening","spokenHook":"6-9 words","mediaKeyword":"literal subject","firstShot":{"text":"short grounded fallback","narration":"a natural spoken beat advancing the hook","mediaKeyword":"2-8 concrete words","shotDirection":"optional framing and action"}}. Include firstShot only when a distinctive illustrative shot serves the story; omit it for an abstract explanation best opened with graphics. When present, the host inserts it once and begins generation immediately; do not emit it again.' : openingAlreadyProvided ? "Start directly with scene.add; the opening has already been supplied." : 'First emit {"type":"video-chat.opening","spokenHook":"6-9 words","mediaKeyword":"literal subject"}. This is host-consumed, not a scene.',
|
|
932
943
|
openingAlreadyProvided ? "Preserve the supplied opening exactly; continue it without repeating its words or claim." : void 0,
|
|
933
944
|
"The spokenHook is already heard before the first scene. Start firstShot.narration with the next fact, cause, action or consequence; never restate the hook. The first ordinary scene must then advance beyond firstShot rather than repeat its narration or opening claim. If firstShot is omitted, that first scene must advance the hook directly. firstShot.text, fallbackText and chapter titles must also express that new beat, not reuse the hook or prior shot copy; write a concise visual anchor of roughly 2\u20136 words within the schema budget, never a full narration sentence, clipped statistic or qualifying clause.",
|
|
945
|
+
"The optional firstShot has five seconds of footage: give its narration one brief sentence of roughly 8\u201310 spoken words that fits that budget. Carry further explanation, actions or dialogue in later scenes instead of squeezing the answer into the first shot. This limit applies only to firstShot; pace later narration to its own scene.",
|
|
934
946
|
"Continue with only as many scenes as the story and duration need. Never pad to a fixed count. Every emitted scene carries narration beside variables and timing; no additional narration request should be needed.",
|
|
935
947
|
"Make most of the film relevant footage: use full-bleed media for action, subjects and atmosphere, with graphics only to clarify a specific relationship or piece of evidence. Maintain consistent setting, lighting and subject while varying shot scale. Avoid unrelated cinematic montages.",
|
|
936
948
|
"Use the installed catalog: ordered events, comparison, exact quote, one key figure, chapter or a message only when its narrative job fits. For an ordinary explanation, roughly one graphic-led explanatory beat per 30 seconds is a starting point, plus a brief chapter opening when needed. Let other intents use the visual form they need. Do not place graphic beats consecutively unless no honest relevant media is available. Give media-capable graphics a relevant background when it supports the meaning; keep black when no matching media exists. Do not force every template into a video or use lists of bullets.",
|
package/package.json
CHANGED