الخطوات الأولى

تتيح حزمة تطوير البرامج (SDK) لـ "مكتبة الوصول الآلي" (PAL) على أجهزة Roku للناشرين الذين لديهمموافقة على طلبات VAST المباشرة (DVC) تحقيق الربح من تطبيقات Roku المستندة إلى طلبات DVC. تسمح لك حزمة تطوير البرامج PAL SDK بطلب أرقام التسلسل العشوائية، وهي سلاسل مشفّرة، من Google، كي تتمكّن من توقيع طلبات DVC. يجب أن يكون كل طلب بث جديد مصحوبًا برمز مميّز تم إنشاؤه حديثًا. ومع ذلك، يمكنك إعادة استخدام الرمز المؤقت نفسه لعدة طلبات إعلانات ضمن البث نفسه.

يوضّح هذا الدليل مثالاً على كيفية دمج حزمة تطوير البرامج (SDK) لبرنامج PAL في تطبيق 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>

إنشاء واجهة اختبارية

تنفيذ مشهد يتضمّن أزرارًا لتنفيذ ما يلي:

  • اطلب الحصول على مفتاح تشفير عشوائي.
  • أرسِل نقرة على إعلان.
  • أرسِل حدثًا يشير إلى بدء التشغيل.
  • أرسِل حدثًا يشير إلى انتهاء التشغيل.
  • انقل التركيز إلى زر الفيديو.

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

للتواصل بين المشهد الرئيسي وحزمة تطوير البرامج (SDK) لـ PAL، تحتاج إلى مكوّن يحتوي على رمز غير متزامن. هذا الإجراء ضروري لأنّ حزمة 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>

استيراد حزمة تطوير البرامج (SDK) لإعلانات الوسائط التفاعلية

لاستخدام مكتبة PAL، عليك طلب حزمة تطوير البرامج لإعلانات الوسائط التفاعلية من 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>

إعداد حزمة تطوير البرامج (SDK) لـ PAL وإنشاء nonceLoader

يمكنك الآن البدء في إنشاء المنطق الأساسي لتنفيذ حزمة تطوير البرامج (SDK) لبرنامج PAL. أولاً، عليك إعداد حزمة 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() عنصر نائبًا لطريقة الحصول على موافقة المستخدم، إما من خلال الدمج مع منصّة إدارة الموافقة (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 عند "بدء تشغيل مشغل الفيديو". تبدأ هذه الطريقة في إرسال طلبات برمجية غير متزامنة إلى خوادم Google لجمع الإشارة اللازمة لرصد هجمات التداخل في عرض الإعلانات واكتشافها. اتصل بالرقم 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
...

هذا كل شيء! أصبح لديك الآن تطبيق Roku يمكنه طلب مفتاح PAL عشوائي وتسجيل أحداث جلسة التشغيل باستخدام حزمة تطوير البرامج (SDK) لبروتوكول PAL.

(اختياري) إرسال إشارات "مدير إعلانات Google" من خلال خوادم إعلانات تابعة لجهات خارجية

ضبط طلب خادم الإعلانات التابع لجهة خارجية في "مدير إعلانات Google"

عليك ضبط خادم الإعلانات التابع لجهة خارجية لتضمين المفتاح العشوائي في طلب الخادم إلى "مدير إعلانات Google". في ما يلي مثال على علامة إعلان تمّ ضبطها داخل خادم إعلانات خارجي:

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

لمزيد من التفاصيل، اطّلِع على دليل التنفيذ من جهة الخادم في "مدير إعلانات Google" .

يبحث "مدير إعلانات Google" عن givn= لتحديد قيمة nonce. يجب أن يتيح خادم الإعلانات التابع لجهة خارجية بعض العلامات البرمجية الخاصة به، مثل %%custom_key_for_google_nonce%%، واستبدالها بمَعلمة طلب البحث nonce التي قدّمتها في الخطوة السابقة. من المفترض أن تتوفّر في مستندات خادم الإعلانات التابع لجهة خارجية معلومات إضافية حول كيفية تنفيذ ذلك.

هذا كل شيء! من المفترض أن يتم الآن نشر مَعلمة nonce من حزمة تطوير البرامج (SDK) لمنصّة PAL، من خلال الخوادم الوسيطة، ثم إلى "مدير إعلانات Google". يتيح ذلك تحقيق أرباح أفضل من خلال "مدير إعلانات Google".