O SDK da biblioteca de acesso programático (PAL, na sigla em inglês) para Roku permite que os editores com aprovação de chamada VAST direta (DVC, na sigla em inglês) gerem receita com aplicativos Roku baseados em DVC. O SDK PAL permite que você solicite valores de uso único, que são strings criptografadas, do Google para assinar solicitações de DVC. Cada nova solicitação de transmissão precisa ser acompanhada por um valor de uso único recém-gerado. No entanto, é possível reutilizar o mesmo valor de uso único para várias solicitações de anúncio no mesmo stream.
Este guia explica um exemplo de como incorporar o SDK da PAL a um aplicativo Roku, solicitar um valor de uso único e registrar impressões de anúncios.
Pré-requisitos
Antes de iniciar este guia, você precisa fazer o seguinte:
- Um ambiente de desenvolvimento Roku. Consulte o Guia de configuração do ambiente de desenvolvimento Roku para mais informações.
Uma pasta de projeto com a seguinte estrutura:
./ 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
Criar o projeto
Antes de integrar o SDK do PAL, você precisa configurar os arquivos do projeto.
manifesto
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
Criar um player de vídeo de exemplo
O componente SampleVideoPlayer
simplesmente envolve um componente de vídeo para capturar
pressionamentos do controle remoto. Substitua onKeyEvent
para que, quando o foco do controle remoto for
transferido para o player de vídeo/anúncio, todas as outras teclas pressionadas (para cima, para baixo, esquerda, direita, clique etc.) sejam capturadas e transmitidas para o 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>
Criar uma interface de teste
Implementa uma cena com botões para fazer o seguinte:
- Solicite um valor de uso único.
- Enviar um clique no anúncio.
- Envie um evento de reprodução iniciada.
- Envie um evento de reprodução encerrada.
- Transfira o foco para o botão de vídeo.
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>
Criar um componente de interface do SDK
Para se comunicar entre a cena principal e o SDK do PAL, você precisa de um componente que contenha código assíncrono. Isso é necessário porque o SDK do PAL faz solicitações de rede externas, que não podem ocorrer na linha de execução principal em um aplicativo Roku. Para enviar dados a esse componente, você precisa de uma interface que defina quais dados o componente envia e recebe.
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>
Importar o SDK do IMA
Para usar a biblioteca PAL, é necessário exigir o SDK do IMA para Roku no manifesto
do app e importá-lo para o componente PALInterface
.
manifesto
... 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>
Acionar o componente da interface da cena
Em seguida, adicione o código do BrightScript que detecta interações do usuário e aciona mudanças no componente da interface:
Para receber a saída do componente de interface, implemente observadores de campo nos campos de interface associados a essas saídas e anexe-os a funções de callback no componente principal. Faça isso quando o componente for registrado pela primeira vez.
Para enviar interações do usuário ao componente de interface, implemente observadores de campo nos botões que você criou anteriormente para acionar mudanças nos campos de interface associados a esses 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>
Métodos de transferência de foco foram adicionados.
Em seguida, capture as teclas pressionadas pelo usuário para transferir o foco para e do elemento de vídeo.
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>
Inicializar o SDK do PAL e criar um nonceLoader
Agora você pode começar a criar a lógica principal da implementação do SDK da PAL. Primeiro, inicialize o SDK em uma linha de execução separada.
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>
Processar solicitações de valor de uso único
Depois que o nonceLoader
for criado, você precisará processar as solicitações de valor de uso único
anexando um observador ao campo requestNonce
. Ao anexar esse observador somente
depois que o nonceLoader
for inicializado, você pode garantir que as solicitações de valor de uso único sejam
processadas na linha de execução do SDK e que uma solicitação de valor de uso único só possa ser feita se houver
um 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>
Coletar informações de consentimento de armazenamento
O valor padrão de
NonceRequest.storageAllowed
é true
, mas esse valor pode ser alterado depois que você coletar o consentimento adequado. O método
getConsentToStorage()
é um marcador de posição para seu próprio método de recebimento do
consentimento do usuário, seja por integração com uma CMP ou com base em outros métodos para
processar o consentimento de armazenamento.
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
Detectar sinais do ciclo de vida do jogador
Para permitir que a integração do PAL envie sinais corretamente, é necessário configurar um loop para detectar os sinais do ciclo de vida do jogador.
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>
Registre listeners para sendPlaybackStart
, sendPlaybackEnd
, sendAdClick
e sendAdTouch
Em seguida, chame sendPlaybackStart
em "início do player de vídeo". Esse método inicia
chamadas assíncronas para os servidores do Google para coletar o sinal necessário para o monitoramento e a detecção
de IVT. Chame sendPlaybackEnd
quando a reprodução terminar.
Chamar sendAdClick
em resposta ao clique no anúncio. Em seguida, chame sendAdTouch
para
eventos de toque ou clique do usuário sem clique.
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>
Anexar o valor de uso único às solicitações de anúncios
Para usar o valor de uso único que você recebe da biblioteca PAL em um aplicativo de produção,
inicie as solicitações de anúncio somente depois que o valor de uso único for gerado. Em seguida, anexe
o valor de uso único à tag de anúncio usando o 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 ...
Pronto! Agora você tem um app Roku que pode solicitar um valor de uso único do PAL e registrar eventos de sessão de reprodução com o SDK do PAL.
(Opcional) Enviar indicadores do Google Ad Manager por servidores de anúncios de terceiros
Configure a solicitação do servidor de anúncios de terceiros para o Ad Manager.
Configure o servidor de anúncios de terceiros para incluir o valor de uso único na solicitação do servidor para o Ad Manager. Confira um exemplo de tag de anúncio configurada no servidor de anúncios de terceiros:
'https://pubads.serverside.net/gampad/ads?givn=%%custom_key_for_google_nonce%%&...'
Para mais detalhes, consulte o guia de implementação do lado do servidor do Google Ad Manager.
O Ad Manager procura givn=
para identificar o valor de uso único. O servidor de anúncios
de terceiros precisa oferecer suporte a uma macro própria, como
%%custom_key_for_google_nonce%%
, e substituí-la pelo parâmetro de consulta de valor único
fornecido na etapa anterior. Mais informações sobre como fazer isso
estão disponíveis na documentação do servidor de anúncios de terceiros.
Pronto! Agora, o parâmetro de valor de uso único será propagado do SDK do PAL através dos servidores intermediários e, em seguida, para o Google Ad Manager. Isso permite uma melhor monetização pelo Google Ad Manager.