Skip to content

Roku Advertising Framework (RAF) Integration

CastConductor integrates with the Roku Advertising Framework (RAF) to enable monetization through pre-roll, mid-roll, and overlay ads. This document explains the technical architecture and flow.

Overview

RAF is Roku's official advertising library. When ads are enabled in your CastConductor settings, the Roku app will:

  1. Request ads from the configured VAST endpoint (or Roku's default)
  2. Display video ads before your content starts (pre-roll)
  3. Optionally show ads during playback (mid-roll) or as overlays

Architecture

Separate Video Nodes

CastConductor uses separate video nodes for content and ads:

Node Purpose Location
m.videoPlayer HLS video content / background video MainScene.xml
m.liveAudioPlayer Audio streaming (MP3/AAC) MainScene.xml
m.adVideo RAF video ads AdManager.xml

This separation ensures clean handoffs between content and ads.

How showAds() Works

RAF's showAds() function is blocking - when called, your BrightScript execution pauses completely until all ads finish or the user exits:

Your Code → showAds() → [RAF TAKES FULL CONTROL] → showAds() returns → Your Code Resumes

During ad playback: - RAF handles all rendering in the provided video node - RAF manages user input (back button, etc.) - RAF displays standard UI ("Ad 1 of 3", progress bar) - Your code is completely paused

Playback Flows

Audio-Only Mode

When your channel is playing audio (radio stations, podcasts):

┌────────────────────────────────────────────────────────┐
│ 1. Audio playing in m.liveAudioPlayer                  │
│                                                        │
│ 2. Pre-roll triggers                                   │
│    → Audio PAUSED (not muted)                          │
│                                                        │
│ 3. showAds(pods, invalid, m.adVideo) called            │
│    ┌────────────────────────────────────┐              │
│    │ RAF TAKES CONTROL                  │              │
│    │ • Video ad renders in m.adVideo    │              │
│    │ • Ad audio plays through TV        │              │
│    │ • Your audio is paused             │              │
│    │ • User watches ad(s)               │              │
│    │ • All ads complete                 │              │
│    └────────────────────────────────────┘              │
│                                                        │
│ 4. showAds() returns true/false                        │
│                                                        │
│ 5. Audio RESUMED                                       │
│    → m.liveAudioPlayer.control = "play"                │
└────────────────────────────────────────────────────────┘

HLS Video Mode

When your channel is playing HLS video streams:

┌────────────────────────────────────────────────────────┐
│ 1. HLS video playing in m.videoPlayer                  │
│                                                        │
│ 2. Pre-roll triggers                                   │
│    → Video PAUSED (stays visible but frozen)           │
│                                                        │
│ 3. showAds(pods, invalid, m.adVideo) called            │
│    ┌────────────────────────────────────┐              │
│    │ RAF TAKES CONTROL                  │              │
│    │ • Ad video renders in m.adVideo    │              │
│    │ • m.adVideo appears on top         │              │
│    │ • m.videoPlayer still exists       │              │
│    │   (paused, behind ad layer)        │              │
│    │ • User watches ad(s)               │              │
│    │ • All ads complete                 │              │
│    └────────────────────────────────────┘              │
│                                                        │
│ 4. showAds() returns                                   │
│                                                        │
│ 5. Video RESUMED                                       │
│    → m.videoPlayer.control = "play"                    │
│    (Live streams catch up to live edge)                │
└────────────────────────────────────────────────────────┘

Key Implementation Details

Required Parameters

For SceneGraph applications, showAds() requires a Video node as the third parameter:

' WRONG - will crash on SceneGraph!
adResult = m.adFramework.showAds(adPods)

' CORRECT - pass video node for rendering
adResult = m.adFramework.showAds(adPods, invalid, m.adVideo)

Content Metadata

RAF requires content metadata for ad targeting and measurement compliance:

m.adFramework.setContentId(appInfo.GetTitle())  ' Channel name
m.adFramework.setContentLength(86400)           ' 24 hours for live streams
m.adFramework.setContentGenre("music")          ' Content category
m.adFramework.enableAdMeasurements(true)        ' RAF 1.3 requirement

Pause/Resume Pattern

Content must be paused before ads and resumed after:

sub onAdPlayingStateChanged()
    isAdPlaying = m.adManager.isAdPlaying

    if isAdPlaying = true then
        ' PAUSE content during ad
        if m.liveAudioPlayer <> invalid then
            m.liveAudioPlayer.control = "pause"
        end if
        if m.videoPlayerMode = "livestream" then
            m.videoPlayer.control = "pause"
        end if
    else
        ' RESUME content after ad
        if m.liveAudioPlayer <> invalid then
            m.liveAudioPlayer.control = "play"
        end if
        if m.videoPlayerMode = "livestream" then
            m.videoPlayer.control = "play"
        end if
    end if
end sub

Anti-Patterns to Avoid

❌ Wrong ✅ Correct
Muting audio while ad plays Pause audio completely
Using same video node for ads and content Separate video nodes
Playing ad audio over content audio Pause content first
Hiding video visually Just pause - RAF handles z-order
Forgetting showAds() view parameter Always pass video node for RSG

Ad Server Configuration

Default Roku Ads

When no custom VAST URL is configured, CastConductor uses Roku's default ad service. Note that default ads only serve to published channels - sideloaded/dev apps will receive "no fill".

Custom VAST

Publishers can configure custom VAST endpoints in the WordPress plugin settings to use their own ad networks (FreeWheel, Google Ad Manager, etc.).

Debugging

Enable RAF debug output to see ad requests and responses:

m.adFramework.setDebugOutput(true)

Debug output appears in telnet on port 8085:

[RAF] setDebugOutput(true)
[RAF] Roku_Ads Framework version 3.2021
📺📺📺 setContentId(CastConductor)
📺📺📺 setContentLength(86400)
📺📺📺 Calling getAds()...
📺📺📺 Got 1 ad pod(s)!