Get started with pod serving

IMA SDKs make it easy to integrate multimedia ads into your websites and apps. IMA SDKs can request ads from any VAST-compliant ad server and manage ad playback in your apps. With IMA DAI SDKs, apps make a stream request for ad and content video—either VOD or live content. The SDK then returns a combined video stream, so that you don't have to manage switching between ad and content video within your app.

This guide demonstrates how to play a DAI Pod Serving stream, using the IMA DAI SDK for Roku.

IMA DAI Pod Serving overview

Implementing pod serving using the IMA DAI involves two main SDK components, which are demonstrated in this guide:

  • PodStreamRequest: An object that defines a stream request to Google's advertising servers. Requests specify a Network Code, Custom Asset Key, and an optional API key, and other optional parameters.
  • StreamManager: An object that handles communication between the video stream and the IMA DAI SDK, such as firing tracking pings and forwarding stream events to the publisher.

In addition, you will need to make a request to your manifest manipulation server to retrieve the stream manifest for your app to display. The exact process may vary from server to server.

Prerequisites

Before you begin, you need to:

  • Read through our compatibility page to make sure your intended use case is supported.
  • Download our Roku sample player code.
  • Deploy the above sample player code to a Roku device to verify that your development set up is working.

Play your video

The sample video player provided plays a content video out of the box. Deploy the sample player to your Roku device to ensure your development environment is set up properly.

Turn your video player into an IMA Dynamic Ad Insertion stream player

Create Sdk.xml

Add a new file to your project alongside MainScene.xml called Sdk.xml, and add the following boilerplate:

Sdk.xml

<?xml version = "1.0" encoding = "utf-8" ?>

  <component name = "imasdk" extends = "Task">
  <interface>
  </interface>
  <script type = "text/brightscript">
  <![CDATA[
    ' Your code goes here.
  ]]>
  </script>
  </component>

You need to edit both of these files (MainScene.xml and Sdk.xml) throughout this guide. A heading above each code snippet indicates in which file you need to add that snippet.

Load the IMA SDK Framework

To load the framework, add the following to manifest and Sdk.xml:

Manifest

bs_libs_required=googleima3

Sdk.xml

<?xml version = "1.0" encoding = "utf-8" ?>

  <component name = "imasdk" extends = "Task">
  <interface>
  </interface>
  <script type = "text/brightscript">
  <![CDATA[
  Library "IMA3.brs"
  ]]>
  </script>
  </component>

Initialize the IMA SDK

The first step to loading your IMA Dynamic Ad Insertion stream is to load and initialize the IMA SDK. The following initializes the IMA SDK script.

Sdk.xml

<?xml version = "1.0" encoding = "utf-8" ?>

  <component name = "imasdk" extends = "Task">
  <interface>
  <field id="sdkLoaded" type="Boolean" />
    <field id="errors" type="stringarray" />
  </interface>
  <script type = "text/brightscript">
  <![CDATA[
    Library "IMA3.brs"
    
    sub init()
      m.top.functionName = "runThread"
    End Sub

    sub runThread()
      if not m.top.sdkLoaded
        loadSdk()
      End If
    End Sub

    sub loadSdk()
        If m.sdk = invalid
          m.sdk = New_IMASDK()
        End If
        m.top.sdkLoaded = true
    End Sub
    
  ]]>
  </script>
  </component>

Now start this task in MainScene.xml and remove the call to load the content stream.

MainScene.xml

function init()
    m.video = m.top.findNode("myVideo")
    m.video.notificationinterval = 1
    loadImaSdk()
  end function

  function loadImaSdk() as void
    m.sdkTask = createObject("roSGNode", "imasdk")
    m.sdkTask.observeField("sdkLoaded", "onSdkLoaded")
    m.sdkTask.observeField("errors", "onSdkLoadedError")

    m.sdkTask.control = "RUN"
  end function

  Sub onSdkLoaded(message as Object)
    print "----- onSdkLoaded --- control " ; message
  End Sub

  Sub onSdkLoadedError(message as Object)
    print "----- errors in the sdk loading process --- " ; message
  End Sub

Create an IMA stream player

Next, you need to use your existing roVideoScreen to create an IMA stream player. This stream player implements three callback methods: loadUrl, adBreakStarted, and adBreakEnded. Also disable trick play when the stream is loaded. This prevents users from skipping a pre-roll in the instant that it starts, before the ad break started event is fired.

Sdk.xml

<interface>
    <field id="sdkLoaded" type="Boolean" />
    <field id="errors" type="stringarray" />
    <field id="urlData" type="assocarray" />
    <field id="adPlaying" type="Boolean" />
    <field id="video" type="Node" />
  </interface>

  ...
  sub setupVideoPlayer()
    sdk = m.sdk
    m.player = sdk.createPlayer()
    m.player.top = m.top
    m.player.loadUrl = Function(urlData)
      m.top.video.enableTrickPlay = false
      m.top.urlData = urlData
    End Function
    m.player.adBreakStarted = Function(adBreakInfo as Object)
      print "---- Ad Break Started ---- "
      m.top.adPlaying = True
      m.top.video.enableTrickPlay = false
    End Function
    m.player.adBreakEnded = Function(adBreakInfo as Object)
      print "---- Ad Break Ended ---- "
      m.top.adPlaying = False
      m.top.video.enableTrickPlay = true
    End Function
    m.player.seek = Function(timeSeconds as Double)
      print "---- SDK requested seek to ----" ; timeSeconds
      m.top.video.seekMode = "accurate"
      m.top.video.seek = timeSeconds
    End Function
  End Sub

Create and execute a pod serving stream request

Now that you have a stream player, you can create and execute a stream request. This example has data for a pod serving stream stored in m.testPodServingStream.

In the m.testPodServingStream object, you'll store the parameters that Google Ad Manager needs to identify the stream in question, such as network code and custom asset key. Also store the manifest url used to access your video stitching server. In this case the manifest URL will need to have the Google Stream ID added, after the stream request is returned.

To be able to support AdUI, such as adChoices icons, you must also pass a reference to the node containing your content video as part of your request.

MainScene.xml

function init()
    m.video = m.top.findNode("myVideo")
    m.video.notificationinterval = 1
    m.testPodServingStream = {
      title: "Pod Serving Stream",
      networkCode: "51636543",
      customAssetKey: "google-sample",
      manifestUrl: "https://encodersim.sandbox.google.com/masterPlaylist/9c654d63-5373-4673-8c8d-6d92b66b9d46/master.m3u8?gen-seg-redirect=true&network=51636543&event=google-sample&pids=devrel4628000,devrel896000,devrel3528000,devrel1428000,devrel2628000,devrel1928000&seg-host=dai.google.com&stream_id=[[STREAMID]]",
      apiKey: "",
    }
    loadImaSdk()
  end function

  function loadImaSdk() as void
    m.sdkTask = createObject("roSGNode", "imasdk")
    m.sdkTask.observeField("sdkLoaded", "onSdkLoaded")
    m.sdkTask.observeField("errors", "onSdkLoadedError")

    selectedStream = m.testPodServingStream
    m.videoTitle = selectedStream.title
    m.sdkTask.streamData = selectedStream

    m.sdkTask.video = m.video
    m.sdkTask.control = "RUN"
  end function

Sdk.xml

<interface>
    <field id="sdkLoaded" type="Boolean" />
    <field id="errors" type="stringarray" />
    <field id="urlData" type="assocarray" />
    <field id="adPlaying" type="Boolean" />
    <field id="video" type="Node" />
    <field id="streamData" type="assocarray" />
    <field id="streamManagerReady" type="Boolean" />
  </interface>

  ...

  sub runThread()
    if not m.top.sdkLoaded
      loadSdk()
    End If
    if not m.top.streamManagerReady
      loadStream()
    End If
  End Sub

  Sub loadStream()
    sdk = m.sdk
    stream = m.top.streamData
    sdk.initSdk()
    setupVideoPlayer()

    ' This pod serving request for a live stream will return a stream id along with important information for the IMA DAI SDK.
    request = sdk.streamRequest.CreatePodLiveStreamRequest(stream.customAssetKey, stream.networkCode, stream.apiKey)

    request.player = m.player
    request.adUiNode = m.top.video
    ' Required to support UI elements for 'Why This Ad?' and skippability
    request.videoObject = m.top.video

    requestResult = sdk.requestStream(request)
    If requestResult <> Invalid
      print "Error requesting stream " ; requestResult
    Else
      m.streamManager = Invalid
      While m.streamManager = Invalid
        sleep(50)
        m.streamManager = sdk.getStreamManager()
      End While
      If m.streamManager = Invalid or m.streamManager["type"] <> Invalid or m.streamManager["type"] = "error"
        errors = CreateObject("roArray", 1, True)
        print "error " ; m.streamManager["info"]
        errors.push(m.streamManager["info"])
        m.top.errors = errors
      Else
        m.top.streamManagerReady = True
        addCallbacks()
        m.streamManager.start()
      End If
    End If
  End Sub

Add event listeners and start the stream

After requesting your stream, there are only a few things left to do: add event listeners to track ad progress and forward Roku messages to the SDK. It is important that you forward all messages to the SDK to ensure correct ad playback. Failure to do so will cause ad views to be improperly reported.

In this step, you also add a function to replace the [[STREAMID]] macro with the stream id and pass the completed manifest request url to the video player.

MainScene.xml

function loadImaSdk() as void
    m.sdkTask = createObject("roSGNode", "imasdk")
    m.sdkTask.observeField("sdkLoaded", "onSdkLoaded")
    m.sdkTask.observeField("errors", "onSdkLoadedError")

    selectedStream = m.testVodStream
    m.videoTitle = selectedStream.title
    m.sdkTask.streamData = selectedStream

    m.sdkTask.observeField("urlData", "loadAdPodStream")
    m.sdkTask.video = m.video
    m.sdkTask.control = "RUN"
  end function

  Sub loadAdPodStream(message as Object)
    print "Url Load Requested " ; message
    data = message.getData()
    streamId = data.streamId
    manifest = m.sdkTask.streamData.manifestUrl.Replace("[[STREAMID]]", streamId)
    playStream(manifest, data.format)
  End Sub

  Sub playStream(url as Object, format as String)
    vidContent = createObject("RoSGNode", "ContentNode")
    vidContent.url = url
    vidContent.title = m.videoTitle
    vidContent.streamformat = format
    m.video.content = vidContent
    m.video.setFocus(true)
    m.video.visible = true
    m.video.control = "play"
    m.video.EnableCookies()
  End Sub

Sdk.xml

sub runThread()
    if not m.top.sdkLoaded
      loadSdk()
    End If
    if not m.top.streamManagerReady
      loadStream()
    End If
    If m.top.streamManagerReady
      runLoop()
    End If
  End Sub

  Sub runLoop()
    m.top.video.timedMetaDataSelectionKeys = ["*"]

    m.port = CreateObject("roMessagePort")

    ' Listen to all fields.

    ' IMPORTANT: Failure to listen to the position and timedmetadata fields
    ' could result in ad impressions not being reported.
    fields = m.top.video.getFields()
    for each field in fields
      m.top.video.observeField(field, m.port)
    end for

    while True
      msg = wait(1000, m.port)
      if m.top.video = invalid
        print "exiting"
        exit while
      end if

      m.streamManager.onMessage(msg)
      currentTime = m.top.video.position
      If currentTime > 3 And not m.top.adPlaying
         m.top.video.enableTrickPlay = true
      End If
    end while
  End Sub

  Function addCallbacks() as Void
    m.streamManager.addEventListener(m.sdk.AdEvent.ERROR, errorCallback)
    m.streamManager.addEventListener(m.sdk.AdEvent.START, startCallback)
    m.streamManager.addEventListener(m.sdk.AdEvent.FIRST_QUARTILE, firstQuartileCallback)
    m.streamManager.addEventListener(m.sdk.AdEvent.MIDPOINT, midpointCallback)
    m.streamManager.addEventListener(m.sdk.AdEvent.THIRD_QUARTILE, thirdQuartileCallback)
    m.streamManager.addEventListener(m.sdk.AdEvent.COMPLETE, completeCallback)
  End Function

  Function startCallback(ad as Object) as Void
    print "Callback from SDK -- Start called - "
  End Function

  Function firstQuartileCallback(ad as Object) as Void
    print "Callback from SDK -- First quartile called - "
  End Function

  Function midpointCallback(ad as Object) as Void
    print "Callback from SDK -- Midpoint called - "
  End Function

  Function thirdQuartileCallback(ad as Object) as Void
    print "Callback from SDK -- Third quartile called - "
  End Function

  Function completeCallback(ad as Object) as Void
    print "Callback from SDK -- Complete called - "
  End Function

  Function errorCallback(error as Object) as Void
    print "Callback from SDK -- Error called - " ; error
    m.errorState = True
  End Function