เริ่มต้นใช้งาน

SDK ไลบรารีการเข้าถึงแบบเป็นโปรแกรม (PAL) สําหรับ Roku ช่วยให้ผู้เผยแพร่โฆษณาที่ได้รับอนุมัติการเรียก VAST โดยตรง (DVC) สร้างรายได้จากแอปพลิเคชัน Roku ที่ใช้ DVC ได้ PAL SDK ให้คุณขอ Nonce ซึ่งเป็นสตริงที่เข้ารหัสจาก Google เพื่อให้คุณลงนามในคําขอ DVC ได้ คำขอสตรีมใหม่แต่ละรายการต้องมาพร้อมกับ Nonce ที่สร้างขึ้นใหม่ อย่างไรก็ตาม คุณใช้ Nonce เดียวกันซ้ำสำหรับคำขอโฆษณาหลายรายการภายในสตรีมเดียวกันได้

คู่มือนี้จะอธิบายตัวอย่างวิธีรวม PAL SDK ไว้ในแอปพลิเคชัน Roku ขอ Nonce และบันทึกการแสดงโฆษณา

ข้อกำหนดเบื้องต้น

ก่อนเริ่มต้นคู่มือนี้ คุณต้องดำเนินการต่อไปนี้

สร้างโปรเจ็กต์

คุณต้องกำหนดค่าไฟล์โปรเจ็กต์ก่อนผสานรวม PAL SDK

ไฟล์ Manifest

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 คุณต้องกำหนด IMA SDK สำหรับ Roku ในไฟล์ Manifest ของแอปและนำเข้าลงในคอมโพเนนต์ PALInterface

ไฟล์ Manifest

...
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>

ประมวลผลคำขอ Nonce

เมื่อสร้าง nonceLoader แล้ว คุณต้องจัดการคําขอ Nonce โดยแนบเครื่องมือตรวจสอบกับช่อง requestNonce การเพิ่มผู้สังเกตการณ์นี้หลังจากเริ่มต้น nonceLoader เท่านั้นจะช่วยให้มั่นใจได้ว่าระบบจะจัดการคําขอ Nonce ในเธรด SDK และระบบจะส่งคําขอ Nonce ได้ก็ต่อเมื่อมี 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 ใน "video player start" วิธีนี้จะเริ่มต้นการเรียกใช้แบบไม่พร้อมกันไปยังเซิร์ฟเวอร์ 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>

แนบ Nonce กับคําขอโฆษณา

หากต้องการใช้ Nonce ที่ได้รับจากคลัง PAL ในแอปพลิเคชันเวอร์ชันที่ใช้งานจริง ให้เริ่มคําขอโฆษณาหลังจากที่สร้าง Nonce แล้วเท่านั้น จากนั้นเพิ่มค่า Nonce ต่อท้ายแท็กโฆษณาโดยใช้พารามิเตอร์ 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
...

เท่านี้ก็เรียบร้อย ตอนนี้คุณมีแอป Roku ที่ขอค่า Nonce ของ PAL และลงทะเบียนเหตุการณ์เซสชันการเล่นด้วย PAL SDK ได้แล้ว

(ไม่บังคับ) ส่งสัญญาณ Google Ad Manager ผ่านเซิร์ฟเวอร์โฆษณาของบุคคลที่สาม

กําหนดค่าคําขอของเซิร์ฟเวอร์โฆษณาบุคคลที่สามสําหรับ Ad Manager

กําหนดค่าเซิร์ฟเวอร์โฆษณาบุคคลที่สามให้ใส่ Nonce ไว้ในคําขอของเซิร์ฟเวอร์ไปยัง Ad Manager ต่อไปนี้คือตัวอย่างแท็กโฆษณาที่กําหนดค่าภายในเซิร์ฟเวอร์โฆษณาของบุคคลที่สาม

'https://pubads.serverside.net/gampad/ads?givn=%%custom_key_for_google_nonce%%&...'

ดูรายละเอียดเพิ่มเติมได้ที่คู่มือการติดตั้งใช้งานฝั่งเซิร์ฟเวอร์ของ Google Ad Manager

Ad Manager จะมองหา givn= เพื่อระบุค่า Nonce เซิร์ฟเวอร์โฆษณาของบุคคลที่สามต้องรองรับมาโครบางอย่างของตนเอง เช่น %%custom_key_for_google_nonce%% และแทนที่ด้วยพารามิเตอร์การค้นหา Nonce ที่คุณระบุไว้ในขั้นตอนก่อนหน้า ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีดำเนินการนี้ได้ในเอกสารประกอบของเซิร์ฟเวอร์โฆษณาบุคคลที่สาม

เท่านี้ก็เรียบร้อย ตอนนี้คุณควรมีการนำไปใช้พารามิเตอร์ Nonce จาก PAL SDK ผ่านเซิร์ฟเวอร์สื่อกลาง แล้วนำไปยัง Google Ad Manager ซึ่งจะช่วยให้สร้างรายได้ได้ดีขึ้นผ่าน Google Ad Manager