El SDK de la biblioteca de acceso programático (PAL) para Roku permite a los publicadores con aprobación de llamadas directas a VAST (DVC) monetizar aplicaciones para Roku basadas en DVC. El SDK de PAL te permite solicitar nonces, que son cadenas encriptadas, a Google para que puedas firmar solicitudes de DVC. Cada solicitud de transmisión nueva debe ir acompañada de un nonce generado recientemente. Sin embargo, puedes volver a usar el mismo nonce para varias solicitudes de anuncios dentro de la misma transmisión.
En esta guía, se explica un ejemplo de cómo incorporar el SDK de PAL en una aplicación para Roku, solicitar un nonce y registrar impresiones de anuncios.
Requisitos previos
Antes de comenzar con esta guía, debes hacer lo siguiente:
- Un entorno de desarrollo de Roku. Consulta la guía de configuración del entorno de desarrollo de Roku para obtener más información.
Una carpeta de proyecto con la siguiente estructura:
./ 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
Configura tu proyecto
Antes de integrar el SDK de PAL, debes configurar los archivos de tu proyecto.
manifiesto
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
Crea un reproductor de video de muestra
El componente SampleVideoPlayer
simplemente une un componente de video para capturar las pulsaciones del control remoto. Anula onKeyEvent
para que, una vez que el enfoque del control remoto se transfiera al reproductor de video o anuncio, se capturen y envíen a PAL los demás toques de teclas (arriba, abajo, izquierda, derecha, clic, etcétera).
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>
Crea una interfaz de prueba
Implementa una escena con botones para hacer lo siguiente:
- Solicita un nonce.
- Envía un clic en el anuncio.
- Envía un evento de reproducción iniciada.
- Envía un evento de reproducción finalizada.
- Transfiere el enfoque al botón de 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>
Crea un componente de interfaz de SDK
Para comunicarte entre la escena principal y el SDK de PAL, necesitas un componente que contenga código asíncrono. Esto es necesario porque el SDK de PAL realiza solicitudes de red externas, que no pueden ocurrir en el subproceso principal de una aplicación para Roku. Para enviar datos a este componente, necesitas una interfaz que defina qué datos envía y recibe el componente.
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>
Importa el SDK de IMA
Para usar la biblioteca de PAL, debes requerir el SDK de IMA para Roku en el manifiesto de tu app y, luego, importarlo al componente PALInterface
.
manifiesto
... 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>
Activa el componente de interfaz desde la escena
A continuación, agrega el código de BrightScript que detecta las interacciones del usuario y activa cambios en el componente de interfaz:
Para recibir resultados del componente de interfaz, implementa observadores de campo en los campos de interfaz asociados con esos resultados y adjúntalos a las funciones de devolución de llamada en el componente principal. Haz esto cuando se registre el componente por primera vez.
Para enviar interacciones del usuario al componente de la interfaz, implementa observadores de campo en los botones que creaste anteriormente para activar cambios en los campos de la interfaz asociados con esos comandos.
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>
Agrega métodos para transferir el enfoque
Luego, captura las presiones de las teclas del usuario para transferir el enfoque desde y hacia tu elemento de 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>
Inicializa el SDK de PAL y crea un nonceLoader
Ahora puedes comenzar a compilar la lógica principal de la implementación del SDK de PAL. Primero, inicializa el SDK desde un subproceso independiente.
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>
Procesa solicitudes de nonce
Una vez que se crea el nonceLoader
, debes controlar las solicitudes de nonce adjuntando un observador al campo requestNonce
. Si adjuntas este observador solo después de que se inicializa nonceLoader
, puedes asegurarte de que las solicitudes de nonce se controlen en el subproceso del SDK y de que solo se pueda realizar una solicitud de nonce si hay un nonceLoader
válido.
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>
Recopila información de consentimiento de almacenamiento
El valor predeterminado para NonceRequest.storageAllowed
es true
, pero este valor se puede cambiar después de obtener el consentimiento adecuado. El método getConsentToStorage()
es un marcador de posición para tu propio método de obtención del consentimiento del usuario, ya sea a través de la integración con una CMP o basándose en otros métodos para manejar el consentimiento de almacenamiento.
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
Escucha los indicadores del ciclo de vida del jugador
Para permitir que la integración de PAL envíe señales correctamente, debes configurar un bucle para escuchar las señales del ciclo de vida del reproductor.
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>
Registra objetos de escucha para sendPlaybackStart
, sendPlaybackEnd
, sendAdClick
y sendAdTouch
A continuación, llama a sendPlaybackStart
en "inicio del reproductor de video". Este método inicia llamadas asíncronas a los servidores de Google a fin de recopilar la señal necesaria para la supervisión y detección del IVT. Llama a sendPlaybackEnd
cuando finalice la reproducción.
Llama a sendAdClick
en respuesta al clic en el anuncio. Luego, llama a sendAdTouch
para los eventos de clic o toque del usuario que no son de clic.
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>
Adjunta el nonce a las solicitudes de anuncios
Para usar el nonce que recibes de la biblioteca PAL en una aplicación de producción, inicia tus solicitudes de anuncios solo después de que se haya generado el nonce. Luego, agrega el nonce a la etiqueta de anuncio con el parámetro 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 ...
Eso es todo. Ahora tienes una app para Roku que puede solicitar un nonce de PAL y registrar eventos de sesión de reproducción con el SDK de PAL.
(Opcional) Envía indicadores de Google Ad Manager a través de servidores de anuncios de terceros
Configura la solicitud del servidor de anuncios de terceros para Ad Manager.
Configura tu servidor de anuncios de terceros para que incluya el nonce en la solicitud del servidor a Ad Manager. Este es un ejemplo de una etiqueta de anuncio configurada dentro del servidor de anuncios de terceros:
'https://pubads.serverside.net/gampad/ads?givn=%%custom_key_for_google_nonce%%&...'
Para obtener más detalles, consulta la guía de implementación del servidor de Google Ad Manager.
Ad Manager busca givn=
para identificar el valor de nonce. El servidor de anuncios de terceros debe admitir alguna macro propia, como %%custom_key_for_google_nonce%%
, y reemplazarla por el parámetro de consulta de nonce que proporcionaste en el paso anterior. Debería haber más información para lograrlo en la documentación del servidor de anuncios de terceros.
Eso es todo. Ahora deberías tener el parámetro nonce propagado desde el SDK de PAL a través de tus servidores intermediarios y, luego, a Google Ad Manager. Esto permite una mejor monetización a través de Google Ad Manager.