Bắt đầu

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

Hướng dẫn này giải thích một ví dụ về cách kết hợp SDK PAL vào ứng dụng Roku, yêu cầu một số chỉ dùng một lần và đăng ký số 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 thực hiện những việc sau:

  • Môi trường phát triển Roku — 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 các tệp dự án.

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

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

Thành phần SampleVideoPlayer chỉ cần gói thành phần video để ghi lại các thao tác 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 tiếp theo (lên, xuống, trái, phải, nhấp, v.v.) đều được chụp và tạo bong bóng lên đế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 một sự kiện đã kết thúc phát.
  • Chuyển tiêu điểm vào 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 có 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 đưa ra các yêu cầu về mạng bên ngoài, những yêu cầu này 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 có một giao diện xác định dữ liệu mà thành phần gửi và nhậ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ó SDK IMA cho Roku trong tệp kê khai ứng dụng và nhập vào thành phần 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>

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

Tiếp theo, hãy thêm mã BrightScript giúp theo dõi hoạt động tương tác của người dùng và kích hoạt 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 chúng vào các hàm gọi lại trong thành phần chính. Hãy thực hiện việc này khi thành phần này được đăng ký lần đầu.

  • Để gửi 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 chụp các thao tác nhấn phím của người dùng để chuyển tiêu điểm giữa các phần tử video.

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

Bây giờ, bạn có thể bắt đầu xây dựng logic cốt lõi của việc triển khai SDK PAL. Trước tiên, hãy khởi chạy SDK từ 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 số chỉ dùng một lần bằng cách đính kèm đối tượng tiếp nhận dữ liệu 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 chạy, bạn có thể đảm bảo rằng các yêu cầu số chỉ dùng một lần sẽ đượ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 của NonceRequest.storageAllowedtrue, nhưng bạn có thể thay đổi giá trị này sau khi thu thập sự đồng ý thích hợp. Phương thức getConsentToStorage() là một phần giữ chỗ cho phương thức của riêng bạn để thu thập 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 trên các phương thức khác để xử lý sự đồng ý của người dùng về bộ nhớ.

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

Lắng nghe tín hiệu vòng đời của người chơi

Để cho phép việc tích hợp PAL gửi tín hiệu đúng cách, bạn cần thiết lập vòng lặp để lắng nghe các tín hiệu trong vòng đời của người chơi.

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 "bắt đầu 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 hoạt động giám sát 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 cho nhấp chuột vào quảng cáo. Sau đó, hãy gọi sendAdTouch cho các sự kiện nhấp hoặc chạm không phải 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 các yêu cầu quảng cáo 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 cách sử dụng tham 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! Giờ đây, bạn đã có một ứng dụng Roku có thể yêu cầu một số chỉ dùng một lần PAL và đăng ký các sự kiện phiên phát lại 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 đối với Ad Manager.

Định cấu hình máy chủ quảng cáo bên thứ ba để đưa số chỉ dùng một lần vào yêu cầu của máy chủ đối với 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 bên thứ ba:

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

Để biết thêm thông tin chi tiết, 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 nên 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ẽ truyền tham số số chỉ dùng một lần từ SDK PAL, thông qua các máy chủ trung gian của bạn rồi tới Google Ad Manager. Điều này giúp bạn kiếm tiền hiệu quả hơn thông qua Google Ad Manager.