本指南演示了如何使用 IMA DAI SDK for Roku 播放 DAI Pod Serving 直播或 VOD 视频流。如需查看或遵循已完成的示例集成,请下载播客投放示例。
IMA DAI Pod Serving 概览
使用 IMA DAI 实现 Pod 投放涉及两个主要的 SDK 组件,本指南中对此进行了演示:
StreamRequest.createPodLiveStreamRequest()/StreamRequest.createPodVodStreamRequest():创建一个对象,用于定义向 Google 广告服务器发出的直播请求。这些请求会指定广告资源网代码,Pod Liveima.StreamRequest还需要自定义资源键和可选的 API 密钥。StreamManager:一个用于处理视频流与 IMA DAI SDK 之间通信的对象,例如触发跟踪 ping 和将视频流事件转发给发布商。
此外,您还需要向清单处理服务器发出请求,以检索要让应用显示的直播清单。具体流程可能因视频技术合作伙伴 (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