1. Visão geral
Este codelab ensina a modificar um app Android TV para que ele ofereça suporte a transmissão e comunicação dos apps de envio do Google Cast.
O que é o Google Cast e a Cast Connect?
O Google Cast permite que os usuários transmitam conteúdo de um dispositivo móvel para uma TV. Uma sessão típica do Google Cast consiste em dois componentes: um aplicativo de transmissão e um receptor. Os aplicativos de transmissão, como um app para dispositivos móveis ou um site, como o YouTube.com, iniciam e controlam a reprodução de um aplicativo receptor do Cast. Os aplicativos receptores do Cast são apps HTML 5 executados em dispositivos Chromecast e Android TV.
Quase todo o estado de uma sessão do Google Cast é armazenado no app receptor. Quando o estado for atualizado, por exemplo, se um novo item de mídia for carregado, um status de mídia será transmitido para todos os apps de transmissão. Essas transmissões contêm o estado atual da sessão do Google Cast. Os aplicativos de transmissão usam esse status de mídia para exibir informações de reprodução na IU.
O Cast Connect expande essa infraestrutura, com seu app Android TV atuando como um receptor. A biblioteca Cast Connect permite que seu app Android TV receba mensagens e transmita o status de mídia como se fosse um aplicativo receptor.
O que vamos criar?
Ao concluir este codelab, você poderá usar os apps de transmissão do Google Cast para transmitir vídeos para um app Android TV, que também pode se comunicar com apps de transmissão com o protocolo do Cast.
O que você vai aprender
- Como adicionar a biblioteca Cast Connect a um app ATV de amostra.
- Como conectar um transmissor do Cast e iniciar o app ATV.
- Como iniciar a reprodução de mídia no app ATV usando um app de transmissão do Google Cast.
- Como enviar o status de mídia do app ATV para os apps de transmissão do Google Cast.
O que é necessário
- O SDK do Android mais recente.
- A versão mais recente do Android Studio. Especificamente,
Chipmunk | 2021.2.1
ou versões mais recentes. - Um dispositivo Android TV com as opções do desenvolvedor e a depuração USB ativadas.
- Um smartphone Android com as opções do desenvolvedor e a depuração USB ativadas.
- Um cabo de dados USB para conectar o smartphone Android e os dispositivos Android TV ao computador de desenvolvimento.
- Conhecimento básico de desenvolvimento de apps Android usando Kotlin.
2. Fazer o download do exemplo de código
Você pode fazer download de todo o exemplo de código para seu computador…
e descompactar o arquivo ZIP salvo.
3. Executar o app de amostra
Primeiro, vamos ver a aparência do app de amostra concluído. O app Android TV usa a IU do Leanback e um player de vídeo básico. O usuário pode selecionar um vídeo de uma lista para ser reproduzido na TV. Com o app de transmissão para dispositivos móveis associado, um usuário também pode transmitir um vídeo para o app Android TV.
Registrar dispositivos de desenvolvedor
Para ativar os recursos da Cast Connect para o desenvolvimento de apps, é preciso registrar o número de série do Chromecast integrado do dispositivo Android TV que você vai usar no Console para desenvolvedores do Google Cast. Você pode encontrar o número de série acessando Configurações > Preferências do dispositivo > Chromecast integrado > Número de série no Android TV. Observe que ele é diferente do número de série do dispositivo físico e precisa ser verificado usando o método descrito acima.
Se você não registrar o dispositivo, a Cast Connect só funcionará para apps instalados da Google Play Store por motivos de segurança. Reinicie o dispositivo 15 minutos depois do registro.
Instalar o app Android de transmissão
Para testar o envio de solicitações de um dispositivo móvel, fornecemos um aplicativo de transmissão simples chamado "Transmitir vídeos como um arquivo mobile-sender-0629.apk
" no arquivo ZIP do código-fonte. Usaremos o ADB para instalar o APK. Se você já tiver instalado uma versão diferente do Cast Videos, desinstale essa versão de todos os perfis no dispositivo antes de continuar.
- Ative as opções do desenvolvedor e a depuração USB no smartphone Android.
- Use um cabo de dados USB para conectar o smartphone Android ao computador de desenvolvimento.
- Instale o
mobile-sender-0629.apk
no smartphone Android.
- Encontre o app de transmissão Transmitir vídeos no smartphone Android.
Instalar o app Android TV
As instruções a seguir descrevem como abrir e executar o app de amostra concluído no Android Studio:
- Selecione Import Project na tela de boas-vindas ou as opções File > New > Import Project… do menu.
- Selecione o diretório
app-done
na pasta de exemplo de código e clique em OK. - Clique em File > Sync Project with Gradle Files.
- Ative as opções do desenvolvedor e a depuração USB no dispositivo Android TV.
- Faça a conexão adb com seu dispositivo Android TV. O dispositivo será exibido no Android Studio.
- Clique no botão Run. O app ATV Cast Connect Codelab vai aparecer após alguns segundos.
Vamos usar a Cast Connect com o app ATV
- Acesse a tela inicial do Android TV.
- Abra o app de transmissão Cast Videos no smartphone Android. Clique no botão Transmitir e selecione o dispositivo ATV.
- O app ATV Cast Connect Codelab será iniciado no ATV, e o botão Transmitir no app de transmissão vai indicar que ele está conectado .
- Selecione um vídeo no app ATV e ele começará a ser reproduzido no ATV.
- No smartphone, um minicontrolador estará visível na parte inferior do app de transmissão. Você pode usar o botão assistir/pausar para controlar a reprodução.
- Selecione um vídeo no smartphone para reproduzi-lo. O vídeo começará a ser reproduzido no ATV e o controlador expandido será exibido no transmissor móvel.
- Bloqueie o smartphone e, ao desbloqueá-lo, você verá uma notificação na tela de bloqueio para controlar a reprodução de mídia ou parar a transmissão.
4. Preparar o projeto inicial
Agora que confirmamos a integração completa da Cast Connect no app, precisamos adicionar compatibilidade com a Cast Connect ao app inicial que você transferiu por download. Agora está tudo pronto para você criar com base no projeto inicial usando o Android Studio:
- Selecione Import Project na tela de boas-vindas ou as opções File > New > Import Project… do menu.
- Selecione o diretório
app-start
na pasta de exemplo de código e clique em "OK". - Clique em File > Sync Project with Gradle Files.
- Selecione um dispositivo ATV e clique no botão Run para executar o app e conferir a interface.
Design do app
O app fornece uma lista de vídeos para os usuários navegarem. Os usuários podem selecionar um vídeo para assistir no Android TV. O app consiste em duas atividades principais: MainActivity
e PlaybackActivity
.
MainActivity
Esta atividade contém um fragmento (MainFragment
). A lista de vídeos e os metadados associados são configurados na classe MovieList
. O método setupMovies()
é chamado para criar uma lista de objetos Movie
.
Um objeto Movie
representa uma entidade de vídeo com título, descrição, miniaturas e URL do vídeo. Cada objeto Movie
está vinculado a um CardPresenter
para exibir a miniatura do vídeo com o título e o estúdio, e é transmitido para o ArrayObjectAdapter
.
Quando um item é selecionado, o objeto Movie
correspondente é transmitido para a PlaybackActivity
.
PlaybackActivity
Esta atividade contém um fragmento (PlaybackVideoFragment
) que hospeda uma VideoView
com ExoPlayer
, alguns controles de mídia e uma área de texto para mostrar a descrição do vídeo selecionado e permite que o usuário reproduza o vídeo no Android TV. O usuário pode usar o controle remoto para assistir/pausar ou procurar na reprodução de vídeos.
Pré-requisitos da Cast Connect
A Cast Connect usa novas versões do Google Play Services que exigem que seu app ATV tenha sido atualizado para usar o namespace do AndroidX.
Para que haja compatibilidade com a Cast Connect no app Android TV, você precisa criar e oferecer compatibilidade com eventos de uma sessão de mídia. A biblioteca Cast Connect gera o status de mídia com base no status da sessão de mídia. A sessão de mídia também é usada pela biblioteca Cast Connect para sinalizar quando receber determinadas mensagens de um transmissor, como uma pausa.
5. Como configurar a compatibilidade com o Google Cast
Dependências
Atualize o arquivo build.gradle
do app para incluir as dependências de biblioteca necessárias:
dependencies {
....
// Cast Connect libraries
implementation 'com.google.android.gms:play-services-cast-tv:20.0.0'
implementation 'com.google.android.gms:play-services-cast:21.1.0'
}
Sincronize o projeto para confirmar se foi criado sem erros.
Inicialização
CastReceiverContext
é um objeto Singleton para coordenar todas as interações com o Google Cast. Implemente a interface ReceiverOptionsProvider
para fornecer CastReceiverOptions
quando o CastReceiverContext
for inicializado.
Crie o arquivo CastReceiverOptionsProvider.kt
e adicione a seguinte classe ao projeto:
package com.google.sample.cast.castconnect
import android.content.Context
import com.google.android.gms.cast.tv.ReceiverOptionsProvider
import com.google.android.gms.cast.tv.CastReceiverOptions
class CastReceiverOptionsProvider : ReceiverOptionsProvider {
override fun getOptions(context: Context): CastReceiverOptions {
return CastReceiverOptions.Builder(context)
.setStatusText("Cast Connect Codelab")
.build()
}
}
Em seguida, especifique o provedor de opções do receptor na tag <application>
do arquivo AndroidManifest.xml
do app:
<application>
...
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.sample.cast.castconnect.CastReceiverOptionsProvider" />
</application>
Para se conectar com o app ATV de transmissão do Cast, selecione a atividade que você quer iniciar. Neste codelab, vamos iniciar a MainActivity
do app quando uma sessão do Cast for iniciada. No arquivo AndroidManifest.xml
, adicione o filtro de intent de inicialização na MainActivity
.
<activity android:name=".MainActivity">
...
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Ciclo de vida do contexto do receptor do Cast
Inicie o CastReceiverContext
junto do app e interrompa o CastReceiverContext
quando o app for para o segundo plano. Recomendamos usar o LifecycleObserver
da biblioteca androidx.lifecycle para gerenciar chamadas de CastReceiverContext.start()
e CastReceiverContext.stop()
.
Abra o arquivo MyApplication.kt
e inicialize o contexto de transmissão chamando initInstance()
no método onCreate
do aplicativo. Na classe AppLifeCycleObserver
, use start()
no CastReceiverContext
quando o aplicativo for retomado e stop()
quando ele for pausado:
package com.google.sample.cast.castconnect
import com.google.android.gms.cast.tv.CastReceiverContext
...
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
CastReceiverContext.initInstance(this)
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())
}
class AppLifecycleObserver : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
Log.d(LOG_TAG, "onResume")
CastReceiverContext.getInstance().start()
}
override fun onPause(owner: LifecycleOwner) {
Log.d(LOG_TAG, "onPause")
CastReceiverContext.getInstance().stop()
}
}
}
Como conectar a MediaSession ao MediaManager
MediaManager
é uma propriedade do Singleton CastReceiverContext
, que gerencia o status da mídia, processa a intent de carregamento, converte as mensagens do namespace de mídia dos transmissores em comandos de mídia e envia o status da mídia de volta aos transmissores.
Quando você cria uma MediaSession
, também é necessário fornecer o token da MediaSession
atual para MediaManager
para que ele saiba para onde enviar os comandos e recuperar o estado da reprodução de mídia. No arquivo PlaybackVideoFragment.kt
, verifique se a MediaSession
foi inicializada antes de definir o token como MediaManager
.
import com.google.android.gms.cast.tv.CastReceiverContext
import com.google.android.gms.cast.tv.media.MediaManager
...
class PlaybackVideoFragment : VideoSupportFragment() {
private var castReceiverContext: CastReceiverContext? = null
...
private fun initializePlayer() {
if (mPlayer == null) {
...
mMediaSession = MediaSessionCompat(getContext(), LOG_TAG)
...
castReceiverContext = CastReceiverContext.getInstance()
if (castReceiverContext != null) {
val mediaManager: MediaManager = castReceiverContext!!.getMediaManager()
mediaManager.setSessionCompatToken(mMediaSession!!.getSessionToken())
}
}
}
}
Quando você liberar a MediaSession
devido à reprodução inativa, defina um token nulo em MediaManager
:
private fun releasePlayer() {
mMediaSession?.release()
castReceiverContext?.mediaManager?.setSessionCompatToken(null)
...
}
Vamos executar o app de amostra
Clique no botão Run para implantar o app no dispositivo ATV, feche-o e volte para a tela inicial do ATV. No transmissor, clique no botão Transmitir e selecione o dispositivo ATV. Você verá que o app ATV foi iniciado no dispositivo do ATV e o estado do botão Transmitir mostra que ele está conectado.
6. Carregando mídia
O comando de carregamento é enviado por uma intent com o nome do pacote definido no Developer Console. É necessário adicionar o seguinte filtro de intent pré-definido no app Android TV para especificar a atividade de destino que receberá a intent. No arquivo AndroidManifest.xml
, adicione o filtro de intent de carregamento a PlayerActivity
:
<activity android:name="com.google.sample.cast.castconnect.PlaybackActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Como processar solicitações de carregamento no Android TV
Agora que a atividade está configurada para receber essa intent contendo uma solicitação de carregamento, precisaremos processá-la.
O app chama o método privado processIntent
quando a atividade é iniciada. Esse método contém a lógica para processar as intents recebidas. Para processar uma solicitação de carregamento, modificaremos esse método e enviaremos a intent para ser processada chamando o método onNewIntent
da instância MediaManager
. Se MediaManager
detectar que a intent é uma solicitação de carregamento, ele extrairá o objeto MediaLoadRequestData
da intent e invocará MediaLoadCommandCallback.onLoad()
. Modifique o método processIntent
no arquivo PlaybackVideoFragment.kt
para processar a intent que contém a solicitação de carregamento:
fun processIntent(intent: Intent?) {
val mediaManager: MediaManager = CastReceiverContext.getInstance().getMediaManager()
// Pass intent to Cast SDK
if (mediaManager.onNewIntent(intent)) {
return
}
// Clears all overrides in the modifier.
mediaManager.getMediaStatusModifier().clear()
// If the SDK doesn't recognize the intent, handle the intent with your own logic.
...
}
Em seguida, estenderemos a classe abstrata MediaLoadCommandCallback
que substituirá o método onLoad()
chamado pelo MediaManager
. Este método recebe os dados da solicitação de carregamento e os converte em um objeto Movie
. Depois de convertido, o filme será reproduzido pelo player local. O MediaManager
é atualizado com MediaLoadRequest
e transmite o MediaStatus
para os transmissores conectados. Crie uma classe particular aninhada com o nome MyMediaLoadCommandCallback
no arquivo PlaybackVideoFragment.kt
:
import com.google.android.gms.cast.MediaLoadRequestData
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.cast.MediaError
import com.google.android.gms.cast.tv.media.MediaException
import com.google.android.gms.cast.tv.media.MediaCommandCallback
import com.google.android.gms.cast.tv.media.QueueUpdateRequestData
import com.google.android.gms.cast.tv.media.MediaLoadCommandCallback
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.Tasks
import android.widget.Toast
...
private inner class MyMediaLoadCommandCallback : MediaLoadCommandCallback() {
override fun onLoad(
senderId: String?, mediaLoadRequestData: MediaLoadRequestData): Task<MediaLoadRequestData> {
Toast.makeText(activity, "onLoad()", Toast.LENGTH_SHORT).show()
return if (mediaLoadRequestData == null) {
// Throw MediaException to indicate load failure.
Tasks.forException(MediaException(
MediaError.Builder()
.setDetailedErrorCode(MediaError.DetailedErrorCode.LOAD_FAILED)
.setReason(MediaError.ERROR_REASON_INVALID_REQUEST)
.build()))
} else Tasks.call {
play(convertLoadRequestToMovie(mediaLoadRequestData)!!)
// Update media metadata and state
val mediaManager = castReceiverContext!!.mediaManager
mediaManager.setDataFromLoad(mediaLoadRequestData)
mediaLoadRequestData
}
}
}
private fun convertLoadRequestToMovie(mediaLoadRequestData: MediaLoadRequestData?): Movie? {
if (mediaLoadRequestData == null) {
return null
}
val mediaInfo: MediaInfo = mediaLoadRequestData.getMediaInfo() ?: return null
var videoUrl: String = mediaInfo.getContentId()
if (mediaInfo.getContentUrl() != null) {
videoUrl = mediaInfo.getContentUrl()
}
val metadata: MediaMetadata = mediaInfo.getMetadata()
val movie = Movie()
movie.videoUrl = videoUrl
movie.title = metadata?.getString(MediaMetadata.KEY_TITLE)
movie.description = metadata?.getString(MediaMetadata.KEY_SUBTITLE)
if(metadata?.hasImages() == true) {
movie.cardImageUrl = metadata.images[0].url.toString()
}
return movie
}
Agora que o callback foi definido, registre-o no MediaManager
. O callback precisa ser registrado antes de chamar MediaManager.onNewIntent()
. Adicione setMediaLoadCommandCallback
quando o player for inicializado:
private fun initializePlayer() {
if (mPlayer == null) {
...
mMediaSession = MediaSessionCompat(getContext(), LOG_TAG)
...
castReceiverContext = CastReceiverContext.getInstance()
if (castReceiverContext != null) {
val mediaManager: MediaManager = castReceiverContext.getMediaManager()
mediaManager.setSessionCompatToken(mMediaSession.getSessionToken())
mediaManager.setMediaLoadCommandCallback(MyMediaLoadCommandCallback())
}
}
}
Vamos executar o app de amostra
Clique no botão Run para implantar o app no dispositivo ATV. No transmissor, clique no botão Transmitir e selecione o dispositivo ATV. O app ATV será iniciado no dispositivo ATV. Selecione um vídeo no dispositivo móvel para começar a reprodução no ATV. Verifique se você recebeu uma notificação no smartphone com os controles de reprodução. Tente usar controles, como pausar. O vídeo no dispositivo do ATV precisa ser pausado.
7. Como oferecer compatibilidade com comandos de controle do Cast
Agora, o aplicativo atual é compatível com comandos básicos que são compatíveis com uma sessão de mídia, como reproduzir, pausar e procurar. No entanto, há alguns comandos de controle do Cast que não estão disponíveis na sessão de mídia. Você precisa registrar um MediaCommandCallback
para oferecer compatibilidade com esses comandos de controle do Cast.
Adicione MyMediaCommandCallback
à instância do MediaManager
usando setMediaCommandCallback
quando o player for inicializado:
private fun initializePlayer() {
...
castReceiverContext = CastReceiverContext.getInstance()
if (castReceiverContext != null) {
val mediaManager = castReceiverContext!!.mediaManager
...
mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
}
}
Crie a classe MyMediaCommandCallback
para substituir os métodos, como onQueueUpdate()
, para compatibilidade com esses comandos de controle do Cast:
private inner class MyMediaCommandCallback : MediaCommandCallback() {
override fun onQueueUpdate(
senderId: String?,
queueUpdateRequestData: QueueUpdateRequestData
): Task<Void> {
Toast.makeText(getActivity(), "onQueueUpdate()", Toast.LENGTH_SHORT).show()
// Queue Prev / Next
if (queueUpdateRequestData.getJump() != null) {
Toast.makeText(
getActivity(),
"onQueueUpdate(): Jump = " + queueUpdateRequestData.getJump(),
Toast.LENGTH_SHORT
).show()
}
return super.onQueueUpdate(senderId, queueUpdateRequestData)
}
}
8. Como trabalhar com status de mídia
Como modificar o status da mídia
O Cast Connect recebe o status de mídia base da sessão de mídia. Para oferecer compatibilidade com recursos avançados, o app Android TV pode especificar e substituir outras propriedades de status com um MediaStatusModifier
. MediaStatusModifier
sempre funcionará na MediaSession
que você definiu no CastReceiverContext
.
Por exemplo, para especificar setMediaCommandSupported
quando o callback onLoad
for acionado:
import com.google.android.gms.cast.MediaStatus
...
private class MyMediaLoadCommandCallback : MediaLoadCommandCallback() {
fun onLoad(
senderId: String?,
mediaLoadRequestData: MediaLoadRequestData
): Task<MediaLoadRequestData> {
Toast.makeText(getActivity(), "onLoad()", Toast.LENGTH_SHORT).show()
...
return Tasks.call({
play(convertLoadRequestToMovie(mediaLoadRequestData)!!)
...
// Use MediaStatusModifier to provide additional information for Cast senders.
mediaManager.getMediaStatusModifier()
.setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT, true)
.setIsPlayingAd(false)
mediaManager.broadcastMediaStatus()
// Return the resolved MediaLoadRequestData to indicate load success.
mediaLoadRequestData
})
}
}
Como interceptar o MediaStatus antes da transmissão
Assim como o MessageInterceptor
do SDK do receptor da Web, você pode especificar um MediaStatusWriter
no seu MediaManager
para realizar outras modificações no MediaStatus
antes que ele seja transmitido para os transmissores conectados.
Por exemplo, você pode definir dados personalizados no MediaStatus
antes de enviá-los para transmissores móveis:
import com.google.android.gms.cast.tv.media.MediaManager.MediaStatusInterceptor
import com.google.android.gms.cast.tv.media.MediaStatusWriter
import org.json.JSONObject
import org.json.JSONException
...
private fun initializePlayer() {
if (mPlayer == null) {
...
if (castReceiverContext != null) {
...
val mediaManager: MediaManager = castReceiverContext.getMediaManager()
...
// Use MediaStatusInterceptor to process the MediaStatus before sending out.
mediaManager.setMediaStatusInterceptor(
MediaStatusInterceptor { mediaStatusWriter: MediaStatusWriter ->
try {
mediaStatusWriter.setCustomData(JSONObject("{myData: 'CustomData'}"))
} catch (e: JSONException) {
Log.e(LOG_TAG,e.message,e);
}
})
}
}
}
9. Parabéns
Agora você já sabe como adicionar compatibilidade com o Google Cast a um app Android TV usando a biblioteca Cast Connect.
Consulte o guia para desenvolvedores para mais detalhes: /cast/docs/android_tv_receiver.