Roku용 프로그래매틱 액세스 라이브러리(PAL) SDK를 사용하면 직접 VAST 호출(DVC) 승인을 받은 게시자가 DVC 기반 Roku 애플리케이션으로 수익을 창출할 수 있습니다. 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를 통합하기 전에 프로젝트 파일을 구성해야 합니다.
매니페스트
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
구성요소는 단순히 동영상 구성요소를 래핑하여 리모컨 누르기를 캡처합니다. 리모컨의 포커스가 동영상/광고 플레이어로 전송되면 추가 키 누르기 (위, 아래, 왼쪽, 오른쪽, 클릭 등)가 캡처되어 PAL로 버블링되도록 onKeyEvent
를 재정의합니다.
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 라이브러리를 사용하려면 앱 매니페스트에서 Roku용 IMA SDK를 요구하고 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 코드를 추가합니다.
인터페이스 구성요소에서 출력을 수신하려면 이러한 출력과 연결된 인터페이스 필드에 필드 관찰자를 구현하고 기본 구성요소의 콜백 함수에 연결합니다. 구성요소가 처음 등록될 때 실행합니다.
사용자 상호작용을 인터페이스 구성요소로 전송하려면 이전에 만든 버튼에 필드 관찰자를 구현하여 이러한 명령어와 연결된 인터페이스 필드의 변경사항을 트리거합니다.
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
가 생성되면 관찰자를 requestNonce
필드에 연결하여 nonce 요청을 처리해야 합니다. nonceLoader
가 초기화된 후에만 이 관찰자를 연결하면 nonce 요청이 SDK 스레드에서 처리되고 유효한 nonceLoader
가 있는 경우에만 nonce 요청을 할 수 있습니다.
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 서버에 대한 비동기 호출을 시작하여 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 라이브러리에서 수신한 nonce를 사용하려면 nonce가 생성된 후에만 광고 요청을 시작하세요. 그런 다음 u_paln
매개변수를 사용하여 광고 태그에 nonce를 추가합니다.
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 ...
작업이 끝났습니다. 이제 PAL nonce를 요청하고 PAL SDK에 재생 세션 이벤트를 등록할 수 있는 Roku 앱이 생겼습니다.
(선택사항) 서드 파티 광고 서버를 통해 Google Ad Manager 신호 전송
서드 파티 광고 서버의 Ad Manager 요청을 구성합니다.
서버의 Ad Manager 요청에 nonce를 포함하도록 서드 파티 광고 서버를 구성합니다. 다음은 서드 파티 광고 서버 내에서 구성된 광고 태그의 예입니다.
'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를 통해 더 효과적으로 수익을 창출할 수 있습니다.