1. Visão geral
Este codelab ensinará você a modificar um app de vídeo Android existente para transmitir conteúdo em um dispositivo compatível com Google Cast.
O que é o Google Cast?
O Google Cast permite que os usuários transmitam conteúdo de um dispositivo móvel para uma TV. O dispositivo poderá ser usado como controle remoto para a reprodução de mídia na TV.
O SDK do Google Cast amplia seu app para controlar uma TV ou um sistema de som. Com o SDK do Cast, é possível adicionar os componentes de interface necessários com base na Lista de verificação de design do Google Cast.
Essa lista é fornecida para facilitar a experiência do usuário do Google Cast e deixá-la mais previsível em todas as plataformas compatíveis.
O que vamos criar?
Ao concluir este codelab, você terá um app de vídeo para Android que poderá transmitir vídeos para um dispositivo compatível com Google Cast.
O que você vai aprender
- Como adicionar o SDK do Google Cast a um app de vídeo de amostra.
- Como adicionar o botão "Transmitir" para selecionar um dispositivo com Google Cast.
- Como se conectar a um dispositivo com Google Cast e iniciar um receptor de mídia.
- Como transmitir um vídeo.
- Como adicionar um minicontrole do Google Cast ao seu app.
- Como oferecer compatibilidade com notificações de mídia e controles na tela de bloqueio.
- Como adicionar um controle expandido.
- Como fornecer uma sobreposição introdutória.
- Como personalizar os widgets do Google Cast.
- Como fazer a integração com a Cast Connect
O que é necessário
- O SDK do Android mais recente
- O Android Studio versão 3.2 ou mais recente.
- Um dispositivo móvel com Android 4.1 Jelly Bean (API de nível 16) ou mais recente.
- Um cabo de dados USB para conectar seu dispositivo móvel ao computador de desenvolvimento.
- Um dispositivo com Google Cast, como um Chromecast ou Android TV, configurado com acesso à Internet.
- Uma TV ou um monitor com entrada HDMI.
- O Chromecast com Google TV é necessário para testar a integração do Cast Connect, mas é opcional no restante do codelab. Caso ainda não tenha, pule a etapa Adicionar suporte do Cast Connect ao final deste tutorial.
Experiência
- É necessário ter conhecimento sobre desenvolvimento em Kotlin e Android.
- Também é necessário ter conhecimento prévio de como assistir TV :)
Como você usará este tutorial?
Como você classificaria sua experiência com a criação de apps Android?
Como você classificaria sua experiência com o ato de assistir TV?
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 é um player de vídeo básico. O usuário pode selecionar um vídeo em uma lista e assisti-lo localmente no dispositivo ou transmiti-lo para um dispositivo com Google Cast.
Com o código transferido por download, as instruções a seguir descrevem como abrir e executar o app de exemplo concluído no Android Studio:
Selecione Import Project na tela inicial ou clique em File > Novo > Opções de menu de importação de projeto...
Selecione o diretório app-done
na pasta de exemplo de código e clique em OK.
Clique em Arquivo > Sincronizar o projeto com arquivos do Gradle.
Ative a depuração USB no seu dispositivo Android. No Android 4.2 e versões mais recentes, a tela "Opções do desenvolvedor" fica oculta por padrão. Para exibi-la, acesse Configurações > Sobre o dispositivo e toque em Número da versão sete vezes. Retorne à tela anterior, acesse Sistema > Avançado e toque em Opções do desenvolvedor na parte inferior da tela. Em seguida, toque em Depuração USB para ativar essa opção.
Conecte o dispositivo Android e clique no botão Run no Android Studio. Você verá o app de vídeo Cast Videos após alguns segundos.
Clique no botão "Transmitir" no app de vídeo e selecione seu dispositivo com Google Cast.
Selecione um vídeo e clique no botão para assistir.
O vídeo começará a ser reproduzido no seu dispositivo com Google Cast.
O controle expandido será exibido. É possível usar o botão "Assistir/Pausar" para controlar a reprodução.
Volte à lista de vídeos.
Um minicontrole agora está visível na parte inferior da tela.
Clique no botão "Pausar" do minicontrole para pausar o vídeo no receptor. Clique no botão "Assistir" para continuar a reprodução do vídeo.
Clique no botão home do dispositivo móvel. Puxando as notificações para baixo, você verá uma para a sessão do Google Cast.
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.
Retorne ao app de vídeo e clique no botão "Transmitir" para interromper a transmissão no dispositivo com Google Cast.
Perguntas frequentes
4. Preparar o projeto inicial
Precisamos adicionar compatibilidade com o Google Cast ao app inicial que você transferiu por download. Veja alguns termos do Google Cast que usaremos neste codelab:
- Um app remetente é executado em um dispositivo móvel ou laptop.
- Um aplicativo receptor é executado no dispositivo com Google Cast.
Agora está tudo pronto para você criar com base no projeto inicial usando o Android Studio:
- Selecione o diretório
app-start
no download do exemplo de código (selecione Import Project na tela inicial ou a opção de menu File > New > Import Project...). - Clique no botão Sync Project with Gradle Files.
- Clique no botão Run para executar o app e explorar a interface.
Design do app
O app busca uma lista de vídeos de um servidor da Web remoto e fornece uma lista para o usuário navegar. Os usuários podem selecionar um vídeo para ver os detalhes ou assisti-lo localmente no dispositivo móvel.
O app consiste em duas atividades principais: VideoBrowserActivity
e LocalPlayerActivity
. Para integrar a funcionalidade do Google Cast, as atividades precisam ser herdadas da AppCompatActivity
ou do elemento pai FragmentActivity
. Essa limitação existe porque precisamos adicionar o MediaRouteButton
(fornecido na biblioteca de suporte MediaRouter) como um MediaRouteActionProvider
, e isso só funcionará se a atividade for herdada das classes mencionadas acima. A biblioteca de suporte MediaRouter depende da biblioteca de suporte AppCompat, que fornece as classes necessárias.
VideoBrowserActivity
Esta atividade contém um Fragment
(VideoBrowserFragment
). Essa lista é baseada em um ArrayAdapter
(VideoListAdapter
). A lista de vídeos e os metadados associados a eles são hospedados em um servidor remoto como um arquivo JSON. Um AsyncTaskLoader
(VideoItemLoader
) busca esse JSON e o processa para criar uma lista de objetos MediaItem
.
Um objeto MediaItem
modela um vídeo e os metadados dele, como o título, a descrição, o URL do stream, o URL das imagens de apoio e faixas de texto associadas (para closed captions), se houver. O objeto MediaItem
é transmitido entre as atividades. Portanto, o MediaItem
tem métodos utilitários para convertê-lo em um Bundle
e vice-versa.
Quando o carregador cria a lista de MediaItems
, ele transmite essa lista para o VideoListAdapter
, que apresenta a lista de MediaItems
no VideoBrowserFragment
. Uma lista de miniaturas de vídeos é apresentada ao usuário com uma breve descrição de cada vídeo. Quando um item é selecionado, o MediaItem
correspondente é convertido em um Bundle
e transmitido para a LocalPlayerActivity
.
LocalPlayerActivity
Essa atividade exibe os metadados sobre um determinado vídeo e permite que o usuário o assista localmente no dispositivo móvel.
A atividade hospeda um VideoView
, alguns controles de mídia e uma área de texto para mostrar a descrição do vídeo selecionado. O player cobre a parte superior da tela, deixando espaço para a descrição detalhada do vídeo abaixo. O usuário pode assistir/pausar ou fazer uma busca na reprodução local de vídeos.
Dependências
Como estamos usando AppCompatActivity
, precisamos da biblioteca de suporte AppCompat. Estamos usando a biblioteca Volley para gerenciar a lista de vídeos e receber as imagens dela de maneira assíncrona.
Perguntas frequentes
5. Como adicionar o botão "Transmitir"
Um aplicativo compatível com Cast exibe o botão "Transmitir" em cada uma das atividades dele. Ao clicar nesse botão, é exibida uma lista de dispositivos de transmissão que o usuário pode selecionar. Se o usuário estava assistindo conteúdo localmente no dispositivo remetente, selecionar um dispositivo de transmissão iniciará ou retomará a reprodução no dispositivo com Google Cast. A qualquer momento durante uma sessão, o usuário pode clicar no botão "Transmitir" e parar a transmissão do seu aplicativo para o dispositivo com Google Cast. O usuário precisa conseguir se conectar ou desconectar do dispositivo de transmissão durante qualquer atividade no app, conforme descrito na Lista de verificação de design do Google Cast.
Dependências
Atualize o arquivo build.gradle do app para incluir as dependências necessárias da biblioteca.
dependencies {
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'androidx.mediarouter:mediarouter:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'com.google.android.gms:play-services-cast-framework:21.1.0'
implementation 'com.android.volley:volley:1.2.1'
implementation "androidx.core:core-ktx:1.8.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}
Sincronize o projeto para confirmar se ele foi criado sem erros.
Inicialização
O framework do Google Cast tem um objeto Singleton global, o CastContext
, que coordena todas as interações com o Google Cast.
Implemente a interface OptionsProvider
para fornecer o CastOptions
necessário para inicializar o Singleton CastContext
. A opção mais importante é o ID do aplicativo receptor, que é usado para filtrar os resultados da descoberta de dispositivos com Google Cast e abrir o aplicativo receptor quando uma sessão de transmissão é iniciada.
Quando você desenvolve seu próprio app compatível com Cast, é preciso se registrar como desenvolvedor do Google Cast e receber um ID para seu app. Para este codelab, usaremos um ID de app de amostra.
Adicione o seguinte novo arquivo CastOptionsProvider.kt
ao pacote com.google.sample.cast.refplayer
do projeto:
package com.google.sample.cast.refplayer
import android.content.Context
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.SessionProvider
class CastOptionsProvider : OptionsProvider {
override fun getCastOptions(context: Context): CastOptions {
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.build()
}
override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
return null
}
}
Agora, declare o OptionsProvider
na tag "application
" do arquivo AndroidManifest.xml
do app:
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.sample.cast.refplayer.CastOptionsProvider" />
Inicialize lentamente o CastContext
no método onCreate VideoBrowserActivity
:
import com.google.android.gms.cast.framework.CastContext
private var mCastContext: CastContext? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.video_browser)
setupActionBar()
mCastContext = CastContext.getSharedInstance(this)
}
Adicione a mesma lógica de inicialização à LocalPlayerActivity
.
Botão "Transmitir"
Agora que o CastContext
foi inicializado, precisamos adicionar o botão "Transmitir" para permitir que o usuário selecione um dispositivo com Google Cast. O botão Transmitir é implementado pelo MediaRouteButton
da Biblioteca de Suporte MediaRouter. Como qualquer ícone de ação que você pode adicionar à sua atividade (usando uma ActionBar
ou uma Toolbar
), primeiro é necessário adicionar o item de menu correspondente ao seu menu.
Edite o arquivo res/menu/browse.xml
e adicione o item MediaRouteActionProvider
no menu antes do item de configurações:
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
Substitua o método onCreateOptionsMenu()
do VideoBrowserActivity
usando CastButtonFactory
para conectar o MediaRouteButton
ao framework do Cast:
import com.google.android.gms.cast.framework.CastButtonFactory
private var mediaRouteMenuItem: MenuItem? = null
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.browse, menu)
mediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu,
R.id.media_route_menu_item)
return true
}
Substitua onCreateOptionsMenu
na LocalPlayerActivity
de modo semelhante.
Clique no botão Run para executar o app no seu dispositivo móvel. Você verá um botão "Transmitir" na barra de ações do app. Quando clicado, ele listará os dispositivos com Google Cast na sua rede local. A descoberta de dispositivos é gerenciada automaticamente pelo CastContext
. Selecione o dispositivo de transmissão para que o app de amostra receptor seja carregado nele. Você pode alternar entre a atividade de navegação e a atividade do player local mantendo estado do botão "Transmitir" sincronizado.
Não adicionamos compatibilidade com a reprodução de mídia, então você ainda não pode assistir vídeos no dispositivo de transmissão. Clique no botão "Transmitir" para se desconectar.
6. Como transmitir conteúdo de vídeo
Nós ampliaremos o app de amostra para que ele também reproduza vídeos remotamente em um dispositivo com Google Cast. Para isso, precisamos detectar os diversos eventos gerados pelo framework do Google Cast.
Como transmitir mídia
De modo geral, se você quiser reproduzir mídia em um dispositivo com Google Cast, precisará fazer o seguinte:
- Crie um objeto
MediaInfo
que modele um item de mídia. - Conectar-se ao dispositivo com Google Cast e iniciar o aplicativo receptor.
- Carregar o objeto
MediaInfo
no seu receptor e reproduzir o conteúdo. - Monitorar o status da mídia.
- Enviar comandos de reprodução ao receptor com base nas interações do usuário.
Já fizemos a etapa 2 na seção anterior. A etapa 3 é fácil de fazer com o framework do Google Cast. A etapa 1 é o mesmo que mapear um objeto para outro. MediaInfo
é algo que o framework do Google Cast entende, e MediaItem
é o encapsulamento do nosso app para um item de mídia. É fácil mapear um MediaItem
para um MediaInfo
.
A LocalPlayerActivity
do app de amostra já distingue entre a reprodução local e a remota usando esta enumeração:
private var mLocation: PlaybackLocation? = null
enum class PlaybackLocation {
LOCAL, REMOTE
}
enum class PlaybackState {
PLAYING, PAUSED, BUFFERING, IDLE
}
Neste codelab, não é importante entender exatamente como toda a lógica do player da amostra funciona. É importante compreender que o player de mídia do app precisa ser modificado para estar ciente dos dois locais de reprodução de maneira semelhante.
No momento, o player local está sempre em estado de reprodução local, já que ele ainda não sabe nada sobre os estados de transmissão. Nós precisamos atualizar a IU com base nas transições de estado que ocorrem no framework do Google Cast. Por exemplo, se iniciarmos a transmissão, será necessário parar a reprodução local e desativar alguns controles. Da mesma forma, se pararmos a transmissão quando estivermos nessa atividade, será necessário mudar para a reprodução local. Para isso, precisamos detectar os diversos eventos gerados pelo framework do Google Cast.
Gerenciamento da sessão de transmissão
Para o framework do Google Cast, uma sessão combina as etapas de conexão a um dispositivo, inicialização (ou participação), conexão a um aplicativo receptor e inicialização de um canal de controle de mídia, se adequado. O canal de controle de mídia é a forma como o framework do Google Cast envia e recebe mensagens do player de mídia do receptor.
A sessão do Google Cast será iniciada automaticamente quando o usuário selecionar um dispositivo usando o botão "Transmitir" e será interrompida quando o usuário se desconectar. A reconexão a uma sessão de receptor devido a problemas na rede também é processada automaticamente pelo SDK do Google Cast.
Vamos adicionar um SessionManagerListener
à LocalPlayerActivity
:
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.SessionManagerListener
...
private var mSessionManagerListener: SessionManagerListener<CastSession>? = null
private var mCastSession: CastSession? = null
...
private fun setupCastListener() {
mSessionManagerListener = object : SessionManagerListener<CastSession> {
override fun onSessionEnded(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) {
onApplicationConnected(session)
}
override fun onSessionResumeFailed(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionStarted(session: CastSession, sessionId: String) {
onApplicationConnected(session)
}
override fun onSessionStartFailed(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionStarting(session: CastSession) {}
override fun onSessionEnding(session: CastSession) {}
override fun onSessionResuming(session: CastSession, sessionId: String) {}
override fun onSessionSuspended(session: CastSession, reason: Int) {}
private fun onApplicationConnected(castSession: CastSession) {
mCastSession = castSession
if (null != mSelectedMedia) {
if (mPlaybackState == PlaybackState.PLAYING) {
mVideoView!!.pause()
loadRemoteMedia(mSeekbar!!.progress, true)
return
} else {
mPlaybackState = PlaybackState.IDLE
updatePlaybackLocation(PlaybackLocation.REMOTE)
}
}
updatePlayButton(mPlaybackState)
invalidateOptionsMenu()
}
private fun onApplicationDisconnected() {
updatePlaybackLocation(PlaybackLocation.LOCAL)
mPlaybackState = PlaybackState.IDLE
mLocation = PlaybackLocation.LOCAL
updatePlayButton(mPlaybackState)
invalidateOptionsMenu()
}
}
}
Nas atividades da LocalPlayerActivity
, queremos saber quando ocorrer a conexão ou desconexão com o dispositivo de transmissão para que possamos mudar para o player local ou deixar de usá-lo. Observe que a conectividade pode ser interrompida não só pela instância do seu aplicativo em execução no dispositivo móvel, como também por outra instância do seu (ou de outro) aplicativo em execução em um dispositivo móvel diferente.
A sessão ativa no momento fica acessível como SessionManager.getCurrentSession()
. As sessões são criadas e eliminadas automaticamente em resposta às interações do usuário com as caixas de diálogo do Google Cast.
Precisamos registrar nosso listener de sessão e inicializar algumas variáveis que usaremos na atividade. Mude o método onCreate
da LocalPlayerActivity
para:
import com.google.android.gms.cast.framework.CastContext
...
private var mCastContext: CastContext? = null
...
override fun onCreate(savedInstanceState: Bundle?) {
...
mCastContext = CastContext.getSharedInstance(this)
mCastSession = mCastContext!!.sessionManager.currentCastSession
setupCastListener()
...
loadViews()
...
val bundle = intent.extras
if (bundle != null) {
....
if (shouldStartPlayback) {
....
} else {
if (mCastSession != null && mCastSession!!.isConnected()) {
updatePlaybackLocation(PlaybackLocation.REMOTE)
} else {
updatePlaybackLocation(PlaybackLocation.LOCAL)
}
mPlaybackState = PlaybackState.IDLE
updatePlayButton(mPlaybackState)
}
}
...
}
Como carregar mídia
No SDK do Cast, o RemoteMediaClient
oferece um conjunto de APIs convenientes para gerenciar a reprodução de mídia remota no receptor. Para uma CastSession
compatível com a reprodução de mídia, uma instância de RemoteMediaClient
será criada automaticamente pelo SDK. Para acessá-lo, chame o método getRemoteMediaClient()
na instância de CastSession
. Adicione os seguintes métodos à LocalPlayerActivity
para carregar o vídeo selecionado no receptor:
import com.google.android.gms.cast.framework.media.RemoteMediaClient
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaLoadOptions
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.common.images.WebImage
import com.google.android.gms.cast.MediaLoadRequestData
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
if (mCastSession == null) {
return
}
val remoteMediaClient = mCastSession!!.remoteMediaClient ?: return
remoteMediaClient.load( MediaLoadRequestData.Builder()
.setMediaInfo(buildMediaInfo())
.setAutoplay(autoPlay)
.setCurrentTime(position.toLong()).build())
}
private fun buildMediaInfo(): MediaInfo? {
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
mSelectedMedia?.studio?.let { movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, it) }
mSelectedMedia?.title?.let { movieMetadata.putString(MediaMetadata.KEY_TITLE, it) }
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia!!.getImage(0))))
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia!!.getImage(1))))
return mSelectedMedia!!.url?.let {
MediaInfo.Builder(it)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType("videos/mp4")
.setMetadata(movieMetadata)
.setStreamDuration((mSelectedMedia!!.duration * 1000).toLong())
.build()
}
}
Agora, atualize vários métodos existentes para usar a lógica da sessão do Google Cast e oferecer compatibilidade com a reprodução remota:
private fun play(position: Int) {
startControllersTimer()
when (mLocation) {
PlaybackLocation.LOCAL -> {
mVideoView!!.seekTo(position)
mVideoView!!.start()
}
PlaybackLocation.REMOTE -> {
mPlaybackState = PlaybackState.BUFFERING
updatePlayButton(mPlaybackState)
//seek to a new position within the current media item's new position
//which is in milliseconds from the beginning of the stream
mCastSession!!.remoteMediaClient?.seek(position.toLong())
}
else -> {}
}
restartTrickplayTimer()
}
private fun togglePlayback() {
...
PlaybackState.IDLE -> when (mLocation) {
...
PlaybackLocation.REMOTE -> {
if (mCastSession != null && mCastSession!!.isConnected) {
loadRemoteMedia(mSeekbar!!.progress, true)
}
}
else -> {}
}
...
}
override fun onPause() {
...
mCastContext!!.sessionManager.removeSessionManagerListener(
mSessionManagerListener!!, CastSession::class.java)
}
override fun onResume() {
Log.d(TAG, "onResume() was called")
mCastContext!!.sessionManager.addSessionManagerListener(
mSessionManagerListener!!, CastSession::class.java)
if (mCastSession != null && mCastSession!!.isConnected) {
updatePlaybackLocation(PlaybackLocation.REMOTE)
} else {
updatePlaybackLocation(PlaybackLocation.LOCAL)
}
super.onResume()
}
Para o método updatePlayButton
, mude o valor da variável isConnected
:
private fun updatePlayButton(state: PlaybackState?) {
...
val isConnected = (mCastSession != null
&& (mCastSession!!.isConnected || mCastSession!!.isConnecting))
...
}
Agora, clique no botão Run para executar o app no seu dispositivo móvel. Conecte seu dispositivo com Google Cast e comece a assistir um vídeo. Você verá o vídeo sendo reproduzido no receptor.
7. Minicontrole
A Lista de verificação de design do Google Cast exige que todos os apps do Google Cast ofereçam um minicontrole que aparece quando o usuário sai da página de conteúdo atual. O minicontrole oferece acesso instantâneo e um lembrete visível para a sessão atual do Google Cast.
O SDK do Cast oferece uma visualização personalizada, MiniControllerFragment
, que pode ser adicionada ao arquivo de layout do app das atividades em que você quer mostrar o minicontrole.
Adicione a seguinte definição de fragmento à parte inferior de res/layout/player_activity.xml
e res/layout/video_browser.xml
:
<fragment
android:id="@+id/castMiniController"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="gone"
class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment"/>
Clique no botão Run para executar o app e transmitir um vídeo. Quando a reprodução começar no receptor, você verá o minicontrole na parte inferior de cada atividade. Ele pode ser usado para controlar a reprodução remota. Caso você alterne entre a atividade de navegação e a atividade do player local, o estado do minicontrole ficará sincronizado com o status da reprodução de mídia do receptor.
8. Notificação e tela de bloqueio
A Lista de verificação de design do Google Cast exige que um app remetente implemente controles de mídia em uma notificação e uma tela de bloqueio.
O SDK do Cast fornece um MediaNotificationService
para ajudar o app remetente a criar controles de mídia para a notificação e a tela de bloqueio. O serviço é mesclado automaticamente ao manifesto do app pelo Gradle.
O MediaNotificationService
será executado em segundo plano quando o remetente estiver transmitindo e mostrará uma notificação com uma miniatura da imagem e os metadados sobre o item de transmissão atual, um botão "Assistir/Pausar" e um botão "Parar".
Os controles da notificação e da tela de bloqueio podem ser ativados com as CastOptions
ao inicializar o CastContext
. Os controles de mídia da notificação e da tela de bloqueio ficam ativados por padrão. O recurso da tela de bloqueio fica ativado enquanto a notificação está ativada.
Edite o CastOptionsProvider
e mude a implementação de getCastOptions
para que ela corresponda a este código:
import com.google.android.gms.cast.framework.media.CastMediaOptions
import com.google.android.gms.cast.framework.media.NotificationOptions
override fun getCastOptions(context: Context): CastOptions {
val notificationOptions = NotificationOptions.Builder()
.setTargetActivityClassName(VideoBrowserActivity::class.java.name)
.build()
val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.build()
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.setCastMediaOptions(mediaOptions)
.build()
}
Clique no botão Run para executar o app no seu dispositivo móvel. Transmita um vídeo e saia do app de amostra. Aparecerá uma notificação para o vídeo que está em reprodução no receptor. Bloqueie seu dispositivo móvel para que a tela de bloqueio exiba controles para a reprodução de mídia no dispositivo de transmissão.
9. Sobreposição introdutória
A Lista de verificação de design do Google Cast exige que um app remetente introduza o botão Transmitir para informar aos usuários que agora ele é compatível com a transmissão e ajuda os novos usuários do Google Cast.
O SDK do Cast oferece uma visualização personalizada, IntroductoryOverlay
, que pode ser usada para destacar o botão Transmitir quando ele aparecer pela primeira vez aos usuários. Adicione o código a seguir à VideoBrowserActivity
:
import com.google.android.gms.cast.framework.IntroductoryOverlay
import android.os.Looper
private var mIntroductoryOverlay: IntroductoryOverlay? = null
private fun showIntroductoryOverlay() {
mIntroductoryOverlay?.remove()
if (mediaRouteMenuItem?.isVisible == true) {
Looper.myLooper().run {
mIntroductoryOverlay = com.google.android.gms.cast.framework.IntroductoryOverlay.Builder(
this@VideoBrowserActivity, mediaRouteMenuItem!!)
.setTitleText("Introducing Cast")
.setSingleTime()
.setOnOverlayDismissedListener(
object : IntroductoryOverlay.OnOverlayDismissedListener {
override fun onOverlayDismissed() {
mIntroductoryOverlay = null
}
})
.build()
mIntroductoryOverlay!!.show()
}
}
}
Agora, adicione um CastStateListener
e chame o método showIntroductoryOverlay
quando um dispositivo de transmissão estiver disponível. Para isso, modifique o método onCreate
e substitua os métodos onResume
e onPause
para corresponder ao seguinte:
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.cast.framework.CastStateListener
private var mCastStateListener: CastStateListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.video_browser)
setupActionBar()
mCastStateListener = object : CastStateListener {
override fun onCastStateChanged(newState: Int) {
if (newState != CastState.NO_DEVICES_AVAILABLE) {
showIntroductoryOverlay()
}
}
}
mCastContext = CastContext.getSharedInstance(this)
}
override fun onResume() {
super.onResume()
mCastContext?.addCastStateListener(mCastStateListener!!)
}
override fun onPause() {
super.onPause()
mCastContext?.removeCastStateListener(mCastStateListener!!)
}
Limpe os dados ou remova o app do dispositivo. Em seguida, clique no botão Run para executar o app no seu dispositivo móvel. A sobreposição introdutória vai aparecer. Limpe os dados do app se ela não for exibida.
10. Controle expandido
A lista de verificação de design do Google Cast exige que um app remetente forneça um controle expandido para a mídia transmitida. O controle expandido é uma versão em tela cheia do minicontrole.
O SDK do Cast fornece um widget para o controle expandido chamado ExpandedControllerActivity
. Essa é uma classe abstrata que você precisa transformar em subclasse para adicionar um botão "Transmitir".
Primeiro, crie um novo arquivo de recursos do menu, chamado expanded_controller.xml
, para o controle expandido fornecer o botão "Transmitir":
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
</menu>
Crie um novo pacote expandedcontrols
no pacote com.google.sample.cast.refplayer
. Em seguida, crie um novo arquivo com o nome ExpandedControlsActivity.kt
no pacote com.google.sample.cast.refplayer.expandedcontrols
.
package com.google.sample.cast.refplayer.expandedcontrols
import android.view.Menu
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
import com.google.sample.cast.refplayer.R
import com.google.android.gms.cast.framework.CastButtonFactory
class ExpandedControlsActivity : ExpandedControllerActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.expanded_controller, menu)
CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
return true
}
}
Agora, declare o ExpandedControlsActivity
no AndroidManifest.xml
dentro da tag application
acima de OPTIONS_PROVIDER_CLASS_NAME
:
<application>
...
<activity
android:name="com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.CastVideosDark"
android:screenOrientation="portrait"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.google.sample.cast.refplayer.VideoBrowserActivity"/>
</activity>
...
</application>
Edite o CastOptionsProvider
e mude NotificationOptions
e CastMediaOptions
para definir a atividade de destino para a ExpandedControlsActivity
:
import com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity
override fun getCastOptions(context: Context): CastOptions {
val notificationOptions = NotificationOptions.Builder()
.setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
.build()
val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
.build()
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.setCastMediaOptions(mediaOptions)
.build()
}
Atualize o método loadRemoteMedia
da LocalPlayerActivity
para exibir a ExpandedControlsActivity
quando a mídia remota for carregada:
import com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
if (mCastSession == null) {
return
}
val remoteMediaClient = mCastSession!!.remoteMediaClient ?: return
remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() {
override fun onStatusUpdated() {
val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java)
startActivity(intent)
remoteMediaClient.unregisterCallback(this)
}
})
remoteMediaClient.load(MediaLoadRequestData.Builder()
.setMediaInfo(buildMediaInfo())
.setAutoplay(autoPlay)
.setCurrentTime(position.toLong()).build())
}
Clique no botão Run para executar o app no seu dispositivo móvel e transmitir um vídeo. Você verá o controle expandido. Volte à lista de vídeos e, ao clicar no minicontrole, o controle expandido será carregado novamente. Saia do aplicativo para ver a notificação. Clique na imagem de notificação para carregar o controle expandido.
11. Adicionar suporte ao Cast Connect
A biblioteca Cast Connect permite que aplicativos remetentes existentes se comuniquem com aplicativos do Android TV usando o protocolo Cast. O Cast Connect é baseado na infraestrutura do Cast, com seu app Android TV atuando como receptor.
Dependências
Observação: para implementar a Cast Connect, o play-services-cast-framework
precisa ser 19.0.0
ou mais recente.
LaunchOptions
Para iniciar o app Android TV, também conhecido como receptor do Android, precisamos definir a flag setAndroidReceiverCompatible
como verdadeira no objeto LaunchOptions
. Esse objeto LaunchOptions
determina como o receptor é iniciado e é transmitido para o CastOptions
retornado pela classe CastOptionsProvider
. Definir a flag acima como false
iniciará o receptor da Web para o ID do app definido no Play Console do Google Cast.
No arquivo CastOptionsProvider.kt
, adicione o seguinte ao método getCastOptions
:
import com.google.android.gms.cast.LaunchOptions
...
val launchOptions = LaunchOptions.Builder()
.setAndroidReceiverCompatible(true)
.build()
return new CastOptions.Builder()
.setLaunchOptions(launchOptions)
...
.build()
Definir credenciais de inicialização
No lado do remetente, é possível especificar CredentialsData
para representar quem está entrando na sessão. O credentials
é uma string que pode ser definida pelo usuário, desde que seu app ATV possa entendê-lo. O CredentialsData
só é transmitido ao app Android TV durante a inicialização ou o horário de entrada. Se você configurá-lo novamente enquanto estiver conectado, ele não será transmitido para o app para Android TV.
Para configurar as credenciais de inicialização, o CredentialsData
precisa ser definido e transmitido para o objeto LaunchOptions
. Adicione o seguinte código ao método getCastOptions
no seu arquivo CastOptionsProvider.kt
:
import com.google.android.gms.cast.CredentialsData
...
val credentialsData = CredentialsData.Builder()
.setCredentials("{\"userId\": \"abc\"}")
.build()
val launchOptions = LaunchOptions.Builder()
...
.setCredentialsData(credentialsData)
.build()
Definir credenciais em LoadRequest
Caso seu app Receptor da Web e o app Android TV processem credentials
de maneira diferente, talvez seja necessário definir uma credentials
separada para cada um. Para resolver isso, adicione o seguinte código ao seu arquivo LocalPlayerActivity.kt
na função loadRemoteMedia
:
remoteMediaClient.load(MediaLoadRequestData.Builder()
...
.setCredentials("user-credentials")
.setAtvCredentials("atv-user-credentials")
.build())
Dependendo do app receptor para onde o remetente está transmitindo, o SDK vai processar automaticamente quais credenciais usar na sessão atual.
Como testar a Cast Connect
Etapas para instalar o APK do Android TV no Chromecast com Google TV
- Encontre o endereço IP do seu dispositivo Android TV. Geralmente, está disponível em Configurações > Rede e Internet > Nome da rede a que o dispositivo está conectado. À direita, serão mostrados os detalhes e o IP do dispositivo na rede.
- Use o endereço IP do seu dispositivo para se conectar a ele pelo ADB usando o terminal:
$ adb connect <device_ip_address>:5555
- Na janela do terminal, navegue até a pasta de nível superior para encontrar os exemplos de codelab que você baixou no início deste codelab. Exemplo:
$ cd Desktop/android_codelab_src
- Para instalar o arquivo .apk dessa pasta no Android TV, execute o seguinte:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- Agora você verá um app com o nome Transmitir vídeos no menu Seus apps no dispositivo Android TV.
- Retorne ao seu projeto do Android Studio e clique no botão Run para instalar & execute o app remetente no seu dispositivo móvel físico. No canto superior direito, clique no ícone de transmissão e selecione seu dispositivo Android TV nas opções disponíveis. Agora você verá o app Android TV iniciado no dispositivo Android TV e, ao reproduzir um vídeo, poderá controlar a reprodução usando o controle remoto do Android TV.
12. Personalizar widgets do Google Cast
É possível personalizar os widgets do Google Cast definindo as cores, estilizando os botões, o texto e a aparência da miniatura e escolhendo os tipos de botões que serão exibidos.
Atualizar res/values/styles_castvideo.xml
<style name="Theme.CastVideosTheme" parent="Theme.AppCompat.Light.NoActionBar">
...
<item name="mediaRouteTheme">@style/CustomMediaRouterTheme</item>
<item name="castIntroOverlayStyle">@style/CustomCastIntroOverlay</item>
<item name="castMiniControllerStyle">@style/CustomCastMiniController</item>
<item name="castExpandedControllerStyle">@style/CustomCastExpandedController</item>
<item name="castExpandedControllerToolbarStyle">
@style/ThemeOverlay.AppCompat.ActionBar
</item>
...
</style>
Declare os seguintes temas personalizados:
<!-- Customize Cast Button -->
<style name="CustomMediaRouterTheme" parent="Theme.MediaRouter">
<item name="mediaRouteButtonStyle">@style/CustomMediaRouteButtonStyle</item>
</style>
<style name="CustomMediaRouteButtonStyle" parent="Widget.MediaRouter.Light.MediaRouteButton">
<item name="mediaRouteButtonTint">#EEFF41</item>
</style>
<!-- Customize Introductory Overlay -->
<style name="CustomCastIntroOverlay" parent="CastIntroOverlay">
<item name="castButtonTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Button</item>
<item name="castTitleTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Title</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Button" parent="android:style/TextAppearance">
<item name="android:textColor">#FFFFFF</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Title" parent="android:style/TextAppearance.Large">
<item name="android:textColor">#FFFFFF</item>
</style>
<!-- Customize Mini Controller -->
<style name="CustomCastMiniController" parent="CastMiniController">
<item name="castShowImageThumbnail">true</item>
<item name="castTitleTextAppearance">@style/TextAppearance.AppCompat.Subhead</item>
<item name="castSubtitleTextAppearance">@style/TextAppearance.AppCompat.Caption</item>
<item name="castBackground">@color/accent</item>
<item name="castProgressBarColor">@color/orange</item>
</style>
<!-- Customize Expanded Controller -->
<style name="CustomCastExpandedController" parent="CastExpandedController">
<item name="castButtonColor">#FFFFFF</item>
<item name="castPlayButtonDrawable">@drawable/cast_ic_expanded_controller_play</item>
<item name="castPauseButtonDrawable">@drawable/cast_ic_expanded_controller_pause</item>
<item name="castStopButtonDrawable">@drawable/cast_ic_expanded_controller_stop</item>
</style>
13. Parabéns
Agora você já sabe como adicionar compatibilidade com Cast a um app de vídeo usando os widgets do SDK do Google Cast no Android.
Para ver mais detalhes, consulte o guia do desenvolvedor do Android Sender.