设置 Pod 服务 DAI

请选择平台: HTML5 Android iOS tvOS Cast Roku

本指南演示了如何使用 IMA DAI SDK for Roku 播放 DAI Pod Serving 直播或 VOD 视频流。如需查看或遵循已完成的示例集成,请下载播客投放示例。

IMA DAI Pod Serving 概览

使用 IMA DAI 实现 Pod 投放涉及两个主要的 SDK 组件,本指南中对此进行了演示:

此外,您还需要向清单处理服务器发出请求,以检索要让应用显示的直播清单。具体流程可能因视频技术合作伙伴 (VTP) 而异。

前提条件

  • 请仔细阅读我们的兼容性页面,确保您的预期应用场景受支持。
  • 下载我们的 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>

在本指南中,您需要修改这两个文件(MainScene.xml 和 Sdk.xml)。

加载 IMA DAI SDK 框架

如需加载该框架,请将以下代码添加到 manifest 和 Sdk.xml 中:

清单

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>

初始化 IMA DAI SDK

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

Sdk.xml

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

  <component name="IMASDKTask" extends="Task">
  <interface>
    <field id="IMASDKInitialized" 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.IMASDKInitialized
        initializeIMASDK()
      end if
    end sub

    sub initializeIMASDK()
        if m.sdk = invalid
          m.sdk = New_IMASDK()
        end if
        m.top.IMASDKInitialized = true
    end sub
  ]]>
  </script>
  </component>

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

MainScene.xml

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

<component extends="Scene" initialFocus="myVideo" name="MainScene">
  <script type="text/brightscript">
  <![CDATA[
  function init()
    m.video = m.top.findNode("myVideo")
    m.video.notificationinterval = 1
    runIMASDKTask()
  end function

  function runIMASDKTask()
    m.IMASDKTask = createObject("roSGNode", "IMASDKTask")
    m.IMASDKTask.observeField("IMASDKInitialized", "handleIMASDKInitialized")
    m.IMASDKTask.observeField("errors", "handleIMASDKErrors")

    m.IMASDKTask.control = "RUN"
  end function

  sub handleIMASDKInitialized()

    ' Follow your manifest manipulator (VTP) documentation to register a user
    ' streaming session if needed.

  end sub

  sub handleIMASDKErrors(message as object)
    print "------ IMA DAI SDK failed  ------"
    if message <> invalid and message.getData() <> invalid
      print "IMA DAI SDK Error ";message.getData()
    end if
  end sub
  ]]>
  </script>
  <children>
    <Video height="720" id="myVideo" visible="false" width="1280"/>
  </children>
</component>

创建 IMA 视频流播放器

接下来,您需要使用现有的 roVideoScreen 创建 IMA 视频流播放器。

直播广告连播投放

对于直播,此直播播放器会实现三种回调方法:streamInitialized、adBreakStarted 和 adBreakEnded。

此外,在加载直播时停用特技播放。这样可以防止用户在广告插播时间点开始事件触发之前,在片头广告开始的瞬间跳过该广告。

Sdk.xml

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

  <component name="IMASDKTask" extends="Task">
  <interface>
    <field id="IMASDKInitialized" type="Boolean" />
    <field id="errors" type="stringarray" />
    <field id="urlData" type="assocarray" />
    <field id="adPlaying" type="Boolean" />
    <field id="videoNode" type="Node" />
  </interface>

  <script type="text/brightscript">
  ...
  sub runThread()
    if not m.top.IMASDKInitialized
      initializeIMASDK()
    end if

    setupPlayerCallbacks()
  end sub
  ...
  sub initializeIMASDK()
    if m.ima = invalid
      ima = New_IMASDK()
      ima.initSdk()
      m.ima = ima
    end if
    m.top.IMASDKInitialized = true
  end sub

  sub setupPlayerCallbacks()
    m.player = m.ima.createPlayer()
    m.player.top = m.top

    m.player.streamInitialized = function(urlData)
      m.top.videoNode.enableTrickPlay = false
      m.top.urlData = urlData
    end function

    m.player.adBreakStarted = function(adBreakInfo)
      print "------ Ad break started ------"
      m.top.adPlaying = true
      m.top.videoNode.enableTrickPlay = false
    end function

    m.player.adBreakEnded = function(adBreakInfo)
      print "------ Ad break ended ------"
      m.top.adPlaying = false
      m.top.videoNode.enableTrickPlay = true
    end function

  end sub
  </script>
...
</component>

VOD 视频流 Pod 投放

对于 VOD 视频流,此视频流播放器会实现四种回调方法:streamInitialized、loadUrl、adBreakStarted 和 adBreakEnded。在 streamInitialized 回调中,请务必调用 StreamManager.loadThirdPartyStream()。否则,SDK 将不会触发 loadUrl 函数。

在此步骤中,您还需要使用在loadAdPodStream()中获取的直播 ID,向视频技术合作伙伴 (VTP) 请求直播网址。然后,使用广告连播清单和 VTP 返回的所有字幕调用 StreamManager.loadThirdPartyStream()。

此外,在加载直播时停用特技播放。这样可以防止用户在广告插播时间点开始事件触发之前,在片头广告开始的瞬间跳过该广告。

Sdk.xml

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

  <component name="IMASDKTask" extends="Task">
  <interface>
    <field id="IMASDKInitialized" type="Boolean" />
    <field id="errors" type="stringarray" />
    <field id="adStitchedStreamInfo" type="assocarray" />
    <field id="adPlaying" type="Boolean" />
    <field id="videoNode" type="Node" />
    <field id="streamParameters" type="assocarray" />
  </interface>

  <script type="text/brightscript">
  ...
  sub runThread()
    if not m.top.IMASDKInitialized
      initializeIMASDK()
    end if

    setupPlayerCallbacks()
  end sub
  ...
  sub initializeIMASDK()
    if m.ima = invalid
      ima = New_IMASDK()
      ima.initSdk()
      m.ima = ima
    end if
    m.top.IMASDKInitialized = true
  end sub

  sub loadThirdPartyStream(adStitchedManifest as string, subtitleConfig as dynamic)
    m.streamManager.loadThirdPartyStream(adStitchedManifest, subtitleConfig)
  end sub

  sub setupPlayerCallbacks()
    m.player = m.ima.createPlayer()
    m.player.top = m.top

    m.player.streamInitialized = function(urlData)
      adStitchedManifest = m.top.streamParameters.VTPManifest.replace("[[STREAMID]]", urlData.streamId)
      loadThirdPartyStream(adStitchedManifest, m.top.streamParameters.subtitleConfig)
    end function

    m.player.loadUrl = function(streamInfo)
      m.top.adStitchedStreamInfo = streamInfo
    end function

    m.player.adBreakStarted = function(adBreakInfo)
      print "------ Ad break started ------"
      m.top.adPlaying = true
      m.top.videoNode.enableTrickPlay = false
    end function

    m.player.adBreakEnded = function(adBreakInfo)
      print "------ Ad break ended ------"
      m.top.adPlaying = false
      m.top.videoNode.enableTrickPlay = true
    end function

  end sub
  </script>
...
</component>

创建并执行直播或 VOD Pod Serving 视频流请求

有了直播播放器后,您就可以创建并执行直播请求。 此示例包含存储在 m.testPodServingStream 中的 Pod 投放流的数据。

直播广告连播投放

在 m.testPodServingStream 对象中,存储 Google Ad Manager 识别相关直播所需的参数,例如广告资源网代码和自定义素材资源键。此外,还要存储用于访问清单操纵服务器的清单网址。在这种情况下,清单网址需要在返回直播请求后添加 Google 直播 ID。

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

MainScene.xml

function init()
  m.video = m.top.findNode("myVideo")
  m.video.notificationinterval = 1
  m.testPodServingStream = {
    title: "Test live stream for DAI Pod Serving",
    assetKey: "test-live-stream",
    networkCode: "your-network-code",
    manifest: "https://.../master.m3u8?stream_id=[[STREAMID]]",
    apiKey: ""
  }
  runIMASDKTask()
end function

function runIMASDKTask()
  m.IMASDKTask = createObject("roSGNode", "IMASDKTask")

  m.IMASDKTask.streamParameters = m.testPodservingStream
  m.IMASDKTask.videoNode = m.video

  m.IMASDKTask.observeField("IMASDKInitialized", "handleIMASDKInitialized")
  m.IMASDKTask.observeField("errors", "handleIMASDKErrors")

  m.IMASDKTask.control = "RUN"
end function

Sdk.xml

<interface>
  <field id="IMASDKInitialized" type="Boolean" />
  <field id="errors" type="stringarray" />
  <field id="urlData" type="assocarray" />
  <field id="adPlaying" type="Boolean" />
  <field id="videoNode" type="Node" />
  <field id="streamParameters" type="assocarray" />
</interface>

...

sub runThread()
  if not m.top.IMASDKInitialized
    initializeIMASDK()
  end if

  setupPlayerCallbacks()
  loadAdPodStream()
end sub

sub loadAdPodStream()
  request = m.ima.CreatePodLiveStreamRequest(m.top.streamParameters.assetKey, m.top.streamParameters.networkCode, m.top.streamParameters.apiKey)

  ' Set the player object so that the request can trigger the player's
  ' callbacks at stream initialization or playback events.
  request.player = m.player

  ' Set the video node for the IMA DAI SDK to create ad UI as its child nodes.
  request.adUiNode = m.top.video

  requestResult = m.ima.requestStream(request)
  if requestResult <> invalid
    print "Error requesting stream ";requestResult
    return
  end if

  m.streamManager = invalid
  while m.streamManager = invalid
    sleep(50)
    m.streamManager = m.ima.getStreamManager()
  end while

  if m.streamManager = invalid
    errors = CreateObject("roArray", 1, True)
    invalidStreamManagerError = "Invalid stream manager"
    print invalidStreamManagerError
    errors.push(invalidStreamManagerError)
    m.top.errors = errors
    return
  end if

  if m.streamManager["type"] <> invalid and m.streamManager["type"] = "error"
    errors = CreateObject("roArray", 1, True)
    print "Stream request returns an error. " ; m.streamManager["info"]
    errors.push(m.streamManager["info"])
    m.top.errors = errors
    return
  end if

  setupStreamManager()
  m.streamManager.start()
end sub

VOD 视频流 Pod 投放

在 m.testPodServingStream 对象中,您将存储在直播请求中使用的广告资源网代码,以便 Google Ad Manager 提供直播 ID。此外,还要存储用于访问清单操作服务器上用户特定清单的清单网址。

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

MainScene.xml

sub init()
  m.video = m.top.findNode("myVideo")
  m.video.notificationinterval = 1
  m.testPodServingStream = {
    title: "Pod Serving VOD Stream",
    networkCode: "your-network-code",
    VTPManifest: "https://.../manifest.m3u8?gam-stream-id=[[STREAMID]]",
    subtitleConfig: []
  }
  runIMASDKTask()
end sub

sub runIMASDKTask()
  m.IMASDKTask = createObject("roSGNode", "IMASDKTask")

  m.IMASDKTask.streamParameters = m.testPodservingStream
  m.IMASDKTask.videoNode = m.video

  m.IMASDKTask.observeField("IMASDKInitialized", "handleIMASDKInitialized")
  m.IMASDKTask.observeField("errors", "handleIMASDKErrors")

  m.IMASDKTask.control = "RUN"
end sub

Sdk.xml

sub runThread()
  if not m.top.IMASDKInitialized
    initializeIMASDK()
  end if

  setupPlayerCallbacks()
  loadAdPodStream()
end sub

sub loadAdPodStream()
  request = m.ima.CreatePodVodStreamRequest(m.top.streamParameters.networkCode)

  ' Set the player object so that the request can trigger the player
  ' callbacks at stream initialization or playback events.
  request.player = m.player

  ' Set the video node for the IMA DAI SDK to create ad UI as its child nodes.
  request.adUiNode = m.top.video

  requestResult = m.ima.requestStream(request)
  if requestResult <> invalid
    print "Error requesting stream ";requestResult
    return
  end if

  m.streamManager = invalid
  while m.streamManager = invalid
    sleep(50)
    m.streamManager = m.ima.getStreamManager()
  end while

  if m.streamManager = invalid
    errors = CreateObject("roArray", 1, True)
    invalidStreamManagerError = "Invalid stream manager"
    print invalidStreamManagerError
    errors.push(invalidStreamManagerError)
    m.top.errors = errors
    return
  end if

  if m.streamManager["type"] <> invalid and m.streamManager["type"] = "error"
    errors = CreateObject("roArray", 1, True)
    print "Stream request returns an error. " ; m.streamManager["info"]
    errors.push(m.streamManager["info"])
    m.top.errors = errors
    return
  end if

  setupStreamManager()
  m.streamManager.start()
end sub

添加事件监听器并启动直播

直播广告连播投放

请求直播后,您只需执行以下几项操作:添加事件监听器以跟踪广告进度,并将 Roku 消息转发到 SDK。请务必将所有消息转发到 SDK,以确保广告正常播放。否则会导致广告观看次数报告不正确。

在此步骤中,您还要添加一个函数,用于将 [[STREAMID]] 宏替换为直播 ID,并将完整的清单请求网址传递给视频播放器。此实现会在这一步获取直播 ID,但根据您的 VTP 集成情况,您可能在这一步之前就能获取直播 ID。

MainScene.xml

function runIMASDKTask()
  m.IMASDKTask = createObject("roSGNode", "IMASDKTask")

  m.IMASDKTask.streamParameters = m.testPodservingStream
  m.IMASDKTask.videoNode = m.video

  m.IMASDKTask.observeField("IMASDKInitialized", "handleIMASDKInitialized")
  m.IMASDKTask.observeField("errors", "handleIMASDKErrors")
  m.sdkTask.observeField("adStitchedStreamInfo", "loadAdStitchedStream")

  m.sdkTask.control = "RUN"
end function

sub loadAdStitchedStream(message as object)
  print "Ad pod stream information ";message
  adPodStreamInfo = message.getData()
  manifest = m.testPodservingStream.manifest.Replace("[[STREAMID]]", adPodStreamInfo.streamId)
  playStream(manifest, adPodStreamInfo.format)
end sub

sub playStream(url as string, format as string)
  vidContent = createObject("RoSGNode", "ContentNode")
  vidContent.url = url
  vidContent.title = m.testPodservingStream.title
  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.IMASDKInitialized
    initializeIMASDK()
  end if

  setupPlayerCallbacks()
  loadAdPodStream()
  if m.streamManager <> invalid
    runLoop()
  end if
end sub

sub runLoop()
  m.top.videoNode.timedMetaDataSelectionKeys = ["*"]

  ' IMPORTANT: Failure to listen to the position and timedmetadata fields
  ' could result in ad impressions not being reported.
  m.port = CreateObject("roMessagePort")
  m.top.videoNode.observeField("position", m.port)
  m.top.videoNode.observeField("timedMetaData", m.port)
  m.top.videoNode.observeField("timedMetaData2", m.port)
  m.top.videoNode.observeField("state", m.port)

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

    m.streamManager.onMessage(msg)
    currentTime = m.top.videoNode.position
    if currentTime > 3 And not m.top.adPlaying
      m.top.videoNode.enableTrickPlay = true
    end if
  end while
end sub

sub setupStreamManager()
  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 sub

sub startCallback(ad as object)
  print "Callback from SDK -- Start called - "
end sub

sub firstQuartileCallback(ad as object)
  print "Callback from SDK -- First quartile called - "
end sub

sub midpointCallback(ad as object)
  print "Callback from SDK -- Midpoint called - "
end sub

sub thirdQuartileCallback(ad as object)
  print "Callback from SDK -- Third quartile called - "
end sub

sub completeCallback(ad as object)
  print "Callback from SDK -- Complete called - "
end sub

function errorCallback(error as object)
  print "Callback from SDK -- Error called - " ; error
  m.errorState = True
end function

VOD 视频流 Pod 投放

请求直播后,您只需执行以下几项操作:添加事件监听器以跟踪广告进度,并将 Roku 消息转发到 SDK。请务必将所有消息转发到 SDK,以确保广告正常播放。否则,系统将无法正确报告广告浏览量。

MainScene.xml

sub runIMASDKTask()
  m.IMASDKTask = createObject("roSGNode", "IMASDKTask")

  m.IMASDKTask.streamParameters = m.testPodservingStream
  m.IMASDKTask.videoNode = m.video

  m.IMASDKTask.observeField("IMASDKInitialized", "handleIMASDKInitialized")
  m.IMASDKTask.observeField("errors", "handleIMASDKErrors")
  m.sdkTask.observeField("adStitchedStreamInfo", "loadAdStitchedStream")

  m.sdkTask.control = "RUN"
end sub

sub loadAdStitchedStream(message as object)
  print "Ad pod stream information ";message
  adPodStreamInfo = message.getData()
end sub

sub playStream(url as string, format as string, subtitleConfig as object)
  vidContent = createObject("RoSGNode", "ContentNode")
  vidContent.title = m.testPodservingStream.title
  vidContent.url = url
  vidContent.subtitleConfig = subtitleConfig
  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.IMASDKInitialized
    initializeIMASDK()
  end if

  setupPlayerCallbacks()
  loadAdPodStream()
  if m.streamManager <> invalid
    runLoop()
  end if
end sub

sub runLoop()
  m.top.videoNode.timedMetaDataSelectionKeys = ["*"]

  ' IMPORTANT: Failure to listen to the position and timedmetadata fields
  ' could result in ad impressions not being reported.
  m.port = CreateObject("roMessagePort")
  m.top.videoNode.observeField("position", m.port)
  m.top.videoNode.observeField("timedMetaData", m.port)
  m.top.videoNode.observeField("timedMetaData2", m.port)
  m.top.videoNode.observeField("state", m.port)

  while True
    msg = wait(1000, m.port)
    if m.top.videoNode = invalid
      exit while
    end if

    m.streamManager.onMessage(msg)
    currentTime = m.top.videoNode.position
    if currentTime > 3 and not m.top.adPlaying
      m.top.videoNode.enableTrickPlay = true
    end if
  end while
end sub

sub setupStreamManager()
  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 sub

sub startCallback(ad as object)
  print "Callback from SDK -- Start called - "
end sub

sub firstQuartileCallback(ad as object)
  print "Callback from SDK -- First quartile called - "
end sub

sub midpointCallback(ad as object)
  print "Callback from SDK -- Midpoint called - "
end sub

sub thirdQuartileCallback(ad as object)
  print "Callback from SDK -- Third quartile called - "
end sub

sub completeCallback(ad as object)
  print "Callback from SDK -- Complete called - "
end sub

sub errorCallback(error as object)
  print "Callback from SDK -- Error called - " ; error
  m.errorState = True
end sub