ZWMAP 标注类型规范
1. 概述
标注类型用于描述视频画面上的交互区域和时间轴上的交互事件。协议遵循机制与策略分离原则:提供底层 UI 容器和原子动作,业务场景由用户自由组合。
标注数据包含两种元素:
- 热区 (hotspots):在视频画面上显示的交互区域,占据一定位置和时间范围
- 事件 (events):在时间轴上触发的交互面板,如测验、提示、表单等
hotspots 和 events 至少提供一个。
2. ZWMAP 头部
{
"zwp_protocol": "ZWMAP/1.0",
"zwp_type": "annotation"
}
zwp_protocol 和 zwp_type 为必填字段。不支持向后兼容,必须携带 ZWMAP 协议头。
3. 热区对象 (Hotspot)
热区描述视频画面上的一个交互区域。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id |
string | 是 | 唯一标识符 |
time_range |
array | 是 | 显示时间范围 [start, end](秒) |
position |
object | 是 | 位置与尺寸 { "x", "y", "w", "h" }(百分比 0-100) |
action |
object | 否 | 点击触发的动作(与 content 至少提供一个) |
content |
object | 否 | 显示的内容(文本、图片等) |
style |
object | 否 | 样式配置(边框、透明度、背景色、动画等) |
hint |
string | 否 | 悬停提示文字 |
4. 动作类型 (Actions)
所有 Action type 使用 UPPER_SNAKE_CASE。
4.1 媒体控制类
| type | 参数 | 说明 |
|---|---|---|
SEEK_TIME |
target(number), show_back_btn(bool) |
时间轴跳转 |
LOAD_ITEM |
target(string) |
切换播放列表项 |
PAUSE_MEDIA |
无 | 强制暂停 |
PLAY_MEDIA |
无 | 恢复播放 |
4.2 界面与路由类
| type | 参数 | 说明 |
|---|---|---|
OPEN_LINK |
url(string), target(_blank/_self), pause(bool) |
打开外部链接 |
SET_VISIBILITY |
target_id(string), visible(bool), auto_hide(number) |
控制热区显隐 |
4.3 数据与通信类
| type | 参数 | 说明 |
|---|---|---|
EMIT_MESSAGE |
payload(object) |
向宿主环境发送 postMessage |
SUBMIT_DATA |
api_url(string), method(POST/PUT/PATCH), body(object) |
向外部 API 发送请求 |
4.4 状态管理类
| type | 参数 | 说明 |
|---|---|---|
SET_VARIABLE |
key(string), value(any) |
设置会话变量 |
5. 事件类型 (Events)
所有 Event type 使用 UPPER_SNAKE_CASE,代表 5 种基础 UI 容器。
| type | 说明 | 核心字段 |
|---|---|---|
INFO_CARD |
信息卡片/弹窗 | title, content, media_url, buttons |
CHOICE_BOARD |
选择面板(统一 quiz/branch/questionnaire) | prompt, options, is_evaluative, allow_multiple |
INPUT_FORM |
数据表单 | title, fields, submit_url |
TOAST_NOTICE |
轻提示/气泡 | text, position, duration |
WEB_VIEW |
外部视图容器 | src, width, height |
事件对象字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id |
string | 是 | 唯一标识符 |
time |
number | 是 | 触发时间点(秒) |
type |
string | 是 | 事件类型 |
interrupt |
boolean | 否 | 是否中断播放(暂停视频) |
data |
object | 是 | 事件内容数据 |
6. 完整示例
{
"zwp_protocol": "ZWMAP/1.0",
"zwp_type": "annotation",
"zwp_version": "1.0",
"hotspots": [
{
"id": "hs_link_01",
"time_range": [3.0, 15.0],
"position": { "x": 10, "y": 20, "w": 25, "h": 15 },
"action": {
"type": "OPEN_LINK",
"url": "https://www.example.com",
"pause": true
},
"style": {
"border_color": "#FF6B6B",
"border_style": "solid",
"border_width": 1,
"animation": "pulse"
},
"hint": "点击查看详情"
},
{
"id": "hs_msg_01",
"time_range": [10.0, 30.0],
"position": { "x": 70, "y": 5, "w": 15, "h": 8 },
"content": {
"type": "text",
"text": "加入购物车 →",
"style": { "color": "#fff", "font_size": 13 }
},
"action": {
"type": "EMIT_MESSAGE",
"payload": { "action": "ADD_TO_CART", "sku": "12345" }
}
}
],
"events": [
{
"id": "quiz_01",
"time": 60.0,
"type": "CHOICE_BOARD",
"interrupt": true,
"data": {
"prompt": "以下哪个是正确的?",
"is_evaluative": true,
"options": [
{ "text": "选项 A", "value": "A", "is_correct": false },
{ "text": "选项 B", "value": "B", "is_correct": true },
{ "text": "选项 C", "value": "C", "is_correct": false }
],
"feedback": { "correct": "回答正确!", "wrong": "请重试" },
"max_attempts": 2,
"fallback_time": 10.0,
"on_complete": { "type": "PLAY_MEDIA" }
}
},
{
"id": "toast_01",
"time": 15.0,
"type": "TOAST_NOTICE",
"data": {
"text": "精彩内容即将到来!",
"position": "top_center",
"duration": 3
}
}
]
}
7. 约束规则
- 每个
id在整个文档范围内必须唯一(热区和事件之间不可冲突) position的值为百分比(0-100),非像素值time_range的第一个值必须小于第二个值action和content至少提供一个SET_VISIBILITY的target_id必须指向已存在的热区 ID- 事件按
time值触发,播放器应按时间顺序处理 - 未知的 Action 或 Event type 必须静默忽略,不得导致播放器崩溃