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