Roku SceneGraph Direct Integration Guide for Server-Side Ad Insertion (SSAI)

Framework Version: 3.0.3
Last Updated: 09/25/2023
Supports: SceneGraph
Engineer and co-author: Elias Kahwaji

The channel integration sample included in this document demonstrates a simple integration of the BrightLine Roku Direct SDK within a SceneGraph Roku application that does not rely on a RAF implementation.

Video player implementation considerations:
We'll provide a base SDK for you to integrate. Most video player implementations are unique per channel. We'll work closely with you to make any necessary SDK augmentatations to support your particular video player implementation.
 
Table of Contents:
  • Step 1: BrightLineDirect loading
  • Step 2: BrightLineDirect initialization
  • Step 3: Observers and Callbacks
  • 3.1 State observer
  • 3.2 Event observer
  • Step 4: Setting Ad object
  • Entire code snippet of integration example
  • Release notes
  • Downloads
 

Step 1: BrightLineDirect loading

In your channel's code, you'll load BrightLineDirect using component library. We'll provide you with your BrightLine SDK pkg file location url in our kick-off meeting. The following is a sample load:

                
sub BrightLine_LoadAPI(pkgUrl as String)
    print "Channel _____BrightLineDirect:BrightLine_LoadAPI"
    m.brightlineLoaded = false
    ' Cleaning previous version of the lib
    BrightLine_Cleanup()
    ' Load the SDK using component library
    m.adlib = CreateObject("roSGNode", "ComponentLibrary")
    m.adlib.ObserveField("loadStatus", "BrightLine_LoadStatus")
    m.adlib.SetField("uri", pkgUrl)
end sub

sub BrightLine_Cleanup()
    ' Invalidate previous instance of BrightLineDirect
    m.top.RemoveChild(m.BrightLineDirect)
    m.BrightLineDirect = invalid
end sub

sub BrightLine_LoadStatus()
    print " _____onAdLibLoadStatusChanged: " m.adlib.loadStatus
    print "m.adlib: " m.adlib

    ' Here we check the load status of the component library
    if (m.adlib.loadStatus = "ready")
        BrightLine_RunEnvironmentTask() ' just for demo purpose printing environment info
        m.brightlineLoaded = true
    else if m.adlib.loadStatus = "failed" then
        m.brightlineLoaded = false
    end if
end sub
                
            
 

Step 2: BrightLineDirect initialization

To initialize library you should set the configID, which we'll be provided to you in our kick-off meeting, "1018" value can be used for development purposes. Second part is to provide library with video node and it's width/height using action interface. You can create new instance of the library each time you need it or you can use lib as singleton object, initialize it once and then just setting ad object each time you want to show the ad.

                
sub CreateBrightLineDirect(videoNode as Object)
    ' Createing the BrightLine SDK
    BrightLine_CreateBLDirect()
    ' Here are three examples of "setting an action object"
    ' Set a pointer to the stream player, player must be set before setting the ad
    ' Player should be actual video node that will do playback
    m.BrightLineDirect.action = {video: videoNode}
    ' Set the width of the UI
    m.BrightLineDirect.action = {width: 1920}
    ' Set the height of the UI
    m.BrightLineDirect.action = {height: 1080}
    ' Here we set the trackers object
    m.BrightLineDirect.trackers = GetTrackers()
    ' We can make BrightLineDirect visible now.
    ' We'll use m.BrightLineDirect.state callback to set ad and focus.
    m.BrightLineDirect.visible = true
end sub

sub BrightLine_CreateBLDirect()
    ' Making sure previous SDK instance is removed and cleaned by garbage collector
    BrightLine_Cleanup()
    ' Creating the BrightLine SDK
    m.BrightLineDirect = CreateObject("roSGNode", "BrightLineDirect:BL_init")
    ' m.BrightLineDirect.showDebug = true
    m.top.AppendChild(m.BrightLineDirect)
    ' Set config ID to BLDirect to initialize it (it will be provided, for testing 1018 can be used)
    ' This is critical part, channel must set config id before setting ad
    m.BrightLineDirect.configID = "1018"
    ' We observe the "state" field of the library. This will direct us to make it visible and focused, or not.
    m.BrightLineDirect.ObserveField("state", "BrightLine_OnStateChange")
    ' This is the event listener. This observer is optional and channel can ommit it in most cases.
    ' It tells you when BrightLineDirect expanding and collapsing the microsite.
    m.BrightLineDirect.ObserveField("event", "BrightLine_OnEvent")
end sub
                
            
 

Step 3: Observers and Callbacks

 

3.1 State observer:

Channel should observe state field to know when the library needs focus, visibility or signals the ad exited state. Different templates have different states flow:

Template States Flow
Overlay/StoreLocator ready -> starting -> showing -> overlay_did_open -> overlay_did_close -> ready
Microsite ready -> starting -> showing -> overlay_did_open -> overlay_did_close_to_open_microsite -> microsite_did_open -> microsite_did_close -> ready
AdSelector ready -> starting -> showing -> overlay_did_open -> overlay_did_close -> (microsite_did_open -> microsite_did_close) -> ad_selector_completed -> ready

Below is calback example with best practices of handling different states:

                
sub BrightLine_OnStateChange(msg as Object)
    state = msg.getData()
    print "~~~~~ onBrightLineDirectStateChange", state
    if state = "initialized" then
        print "BrightLine INIT"
        ' Lib is initialized, here we can provide our ad object.
        ' Ad will be shown according to startAt field of the ad object, based on video position
        m.BrightLineDirect.ad = m.currentAd
    else if state = "showing" then
        print "BrightLine SHOWING"
        ' BrightLineDirect needs focus when it's in it's "showing" state
        m.BrightLineDirect.SetFocus(true)
    else if state = "ready" then
        print "BrightLine READY"
        ' Ready is set when lib is initialized and not showing ad.
        ' Send focus back to whatever you want.
        ' In this case we're using the video player.
        m.componentController.currentView.SetFocus(true)
    else if state = "ad_selector_completed" then
        ' To not show stream-stitched ad after ad selector we can skip to needed position in this callback
        m.componentController.currentView.seek = m.adStartTime + m.adDuration
    else if state = "exited" then
        ' BrightLine has received an "exit" signal
        ' this is the equivalent of pressing the "back" key in a stream.
        print "BrightLine EXITED"
        ' this will close player screen
        m.componentController.currentView.exited = true
        m.BrightLineDirect.visible = false
    end if
end sub
                
            
 

3.2 Event observer:

Event field is optional to observe and channel should fully rely on state field. Below is callback with possible events currently fired.

                
sub BrightLine_OnEvent(msg as Object)
    ' Callback for events coming from the BrightLineDirect library.
    blEvent = msg.GetData()
    print "Channel _____onBrightLineAPI_event", blEvent
    if blEvent.type = "UI"
        ' This type of events are fired when library UI was changed
        ' Currently it fires only when microsite is opened/closed
        if blEvent.value = "expanded"
            ' microsite was opened to full screen
        else if blEvent.value = "collapsed"
            ' microsite was collapsed and closed
        end if
    else if blEvent.type = "DL"
        ' This event is fired only by Conversion Template. It indicates when user presses on template
        ' blEvent.value will contain deeplink string for this conversion screen
    end if
end sub
                
            
 

Step 4: Setting Ad object

The method outlined below will prepare one ad for the library. The ad object which channel is setting to lib should have the same format as below. Library will be observing the video player, will start ad showing based on ad.startAt and will hide overlay at ad.startAt+ad.duration.

                
function GetAdObject(adUrl as String) as Object
    return {
        adId: 0315281           ' Populate from your ad
        contentId: 1426392      ' Populate from your ad
        adName: "single ad"     ' Populate from your ad
        provider: "BrightLine"  ' This must be "BrightLine" for the library to work.
        adURL: adUrl            ' This is where the ad JSON url is passed into the single-ad structure
        startAt: m.adStartTime  ' This is when in the stream's progress the ad should appear.
        duration: m.adDuration  ' This is how long the ad should play.
        tracking: [             ' This array is the created from the ad server response.
            {
                event: "AdStart"
                time: 12
                url: "http://www.foo.com/track"
                triggered: false
            },
            {
                event: "AdComplete"
                time: 42
                url: "http://www.foo.com/track"
                triggered: false
            }
        ]
    }
end function
                
            
 

Entire code snippet of integration example

MainScene.brs:
                
sub init()
    m.componentController = m.top.findNode("componentController")
    ShowAdSelectionScreen()
end sub

sub ShowAdSelectionScreen()
    screen = CreateObject("roSGNode", "AdSelectionScreen")
    screen.ObserveFieldScoped("selectedSDK", "OnSDKChanged")
    screen.ObserveFieldScoped("selectedAd", "OnAdSelected")
    m.componentController.callFunc("show", { view: screen })
end sub

sub OnSDKChanged(event as Object)
    sdk = event.GetData()
    if sdk <> invalid and sdk.pkg <> invalid
        BrightLine_LoadAPI("pkg:/components/app.zip")
    end if
end sub

sub OnAdSelected(event as Object)
    m.selectedAd = event.GetData().Clone(false)
    SetAdConstants()
    ' This is the setup for an ad using BrightLineDirect:
    if m.brightlineLoaded = false then
        print "*************************** (BrightLineDirect is NOT LOADED) ***************************"
    else if m.brightlineLoaded = true then
        ' Here I'm getting the ad JSON url. This can be any approach you choose, but it'll be in the companion
        ' node, and have a "Brightline_RSG" subvert
        newAd = event.GetData()
        print"newAd: "newAd.url
        if lcase(newAd.urlType) = "vast"
            vastParser = CreateObject("roSGNode", "VastParserTask")
            vastParser.vastUrl = newAd.url
            vastParser.ObserveFieldScoped("vastData", "OnVastData")
            vastParser.control = "run"
        else ' standard json ad url
            StartBrightLinePlayback(newAd.url)
        end if
    end if
end sub

sub SetAdConstants()
    m.adStartTime = 12
    m.adDuration = 30
    if m.selectedAd <> invalid
        if m.selectedAd.ad_start <> invalid then m.adStartTime = m.selectedAd.ad_start
        if m.selectedAd.ad_duration <> invalid then m.adDuration = m.selectedAd.ad_duration
    end if
end sub

sub OnVastData(event as Object)
    vastData = event.GetData()
    if vastData <> invalid
        m.selectedAd.Append({
            apiFramework: vastData.apiFramework
        })
        StartBrightLinePlayback(vastData.adJsonUrl.trim())
    end if
end sub

sub StartBrightLinePlayback(adUrl as Object)
    adTitle = m.selectedAd.title
    videoURL = "http://cdn-media.brightline.tv/videos/ads/server_side_stitching_test/showmewhatyougot02/showmewhatyougot02.m3u8"
    if m.selectedAd.mediafile <> invalid then videoURL = m.selectedAd.mediafile
    videoNode = ShowPlayerScreen(videoURL)
    ' Creating and saving ad, ad should be set after BLDirect is initialized (state callback).
    m.currentAd = GetAdObject(adUrl)
    ' Creating instance of the library and setting needed interfaces
    CreateBrightLineDirect(videoNode)
    ' starting from v3.0.2 we can set ad object here and lib will handle it properly
    ' m.BrightLineDirect.ad = GetAdObject(adUrl)
end sub

sub CreateBrightLineDirect(videoNode as Object)
    ' Createing the BrightLine SDK
    BrightLine_CreateBLDirect()
    ' Here are three examples of "setting an action object"
    ' Set a pointer to the stream player, player must be set before setting the ad
    ' Player should be actual video node that will do playback
    m.BrightLineDirect.action = {video: videoNode}
    ' Set the width of the UI
    m.BrightLineDirect.action = {width: 1920}
    ' Set the height of the UI
    m.BrightLineDirect.action = {height: 1080}
    ' Here we set the trackers object
    m.BrightLineDirect.trackers = GetTrackers()
    ' We can make BrightLineDirect visible now.
    ' We'll use m.BrightLineDirect.state callback to set ad and focus.
    m.BrightLineDirect.visible = true
end sub

function ShowPlayerScreen(videoURL as String) as Object
    playerScreen = CreateObject("roSGNode", "PlayerScreen")
    videoPlayer = playerScreen.FindNode("videoPlayer")

    ' creating and setting video node content
    videoContent = createObject("RoSGNode", "ContentNode")
    videoContent.url = videoURL
    videoPlayer.content = videoContent

    ' showing screen and starting playback
    m.componentController.callFunc("show", { view: playerScreen })
    videoPlayer.control = "play"
    return videoPlayer
end function

function GetAdObject(adUrl as String) as Object
    return {
        adId: 0315281           ' Populate from your ad
        contentId: 1426392      ' Populate from your ad
        adName: "single ad"     ' Populate from your ad
        provider: "BrightLine"  ' This must be "BrightLine" for the library to work.
        adURL: adUrl            ' This is where the ad JSON url is passed into the single-ad structure
        startAt: m.adStartTime  ' This is when in the stream's progress the ad should appear.
        duration: m.adDuration  ' This is how long the ad should play.
        tracking: [             ' This array is the created from the ad server response.
            {
                event: "AdStart"
                time: 12
                url: "http://www.foo.com/track"
                triggered: false
            },
            {
                event: "AdComplete"
                time: 42
                url: "http://www.foo.com/track"
                triggered: false
            }
        ]
    }
end function

function GetTrackers() as Object
    ' This is your tracking array for the spot quartiles.
    return [
        {
            type:"Impression"
            url:"http://events.brightline.tv/track?data=%7B%22type%22%3A%22impression%22%2C%22valid%22%3Atrue%2C%22ad_id%22%3A2344293%7D"
            time:"12"
            triggered: false
        },

        {
            type:"FirstQuartile"
            url:"http://events.brightline.tv/track?data=%7B%22type%22%3A%22duration%22%2C%22duration_type%22%3A%22impression%22%2C%22percent_complete%22%3A25%2C%22ad_id%22%3A2344293%7D"
            time:"18"
            triggered: false
        },

        {
            type:"Midpoint"
            url:"http://events.brightline.tv/track?data=%7B%22type%22%3A%22duration%22%2C%22duration_type%22%3A%22impression%22%2C%22percent_complete%22%3A50%2C%22ad_id%22%3A2344293%7D"
            time:"27"
            triggered: false
        },

        {
            type:"ThirdQuartile"
            url:"http://events.brightline.tv/track?data=%7B%22type%22%3A%22duration%22%2C%22duration_type%22%3A%22impression%22%2C%22percent_complete%22%3A75%2C%22ad_id%22%3A2344293%7D"
            time:"33"
            triggered: false
        },

        {
            type:"Complete"
            url:"http://events.brightline.tv/track?data=%7B%22type%22%3A%22duration%22%2C%22duration_type%22%3A%22impression%22%2C%22percent_complete%22%3A100%2C%22ad_id%22%3A2344293%7D"
            time:"42"
            triggered: false
        },
    ]
end function
                
            

BrightLineDirect.brs:
                
sub BrightLine_LoadAPI(pkgUrl as String)
    print "Channel _____BrightLineDirect:BrightLine_LoadAPI"
    m.brightlineLoaded = false
    ' Cleaning previous version of the lib
    BrightLine_Cleanup()
    ' Load the SDK using component library
    m.adlib = CreateObject("roSGNode", "ComponentLibrary")
    m.adlib.ObserveField("loadStatus", "BrightLine_LoadStatus")
    m.adlib.SetField("uri", pkgUrl)
end sub

sub BrightLine_Cleanup()
    ' Invalidate previous instance of BrightLineDirect
    m.top.RemoveChild(m.BrightLineDirect)
    m.BrightLineDirect = invalid
end sub

sub BrightLine_LoadStatus()
    print " _____onAdLibLoadStatusChanged: " m.adlib.loadStatus
    print "m.adlib: " m.adlib

    ' Here we check the load status of the component library
    if (m.adlib.loadStatus = "ready")
        BrightLine_RunEnvironmentTask() ' just for demo purpose printing environment info
        m.brightlineLoaded = true
    else if m.adlib.loadStatus = "failed" then
        m.brightlineLoaded = false
    end if
end sub

sub BrightLine_RunEnvironmentTask()
    m.environmentInfoTask = createObject("roSGNode", "EnvironmentInfoTask")
    #if debug
        m.environmentInfoTask.showDebug = true
    #end if
    m.environmentInfoTask.control = "run"
end sub

sub BrightLine_CreateBLDirect()
    ' Making sure previous SDK instance is removed and cleaned by garbage collector
    BrightLine_Cleanup()
    ' Creating the BrightLine SDK
    m.BrightLineDirect = CreateObject("roSGNode", "BrightLineDirect:BL_init")
    ' m.BrightLineDirect.showDebug = true
    m.top.AppendChild(m.BrightLineDirect)
    ' Set config ID to BLDirect to initialize it (it will be provided, for testing 1018 can be used)
    ' This is critical part, channel must set config id before setting ad
    m.BrightLineDirect.configID = "1018"
    ' We observe the "state" field of the library. This will direct us to make it visible and focused, or not.
    m.BrightLineDirect.ObserveField("state", "BrightLine_OnStateChange")
    ' This is the event listener. This observer is optional and channel can ommit it in most cases.
    ' It tells you when BrightLineDirect expanding and collapsing the microsite.
    m.BrightLineDirect.ObserveField("event", "BrightLine_OnEvent")
end sub

sub BrightLine_OnStateChange(msg as Object)
    state = msg.getData()
    print "~~~~~ onBrightLineDirectStateChange", state
    if state = "initialized" then
        print "BrightLine INIT"
        ' Lib is initialized, here we can provide our ad object.
        ' Ad will be shown according to startAt field of the ad object, based on video position
        m.BrightLineDirect.ad = m.currentAd
    else if state = "showing" then
        print "BrightLine SHOWING"
        ' BrightLineDirect needs focus when it's in it's "showing" state
        m.BrightLineDirect.SetFocus(true)
    else if state = "ready" then
        print "BrightLine READY"
        ' Ready is set when lib is initialized and not showing ad.
        ' Send focus back to whatever you want.
        ' In this case we're using the video player.
        m.componentController.currentView.SetFocus(true)
    else if state = "ad_selector_completed" then
        ' To not show stream-stitched ad after ad selector we can skip to needed position in this callback
        m.componentController.currentView.seek = m.adStartTime + m.adDuration
    else if state = "exited" then
        ' BrightLine has received an "exit" signal
        ' this is the equivalent of pressing the "back" key in a stream.
        print "BrightLine EXITED"
        ' this will close player screen
        m.componentController.currentView.exited = true
        m.BrightLineDirect.visible = false
    end if
end sub

sub BrightLine_OnEvent(msg as Object)
    ' Callback for events coming from the BrightLineDirect library.
    blEvent = msg.GetData()
    print "Channel _____onBrightLineAPI_event", blEvent
    if blEvent.type = "UI"
        ' This type of events are fired when library UI was changed
        ' Currently it fires only when microsite is opened/closed
        if blEvent.value = "expanded"
            ' microsite was opened to full screen
        else if blEvent.value = "collapsed"
            ' microsite was collapsed and closed
        end if
    else if blEvent.type = "DL"
        ' This event is fired only by Conversion Template. It indicates when user presses on template
        ' blEvent.value will contain deeplink string for this conversion screen
    end if
end sub
                
            

 

Relseae notes

v3.0.3
  • Implemented click to full-screen template.
  • Implemented CloseAd interface to force closing of the overlay.
  • Added animations on dynamic overlay.
  • Improved ad interface handling, the ad object will be ignored if it exactly the same ad which is currently rendering.
  • Multiple bug fixes and stability improvements.
v3.0.2
  • Implemented frame ads.
  • Implemented support for dynamic components.
  • Implemented support for breadcrumbs on dynamic overlay.
  • Implemented loading of custom fonts on store locator.
  • Handled setting of ad before SDK is initialized.
  • Fixed taking session id from ad url.
  • Multiple bug fixes and stability improvements.
v3.0.1
  • Multiple bug fixes and stability improvements.
  • Added support for new BrightLine products.
v3.0.0
  • Refactored SDK callbacks to make integrations easier while keeping the SDK backwards compatible for existing integrations.
 

Downloads

Roku DI 3.0.2