מתחילים

ערכת ה-SDK של Programmatic Access Library‏ (PAL) ל-Roku מאפשרת לבעלי תוכן דיגיטלי שקיבלו אישור לשימוש בקריאה ישירה ל-VAST‏ (DVC) לייצר הכנסות מאפליקציות Roku שמבוססות על DVC. ‏PAL SDK מאפשר לבקש מ-Google מחרוזות חד-פעמיות (nonces) מוצפנות, כדי שתוכלו לחתום על בקשות DVC. כל בקשה חדשה לשידור חייבת להיות מלווה במזהה חד-פעמי חדש שנוצר. עם זאת, אפשר לעשות שימוש חוזר באותו nonce במספר בקשות להצגת מודעות באותו מקור נתונים.

במדריך הזה מוסבר על דוגמה לשילוב של PAL SDK באפליקציה ל-Roku, לבקשת קוד חד-פעמי (nonce) ולרישום חשיפות של מודעות.

דרישות מוקדמות

לפני שמתחילים את המדריך הזה, צריך לבצע את הפעולות הבאות:

  • סביבת פיתוח של 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>

יצירת ממשק בדיקה

הטמעת סצנה עם לחצנים לביצוע הפעולות הבאות:

  • שולחים בקשה למזהה חד-פעמי.
  • שולחים קליק על מודעה.
  • שולחים אירוע של הפעלת ההפעלה.
  • שולחים אירוע של סיום ההפעלה.
  • מעבירים את המיקוד ללחצן הסרטון.

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 for Roku במניפסט של האפליקציה ולייבא אותו לרכיב 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 שמקשיב לאינטראקציות של משתמשים ומפעיל שינויים ברכיב הממשק:

  • כדי לקבל פלט מרכיב הממשק, מטמיעים משגיחי שדות בשדות הממשק שמשויכים לפלטים האלה ומצרפים אותם לפונקציות קריאה חוזרת (callback) ברכיב הראשי. צריך לעשות זאת בפעם הראשונה שמירת הרכיב.

  • כדי לשלוח אינטראקציות של משתמשים לרכיב הממשק, מטמיעים משגיחי שדות בלחצנים שיצרתם מקודם כדי להפעיל שינויים בשדות הממשק המשויכים לפקודות האלה.

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, צריך לטפל בבקשות ל-nonce על ידי צירוף משתמש מעקב לשדה 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() היא placeholder לשיטה שלכם לקבלת הסכמה מהמשתמשים, באמצעות שילוב עם 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) שאתם מקבלים מספריית 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
...

זהו! עכשיו יש לכם אפליקציה ל-Roku שיכולה לבקש מפתח PAL חד-פעמי ולרשום אירועים של סשן הפעלה באמצעות PAL SDK.

(אופציונלי) שליחת אותות מ-Google Ad Manager דרך שרתי מודעות של צד שלישי

מגדירים את הבקשה של שרת המודעות של הצד השלישי ל-Ad Manager.

מגדירים את שרת המודעות של הצד השלישי כך שיכלול את המזהה החד-פעמי בבקשה של השרת אל 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%%, ולהחליף אותו בפרמטר השאילתה של המזהה החד-פעמי שסיפקתם בשלב הקודם. מידע נוסף על האופן שבו מבצעים את הפעולה הזו אמור להיות זמין במסמכי העזרה של שרת המודעות של הצד השלישי.

זהו! עכשיו הפרמטר של המזהה החד-פעמי אמור להופיע מ-PAL SDK, דרך השרתים המקשרים ואז ב-Google Ad Manager. כך תוכלו לייצר הכנסות טובות יותר דרך Google Ad Manager.