Comenzar

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>

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.