Fazer streaming de mídia usando o ExoPlayer

1. Antes de começar

526b239733391e74.png

Captura de tela: o app Android do YouTube

O ExoPlayer (link em inglês) é um player de mídia no nível do app criado sobre APIs de mídia de baixo nível no Android. O ExoPlayer tem várias vantagens em relação ao MediaPlayer integrado do Android. Ele oferece suporte a muitos dos mesmos formatos de mídia (link em inglês) que o MediaPlayer, além de formatos adaptáveis, como DASH e SmoothStreaming. O ExoPlayer é altamente personalizável e extensível, o que o torna eficiente para muitos casos de uso avançados. Ele é um projeto de código aberto (link em inglês) usado por apps do Google, incluindo o YouTube e o Google Play Filmes e TV.

Pré-requisitos

  • Ter conhecimento sobre o Android Studio e desenvolvimento para Android

O que você vai fazer

  • Criar uma instância SimpleExoPlayer (link em inglês), que prepara e reproduz mídia de várias fontes.
  • Integrar o ExoPlayer ao ciclo de vida da atividade do app para oferecer suporte à execução em segundo ou em primeiro plano e à retomada da reprodução em um ambiente com uma ou várias janelas.
  • Usar os MediaItems (link em inglês) para criar uma playlist.
  • Reproduzir streamings de vídeo adaptáveis, que adaptam a qualidade da mídia à largura de banda disponível.
  • Registrar listeners de eventos para monitorar o estado de reprodução e mostrar como os listeners podem ser usados para medir a qualidade da reprodução.
  • Usar os componentes padrão da IU do ExoPlayer e os personalizar de acordo com o estilo do app.

O que é necessário

  • A versão estável mais recente do Android Studio
  • Um dispositivo Android com JellyBean (4.1) ou versões mais recentes, idealmente com o Nougat (7.1) mais recente, porque essa versão oferece suporte a várias janelas.

2. Começar a configuração

Buscar o código

Para começar, faça o download do projeto do Android Studio:

Como alternativa, você pode clonar o repositório do GitHub:

git clone https://github.com/googlecodelabs/exoplayer-intro.git

Estrutura do diretório

Clonar ou descompactar o diretório fornece uma pasta raiz (exoplayer-intro), que contém um único projeto gradle com vários módulos: um módulo do app e um para cada etapa deste codelab, além de todos os recursos necessários.

Importar o projeto

  1. Inicie o Android Studio.
  2. Clique em File > New > Import Project*.*
  3. Selecione o arquivo raiz build.gradle.

111b190903697765.png

Captura de tela: estrutura de importação do projeto

Após a criação do build, você verá seis módulos: o módulo app do tipo application (aplicativo) e cinco módulos do tipo library (biblioteca) com nomes exoplayer-codelab-N, em que N é 00 a 04,. O módulo app está vazio e tem apenas um manifesto. Todo o conteúdo do módulo exoplayer-codelab-N especificado atualmente é mesclado quando o app é criado usando uma dependência gradle em app/build.gradle.

app/build.gradle

dependencies {
   implementation project(":exoplayer-codelab-00")
}

A atividade do player de mídia é mantida no módulo exoplayer-codelab-N. É preciso usar um módulo de biblioteca separado para que você possa compartilhar a atividade entre APKs direcionados a diferentes plataformas, como dispositivos móveis e Android TV. Isso também permite que você aproveite recursos, como o Dynamic Delivery, para possibilitar que o recurso de reprodução de mídia seja instalado apenas quando o usuário precisar.

  1. Implante e execute o app para ver se está tudo bem. O app precisa preencher a tela com um plano de fundo preto.

2dae13fed92e6c8c.png

Captura de tela: app vazio em execução

3. Iniciar o streaming

Adicionar dependência do ExoPlayer

O ExoPlayer é um projeto de código aberto hospedado no GitHub. Cada versão é distribuída pelo Google Maven, que é um dos repositórios de pacotes padrão usados pelo Android Studio e Gradle (links em inglês). Cada versão é identificada exclusivamente por uma string com este formato:

com.google.android.exoplayer:exoplayer:X.X.X

Você pode adicionar o ExoPlayer ao seu projeto simplesmente importando as classes e componentes de IU. Ele é bem pequeno, com um tamanho reduzido de cerca de 70 a 300 kB, dependendo dos recursos incluídos e dos formatos com suporte. A biblioteca ExoPlayer é dividida em módulos para que os desenvolvedores importem apenas as funcionalidades de que precisam. Para saber mais sobre a estrutura modular do ExoPlayer, consulte Adicionar módulos do ExoPlayer (links em inglês).

  1. Abra o arquivo build.gradle do módulo player-lib.
  2. Adicione as linhas abaixo à seção dependencies e sincronize o projeto.

exoplayer-codelab-00/build.gradle

dependencies {
   [...]

implementation 'com.google.android.exoplayer:exoplayer-core:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.0'

}

Adicionar o PlayerView element

  1. Abra o arquivo de recurso de layout activity_player.xml do módulo exoplayer-codelab-00.
  2. Coloque o cursor no elemento FrameLayout.
  3. Comece a digitar <PlayerView e deixe o Android Studio completar automaticamente o elemento PlayerView.
  4. Use match_parent para a width e a height.
  5. Declare o ID como video_view.

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"/>

Daqui para frente, você vai se referir a esse elemento da IU como a exibição de vídeo.

  1. Na PlayerActivity, agora você pode acessar uma referência à árvore de visualização criada do arquivo XML que acabou de editar.

PlayerActivity.kt

    private val viewBinding by lazy(LazyThreadSafetyMode.NONE) {
        ActivityPlayerBinding.inflate(layoutInflater)
    }
  1. Defina a raiz da árvore de visualização como a visualização de conteúdo da Atividade. Confira também se a propriedade videoView está visível na referência viewBinding e se o tipo dela é PlayerView.
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)
    }

Criar um ExoPlayer

Para reproduzir mídia de streaming, você precisa de um objeto ExoPlayer. A maneira mais simples de criar um é usar a classe SimpleExoPlayer.Builder. Como o nome sugere, ele usa o padrão do builder para criar uma instância SimpleExoPlayer (links em inglês).

O SimpleExoPlayer é uma implementação conveniente e multifuncional da interface ExoPlayer.

Adicione um método particular initializePlayer para criar o SimpleExoPlayer.

PlayerActivity.kt

private var player: SimpleExoPlayer? = null
[...]
   private fun initializePlayer() {
        player = SimpleExoPlayer.Builder(this)
            .build()
            .also { exoPlayer ->
                viewBinding.videoView.player = exoPlayer
            }
    }

Crie um SimpleExoPlayer.Builder usando o contexto, depois, chame o build para criar o objeto SimpleExoPlayer. O objeto vai ser atribuído ao player, que você precisa declarar como um campo do membro. Em seguida, você vai usar a propriedade mutável viewBinding.videoView.player para vincular o player à visualização correspondente.

Criar um item de mídia

O player agora precisa de algum conteúdo para reproduzir. Para isso, crie um MediaItem (link em inglês). Existem muitos tipos diferentes de MediaItems, mas vamos criar um para arquivos MP3 na Internet primeiro.

A maneira mais simples de criar um MediaItem é usar MediaItem.fromUri, que aceita o URI de um arquivo de mídia. Adicione o MediaItem ao player usando player.setMediaItem.

  1. Adicione o código abaixo ao initializePlayer no bloco also:

PlayerActivity.kt

private fun initializePlayer() {
    [...]
        .also { exoPlayer ->
            [...]
            val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3))
            exoPlayer.setMediaItem(mediaItem)
        }
}

Observe que o R.string.media_url_mp3 é definido como https://storage.googleapis.com/exoplayer-test-media-0/play.mp3 em strings.xml.

Boas práticas relacionadas ao ciclo de vida da atividade

Nosso player pode monopolizar muitos recursos, incluindo memória, CPU, conexões de rede e codecs de hardware. Muitos desses recursos são escassos, principalmente para codecs de hardware, já que talvez haja apenas um no dispositivo. É importante liberar esses recursos para que outros apps possam usá-los quando você não precisar deles, como quando o app for colocado em segundo plano.

Ou seja, o ciclo de vida do player precisa estar vinculado ao ciclo de vida do app. Para implementar isso, você precisa substituir os quatro métodos da PlayerActivity: onStart, onResume, onPause e onStop.

  1. Com a PlayerActivity aberta, clique em Code menu > Override methods....
  2. Selecione onStart, onResume, onPause e onStop.
  3. Inicialize o player no callback onStart ou onResume dependendo do nível da API.

PlayerActivity.kt

public override fun onStart() {
 super.onStart()
 if (Util.SDK_INT >= 24) {
   initializePlayer()
 }
}

public override fun onResume() {
 super.onResume()
 hideSystemUi()
 if ((Util.SDK_INT < 24 || player == null)) {
   initializePlayer()
 }
}

O nível 24 da API do Android e versões mais recentes oferecem suporte a várias janelas. Como o app pode estar visível e não ativo no modo de janela dividida, você precisa inicializar o player em onStart. O nível 24 da API do Android e versões anteriores exige que você espere o máximo possível até instalar recursos. Portanto, espere pelo callback onResume antes de inicializar o player.

  1. Adicione o método hideSystemUi.

PlayerActivity.kt

@SuppressLint("InlinedApi")
private fun hideSystemUi() {
 viewBinding.videoView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
     or View.SYSTEM_UI_FLAG_FULLSCREEN
     or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
     or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
     or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
     or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
}

hideSystemUi é um método auxiliar chamado em onResume, que permite que você tenha uma experiência em tela cheia.

  1. Libere recursos usando o callback releasePlayer, que você vai criar em breve, em onPause e onStop.

PlayerActivity.kt

public override fun onPause() {
 super.onPause()
 if (Util.SDK_INT < 24) {
   releasePlayer()
 }
}

public override fun onStop() {
 super.onStop()
 if (Util.SDK_INT >= 24) {
   releasePlayer()
 }
}

Com o nível 24 da API e versões anteriores, não há garantia de que o callback onStop vai ser chamado, então você precisa liberar o player o mais cedo possível em onPause. Com o nível 24 da API e versões mais recentes, que oferecem o modo de várias janelas e de janela dividida, é garantido que o onStop vai ser chamado. No estado pausado, a atividade ainda está visível, então você espera para liberar o player até que o callback onStop seja chamado.

Agora, você precisa criar um método releasePlayer, que libere os recursos do player e o destrua.

  1. Adicione o código abaixo à atividade:

PlayerActivity.kt

private var playWhenReady = true
private var currentWindow = 0
private var playbackPosition = 0L
[...]

private fun releasePlayer() {
    player?.run {
        playbackPosition = this.currentPosition
        currentWindow = this.currentWindowIndex
        playWhenReady = this.playWhenReady
        release()
    }
    player = null
}

Antes de liberar e destruir o player, armazene estas informações:

  • O estado reproduzir/pausar usando o playWhenReady.
  • A posição de reprodução atual usando a currentPosition.
  • O índice da janela atual usando currentWindowIndex. Para mais informações sobre janelas, consulte a linha do tempo (link em inglês).

Fazer isso vai possibilitar que você retome a reprodução de onde o usuário parou. Tudo o que você precisa fazer é fornecer essas informações de estado ao inicializar o player.

Preparação final

Tudo o que você precisa fazer agora é fornecer as informações de estado que você salvou no releasePlayer ao player durante a inicialização.

  1. Adicione o código abaixo ao método initializePlayer:

PlayerActivity.kt

private fun initializePlayer() {
    [...]
    exoPlayer.playWhenReady = playWhenReady
    exoPlayer.seekTo(currentWindow, playbackPosition)
    exoPlayer.prepare()
}

Veja o que está acontecendo:

  • playWhenReady (link em inglês) informa ao player se ele precisa começar a reproduzir assim que todos os recursos necessário são adquiridos. Como o método playWhenReady é inicialmente true, a reprodução vai ser iniciada automaticamente na primeira vez que o app for executado.
  • seekTo (link em inglês) diz ao player para procurar uma determinada posição em uma janela específica. Tanto a currentWindow como a playbackPosition são inicializadas como zero para que a reprodução comece desde o início na primeira vez que o app for executado.
  • prepare (link em inglês) informa ao player todos os recursos que precisam ser adquiridos para iniciar a reprodução.

Tocar o áudio

Parabéns, você terminou. Inicie o app para reproduzir o arquivo MP3 e veja a arte incorporada.

d92917867ee23ef8.png

Captura de tela: o app reproduzindo uma única faixa.

Testar o ciclo de vida da atividade

Teste se o app funciona em todos os diferentes estados do ciclo de vida da atividade.

  1. Inicie outro app e coloque seu app em primeiro plano novamente. Ele retoma na posição correta?
  2. Pause o app, coloque-o no plano de fundo e depois no primeiro plano novamente. Ele fica pausado quando está em segundo plano no estado pausado?
  3. Gire o app. Como ele se comporta se você mudar a orientação de retrato para paisagem e vice-versa?

Reproduzir vídeos

Se você quer reproduzir vídeos, basta modificar o URI do item de mídia para um arquivo MP4.

  1. Mude o URI em initializePlayer para R.string.media_url_mp4.
  2. Inicie o app novamente e teste o comportamento depois de entrar em segundo plano com a reprodução de vídeo também.

PlayerActivity.kt

private fun initializePlayer() {
  [...]
     val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp4));
  [...]
}

A PlayerView faz de tudo. Em vez de mostrar a arte, o vídeo é renderizado em tela cheia.

425c6c65f78e8d46.png

Captura de tela: o app reproduzindo vídeo.

Você é demais! Você acabou de criar um app para streaming de mídia em tela cheia no Android, completo com gerenciamento do ciclo de vida, estado salvo e controles de IU.

4. Criar uma playlist

O app atual reproduz um único arquivo de mídia, mas, e se você quiser reproduzir mais de um arquivo de mídia, um após o outro? Para isso, você precisa de uma playlist.

As playlists (link em inglês) podem ser criadas adicionando mais MediaItems ao player usando addMediaItem. Isso permite que a reprodução contínua e o buffer sejam gerenciados em segundo plano para que o usuário não veja um ícone de carregamento do buffer ao mudar os itens de mídia.

  1. Adicione o código abaixo ao método initializePlayer:

PlayerActivity.kt

private void initializePlayer() {
  [...]
  exoPlayer.addMediaItem(mediaItem) // Existing code

  val secondMediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3));
  exoPlayer.addMediaItem(secondMediaItem);
  [...]
}

Veja como os controles do player se comportam. Você pode usar 1f79fee4d082870f.png e 39627002c03ce320.png para navegar na sequência de itens de mídia.

7b5c034dafabe1bd.png

Captura de tela: controles de reprodução mostrando os botões "Próximo" e "Anterior"

Isso é bastante útil. Para saber mais, consulte a documentação do desenvolvedor sobre Itens de mídia e Playlists, e este artigo sobre a API Playlist (links em inglês).

5. Streaming adaptável

O streaming adaptável (link em inglês) é uma técnica de streaming de mídia que varia a qualidade da stream com base na largura de banda de rede disponível. Dessa forma, o usuário pode experimentar a mídia na melhor qualidade permitida pela largura de banda dele.

Normalmente, o mesmo conteúdo de mídia é dividido em várias faixas com diferentes qualidades (taxas de bits e resoluções). O player escolhe uma faixa com base na largura de banda de rede disponível.

Cada faixa é dividida em partes de uma determinada duração, normalmente entre 2 e 10 segundos. Isso permite que o player alterne rapidamente entre as faixas conforme a largura de banda disponível muda. O player é responsável por juntar esses pedaços para uma reprodução perfeita.

Seleção de faixa adaptável

A ideia do streaming adaptável é selecionar a faixa mais adequada ao ambiente atual. Atualize o app para reproduzir mídia de streaming adaptável usando a seleção de faixa (link em inglês) adaptável.

  1. Atualize o initializePlayer com o código abaixo:

PlayerActivity.kt

private fun initializePlayer() {
   val trackSelector = DefaultTrackSelector(this).apply {
        setParameters(buildUponParameters().setMaxVideoSizeSd())
    }
   player = SimpleExoPlayer.Builder(this)
        .setTrackSelector(trackSelector)
        .build()
  [...]
}

Primeiro, crie um DefaultTrackSelector, que é responsável por escolher as faixas no item de mídia. Em seguida, informe ao trackSelector para escolher apenas faixas com a definição padrão ou mais baixa, que é uma boa maneira de economizar os dados do usuário em detrimento da qualidade (links em inglês). Por fim, transmita o trackSelector ao builder, para que ele seja usado ao construir a instância SimpleExoPlayer.

Criar um MediaItem adaptável

O DASH (link em inglês) é um formato de streaming adaptável bastante usado. Para fazer streaming de conteúdo DASH, você precisa criar um MediaItem como antes. No entanto, desta vez, precisamos usar um MediaItem.Builder em vez de fromUri.

Isso ocorre porque fromUri usa a extensão de arquivo para determinar o formato de mídia implícito, mas nosso URI do DASH não tem uma extensão de arquivo. Portanto, precisamos fornecer um tipo MIME (link em inglês) de APPLICATION_MPD ao construir o MediaItem.

  1. Atualize o initializePlayer desta maneira:

PlayerActivity.kt

private void initializePlayer() {
  [...]

  // Replace this line
  val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp4));

  // With this
   val mediaItem = MediaItem.Builder()
        .setUri(getString(R.string.media_url_dash))
        .setMimeType(MimeTypes.APPLICATION_MPD)
        .build()

  // Also remove the following lines
  val secondMediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3))
    exoPlayer.addMediaItem(secondMediaItem)
}
  1. Reinicie o app e veja o streaming de vídeo adaptável com o DASH em ação. O ExoPlayer torna tudo mais fácil.

Outros formatos de streaming adaptáveis

O HLS (MimeTypes.APPLICATION_M3U8) e o SmoothStreaming (MimeTypes.APPLICATION_SS) são outros formatos de streaming adaptáveis normalmente usados e o ExoPlayer oferece suporte a ambos. Para mais informações sobre a construção de outras fontes de mídia adaptável, consulte o app de demonstração do ExoPlayer (links em inglês).

6. Detectar eventos

Nas etapas anteriores, você aprendeu a transmitir streams de mídia progressivos e adaptáveis. O ExoPlayer faz muito trabalho para você nos bastidores, por exemplo:

  • Alocação de memória
  • Download de arquivos de contêiner
  • Extração de metadados do contêiner
  • Decodificação de dados
  • Renderização de vídeos, áudios e textos para a tela e caixas de som

Às vezes, é útil saber o que o ExoPlayer está fazendo durante a execução a fim de entender e melhorar a experiência de reprodução para seus usuários.

Por exemplo, talvez você queira refletir as mudanças do estado de reprodução na interface do usuário dessa forma:

  • Exibir um ícone de carregamento quando o player entra em um estado de buffer.
  • Mostrar uma sobreposição com as opções "assistir a seguir" quando a faixa termina.

O ExoPlayer oferece várias interfaces de listener (link em inglês) que fornecem callbacks para eventos úteis. Você usa um listener para registrar em que estado o player está.

Ouça agora

  1. Crie uma constante TAG fora da classe PlayerActivity, que você vai usar para gerar registros mais tarde.

PlayerActivity.kt

private const val TAG = "PlayerActivity"
  1. Implemente a interface Player.EventListener (link em inglês) em uma função de fábrica fora da classe PlayerActivity. Ela vai ser usada para informar você sobre eventos importantes do player, incluindo erros e mudanças no estado de reprodução.
  2. Substitua onPlaybackStateChanged adicionando o código abaixo:

PlayerActivity.kt

private fun playbackStateListener() = object : Player.EventListener {
    override fun onPlaybackStateChanged(playbackState: Int) {
        val stateString: String = when (playbackState) {
            ExoPlayer.STATE_IDLE -> "ExoPlayer.STATE_IDLE      -"
            ExoPlayer.STATE_BUFFERING -> "ExoPlayer.STATE_BUFFERING -"
            ExoPlayer.STATE_READY -> "ExoPlayer.STATE_READY     -"
            ExoPlayer.STATE_ENDED -> "ExoPlayer.STATE_ENDED     -"
            else -> "UNKNOWN_STATE             -"
        }
        Log.d(TAG, "changed state to $stateString")
    }
}
  1. Declare um membro particular do tipo Player.EventListener na PlayerActivity.

PlayerActivity.kt

class PlayerActivity : AppCompatActivity() {
    [...]

    private val playbackStateListener: Player.EventListener = playbackStateListener()
}

onPlaybackStateChanged é chamado quando o estado de reprodução muda. O novo estado é fornecido pelo parâmetro playbackState.

O player pode estar em um dos quatro estados abaixo:

Estado

Descrição

ExoPlayer.STATE_IDLE

O player foi instanciado, mas ainda não foi preparado.

ExoPlayer.STATE_BUFFERING

O player não pode reproduzir da posição atual porque não foram armazenados dados suficientes no buffer.

ExoPlayer.STATE_READY

O player inicia a reprodução imediatamente da posição atual. Isso significa que o player vai começar a reproduzir mídia automaticamente se a propriedade playWhenReady (link em inglês) do player for true. Se ela for false, o player vai ser pausado.

ExoPlayer.STATE_ENDED

O player terminou de reproduzir a mídia.

Registrar o listener

Para que seus callbacks sejam chamados, você precisa registrar o playbackStateListener com o player. Faça isso no initializePlayer.

  1. Registre o listener antes que a reprodução seja preparada.

PlayerActivity.kt

private void initializePlayer() {
    [...]
    exoPlayer.seekTo(currentWindow, playbackPosition)
    exoPlayer.addListener(playbackStateListener)
    [...]
}

Você precisa se organizar para evitar referências pendentes do player que podem causar um vazamento de memória.

  1. Remova o listener em releasePlayer:

PlayerActivity.kt

private void releasePlayer() {
 player?.run {
   [...]
   removeListener(playbackStateListener)
   release()
 }
  player = null
}
  1. Abra o logcat e execute o app.
  2. Use os controles da IU para procurar, pausar e retomar a reprodução. Você precisa ver a mudança do estado da reprodução nos registros.

Mais opções

O ExoPlayer oferece vários outros listeners, que são úteis para entender a experiência de reprodução de mídia dos usuários. Existem listeners de áudio e vídeo, assim como um AnalyticsListener, que contém os callbacks de todos os listeners (links em inglês). Alguns dos métodos mais importantes são:

  • onRenderedFirstFrame (link em inglês) que é chamado quando o primeiro frame de um vídeo é renderizado. Com isso, você pode calcular quanto tempo o usuário teve que esperar para ver um conteúdo significativo na tela.
  • onDroppedVideoFrames (link em inglês) que é chamado quando há uma queda de frames. Quedas de frames indicam que a reprodução está instável e a experiência do usuário provavelmente vai ser ruim.
  • onAudioUnderrun (link em inglês) que é chamado quando há um underrun de áudio. Underruns causam falhas audíveis no som e são mais perceptíveis do que queda de frames de vídeo.

O AnalyticsListener pode ser adicionado ao player usando o addAnalyticsListener. Também existem métodos correspondentes para os listeners de áudio e vídeo (links em inglês).

Pense em quais eventos são importantes para o app e os usuários. Para saber mais, consulte Detectar eventos de players (link em inglês). Este é o fim da seção sobre listener de eventos.

7. Personalizar a interface do usuário

Até agora, você usou a PlayerControlView (link em inglês) do ExoPlayer para exibir um controlador de reprodução para o usuário.

bcfe17eebcad9e13.png

Captura de tela: controlador de reprodução padrão

E se você quiser mudar a funcionalidade ou a aparência desses controles? Felizmente, esses controles são altamente personalizáveis.

A primeira personalização simples é não usar o controlador. Isso pode ser feito facilmente usando o atributo use_controller no elemento PlayerView no activity_player.xml.

  1. Defina o atributo use_controller como false para o controle não aparecer mais:

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   [...]
   app:use_controller="false"/>
  1. Adicione o namespace abaixo ao seu FrameLayout:

activity_player.xml

<FrameLayout
  [...]
  xmlns:app="http://schemas.android.com/apk/res-auto">

Faça um teste.

Personalizar o comportamento

A PlayerControlView tem vários atributos (link em inglês) que afetam o comportamento dela. Por exemplo, você pode usar show_timeout para personalizar o atraso em milissegundos, antes que o controle seja ocultado após a última interação do usuário com ele. Para fazer isso, siga estas etapas:

  1. Remova app:use_controller="false".
  2. Mude a visualização do player para usar show_timeout:

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   app:show_timeout="10000"/>

Os atributos da PlayerControlView também podem ser definidos programaticamente (link em inglês).

Personalizar a aparência

Este é um bom começo. Mas, e se você quiser mudar quais botões são mostrados ou a aparência da PlayerControlView? A implementação da PlayerControlView não pressupõe que existam botões, por isso é fácil os remover e adicionar novos.

Veja como você pode personalizar a PlayerControlView.

  1. Crie um novo arquivo XML custom_player_control_view.xml na pasta player-lib/res/layout/.
  2. No menu de contexto da pasta de layout, selecione New - Layout resource file e o nomeie como custom_player_control_view.xml.

ae1e3795726d4e4e.png

Captura de tela: o arquivo de layout para a visualização dos controles do player foi criado.

  1. Copie o arquivo de layout original deste link para custom_player_control_view.xml.
  2. Remova os elementos ImageButton com os IDs @id/exo_prev e @id/exo_next.

Para usar seu layout personalizado, você precisa definir o atributo app:controller_layout_id do elemento PlayerView no arquivo activity_player.xml.

  1. Use o ID de layout do arquivo personalizado como no snippet de código abaixo:

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   app:controller_layout_id="@layout/custom_player_control_view"/>
  1. Inicie o app novamente. A visualização de controles do player não vai mostrar mais os botões "Anterior" e "Próximo".

89e6535a22c8e321.png

Captura de tela: visualização personalizada do controle do player sem os botões "Anterior" ou "Próximo"

Você pode aplicar as mudanças que quiser no arquivo de layout. Por padrão, as cores do tema Android são escolhidas. É possível mudar as cores para corresponder ao design do app.

  1. Adicione um atributo android:tint a cada elemento ImageButton:

custom_player_control_view.xml

<ImageButton android:id="@id/exo_rew"
   android:tint="#FF00A6FF"
   style="@style/ExoMediaButton.Rewind"/>
  1. Mude todos os atributos android:textColor encontrados no arquivo personalizado para a mesma cor: #FF00A6FF.

custom_player_control_view.xml

<TextView android:id="@id/exo_position"
   [...]
   android:textColor="#FF00A6FF"/>
<TextView android:id="@id/exo_duration"
   [...]
   android:textColor="#FF00A6FF"/>
  1. Execute o app. Agora você tem componentes de IU bonitos e coloridos.

e9835d65d6dd0634.png

Captura de tela: botões coloridos e visualização de texto

Substituir o estilo padrão

Você acabou de criar um arquivo de layout personalizado e o referenciou usando controller_layout_id em activity_player.xml.

Outra abordagem é substituir o arquivo de layout padrão que a PlayerControlView usa. O código-fonte da PlayerControlView (link em inglês) mostra que a visualização usa R.layout.exo_player_control_view para o layout. Se você criar um arquivo de layout próprio com o mesmo nome de arquivo, a PlayerControlView vai usar seu arquivo.

  1. Remova o atributo controller_layout_id que você acabou de adicionar.
  2. Exclua o arquivo custom_player_control_view.xml.

Agora, a PlayerView em activity_player.xml vai ficar assim:

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"/>
  1. Crie um arquivo com o nome exo_player_control_view.xml na pasta res/layout do módulo de biblioteca player-lib.
  2. Insira o código abaixo em exo_player_control_view.xml para adicionar o botão "Reproduzir", "Pausar" e uma ImageView com o logotipo:

exo_player**_control_view.xml**

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_gravity="bottom"
   android:layoutDirection="ltr"
   android:background="#CC000000"
   android:orientation="vertical">

 <LinearLayout
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:gravity="center"
   android:paddingTop="4dp"
   android:orientation="horizontal">

   <ImageButton android:id="@id/exo_play"
      style="@style/ExoMediaButton.Play"/>

   <ImageButton android:id="@id/exo_pause"
      style="@style/ExoMediaButton.Pause"/>

 </LinearLayout>

 <ImageView
     android:contentDescription="@string/logo"
     android:src="@drawable/google_logo"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"/>

</LinearLayout>

Isso demonstra como você pode adicionar seus próprios elementos aqui e os misturar com elementos de controle padrão. Agora, a ExoPlayerView usa o controle personalizado, e toda a lógica para ocultar e mostrar ao interagir com o controle é preservada.

8. Parabéns

Parabéns! Você aprendeu muito sobre como integrar o ExoPlayer ao app.

Saiba mais

Para saber mais sobre o ExoPlayer, confira o guia para desenvolvedores e o código-fonte, e se inscreva no blog do ExoPlayer (links em inglês).