状态和事件
Sound Show 在认证后及每次变化时发送当前演出状态。请根据这些快照构建控制器,并使用命令参考请求更改。
完整状态
“完整状态”章节state 消息会替换全部本地状态。它会在 hello_ok 后、整体或项目变化后自动发送,也会作为 request_state 的响应。本精简示例显示主要字段;真实项目包含已填充的数组及更多元数据。
{ "type": "state", "protocol_version": 2, "server_time_ms": 1712345678901, "state": { "project": { "name": "My show", "categories": [] }, "players": [], "playable_runtime": [], "playlists": { "current_index": 0, "playlists": [] }, "instructions": { "current_index": 0, "players": [] }, "timers": [], "logical_devices": [], "control": { "next_sound_volume": 70, "selected_category_id": null, "low_volume_active": false, "volume_level_presets": [], "active_volume_level_ids": [], "effective_volume_level_id": null }, "capabilities": { "audio_controls": [], "manual_grid_layout": false }, "audio_controls": { "revision": 1, "logical_devices": [], "players": [] }, "lights": { "revision": 1, "lights": [] } }}请处理缺失或 null 的项目、空数组、可选字段和新增的未知字段。这些文档描述当前 v2 实现。较早的 Sound Show 2 版本可能提供较少的字段、命令或功能;使用可选功能前,请检查 hello_ok.features 和状态中的能力信息。
实时域快照
“实时域快照”章节完整状态之后,大多数运行时更新只替换一个域,即有名称的状态部分。空数组有明确意义,例如 players: [] 会清除当前播放器。不要将数组项合并到旧列表中。
消息 type |
替换的本地字段 | 发送条件 |
|---|---|---|
control_snapshot |
control |
音量级别命令的直接回复;启用 runtime_snapshots 时,所有操作者的控制更新。 |
players_snapshot |
players |
播放器变化。 |
instructions_snapshot |
instructions |
启用 runtime_snapshots 时。 |
playlists_snapshot |
playlists |
启用 runtime_snapshots 时。 |
playable_runtime_snapshot |
playable_runtime |
启用 runtime_snapshots 时。 |
logical_devices_snapshot |
logical_devices |
输出变化。 |
timers_snapshot |
timers |
计时器变化。 |
audio_controls_snapshot |
audio_controls |
效果变化;比较 revision。 |
lights_snapshot |
lights |
灯光变化;比较 revision。 |
每条域消息都包含对应名称的数据字段和 server_time_ms。例如:
{ "type": "players_snapshot", "players": [], "server_time_ms": 1712345678901}音频控制和灯光消息还包含域修订号:
{ "type": "audio_controls_snapshot", "revision": 5, "audio_controls": { "revision": 5, "logical_devices": [], "players": [] }, "server_time_ms": 1712345678901}对于这两个域,请忽略修订号低于该域已应用修订号的消息。不要跨域比较修订号。完整的 state 会重置基准;断开时应清除所有本地状态和修订号记录。其他域按接收顺序应用。
请在 hello.features 中请求 runtime_snapshots。未启用时,除直接控制回复外,控制、指令和播放列表变化会使用合并后的完整状态。对于没有此功能的客户端,仅可播放项活动变化不会触发完整状态。
运行时变化按 50 ms 周期合并。这不保证每 50 ms 都有消息,也不是记录所有中间变化的事件日志。完整状态可以替换待发送的域更新。处理所有当前快照类型的归约器可见 JavaScript 示例。
选择正确目标
“选择正确目标”章节| 目的 | 使用的值 |
|---|---|
| 启动项目按钮 | 将 project.categories[].playables[].id 用作 playable_id。 |
| 选择分类 | 将 project.categories[].id 用作 category_id。 |
| 停止、暂停、跳转或调整一个运行实例 | 将 players[].id 用作 player_id。 |
| 控制输出 | 将 logical_devices[].id 用作 logical_device_id。 |
| 激活音量预设 | 将 control.volume_level_presets[].id 用作 level_id。 |
| 调整效果 | 将描述符的 id 用作 control_id,并指定当前播放器或逻辑设备目标。 |
| 设置灯光级别 | 将 lights.lights[].id 用作 project_light_id。 |
| 播放列表项目 | 当前播放列表和队列中从零开始的位置。 |
| 选择指令 | 当前指令播放器从零开始的位置,以及项目的 path。 |
| 控制计时器 | 将其当前从零开始的位置用作 timer_index。 |
播放器 ID 表示运行中的实例,不应作为永久项目标识保存。一个可播放项可以有多个活动播放器。请在当前播放器快照中查找匹配的 source_playable_id 或 state_playable_id,并明确选择所需实例。名称适合用于显示,但不一定唯一。
项目和可播放项
“项目和可播放项”章节project.categories[] 包含分类的 id、name、color、is_hidden、可选的 icon_url 和 playables。可播放项包括:
{ "id": "playable-id", "name": "Wind", "kind": "sound", "color": "#3b82f6", "tags": [], "is_searchable": true, "runtime_state_key": "playable-id", "target_category_id": null}常见类型有 sound、video、image、random、timer、playlist、action、webbrowser、category_link 和 instruction。请兼容新类型或 unsupported。可选元数据包括 icon_url、键盘快捷键和配置的点击操作 ID。
playable_runtime 中的条目包含 state_playable_id、play_count、is_locked 和 is_playing。将可播放项的 runtime_state_key(如果不存在则使用 id)与 state_playable_id 匹配。链接的可播放项可以共享活动及锁定状态。项目按钮的锁定不同于运行中播放器的控制锁定。
分类还可以包含可选的 grid_layout,其中有 enabled、columns、rows、pages、positions 和 revision。使用前请检查 capabilities.manual_grid_layout。客户端可以忽略此元数据并显示普通可播放项列表;没有远程布局编辑命令。
播放器
“播放器”章节{ "id": "runtime-player-id", "kind": "sound", "title": "Wind", "color": "#3b82f6", "panel_position": 0, "position_seconds": 12.3, "duration_seconds": 95, "start_time_seconds": 5, "stop_time_seconds": 90, "timeline_duration_seconds": 95, "volume_percent": 70, "muted": false, "is_playing": true, "is_locked": false, "loop_enabled": true, "output_logical_device_id": "main", "source_playable_id": "playable-id", "state_playable_id": "playable-id"}直接使用 volume_percent 显示。单独的原始 volume 字段即使存在,也不是可以乘以 100 得到百分比的值;应用可能使用非线性音量刻度。
时间线字段的单位是秒。start_time_seconds、stop_time_seconds 和 timeline_duration_seconds 可能缺失或为 null。跳转时可用它们将裁剪后的播放区间映射到源时间线。其他可选字段包括 remaining_playback_seconds、markers 和 icon_url。
输出和共享控制
“输出和共享控制”章节logical_devices[] 提供 id、label、display_label、color、output_role、available、selected 和 volume_percent。显示使用 display_label,命令使用 id。
control 提供下次启动音量、所选分类,以及 volume_level_presets、active_volume_level_ids 和 effective_volume_level_id。预设包括 id、name、direction(both、up_only 或 down_only)、target_volume_percent、fade_down_ms 和 fade_up_ms。low_volume_active 用于兼容旧控制器。请从快照读取活动 ID,因为其他操作者也可能更改它们。
播放列表、指令和计时器
“播放列表、指令和计时器”章节| 域 | 常用字段 |
|---|---|
playlists |
current_index 和 playlists[]。每个列表有 id、title、autoplay、loop_enabled 和 queue。队列项目包含 playable_id、title 和可选的 icon_url。运行时元数据可能包含 active_item_id、active_item_title 和 active_player_id。 |
instructions |
current_index 和 players[]。播放器有 id、title、has_instruction、waiting_for_go、next_instruction_text 和 items。项目提供 item_id、title、kind、depth、path、can_set_next、is_next_to_play 和 is_visibly_playing。 |
timers |
包含数字 id、mode、is_running、is_locked、elapsed_ms、duration_ms、remaining_ms 和 is_over_time 的数组。计时器时间单位为毫秒。 |
预览 GO 时,优先选择层级最深、path 非空且 kind 不为 group 的下一项目;若没有,则使用任意层级最深的下一项目。选择下一项目时,请使用实际的 path,不要根据标题或深度推断路径。
音频控制和能力信息
“音频控制和能力信息”章节capabilities.audio_controls[] 描述可用的效果控制项。每个描述符提供:
| 字段 | 含义 |
|---|---|
id, label |
协议标识符和显示标签。 |
scopes |
支持的目标类型:logical_device、player 或两者。 |
min, max, default_value |
原生单位数值边界和重置值。 |
suggested_step |
建议的调整步长。 |
unit, scale |
显示单位和控制刻度。log2 表示以八度为单位的步长。 |
display_multiplier |
显示时乘以该值;用户输入在发送前除以该值。 |
例如,比例 0.5 配合 display_multiplier: 100 时显示为 50%。请使用服务器公布的目录,不要将范围写死。
audio_controls 包含 revision、logical_devices[] 和 players[]。每个目标有 kind、id、label、available、locked,以及将控制 ID 映射到原生数值的 values 对象。线性控制按线性变化渐变;log2 控制按几何变化渐变。
灯光
“灯光”章节lights 包含 revision 和 lights[]。每盏灯有 id、name、capability(当前为 level)和 available。底层适配器不可用时会省略 value_percent。请显示为不可用,不要将其视为零。
图标
“图标”章节快照条目可能包含不透明的相对 icon_url,例如 /asset/file?id=opaque-asset-id。请以远程服务器的 HTTP 地址为基准解析,并通过 HTTP 获取。图标不是 WebSocket 消息。
使用提供的 URL,不要自行构造 ID 或发送文件路径。在当前服务器会话内缓存图标,并预计重启后 ID 会变化。项目变化可能导致资源不可用;图片请求失败时应提供替代显示。快照不公开本地文件系统路径。
错误和限制
“错误和限制”章节已认证的连接可能收到:
{ "type": "error", "code": "invalid_value", "command_id": "eq-change-1", "message": "Human-readable error description"}code 和 command_id 为可选项。音频或灯光命令错误可能包含 unknown_control、target_locked、target_not_found、invalid_value、target_unavailable 或 command_failed。旧命令处理路径可能只返回 message。不要将面向人的说明文字解析为稳定的错误代码。发生错误后连接可能仍然打开,请继续处理快照。
| 限制 | 客户端行为 |
|---|---|
| 首条消息须在 10 秒内发送 | 连接打开后立即发送 hello。 |
| 最多 16 个同时存在的 WebSocket 连接 | 复用连接,关闭不用的客户端。未认证的套接字也计入限制。 |
| 每条传入消息/帧最多 64 KiB | 每条消息发送一条小型 JSON 命令。 |
| 突发额度 60 条命令,每个连接每秒补充 20 条 | 限制滑块发送频率,合并待发送值。ping 和刷新请求也消耗此额度。 |
| 同一来源地址在 60 秒内输错密码 5 次 | 更正密码并等待;新的升级请求会被阻止 60 秒,并返回 HTTP 429 和 Retry-After。 |
被限流器拒绝的命令不会执行,并返回 code: "rate_limited"。持续超额可能关闭连接。如果客户端能读取 HTTP 响应,请遵循 Retry-After。若连续认证失败后浏览器只报告连接失败,请至少等待 60 秒再试。不要盲目重试播放、切换或 GO 命令;重试可能触发第二次操作。
心跳和重连
“心跳和重连”章节当 hello_ok.features 包含 ping 时,已认证客户端可以发送 {"type":"ping"} 并等待 {"type":"pong"}。内置遥控器每 10 秒发送一次 ping,并在该次检查时,如果超过 25 秒未收到有效消息则重新连接。未变化的域在其他时候可以保持静默。
对于持续连接的控制器:
- 套接字关闭或心跳失败时立即禁用命令发送。
- 清除缓存状态、播放器 ID 和修订号记录。
- 使用有上限的延迟重新连接,连续失败时增加等待时间。遵循认证错误和频率限制,不要持续重试。
- 发送新的
hello,等待新的完整state,然后重新确定目标。 - 根据该状态重新启用控制。重连后不要重放排队的启动、切换或 GO 命令。
连接范围内的音量按住状态会在断开时释放。即使服务器地址未变,也应将重连视为新会话。
在 Sound Show 中更改允许外部来源会立即生效并断开当前客户端。关闭后,其他来源的浏览器无法重新连接。配置和浏览器限制请参阅自定义浏览器遥控器。