ZWMAP Annotation Type Specification

1. Overview

The annotation protocol follows a mechanism/policy separation principle: 13 node types provide the UI containers, and the unified StandardAction system drives interactions. Use the online annotation editor to create and edit annotation data visually.

Annotation data is a single unified nodes array. (The legacy hotspots + events dual-array format has been abolished; legacy fields are migrated automatically at load time.)

2. ZWMAP Header

{
  "zwp_protocol": "ZWMAP/1.0",
  "zwp_type": "annotation"
}

Both fields are required.

3. The 13 Node Types

Group Types Notes
Base elements (4) hotspot / text / image / button Rendered inline on the video; support event.actions
Interactive collection (5) choice / quiz / form / vote / card Branch / quiz / form / vote / info card
Advanced display (2) webview / map Embedded iframe page / map
System flow (2) countdown / speed_controller Countdown (fires an action on expiry) / playback rate

Common node fields

Field Type Notes
id string Unique across the document (required)
type string One of the 13 types (required)
time_range array [start, end] in seconds (required, start < end)
position object {x, y, w, h} in percent of the video frame; may be omitted for popup types (centered)
pause_on_show bool Pause playback when entering the range
mandatory bool Anti-skip protection; popup cannot be dismissed unless dismissible: true is set explicitly
hidden bool Initially hidden (can be shown via CONTROL_NODE)
z_index number Stacking order
animation object {enter, emphasis, exit, duration}

4. Actions

All interactions are defined via the event.actions[] array; each entry is { "type": ..., "payload": {...} }:

type payload Notes
SEEK_TIME target, show_back_btn Seek the timeline
LOAD_ITEM target Switch playlist item
PAUSE_MEDIA / PLAY_MEDIA Force pause / resume
OPEN_LINK url, pause Open external link (http/https whitelist)
CONTROL_NODE target_id, operation, auto_hide Show/hide/toggle/activate other nodes
EMIT_MESSAGE params, target_origin postMessage to the host page
SUBMIT_DATA api_url, method, params Submit data to an external API
SET_VARIABLE key, value Set a session variable

5. Full Example

{
  "zwp_protocol": "ZWMAP/1.0",
  "zwp_type": "annotation",
  "zwp_version": "1.0",
  "nodes": [
    {
      "id": "hs_link_01",
      "type": "hotspot",
      "time_range": [3.0, 15.0],
      "position": { "x": 10, "y": 20, "w": 25, "h": 15 },
      "event": {
        "trigger": "click",
        "actions": [
          { "type": "OPEN_LINK", "payload": { "url": "https://www.example.com", "pause": true } }
        ]
      },
      "style": { "border_color": "#FF6B6B", "border_width": 1 },
      "animation": { "enter": "fadein", "emphasis": "pulse", "exit": "none", "duration": 500 },
      "hint": "Click for details"
    },
    {
      "id": "quiz_01",
      "type": "quiz",
      "time_range": [60.0, 90.0],
      "pause_on_show": true,
      "mandatory": true,
      "content": {
        "title": "Knowledge check",
        "prompt": "Which one is correct?",
        "options": [
          { "text": "Option A", "value": "A", "is_correct": false },
          { "text": "Option B", "value": "B", "is_correct": true }
        ],
        "feedback": { "correct": "Correct!", "wrong": "Try again" },
        "max_attempts": 2,
        "fallback_time": 60.0,
        "show_toast": true
      }
    },
    {
      "id": "cd_01",
      "type": "countdown",
      "time_range": [55.0, 62.0],
      "position": { "x": 40, "y": 35, "w": 20, "h": 30 },
      "content": {
        "countdown": 5,
        "pause_during": true,
        "on_expire": { "type": "PLAY_MEDIA", "payload": {} }
      }
    }
  ]
}

6. Constraints

  1. Every id must be unique across the document
  2. position values are percentages (0-100), not pixels
  3. time_range must be an array with the first value smaller than the second
  4. Unknown action/node types are silently ignored (no crash); a single invalid node only skips itself
  5. mandatory popup nodes are non-dismissible by default (unless content.dismissible: true)