playback
playback 把 guest 生成的 PCM 采样送到系统音频输出。宿主代持音频设备和有界缓冲;guest 不获得 Web Audio context、设备对象或任意音频图。
适用场景
- 游戏或模拟器实时生成声音。
- 语音合成、音乐工具或解码器播放自己产生的 PCM。
- 需要可观察背压的连续音频流。
播放包内压缩音频时,guest 先用 L2 codec 解码;多轨混音、效果器、空间音频和音乐时间线也属于 guest 库。
能力声明
{
"capabilities": [
"host:playback.open",
"host:playback.write",
"host:playback.stop"
]
}
Reference
数据类型
PlaybackOptions {
preferredSampleRate?: u32
channels?: 1 | 2
latency?: "interactive" | "balanced"
}
PlaybackStream {
output: Handle<audio-output, write, app-context, session>
sampleRate: u32
channels: 1 | 2
maxQueuedFrames: u32
}
v0.1 的采样格式固定为交错、little-endian f32 PCM,有限值范围为 [-1, 1]。宿主可以钳制越界有限值,但必须拒绝 NaN 和 Infinity。
方法
playback.open
playback.open(options?) -> PlaybackStream
创建一次会话级输出并返回宿主实际采用的采样率、声道数和最大排队帧数。偏好不是强制要求;guest 必须按返回值生产或重采样。
playback.write
playback.write(output, samples: ByteSource) -> {
acceptedFrames: u32,
queuedFrames: u32,
droppedFrames: u32
}
提交完整采样帧。字节数必须是 channels × 4 的整数倍。返回值使背压和丢帧可观察;宿主不得无限扩张缓冲。
playback.stop
playback.stop(output, mode? = "drain") -> void
drain 播放完已接受的数据后关闭,immediate 丢弃缓冲并立即关闭。重复停止是幂等操作。
事件
event playback.ready {
output,
writableFrames: u32
}
event playback.ended {
output,
reason: "stopped" | "device-lost" | "permission-revoked" | "error"
}
ready 是继续写入的提示,可以合并;guest 仍必须以 write 返回值为准。
JS 工作流示例
下面按宿主实际采样率生成 440 Hz 正弦波:
const stream = await host.playback.open({
preferredSampleRate: 48_000,
channels: 1,
latency: 'interactive',
});
let phase = 0;
host.events.on('playback.ready', async ({ output, writableFrames }) => {
if (output !== stream.output) return;
const frames = Math.min(writableFrames, 1024);
const pcm = new Float32Array(frames);
for (let i = 0; i < frames; i++) {
pcm[i] = Math.sin(phase);
phase += (2 * Math.PI * 440) / stream.sampleRate;
}
await host.playback.write(stream.output, new Uint8Array(pcm.buffer));
});
用户停止时应显式选择是否排空:
await host.playback.stop(stream.output, 'immediate');
原生 WASM 绑定示例
PlaybackStream stream = host_playback_open((PlaybackOptions){48000, 2, INTERACTIVE});
float pcm[1024 * 2];
mix_stereo(pcm, 1024, stream.sample_rate);
PlaybackWriteResult r = host_playback_write(
stream.output,
byte_source((uint8_t *)pcm, sizeof(pcm))
);
if (r.accepted_frames < 1024) retain_unwritten_tail(r.accepted_frames);
guest 应保留未接受的尾部,等待下一次 ready,而不是忙循环重试。
生命周期与用户激活
- 输出句柄属于当前应用会话,不能持久化。
- 浏览器要求时,第一次
open或首次发声必须继承可信用户操作;缺少激活返回activation-required。 - 页面隐藏时宿主可以降低速率或暂停设备,但必须通过事件使状态可观察。
- 设备断开后句柄失效并发送
ended。
错误
| code | 含义 | retryable |
|---|---|---|
activation-required |
平台要求用户操作后才能开始播放 | true |
invalid-format |
字节数、采样值或声道布局非法 | false |
stale-handle |
输出已关闭或属于其他会话 | false |
device-unavailable |
当前没有可用输出设备 | true |
limit-exceeded |
输出数、排队帧或写入速率超预算 | true |
安全与预算
- 信任档位:yellow,因为它产生共享硬件副作用。
- 每个 guest 的活跃输出数、最大排队时长、单次帧数和时间窗总采样数必须有上限。
- 宿主不得自动选择或泄露具体设备标识。
- 页面不可见、系统静音和用户撤销必须由宿主优先执行。
- 错误数据不能进入底层音频后端。
不属于本 API
编解码、混音、音频节点图、播放列表、媒体元数据和录音不属于 playback。麦克风输入见 capture。
一致性测试
宿主至少覆盖:采样率协商、单/双声道、部分接受、缓冲溢出、NaN/Infinity、隐藏页面、设备断开、两种 stop 模式、重复停止和跨会话句柄。