设置 IMA SDK for DAI

借助 IMA SDK,您可以轻松地将多媒体广告集成到网站和应用中。IMA SDK 可以从任何 符合 VAST 标准的广告服务器请求广告,并在您的应用中管理广告播放。借助 IMA DAI SDK,应用可以针对广告和内容视频(视频点播或直播内容)发出视频流请求。然后,SDK 会返回一个组合视频流,这样您就不必在应用中管理广告和内容视频之间的切换。

选择您感兴趣的 DAI 解决方案

全方位服务 DAI

本指南演示了如何将 IMA DAI SDK 集成到简单的视频播放器应用中。如果您想查看或跟随完成的示例集成,请从 GitHub 下载基本示例

IMA DAI 概览

实现 IMA DAI 涉及两个主要 SDK 组件,如本指南所示:

  • StreamRequest: 用于定义流式请求的对象。视频流请求可以是视频点播请求,也可以是直播视频流请求。直播请求会指定素材资源键,而 VOD 请求会指定 CMS ID 和视频 ID。这两种请求类型都可以选择性地包含访问指定视频流所需的 API 密钥,以及 Google Ad Manager 网络代码,以便 IMA SDK 按照 Google Ad Manager 设置中的指定方式处理广告标识符。
  • StreamManager:用于处理动态广告插播流以及与 DAI 后端互动的对象。流管理器还会处理跟踪 ping,并将流和广告事件转发给发布商。

前提条件

  • 请仔细阅读我们的兼容性页面,确保您的预期使用情形受支持。
  • 下载我们的 Roku 示例播放器代码
  • 将示例播放器代码部署到 Roku 设备,以验证您的开发设置是否正常运行。

播放视频

提供的示例视频播放器可直接播放内容视频。将示例播放器部署到 Roku 播放器,以确保您的开发环境设置正确无误。

将视频播放器转换为 IMA DAI 流播放器

请按照以下步骤实现流播放器。

创建 Sdk.xml

在项目中添加一个与 MainScene.xml 并行的新文件,名为 Sdk.xml,并添加以下样板代码:

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>

在本指南中,您需要编辑这两个文件。

加载 Roku 广告框架

IMA DAI SDK 依赖于 Roku 广告框架。如需加载框架,请将以下内容添加到 manifestSdk.xml

清单

bs_libs_required=roku_ads_lib,googleima3

Sdk.xml

Library "Roku_Ads.brs"
Library "IMA3.brs"

加载 IMA DAI SDK

加载 IMA 动态广告插播视频流的第一步是加载并初始化 IMA DAI SDK。以下代码用于加载 IMA DAI SDK 脚本。

Sdk.xml

<interface>
  <field id="sdkLoaded" type="Boolean" />
  <field id="errors" type="stringarray" />
</interface>
...
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

现在,在 MainScene.xml 中启动此任务,并移除用于加载内容流的调用。

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

创建 IMA 流播放器

接下来,您需要使用现有的 roVideoScreen 创建 IMA 流播放器。此流播放器实现了三种回调方法:loadUrladBreakStartedadBreakEnded。此外,在加载视频流时停用快进/快退播放。这样可防止用户在广告插播开始事件触发之前,即前贴片广告开始播放时立即跳过该广告。

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
End Sub

创建并执行流式请求

现在您已经有了流播放器,可以创建并执行流请求了。 此示例包含直播和 VOD 流的数据。它使用 VOD 流,但您可以通过将 selectedStreamm.testVodStream 更改为 m.testLiveStream 来改为使用直播。

为了能够支持 AdUI(例如“广告选项”图标),您还必须在请求中传递对包含内容视频的节点的引用。

MainScene.xml

function init()
  m.video = m.top.findNode("myVideo")
  m.video.notificationinterval = 1
  m.testLiveStream = {
    title: "Livestream",
    assetKey: "c-rArva4ShKVIAkNfy6HUQ",
    networkCode: "21775744923",
    apiKey: "",
    type: "live"
  }
  m.testVodStream = {
    title: "VOD stream"
    contentSourceId: "2548831",
    videoId: "tears-of-steel",
    networkCode: "21775744923",
    apiKey: "",
    type: "vod"
  }
  loadImaSdk()
end function

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.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
  sdk.initSdk()
  setupVideoPlayer()

  request = {}
  streamData = m.top.streamData
  if streamData.type = "live"
    request = sdk.CreateLiveStreamRequest(streamData.assetKey, streamData.apiKey, streamData.networkCode)
  else if streamData.type = "vod"
    request = sdk.CreateVodStreamRequest(streamData.contentSourceId, streamData.videoId, streamData.apiKey, streamData.networkCode)
  else
    request = sdk.CreateStreamRequest()
  end if

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

  requestResult = sdk.requestStream(request)
  If requestResult &lt;&gt; 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"] &lt;&gt; 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

添加事件监听器并启动流

请求直播后,您只需再完成以下几项操作:添加事件监听器以跟踪广告进度、启动直播,并将 Roku 消息转发到 SDK。

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", "urlLoadRequested")
  m.sdkTask.video = m.video
  m.sdkTask.control = "RUN"
end function

Sub urlLoadRequested(message as Object)
  print "Url Load Requested ";message
  data = message.getData()

  playStream(data.manifest)
End Sub

Sub playStream(url as Object)
  vidContent = createObject("RoSGNode", "ContentNode")
  vidContent.url = url
  vidContent.title = m.videoTitle
  vidContent.streamformat = "hls"
  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

添加对可跳过广告的支持(可选)

为了支持可跳过的广告,您需要向 IMA DAI SDK 的 player 对象添加一个 seek 方法,该方法以浮点秒为单位,以编程方式将视频定位到指定位置。

为了支持可跳过的广告,您还必须确保在请求中设置 adUiNode

Sdk.xml

  m.player.loadUrl = Function(urlData)
    m.top.video.enableTrickPlay = false
    m.top.urlData = urlData
  End Function

  m.player.seek = Function(timeSeconds as Float)
     print "---- SDK requested seek to ----" ; timeSeconds
     m.top.video.seekMode = "accurate"
     m.top.video.seek = timeSeconds
  End Function

  m.player.adBreakStarted = Function(adBreakInfo as Object)
    print "---- Ad Break Started ---- "
    m.top.adPlaying = True
    m.top.video.enableTrickPlay = false
  End Function