/** * API 调用执行器,提供成功和失败的回调。 * * @public */ export declare interface AgoraApiExecutor { onSuccess: (result: T) => void; onError: (err: Error) => void; } /** * 音频插件基类。 * * 主线程上的 AudioExtension 只承载控制面状态和生命周期。实时音频处理放在 * extensionDefinition 指向的 AudioWorklet 侧插件中;框架每次把 128 采样 * 直接传给 worklet 侧插件的 processAudioFrame()。 * * @public */ export declare abstract class AudioExtension extends Extension implements IAudioExtension { get kind(): Kind; /** * 框架级兼容性检测:音频实时处理依赖 AudioWorklet。 * * 静态方法,集成方可在不实例化插件的前提下直接调用(`MyAudioExtension.checkCompatibility()`)。 * 检测 `AudioWorkletNode` 与 `AudioContext`(含 `webkitAudioContext`)的 `audioWorklet` * 能力是否存在。具体插件可覆写并调用 `super.checkCompatibility()` 叠加自身依赖(如 WebAssembly、SIMD)。 */ static checkCompatibility(): boolean; /** * 创建一个绑定到给定音频 track 的 `AudioExtensionManager`。 * * Escape hatch:供宿主 SDK 在**不对框架做 value import** 的前提下,经"业务传入的插件 * 构造器"触达框架运行时。内部委派到 globalThis 单例(见 `singleton.ts` 的 * `ensureRuntime`),因此即便页面上存在多份框架副本,全页面也只有一份活跃 runtime。 * * 注意:manager 是 **track 级**、托管该 track 上所有插件的对象,与调用该静态方法的具体 * 插件构造器实例无关——这里只是把构造器当作框架运行时的入口句柄。 */ static createAudioExtensionManager(track: AudioTrackLike): AudioExtensionManager; protected _audioContext: AudioContext | null; protected _sampleRate: number; init(context: AudioExtensionContext): void; destroy(options?: ExtensionDestroyOptions): void; applyEffect(): any; } /** * 音频插件构造器类型。 * @public */ export declare interface AudioExtensionConstructor { new (...args: any[]): T; readonly extensionDefinition?: AudioExtensionDefinition; /** * 静态兼容性检测:可在不实例化插件的前提下直接调用,例如 * `if (!MyAudioExtension.checkCompatibility()) { ... } else { manager.useExtension(MyAudioExtension); }`。 * 由 `AudioExtension` 基类提供默认实现(AudioWorklet 检测),子类可覆写叠加自身依赖。 */ checkCompatibility?(): boolean; /** * 创建一个绑定到给定音频 track 的 `AudioExtensionManager`。 * * 由 `AudioExtension` 基类提供的静态方法(子类继承),委派到框架的 globalThis 单例运行时。 * 宿主 SDK 可借此在**仅做类型依赖**的前提下创建 manager,无需 value import 框架。 * 详见 `extension_audio.ts` 中的实现与 `singleton.ts`。 */ createAudioExtensionManager(track: AudioTrackLike): AudioExtensionManager; } /** * 音频插件的上下文信息,包含 AudioContext 和采样率。 * * @public */ export declare type AudioExtensionContext = { audioContext: AudioContext; sampleRate: number; /** 用户通过 useExtension() 传入的插件初始化选项。 */ extensionOptions?: Record; }; /** * 框架在 AudioWorklet 中实例化插件所需的元数据。 * * moduleURL 会先通过 audioContext.audioWorklet.addModule() 加载。该模块需要调用 * registerAudioWorkletExtension(registrationName, WorkletExtensionCtor),随后框架 * 通过 registrationName 从 Worklet 全局注册表中创建插件实例。 * * @public */ export declare type AudioExtensionDefinition = { moduleURL: string; registrationName: string; }; /** * 单个音频 Track 的插件管理器。 * * 每个实例绑定到一个输入音频 Track,负责该 Track 上插件链的注册、启用、 * 销毁与参考 Track 接入。第一个插件注册时自动启动处理链,最后一个插件 * 销毁后自动停止;中间过程可以热增删插件而无需重建管线。 */ export declare class AudioExtensionManager { private _audioTrack; private _extensions; private _extensionCtors; private _extensionIds; private _extensionOptions; private _extensionEventCleanups; private _extensionsByCtor; private _extensionInitPromises; private _pendingRemovals; private _nextExtensionId; private _loadedWorkletModuleURLs; private _referenceTracks; private _referenceSourceNodes; private _context?; private _workletNode?; private _sourceNode?; private _destNode?; private _isRunning; private _outputTrack?; private _workletRegistered; private _destroyed; onOutputTrackChanged?: (track: MediaStreamTrack | undefined) => void; constructor(audioTrack: AudioTrackLike); /** 是否仍有处于活跃状态的音频插件。 */ get hasExtensions(): boolean; /** * 为指定音频插件构造器创建或复用主线程控制面实例。 * 同一个构造器始终返回同一个实例。 */ useExtension(ctor: AudioExtensionConstructor, extensionOptions?: Record): T; /** * 初始化插件的主线程控制面,并将其挂载到处理链上。 * * 首次注册插件时会同时启动整条处理链;后续注册走热添加路径,仅加载该插件 * 的 worklet 模块并通过 postMessage 通知 worklet 端实例化。 */ private _initAndRegister; /** * 整体启用或禁用音频处理链。 * 禁用状态下 worklet 直接透传输入到输出,不调用任何插件的 processAudioFrame。 */ setEnabled(enabled: boolean): void; /** * 从处理链中移除并销毁指定插件。 * 如果是最后一个插件,会自动停止整条处理链;返回值指示插件是否真的被移除。 */ removeExtension(extension: IAudioExtension): boolean; /** * 添加一个参考 Track,作为额外的 worklet 输入接入到 1..N 端口。 * * 用于回声消除等需要远端音频做参考的算法。如果链路尚未启动,仅记录到 * `_referenceTracks` 列表,等待 `_startProcessing` 阶段一并接入。 */ addReferenceTrack(track: AudioTrackLike): void; /** * 移除已添加的参考 Track,并断开其在 worklet 上的输入端口。 * 若 track 未注册,返回 false。 */ removeReferenceTrack(track: AudioTrackLike): boolean; /** 停止处理链,销毁所有插件并清空内部映射。整体不可恢复,重新使用须新建 manager。 */ destroy(): void; /** 懒创建共享 AudioContext 并缓存采样率。失败时记录错误并保持 `_context` 为 undefined。 */ private _initContext; /** * 把 worklet 端上报的插件错误打印到主线程控制台。 * * worklet 内的 `console.*` 不会出现在主线程控制台,因此插件在 worklet 里的 * init / processAudioFrame 异常必须通过 port 消息回传。这里把 worklet id 映射回 * 插件名,优先打印 stack(定位到插件 bundle 内的具体位置),并标注来源阶段 * (`source`)与节流窗口内的累计次数(`occurrences`)。 */ private _reportWorkletError; /** 按 worklet 分配的数字 id 反查插件名(用于错误日志定位)。 */ private _extensionNameById; /** 清理单个扩展的所有主线程记录,可选择调用 extension.destroy()。 */ private _cleanupExtensionRecord; /** * 将单个插件连接到 worklet。 * * 插件必须通过 extensionDefinition 提供 AudioWorklet 侧实现, * 框架在 worklet 中实例化插件并直接传递 128 采样进行同步处理。 */ private _connectExtension; /** * 启动 AudioWorklet 处理链。 * * 步骤: * 1. 为所有已注册插件加载各自的 worklet 模块; * 2. 注册框架 worklet 处理器(每个 AudioContext 只注册一次); * 3. 构造 AudioWorkletNode,并把它与源 Track / 参考 Tracks 连接到对应的端口; * 4. 通知 worklet 端实例化各插件; * 5. 通过 `MediaStreamAudioDestinationNode` 把输出转回 MediaStream Track; * 6. 发送 enable 消息,让 worklet 真正开始转发到插件。 * * 启动过程中任意一步抛错都会把 `_isRunning` 回退到 false,避免半启动状态。 */ private _startProcessing; /** * 关闭处理链:先让 worklet 进入 disable(透传),再断开主线程上的所有 AudioNode, * 并把输出 Track 置空通知宿主。再次启动需要重新走 `_startProcessing`。 */ private _stopProcessing; /** 断开所有参考 Track 对应的 AudioNode,并清空缓存。 */ private _disconnectReferences; /** * 按需通过 `audioWorklet.addModule` 加载某个插件提供的 worklet 模块。 * 同一个 moduleURL 只会被加载一次(用 `_loadedWorkletModuleURLs` 去重)。 */ private _loadExtensionWorkletModule; /** * 把主线程控制面实例的 `enabled` / `disabled` 事件桥接到 worklet 端: * 主线程上插件被 enable/disable 时,会通过 postMessage 通知 worklet 切换该插件的开关, * 而无需把整条链路重启。 */ private _bindExtensionEvents; /** 解绑由 `_bindExtensionEvents` 注册的所有事件回调。 */ private _cleanupExtensionEvents; } /** * 运行时框架使用的音频 Track 抽象接口。 * * @public */ export declare interface AudioTrackLike { readonly mediaStreamTrack: MediaStreamTrack; } /** * AudioWorklet 侧插件构造器。 * * @public */ export declare interface AudioWorkletExtensionConstructor { new (...args: any[]): T; } /** * AudioWorklet 侧插件的初始化上下文。 * * @public */ export declare type AudioWorkletExtensionContext = { sampleRate: number; extensionOptions?: Record; }; /** * `EventEmitter` 类提供了定义、触发和处理事件的方式。 * * @public */ export declare class EventEmitter { /** * 存储所有已注册事件及其对应监听器列表的映射表。 */ private _events; /** * 单个事件允许的最大监听器数量。超过此数量时会输出警告(可能存在内存泄漏)。 * 设为 0 表示不限制。 */ private _maxListeners; /** * 设置单个事件允许的最大监听器数量。 * * @param n - 最大监听器数量,0 表示不限制。 */ setMaxListeners(n: number): void; /** * 指定一个事件名,获取当前所有监听这个事件的回调函数。 * * @param event - 事件名称。 * @returns 该事件的所有回调函数数组,如果没有监听器则返回空数组。 */ getListeners(event: string): Function[]; /** * 监听一个指定的事件,当事件触发时会调用传入的回调函数。 * * @param event - 指定事件的名称。 * @param listener - 传入的回调函数。 */ on(event: string, listener: Function): void; /* Excluded from this release type: addListener */ /** * 监听一个指定的事件,当事件触发时会调用传入的回调函数。 * * 当监听后事件第一次触发时,该监听和回调函数就会被立刻移除,也就是只监听一次指定事件。 * * @param event - 指定事件的名称。 * @param listener - 传入的回调函数。 */ once(event: string, listener: Function): void; /** * 取消一个指定事件的监听。 * * @param event - 指定事件的名称。 * @param listener - 监听事件时传入的回调函数。 */ off(event: string, listener: Function): void; /** * 指定一个事件,取消其所有的监听。 * * @param event - 指定事件的名称,如果没有指定事件,则取消所有事件的所有监听。 */ removeAllListeners(event?: string): void; /** * 触发一个指定的事件,依次调用该事件的所有监听器。 * * 如果监听器是通过 {@link EventEmitter.once | once} 注册的,触发后会被自动移除。 * * @param event - 要触发的事件名称。 * @param args - 传递给监听器回调函数的参数。 */ emit(event: string, ...args: any[]): void; /* Excluded from this release type: safeEmit */ /** * 在监听器数组中查找指定回调函数的索引。 * * @param listeners - 监听器数组。 * @param listener - 要查找的回调函数。 * @returns 回调函数在数组中的索引,如果未找到则返回 `-1`。 */ private _indexOfListener; } /** * 插件基类,提供了插件的基本生命周期管理和事件机制。 * * @public */ export declare abstract class Extension extends EventEmitter implements IExtension { abstract name: string; protected _enabled: boolean; readonly ID: string; /* Excluded from this release type: __logger */ abstract get kind(): Kind; get enabled(): boolean; constructor(); init(context: any): void; destroy(_options?: ExtensionDestroyOptions): void; enable(): Promise; protected onEnable(): Promise; disable(): Promise; protected onDisable(): Promise; /** * 检测当前运行环境是否支持该插件(静态版本)。 * * 设计为**静态方法**,以便集成方在**不实例化插件**的前提下于主线程直接调用,例如 * `if (!MyVideoExtension.checkCompatibility()) { ... } else { manager.useExtension(MyVideoExtension); }`。 * 基类默认返回 `true`;`VideoExtension` / `AudioExtension` 覆写为框架级检测 * (WebGL2 / AudioWorklet),具体插件可进一步覆写叠加自身依赖(如 WebAssembly、SIMD)。 */ static checkCompatibility(): boolean; /** * 检测当前运行环境是否支持该插件(实例版本)。 * * 默认委派到构造器上的同名静态方法,因此子类只需覆写静态方法即可同时影响静态与实例调用。 * 推荐直接用静态形式 `MyExtension.checkCompatibility()`,无需先创建实例。 */ checkCompatibility(): boolean; abstract applyEffect(frame: any): any; } /** * 销毁插件时传入的原因/选项。 * * @public */ export declare type ExtensionDestroyOptions = { reason?: "normal" | "context-loss"; }; /** * {@link ExtensionEvents.ERROR} 事件的负载。 * * @public */ export declare type ExtensionErrorEvent = { /** * 失败来源标签,便于区分场景。例如: * - `"worker-fallback"`:框架共享 worker 不可用,整条 track 已切到插件自有 worker * (仍在运行,只是更低效),此时 `degraded` 为 `false`; * - `"applyEffect"`:逐帧处理失败(worker 或主线程); * - `"contextRestore.reinit"` / `"contextLoss.cpuInit"`:context 恢复/回退失败。 */ source: string; /** 归一化后的错误信息。`name`/`message` 来自原始错误,`stack` 尽量保留以便定位。 */ error: { name: string; message: string; stack?: string; }; /** * 是否为"退化但仍在运行"的状态:例如逐帧失败时框架会透传原始帧, * 画面可用但插件效果缺失。业务可据此决定是否提示用户或停用插件。 */ degraded: boolean; /** 节流窗口内该错误的累计次数(逐帧错误会聚合上报)。默认为 1。 */ occurrences?: number; }; /** * 插件事件枚举,定义了插件可能触发的事件。 */ export declare enum ExtensionEvents { /** * 插件被启用时触发。 */ ENABLED = "enabled", /** * 插件被禁用时触发。 */ DISABLED = "disabled", /** * 插件运行期间发生失败时触发(业务侧可观测的失败信号)。 * * 与 `enable()`/`setOptions()` 等调用直接 reject 的"操作级"错误不同,本事件覆盖 * 那些只在控制台打印、业务侧无法从调用处感知的失败,典型来源: * - worker / 主线程逐帧 `applyEffect` 失败(插件已在静默透传 passthrough); * - WebGL context 丢失后的重建 / 配置重放失败。 * * 监听器收到的参数形状见 {@link ExtensionErrorEvent}。该事件**不**取代调用处的 * try/catch;它是补充的"带外"信号,便于业务做降级提示或埋点。 */ ERROR = "error" } /** * useExtension 的选项。 * @public */ export declare type ExtensionOptions = { /** * 渲染模式。 * - "auto"(默认):优先 GPU(WebGL2),不可用时降级到 CPU * - "gpu":强制 GPU 模式,WebGL2 不可用时抛错 * - "cpu":强制 CPU 模式,即使 WebGL2 可用也走 CPU 路径 */ renderMode?: "auto" | "gpu" | "cpu"; /** * 线程模式。 * - "auto"(默认):优先 Worker(需要流 API 支持),不可用时降级到主线程 * - "worker":强制 Worker 模式,流 API 不可用时抛错 * - "main":强制主线程模式 * * Worker 模式下 GPU 处理通过 OffscreenCanvas + WebGL2 在 Worker 内执行, * CPU 处理通过 TransformStream 在 Worker 内执行。 * 两种情况都不阻塞主线程。 */ threadMode?: "auto" | "worker" | "main"; /** * 插件的初始业务配置。 * * 框架会在插件 `init()` 完成后,自动用这份配置调用一次 `setOptions(extensionOptions)`, * 因此调用方无需在 `useExtension()` 之后再手动调一次 `setOptions`。 * 后续运行时更新配置请直接调用代理上的 `setOptions(...)`。 * * 仅在首次创建该插件代理时消费;同一插件代理已存在时,重复传入的 * `extensionOptions` 会被忽略(要改配置请用 `setOptions`)。 * * 与 `renderMode` / `threadMode` 正交:这是纯业务数据,框架只透传不解读, * 在 worker-gpu / worker-cpu / main-gpu 各后端下传给插件的内容完全一致。 */ extensionOptions?: Record; /** * 显式指定插件产物的可加载 URL(Worker 会 `import()` 它)。 * * 这是给集成方的“逃生舱”:当插件被二次打包进宿主 bundle、走 CDN、或自定义 * 部署路径,导致插件构造器里写死的 `import.meta.url` 解析不到独立产物文件时, * 集成方可以在 `useExtension` 时直接提供真实可访问的 URL。 * * 优先级:本字段 > 构造器静态 `extensionDefinition.moduleURL`。 * 缺省时回退到构造器静态字段(行为与历史一致,向后兼容)。 * * 典型用法(让打包器算出独立资源 URL,避免硬编码): * ```ts * import url from "my-extension/dist/index.esm.js?url"; // Vite * useExtension(MyExtension, { moduleURL: url }); * ``` * 或直接给 CDN / 静态目录地址: * ```ts * useExtension(MyExtension, { moduleURL: "https://cdn.example.com/my-extension.esm.js" }); * ``` * * 注意:仅在首次创建该插件代理时消费;跨源 URL 需服务端带 CORS 头。 */ moduleURL?: string; /** * 与 {@link moduleURL} 配套的注册名,对应插件产物顶层 * `registerVideoExtension(registrationName, Ctor)` 使用的名字。 * * 通常无需提供:缺省时回退到构造器静态 `extensionDefinition.registrationName`, * 因此集成方一般只需传 `moduleURL` 即可。仅当插件构造器未声明 * `extensionDefinition` 时,才需要连同 `moduleURL` 一起显式提供。 */ registrationName?: string; }; /** * 框架 Worker 与插件**自有 fallback worker** 脚本之间的 process-only 消息协议。 * * 仅用于 routed 子模式(框架共享 worker 的 `import(moduleURL)` 失败后,整条 track 改用 * 各插件自带的 worker 处理)。**老插件模式:每个插件 worker 自有 `MSTP→处理→MSTG` 管线**, * 由主线程用原生 `MediaStreamTrackProcessor`/`MediaStreamTrackGenerator` 在主线程侧搭桥 * (`capture → MSTP → 插件1 → track → MSTP → 插件2 → … → 输出 track`)。 * * 帧通道用**原生 MSTP/MSTG**而非普通 transferable stream:把 VideoFrame 写入跨 realm 的普通 * `TransformStream` 会**克隆**(不转移所有权)导致发送侧泄漏;而 `MSTP.readable`(worker 内读) * 与 `MSTG.writable`(worker 内写)是原生帧通道,不克隆/不泄漏。因此 `connect-frame-pipe` 把 * 主线程建的输入 `readable`(MSTP)+ 可选输出 `writable`(Chrome 的 MSTG)交进插件 worker; * Safari 无 `MediaStreamTrackGenerator`,插件 worker 自建 `VideoTrackGenerator` 并在回复里把 * `{ track }` 交回主线程作为该段输出。 * * 插件提供的 worker 脚本(通常由框架的 `runPluginFallbackWorker` helper 实现)必须响应: * - init-plugin: 初始化插件实例(gpu 模式下创建该 worker 自己的 GL context) * - connect-frame-pipe: 接入输入/输出端,建立本段 `readable.pipeThrough(处理).pipeTo(writable)` * (重建帧链时再次下发以换用新端;Safari 回复携带自建 VTG 的 `track`) * - set-options: 声明式下发配置 * - enable / disable: 启用 / 禁用(禁用时直通) * - destroy: 释放资源 * * 注:`type` 的字符串值与框架内部的 `WorkerMessageType` 枚举一致;每条请求都带 `requestId`, * worker 回复同 `requestId` 表示完成(见 `ExtensionWorkerResponse`)。 * * @public */ export declare type ExtensionWorkerMessage = { type: "init-plugin"; mode: VideoExtensionBackend; enabled?: boolean; extensionOptions?: Record; requestId?: string; data?: any; } | { type: "connect-frame-pipe"; readable: ReadableStream; writable?: WritableStream; requestId?: string; } | { type: "set-options"; options: Record; requestId?: string; } | { type: "enable"; requestId?: string; } | { type: "disable"; requestId?: string; } | { type: "destroy"; requestId?: string; } | { type: "simulate-context-loss"; requestId?: string; } | { type: "simulate-context-restore"; requestId?: string; }; /** * 插件自有 fallback worker 脚本回复给框架的消息。 * * 应答类(带 `requestId`)回执同类型表示完成;`connect-frame-pipe` 的回复在 Safari 下携带 * 自建 `VideoTrackGenerator` 的 `track`。主动上行:`on-emit`(插件出站事件)、`async-error` * (逐帧/context 恢复失败,节流)、`error`(init 等控制路径失败)、`log`(日志转发)。 * @public */ export declare type ExtensionWorkerResponse = { type: "init-plugin"; requestId?: string; } | { type: "connect-frame-pipe"; track?: MediaStreamTrack; requestId?: string; } | { type: "set-options"; requestId?: string; } | { type: "enable"; requestId?: string; } | { type: "disable"; requestId?: string; } | { type: "destroy"; requestId?: string; } | { type: "simulate-context-loss"; requestId?: string; } | { type: "simulate-context-restore"; requestId?: string; } | { type: "on-emit"; name: string; data?: any; } | { type: "async-error"; extensionId?: string; sourceType?: string; name?: string; message?: string; stack?: string; occurrences?: number; } | { type: "error"; requestId?: string; sourceType?: string; name?: string; message: string; stack?: string; }; /** * 插件处理输入帧的统一抽象。 * * @public */ export declare type Frame = ({ kind: "gpu"; videoFrame?: undefined; } & VideoFrameTextures) | { kind: "cpu"; width: number; height: number; input?: undefined; output?: undefined; videoFrame: VideoFrame; }; /** * 从音频插件构造器读取 AudioWorklet 实例化元数据。 * * @public */ export declare function getAudioExtensionDefinition(ctor: AudioExtensionConstructor): AudioExtensionDefinition | undefined; /** * 从视频插件构造器读取 Worker 实例化元数据。 * * @public */ export declare function getVideoExtensionDefinition(ctor: VideoExtensionConstructor): VideoExtensionDefinition | undefined; /** * 音频插件接口。 * * 主线程上的 AudioExtension 只承载控制面状态和生命周期。真正的实时音频处理由 * extensionDefinition 指向的 AudioWorklet 侧插件完成。 * * 框架每次将 128 采样直接传给 AudioWorklet 侧插件的 processAudioFrame(), * 插件处理完返回 128 采样,再传给下一个插件,最终输出到音频图。 * * @public */ export declare interface IAudioExtension extends IExtension { readonly enabled: boolean; /** * 初始化插件。 */ init(context: AudioExtensionContext): void | Promise; /** * 销毁插件并释放相关资源。 */ destroy(options?: ExtensionDestroyOptions): void; } /** * AudioWorklet 侧插件实例。 * * 实例运行在 AudioWorkletProcessor 所在的实时音频线程中,processAudioFrame() * 必须同步、低分配、不可 await。需要更大帧长或 WASM 资源的插件应在 init() * 中准备资源,并在实例内部缓冲,把 128 sample quantum 和算法帧长解耦。 * * @public */ export declare interface IAudioWorkletExtension { init?(context: AudioWorkletExtensionContext): void | Promise; destroy?(): void; enable?(): void; disable?(): void; processAudioFrame(input: Float32Array, referenceInputs?: Float32Array[]): Float32Array; } /** * 插件接口,定义了插件的基本结构和生命周期方法。 * * @public */ export declare interface IExtension { /** * 插件的名称,只读属性。 */ readonly name: string; /** * 插件处理的媒体类型。 */ kind: Kind; /** * 初始化插件。 * * @param context - 插件初始化时的上下文信息。 */ init(context: any): void | Promise; /** * 启用插件。 */ enable(): void | Promise; /** * 禁用插件。 */ disable(): void | Promise; /** * 销毁插件并释放相关资源。 */ destroy(options?: ExtensionDestroyOptions): void; /** * 检测当前运行环境是否支持该插件,供集成方在使用前做统一的兼容性自检。 * * 返回 `true` 表示当前浏览器/环境满足插件运行所需能力;`false` 表示不满足, * 集成方应避免启用该插件并给出降级提示。基类提供默认实现(video 检测 WebGL2、 * audio 检测 AudioWorklet),具体插件可覆写以叠加自身依赖(如 WebAssembly、SIMD 等)。 * * @returns 当前环境是否兼容该插件。 */ checkCompatibility?(): boolean; } /** * 插件日志接口,定义了日志输出的方法。 * * @public */ export declare interface IExtensionLogger { debug(...args: any): void; info(...args: any): void; warning(...args: any): void; error(...args: any): void; setLogLevel(level: number): void; } /** * 插件上报接口,定义了 API 调用上报的方法。 * * @public */ export declare interface IExtensionReporter { reportApiInvoke(params: ReportApiInvokeParams, throttleTime?: number): AgoraApiExecutor; } /** * 视频插件接口。 * * 框架调度策略: * 1. 收集所有 enabled 插件,按 useExtension 顺序排列 * 2. 按顺序 ping-pong 执行每个插件的 applyEffect * 3. 最终结果 blit 到 canvas 输出 * * 常见插件路径: * - 纯 GPU 路径:input texture → output texture。零额外 CPU 拷贝,效率最高。 * 例:SuperClarity、Beauty。所有新插件优先采用这一模式。 * - VideoFrame 兼容路径:框架 blit texture → canvas → VideoFrame → 插件 * → 插件返回 VideoFrame → 框架 texImage2D 回 texture。会增加 CPU↔GPU 往返。 * 仅建议用于无法适配 TEXTURE 模式的存量插件迁移。 * - Worker 后端:框架把帧 I/O 放到 Worker,中间仍复用同一套 Extension 接口。 * * ─── 与业务侧的双向通信 ─── * * - 入站(业务 → 插件):唯一入口是 {@link IVideoExtension.setOptions | setOptions}。 * 配置、甚至水印图片等都通过它声明式地下发,框架自动把图片 / ArrayBuffer 等 * 不可克隆数据转成 Transferable 后跨 Worker 边界传递。 * - 出站(插件 → 业务):插件调用继承自 {@link EventEmitter} 的 `emit(name, data)`, * 框架把它转发到主线程代理,业务侧通过代理的 `on(name, handler)` 监听 * (例如逐帧 face landmark 数据)。 * * @typeParam TOptions - 该插件 `setOptions` 接受的配置形状。 * * @public */ export declare interface IVideoExtension> extends IExtension { readonly enabled: boolean; readonly mode: VideoExtensionMode; /** * 插件是否支持 CPU 回退模式。 * * 当 WebGL context 丢失时,框架会对 supportsCPUMode=true 的插件 * 调用 `applyEffect({ kind: "cpu" })` 进行 CPU 回退处理。 * 不支持 CPU 模式的插件在 context 丢失期间会被跳过。 * 默认 false。 */ readonly supportsCPUMode: boolean; init(context: VideoExtensionContext): void | Promise; destroy(options?: ExtensionDestroyOptions): void; /** * 唯一的入站配置入口(声明式)。 * * 业务侧的所有配置更新都经由此方法下发:插件根据传入的配置自行 diff 并应用。 * 配置中的图片(HTMLImageElement / ImageBitmap)、ArrayBuffer 等不可结构化克隆 * 的数据,会由框架在跨 Worker 边界前自动转换为 Transferable,插件无需手动序列化。 * * 框架在插件 `init()` 完成后会用 `useExtension()` 的初始 `extensionOptions` * 自动调用一次本方法,因此「初始配置」与「运行时更新」走同一段代码路径。 * * 返回的 Promise 在配置应用完成(worker 模式下为 Worker 内应用完成)后 resolve, * 出错时 reject,业务侧可 await 并 try/catch。 * * ─── 重放(context loss / worker 重启):框架自动深合并,作者无需配合 ─── * * 框架会缓存每次下发的 options,用于 WebGL context 丢失 / Worker 重启后重建实例时 * **自动重放**配置。缓存合并由框架统一处理(见 `internal/options_merge.ts`): * * - **普通对象(plain object)子树会被深合并**:先 `setOptions({ beauty: { smoothness } })` * 再 `setOptions({ beauty: { whitening } })`,重放时 `smoothness` 与 `whitening` 都在。 * 分片下发的配置天然保真,插件作者**无需**在实例内部自行累积,也无需把完整状态 * 每次整体传入。 * - **数组整体替换**:`setOptions({ watermarks: [...] })` 传入新列表即替换旧列表, * 不会把多次传入的数组元素并起来。这保留了"传入完整列表即替换全部"的声明式语义。 * - **类实例 / ImageBitmap / ArrayBuffer 等非普通对象整体替换**(不深克隆)。 * * 即:嵌套对象会累积、数组会替换。绝大多数配置形状都能直接得到符合直觉的重放结果, * 无需任何额外约定。 */ setOptions(options: Partial): void | Promise; /** 统一处理入口:GPU 模式传入纹理帧,CPU 模式传入 VideoFrame。 */ applyEffect(frame: Frame): Promise; /** * VIDEO_FRAME 模式的处理方法。 * 在 GPU 管线中,框架自动做 texture ↔ VideoFrame 转换后调用此方法。 * 在 CPU 管线中,直接收到 VideoFrame。 * * 也可以通过 applyEffect(frame) 处理 kind === "cpu" 来替代。 */ processVideoFrame?(frame: VideoFrame): Promise; } /** * 媒体类型,表示视频或音频。 * * @public */ export declare type Kind = "video" | "audio"; /** * 日志管理器,提供不同级别的日志输出能力。 * * @public */ export declare class Logger implements IExtensionLogger { private logLevel; private hookLog?; /** * 设置 SDK 的日志输出级别 * @param level - SDK 日志级别依次为 NONE(4),ERROR(3),WARNING(2),INFO(1),DEBUG(0)。选择一个级别, * 你就可以看到在该级别及该级别以上所有级别的日志信息。 * * 例如,如果你输入代码 Logger.setLogLevel(1);,就可以看到 INFO,ERROR 和 WARNING 级别的日志信息。 */ setLogLevel(level: number): void; /** 以 DEBUG 级别输出日志。 */ debug(...args: any): void; /** 以 INFO 级别输出日志。 */ info(...args: any): void; /** 以 WARNING 级别输出日志。 */ warning(...args: any): void; /** 以 ERROR 级别输出日志。 */ error(...args: any): void; /** * 真正写日志的内部入口。 * - 第一个参数为日志级别(0-4),其余为透传给 console 的内容。 * - 启动后 100ms 内的日志会被延后输出,避免用户尚未来得及调用 * `setLogLevel` 关闭日志时仍打印初始消息。 * - 同步触发 `hookLog`(如果设置),供 Worker 侧把日志转发到主线程。 * - 仅当级别 ≥ 当前 `logLevel` 时才输出到 console。 */ private log; } export declare const logger: Logger; /** * 在 AudioWorklet 模块中注册音频插件类。 * * 插件的 worklet 侧产物在顶层调用一次本函数,把处理类按 `registrationName` 写入挂在 * `globalThis` 上的注册表。框架 `addModule()` 加载该产物触发此注册后,按 definition 里的 * `registrationName` 取出处理类并实例化。 * * 注册表挂在 `globalThis`(而非模块级变量):插件 bundle 与 AudioWorklet 是两个独立 realm, * 各自持有自己的模块实例,只能通过共享的全局对象对接。 * * @public */ export declare function registerAudioWorkletExtension(registrationName: string, ctor: AudioWorkletExtensionConstructor): void; /** * 在 Worker 模块中注册视频插件类。 * * 插件产物(ESM/UMD)在顶层调用一次本函数,把插件类按 `registrationName` 写入挂在 * `globalThis` 上的注册表。框架 `import()` 该产物触发此注册后,按 definition 里的 * `registrationName` 取出插件类并实例化。 * * 注册表挂在 `globalThis`(而非模块级变量):插件 bundle 与框架 Worker bundle 是两份 * 独立产物,各自持有自己的模块实例,只能通过共享的全局对象对接。 * * @public */ export declare function registerVideoExtension(registrationName: string, ctor: VideoExtensionConstructor): void; /** * API 调用上报参数。 * * @public */ export declare interface ReportApiInvokeParams { name: string; options: any; reportResult?: boolean; timeout?: number; } /** * 上报器,用于收集和上报 API 调用信息。 * * @public */ export declare class Reporter implements IExtensionReporter { /** * 在 `hookApiInvoke` 尚未注入前积压待上报消息。 * 一旦宿主侧(如 RTC SDK)注入 hook,则把队列与最新消息一起刷新出去, * 之后队列保持为空。 */ private apiInvokeMsgQueue; /** 由宿主 SDK 注入的实际上报实现;未注入时消息会被缓存到 `apiInvokeMsgQueue`。 */ private hookApiInvoke?; /** * 上报一次 API 调用,返回一个执行器,调用方在异步操作完成后调用 * `onSuccess` / `onError` 即可触发上报。 * * 默认行为: * - `params.timeout`:默认 60s。超时未结束自动以 `API_INVOKE_TIMEOUT` 上报失败。 * - `params.reportResult`:默认为 true,会把 `onSuccess` 接收到的结果一并上报。 * - 同一个执行器只能结束一次;二次调用 `onSuccess`/`onError` 会抛出。 * * @typeParam T - `onSuccess` 接收到的结果类型。 */ reportApiInvoke(params: ReportApiInvokeParams): AgoraApiExecutor; /** * 将一条上报消息派发出去。 * * 若宿主 SDK 已注入 `hookApiInvoke`,则连同积压队列一起送出后清空队列; * 否则把消息压入队列,等待后续 hook 注入时再 flush。 */ private sendApiInvoke; } /** * 全局 Reporter 单例,用于上报 API 调用信息。 * * @public */ export declare const reporter: Reporter; /** * 在插件自有 worker 内启动 process-only 运行时(原生 MSTP/MSTG 帧通道)。 * * @param Ctor 插件构造器(与主线程 `useExtension` 用的是同一个类)。 * @param scope 控制/出站通道端点;默认 `self`(worker 全局,连到插件主线程)。 */ export declare function runPluginFallbackWorker(Ctor: VideoExtensionConstructor, scope?: { postMessage: (msg: any, transfer?: Transferable[]) => void; onmessage: ((ev: MessageEvent) => void) | null; }): void; /** 框架版本号。构建期由 rollup `@rollup/plugin-replace` 把 `__VERSION__` 替换为 package.json version。 * * @public */ export declare const VERSION: string; /** * 视频插件基类,提供共享的 WebGL 工具方法。 * * 子类只需关注 WASM 初始化和调用,通用的 GL 操作(直通渲染、 * texture 拷贝、FBO 管理等)由基类提供。 * * 业务侧通过 `setOptions` 下发配置(声明式,子类覆写实现),通过继承自 * `EventEmitter` 的 `emit` 向业务侧推送数据(框架自动跨线程转发)。 * * @typeParam TOptions - 该插件 `setOptions` 接受的配置形状。 * * @public */ export declare abstract class VideoExtension> extends Extension implements IVideoExtension { get kind(): Kind; /** * 创建一个绑定到给定视频 track 的 `VideoExtensionManager`。 * * Escape hatch:供宿主 SDK 在**不对框架做 value import** 的前提下,经"业务传入的插件 * 构造器"触达框架运行时。内部委派到 globalThis 单例(见 `singleton.ts` 的 * `ensureRuntime`),因此即便页面上存在多份框架副本,全页面也只有一份活跃 runtime。 * * 注意:manager 是 **track 级**、托管该 track 上所有插件的对象,与调用该静态方法的具体 * 插件构造器实例无关——这里只是把构造器当作框架运行时的入口句柄。 */ static createVideoExtensionManager(track: VideoTrackLike): VideoExtensionManager; /** * 默认为 TEXTURE 模式。子类可覆写为 VIDEO_FRAME。 */ get mode(): VideoExtensionMode; /** * 默认不支持 CPU 回退模式。支持的子类覆写返回 true。 * 当 WebGL context 丢失时,框架会对 supportsCPUMode=true 的插件 * 调用 `applyEffect({ kind: "cpu" })` 进行 CPU 回退处理。 */ get supportsCPUMode(): boolean; /** * 框架级兼容性检测:视频管线依赖 WebGL2。 * * 静态方法,集成方可在不实例化插件的前提下直接调用(`MyVideoExtension.checkCompatibility()`)。 * 在主线程(`HTMLCanvasElement`)或 Worker(`OffscreenCanvas`)下尝试获取 `webgl2` * 上下文,任一可用即视为兼容。具体插件可覆写并调用 `super.checkCompatibility()` * 叠加自身依赖(如 WebAssembly、特定 GL 扩展等)。 */ static checkCompatibility(): boolean; protected _gl: WebGL2RenderingContext | null; protected _canvas: HTMLCanvasElement | OffscreenCanvas | null; /** 直通 shader program,用于原样拷贝 texture。 */ protected _passthroughProgram: WebGLProgram | null; private _vertexBuffer; private _vertexArray; /** blitTexture 使用的 FBO 缓存,避免每帧 create/delete。 */ private _blitReadFbo; private _blitDrawFbo; /** * 按 GL context 缓存共享资源,避免多个插件共用同一个 GL context 时 * 重复编译 shader 和创建 buffer。 */ private static _glCache; /** * 清理指定 GL context 的资源缓存,context loss/restore 后需要调用。 */ static clearGLCache(gl: WebGL2RenderingContext): void; /** * 初始化共享 GL 资源。子类覆写时必须调用 super.init(context)。 */ init(context: VideoExtensionContext): void; /** * 释放 GL 资源引用和事件监听。子类覆写时必须调用 super.destroy()。 */ destroy(options?: ExtensionDestroyOptions): void; /** 当前 WebGL context 是否已丢失。 */ private _contextLost; /** 当前 WebGL context 是否已丢失,供子类和框架只读访问。 */ get contextLost(): boolean; private _onContextLost; private _onContextRestored; /** * WebGL context 恢复后调用。子类可覆写此方法,重新初始化自己的 * GL 资源,例如 shader program、texture、FBO 等。 * * 在调用此方法前,基类已经重新初始化共享 GL 资源 * (直通 shader、vertex buffer 等)。 * * 默认实现为空。 */ protected onContextRestored(): void; /** * TEXTURE 模式的处理方法。子类覆写此方法实现 GPU texture 处理。 * 默认实现:直接拷贝 input → output(直通)。 */ applyEffect(frame: Frame): Promise; /** * VIDEO_FRAME 模式的处理方法。子类覆写此方法实现 VideoFrame 处理。 * 默认实现:返回原始帧(直通)。 * * 在 GPU 管线中,框架会自动做 texture → VideoFrame → 插件 → texture 的转换。 * 在 CPU 管线中,插件直接收到 VideoFrame。 * * 也可以通过 applyEffect(frame) 处理 kind === "cpu" 的帧来替代此方法。 */ processVideoFrame(frame: VideoFrame): Promise; /** * 声明式配置入口。默认实现为空(no-op),需要配置的子类覆写此方法, * 根据传入的 `options` 自行 diff 并应用(例如更新水印列表)。 * * 框架在 `init()` 之后会用 `useExtension()` 的初始 `extensionOptions` * 自动调用一次本方法,之后业务侧每次 `setOptions(...)` 也走到这里。 */ setOptions(_options: Partial): void | Promise; /** * 将 srcTexture 的内容通过直通 shader 绘制到 fbo 上。 */ protected drawTextureToFbo(texture: WebGLTexture, fbo: WebGLFramebuffer, width: number, height: number): void; /** * 通过 blitFramebuffer 将一个 texture 的内容拷贝到另一个 texture。 * 要求 GL context 的 antialias 为 false。 * FBO 被缓存复用以避免每帧 create/delete 的开销。 */ protected blitTexture(src: WebGLTexture, dst: WebGLTexture, width: number, height: number): void; /** * 将像素坐标(左上角原点)转换为框架纹理空间中的 GL NDC 坐标。 * * 框架的纹理方向约定:图像顶部在帧缓冲底部(GL y=0),框架在最终输出时 * 执行 Y 翻转。因此像素 y 坐标映射到 GL y 时方向与标准 GL 相反。 * * @param px - 像素 x 坐标(0 = 左边缘) * @param py - 像素 y 坐标(0 = 上边缘) * @param frameWidth - 帧宽度(像素) * @param frameHeight - 帧高度(像素) * @returns [glX, glY] — GL NDC 坐标,范围 [-1, +1] * * 用法示例(定位水印中心): * ```ts * const [cx, cy] = this.pixelToGL( * watermark.x + watermark.width / 2, * watermark.y + watermark.height / 2, * frameWidth, frameHeight * ); * ``` */ protected pixelToGL(px: number, py: number, frameWidth: number, frameHeight: number): [number, number]; /** * 创建一个指定尺寸的 RGBA texture。 */ protected createTexture(width: number, height: number): WebGLTexture; /** * 创建一个绑定到指定 texture 的 FBO。 */ protected createFbo(texture: WebGLTexture): WebGLFramebuffer; /** * 编译并链接一个 shader program。 */ protected compileProgram(vertSrc: string, fragSrc: string): WebGLProgram | null; private _initSharedGL; private _drawWithProgram; } /** @public */ export declare type VideoExtensionBackend = "gpu" | "cpu"; /** * 视频插件构造器,可携带 Worker 实例化元数据。 * * @public */ export declare interface VideoExtensionConstructor { new (...args: any[]): T; readonly extensionDefinition?: VideoExtensionDefinition; /** * 静态兼容性检测:可在不实例化插件的前提下于主线程直接调用,例如 * `if (!MyVideoExtension.checkCompatibility()) { ... } else { manager.useExtension(MyVideoExtension); }`。 * 由 `VideoExtension` 基类提供默认实现(WebGL2 检测),子类可覆写叠加自身依赖(如 WebAssembly)。 */ checkCompatibility?(): boolean; /** * 创建该插件**自有的 fallback worker**(process-only)。 * * 当框架共享 worker 的 `import(moduleURL)` 失败时,整条 track 会改用各插件自带的 worker * 处理帧。该工厂在**主线程**调用,返回插件自己打包的 worker(推荐用 inline-blob, * 不依赖 `import.meta.url` / `new URL`,避免重蹈触发 fallback 的同类 URL 解析失败)。 * * 允许返回 `Promise`:插件通常用**动态 import** 把(可能很大、含 WASM 的) * inline-blob 拆成懒加载 chunk(仅在 fallback 真正触发时才加载),避免它被静态内联进 * 主包(尤其当下游以源码方式消费插件时会显著膨胀甚至 OOM)。 * * **必填**:`useExtension` 只接受声明了本字段的构造器,未声明的插件在编译期即报错, * 因此每个插件天然具备 fallback 能力,"插件没有 fallback worker"不是运行时状态。 * * **运行时装配约定(重要)**:插件类文件**不应**直接 `import` 自己生成的 inline-blob * 模块——那会与 fallback worker 自身形成“类 → 生成 blob → blob 内含该类”的自包含循环, * 并把巨大的 base64 静态内联进主包(曾导致打包 OOM)。因此实际工厂在插件**包入口** * (barrel `index.ts`)用**动态 import** 副作用装配(懒加载 chunk)。这意味着本字段的 * 运行时值依赖“插件从其包入口被消费”:若下游绕过入口、深路径直接 import 插件类文件, * 本工厂将缺失(`undefined`),框架据此判定 terminal 并打出可操作的报错。集成方务必从 * 插件包入口消费插件,不要深路径 import 类文件。 */ readonly createFallbackWorker: () => Worker | Promise; /** * 创建一个绑定到给定视频 track 的 `VideoExtensionManager`。 * * 由 `VideoExtension` 基类提供的静态方法(子类继承),委派到框架的 globalThis 单例运行时。 * 宿主 SDK 可借此在**仅做类型依赖**的前提下创建 manager,无需 value import 框架。 * 详见 `extension_video.ts` 中的实现与 `singleton.ts`。 */ createVideoExtensionManager(track: VideoTrackLike): VideoExtensionManager; } /** * 视频插件的上下文信息,包含画布和 WebGL 渲染上下文。 * @public */ export declare type VideoExtensionContext = { backend: VideoExtensionBackend; threadMode: VideoExtensionThreadMode; canvas?: HTMLCanvasElement | OffscreenCanvas; gl?: WebGL2RenderingContext; }; /** * 框架在共享管线 Worker 内实例化插件类所需的模块元数据。 * * `moduleURL` 指向插件产物(ESM 或 UMD 均可);Worker 会动态 `import()` 它, * 触发该模块顶层的 `registerVideoExtension(registrationName, Ctor)` 自注册副作用。 * 随后框架按 `registrationName` 从 Worker 全局注册表中取出插件类并实例化。 * * 框架不从模块 URL 解析具名导出,因此不耦合集成产物的格式(ESM/UMD)、导出名或全局名。 * * @public */ export declare type VideoExtensionDefinition = { moduleURL: string; registrationName: string; }; /** * 视频插件运行时的对外入口(由宿主 SDK 暴露给业务侧)。 * * - 一个 `VideoExtensionManager` 实例绑定到一个视频 Track 上,托管其所有插件的生命周期。 * - 业务通过 {@link VideoExtensionManager.useExtension | useExtension} 获取插件代理; * 代理对外暴露 `enable/disable/destroy`、声明式配置入口 `setOptions`,以及 * `on/off` 事件监听,背后通过 {@link VEMMain} 或 {@link VEMWorker} 实际驱动插件运行。 * - `onOutputTrackChanged` 在输出 Track 变化时被调用;`onEmpty` 在最后一个插件销毁后触发, * 宿主可以借此把输出 Track 切回原始源 Track。 * * 后端选择在首次 `useExtension` 时锁定,后续不同的 `renderMode/threadMode` 会被忽略并打 warning。 */ export declare class VideoExtensionManager { private _track; private _options?; private _backend?; private _states; private _main?; private _worker?; private _disposing; private _disposed; /** routed fallback 单次切换守卫:第一个失败的插件触发切换,其余 reject 合流到此 promise。 */ private _fallingBack?; /** 整条 track 是否已进入 routed fallback(插件自有 worker)。 */ private _routed; /** routed fallback 的 loud 日志去重:整条 track 降级只 loud log 一次(track 级状态)。 */ private _fallbackLogged; private _outputTrack?; /** `track-updated` 监听器引用,构造时订阅、`_destroyRuntime` 时解除。 */ private _onTrackUpdated?; onOutputTrackChanged?: (track: MediaStreamTrack | undefined) => void; onEmpty?: () => void; constructor(track: VideoTrackLike); /** 是否仍有未销毁的插件代理。宿主可据此判断是否要把输出 Track 切回原始源。 */ get hasExtensions(): boolean; /** * 当前处理后的输出 track(未启动管线时为 `undefined`)。 * * 供宿主在换源后把发布/播放重新指回处理后的输出,而不是停在裸 origin *(见 `LocalVideoTrack._updateOriginMediaStreamTrack` 的 extension 分支)。 */ get currentOutputTrack(): MediaStreamTrack | undefined; /** * 为指定插件构造器创建(或复用)一个代理。 * * - 同一个构造器在销毁前重复调用会返回同一个代理;初始 `extensionOptions` * 只在首次创建时消费,重复调用传入的 `extensionOptions` 会被忽略 * (要更新配置请调用代理的 `setOptions(...)`)。 * - 首次调用时根据 `options` 解析后端并锁定,后续插件会被强制并入同一条管线。 * - Worker 模式下要求构造器带 `extensionDefinition`,否则抛错。 */ useExtension(ctor: T, options?: ExtensionOptions): InstanceType; /** * 源 Track 被替换时调用。生产路径由构造函数订阅的 `track-updated` 事件驱动并显式传入新源; * 也可由外部直接调用(不传则回读 `this._track.mediaStreamTrack`,供测试/兜底)。 * * `track-updated` 被宿主 SDK 双向复用:既表示「框架输出写回(框架 → SDK)」,又表示 * 「源变更(SDK → 框架)」。本方法据「新源是否等于框架当前输出 track」区分两者: * * - 没有插件 / 正在销毁时为 no-op; * - **输出写回(`next === this._outputTrack`)**:这是框架自己的输出 track 被写回(宿主把处理后的 * 输出赋给 `_mediaStreamTrack`,其 setter 再次 `emit("track-updated")`)。这不是真正的源变更, * **静默忽略**——既避免把自身输出当输入形成自反馈环,也消除了过去每次启用插件都误报的 ERROR; * - **真正的源变更**:转发给当前后端,由后端**只替换上游输入、保持输出 track 不变**(运行时不变量), * 随后把当前输出 track 经 `onOutputTrackChanged` 重新落回宿主,确保换源后发布/播放仍走处理后的输出。 */ updateSourceTrack(newTrack?: MediaStreamTrack): void; /** * 移除单个插件:销毁其代理并从内部状态表移除。 * 与 {@link AudioExtensionManager.removeExtension} 对称,供 SDK Track 调用, * 避免外部反向访问私有 `_states`。代理 `destroy()` 会同步从 `_states` 删除该 * 插件并在清空后异步释放后端,因此调用方随后读取 `hasExtensions` 即为最新值。 * * @param extension 由 `useExtension` 返回的插件代理。 * @returns 找到并移除返回 `true`;未注册返回 `false`。 */ removeExtension(extension: IVideoExtension): boolean; /** * 强制销毁整个 manager:所有代理 state 标记为 destroyed, * 主线程 / Worker 后端依次释放,输出 Track 重置为 undefined。 * Worker 销毁是异步的,但本方法立即清空状态,避免 race condition。 */ destroy(): void; /** 调试:把 context 丢失请求转发给当前后端,便于校验 CPU 回退路径。 */ simulateContextLoss(): void; /** 调试:触发 context 恢复,等同于真实事件。常与 `simulateContextLoss` 成对使用。 */ simulateContextRestore(): void; /* Excluded from this release type: enableStats */ /* Excluded from this release type: getStats */ /* Excluded from this release type: resetStats */ /** * 在首次 `useExtension` 调用时解析并锁定运行时后端; * 后续调用若传入与首次不一致的 renderMode/threadMode,会打 warning 但仍沿用已选后端。 * * 还会针对“auto + Worker 能力足够但缺少 extensionDefinition”的常见配置错误 * 给出主动提示。 */ private _ensureBackend; /** * 串行化单个 proxy 的控制操作,同时保证一次失败不会毒化后续操作队列。 * 普通操作默认只在前序成功后执行;清理类操作可传 `runAfterFailure=true`。 */ private _enqueueStateOp; /** * 把一个插件失败信号通过其代理以 {@link ExtensionEvents.ERROR} 事件派发给业务侧 * (与出站数据同一条 on/emit 通道)。这是补充的"带外"可观测信号,不取代调用处的 * try/catch。`safeEmit` 会吞掉 listener 自身的异常。 */ private _emitExtensionError; /** * 启动期 reject 的分诊(auto-enable 链失败时调用)。 * * worker 后端的「import 失败 → 插件自有 worker fallback」已在 `_addWorker` 内联处理 *(state.ready 直接反映 re-home 结果),因此正常 fallback 路径不会走到这里。 * 所有失败的可观测性都由 loud `logger.error` 承载(不依赖业务订阅,见类型/集成指南的 * 「log 为主通道」约定);本方法只在该插件尚处 `Pending` 时把它推进到 `Terminal`, * 维持「最终启动信号只产出一次」不变量:已离开 `Pending` 的插件(已被 re-home 认领 * 或已 Terminal)其遗留的 auto-enable reject 在此被直接吞掉,避免重复处理。 */ private _onStartupReject; /** * routed fallback 的目标处理模式:worker-cpu 后端 → "cpu",其余(worker-gpu)→ "gpu"。 * 多处 re-home 共用,避免散落的三元判断漂移。 */ private _routedMode; /** * 「未被认领的运行中插件」谓词:仍处 `Pending`(尚未被各自 `_addWorker` 或本轮 sweep 认领) * 的存活插件。`_engageTrackFallback` 据此快照要 re-home 的运行中插件。 */ private _isUnclaimedRunningPlugin; /** * 「活跃 routed 插件」谓词:已成功切到插件自有 worker(`Routed`)的存活插件。 * `_commitRoutedChain` 据此收敛帧链到当前插件集合。 */ private _isRoutedPlugin; /** * 单次触发整条 track 的 fallback,并 re-home **其余正在运行的插件**(那些已在框架共享 worker * 注册成功、正在运行的插件)。触发它的那个失败插件由其自己的 `_addWorker` 内联 re-home * (见 `_rehomePlugin`),不在这里重复处理(靠 `startup` 状态认领去重)。 * * 认领协同:失败插件在 `_addWorker` 先认领自己(`startup` 离开 `Pending`),再调本方法; * 本方法只处理仍处 `Pending` 的运行中插件,每个插件在 `_rehomePlugin` 内同步推进出 `Pending` * 完成认领,从而与失败插件、与并发的多失败 sweep 互斥,保证每个插件最多 re-home 一次。 * * 不改 `this._backend`(仍为 worker-*),不动 capture/output/pump(输出 track 稳定)。 */ private _engageTrackFallback; /** * 把一个**正在共享 worker 上运行的插件**重置并 re-home 到它自有的 worker。 * * 该插件的 `state.ready` 此前已 resolve;这里用一个 deferred 把 `state.ready` 重置为「本次 * re-home」:在 re-home 落定前,它后续排队的代理操作(setOptions/enable 等)都会 await * 这个新的 ready,从而不会撞上一条「指向旧共享-worker 实例」的已 resolve promise。 * 先 `await prevReady` 排空其在途操作,再 re-home;成功则 resolve 并按 `desiredEnabled` * 重新启用,失败则 reject。 */ private _rehomeRunningPlugin; /** * 用当前注册顺序中所有已切到自有 worker 的插件(重)建 routed 帧链。 * 新增/注销插件后调用,把帧链收敛到当前插件集合。 */ private _commitRoutedChain; /** * 把单个插件接到它自有的 worker 上,并产出最终启动信号。 * 返回 true=成功切到插件自有 worker(`Routed`);false=terminal(环境原因,插件没跑起来)。 * * 同步认领该插件(`startup`:`Pending` → `Routing`)以与 sweep 互斥;不在此 enable *(由调用方按各自语义处理:失败插件靠 auto-enable,运行中插件由 `_engageTrackFallback` 重新 enable)。 * * @param announce 是否为该插件派发可订阅的 `worker-fallback` ERROR 事件。仅对「因降级被迫 * 从共享 worker 迁走」的插件(触发者 + 当时在跑的其他插件)置 true;之后**新加入**已降级 * track 的插件置 false——它自己没经历失败,静默接入即可,不应收到「伪失败」事件。 */ private _rehomePlugin; /** 整条 track 首次降级时 loud log 一次(track 级状态,避免按插件数量重复刷屏)。 */ private _logFellBackOnce; /** * 调用插件的 `createFallbackWorker()` 工厂创建其自有 worker。 * 工厂抛错(terminal)时打 loud log 并返回 `undefined`,由调用方据此判定 terminal。 */ private _createPluginWorker; /** 按 worker 分配的插件 id 找到对应的 proxy state(用于把 worker 侧失败映射回代理)。 */ private _findStateById; /** 按主线程实例找到对应的 proxy state(用于把 main-gpu 侧失败映射回代理)。 */ private _findStateByInstance; /** 销毁当前运行时后端并重置 backend/options 锁,供最后一个插件移除后重新选择后端。 */ private _destroyRuntime; /** * 构造插件代理: * - 把 enable/disable/destroy 替换为串行化的、跨进程透明的版本; * 并由代理(而非插件实例)作为生命周期事件 ENABLED/DISABLED 的唯一来源, * 在对应操作的 `state.op` 确认后再 emit,保证事件时序与 await 一致; * - 提供声明式配置入口 `setOptions`:main-gpu 模式直接调本地实例, * worker 模式经 `VEMWorker.setOptions` → `SET_OPTIONS` 消息转发到 Worker; * - 共享同一份 `state` 对象,保证业务侧多次 await 的执行顺序一致。 * * 注:插件入站配置(含图片等不可克隆数据)由 `preprocessMessage` 在发送前 * 转成 Transferable;插件出站数据由插件 `emit`,经 `ON_EMIT` 转发回代理。 */ private _createProxy; /** * Worker/main proxy 都只桥接框架生命周期与 setOptions。保留插件自定义便捷方法, * 但要求它们在【同步执行段内】最终调用 `this.setOptions(...)` 转发配置;否则立即 * 以 rejected promise 报错,避免方法体在空 proxy 上静默执行却让业务以为配置已生效。 * * 判定只看方法的**同步段**:空 proxy 上 await 之后的副作用本就是无意义 no-op, * 真正有效的转发只可能发生在同步段(`return this.setOptions(...)`)。每次调用持有 * 独立 `frame` 且仅在自身同步段内活跃,保证并发的两个自定义方法各自独立判定。 */ private _installCustomMethodGuards; /** 懒创建主线程 VEMMain 实例,并连接其输出 Track 回调到对外的 `onOutputTrackChanged`。 */ private _ensureMain; /** * 懒创建 VEMWorker 实例。把 Worker 侧的 `on-emit` 通知派发到对应代理上, * 让业务侧能像主线程模式一样监听插件事件。 */ private _ensureWorker; /** * 主线程模式下注册插件:实例化、把插件 `emit` 转发到代理事件、调用 `VEMMain.registerExtension`, * 并用初始 `extensionOptions` 调用一次插件 `setOptions`。 * 注册完成后由 `useExtension()` 自动通过代理 enable() 进入帧链。 */ private _addMain; /** * Worker 模式下注册插件:把生效的 moduleURL/registrationName 透传给 Worker, * 由 Worker 动态 import 并实例化插件。`definition` 在 `useExtension` 时已解析 * (集成方 options 覆盖优先于构造器静态 `extensionDefinition`)。 * 初始 `extensionOptions` 随注册消息一并下发,由 Worker 在 init 后调用插件 `setOptions`。 */ private _addWorker; } /** * 视频插件的处理模式。 * @public */ export declare enum VideoExtensionMode { /** GPU Texture 模式(推荐):插件在共享 GL context 上操作 texture,零 CPU 拷贝。 */ TEXTURE = "texture", /** * VideoFrame 模式:插件接收/返回 VideoFrame。 * * ⚠️ 性能注意:在 GPU 管线中,此模式每个插件每帧产生两次 GPU↔CPU 拷贝 * (texture → canvas → VideoFrame → 插件处理 → texImage2D 回 texture), * 在 1080p 下约增加 5-10ms/帧延迟。 * * 适用于需要访问原始像素数据的插件(如基于 CPU/WASM 的图像处理), * 或无法适配 TEXTURE 模式的场景。 * 纯 GPU 处理的新插件推荐使用 TEXTURE 模式以获得最佳性能。 */ VIDEO_FRAME = "videoFrame" } /** @public */ export declare type VideoExtensionThreadMode = "worker" | "main"; /** * 传递给 TEXTURE 模式插件的帧数据。 * * 所有 TEXTURE 模式插件的标准 I/O 契约: * - 输入:textures.input(WebGLTexture,只读) * - 输出:textures.output(WebGLTexture,插件必须写入处理结果) * * ─── 纹理坐标系与方向约定 ─── * * 框架使用 texImage2D 将视频帧上传到 input texture,**不设置 UNPACK_FLIP_Y_WEBGL**。 * 这意味着图像的第一行(视觉上的顶部)存储在纹理坐标 v=0 处(即 GL 帧缓冲的底部)。 * * 框架在最终输出时会执行一次 Y 翻转的 blitFramebuffer: * blitFramebuffer(0, 0, w, h, 0, h, w, 0) * 将帧缓冲底部映射到画布顶部,从而使视频正确显示。 * * 对插件的影响: * - 如果插件只做全帧处理(如超分、美颜),直接操作 input/output texture 即可, * 无需关心方向——输入输出保持一致的方向即可。 * - 如果插件需要在特定像素位置绘制内容(如水印、贴纸),必须注意: * 在 output texture 中,GL 帧缓冲的 y=0(底部)对应最终显示的顶部。 * 因此,像素坐标 (px, py)(左上角原点)转换为 GL NDC 坐标时应使用: * glX = px / width * 2 - 1 (左→右 对应 -1→+1) * glY = py / height * 2 - 1 (上→下 对应 -1→+1,注意:非标准 GL 方向) * 而不是通常的 glY = 1 - py / height * 2。 * - 如果插件通过 readPixels 读取 input texture,返回的像素数据行序为: * 第一行 = 图像顶部(因为图像顶部在帧缓冲底部,readPixels 从底部开始读取)。 * 这与 Canvas 2D 的 getImageData() 行序一致。 * - 如果插件的 WASM 模块渲染到默认帧缓冲(canvas)并使用标准 GL 坐标系 * (y+ = 上 = 视觉顶部),则需要在写入 output texture 时执行一次 Y 翻转 blit: * blitFramebuffer(0, 0, w, h, 0, h, w, 0) * 以匹配框架的纹理方向约定。 * * 插件设计者须知: * 1. 纯 GPU 插件(如 SuperClarity):直接读 input texture,写 output texture。 * 这是最高效的路径,零 CPU 拷贝。 * 3. 渲染到 canvas 的插件:如果 WASM 渲染到共享 canvas(如通过 glfwSwapBuffers), * 插件需要自行 texImage2D(canvas) → output texture。 * * @public */ export declare type VideoFrameTextures = { /** 当前帧的输入纹理(只读)。 */ input: WebGLTexture; /** 插件必须将处理结果写入此纹理。 */ output: WebGLTexture; /** * 当前帧宽度(偶数对齐)。 * 框架会将奇数尺寸向下对齐到偶数,以避免 YUV 色度子采样(NV12/I420) * 在编解码链路中产生绿屏。插件收到的 width/height 与 texture 尺寸一致。 */ width: number; /** * 当前帧高度(偶数对齐)。 * @see width */ height: number; }; /** * 运行时框架使用的 Track 抽象接口。 * * SDK(如 rtcn.js)的 LocalVideoTrack / RemoteVideoTrack 等天然满足此接口, * 无需额外适配。 * * @public */ export declare interface VideoTrackLike { readonly mediaStreamTrack: MediaStreamTrack; /** * 可选事件订阅(宿主 SDK 的 Track 继承自 EventEmitter,天然满足)。框架用它监听 * `"track-updated"` 以在换源时只替换上游输入。非 EventEmitter 的 track-like 可不实现, * 此时框架跳过自动订阅,仍可由外部显式调用 `updateSourceTrack(newTrack)`。 */ on?(event: string, listener: (...args: any[]) => void): void; off?(event: string, listener: (...args: any[]) => void): void; } /** * Worker 消息协议类型枚举。 * * 主线程与 Worker 之间所有 postMessage 通信的消息类型统一在此定义。 * 命名规范:kebab-case。 */ export declare enum WorkerMessageType { START = "start", START_VTG = "start-vtg", STOP = "stop", DESTROY = "destroy", /** * 主线程 → Worker(local 模式换源,Chrome):只换输入,保留输出。 * 携带新源的 `readableStream`(主线程为新 origin 新建的 MSTP.readable,transfer 进来)。 * Worker 先 abort 旧输入 pipe(**保留** `state.writable` 不 abort/close),再用新 readable * 重接到同一个 generator.writable —— 输出 track 身份不变,无需 `onOutputTrackChanged`。 */ SWAP_INPUT = "swap-input", /** * 主线程 → Worker(local 模式换源,Safari VTG):只换输入,保留 VideoTrackGenerator 输出。 * 携带新源 `mediaTrack`(clone 后 transfer)。Worker stop 旧 sourceTrack、用新 track 自建 * MSTP,重接到同一个 VTG.writable —— VTG.track 身份不变,无需 `onOutputTrackChanged`。 */ SWAP_INPUT_VTG = "swap-input-vtg", REGISTER_EXTENSION = "register-extension", UNREGISTER_EXTENSION = "unregister-extension", ENABLE = "enable", DISABLE = "disable", /** 主线程 → Worker:下发插件配置(setOptions) */ SET_OPTIONS = "set-options", /** Worker → 主线程:插件通过 emit 向外推送数据 */ ON_EMIT = "on-emit", /** * 主线程 → 插件 worker:把该插件在帧链中那一段的**输入/输出端**交给它。 * * 帧传输用**原生 MSTP/MSTG**(与本框架本地路径、与老插件模式同一套,已验证不克隆/不泄漏): * - `readable`(**Chrome**):主线程为该 stage 建的 `MediaStreamTrackProcessor.readable`(其源是 * 上一段的输出 track,或首段的 capture track),转移进插件 worker,由插件 worker **原生读取**帧。 * - `writable`(**Chrome**):主线程建的 `MediaStreamTrackGenerator.writable`,转移进插件 * worker,由插件 worker **原生写入**结果帧;其 `.track` 在主线程作为该 stage 的输出,桥接到 * 下一段的 MSTP。 * - **Safari**:MSTP/VTG 仅在 Worker 作用域可用,主线程建不了 MSTP,故不带 `readable`/`writable`, * 改带 `mediaTrack`(本段输入 track,preprocessMessage 会 clone 后 transfer)。插件 worker 内 * 自建 `MediaStreamTrackProcessor`(输入)+ `VideoTrackGenerator`(输出),在**回复**里把 * `{ track }` 交回主线程作为该 stage 输出(Safari 无 `MediaStreamTrackGenerator`)。 * * 插件 worker 内部 `readable.pipeThrough(processFrame).pipeTo(writable)` 全在本 realm, * 不写入任何**跨 realm 的普通 stream**(那才会克隆 VideoFrame)。重建链路时再次下发以换用 * 新的输入/输出端(插件 worker 先 abort 旧 pipe)。 */ CONNECT_FRAME_PIPE = "connect-frame-pipe", /** * 主线程/共享 worker → 插件 worker:初始化插件实例与(GPU 模式下)其自有 GL context。 * 携带 mode(gpu/cpu)。 */ INIT_PLUGIN = "init-plugin", SIMULATE_CONTEXT_LOSS = "simulate-context-loss", SIMULATE_CONTEXT_RESTORE = "simulate-context-restore", /** 主线程 → Worker:开启/关闭逐帧性能采样(调试统计)。 */ ENABLE_STATS = "enable-stats", /** 主线程 → Worker:清零累计的帧/插件耗时统计。 */ RESET_STATS = "reset-stats", LOG = "log", ERROR = "error", ASYNC_ERROR = "async-error", /** * Worker → 主线程:节流推送的逐帧性能统计快照(调试)。 * 主线程缓存最近一次快照,供同步 `getStats()` 读取(worker 路径无法同步回读累加量, * 故用「worker 周期推送 + 主线程缓存」替代请求-响应)。 */ STATS = "stats" } export { }