> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://fastpix.com/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://fastpix.com/docs/_mcp/server.

# Monitor Roku

This guide explains how to add FastPix video analytics to a Roku channel. It walks you through installation, a minimal three-step integration, the complete configuration reference, and troubleshooting.

\


## Before you begin

Make sure you have the following:

* A Roku device with [Developer Mode](https://developer.roku.com/docs/developer-program/getting-started/developer-setup.md) enabled.
* A SceneGraph channel project that includes a `manifest`, a `source/` directory, and a `components/` directory.
* A FastPix workspace ID. The SDK uses this value as the beacon subdomain.
* Roku OS 9.1 or later.

\


## How the SDK works

The FastPix Data Core SDK observes a SceneGraph `Video` node and translates Roku state transitions into FastPix events. As playback proceeds, the SDK accumulates view-level quality-of-experience (QoE) metrics such as watch time, rebuffer count, rebuffer duration, rebuffer percentage, seek count, time to first frame, throughput, and scaling ratio and sends them as compact JSON beacons to a FastPix collector.

The SDK is wire-compatible with the FastPix Web SDK, so the same backend ingests data from both. You don't need to make any backend changes.

\


### Add the SDK files to your channel

The SDK ships as BrightScript source files. There is no Roku package manager, so you copy two directory trees directly into your channel.

To install the SDK, copy the `fastpix` folders from the SDK into your channel's `source/` and `components/` directories:

```bash
cp -R source/fastpix      /path/to/your-channel/source/
cp -R components/fastpix  /path/to/your-channel/components/
```

After you copy the files, your channel structure looks like this:

```text
your-channel/
  manifest
  source/
    main.brs
    fastpix/                       # Copied from the SDK
      FastPixCore.brs
      FastPixConstants.brs
      FastPixUtils.brs
      FastPixIdGeneration.brs
      FastPixListenerManager.brs
      FastPixFieldMap.brs
      FastPixFormatter.brs
      FastPixDeviceInfo.brs
      FastPixRegistry.brs
      FastPixDebug.brs
      FastPixState.brs
      FastPixPlaybackEventHandler.brs
      FastPixConnectionHandler.brs
      monitors/
        FastPixErrorManager.brs
        FastPixVideoStateObserver.brs
        FastPixPlayheadObserver.brs
        FastPixVideoSeekTracker.brs
        FastPixVideoBufferMonitor.brs
        FastPixWallClockTimeTracker.brs
        FastPixPlaybackProgressMonitor.brs
        FastPixPlaybackStartupMonitor.brs
        FastPixVideoResolutionHandler.brs
        FastPixRequestMetricsMonitor.brs
  components/
    fastpix/                       # Copied from the SDK
      FastPixTracker.xml
      FastPixTracker.brs
      FastPixBeaconTask.xml
      FastPixBeaconTask.brs
```

That completes the installation. You don't need to vendor anything else.

> **Important:** Copy every file under `source/fastpix/` and `components/fastpix/`. A missing file prevents the component from loading and causes a compile error when you sideload.

\


### Integrate the tracker

After you install the SDK, complete the following three steps to start tracking playback.

#### Declare the tracker in your scene XML

In the scene component that owns your `Video` node, add a `<FastPixTracker>` element as a sibling of the `Video` node. Replace `YOUR_WORKSPACE_ID` with your FastPix workspace key.

```xml
<?xml version="1.0" encoding="utf-8" ?>
<component name="MyPlayerScene" extends="Scene">
    <children>
        <Video id="player"
               translation="[0, 0]"
               width="1920"
               height="1080" />

        <FastPixTracker id="fp"
                        workspaceId="YOUR_WORKSPACE_ID"
                        debug="false"
                        beaconDomain="anlytix.io" />
    </children>

    <script type="text/brightscript" uri="MyPlayerScene.brs" />
</component>
```

\


#### Wire the Video node in your scene's `init()`

Tell the tracker which `Video` node to observe. Set the `video` field before you start the tracker.

```brightscript
sub init()
    m.player = m.top.findNode("player")
    m.fp     = m.top.findNode("fp")

    ' Required: tell the tracker which Video node to observe.
    m.fp.video = m.player
end sub
```

\


#### Set metadata and start tracking before play

Set the per-video metadata, start the tracker, and then start playback. Start the tracker *before* you play the video so that the `playerReady` and `viewBegin` events fire before the first frame.

```brightscript
sub playVideo(videoMetadata as object)
    ' Set per-video metadata. See the metadata schema for the full field list.
    m.fp.data = {
        video_id:          videoMetadata.id
        video_title:       videoMetadata.title
        viewer_id:         m.global.userId       ' Your viewer ID
        video_stream_type: "on-demand"
        player_name:       "MyChannel Player"
        player_version:    "1.0.0"
    }

    ' Build the Roku content node.
    content = CreateObject("roSGNode", "ContentNode")
    content.url          = videoMetadata.streamUrl
    content.streamFormat = "hls"
    m.player.content     = content

    ' Order matters: start the tracker before play.
    m.fp.control     = "start"
    m.player.control = "play"
    m.player.SetFocus(true)
end sub

sub stopVideo()
    m.player.control = "stop"
    m.fp.control     = "stop"
end sub
```

After you complete these steps, the SDK emits beacons to `https://YOUR_WORKSPACE_ID.anlytix.io` for every state change, plus a heartbeat every 10 seconds during steady playback.

\


## FastPixTracker interface reference

The `<FastPixTracker>` component exposes input fields that you set from your channel and output fields that you can observe. Every field is optional unless it's marked as required.

### Input fields

These fields pass data from your channel to the SDK.

| Field                    | Type       | Default        | Purpose                                                                                                                                                            |
| ------------------------ | ---------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `workspaceId`            | string     | *Required*     | Your FastPix workspace ID. The SDK uses this value as the `https://{workspaceId}.{beaconDomain}` subdomain.                                                        |
| `video`                  | node       | *Required*     | The SceneGraph `Video` node to observe. Set this field before you set `control` to `"start"`.                                                                      |
| `data`                   | assocarray | `{}`           | The per-video metadata bag. For the full schema, see [Metadata schema](#metadata-schema).                                                                          |
| `debug`                  | boolean    | `false`        | Prints SDK diagnostics to the BrightScript console. View them with `telnet <roku-ip> 8085`.                                                                        |
| `beaconDomain`           | string     | `"anlytix.io"` | The base domain for the collector. Override this value only for non-default deployments.                                                                           |
| `beaconCollectionDomain` | string     | `""`           | A full URL override. This value takes precedence over `beaconDomain` and `workspaceId`.                                                                            |
| `disableCookies`         | boolean    | `false`        | Skips writing the viewer and session UUID to `roRegistrySection`. Use this option only if your channel forbids registry writes.                                    |
| `respectDoNotTrack`      | boolean    | `false`        | A kill switch. When set to `true`, the SDK suppresses every beacon.                                                                                                |
| `sampleRate`             | float      | `1.0`          | A value from `0.0` to `1.0`. The SDK currently stores and forwards this value but doesn't use it to gate events. It's reserved for future sampling.                |
| `automaticErrorTracking` | boolean    | `true`         | Automatically emits error events when `Video.errorCode` becomes non-zero.                                                                                          |
| `control`                | string     | `""`           | Set to `"start"` to begin tracking or `"stop"` to tear down.                                                                                                       |
| `dispatch`               | assocarray | `invalid`      | Set to `{ name: "<eventName>", data: { ... } }` to fire a custom SDK event from your channel. For more information, see [Fire custom events](#fire-custom-events). |

\


### Output fields

You can observe these fields to monitor the SDK from your channel.

| Field              | Type    | What it carries                                                                                  |
| ------------------ | ------- | ------------------------------------------------------------------------------------------------ |
| `viewId`           | string  | The current `view_id`. This value rotates at every view boundary.                                |
| `lastBeaconStatus` | integer | The HTTP status code of the most recent beacon POST. A value of `0` indicates a transport error. |
| `lastBeaconError`  | string  | The failure reason when `lastBeaconStatus` is not a 2xx code.                                    |

Use the output fields to build an in-app debug overlay or to detect persistent transport failures:

```brightscript
m.fp.observeField("lastBeaconStatus", "onBeaconStatus")

sub onBeaconStatus()
    if m.fp.lastBeaconStatus >= 400
        print "[App] FastPix transport error: "; m.fp.lastBeaconError
    end if
end sub
```

\


## Metadata schema

Set the `m.fp.data` associative array before you set `m.fp.control` to `"start"`. The keys in the following tables are the ones that the FastPix dashboard recognizes. The SDK passes through any other keys that you add, which is useful for custom attributes.

### Identity

| Key               | Type   | Example                    | Notes                                                                           |
| ----------------- | ------ | -------------------------- | ------------------------------------------------------------------------------- |
| `video_id`        | string | `"abc-123"`                | Your internal ID for this asset.                                                |
| `video_title`     | string | `"Episode 4: The Heist"`   | The display title.                                                              |
| `video_series`    | string | `"Season 2"`               | The series or season name.                                                      |
| `video_producer`  | string | `"Studio X"`               | The producer name.                                                              |
| `viewer_id`       | string | `"user-42"`                | Your stable viewer identifier, such as an account ID or an anonymous device ID. |
| `experiment_name` | string | `"abr-experiment-2025-q1"` | The A/B test bucket name.                                                       |

### Player

| Key                | Type        | Example                           |
| ------------------ | ----------- | --------------------------------- |
| `player_name`      | string      | `"MyChannel Player"`              |
| `player_version`   | string      | `"1.0.0"`                         |
| `player_init_time` | longinteger | Set automatically by the tracker. |

### Stream

| Key                      | Type             | Example                                   |
| ------------------------ | ---------------- | ----------------------------------------- |
| `video_source_url`       | string           | `"https://stream.example.com/abc.m3u8"`   |
| `video_content_type`     | string           | `"episode"`, `"movie"`, or `"short"`      |
| `video_stream_type`      | string           | `"on-demand"`, `"live"`, or `"dvr"`       |
| `video_duration`         | longinteger (ms) | `3600000`                                 |
| `video_language_code`    | string           | `"en-US"`                                 |
| `video_cdn`              | string           | `"akamai"` or `"cloudfront"`              |
| `video_drm_type`         | string           | `"widevine"`, `"playready"`, or `"clear"` |
| `video_encoding_variant` | string           | `"h264-1080p"`                            |

### Custom attributes

| Key                      | Type   | Notes                                                                                   |
| ------------------------ | ------ | --------------------------------------------------------------------------------------- |
| `custom_1` … `custom_10` | string | Use these keys for any host-specific attribute that you want the dashboard to facet on. |

### Auto-populated fields

The SDK sets the following fields automatically. Don't set them yourself.

| Key                                                                     | Source                                                                        |
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `workspace_id`                                                          | The `workspaceId` attribute on `<FastPixTracker>`.                            |
| `player_software_name`, `player_software_version`                       | SDK constants.                                                                |
| `player_fastpix_sdk_name`, `player_fastpix_sdk_version`                 | SDK constants.                                                                |
| `device_manufacturer`, `device_model`, `device_name`, `device_category` | `roDeviceInfo`.                                                               |
| `os_name`, `os_version`                                                 | `roDeviceInfo`.                                                               |
| `viewer_connection_type`                                                | `roDeviceInfo.GetConnectionType()`, mapped to `wifi`, `wired`, or `other`.    |
| `fastpix_viewer_id`, `fastpix_sample_number`                            | Persisted in `roRegistrySection("FastPix")` across launches.                  |
| `session_id`, `session_start`, `session_expiry_time`                    | Managed automatically, with a 25-minute idle expiry and a 24-hour hard reset. |

\


## Lifecycle patterns

The way you manage the tracker depends on whether you play a single video, a playlist, or swap content within a single tracker.

### Play a single video and then exit

Start the tracker and player, and then stop both when playback ends. Setting `control` to `"stop"` flushes the final `viewCompleted` beacon.

```brightscript
m.fp.data = { video_id: "abc", video_title: "Demo" }
m.fp.control     = "start"
m.player.control = "play"
' ... user watches ...
m.player.control = "stop"
m.fp.control     = "stop"     ' Flushes the final viewCompleted beacon
```

### Play a playlist

For each new video, stop the tracker, set the new metadata, and restart the tracker.

```brightscript
sub onPlayNext(videoMetadata as object)
    ' Tear down the previous view. This sends viewCompleted.
    m.fp.control     = "stop"
    m.player.control = "stop"

    ' Configure the next video.
    m.fp.data = {
        video_id:    videoMetadata.id
        video_title: videoMetadata.title
        ' ... etc.
    }
    content = CreateObject("roSGNode", "ContentNode")
    content.url          = videoMetadata.streamUrl
    content.streamFormat = "hls"
    m.player.content     = content

    m.fp.control     = "start"
    m.player.control = "play"
end sub
```

### Swap content within a single tracker

To load a new video into the same tracker, for example, when you move from one episode to the next in the same UI fire a `videoChange` event with the `dispatch` field.

```brightscript
m.fp.dispatch = {
    name: "videoChange"
    data: {
        video_id:    nextEpisode.id
        video_title: nextEpisode.title
        video_source_url: nextEpisode.streamUrl
    }
}
```

When you fire a `videoChange` event, the SDK closes the previous view by sending a `viewCompleted` beacon, creates a new `view_id`, and starts a fresh view. You don't need to stop and restart the tracker.

\


## Fire custom events

To fire a custom event, set `m.fp.dispatch` to `{ name: "<eventName>", data: { ... } }`. The following events are available to fire from your channel.

| Event name      | When to fire                                                       | Notes                                                 |
| --------------- | ------------------------------------------------------------------ | ----------------------------------------------------- |
| `videoChange`   | When you load a new video into the same tracker.                   | Creates a new `view_id` and a full state.             |
| `programChange` | When a new episode or segment starts within the same play session. | Triggers `viewCompleted`, `viewBegin`, and auto-play. |
| `pulse`         | When you want to force a heartbeat. Rarely needed.                 | Maps the event to a beacon.                           |

> **Note:** Don't fire playback events such as `play`, `playing`, or `buffering` directly. The SDK derives these events from `Video.state` automatically.

\


## Emitted events reference

The SDK emits the following events, which appear as `event_name=...` in each beacon body. A *full state* event carries the complete view state, and a *delta* event carries only changed fields.

| Event              | When it fires                                                                 | Payload                 |
| ------------------ | ----------------------------------------------------------------------------- | ----------------------- |
| `playerReady`      | The tracker started, before the first frame.                                  | Delta                   |
| `viewBegin`        | The first play of a view.                                                     | Full state              |
| `loadstart`        | Initial buffering (`none` → `buffering`).                                     | Delta                   |
| `play`             | Playback started or restarted.                                                | Delta                   |
| `playing`          | Playback is actively advancing.                                               | Delta                   |
| `pause`            | The user paused.                                                              | Delta                   |
| `waiting`          | A mid-play stall is starting.                                                 | Delta                   |
| `buffering`        | A mid-play stall is confirmed, either by state or by playhead detection.      | Delta                   |
| `buffered`         | A stall ended and playback is resuming.                                       | Delta                   |
| `seeking`          | A position jump was detected.                                                 | Internal (no beacon)    |
| `seeked`           | A seek completed. The payload carries `view_seek_duration`.                   | Delta                   |
| `timeupdate`       | A position callback that updates the internal playhead.                       | Internal (no beacon)    |
| `variantChanged`   | `streamInfo` updated with a new bitrate or resolution.                        | `video_source_*` fields |
| `pulse`            | A 10-second heartbeat during steady playback.                                 | Delta                   |
| `ended`            | The video reached its end.                                                    | Delta                   |
| `error`            | `Video.errorCode` became non-zero.                                            | Delta                   |
| `viewCompleted`    | The tracker is stopping and the view is ending.                               | Final summary           |
| `requestCompleted` | A segment finished downloading, reported by the `downloadedSegment` observer. | Per-request fields      |

\


## Verify the integration

After you integrate the SDK, verify that beacons reach your workspace by using the dashboard, the telnet debug console, or in-app observers.

### Check the FastPix dashboard

1. Sideload your channel and play a video for about 30 seconds.
2. Open the real-time view in your FastPix workspace dashboard.

Within about 15 seconds of the first beacon, the view appears with a **Playing** status. As the view progresses, the dashboard updates with real metrics.

### Use the telnet debug console

Set `debug="true"` on the tracker, and then connect to the BrightScript console:

```bash
telnet <roku-ip> 8085
```

With debugging enabled, the console prints output similar to the following:

```text
[FastPix] tracker started workspaceId=... beaconUrl=https://...anlytix.io
[FastPix] dispatch -> configureView
[FastPix] dispatch -> playerReady
[FastPix] queue beacon: playerReady view=<uuid> playhead=0ms
[FastPix] Video.state none -> buffering
[FastPix] dispatch -> loadstart
[FastPix] Video.state buffering -> playing
[FastPix] dispatch -> play
[FastPix] dispatch -> playing
[FastPix] POST https://...anlytix.io batch=4 bytes=2347
[FastPix] beacon response code=200 rtt=87ms
```

### Use in-app observers

Observe the `lastBeaconStatus` and `lastBeaconError` fields on the tracker to build an in-app debug overlay. This approach is useful in shipped builds where telnet isn't available. For an example, see [Output fields](#output-fields).

\


## Troubleshoot common issues

| Symptom                                                      | Resolution                                                                                                                                                                                             |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| The console shows `[FastPix WARN] 'video' field is not set`. | You didn't set `m.fp.video = m.player` in your scene's `init()`. Set the `video` field before you set `m.fp.control = "start"`.                                                                        |
| The dashboard shows `0` for every metric.                    | Confirm that `workspaceId` matches your FastPix project. Check `m.fp.lastBeaconStatus`. A non-200 status means that beacons aren't landing.                                                            |
| `lastBeaconStatus` is `0`.                                   | The TLS handshake or DNS lookup failed. Confirm that the Roku has internet access and that `beaconDomain` resolves. The `common:/certs/ca-bundle.crt` file is present on retail Roku OS 8.0 and later. |
| `lastBeaconStatus` is a 4xx code.                            | The workspace ID contains a typo, or `beaconDomain` is wrong. The collector subdomain must exist.                                                                                                      |
| `view_dropped_frame_count` is missing.                       | This metric is firmware-specific. Some devices don't expose dropped frames in `streamInfo` or `decoderStats`, so the field stays at `0`.                                                               |
| The channel fails to compile after you sideload it.          | Confirm that you copied every file under `source/fastpix/` and `components/fastpix/`. A missing file breaks the component load.                                                                        |
| The tracker fires `playerReady` but never `viewBegin`.       | `Video.state` never reached `playing`, which usually indicates a stream URL issue. Check the Roku debug console.                                                                                       |

\


## Complete example

Drop the following three files into a fresh Roku channel, alongside the SDK trees that you copied in [the install step](#add-the-sdk-files-to-your-channel), to create a working integration.

`source/main.brs`:

```brightscript
sub Main()
    screen = CreateObject("roSGScreen")
    port = CreateObject("roMessagePort")
    screen.SetMessagePort(port)
    screen.CreateScene("MyPlayerScene")
    screen.Show()
    while true
        msg = wait(0, port)
        if type(msg) = "roSGScreenEvent"
            if msg.IsScreenClosed() then exit while
        end if
    end while
end sub
```

`components/MyPlayerScene.xml`:

```xml
<?xml version="1.0" encoding="utf-8" ?>
<component name="MyPlayerScene" extends="Scene">
    <children>
        <Video id="player" width="1920" height="1080" />
        <FastPixTracker id="fp"
                        workspaceId="YOUR_WORKSPACE_ID"
                        debug="true" />
    </children>
    <script type="text/brightscript" uri="MyPlayerScene.brs" />
</component>
```

`components/MyPlayerScene.brs`:

```brightscript
sub init()
    m.player = m.top.findNode("player")
    m.fp     = m.top.findNode("fp")
    m.fp.video = m.player

    m.fp.data = {
        video_id:          "demo-001"
        video_title:       "Demo Video"
        viewer_id:         "demo-viewer"
        video_stream_type: "on-demand"
        player_name:       "Demo Player"
        player_version:    "1.0.0"
    }

    content = CreateObject("roSGNode", "ContentNode")
    content.url          = "https://test-streams.fastpix.dev/x36xhzz/x36xhzz.m3u8"
    content.streamFormat = "hls"
    m.player.content     = content

    m.fp.control     = "start"
    m.player.control = "play"
    m.player.SetFocus(true)
end sub
```

For a complete reference channel that includes a picker, playback, and a debug overlay, see `samples/host-channel/` in the SDK repository.

\


## Advanced configuration

Use the following patterns to extend your integration.

* **Multiple players per scene:** Instantiate one `<FastPixTracker>` per `Video` node. Trackers don't share state.
* **A/B testing:** Vary the `experiment_name` value in `m.fp.data` per session.
* **Custom dimensions:** Any unknown key that you add to `m.fp.data` rides every beacon. Use `custom_1` through `custom_10` for dimensions that you want the FastPix dashboard to surface as filterable.
* **Final flush:** The `viewCompleted` beacon is sent through the Task thread when you set `m.fp.control = "stop"`. The tracker keeps the Task alive long enough for the beacon to land, so you don't need to wait.
* **Registry usage:** The SDK uses one `roRegistrySection` named `FastPix`. Its footprint is less than 300 bytes.

\


## Release notes

For a complete release history, including new features, improvements, and bug fixes, see the [FastPix Roku SDK CHANGELOG on GitHub](https://github.com/FastPix/Roku-data-core-SDK/blob/main/CHANGELOG.md).

## Get support

To diagnose issues, set `debug="true"` on the tracker and watch the console with `telnet <roku-ip> 8085` while you test. The `lastBeaconStatus` and `lastBeaconError` output fields surface the most recent HTTP result for in-app monitoring.