v0.1首个版本化设计基线查看版本范围
Developer

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 模式、重复停止和跨会话句柄。

Host API 版本与分层概览Host API v0.1版本非正式提议提议canvas API呈现canvas.getInfo()方法canvas.draw()方法canvas.present()方法canvas.measureText()方法canvas.frame event事件canvas.resize event事件asset API呈现asset.read()方法asset.loadFont()方法playback API呈现playback.open()方法playback.write()方法playback.stop()方法playback.ready event事件playback.ended event事件input API输入input.focus()方法input.pointer event事件input.key event事件input.wheel event事件ime API输入ime.openSession()方法ime.updateState()方法ime.updateGeometry()方法ime.closeSession()方法ime.textUpdate event事件ime.composition event事件ime.formatUpdate event事件ime.characterBoundsRequest event事件capture API输入capture.requestCamera()方法capture.requestMic()方法capture.frame()方法capture.readAudio()方法capture.stop()方法capture.ended event事件store API数据store.get()方法store.put()方法store.delete()方法store.list()方法store.deletePrefix()方法clipboard API数据clipboard.readText()方法clipboard.writeText()方法clipboard.readImage()方法clipboard.writeImage()方法net API网络net.fetch()方法identity API身份identity.status()方法identity.login()方法identity.logout()方法env API平台集成env.snapshot()方法env.change event事件print API平台集成print.open()方法开始调用 Host API指南构建交互式绘图应用指南构建自绘文本编辑器指南管理媒体采集与播放指南组织本地数据与同步指南