@vanillaskyai/video 0.10.16 → 0.10.18

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 CHANGED
@@ -4,6 +4,16 @@ VanillaSky follows semantic versioning. This changelog begins with the 0.1 beta.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.10.18
8
+
9
+ - Preserve later valid shots after a malformed body record containing an unescaped newline, while keeping malformed initial containers rejected. Bound unfinished JSON records independently of provider chunk sizes.
10
+ - Keep the outgoing scene visible while a canonical image backdrop prepares or reaches its bounded chapter recovery.
11
+
12
+ ## 0.10.17
13
+
14
+ - Let video playback prepare without waiting for an optional poster, while retaining actual video-frame and narration readiness checks.
15
+ - Guide shorter, useful spoken openings and compact streamed briefs, with clearer direct answers, comparisons, and narration-aligned actions.
16
+
7
17
  ## 0.10.16
8
18
 
9
19
  - Start the first fully prepared scene without waiting for an eight-second startup buffer, while preserving narration and visual readiness.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VideoFrame
3
- } from "./chunk-MXLVTOGR.js";
3
+ } from "./chunk-M4QTEJTK.js";
4
4
  import "./chunk-5JBMYQP6.js";
5
5
  import "./chunk-224QNWRA.js";
6
6
  import "./chunk-SPVTJH3F.js";
@@ -448,11 +448,15 @@ function VideoFrame({
448
448
  }
449
449
  const previousIndex = timeline.findIndex((range) => sceneReadinessKey(range.scene) === displayedKey.current);
450
450
  const previous = timeline[previousIndex];
451
- const canPrepare = (range) => Boolean(range && mediaAudioMuted && sceneHasVideoBackdrop(range) && supportsExternalVideoBackdrop(kit.getTemplate(range.scene.templateId)));
451
+ const canPrepare = (range) => Boolean(range && (mediaAudioMuted || !sceneHasVideoBackdrop(range)) && sceneHasBackdrop(range) && supportsExternalVideoBackdrop(kit.getTemplate(range.scene.templateId)));
452
452
  const canRetain = (range) => canPrepare(range) || range?.scene.templateId === "chapterTitle";
453
453
  const hasPlayableMedia = (range) => {
454
454
  if (!range || !preparedMedia.has(sceneReadinessKey(range.scene))) return false;
455
455
  const layer = [...recoveryRoot.current?.querySelectorAll("[data-layer-scene-id]") ?? []].find((node) => node.getAttribute("data-layer-scene-id") === range.scene.id);
456
+ if (!sceneHasVideoBackdrop(range)) {
457
+ const image = [...layer?.querySelectorAll("img") ?? []].find((element) => element.getAttribute("src") === range.scene.variables.mediaUrl);
458
+ return Boolean(image && image.getAttribute("src") === range.scene.variables.mediaUrl && image.complete && image.naturalWidth > 0);
459
+ }
456
460
  const video = layer?.querySelector("video");
457
461
  return Boolean(video && video.getAttribute("src") === range.scene.variables.mediaUrl && video.currentSrc === video.src && video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA);
458
462
  };
package/dist/react.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  VideoFrame,
41
41
  getDimensions,
42
42
  sceneReadinessKey
43
- } from "./chunk-MXLVTOGR.js";
43
+ } from "./chunk-M4QTEJTK.js";
44
44
  import "./chunk-5JBMYQP6.js";
45
45
  import "./chunk-224QNWRA.js";
46
46
  import {
@@ -1501,9 +1501,7 @@ async function prepareSceneMedia(variables, signal) {
1501
1501
  const url = typeof variables.mediaUrl === "string" ? variables.mediaUrl.trim() : "";
1502
1502
  if (!url || variables.mediaType === "gradient") return "graphic";
1503
1503
  const video = variables.mediaType === "video" || /\.(mp4|webm|mov)(?:[?#]|$)/i.test(url);
1504
- const poster = typeof variables.mediaPoster === "string" ? variables.mediaPoster.trim() : "";
1505
- if (video && !poster) return "awaiting-video-frame";
1506
- let posterFailed = false;
1504
+ if (video) return "awaiting-video-frame";
1507
1505
  await new Promise((resolve, reject) => {
1508
1506
  const image = new Image();
1509
1507
  let settled = false;
@@ -1526,13 +1524,10 @@ async function prepareSceneMedia(variables, signal) {
1526
1524
  void (image.decode ? image.decode() : Promise.resolve()).then(() => finish(), () => finish(new Error("Could not prepare scene image")));
1527
1525
  };
1528
1526
  image.onerror = () => finish(new Error("Could not prepare scene image"));
1529
- image.src = video ? poster : url;
1527
+ image.src = url;
1530
1528
  if (image.complete && image.naturalWidth > 0) image.onload(new Event("load"));
1531
- }).catch((error) => {
1532
- if (!video || signal.aborted) throw error;
1533
- posterFailed = true;
1534
1529
  });
1535
- return video ? posterFailed ? "awaiting-video-frame" : "poster-bridge" : "image";
1530
+ return "image";
1536
1531
  }
1537
1532
 
1538
1533
  // src/video-chat/voice.ts
package/dist/server.js CHANGED
@@ -1074,6 +1074,13 @@ function createChatShotPlanner(options) {
1074
1074
  for (; cursor < buffer.length; cursor++) {
1075
1075
  const character = buffer[cursor];
1076
1076
  if (quoted) {
1077
+ if ((character === "\n" || character === "\r") && brief && depth === 1 && buffer[0] === "{") {
1078
+ const raw = buffer.slice(0, cursor + 1);
1079
+ buffer = buffer.slice(cursor + 1);
1080
+ cursor = depth = 0;
1081
+ quoted = escaped = false;
1082
+ return raw;
1083
+ }
1077
1084
  if (escaped) escaped = false;
1078
1085
  else if (character === "\\") escaped = true;
1079
1086
  else if (character === '"') quoted = false;
@@ -1094,17 +1101,22 @@ function createChatShotPlanner(options) {
1094
1101
  for await (const delta of upstream) {
1095
1102
  context.signal.throwIfAborted();
1096
1103
  if (typeof delta !== "string") throw new Error("The LLM adapter returned a non-text delta");
1097
- buffer += delta;
1098
- if (buffer.length > 32768) throw new Error("Chat plan line exceeds the bounded stream limit");
1099
- let raw = takeFrame();
1100
- while (raw !== void 0) {
1101
- try {
1102
- const part = line(raw);
1103
- if (part) yield JSON.stringify(part) + "\n";
1104
- } catch (cause) {
1105
- reject(cause);
1104
+ for (let offset = 0; offset < delta.length; ) {
1105
+ const capacity = 32768 - buffer.length;
1106
+ if (capacity <= 0) throw new Error("Chat plan line exceeds the bounded stream limit");
1107
+ const piece = delta.slice(offset, offset + capacity);
1108
+ buffer += piece;
1109
+ offset += piece.length;
1110
+ let raw = takeFrame();
1111
+ while (raw !== void 0) {
1112
+ try {
1113
+ const part = line(raw);
1114
+ if (part) yield JSON.stringify(part) + "\n";
1115
+ } catch (cause) {
1116
+ reject(cause);
1117
+ }
1118
+ raw = takeFrame();
1106
1119
  }
1107
- raw = takeFrame();
1108
1120
  }
1109
1121
  }
1110
1122
  if (buffer.trim()) {
@@ -1217,18 +1229,21 @@ function createVideoChatResponseInstructions(generatedVideoAvailable, openingAlr
1217
1229
  return [
1218
1230
  'Use the exact record type "answer" for the first brief and "shot" for developing beats. Output JSON records only, with no prose outside them, including when explaining a limitation.',
1219
1231
  "Write a complete, intentful video answer as newline-delimited JSON. Match the user's form and tone; mixed intents can combine directions.",
1220
- `First write one brief: {"type":"answer","intent":"explanation|story|comedy|imagination|practical","opening":"a short inviting spoken introduction of 6\u20139 words","subject":"literal visual subject","development":"the essential development of this answer","visualDirection":"consistent subjects, appearance and visual approach","ending":{"title":"short meaningful chapter title, at most 65 characters","narration":"the authored payoff","subject":"literal subject","action":"visible action or change","durationSec":${clipDurationSec},"continuity":"cut|continue"}}.`,
1232
+ `First write one brief: {"type":"answer","intent":"explanation|story|comedy|imagination|practical","opening":"one useful spoken line of 4\u20137 ordinary words","subject":"literal visual subject","development":"the essential development of this answer","visualDirection":"consistent subjects, appearance and visual approach","ending":{"title":"short meaningful chapter title, at most 65 characters","narration":"the authored payoff","subject":"literal subject","action":"visible action or change","durationSec":${clipDurationSec},"continuity":"cut|continue"}}.`,
1233
+ "The opening should take roughly 2\u20133 seconds at a natural pace: give the core answer, a useful starting cue, or the story's immediate situation. No greeting, topic announcement, promise to explain, or description of loading. Never compress away an essential qualifier just to hit the word target.",
1221
1234
  `Then stream each developing shot on its own line: {"type":"shot","title":"short meaningful chapter title, at most 65 characters","narration":"the exact spoken beat","subject":"2\u20138 literal filmable words, at most 80 characters","action":"concrete subject, action or visible change and useful framing","durationSec":${clipDurationSec},"continuity":"cut|continue"}.`,
1222
1235
  `The selected footage mode is ${mode === "pexels" ? "Pexels stock search: use literal filmable subjects; never imply stock proves a mechanism or depicts fictional events exactly" : `AI video, with at most ${generatedVideoAvailable ? maxGeneratedVideos : 0} generation attempts`}. Missing footage becomes the authored chapter title, with complete narration. Never truncate already-authored narration when footage fails. The host selects providers; do not make source choices.`,
1223
1236
  ...mode === "cinematic" ? [generatedVideoAvailable && maxGeneratedVideos > 0 ? `Plan at most ${maxGeneratedVideos} generated-video beats in total, including the saved ending. Use at most ${maxGeneratedVideos - 1} developing shot records; the ending uses the remaining beat. The opening chapter does not consume a generated clip. Before writing, choose a concise, complete treatment that fits this budget: combine related ideas, preserve essential facts and qualifiers, and finish the requested answer. Do not plan an extra chapter tail simply because generation attempts will run out.${maxGeneratedVideos === 1 ? " Put the complete answer in the saved ending, set development to an empty string, and emit no developing shot records." : ""}` : "No generated-video attempts are available; plan a complete chapter-led answer with a useful ending. Do not omit the answer to satisfy a zero clip budget."] : [],
1224
1237
  ...mode === "pexels" ? ["Stock queries must retain the essential subject, activity and distinguishing equipment in the shot's subject field, within its word limit. That field alone is the search query; action and visualDirection do not refine it. Prefer common observable actions with usable framing. Do not replace the required actor or activity with scenery, a different sport or a loosely related setting. Preserve fictional or comic narration, but do not depend on stock showing an exact invented expression or sequence; choose an illustrative action that supports the beat."] : [],
1225
1238
  ...mode === "pexels" ? ['Include stockSelection on every shot and the saved ending when the essential subject is known: "stockSelection":{"subject":"essential actor or object category","activity":"optional literal activity","equipment":"optional distinguishing equipment","exclude":["optional contradictory subject or activity"]}. Each phrase must be 1\u20134 words and at most 48 characters; exclude has at most 3 phrases. The essential subject is separate from the setting: do not use scenery, mood, camera framing or incidental appearance as the actor. Keep the search query broad enough to find footage; the optional hint helps select results without substituting a different actor or task. Use exclusions only for actual contradictions, not every detail absent from the story. Omit unknown fields or the whole hint rather than inventing an anchor. This is selection guidance, not verification that footage depicts the exact narration.'] : [],
1239
+ "Keep development to one concise sentence and visualDirection to the few details needed for consistency. Emit the complete brief, then the first developing shot immediately when developing shots are needed and allowed by the budget; otherwise end after the brief. Continue the same stream without an outline, recap or second planning pass. The saved ending must still contain the complete payoff before the brief is emitted.",
1226
1240
  "For a very short answer whose ending alone fulfills the request, development may be empty and no developing shots are needed. Otherwise, develop the essential content before the ending.",
1227
1241
  "The brief's ending is saved and played after your developing shots. Do not repeat it as a shot. Stop writing after the last developing shot. No technical events, identifiers, template choices, media providers, URLs or unlisted fields.",
1228
1242
  "Every shot uses moving footage with separate narration and subtitles. Generated footage is silent: do not ask its subjects to speak or render words. No headline cards or on-screen explanatory text.",
1229
1243
  `Each clip has at most ${clipDurationSec} seconds. Write spoken beats that fit naturally, usually ${Math.floor(clipDurationSec * 1.6)}\u2013${Math.floor(clipDurationSec * 2)} words per shot. Split longer ideas across purposeful shots, preserving facts and qualifiers. Never truncate a claim to meet a word target. Use only the shots needed within the total duration, including the ending; do not pad to a fixed count.`,
1230
- "Identify the full answer and its ending before developing shots. Each action must support what is said: camera movement alone is not progression. Vary scale, viewpoint and meaningful details while keeping subjects consistent.",
1231
- "Explanations: clarify the actual causal mechanism, separating physical cause from a metaphor. Generated cutaways and animation illustrate ideas; they are not factual evidence. Preserve uncertainty, quantities and conditions; never invent evidence or quotations.",
1244
+ "Identify the full answer and its ending before developing shots. Each shot should carry one clear action or change, timed to the narration of that beat; do not describe an outcome before its shot. Use framing that lets the viewer see the relevant action, not just its setting. Each action must support what is said: camera movement alone is not progression. Vary scale, viewpoint and meaningful details while keeping subjects consistent.",
1245
+ "Explanations: answer the actual question first, then show the essential causal link rather than a tour of the topic. Clarify the actual causal mechanism, separating physical cause from a metaphor. Generated cutaways and animation illustrate ideas; they are not factual evidence. Preserve uncertainty, quantities and conditions; never invent evidence or quotations.",
1246
+ "Comparisons and choices: Compare the same criteria for both alternatives, using only supported or supplied differences. Finish with the requested choice and the condition that makes it appropriate; if evidence is insufficient, say what is missing. Do not invent scores, advantages or a winner. Use explanation or practical intent as appropriate, not a new record type.",
1232
1247
  "Stories: portray characters making choices and experiencing consequences; use consistent character descriptions and an earned resolution, not a promised next scene.",
1233
1248
  "Comedy: establish the premise, time the visual or spoken reveal, allow a reaction beat, and stop on the payoff without explaining the joke.",
1234
1249
  "Imagination: make the impossible action concrete, establish the world's internal rules and keep its imagery consistent. Do not replace imagination with an explanation of it.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanillaskyai/video",
3
- "version": "0.10.16",
3
+ "version": "0.10.18",
4
4
  "description": "Open-source voice-and-video chat SDK for AI applications.",
5
5
  "keywords": [
6
6
  "video-chat",
@@ -9,7 +9,7 @@
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
12
- "@vanillaskyai/video": "0.10.16",
12
+ "@vanillaskyai/video": "0.10.18",
13
13
  "react": "^19.2.8",
14
14
  "react-dom": "^19.2.8",
15
15
  "@ai-sdk/anthropic": "^3.0.0",