Bắt đầu

SDK Thư viện quyền truy cập có lập trình (PAL) cho Roku cho phép các nhà xuất bản đã được phê duyệt lệnh gọi VAST trực tiếp (DVC) kiếm tiền từ các ứng dụng Roku dựa trên DVC. PAL SDK cho phép bạn yêu cầu số chỉ dùng một lần (là chuỗi được mã hoá) từ Google, vì vậy mà bạn có thể ký yêu cầu DVC. Mỗi yêu cầu luồng mới phải đi kèm với một số chỉ dùng một lần mới tạo. Tuy nhiên, bạn có thể sử dụng lại cùng một số chỉ dùng một lần cho nhiều quảng cáo các yêu cầu trong cùng một luồng.

Hướng dẫn này giải thích ví dụ về cách kết hợp SDK PAL vào ứng dụng Roku, yêu cầu số chỉ dùng một lần và đăng ký lượt hiển thị quảng cáo.

Điều kiện tiên quyết

Trước khi bắt đầu hướng dẫn này, bạn cần:

  • Môi trường phát triển Roku — hãy xem Hướng dẫn thiết lập môi trường dành cho nhà phát triển Roku để biết thêm thông tin.
  • Một thư mục dự án có cấu trúc như sau:

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

Thiết lập dự án

Trước khi tích hợp SDK PAL, bạn cần định cấu hình tệp dự án của mình.

tệp kê khai

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

Tạo trình phát video mẫu

Thành phần SampleVideoPlayer chỉ bao bọc một thành phần video để quay nhấn điều khiển từ xa. Ghi đè onKeyEvent để sau khi tiêu điểm của điều khiển từ xa được chuyển sang trình phát video/quảng cáo, mọi thao tác nhấn phím khác (lên, xuống, trái, phải, nhấp, v.v.) sẽ được ghi lại và chuyển lên 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>

Tạo giao diện kiểm thử

Triển khai một cảnh có các nút để thực hiện những việc sau:

  • Yêu cầu một số chỉ dùng một lần.
  • Gửi một lượt nhấp vào quảng cáo.
  • Gửi một sự kiện đã bắt đầu phát.
  • Gửi sự kiện phát xong.
  • Chuyển tiêu điểm sang nút video.

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>

Tạo thành phần giao diện SDK

Để giao tiếp giữa cảnh chính và SDK PAL, bạn cần một thành phần chứa mã không đồng bộ. Điều này là cần thiết vì SDK PAL tạo các yêu cầu mạng không thể xảy ra trên luồng chính trong ứng dụng Roku. Để gửi dữ liệu đến thành phần này, bạn cần một giao diện xác định dữ liệu mà gửi và nhận thành phần.

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>

Nhập SDK IMA

Để sử dụng thư viện PAL, bạn cần có IMA SDK cho Roku trong ứng dụng của mình tệp kê khai rồi nhập tệp đó vào thành phần PALInterface.

tệp kê khai

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

Kích hoạt thành phần giao diện từ cảnh

Tiếp theo, hãy thêm mã BrightScript theo dõi các tương tác và điều kiện kích hoạt của người dùng các thay đổi trong thành phần giao diện:

  • Để nhận đầu ra từ thành phần giao diện, hãy triển khai trình quan sát trường trên các trường giao diện liên kết với các đầu ra đó và đính kèm các trình quan sát đó vào các hàm gọi lại trong thành phần chính. Thực hiện việc này khi thành phần này được đăng ký lần đầu.

  • Để gửi các lượt tương tác của người dùng đến thành phần giao diện, hãy triển khai trình quan sát trường trên các nút mà bạn đã tạo trước đó để kích hoạt các thay đổi trong các trường giao diện liên kết với các lệnh đó.

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>

Thêm phương thức để chuyển tiêu điểm

Tiếp theo, hãy quay cảnh người dùng nhấn phím để chuyển tiêu điểm đến và từ phần tử video của bạn.

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>

Khởi chạy SDK PAL và tạo một nonceLoader

Giờ đây, bạn có thể bắt đầu xây dựng logic cốt lõi khi triển khai SDK PAL. Trước tiên, hãy khởi chạy SDK qua một luồng riêng.

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>

Xử lý yêu cầu số chỉ dùng một lần

Sau khi tạo nonceLoader, bạn cần xử lý các yêu cầu về số chỉ dùng một lần bằng cách đính kèm một trình quan sát vào trường requestNonce. Bằng cách chỉ đính kèm trình quan sát này sau khi nonceLoader được khởi tạo, bạn có thể đảm bảo rằng các yêu cầu số chỉ dùng một lần được xử lý trong luồng SDK và chỉ có thể thực hiện yêu cầu số chỉ dùng một lần nếu có nonceLoader hợp lệ.

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>

Giá trị mặc định cho NonceRequest.storageAllowedtrue, nhưng bạn có thể thay đổi giá trị này sau khi có được sự đồng ý thích hợp. Chiến lược phát hành đĩa đơn Phương thức getConsentToStorage() là phần giữ chỗ cho phương thức lấy dữ liệu của riêng bạn sự đồng ý của người dùng, bằng cách tích hợp với một CMP hoặc dựa vào các phương pháp khác để xử lý sự đồng ý về việc lưu trữ.

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

Nghe các tín hiệu về vòng đời của người chơi

Để cho phép tính năng tích hợp PAL gửi tín hiệu đúng cách, bạn cần thiết lập một vòng lặp để theo dõi các tín hiệu trong vòng đời của trình phát.

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>

Đăng ký trình nghe cho sendPlaybackStart, sendPlaybackEnd, sendAdClicksendAdTouch

Tiếp theo, hãy gọi sendPlaybackStart khi "khởi động trình phát video". Phương thức này bắt đầu các lệnh gọi không đồng bộ đến máy chủ của Google để thu thập tín hiệu cần thiết cho việc theo dõi và phát hiện IVT. Gọi sendPlaybackEnd khi quá trình phát kết thúc. Gọi sendAdClick để phản hồi lượt nhấp vào quảng cáo. Sau đó, hãy gọi sendAdTouch cho các sự kiện nhấn hoặc nhấp không phải lượt nhấp của người dùng.

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>

Đính kèm số chỉ dùng một lần vào yêu cầu quảng cáo

Để sử dụng số chỉ dùng một lần mà bạn nhận được từ thư viện PAL trong ứng dụng chính thức, chỉ bắt đầu yêu cầu quảng cáo của mình sau khi số chỉ dùng một lần đã được tạo. Sau đó, hãy thêm số chỉ dùng một lần vào thẻ quảng cáo bằng thông số 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
...

Vậy là xong! Bạn hiện đã có một ứng dụng Roku có thể yêu cầu số chỉ dùng một lần PAL và đăng ký các sự kiện trong phiên phát bằng SDK PAL.

(Không bắt buộc) Gửi tín hiệu Google Ad Manager thông qua máy chủ quảng cáo của bên thứ ba

Định cấu hình yêu cầu của máy chủ quảng cáo bên thứ ba cho Ad Manager.

Định cấu hình máy chủ quảng cáo của bên thứ ba để đưa số chỉ dùng một lần vào yêu cầu cho Ad Manager. Dưới đây là ví dụ về một thẻ quảng cáo được định cấu hình bên trong máy chủ quảng cáo của bên thứ ba:

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

Để biết thêm thông tin, hãy xem Hướng dẫn triển khai phía máy chủ của Google Ad Manager.

Ad Manager tìm givn= để xác định giá trị số chỉ dùng một lần. Máy chủ quảng cáo bên thứ ba cần hỗ trợ một số macro của riêng mình, chẳng hạn như %%custom_key_for_google_nonce%%, và thay thế bằng tham số truy vấn số chỉ dùng một lần mà bạn đã cung cấp ở bước trước. Bạn có thể xem thêm thông tin về cách thực hiện việc này trong tài liệu của máy chủ quảng cáo bên thứ ba.

Vậy là xong! Bây giờ, bạn sẽ thấy thông số số chỉ dùng một lần được truyền từ SDK PAL, thông qua các máy chủ trung gian rồi đến Google Ad Manager. Điều này cho phép kiếm tiền hiệu quả hơn thông qua Google Ad Manager.