Web Player API
Cavalry Web Player 和 API 目前处于测试阶段,因此可能会发生变化。
Cavalry Web Player 可用于在网页浏览器中播放和交互 Cavalry 场景。本文档提供了完整的集成指南和 API 参考。
Web Player 由两个主要部分组成。
- WebAssembly 运行时
- JavaScript 模块
JavaScript 模块用于实例化 Web Player。Web Player 包含 4 个方面。
- Module — 导入的
CavalryWasm模块的实例 - Player — 控制播放、资源管理等功能的 Cavalry 播放器(类似于 Cavalry 的脚本 API)
- Canvas — 用作视图的
canvas元素 - Surface — 将场景渲染到
canvas的 WebGL 表面
基本示例
标题为“基本示例”的章节// Import the Cavalry moduleconst wasm = await import("../wasm-lib/CavalryWasm.js")// Initialise the moduleconst Module = await wasm.default({ // `locateFile` is required for the module to load the wasm files locateFile: (path) => `../wasm-lib/${path}`, print: (text) => console.log(text), printErr: (text) => console.error(text),})// Load a scene fileconst response = await fetch("scene.cv")const sceneData = await response.arrayBuffer()Module.FS.writeFile("scene.cv", new Uint8Array(sceneData))// Create a player instanceconst player = Module.Cavalry.MakeWithPath("scene.cv")// Read the scene resolutionconst scene = player.getSceneResolution()const canvas = document.getElementById("canvas")canvas.width = scene.widthcanvas.height = scene.height// Set up a rendering surfaceconst surface = Module.makeWebGLSurfaceFromElement( canvas, scene.width, scene.height,)// Render the current frameplayer.render(surface)// Use `requestAnimationFrame` for efficient playbacklet animationFrameId = 0const runPlaybackLoop = () => { const tick = (timestamp) => { // `tick` advances the frame based on the elapsed time const status = player.tick(surface, timestamp); // Optionally update the UI when a frame changes if (status.frameChanged) { console.log("Frame", status.currentFrame); } animationFrameId = requestAnimationFrame(tick) } animationFrameId = requestAnimationFrame(tick)}runPlaybackLoop()player.play()// To stop playback// player.stop();// cancelAnimationFrame(animationFrameId);Module
标题为“Module”的章节使用以下方式导入模块:
// Import the Cavalry moduleconst wasm = await import("../wasm-lib/CavalryWasm.js")// Initialise the moduleconst Module = await wasm.default({ // `locateFile` is required for the module to load the wasm files locateFile: (path) => `../wasm-lib/${path}`, // Logs diagnostic data to the console print: (text) => console.log(text), // Logs errors to the console printErr: (text) => console.error(text),})makeWebGLSurface(canvasId:string, width:number, height:number) → SkSurface
标题为“makeWebGLSurface(canvasId:string, width:number, height:number) → SkSurface”的章节使用指定的 canvas 元素初始化 WebGL2 上下文和 Skia 表面。
参数:
canvasId: 画布 HTML 元素的 ID。width: 渲染表面的宽度。height: 渲染表面的高度。
返回值: 绑定到 WebGL 上下文的 Skia SkSurface。
const surface = Module.makeWebGLSurface("canvas", 800, 600);makeWebGLSurfaceFromElement(canvas:emscripten::val, width:number, height:number) → SkSurface
标题为“makeWebGLSurfaceFromElement(canvas:emscripten::val, width:number, height:number) → SkSurface”的章节使用指定的 canvas 元素初始化 WebGL2 上下文和 Skia 表面。
参数:
canvas: 画布 HTML 元素。width: 渲染表面的宽度。height: 渲染表面的高度。
返回值: 绑定到 WebGL 上下文的 Skia SkSurface。
const surface = Module.makeWebGLSurfaceFromElement(canvas, canvas.width, canvas.height);loadFont(path:string, alias?:string) → string
标题为“loadFont(path:string, alias?:string) → string”的章节将字体从虚拟文件系统加载到运行时字体注册表中。
参数:
path: 虚拟文件系统中字体文件的路径(例如.ttf、.otf、.woff、.woff2)。alias(可选): 注册字体时使用的自定义名称。如果提供,字体将使用此别名而不是其内部族名进行注册。这对于匹配外部命名系统(例如 CSS font-family 名称)非常有用。
返回值: 注册的族名(如果提供了别名则为别名,否则为字体的内部族名)。
// Load font using its internal family nameModule.FS.writeFile("Montserrat.ttf", fontBytes);const familyName = Module.loadFont("Montserrat.ttf", "");// familyName = "Montserrat" (from font's internal name)// Load font with a custom aliasModule.FS.writeFile("AC-Compacta.woff", fontBytes);const familyName = Module.loadFont("AC-Compacta.woff", "my-custom-id");// familyName = "my-custom-id"// Use the alias with other font APIsconst styles = player.getFontStyles("my-custom-id");player.setAttributeFont("textShape#1", "font", { font: "my-custom-id", style: "Regular" });getFontMetadata(familyName:string) → FontMetadata
标题为“getFontMetadata(familyName:string) → FontMetadata”的章节获取已加载字体的详细元数据,包括所有可用样式及其 CSS 兼容值。
参数:
familyName: 注册的族名(来自loadFont()或queryFonts())。
返回值: 包含字体名称变体和样式信息的 FontMetadata 对象。
// Load a fontconst familyName = Module.loadFont("Inter.ttf", "");// Get metadataconst metadata = Module.getFontMetadata(familyName);console.log("Family:", metadata.registeredFamily);console.log("Styles:", metadata.styles);// Use style info for CSS mappingmetadata.styles.forEach(style => { console.log(`${style.name}: weight=${style.weight}, slant=${style.slant}`);});// Output: "Regular: weight=400, slant=normal"// Output: "Bold: weight=700, slant=normal"// Output: "Italic: weight=400, slant=italic"loadFontData(url:string)
标题为“loadFontData(url:string)”的章节从给定的 URL 加载字体数据并将其加载到运行时字体注册表中。
参数:
url(字符串): 获取字体数据的 URL。
// Load a font from a URLModule.loadFontData("/url/to/font.ttf");Cavalry
标题为“Cavalry”的章节使用以下方式创建实例:
const player = Module.Cavalry.Make("scene.cv");pendingAssets
标题为“pendingAssets”的章节当通过 MakeWithPath 加载场景时,其资源将位于 pendingAssets 数组中。然后可以通过遍历该数组来加载资源。
有关实现示例,请参见加载资源。
const player = Module.Cavalry.MakeWithPath("scene.cv");console.log(Module.pendingAssets)specialHTMLTargets
标题为“specialHTMLTargets”的章节向此对象添加 canvas 元素允许 CavalryWasm 模块在 canvas 位于 Shadow DOM 中时找到它。
请参见 Shadow DOM。
const id = "my-canvas"const canvas = document.createElement("canvas")canvas.id = idModule.specialHTMLTargets[id] = canvasMake(scenePath?:string) → Cavalry
标题为“Make(scenePath?:string) → Cavalry”的章节构造并返回一个新的 Cavalry 类实例。
参数:
scenePath(可选): 虚拟文件系统中场景文件的路径。默认为"scene.cv"以保持向后兼容。
// Load default scene.cvconst player = Module.Cavalry.Make();// Load a specific scene fileconst player = Module.Cavalry.Make("myAnimation.cv");MakeWithPath(scenePath:string) → Cavalry
标题为“MakeWithPath(scenePath:string) → Cavalry”的章节构造并返回一个新的 Cavalry 类实例。
这是 Make 的替代方法。
参数:
scenePath: 虚拟文件系统中场景文件的路径。
// Load a specific scene fileconst player = Module.Cavalry.MakeWithPath("scene.cv");render(surface:SkSurface)
标题为“render(surface:SkSurface)”的章节将当前帧渲染到指定的 WebGL Skia 表面。
player.render(surface);getSceneResolution() → Resolution
标题为“getSceneResolution() → Resolution”的章节获取当前合成画面的分辨率。
返回值: Resolution 类型的对象。
const resolution = player.getSceneResolution();console.log(resolution.width, resolution.height);replaceImageAsset(filePath:string, assetId:string)
标题为“replaceImageAsset(filePath:string, assetId:string)”的章节将 assetId 处的图像资源替换为给定路径的图像。
Module.FS.writeFile("image.png", imageBytes);player.replaceImageAsset("image.png", "asset#2");player.render(surface);replaceImageAssetData(assetId:string, image:string)
标题为“replaceImageAssetData(assetId:string, image:string)”的章节将 assetId 处的图像资源替换为虚拟文件系统中的图像。
player.replaceImageAssetData("asset#2", "test.png");player.render(surface);replaceFontAsset(filePath:string, assetId:string)
标题为“replaceFontAsset(filePath:string, assetId:string)”的章节将 assetId 处的字体资源替换为虚拟文件系统中的字体。
player.replaceFontAsset("font.ttf", "asset#2");player.render(surface);replaceCSVAsset(filePath:string, assetId:string)
标题为“replaceCSVAsset(filePath:string, assetId:string)”的章节将 assetId 处的 CSV 资源替换为给定路径的 CSV。
轻量快速的 Web CSV 解析器与 Cavalry 自带的大型全功能解析器相比功能较为基础。目前不支持自定义分隔符和引号。如果您在使用 CSV 文件时遇到问题,请告知我们。
Module.FS.writeFile("test.csv", csvBytes);player.replaceCSVAsset("text.csv", "asset#2");player.render(surface);replaceCSVAssetData(assetId:string, csvData:string)
标题为“replaceCSVAssetData(assetId:string, csvData:string)”的章节将 assetId 处的 CSV 资源替换为 CSV 数据。
请注意 — 轻量快速的 Web CSV 解析器与 Cavalry 自带的大型全功能解析器相比功能较为基础。目前不支持自定义分隔符和引号。如果您在使用 CSV 文件时遇到问题,请告知我们。
player.replaceCSVAssetData("asset#2", "test.csv");player.render(surface);replaceExcelAsset(filePath:string, assetId:string)
标题为“replaceExcelAsset(filePath:string, assetId:string)”的章节将 assetId 处的 Excel 资源替换为虚拟文件系统中的 Excel 资源。
player.replaceExcelAsset("sheet.xlsx", "asset#2");player.render(surface);replaceExcelAssetData(assetId:string, csvData:string)
标题为“replaceExcelAssetData(assetId:string, csvData:string)”的章节将 assetId 处的 CSV 资源替换为 CSV 数据。
player.replaceExcelAssetData("asset#2", "test.csv");player.render(surface);replaceSVGAsset(filePath:string, assetId:string)
标题为“replaceSVGAsset(filePath:string, assetId:string)”的章节将 assetId 处的 SVG 资源替换为虚拟文件系统中的 SVG 资源。
player.replaceExcelAsset("image.svg", "asset#2");player.render(surface);replaceSVGAssetData(assetId:string, svgData:string)
标题为“replaceSVGAssetData(assetId:string, svgData:string)”的章节将 assetId 处的 SVG 资源替换为 SVG 数据。
player.replaceExcelAssetData("asset#2", "image.svg");player.render(surface);replaceGoogleSheet(sheetUrl:string, assetId:string)
标题为“replaceGoogleSheet(sheetUrl:string, assetId:string)”的章节Google Sheets 会自动加载。使用 replaceGoogleSheet 在运行时替换 Google Sheet 资源。
将 Google Sheet 的共享权限设置为”知道链接的任何人”以避免错误。
const sheetId = "UNIQUE_ID_OF_GOOGLE_SHEET"const url = `https://docs.google.com/spreadsheets/d/${sheetId}`player.replaceGoogleSheet(url, assetId);getActiveComp() → string
标题为“getActiveComp() → string”的章节返回当前活动合成画面的 ID。
const compId = player.getActiveComp();getInConnection(layerId:string, attrId:string) → string
标题为“getInConnection(layerId:string, attrId:string) → string”的章节返回属性的输入连接。如果该属性没有输入,则返回空字符串。
const path = player.getInConnection("basicShape#2", "scale");disconnect(fromLayerId:string, fromAttrId:string, toLayerId:string, toAttrId:string)
标题为“disconnect(fromLayerId:string, fromAttrId:string, toLayerId:string, toAttrId:string)”的章节移除属性之间的连接。当连接被移除时返回空字符串。
player.disconnect("basicShape#2", "position", "basicShape#2", "scale");const path = player.getInConnection("basicShape#2", "scale"); console.log("Connection for Scale Attribute after disconnect is", path);setCompTimeMultiplier(multiplier:number)
标题为“setCompTimeMultiplier(multiplier:number)”的章节设置合成画面的时间倍率。将其设置为 2.0 意味着合成画面将以两倍速度播放。
player.setCompTimeMultiplier(0.5);setActiveComp(compId:string)
标题为“setActiveComp(compId:string)”的章节通过 ID 设置活动合成画面。如果找不到该合成画面,则会记录警告。
setAttributeViaStringify(layerId:string, jsonString:string)
标题为“setAttributeViaStringify(layerId:string, jsonString:string)”的章节通过传递 JSON 字符串来设置图层上的一个或多个属性。
参数:
layerId: 要修改的图层的 ID。jsonString: 将属性名称映射到值的 JSON 字符串。
const color = JSON.stringify({ backgroundColor: { r: 255, g: 0, b: 128 } });player.setAttributeViaStringify(player.getActiveComp(), color);player.render(surface);const playbackStep = JSON.stringify({ playbackStep: 2 });player.setAttributeViaStringify(player.getActiveComp(), playbackStep);setAttribute(layerId:string, attrId:string, value:any)
标题为“setAttribute(layerId:string, attrId:string, value:any)”的章节设置图层上的属性。值类型会自动检测并转换为适当的 C++ 类型。
参数:
layerId: 要修改的图层的 ID。attrId: 要修改的属性。value: 要设置的值。支持的类型:- 数字: 根据属性类型自动检测为
double、int或enum - 字符串: 用于文本和富文本属性
- 布尔值: 用于布尔属性
- 数组:
[x, y]用于 2D 向量,[x, y, z]用于 3D 向量,[r, g, b]或[r, g, b, a]用于颜色 - 对象:
{x, y}或{x, y, z}用于向量,{r, g, b}或{r, g, b, a}用于颜色
- 数字: 根据属性类型自动检测为
// Numbers (auto-detected as double, int, or enum based on attribute type)player.setAttribute("textShape#1", "fontSize", 24.5);player.setAttribute("duplicator#1", "count", 10);player.setAttribute("dropdown#1", "alignment", 2);// Stringsplayer.setAttribute("textShape#1", "text", "Hello World!");// Booleansplayer.setAttribute("shape#1", "visible", true);// 2D vectors (array or object syntax)player.setAttribute("shape#1", "position", [100, 200]);player.setAttribute("shape#1", "scale", {x: 1.5, y: 2.0});// 3D vectors (array or object syntax)player.setAttribute("camera#1", "position", [10, 20, 30]);player.setAttribute("transform#1", "rotation", {x: 0, y: 45, z: 0});// Colours (array or object syntax, values 0-255)player.setAttribute("shape#1", "fillColor", [255, 0, 0, 255]);player.setAttribute("shape#1", "material.materialColor", {r: 128, g: 64, b: 255, a: 255});// Composition background colourplayer.setAttribute(player.getActiveComp(), "backgroundColor", {r: 30, g: 30, b: 30});getAttribute(layerId:string, attrId:string) → any
标题为“getAttribute(layerId:string, attrId:string) → any”的章节从图层获取属性值。返回类型取决于属性类型。
参数:
layerId: 要查询的图层的 ID。attrId: 要获取的属性。
返回值: 属性值。返回类型:
- double/int/enum:
number - bool:
boolean - string/richText:
string - double2/int2:
[x, y]数组 - double3/int3:
[x, y, z]数组 - color:
{r, g, b, a}对象,值范围为 0-255
// Numbersconst fontSize = player.getAttribute("textShape#1", "fontSize");const count = player.getAttribute("duplicator#1", "count");// Stringsconst text = player.getAttribute("textShape#1", "text");// Booleansconst isVisible = player.getAttribute("shape#1", "visible");// 2D vectors (returns array)const position = player.getAttribute("shape#1", "position");console.log(`Position: ${position[0]}, ${position[1]}`);// 3D vectors (returns array)const rotation = player.getAttribute("transform#1", "rotation");console.log(`Rotation: ${rotation[0]}, ${rotation[1]}, ${rotation[2]}`);// Colours (returns object)const color = player.getAttribute("shape#1", "material.materialColor");console.log(`Color: R=${color.r}, G=${color.g}, B=${color.b}, A=${color.a}`);// Convert colour to CSS hexconst hex = "#" + [color.r, color.g, color.b] .map(x => x.toString(16).padStart(2, "0")) .join("");getAttributeViaStringify(layerId:string, attrId:string) → string
标题为“getAttributeViaStringify(layerId:string, attrId:string) → string”的章节将任何属性值作为 JSON 字符串获取。这是没有特定 getter 的复杂属性类型的备用方法。
参数:
layerId: 要查询的图层的 ID。attrId: 要获取的属性。
返回值: 属性值的 JSON 字符串表示。
const jsonString = player.getAttributeViaStringify("basicShape#1", "customData");const data = JSON.parse(jsonString);getAttributeName(layerId:string, attrId:string) → string
标题为“getAttributeName(layerId:string, attrId:string) → string”的章节获取属性名称。可以在 Cavalry 中通过在Attribute Editor中右键点击属性并选择”Rename…”来设置属性名称。
参数:
layerId: 要查询的图层的 ID。attrId: 要获取的属性。
返回值: 属性名称或属性 ID。
const name = player.getAttributeName("basicShape#1", "position");getAttributeDefinition(layerId:string, attrId:string) → AttributeDefinition
标题为“getAttributeDefinition(layerId:string, attrId:string) → AttributeDefinition”的章节获取有关属性的详细元数据,包括其类型、约束、默认值和结构。这对于构建需要了解属性特征的动态用户界面特别有用。
参数:
layerId: 要查询的图层的 ID。attrId: 要检查的属性。
返回值: 包含全面属性元数据的 AttributeDefinition 对象。
// Get definition for a numeric attribute with constraintsconst sizeDef = player.getAttributeDefinition("pixelateFilter#1", "size");console.log(`Type: ${sizeDef.type}`);console.log(`Default: ${sizeDef.defaultValue}`);console.log(`Min: ${sizeDef.numericInfo.hardMin}, Max: ${sizeDef.numericInfo.hardMax}`);// Use definition to create appropriate UIif (sizeDef.type === "int" && sizeDef.numericInfo.hasHardMin && sizeDef.numericInfo.hasHardMax) { // Create slider with proper min/max values const slider = document.createElement("input"); slider.type = "range"; slider.min = sizeDef.numericInfo.hardMin; slider.max = sizeDef.numericInfo.hardMax; slider.value = JSON.parse(sizeDef.defaultValue);}AttributeDefinition 属性:
interface AttributeDefinition { attrId: string; // Attribute id type: string; // Data type (int, double, bool, string, Color, etc.) prefix: string; // UI prefix text (e.g X, or R) placeholder: string; // UI placeholder text for string attributes defaultValue: string; // JSON-serialized default value isArray: boolean; // Whether this is an array attribute isCompound: boolean; // Whether this has child attributes isDynamic: boolean; // Whether this is dynamically created isAttrReadOnly: boolean; // Whether this is read-only allowsConstrainProportions: boolean; // Whether proportions can be constrained multiline: boolean; // Whether text should be multiline numericInfo: NumericInfo; // Numeric constraint information enumValues: number[]; // Enum option values children: AttributeDefinition[]; // Child attribute definitions}interface NumericInfo { hardMin: number; // Hard minimum constraint hardMax: number; // Hard maximum constraint softMin: number; // Soft minimum constraint softMax: number; // Soft maximum constraint step: number; // Step increment hasHardMin: boolean; // Whether hardMin is set hasHardMax: boolean; // Whether hardMax is set hasSoftMin: boolean; // Whether softMin is set hasSoftMax: boolean; // Whether softMax is set hasStep: boolean; // Whether step is set}getControlCentreAttributes(compId:string) → [string]
标题为“getControlCentreAttributes(compId:string) → [string]”的章节获取给定合成画面的所有控制中心属性路径列表。
参数:
compId: 要查询的合成画面的 ID(通常从getActiveComp()获取)。
返回值: 表示属性路径的字符串数组,格式为 "layerId.attributeId"。
// Get active composition and its control centre attributesconst activeComp = player.getActiveComp();const attributes = player.getControlCentreAttributes(activeComp);console.log("Control Centre Attributes:", attributes);// Output example: [ "pixelateFilter#1.size", "pixelateFilter#1.outline", "pixelateFilter#1.borderColor" ]// Generate dynamic UI for each Attributeattributes.forEach((attrPath) => { const [layerId, attrId] = attrPath.split("."); // Get attribute definition to determine UI type const definition = player.getAttributeDefinition(layerId, attrId); // Create appropriate control based on type switch (definition.type) { case "int": // Create integer input or slider break; case "bool": // Create checkbox break; case "color": // Create color picker break; // ... handle other types }});getFontAxisInfo(fontName:string, fontStyle:string) → FontAxisInfo
标题为“getFontAxisInfo(fontName:string, fontStyle:string) → FontAxisInfo”的章节获取给定字体的可变字体轴信息。可变字体包含可调节的轴(如宽度、字重、倾斜度),允许对字体的外观进行精细控制。此方法返回有关每个可用轴的元数据,包括其名称、标签、范围和当前值。
参数:
fontName: 字体族名称(例如 “Roboto”、“Inter”)。fontStyle: 字体样式(例如 “Regular”、“Bold”)。
返回值: FontAxisInfo 对象数组,每个可变字体轴对应一个。如果字体不是可变字体或没有轴,则返回空数组。
// Query available fonts and their stylesconst fonts = player.queryFonts();const styles = player.getFontStyles(fonts[0]);// Get axis information for a variable fontconst axisInfo = player.getFontAxisInfo("Roboto Flex", "Regular");if (!axesInfo.length) { console.warn("This font has no variable axes (not a variable font)");} else { console.log(`Font has ${axisInfo.length} variable axes`); console.log(axisInfo) // Create sliders for each axis axisInfo.forEach(axis => { const slider = document.createElement("input"); slider.type = "range"; slider.min = axis.minValue; slider.max = axis.maxValue; slider.value = axis.currentValue; slider.addEventListener("input", (e) => { // Update font axis value when slider changes console.log(`${axis.axisName} set to ${e.target.value}`); }) })}fetchAndProcess(zipUrl:string) → Promise<void>
标题为“fetchAndProcess(zipUrl:string) → Promise<void>”的章节获取并处理 ZIP 文件,解压其内容并在播放器中创建图像序列资源。
参数:
zipUrl(字符串): 要获取和处理的 ZIP 文件的 URL。
await player.fetchAndProcess("/url/to/ImageSequence.zip");播放控制
标题为“播放控制”的章节Web Player 提供了一个原生播放管理器来处理帧计时和渲染。
播放模式
标题为“播放模式”的章节提供两种播放模式:
- 精确模式(默认): 基于墙上时钟的计时,跳过帧以保持正确的时序。最适合音频同步和实时播放。
- 平滑模式: 按顺序显示每一帧,但在复杂场景上可能比实时播放慢。最适合逐帧精确预览。
基本播放模式
标题为“基本播放模式”的章节// Start playbackplayer.play();// Run the playback loopfunction runPlaybackLoop() { const tick = (timestamp) => { if (!player.isPlaying()) { return; } const status = player.tick(surface, timestamp); // Update UI if needed if (status.frameChanged) { console.log(status) } requestAnimationFrame(tick); }; requestAnimationFrame(tick);}runPlaybackLoop();// Stop playbackplayer.stop();play()
标题为“play()”的章节从当前帧位置开始播放。在启动 requestAnimationFrame 循环之前调用此方法。
player.play();runPlaybackLoop();stop()
标题为“stop()”的章节停止播放。当前帧位置被保留,因此再次调用 play() 将从停止的位置恢复播放。
player.stop();cancelAnimationFrame(animationFrameId);toggle()
标题为“toggle()”的章节切换播放状态。如果正在播放,则停止播放。如果已停止,则开始播放。
player.toggle();console.log(player.isPlaying() ? "Stop" : "Play");isPlaying() → boolean
标题为“isPlaying() → boolean”的章节返回当前是否正在播放。
console.log("Playback is running?", player.isPlaying());tick(surface:SkSurface, timestampMs:number) → PlaybackStatus
标题为“tick(surface:SkSurface, timestampMs:number) → PlaybackStatus”的章节根据经过的时间推进播放并渲染当前帧。这应该从 requestAnimationFrame 回调中调用。
参数:
surface: 要渲染到的 Skia 表面。timestampMs: 来自requestAnimationFrame的时间戳(DOMHighResTimeStamp)。
返回值: 包含当前播放状态的 PlaybackStatus 对象。
const tick = (timestamp) => { if (!player.isPlaying()) { return; } const status = player.tick(surface, timestamp); if (status.frameChanged) { console.log("Frame", status.currentFrame); } requestAnimationFrame(tick);}setFrame(frame:number)
标题为“setFrame(frame:number)”的章节设置当前帧。帧号会被限制在有效的播放范围内(getStartFrame() 和 getEndFrame() 之间)。
注意: 如果正在播放,请在设置帧之前停止播放,然后可以选择重新启动。
// Seek to a specific frameconst wasPlaying = player.isPlaying();player.stop();player.setFrame(50);player.render(surface);if (wasPlaying) { player.play(); runPlaybackLoop();}getFrame() → number
标题为“getFrame() → number”的章节返回当前帧号。
const currentFrame = player.getFrame();console.log("Current frame", currentFrame);getStartFrame() → number
标题为“getStartFrame() → number”的章节返回当前合成画面播放范围的起始帧。
const startFrame = player.getStartFrame();console.log(startFrame);getEndFrame() → number
标题为“getEndFrame() → number”的章节返回当前合成画面播放范围的结束帧。
const endFrame = player.getEndFrame();console.log(endFrame);getFPS() → number
标题为“getFPS() → number”的章节返回当前合成画面的每秒帧数(FPS)。
const fps = player.getFPS();console.log(`Scene runs at ${fps} FPS`);getActualFPS() → number
标题为“getActualFPS() → number”的章节返回活动播放期间测量的播放 FPS。这是在 1 秒窗口内计算的,可用于监控播放性能。
const actualFps = player.getActualFPS();console.log(`${actualFps.toFixed(1)} FPS`);setPlaybackMode(mode:number)
标题为“setPlaybackMode(mode:number)”的章节设置播放模式。
参数:
mode:0为精确模式(默认),1为平滑模式。
// Use Smooth mode for frame-accurate previewplayer.setPlaybackMode(1);// Use Accurate mode for real-time playbackplayer.setPlaybackMode(0);getPlaybackMode() → number
标题为“getPlaybackMode() → number”的章节返回当前播放模式。0 = 精确模式,1 = 平滑模式。
const mode = player.getPlaybackMode();console.log(mode === 0 ? "Accurate" : "Smooth");setLoop(loop:boolean)
标题为“setLoop(loop:boolean)”的章节设置播放到达结束帧时是否应循环播放。
参数:
loop:true启用循环(默认),false在结束时停止。
player.setLoop(false);isLooping() → boolean
标题为“isLooping() → boolean”的章节返回是否启用了播放循环。
console.log("Playback will loop?", player.isLooping());incrementFrame()
标题为“incrementFrame()”的章节手动推进到下一帧。这是一个用于手动帧控制的低级方法 — 对于大多数用例,请使用 tick() 代替,它会自动处理计时。
player.incrementFrame();player.render(surface);数据类型
标题为“数据类型”的章节Resolution
标题为“Resolution”的章节interface Resolution { width: number; height: number;}表示场景或画布分辨率。
ColorRGBA
标题为“ColorRGBA”的章节interface ColorRGBA { r: number; // Red component (0-255) g: number; // Green component (0-255) b: number; // Blue component (0-255) a: number; // Alpha component (0-255)}表示 RGBA 颜色值,各分量范围为 0-255。通过 getAttribute() 获取颜色属性时返回。
const color = player.getAttribute("shape#1", "fillColor");// Convert to CSS hex colourconst hex = "#" + [color.r, color.g, color.b] .map(x => x.toString(16).padStart(2, "0")) .join("");// Set a colour using an objectplayer.setAttribute("shape#1", "fillColor", {r: 255, g: 128, b: 0, a: 255});Double2
标题为“Double2”的章节interface Double2 { x: number; // X component y: number; // Y component}表示双精度 2D 向量。请注意,getAttribute() 为了方便,将 2D 向量作为数组 [x, y] 返回。
// getAttribute returns an array [x, y]const position = player.getAttribute("shape#1", "position");console.log(`Position: (${position[0]}, ${position[1]})`);// Set a new position using array syntaxplayer.setAttribute("shape#1", "position", [position[0] + 10, position[1] + 20]);// Or use object syntaxplayer.setAttribute("shape#1", "position", {x: 100, y: 200});Double3
标题为“Double3”的章节interface Double3 { x: number; // X component y: number; // Y component z: number; // Z component}表示双精度 3D 向量。请注意,getAttribute() 为了方便,将 3D 向量作为数组 [x, y, z] 返回。
// getAttribute returns an array [x, y, z]const cameraPos = player.getAttribute("camera#1", "position");console.log(`Camera at: (${cameraPos[0]}, ${cameraPos[1]}, ${cameraPos[2]})`);// Set camera position using array syntaxplayer.setAttribute("camera#1", "position", [ cameraPos[0], cameraPos[1], cameraPos[2] + 100]);// Or use object syntaxplayer.setAttribute("camera#1", "position", { x: 10, y: 20, z: 30 });FontMetadata
标题为“FontMetadata”的章节interface FontMetadata { registeredFamily: string; // The name used for registration (use with getFontStyles) registeredStyle: string; // Default style name fullName: string; // Full font name from the font file preferredFamily: string; // Preferred family name (may differ from registeredFamily) preferredSubfamily: string; // Preferred subfamily name postscriptName: string; // PostScript name wwsFamilyName: string; // WWS (Weight Width Slope) family name styles: FontStyleInfo[]; // All available styles with CSS-compatible values}包含字体元数据,包括字体名称表中的名称变体和所有可用样式。由 getFontMetadata() 返回。
const metadata = Module.getFontMetadata("Inter");console.log(metadata.registeredFamily); // "Inter"console.log(metadata.styles); // Array of FontStyleInfo objectsFontStyleInfo
标题为“FontStyleInfo”的章节interface FontStyleInfo { name: string; // Style name (e.g., "Regular", "Bold Italic") weight: number; // CSS font-weight: 100-900 (400=normal, 700=bold) slant: string; // CSS font-style: "normal", "italic", or "oblique" width: number; // CSS font-stretch: 1-9 (5=normal, 3=condensed, 7=expanded)}表示具有 CSS 兼容值的字体样式。使用这些值将 Cavalry 字体样式映射到 CSS 属性。
const metadata = Module.getFontMetadata("Roboto");metadata.styles.forEach(style => { // Create CSS font-face rules or match styles console.log(`@font-face { font-family: "${style.name}"; font-weight: ${style.weight}; font-style: ${style.slant}; }`);});FontAxisInfo
标题为“FontAxisInfo”的章节interface FontAxisInfo { axisName: string; // Human-readable name (e.g., "Weight", "Width") axisTag: string; // Four-character axis tag (e.g., "wght", "wdth") minValue: number; // Minimum allowed value for this axis maxValue: number; // Maximum allowed value for this axis defaultValue: number; // Default value for this axis currentValue: number; // Current value for this axis}表示可变字体轴的信息。可变字体可以有多个轴,允许对字体的外观进行精细控制。常见的轴包括字重(wght)、宽度(wdth)、倾斜度(slnt)和光学尺寸(opsz)。
// Get variable font axis informationconst axisInfo = player.getFontAxisInfo("Inter", "Regular");console.log(axisInfo)PlaybackStatus
标题为“PlaybackStatus”的章节interface PlaybackStatus { isPlaying: boolean; // Whether playback is currently active currentFrame: number; // The current frame number frameChanged: boolean; // Whether the frame changed since the last tick actualFPS: number; // Measured playback FPS}由 tick() 返回,提供有关当前播放状态的信息。使用 frameChanged 来确定何时更新 UI 元素。
const status = player.tick(surface, timestamp);if (status.frameChanged) { console.log(status.currentFrame); console.log("Frame", status.currentFrame);}if (status.actualFPS > 0) { console.log(`${status.actualFPS.toFixed(1)} FPS`)}PlaybackMode
标题为“PlaybackMode”的章节enum PlaybackMode { Accurate = 0, // Wall-clock based, skips frames to maintain timing Smooth = 1 // Shows every frame, may run slow}定义播放管理器如何推进帧:
- 精确模式(0): 根据经过的墙上时钟时间计算正确的帧。如果渲染较慢,将跳过帧以保持正确的时序。最适合音频同步。
- 平滑模式(1): 当足够的时间过去时,一次精确推进一帧。从不跳帧,但在复杂场景上播放可能比实时更慢。
// Check current modeconsole.log(player.getPlaybackMode());// Set to Smooth mode for frame-by-frame previewplayer.setPlaybackMode(1);// Set to Accurate mode for real-time playbackplayer.setPlaybackMode(0);Cavalry Web Player 会自动为某些与资源相关的操作派发事件。这些事件允许自动加载资源和其他集成场景。
cavalryAutoLoadAsset
标题为“cavalryAutoLoadAsset”的章节事件类型: CustomEvent
当 Cavalry Web Player 检测到场景文件中需要从 Web 服务器加载到虚拟文件系统的图像或字体资源时,自动派发。
事件详情:
interface CavalryAutoLoadAssetDetail { type: "image" | "font" | "csv" | "svg" | "excel" | "googlesheet"; assetId: string; // Asset ID from the scene file (e.g., "asset#2") filename: string; // Extracted filename (e.g., "3.jpg" or "font.ttf")}// Listen for automatic asset loading eventswindow.addEventListener("cavalryAutoLoadAsset", async (event) => { console.log("Auto-loading asset:", event.detail); const path = "/path/to/assets/" if (event.detail.type === "image") { const { assetId, filename } = event.detail; const response = await fetch(path + filename); if (!response.ok) { throw new Error(`Could not find image ${filename}`); } const imageData = await response.arrayBuffer(); // Write to virtual filesystem Module.FS.writeFile(filename, new Uint8Array(imageData)); // Load the asset player.replaceImageAsset(filename, assetId); // Re-render to show the image player.render(surface); console.log(`Auto-loaded image: ${filename} for ${assetId}`); } if (event.detail.type === "font") { const { assetId, filename } = event.detail; const response = await fetch(path + filename); if (!response.ok) { throw new Error(`Could not find font ${filename}`); } const fontData = await response.arrayBuffer(); // Write to virtual filesystem Module.FS.writeFile(filename, new Uint8Array(fontData)); // Load the font into the font registry Module.loadFont(filename); // Re-render to show font changes player.render(surface); console.log(`Auto-loaded font: ${filename} for ${assetId}`); }});工作原理:
- 当加载 Cavalry 场景(.cv 文件)时,Web Player 会扫描资源定义
- 具有
"type": "file"和受支持扩展名的文件资源会触发此事件:- 图像文件: png、jpg、jpeg、exr、webp
- 字体文件: ttf、otf、woff、woff2
- 事件提供资源 ID 和提取的文件名供应用程序处理
- 事件处理程序应从 Web 服务器获取文件并将其写入虚拟文件系统
- 然后调用适当的 API 加载资源:
- 图像:
replaceImageAsset(filename, assetId) - 字体:
Module.loadFont(filename)
- 图像:
自动加载:
- Google Sheets 资源 会自动处理,不需要此事件
- 图像和字体资源 需要 JavaScript 处理,因为它们需要从 Web 服务器获取
文件路径解析: Web Player 从 Cavalry 的内部路径中提取文件名,例如:
"@assets/image.jpg"→"image.jpg""@assets/Changa_One/ChangaOne-Regular.ttf"→"ChangaOne-Regular.ttf""subfolder/font.ttf"→"font.ttf"
事件处理程序应尝试公共位置,如根目录(./filename)和 Assets 文件夹(./Assets/filename)。
示例实现:
- 图像加载: 请参见
src/wasm/cavalry-web-player/image-asset/中的图像资源演示 - 字体加载: 请参见
src/wasm/cavalry-web-player/custom-font/中的自定义字体演示