本指南演示了如何将 IMA DAI SDK 集成到视频播放器应用中。如果您想查看或按照已完成的集成示例进行操作,请从 GitHub 下载基本示例。
IMA DAI 概览
如本指南所示,实现 IMA DAI 涉及两个主要的 SDK 组件:
StreamRequest:定义流请求的对象。视频流请求可以针对视频点播或直播。直播请求会指定素材资源键,而 VOD 请求会指定 CMS ID 和视频 ID。这两种请求类型都可以选择包含访问指定视频流所需的 API 密钥,以及供 IMA SDK 按照 Google Ad Manager 设置中的指定方式处理广告标识符的 Google Ad Manager 广告资源网代码。StreamManager:用于处理动态广告插播视频流以及与 DAI 后端进行互动的对象。直播管理工具还会处理跟踪 ping,并将直播和广告事件转发给发布商。
前提条件
- 请仔细阅读我们的兼容性页面,确保您的预期应用场景受支持。
- 下载我们的 Roku 示例播放器代码
- 将示例播放器代码部署到 Roku 设备,以验证您的开发设置是否正常运行。
播放视频
提供的示例视频播放器可直接播放内容视频。将示例播放器部署到 Roku 播放器,以确保您的开发环境设置正确。
将视频播放器转换为 IMA DAI 视频流播放器
请按照以下步骤实现直播播放器。
创建 Sdk.xml
在项目中添加一个名为 Sdk.xml 的新文件(与 MainScene.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 Advertising Framework
IMA DAI SDK 依赖于 Roku Advertising Framework。如需加载该框架,请将以下代码添加到 manifest 和 Sdk.xml 中:
bs_libs_required=roku_ads_lib,googleima3
Library "Roku_Ads.brs"
Library "IMA3.brs"
加载 IMA DAI SDK
如需加载 IMA DAI SDK,请执行以下操作:
使用
New_IMASDK()调用初始化 IMA SDK:sub loadSdk() If m.sdk = invalid m.sdk = New_IMASDK() End If m.top.sdkLoaded = true End Sub通过创建
sdkLoaded布尔值字段来跟踪 IMA 是否已加载:<field id="sdkLoaded" type="Boolean" />从主
runThread()子例程调用loadSdk()子例程:if not m.top.sdkLoaded loadSdk() End If在
MainScene.xml中创建loadImaSdk()函数,以创建并运行sdkTask对象:function loadImaSdk() as void m.sdkTask = createObject("roSGNode", "imasdk") m.sdkTask.observeField("sdkLoaded", "onSdkLoaded") m.sdkTask.observeField("errors", "onSdkLoadedError") ' Change to m.testLiveStream to demo live instead of VOD. selectedStream = m.testVodStream m.videoTitle = selectedStream.title m.sdkTask.streamData = selectedStream m.sdkTask.observeField("urlData", "urlLoadRequested") m.sdkTask.video = m.video ' Setting control to run starts the task thread. m.sdkTask.control = "RUN" end function从
init()函数调用loadImaSdk()函数。创建
onSdkLoaded()和onSdkLoadedError()监听器子例程,以响应 SDK 加载事件: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 视频流播放器
如需创建 IMA 视频流播放器,请执行以下操作:
创建一个执行以下操作的
setupVideoPlayer()子例程:使用
createPlayer()方法创建直播播放器。让该直播播放器实现三种回调方法:
loadUrl、adBreakStarted和adBreakEnded。在加载直播时停用特技播放,以防止用户在直播开始时、广告插播时间点开始事件触发之前跳过前贴片广告。
sub setupVideoPlayer() sdk = m.sdk m.player = sdk.createPlayer() m.player.top = m.top m.player.loadUrl = Function(urlData) ' This line prevents users from scanning during buffering ' or during the first second of the ad before we have a callback from roku. ' If there are no prerolls disabling trickplay isn't needed. 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添加
seek回调方法以支持可跳过的广告。如需了解详情,请参阅添加对可跳过广告的支持。添加
setupVideoPlayer()子例程中使用的urlData、adPlaying和video字段:<field id="urlData" type="assocarray" /> <field id="adPlaying" type="Boolean" /> <field id="video" type="Node" />
创建并执行流式传输请求
如需请求 DAI 视频流,请执行以下操作:
创建一个
loadStream()子例程,用于创建和请求数据流。如需支持广告界面(例如 AdChoices 图标),您还必须在请求中传递对包含内容视频的节点的引用: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.adUiNode = 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添加
loadStream()子例程中使用的streamData和streamManagerReady字段:<field id="streamManagerReady" type="Boolean" /> <field id="streamData" type="assocarray" />如果直播管理器不可用,请从
runThread()子例程中调用loadStream()子例程:if not m.top.streamManagerReady loadStream() End If选择 VOD 或直播。以下示例包含直播和 VOD 视频流的视频流参数:
m.testLiveStream = { title: "Live Stream", assetKey: "c-rArva4ShKVIAkNfy6HUQ", networkCode: "21775744923", apiKey: "", type: "live" } m.testVodStream = { title: "VOD stream" contentSourceId: "2548831", videoId: "tears-of-steel", networkCode: "21775744923", apiKey: "", type: "vod" }默认情况下,本指南使用 VOD 视频流。您可以通过将
selectedStream变量从m.testVodStream对象更改为m.testLiveStream对象来改用直播。
启动数据流
创建 urlLoadRequested() 子例程以监听流数据并调用 playStream() 子例程:
Sub urlLoadRequested(message as Object)
print "Url Load Requested ";message
data = message.getData()
playStream(data.manifest, data.format)
End Sub
创建 playStream() 以开始播放直播:
Sub playStream(url as String, 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
监听直播元数据
创建 runLoop() 子例程,其中包含一个在直播播放期间运行的 while 循环,并使用 StreamManager.onMessage() 将直播元数据发送到 IMA:
Sub runLoop()
' Forward all timed metadata events.
m.top.video.timedMetaDataSelectionKeys = ["*"]
' Cycle through all the fields and just listen to them all.
m.port = CreateObject("roMessagePort")
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
' Only enable trickplay after a few seconds, in case we start with an ad,
' to prevent users from skipping through that ad.
If currentTime > 3 And not m.top.adPlaying
m.top.video.enableTrickPlay = true
End If
end while
End Sub
监听广告事件
现在,您已将直播元数据传递给 IMA,IMA 可以在广告插播时间点发出广告事件。根据需要创建广告事件监听器,以响应广告事件:
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
' errors are critical and should terminate the stream.
m.errorState = True
End Function
添加对可跳过广告的支持(可选)
为了支持可跳过的广告,您需要向 IMA DAI SDK 的 player 对象添加一个 seek 方法,该方法会以编程方式将视频跳转到指定位置(以浮点秒数为单位)。
如需支持可跳过的广告,您还必须确保在请求中设置 adUiNode。
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