開始

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>

sendPlaybackStartsendPlaybackEndsendAdClicksendAdTouch のリスナーを登録する

次に、「動画プレーヤーの開始」で 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 アド マネージャーでの収益化が促進されます。