Monitor Roku

Add FastPix video analytics to any Roku SceneGraph channel with the Data Core SDK: drop-in installation, a three-step tracker integration, view-level QoE metrics, and beacons that are wire-compatible with the FastPix Web SDK.

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 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:

$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:

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.

1<?xml version="1.0" encoding="utf-8" ?>
2<component name="MyPlayerScene" extends="Scene">
3 <children>
4 <Video id="player"
5 translation="[0, 0]"
6 width="1920"
7 height="1080" />
8
9 <FastPixTracker id="fp"
10 workspaceId="YOUR_WORKSPACE_ID"
11 debug="false"
12 beaconDomain="anlytix.io" />
13 </children>
14
15 <script type="text/brightscript" uri="MyPlayerScene.brs" />
16</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.

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.

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.

FieldTypeDefaultPurpose
workspaceIdstringRequiredYour FastPix workspace ID. The SDK uses this value as the https://{workspaceId}.{beaconDomain} subdomain.
videonodeRequiredThe SceneGraph Video node to observe. Set this field before you set control to "start".
dataassocarray{}The per-video metadata bag. For the full schema, see Metadata schema.
debugbooleanfalsePrints SDK diagnostics to the BrightScript console. View them with telnet <roku-ip> 8085.
beaconDomainstring"anlytix.io"The base domain for the collector. Override this value only for non-default deployments.
beaconCollectionDomainstring""A full URL override. This value takes precedence over beaconDomain and workspaceId.
disableCookiesbooleanfalseSkips writing the viewer and session UUID to roRegistrySection. Use this option only if your channel forbids registry writes.
respectDoNotTrackbooleanfalseA kill switch. When set to true, the SDK suppresses every beacon.
sampleRatefloat1.0A 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.
automaticErrorTrackingbooleantrueAutomatically emits error events when Video.errorCode becomes non-zero.
controlstring""Set to "start" to begin tracking or "stop" to tear down.
dispatchassocarrayinvalidSet to { name: "<eventName>", data: { ... } } to fire a custom SDK event from your channel. For more information, see Fire custom events.

Output fields

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

FieldTypeWhat it carries
viewIdstringThe current view_id. This value rotates at every view boundary.
lastBeaconStatusintegerThe HTTP status code of the most recent beacon POST. A value of 0 indicates a transport error.
lastBeaconErrorstringThe 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:

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

KeyTypeExampleNotes
video_idstring"abc-123"Your internal ID for this asset.
video_titlestring"Episode 4: The Heist"The display title.
video_seriesstring"Season 2"The series or season name.
video_producerstring"Studio X"The producer name.
viewer_idstring"user-42"Your stable viewer identifier, such as an account ID or an anonymous device ID.
experiment_namestring"abr-experiment-2025-q1"The A/B test bucket name.

Player

KeyTypeExample
player_namestring"MyChannel Player"
player_versionstring"1.0.0"
player_init_timelongintegerSet automatically by the tracker.

Stream

KeyTypeExample
video_source_urlstring"https://stream.example.com/abc.m3u8"
video_content_typestring"episode", "movie", or "short"
video_stream_typestring"on-demand", "live", or "dvr"
video_durationlonginteger (ms)3600000
video_language_codestring"en-US"
video_cdnstring"akamai" or "cloudfront"
video_drm_typestring"widevine", "playready", or "clear"
video_encoding_variantstring"h264-1080p"

Custom attributes

KeyTypeNotes
custom_1custom_10stringUse 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.

KeySource
workspace_idThe workspaceId attribute on <FastPixTracker>.
player_software_name, player_software_versionSDK constants.
player_fastpix_sdk_name, player_fastpix_sdk_versionSDK constants.
device_manufacturer, device_model, device_name, device_categoryroDeviceInfo.
os_name, os_versionroDeviceInfo.
viewer_connection_typeroDeviceInfo.GetConnectionType(), mapped to wifi, wired, or other.
fastpix_viewer_id, fastpix_sample_numberPersisted in roRegistrySection("FastPix") across launches.
session_id, session_start, session_expiry_timeManaged 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.

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.

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.

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 nameWhen to fireNotes
videoChangeWhen you load a new video into the same tracker.Creates a new view_id and a full state.
programChangeWhen a new episode or segment starts within the same play session.Triggers viewCompleted, viewBegin, and auto-play.
pulseWhen 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.

EventWhen it firesPayload
playerReadyThe tracker started, before the first frame.Delta
viewBeginThe first play of a view.Full state
loadstartInitial buffering (nonebuffering).Delta
playPlayback started or restarted.Delta
playingPlayback is actively advancing.Delta
pauseThe user paused.Delta
waitingA mid-play stall is starting.Delta
bufferingA mid-play stall is confirmed, either by state or by playhead detection.Delta
bufferedA stall ended and playback is resuming.Delta
seekingA position jump was detected.Internal (no beacon)
seekedA seek completed. The payload carries view_seek_duration.Delta
timeupdateA position callback that updates the internal playhead.Internal (no beacon)
variantChangedstreamInfo updated with a new bitrate or resolution.video_source_* fields
pulseA 10-second heartbeat during steady playback.Delta
endedThe video reached its end.Delta
errorVideo.errorCode became non-zero.Delta
viewCompletedThe tracker is stopping and the view is ending.Final summary
requestCompletedA 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:

$telnet <roku-ip> 8085

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

[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.


Troubleshoot common issues

SymptomResolution
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, to create a working integration.

source/main.brs:

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:

1<?xml version="1.0" encoding="utf-8" ?>
2<component name="MyPlayerScene" extends="Scene">
3 <children>
4 <Video id="player" width="1920" height="1080" />
5 <FastPixTracker id="fp"
6 workspaceId="YOUR_WORKSPACE_ID"
7 debug="true" />
8 </children>
9 <script type="text/brightscript" uri="MyPlayerScene.brs" />
10</component>

components/MyPlayerScene.brs:

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.

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.