Roku 向けの Programmatic Access Library(PAL)SDK を使用すると、直接 VAST 呼び出し(DVC)の承認を得ているパブリッシャーは、DVC ベースの Roku アプリを収益化できます。PAL SDK を使用すると、Google からノンス(暗号化された文字列)をリクエストして、DVC リクエストに署名できます。新しいストリーム リクエストごとに、新しく生成されたノンスが必要です。ただし、同じストリーム内の複数の広告リクエストで同じノンスを使用できます。
このガイドでは、PAL SDK を Roku アプリに組み込み、ノンスをリクエストして広告インプレッションを登録する方法の例について説明します。
前提条件
このガイドを開始する前に、次のことを行う必要があります。
- Roku 開発環境 - 詳細については、Roku デベロッパー環境の設定ガイドをご覧ください。
次の構造のプロジェクト フォルダ。
./ components/ MainScene.xml PALInterface.xml SampleVideoPlayer.xml images/ icon_focus_hd.png icon_focus_sd.png icon_side_hd.png icon_side_sd.png splash_fhd.png splash_hd.png splash_sd.png source/ main.brs manifest
プロジェクトを設定する
PAL SDK を統合する前に、プロジェクト ファイルを構成する必要があります。
マニフェスト
title=PAL for Roku Sample
subtitle=As seen in the PAL for Roku Get Started Guide
major_version=1
minor_version=0
build_version=00001
mm_icon_focus_hd=pkg:/images/icon_focus_hd.png
mm_icon_side_hd=pkg:/images/icon_side_hd.png
mm_icon_focus_sd=pkg:/images/icon_focus_sd.png
mm_icon_side_sd=pkg:/images/icon_side_sd.png
splash_screen_sd=pkg:/images/splash_sd.jpg
splash_screen_hd=pkg:/images/splash_hd.jpg
splash_screen_fhd=pkg:/images/splash_fhd.jpg
splash_color=#000000
splash_min_time=1000
ui_resolutions=hd
source/main.brs
sub Main()
showChannelSGScreen()
end sub
sub showChannelSGScreen()
screen = CreateObject("roSGScreen")
m.port = CreateObject("roMessagePort")
screen.setMessagePort(m.port)
m.scene = screen.CreateScene("MainScene")
screen.show()
while(true)
msg = wait(0, m.port)
msgType = type(msg)
if msgType = "roSGScreenEvent"
if msg.isScreenClosed() then return
end if
end while
end sub
サンプル動画プレーヤーを作成する
SampleVideoPlayer
コンポーネントは、リモコンの押下をキャプチャするために動画コンポーネントをラップするだけです。onKeyEvent
をオーバーライドして、リモコンのフォーカスが動画/広告プレーヤーに移行された後、キーの押下(上、下、左、右、クリックなど)がキャプチャされ、PAL にバブルアップされるようにします。
components/SampleVideoPlayer.xml
<?xml version="1.0" encoding="utf-8" ?>
<component name="SampleVideoPlayer" extends="Video">
<interface>
<field id="pressedKey" type="String" />
</interface>
<script type="text/brightscript">
<![CDATA[
Function onKeyEvent(key as String, press as Boolean) as Boolean
If press
m.top.pressedKey = key
End If
return True
End Function
]]>
</script>
<children>
<Label text="VIDEO" color="0xFFFFFFFF" font="font:MediumBoldSystemFont" horizAlign="center" vertAlign="center" width="720" height="480" />
</children>
</component>
テスト インターフェースを作成する
次の操作を行うボタンを含むシーンを実装します。
- nonce をリクエストします。
- 広告クリックを送信します。
- 再生開始イベントを送信します。
- 再生終了イベントを送信します。
- 動画ボタンにフォーカスを移動します。
components/MainScene.xml
<?xml version="1.0" encoding="utf-8" ?>
<component name="MainScene" extends="Scene" initialFocus="requestNonceButton">
<children>
<ButtonGroup>
<button text="Request Nonce" id="requestNonceButton" />
<button text="Send Ad Click" id="sendAdClickButton" />
<button text="Send Playback Start" id="sendPlaybackStartButton" />
<button text="Send Playback End" id="sendPlaybackEndButton" />
<button text="Transfer Focus to Video" id="transferFocusToVideoButton" />
</ButtonGroup>
<SampleVideoPlayer id="YourVideoPlayer" width="720" height="480" focusable="true" />
</children>
</component>
SDK インターフェース コンポーネントを作成する
メインシーンと PAL SDK の間で通信するには、非同期コードを含むコンポーネントが必要です。これは、PAL SDK が外部ネットワーク リクエストを行うため必要です。これは、Roku アプリケーションのメインスレッドでは実行できません。このコンポーネントにデータを送信するには、コンポーネントが送受信するデータを定義するインターフェースが必要です。
components/PALInterface.xml
<?xml version="1.0" encoding="utf-8" ?>
<component name="PALInterface" extends="Task">
<interface>
<!--Commands-->
<field id="requestNonce" type="Boolean" />
<field id="sendAdClick" type="Boolean" />
<field id="sendAdTouchKey" type="String" />
<field id="sendPlaybackStart" type="Boolean" />
<field id="sendPlaybackEnd" type="Boolean" />
<field id="endThread" type="Boolean" />
<!--Responses-->
<field id="errors" type="stringarray" />
<field id="nonce" type="String" />
</interface>
</component>
IMA SDK をインポートする
PAL ライブラリを使用するには、アプリ マニフェストで Roku 用の IMA SDK を必須とし、PALInterface
コンポーネントにインポートする必要があります。
マニフェスト
... splash_color=#000000 splash_min_time=1000 ui_resolutions=hd bs_libs_required=googleima3
components/PALInterface.xml
<?xml version = "1.0" encoding = "utf-8" ?> <component name="PALInterface" extends="Task"> <interface> <!-- commands --> <field id="requestNonce" type="Boolean" /> <field id="sendAdClick" type="Boolean" /> <field id="sendAdTouchKey" type="String" /> <field id="sendPlaybackStart" type="Boolean" /> <field id="sendPlaybackEnd" type="Boolean" /> <field id="endThread" type="Boolean" /> <!-- responses --> <field id="errors" type="stringarray" /> <field id="nonce" type="String" /> </interface> <script type = "text/brightscript"> <![CDATA[ Library "IMA3.brs" ]]> </script> </component>
シーンからインターフェース コンポーネントをトリガーする
次に、ユーザー操作をリッスンし、インターフェース コンポーネントの変更をトリガーする BrightScript コードを追加します。
インターフェース コンポーネントから出力を受け取るには、それらの出力に関連付けられたインターフェース フィールドにフィールド オブザーバーを実装し、メイン コンポーネントのコールバック関数に接続します。これは、コンポーネントが最初に登録されるときに行います。
ユーザー操作をインターフェース コンポーネントに送信するには、前に作成したボタンにフィールド オブザーバーを実装して、それらのコマンドに関連付けられたインターフェース フィールドの変更をトリガーします。
components/MainScene.xml
<?xml version="1.0" encoding="utf-8" ?> <component name="MainScene" extends="Scene" initialFocus="requestNonceButton"> <children> <ButtonGroup> <button text="Request Nonce" id="requestNonceButton" /> <button text="Send Ad Click" id="sendAdClickButton" /> <button text="Send Ad Touch" id="sendAdTouchButton" /> <button text="Send Playback Start" id="sendPlaybackStartButton" /> <button text="Send Playback End" id="sendPlaybackEndButton" /> </ButtonGroup> <Video id="YourVideoPlayer" width="720" height="480" focusable="true" /> </children> <script type="text/brightscript"> <![CDATA[ Function init() requestNonceButton = m.top.findNode("requestNonceButton") requestNonceButton.observeField("buttonSelected", "requestNonce") sendAdClickButton = m.top.findNode("sendAdClickButton") sendAdClickButton.observeField("buttonSelected", "sendAdClick") sendPlaybackStart = m.top.findNode("sendPlaybackStartButton") sendPlaybackStart.observeField("buttonSelected", "sendPlaybackStart") sendPlaybackEnd = m.top.findNode("sendPlaybackEndButton") sendPlaybackEnd.observeField("buttonSelected", "sendPlaybackEnd") loadImaSdk() End Function ' Initialize SDK Interface component and attach callbacks to field observers. Function loadImaSdk() as Void m.sdkTask = createObject("roSGNode", "PALInterface") m.sdkTask.observeField("errors", "onSdkLoadedError") m.sdkTask.observeField("nonce", "onNonceLoaded") print "Running load IMA task." m.sdkTask.control = "RUN" End Function Sub onSdkLoadedError(message as Object) print "----- errors in the sdk loading process --- ";message.getData() End Sub ' Callback triggered when Nonce is loaded. Sub onNonceLoaded(message as Object) nonce = m.sdkTask.nonce print "onNonceLoaded ";nonce End Sub Function requestNonceButtonPressed() As Void print "Request Nonce" ' Inform the SDK interface component to request a nonce. m.sdkTask.requestNonce = True End Function ' Action triggered on player start, either from user action or autoplay. Function sendPlaybackStart() As Void m.sdkTask.sendPlaybackStart = True End Function ' Action triggered on player end, either when content ends or the user exits ' playback of this content. Function sendPlaybackEnd() As Void m.sdkTask.sendPlaybackEnd = True End Function ]]> </script> </component>
フォーカスを移動するメソッドを追加
次に、ユーザーのキー入力をキャプチャして、動画要素との間でフォーカスを移動します。
components/MainScene.xml
... <script type="text/brightscript"> <![CDATA[ Function init() ... m.sendPlaybackStart = m.top.findNode("sendPlaybackStartButton") m.sendPlaybackStart.observeField("buttonSelected", "sendPlaybackStart") m.sendPlaybackEnd = m.top.findNode("sendPlaybackEndButton") m.sendPlaybackEnd.observeField("buttonSelected", "sendPlaybackEnd") m.transferFocusToVideoButton = m.top.findNode("transferFocusToVideoButton") m.transferFocusToVideoButton.observeField("buttonSelected", "transferFocusToVideo") ' Your video player set up to handle key press events. m.video = m.top.findNode("YourVideoPlayer") m.video.observeField("pressedKey", "onVideoKeyPress") loadImaSdk() End Function ... ' Action triggered on player end, either when content ends or the user exits ' playback of this content. Function sendPlaybackEnd() As Void m.sdkTask.sendPlaybackEnd = True End Function Function transferFocusToVideo() As Void m.video.setFocus(true) End Function Function onVideoKeyPress() As Void key = m.video.pressedKey If key = "" Return End If m.sdkTask.sendAdTouchKey = key ' If back or up is pressed, transfer focus back up to the buttons. If key = "back" or key = "up" m.transferFocusToVideoButton.setFocus(true) End If ' Reset so that we get the next key press, even if it's a repeat of the last ' key. m.video.pressedKey = "" End Function ]]> </script> </component>
PAL SDK を初期化して nonceLoader を作成する
これで、PAL SDK 実装のコアロジックの構築を開始できます。まず、別のスレッドから SDK を初期化します。
components/PALInterface.xml
... <script type = "text/brightscript"> <![CDATA[ Library "IMA3.brs" Sub init() ' It is not possible to access roUrlTransfer on the main thread. Setting ' functionName to a function and then setting control to "RUN" causes that 'function to run on a separate thread. m.top.functionName = "runPalThread" ' Loads the SDK on the current thread if it is not yet loaded. ' This blocks execution of other functions on this thread until the SDK is loaded. If m.sdk = Invalid m.sdk = new_imaSdk() End If m.nonceLoader = m.sdk.CreateNonceLoader() End Sub ' Starts the player event loop. This loop only terminates when "endThread" is sent. Function runPalThread() as Void ' Used for the player life cycle loop. m.top.endThread = False port = CreateObject("roMessagePort") End Function ]]> </script> </component>
ノンス リクエストを処理する
nonceLoader
が作成されたら、requestNonce
フィールドにオブザーバーを接続して、ノンス リクエストを処理する必要があります。このオブザーバーを nonceLoader
の初期化後にのみ接続することで、ノンス リクエストが SDK スレッドで処理され、有効な nonceLoader
がある場合にのみノンス リクエストを実行できます。
components/PALInterface.xml
... ' Starts the player event loop. This loop only terminates when "endThread" is sent. Function runPalThread() as Void ' Used for the player life cycle loop. m.top.endThread = False port = CreateObject("roMessagePort") ' Now that the nonceLoader exists, begin listening for nonce requests. m.top.observeField("requestNonce", m.port) End Function ' Requests a nonce from the PAL SDK. Function requestNonce() as Void nonceRequest = m.sdk.CreateNonceRequest() m.nonceManager = m.nonceLoader.loadNonceManager(nonceRequest) m.top.nonce = nonceManager.getNonce() End Function ]]> </script> </component>
ストレージの同意情報を収集する
NonceRequest.storageAllowed
のデフォルト値は true
ですが、適切な同意を得た後にこの値を変更できます。getConsentToStorage()
メソッドは、CMP との統合や、ストレージの同意を処理する他の方法に基づいて、ユーザーの同意を得る独自の方法のプレースホルダです。
components/PALInterface.xml
... <script type = "text/brightscript"> <![CDATA[ Library "IMA3.brs" Sub init() ' It is not possible to access roUrlTransfer on the main thread. Setting ' functionName to a function and then setting control to "RUN" causes that 'function to run on a separate thread. m.top.functionName = "runPalThread" ' Loads the SDK on the current thread if it is not yet loaded. ' This blocks execution of other functions on this thread until the SDK is loaded. If m.sdk = Invalid m.sdk = new_imaSdk() End If m.isConsentToStorage = getConsentToStorage() m.nonceLoader = m.sdk.CreateNonceLoader() End Sub ... ' Requests a nonce from the PAL SDK. Function requestNonce() as Void nonceRequest = m.sdk.CreateNonceRequest() ' Include changes to storage consent here. nonceRequest.storageAllowed = m.isConsentToStorage m.nonceManager = m.nonceLoader.loadNonceManager(nonceRequest) m.top.nonce = nonceManager.getNonce() End Function
プレーヤーのライフサイクル シグナルをリッスンする
PAL 統合でシグナルを適切に送信できるようにするには、プレーヤーのライフサイクル シグナルをリッスンするループを設定する必要があります。
components/PALInterface.xml
... ' Now that the nonceLoader exists, begin listening for nonce requests. m.top.observeField("requestNonce", m.port) m.top.observeField("sendAdClick", m.port) m.top.observeField("sendAdTouchKey", m.port) m.top.observeField("sendPlaybackStart", m.port) m.top.observeField("sendPlaybackEnd", m.port) ' Setting endThread to true causes the while loop to exit. m.top.observeField("endThread", m.port) While Not m.top.endThread message = m.port.waitMessage(1000) If message = Invalid pollManager() Else If message.getField() = "requestNonce" And m.top.requestNonce = True requestNonce() m.top.requestNonce = False Else If message.getField() = "sendAdClick" And m.top.sendAdClick = True sendAdClick() m.top.sendAdClick = False Else If message.getField() = "sendAdTouchKey" And m.top.sendAdTouchKey <> "" sendAdTouch(m.top.sendAdTouchKey) m.top.sendAdTouchKey = "" Else If message.getField() = "sendPlaybackStart" And m.top.sendPlaybackStart = True sendPlaybackStart() m.top.sendPlaybackStart = False Else If message.getField() = "sendPlaybackEnd" And m.top.sendPlaybackEnd = True sendPlaybackEnd() m.top.sendPlaybackEnd = False End If End While End Function Function pollManager() as Void If m.nonceManager <> Invalid m.nonceManager.poll() End If End Function ' Requests a nonce from the PAL SDK. Function requestNonce() as Void nonceRequest = m.sdk.CreateNonceRequest() m.nonceManager = m.nonceLoader.loadNonceManager(nonceRequest) m.top.nonce = nonceManager.getNonce() End Function ]]> </script> </component>
sendPlaybackStart
、sendPlaybackEnd
、sendAdClick
、sendAdTouch
のリスナーを登録する
次に、「動画プレーヤーの開始」で sendPlaybackStart
を呼び出します。この方法では、Google サーバーの非同期呼び出しを開始して、IVT のモニタリングと検出に必要なシグナルを収集します。再生が終了したら sendPlaybackEnd
を呼び出します。広告のクリックスルーに応答して sendAdClick
を呼び出します。次に、クリックスルー以外のユーザーのタップまたはクリック イベントに対して sendAdTouch
を呼び出します。
components/PALInterface.xml
... ' Requests a nonce from the IMA SDK. Function requestNonce() as Void nonceRequest = m.sdk.CreateNonceRequest() m.nonceManager = m.nonceLoader.loadNonceManager(nonceRequest) m.top.nonce = nonceManager.getNonce() End Function ' Registers an ad click using the IMA SDK. Function sendAdClick() as Void If m.nonceManager <> Invalid m.nonceManager.sendAdClick() End If End Function ' Registers an ad touch event using the IMA SDK. Function sendAdTouch(touch as String) as Void If m.nonceManager <> Invalid m.nonceManager.sendAdTouch(touch) End If End Function ' Registers the start of playback using the IMA SDK. Function sendPlaybackStart() as Void If m.nonceManager <> Invalid m.nonceManager.sendPlaybackStart() End If End Function ' Registers the end of playback using the IMA SDK. Function sendPlaybackEnd() as Void If m.nonceManager <> Invalid m.nonceManager.sendPlaybackEnd() End If End Function ]]> </script> </component>
広告リクエストにノンスを添付する
PAL ライブラリから受信したノンスを本番環境のアプリで使用するには、ノンスが生成された後にのみ広告リクエストを開始します。次に、u_paln
パラメータを使用して、ノンスを広告タグに追加します。
components/MainScene.xml
... ' Callback triggered when Nonce is loaded. Sub onNonceLoaded(message as Object) nonce = m.sdkTask.nonce print "onNonceLoaded ";nonce makeAdRequest(nonce) End Sub Sub makeAdRequest(nonce) ' Sample ad tag URL used in this sample. Your apps method of getting this ' URL will likely be different. adTag = "https://pubads.g.doubleclick.net/gampad/ads?iu=/124319096/external/single_ad_samples" preparedTag = adTag + "&u_paln=" + nonce ' Implement custom ad request logic here. Print "ad tag with nonce ";preparedTag End Sub ...
これで、これで、PAL ノンスをリクエストし、PAL SDK で再生セッション イベントを登録できる Roku アプリが完成しました。
(省略可)第三者広告サーバー経由で Google アド マネージャーのシグナルを送信する
アド マネージャーに対するサードパーティ広告サーバーのリクエストを設定します。
アド マネージャーへのサーバー リクエストにノンスを含めるように、第三者広告サーバーを設定します。第三者広告サーバー内で設定された広告タグの例を次に示します。
'https://pubads.serverside.net/gampad/ads?givn=%%custom_key_for_google_nonce%%&...'
詳しくは、Google アド マネージャーのサーバーサイド実装ガイドをご覧ください。
アド マネージャーは givn=
を探してノンス値を特定します。サードパーティ広告サーバーは、%%custom_key_for_google_nonce%%
などの独自のマクロをサポートし、それを前の手順で指定したノンスクエリ パラメータに置き換える必要があります。詳しくは、第三者広告サーバーのドキュメントをご覧ください。
これで、これで、nonce パラメータが PAL SDK から中間サーバー経由で Google アド マネージャーに伝播されるようになります。これにより、Google アド マネージャーでの収益化が促進されます。