Adicionar compatibilidade com Cast a um app Android

1. Visão geral

Logotipo do Google Cast

Este codelab ensina a modificar um app de vídeo para Android para transmitir conteúdo em um dispositivo 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 ele, você pode adicionar os componentes de IU necessários de acordo com a Checklist 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 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 widgets do Google Cast.
  • Como fazer a integração com o 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.
  • Um Chromecast com Google TV é necessário para testar a integração do Cast Connect, mas é opcional para o restante do codelab. Se você não tiver um, pule a etapa Adicionar suporte ao Google Cast Connect, no final deste tutorial.

Experiência

  • Você precisa ter conhecimento prévio em desenvolvimento para Kotlin e Android.
  • Também é necessário ter conhecimento prévio de como assistir TV :)

Como você usará este tutorial?

Apenas leitura Leitura e exercícios

Como você classificaria sua experiência com a criação de apps Android?

Iniciante Intermediário Proficiente

Como você classificaria sua experiência com o ato de assistir TV?

Iniciante Intermediário Proficiente

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

ícone de um par de bússolas

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 as opções do menu File > New > Import Project....

Selecione o diretório ícone da pastaapp-done na pasta de exemplo de código e clique em "OK".

Clique em File > Botão "Sync Project with Gradle" do Android Studio Sync Project with Gradle Files.

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 Botão "Run" do Android Studio, um triângulo verde apontando para a direitaRun 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. Ilustração de um smartphone Android executando o app "Transmitir vídeos" com o minicontrole aparecendo na parte de baixo 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

Ilustração de um smartphone Android com o app "Cast Videos"

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:

  1. Selecione o diretório ícone da pastaapp-start no download do exemplo de código (selecione Import Project na tela inicial ou a opção de menu File > New > Import Project...).
  2. Clique no botão Botão "Sync Project with Gradle" do Android Studio Sync Project with Gradle Files.
  3. Clique no botão Botão "Run" do Android Studio, um triângulo verde apontando para a direitaRun 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 é apoiada por um ArrayAdapter (VideoListAdapter). A lista de vídeos e seus metadados associados estã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 uma 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 a AppCompatActivity, precisamos da biblioteca de suporte AppCompat. Para gerenciar a lista de vídeos e receber as imagens dela de maneira assíncrona, usamos a biblioteca Volley.

Perguntas frequentes

5. Como adicionar o botão "Transmitir"

Ilustração da parte superior de um smartphone Android com o app Cast Video em execução. O botão Transmitir aparece no canto superior direito da tela

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 Cast.

Você precisa implementar a interface OptionsProvider para fornecer as CastOptions necessárias 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 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. Esse botão é implementado pelo MediaRouteButton da biblioteca de suporte MediaRouter. Como qualquer ícone de ação que pode ser adicionado à sua atividade (com 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() da 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 Botão &quot;Run&quot; do Android Studio, um triângulo verde apontando para a direitaRun 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

Ilustração de um smartphone Android com o app &quot;Cast Videos&quot;

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:

  1. Crie um objeto MediaInfo que modele um item de mídia.
  2. Conectar-se ao dispositivo com Google Cast e iniciar o aplicativo receptor.
  3. Carregar o objeto MediaInfo no seu receptor e reproduzir o conteúdo.
  4. Monitorar o status da mídia.
  5. 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 fornece 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))
    ...
}

Clique no botão Botão &quot;Run&quot; do Android Studio, um triângulo verde apontando para a direitaRun 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.

Ilustração da parte inferior de um smartphone Android mostrando o miniplayer no app Cast Videos

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 Botão &quot;Run&quot; do Android Studio, um triângulo verde apontando para a direitaRun 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.

Ilustração de um smartphone Android mostrando controles de mídia na área de notificações

O SDK do Cast oferece 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 Botão &quot;Run&quot; do Android Studio, um triângulo verde apontando para a direitaRun 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.

Ilustração de um smartphone Android mostrando controles de mídia na tela de bloqueio

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 os usuários atuais. O botão informa-os que esse app agora é compatível com transmissões e também ajuda os usuários novos no Google Cast.

Ilustração mostrando a sobreposição introdutória do Google Cast ao redor do botão &quot;Transmitir&quot; no app Android Cast Videos

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 modificando o método onCreate e substituindo 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 do app ou remova-o do dispositivo. Em seguida, clique no botão Botão &quot;Run&quot; do Android Studio, um triângulo verde apontando para a direitaRun para executar o app no dispositivo móvel. Você vai ver a sobreposição introdutória. Limpe os dados do app se a sobreposição 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.

Ilustração de um vídeo sendo reproduzido em um smartphone Android com o controle expandido sobreposto

O SDK do Cast oferece 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 a ExpandedControlsActivity no AndroidManifest.xml dentro da tag application acima do 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 Botão &quot;Run&quot; do Android Studio, um triângulo verde apontando para a direitaRun 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 os apps de envio se comuniquem com os apps Android TV pelo protocolo do Cast. O Cast Connect é baseado na infraestrutura do Google Cast, com seu app Android TV atuando como um receptor.

Dependências

Observação: para implementar o Cast Connect, o play-services-cast-framework precisa ser 19.0.0 ou mais recente.

LaunchOptions

Para iniciar o aplicativo Android TV, também chamado de receptor do Android, é necessário definir a flag setAndroidReceiverCompatible como true no objeto LaunchOptions. Esse objeto LaunchOptions determina como o receptor é iniciado e é transmitido para o CastOptions retornado pela classe CastOptionsProvider. Definir a flag mencionada acima como false vai 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á participando da sessão. O credentials é uma string que pode ser definida pelo usuário, desde que o app ATV a entenda. O CredentialsData só é transmitido para o app Android TV durante a inicialização ou a participação. 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 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 no LoadRequest

Caso o app do receptor da Web e o app do Android TV processem credentials de maneira diferente, talvez seja necessário definir credentials separados para cada um. Para fazer isso, adicione o seguinte código ao arquivo LocalPlayerActivity.kt na função loadRemoteMedia:

remoteMediaClient.load(MediaLoadRequestData.Builder()
       ...
       .setCredentials("user-credentials")
       .setAtvCredentials("atv-user-credentials")
       .build())

Dependendo do app receptor para o qual o remetente está transmitindo, o SDK agora processa automaticamente quais credenciais usar para a sessão atual.

Como testar o Cast Connect

Etapas para instalar o APK do Android TV no Chromecast com Google TV

  1. Encontre o endereço IP do dispositivo Android TV. Normalmente, ela está disponível em Configurações > Rede e Internet > (Nome da rede à qual o dispositivo está conectado). À direita, serão mostrados os detalhes e o IP do dispositivo na rede.
  2. Use o endereço IP do dispositivo para se conectar a ele pelo ADB usando o terminal:
$ adb connect <device_ip_address>:5555
  1. 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
  1. Para instalar o arquivo .apk dessa pasta no Android TV, execute o seguinte:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. Agora você vai encontrar um app chamado Cast Videos no menu Seus apps no dispositivo Android TV.
  2. Volte para o projeto do Android Studio e clique no botão "Run" para instalar e executar o app de envio 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. O app Android TV será iniciado no dispositivo Android TV, e a reprodução de um vídeo vai permitir que você controle 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 mais detalhes, consulte o guia do desenvolvedor do Android Sender.