
Zoom Apps SDK Layers API 完全指南用沉浸式布局与摄像头叠加打造自定义视频体验【免费下载链接】knowledge-work-pluginsOpen source repository of plugins primarily intended for knowledge workers to use in Claude Cowork项目地址: https://gitcode.com/GitHub_Trending/kn/knowledge-work-plugins导读本文以 layers-api.md 为核心系统讲解 Zoom Apps SDKv0.16zoom/appssdk中 Layers API 的四种渲染模式Team / Presentation / Camera / Controller、绘制方法、生命周期管理与坐标系规则并结合本仓库中 zoom-apps-sdk 技能包的示例文档与排障经验进行源码级佐证。读完本文你将能够独立实现双人播客布局式的沉浸式会议画面、为摄像头叠加品牌边框与名牌并绕开 Camera Mode 的 CEF 初始化竞态条件等真实开发坑点。一、Layers API 是什么Layers APIv1.5是 Zoom Apps SDK 提供的渲染能力让运行在 Zoom 客户端内的 Web 应用可以接管会议视频区域或用户自身摄像头画面绘制自定义视觉内容。它要求Zoom Client v5.10.6更高版本的客户端特性差异见文末版本历史表。在 SKILL.md 的集成索引中Layers API 被明确归类为沉浸式视频布局与摄像头模式叠加两大场景的核心接口。Layers API 提供四种渲染模式模式说明典型场景Teamimmersiveperson抠像在画布上摆放去除背景的参会者剪影播客、脱口秀、课堂Presentationimmersiverectangle抠像在画布上摆放保留背景的全宽参会者视频块演示会、品牌化会议Camera叠加在用户自身摄像头画面OSR 离屏渲染上品牌元素、名牌、特效Controller负责协调上述各模式的侧边栏应用上述所有模式的前提注意使用 Layers API 的应用在 Marketplace 上会被归类为Immersive App。场景定位在 Zoom Apps 的运行时上下文体系中Layers 模式对应inImmersive沉浸式全屏自定义渲染与inCamera虚拟摄像头叠加两种上下文详见 running-contexts.md 中的上下文矩阵表。二、能力声明config 中的 Required Capabilities使用任何 Layers API 方法前必须先在zoomSdk.config()中声明对应能力。config()是 SDK 初始化的唯一入口且必须在调用其他任何 SDK 方法之前执行——这是 SKILL.md 中反复强调的硬性规则Only capabilities listed inconfig()are available未声明的能力调用会直接抛错。await zoomSdk.config({ capabilities: [ getRunningContext, runRenderingContext, closeRenderingContext, drawParticipant, clearParticipant, drawImage, clearImage, drawWebView, clearWebView, postMessage, onMessage, sendAppInvitationToAllParticipants, onMyMediaChange, onRenderedAppOpened ], version: 0.16 });能力清单解析runRenderingContext/closeRenderingContext启动 / 关闭渲染上下文Layers 模式的核心开关drawParticipant/clearParticipant绘制 / 清除参会者视频drawImage/clearImage绘制 / 清除静态图像drawWebView/clearWebView绘制 / 清除应用自身的离屏 WebViewgetRunningContext查询当前运行上下文判断当前处于哪种模式postMessage/onMessage同一应用的多个实例侧边栏 ↔ 摄像头/沉浸式之间通信sendAppInvitationToAllParticipants主持人在过渡到沉浸式时向全体参会者发送应用邀请onMyMediaChange监听用户视频流变化切摄像头、原比例、HD 开关onRenderedAppOpened渲染上下文就绪事件Camera Mode 下 CEF 初始化完成的关键信号。Gotcha真实踩坑官方指南有一处示例把clearWebView写成了clearWebview小写 v。请务必使用驼峰命名clearWebView以匹配 SDK 中实际的方法名。补充能力与 OAuth Scope 的对应关系根据 SKILL.md 的OAuth Scopes (Required)表runRenderingContext等能力需要对应的zoomapp:inmeetingscope 在 Marketplace 中启用。缺失 scope 时能力会静默失败或抛错且新增 scope 后用户需要重新授权。这是排查Layers API 调用无响应时的首要检查项与 common-issues.md 中Collaborate/Layers APIs missing → 检查 Host 权限与客户端版本、确认 Marketplace 特性已启用的诊断结论一致。三、核心类型系统3.1 PixelValue三种坐标格式所有位置/尺寸参数都接受三种格式统一由PixelValue类型表示type PixelValue ${string}px | ${string}% | number;格式示例含义Npx100pxCSS 参考像素N%50%容器/视图的百分比number1280原始物理像素实战提示Npx与number在大多数场景下数值相同但语义不同——字符串是 CSS 像素数字是物理像素。在 Camera Mode 中坐标系是原始物理像素相对renderTarget在 Immersive Mode 中是 CSS 像素自动随窗口缩放混用时要注意换算详见坐标系章节。3.2 ParticipantCutoutShape抠像形状type ParticipantCutoutShape | person // v5.9.3 — AI 分割去除背景 | standard // v5.11.3 — 完整未裁剪视频直角 | rectangle // v5.11.0 — 圆角矩形30px 圆角 | circle // v5.11.3 — 圆形 | square // v5.11.3 — 正方形30px 圆角 | verticalRectangle // v5.11.3 — 竖向矩形30px 圆角所有形状均带30px 圆角唯一例外是standard直角。各形状对客户端版本有明确要求见版本历史表在生产环境中应先通过getSupportedJsApis()或configResponse.unsupportedApis做能力探测再降级这与 running-contexts.md 推荐的运行时检查 API 可用性模式一致。3.3 RenderingContextView渲染上下文视图type RenderingContextView immersive | camera;只有两种视图类型immersive接管整个会议视频区与camera叠加到用户摄像头。四、生命周期管理4.1 启动渲染上下文runRenderingContext(options)// Team 模式person 抠像 — AI 去除背景 await zoomSdk.runRenderingContext({ view: immersive, defaultCutout: person }); // Presentation 模式rectangle 抠像 — 保留背景 await zoomSdk.runRenderingContext({ view: immersive, defaultCutout: rectangle }); // Camera 模式只影响自己的视频流 await zoomSdk.runRenderingContext({ view: camera });参数说明view必填immersive或cameradefaultCutout可选设置当前上下文内所有drawParticipant()调用的默认抠像形状。配套说明runRenderingContext需要zoomapp:inmeetingOAuth scope且仅会议宿主host能执行沉浸式切换。非宿主调用会失败——这是由 Zoom 客户端侧强制约束的见下文约束。4.2 运行上下文值上下文含义inMeeting默认侧边栏面板inImmersive运行在沉浸式模式Team 或 PresentationinCamera以虚拟摄像头运行离屏渲染 OSRconst { runningContext } await zoomSdk.getRunningContext(); // 调用 runRenderingContext() 后 runningContext 会自动变化4.3 更新内容先清除再重绘Layers API没有就地更新in-place update机制。要移动、缩放或调整已绘制元素必须先 clear 再重新 draw// 移动一名参会者 await zoomSdk.clearParticipant({ participantUUID: uuid }); await zoomSdk.drawParticipant({ participantUUID: uuid, x: 100, y: 200, width: 640, height: 480, zIndex: 1 });这一设计意味着高频布局更新如窗口 resize会产生 cleardraw 两次调用因此 SKILL.md 与示例文档都建议批量更新、最小化 draw 调用。4.4 关闭渲染上下文closeRenderingContext()await zoomSdk.closeRenderingContext(); // 应用回到侧边栏runningContext 变回 inMeeting4.5 约束Constraints只有会议宿主host能将会话设置为沉浸式渲染同一时刻只能存在一个沉浸式上下文第二次尝试会报错Camera 模式与 Presentation 模式可以同时运行宿主必须使用sendAppInvitationToAllParticipants让其他参会者过渡到沉浸式布局若aomhost包需要下载runRenderingContext会返回非成功状态——这意味着首次进入沉浸式可能伴随下载延迟应用应处理该非成功返回值并提示用户等待。五、绘制方法详解5.1 drawParticipant摆放参会者视频drawParticipant(options: DrawParticipantOptions): PromiseGeneralMessageResponse参数类型默认值说明participantUUIDstring—会议内唯一的参会者标识participantIdstring—已废弃DEPRECATED— 请改用participantUUIDxPixelValue0px水平位置yPixelValue0px垂直位置widthPixelValue100%宽度保持宽高比heightPixelValue100%高度保持宽高比zIndexnumber1层叠顺序越大越靠上cutoutParticipantCutoutShape上下文默认值抠像行为v5.9.3cameraModeMirroringbooleanfalse在 Camera 模式下镜像视频v5.13.5模式差异Immersive可以绘制任意参会者Camera只能绘制当前用户自己。// Immersive — 用 person 抠像绘制任意参会者 await zoomSdk.drawParticipant({ participantUUID: uuid-from-getMeetingParticipants, x: 40, y: 100, width: 580, height: 500, zIndex: 1, cutout: person }); // Camera — 绘制自己并开启镜像 await zoomSdk.drawParticipant({ participantUUID: myUUID, x: 0, y: 0, width: 1280, height: 720, zIndex: 1, cameraModeMirroring: true // v5.13.5 });从源码结构看实现要点在 layers-immersive.md 的完整示例中participantUUID来自getMeetingParticipants()返回的participants[i].participantUUID且建议监听onParticipantChange事件在参会者变化时重新布局——这印证了drawParticipant的输入依赖运行时参会者数据任何基于硬编码 ID 的布局都应在人数变化后重绘。5.2 drawImage绘制静态图像drawImage(options: DrawImageOptions): PromiseDrawImageResponse参数类型默认值说明imageDataImageData—必填。标准 JS ImageData 对象含 width、height、像素字节xPixelValue0px水平位置yPixelValue0px垂直位置zIndexnumber1层叠顺序返回值{ imageId: string }—— 后续用该 ID 调用clearImage()。重要imageData必须是标准 JavaScriptImageData对象来自canvas.getImageData()不是 base64 data URL。这是 layers-camera.md 中特别强调的易错点——尽管该示例的 retry 代码块里有一处以toDataURL()传参的反例写法但官方类型定义与 layers-api.md 均要求ImageData请以 TypeDoc 类型定义为准。const canvas document.createElement(canvas); canvas.width 1280; canvas.height 720; const ctx canvas.getContext(2d); // ... 在 canvas 上绘制 ... const imageData ctx.getImageData(0, 0, canvas.width, canvas.height); const { imageId } await zoomSdk.drawImage({ imageData, x: 0, y: 0, zIndex: 0 });HiDPI / Retina 约束drawImage()不直接支持HiDPI 图像尺寸。处理 Retina 屏的标准做法用缩放比window.devicePixelRatio把内容绘制到 canvas 上传给drawImage的 x/y 坐标要除以缩放比width / height 保持乘以缩放比全屏大图可能需要分块tile平铺绘制。const dpr window.devicePixelRatio || 1; const canvas document.createElement(canvas); canvas.width 1280 * dpr; canvas.height 720 * dpr; const ctx canvas.getContext(2d); ctx.scale(dpr, dpr); // ... 按逻辑像素绘制 ... const imageData ctx.getImageData(0, 0, canvas.width, canvas.height); await zoomSdk.drawImage({ imageData, x: 0, y: 0, zIndex: 0 });延伸layers-immersive.md 的 HiDPI 章节对drawParticipant给出了相反的处理——坐标与尺寸都要乘以dpr。可见 ImmersiveCSS 像素与 Camera物理像素两种坐标系下 HiDPI 换算策略不同务必区分对待。5.3 drawWebView绘制离屏 WebView将应用的 OSROff-Screen RenderingWebView 定位到 Layers 画布中。drawWebView(options: DrawWebViewOptions): PromiseGeneralMessageResponse参数类型默认值说明xPixelValue0OSR 目标区域的水平位置yPixelValue0OSR 目标区域的垂直位置widthPixelValue完整渲染宽度OSR 目标区域宽度heightPixelValue完整渲染高度OSR 目标区域高度zIndexnumber1层叠顺序⚠ 文档不一致警告官方 Zoom 指南的示例中出现了webviewId参数但 TypeDoc 类型定义v0.16.36中并不包含它。由于每个应用每个渲染上下文只有一个 WebView该参数很可能是历史遗留vestigial。拿不准时就省略它。WebView 渲染什么应用在zoomSdk.config()中配置的 Home URL 的离屏渲染——不是可配置的其他 URL。每个渲染上下文仅支持一个 WebView没有多 WebView 支持。// Camera 模式下的全屏 WebView const config await zoomSdk.config({ /* ... */ }); await zoomSdk.runRenderingContext({ view: camera }); await zoomSdk.drawWebView({ x: 0, y: 0, width: config.media.renderTarget.width, // 默认: 1280 height: config.media.renderTarget.height, // 默认: 720 zIndex: 2 });// 局部 WebView 叠加摄像头底部三分之一 await zoomSdk.drawWebView({ x: 0, y: 480, width: 1280, height: 240, zIndex: 2 });WebView 通信侧边栏 ↔ 摄像头/沉浸式侧边栏应用与摄像头/沉浸式应用是两个独立实例。用postMessage()与onMessage在它们之间通信// 侧边栏实例 → 摄像头实例无需 connect() zoomSdk.postMessage({ command: update-overlay, text: QA Time }); // 摄像头实例监听 zoomSdk.addEventListener(onMessage, (eventInfo) { if (eventInfo.command update-overlay) { document.getElementById(overlay-text).textContent eventInfo.text; } });注意Layers 场景下应用间消息传递不需要connect()。postMessage可以直接在同一应用的多个实例之间工作。这一点与 running-contexts.md 中描述的通用多实例通信模式先connect()再postMessage()不同——Layers 场景属于特例layers-camera.md 中同样注明no connect() required。5.4 清除方法// 清除参会者用 participantUUID不要用已废弃的 participantId await zoomSdk.clearParticipant({ participantUUID: uuid }); // 清除图像用 drawImage 返回的 imageId await zoomSdk.clearImage({ imageId: id-from-drawImage }); // 清除 WebView隐藏它 — 应用继续运行 await zoomSdk.clearWebView(); // 注意: TypeDoc v0.16.36 显示无参数。 // 指南示例中的 { webviewId: xxx } 可能已过时。六、坐标系原点左上角 (0, 0)X 轴向右递增Y 轴向下递增单位PixelValue — 支持Npx、N%或原始number。6.1 Immersive 模式坐标是相对会议画布的 CSS 像素窗口大小变化时自动缩放因此需要监听窗口 resize 并重排。6.2 Camera 模式坐标是相对renderTarget尺寸的原始像素默认 renderTarget1280×720可配置通过config.media.renderTarget.width/.height访问const config await zoomSdk.config({ /* ... */ }); const rtWidth config.media.renderTarget.width; // 例如 1280 const rtHeight config.media.renderTarget.height; // 例如 720实战要点Camera 模式下不要硬编码 1280×720应始终从config.media.renderTarget读取因为客户端版本或用户设置可能改变该值。layers-camera.md 的快速入门代码使用了config.media?.renderTarget?.width || 1280的安全兜底写法值得在生产中沿用。七、Z-Index 层叠规则zIndex: 2 ─ WebView、交互式叠加层最顶层 zIndex: 1 ─ 参会者视频 zIndex: 0 ─ 背景图像最底层zIndex 值越大越靠上渲染。参会者、图像、WebView 三种元素共享同一个 z-index 空间可以互相叠加。建议将 zIndex 控制在 0–10 的低区间内并清除不再使用的元素以释放资源。八、事件监听8.1 onRenderedAppOpened渲染上下文就绪当渲染上下文就绪时触发是 Camera 模式下 CEF 已初始化的最佳信号zoomSdk.addEventListener(onRenderedAppOpened, () { // 此时可以安全调用 drawParticipant、drawImage、drawWebView });8.2 onMyMediaChange用户视频流变化当用户视频发生变化切换摄像头、原始比例开关、HD 开关时触发返回源视频的设备像素尺寸zoomSdk.addEventListener(onMyMediaChange, (event) { // event.media.video.width / height — 源视频的设备像素 // 如需要重绘布局 });8.3 窗口 resize仅 ImmersiveZoom 会议窗口被缩放时应用必须移动并缩放参会者/图像。此问题与 Camera 模式无关renderTarget 固定。九、Immersive 模式 vs Camera 模式对比方面ImmersiveCamera作用范围整个会议视图仅用户摄像头drawParticipant任意参会者仅自己drawImage支持支持drawWebView支持支持谁看到所有参会者所有人在该用户视频流上看到浏览器内核标准 WebViewCEFChromium Embedded Framework渲染方式屏幕上渲染离屏渲染OSR坐标系CSS 像素原始像素renderTarget并行性同一时刻只有一个 Immersive可与 Presentation 模式同时运行十、Camera 模式的 CEF 竞态条件关键坑点Camera 模式使用 CEF初始化需要时间。过早调用绘制方法可能失败且常为静默失败。这是本仓库多份文档layers-api.md、layers-camera.md、common-issues.md、SKILL.md一致强调的头号 Camera 模式问题。最佳方案监听onRenderedAppOpenedzoomSdk.addEventListener(onRenderedAppOpened, async () { await zoomSdk.drawWebView({ x: 0, y: 0, width: 1280, height: 720, zIndex: 2 }); });兜底方案指数退避重试async function drawWithRetry(drawFn, maxRetries 5) { for (let i 0; i maxRetries; i) { try { await drawFn(); return; } catch (error) { if (i maxRetries - 1) throw error; await new Promise(r setTimeout(r, 200 * Math.pow(2, i))); } } }退避序列为 200ms、400ms、800ms、1600ms、3200ms。替代方案检查运行上下文const { runningContext } await zoomSdk.getRunningContext(); if (runningContext inCamera) { // CEF 已就绪可以安全绘制 }排障关联common-issues.md 的诊断表中将 drawImage fails in camera mode 直接归因于 CEF not ready建议采用指数退避重试并引用 camera-mode 示例。这与本节的三种解决方案相互印证。十一、性能优化建议动画使用requestAnimationFrame最小化绘制调用尽可能批量更新将复杂背景预渲染成单个 canvas 的 ImageData 再提交zIndex 控制在低区间0–10清除不再使用的元素以释放资源在低端硬件上实测全屏大图需要分块平铺HiDPI 限制。原理层面预渲染成单张 ImageData与批量更新本质上是把多次昂贵的getImageData/SDK 桥接调用合并为一次规避了先 clear 再 redraw生命周期带来的双重开销这与文档中没有就地更新的设计强相关。十二、完整示例双人播客布局结合背景、主持人左与嘉宾右的 person 抠像构成标准的 Team 模式播客画面// 背景 const canvas document.createElement(canvas); canvas.width 1280; canvas.height 720; const ctx canvas.getContext(2d); const gradient ctx.createLinearGradient(0, 0, 1280, 720); gradient.addColorStop(0, #1a1a2e); gradient.addColorStop(1, #16213e); ctx.fillStyle gradient; ctx.fillRect(0, 0, 1280, 720); const imageData ctx.getImageData(0, 0, 1280, 720); await zoomSdk.drawImage({ imageData, x: 0, y: 0, zIndex: 0 }); // 主持人左— person 抠像去除背景 await zoomSdk.drawParticipant({ participantUUID: hostUUID, x: 40, y: 100, width: 580, height: 500, zIndex: 1, cutout: person }); // 嘉宾右 await zoomSdk.drawParticipant({ participantUUID: guestUUID, x: 660, y: 100, width: 580, height: 500, zIndex: 1, cutout: person });进阶layers-immersive.md 提供了完整的PodcastLayout类实现包含背景绘制、onParticipantChange动态重排1 人居中 / 2 人并排与多参会者 Socket.io 布局同步模式——当参会者数量变化时宿主通过后端广播布局变更全体参会者按同一套坐标重绘这是多端视觉一致性的推荐实现路径。十三、版本历史特性客户端版本SDK 版本核心 Layers API5.9.00.16cutout: person5.9.30.16cutout: rectangle5.11.00.16cutout: circle、square、verticalRectangle5.11.30.16drawWebView()/clearWebView()5.10.60.16.11Camera 模式5.13.10.16cameraModeMirroring5.13.50.16版本说明注意config({ version: 0.16 })中的version是API 版本号而非 NPM 包版本。关于包版本管理migration.md 建议当前使用zoom/appssdkv0.16.26最新稳定版并给出了精确锁版0.16.26生产关键稳定性、补丁锁版~0.16.26多数应用、次版本范围^0.16.26活跃开发三种策略。由于 SKILL.md 明确指出官方 Layers 示例仓库 zoomapps-customlayout-js 停留在^0.16.8已过时Layers API may differ升级到 0.16.26 后务必回归验证绘制类方法的行为差异。十四、资源导航以下仓库内文档可继续深入Immersive 完整示例examples/layers-immersive.md含 PodcastLayout 类、Socket.io 多端同步、HiDPI 处理Camera 完整示例examples/layers-camera.md含品牌边框、名牌叠加、WebView 通信、CEF 重试能力与 Scope 对照SKILL.mdconfig 规则、OAuth scopes、常见坑点清单上下文体系concepts/running-contexts.mdinImmersive/inCamera上下文行为与多实例通信排障速查troubleshooting/common-issues.mdLayers API 缺失、drawImage 失败的诊断版本迁移troubleshooting/migration.md0.16.x 包版本策略与兼容性SDK 总览RUNBOOK.md开发前的预检清单【免费下载链接】knowledge-work-pluginsOpen source repository of plugins primarily intended for knowledge workers to use in Claude Cowork项目地址: https://gitcode.com/GitHub_Trending/kn/knowledge-work-plugins创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考