> For the complete documentation index, see [llms.txt](https://flowout.gitbook.io/vidzflow/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://flowout.gitbook.io/vidzflow/vidzflow-web-app/events.md).

# Events

## Vidzflow Player Events

When you embed a Vidzflow video with an `<iframe>`, the player emits events to the parent window using the browser's [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) API. This lets your page react to what's happening inside the player — track analytics, sync UI, trigger custom behaviour when a video ends, and more.

### Quick start

Add a `message` listener to the window that holds the iframe:

```html
<script>
  window.addEventListener('message', function (event) {
    const data = event.data;

    // Always make sure the message is from a Vidzflow player.
    if (!data || data.source !== 'vidzflow') {
      return;
    }

    switch (data.eventType) {
      case 'videoPlay':
        console.log('Video started playing at', data.currentTime);
        break;
      case 'videoPause':
        console.log('Video paused at', data.currentTime);
        break;
      case 'videoTimeUpdate':
        console.log('Playback position (seconds):', data.currentTime);
        break;
      case 'videoProgress':
        console.log(`Progress: ${data.percent.toFixed(1)}%`);
        break;
      case 'videoEnded':
        console.log('Video finished');
        break;
    }
  });
</script>
```

### Message format

Every message shares the same envelope:

```js
{
  source: 'vidzflow',         // always 'vidzflow' — use this to filter messages
  eventType: 'videoPlay',     // the event name (see the table below)
  videoId: 123,               // the numeric ID of the video
  videoTitle: 'My video',     // the title of the video
  // ...additional fields that depend on the event type
}
```

`event.data` is already a structured JavaScript object — it is **not** a JSON string, so you do **not** need to call `JSON.parse()` on it. (`postMessage` uses the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), which transfers the object directly.) Just read the properties off `event.data`.

### Available events

| `eventType`             | When it fires                                                             | Extra fields                         |
| ----------------------- | ------------------------------------------------------------------------- | ------------------------------------ |
| `videoReady`            | The player has finished initializing and is ready to play.                | `duration`                           |
| `videoPlay`             | Playback starts or resumes.                                               | `currentTime`, `duration`            |
| `videoPause`            | Playback is paused (not fired on the natural end of the video).           | `currentTime`, `duration`            |
| `videoTimeUpdate`       | While playing, roughly once per second. Reports the playback position.    | `currentTime`                        |
| `videoProgress`         | While playing, roughly once per second. Reports progress as a percentage. | `currentTime`, `duration`, `percent` |
| `videoSeeked`           | The user finishes seeking to a new position.                              | `currentTime`, `duration`            |
| `videoVolumeChange`     | The volume changes or the player is muted/unmuted.                        | `volume`, `muted`                    |
| `videoFullscreenChange` | The player enters or exits fullscreen.                                    | `isFullscreen`                       |
| `videoEnded`            | Playback reaches the end of the video.                                    | `duration`                           |
| `videoError`            | The player encounters a playback error.                                   | `code`, `message`                    |

#### Field reference

| Field          | Type      | Description                                                       |
| -------------- | --------- | ----------------------------------------------------------------- |
| `currentTime`  | `number`  | Current playback position, in seconds.                            |
| `duration`     | `number`  | Total length of the video, in seconds (`0` until metadata loads). |
| `percent`      | `number`  | Playback progress as a percentage from `0` to `100`.              |
| `volume`       | `number`  | Current volume from `0` (silent) to `1` (full).                   |
| `muted`        | `boolean` | Whether the player is currently muted.                            |
| `isFullscreen` | `boolean` | Whether the player is currently in fullscreen.                    |
| `code`         | `number`  | The media error code (`1`–`4`), or `null` if unavailable.         |
| `message`      | `string`  | A human-readable description of the error.                        |

### Notes & behaviour

* **`videoTimeUpdate` reports seconds; `videoProgress` reports percentage.** Use `videoTimeUpdate` when you just need the current playback position in seconds, and `videoProgress` when you need progress relative to the total duration.
* **Time events are throttled.** The player would otherwise emit several times per second. To keep the message stream light, `videoTimeUpdate` and `videoProgress` each emit at most once per whole second of playback, only while the video is actually playing (paused and seeking states are skipped).
* **`videoPause` vs `videoEnded`.** When a video plays to its natural end, the player fires `videoEnded` only — it deliberately suppresses the `videoPause` message that would otherwise fire at the same moment.
* **Multiple players on one page.** If you embed more than one video, use the `videoId` field (and/or `event.source`, the specific iframe `window`) to tell the players apart.
* **The player only posts when embedded.** If the page is loaded directly (not in an iframe), no messages are sent.

### Full example: a custom progress bar

```html
<iframe
  id="vidzflow-iframe"
  src="https://app.vidzflow.com/v/your-video-slug"
  allow="autoplay; fullscreen"
  frameborder="0"
></iframe>

<div class="progress"><div class="progress__bar" id="bar"></div></div>

<script>
  const bar = document.getElementById('bar');

  window.addEventListener('message', function (event) {
    const data = event.data;
    if (!data || data.source !== 'vidzflow') return;

    if (data.eventType === 'videoProgress') {
      bar.style.width = data.percent + '%';
    }

    if (data.eventType === 'videoEnded') {
      bar.style.width = '100%';
    }
  });
</script>
```
