管理媒体采集与播放
媒体能力的核心不是一串字节,而是用户授权后产生、可随时撤销的设备句柄。
1. 在用户操作中申请
let camera;
startButton.onclick = async () => {
camera = await host.capture.requestCamera({
facing: 'user', maxWidth: 1280, maxHeight: 720,
});
if (camera) startPreview(camera);
};
用户取消返回 null。不要在定时器或页面启动时自动申请。
2. 读取与背压
async function readOneFrame(camera) {
const bytes = framePool.acquire();
const frame = await host.capture.frame(camera, bytes, 'rgba8');
return { bytes: bytes.subarray(0, frame.written), frame };
}
复用缓冲并由可读通知驱动;忙轮询会浪费配额且增加功耗。
3. 预览或处理
将摄像头帧处理后交给 canvas.present():
const { bytes, frame } = await readOneFrame(camera);
applyEffect(bytes, frame);
await host.canvas.present('preview', bytes, {
...frame,
format: 'rgba8',
});
句柄不穿过 asset/file token;只有已校验字节进入应用处理路径。
4. 播放 guest 产生的音频
const output = await host.playback.open({ channels: 1, latency: 'interactive' });
host.events.on('playback.ready', ({ writableFrames }) => {
host.playback.write(output.output, synth.renderBytes(writableFrames));
});
capture.readAudio() 与 playback.write() 可以组合,但回声消除、混音和重采样属于 guest 库。
5. 撤销与清理
host.events.on('capture.ended', ({ handle }) => detach(handle));
await host.capture.stop(camera);
await host.playback.stop(output.output, 'immediate');
事件送达后句柄已失效。只清理本地状态,不要对失效句柄循环重试。