A interface Player
é o núcleo da biblioteca ExoPlayer. Um Player
expõe a funcionalidade tradicional de alto nível do media player, como a capacidade de
armazenar em buffer, reproduzir, pausar e procurar. A implementação padrão ExoPlayer
foi
projetada para fazer poucas suposições sobre (e, portanto, impor poucas restrições a)
o tipo de mídia que está sendo reproduzida, como e onde ela é armazenada e como ela é
renderizada. Em vez de implementar o carregamento e a renderização de mídia diretamente,
as implementações de ExoPlayer
delegam esse trabalho a componentes injetados
quando um player é criado ou quando novas fontes de mídia são transmitidas ao player.
Os componentes comuns a todas as implementações de ExoPlayer
são:
- Instâncias de
MediaSource
que definem a mídia a ser reproduzida, carregam a mídia e de onde a mídia carregada pode ser lida. Uma instância deMediaSource
é criada de umMediaItem
por umMediaSource.Factory
dentro do player. Eles também podem ser transmitidos diretamente para o player usando a API de playlist baseada na fonte de mídia. - Uma instância de
MediaSource.Factory
que converte umMediaItem
em umMediaSource
. OMediaSource.Factory
é injetado quando o player é criado. - instâncias
Renderer
que renderizam componentes individuais da mídia. Eles são injetados quando o player é criado. - Um
TrackSelector
que seleciona faixas fornecidas peloMediaSource
para serem usadas por cadaRenderer
disponível. UmTrackSelector
é injetado quando o player é criado. - Um
LoadControl
que controla quando oMediaSource
armazena mais mídias em buffer e a quantidade de mídia que será armazenada em buffer. UmLoadControl
é injetado quando o player é criado. - Um
LivePlaybackSpeedControl
que controla a velocidade de reprodução durante as transmissões ao vivo para permitir que o player fique próximo de um deslocamento ao vivo configurado. UmLivePlaybackSpeedControl
é injetado quando o player é criado.
O conceito de injetar componentes que implementam partes da funcionalidade do player está presente em toda a biblioteca. As implementações padrão de alguns componentes delegam o trabalho a outros componentes injetados. Isso permite que muitos subcomponentes sejam substituídos individualmente por implementações configuradas de maneira personalizada.
Personalização do player
Confira abaixo alguns exemplos comuns de personalização do player com injeção de componentes.
Como configurar a pilha de rede
Temos uma página sobre como personalizar a pilha de rede usada pelo ExoPlayer (links em inglês).
Armazenamento em cache de dados carregados da rede
Consulte os guias sobre armazenamento em cache temporário em tempo real e como fazer o download de mídia.
Personalizar interações do servidor
Alguns apps podem querer interceptar solicitações e respostas HTTP. Talvez você queira injetar cabeçalhos de solicitação personalizados, ler os cabeçalhos de resposta do servidor, modificar os URIs das solicitações etc. Por exemplo, o app pode se autenticar injetando um token como um cabeçalho ao solicitar os segmentos de mídia.
O exemplo abaixo demonstra como implementar esses comportamentos
injetando um DataSource.Factory
personalizado no DefaultMediaSourceFactory
:
Kotlin
val dataSourceFactory = DataSource.Factory { val dataSource = httpDataSourceFactory.createDataSource() // Set a custom authentication request header. dataSource.setRequestProperty("Header", "Value") dataSource } val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory) ) .build()
Java
DataSource.Factory dataSourceFactory = () -> { HttpDataSource dataSource = httpDataSourceFactory.createDataSource(); // Set a custom authentication request header. dataSource.setRequestProperty("Header", "Value"); return dataSource; }; ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)) .build();
No snippet de código acima, o HttpDataSource
injetado inclui o cabeçalho
"Header: Value"
em cada solicitação HTTP. Esse comportamento foi corrigido para todas as
interações com uma origem HTTP.
Para uma abordagem mais granular, injete o comportamento just-in-time usando um
ResolvingDataSource
. O snippet de código abaixo mostra como injetar
cabeçalhos de solicitação antes de interagir com uma origem HTTP:
Kotlin
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time request headers. dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)) }
Java
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time request headers. dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));
Também é possível usar um ResolvingDataSource
para fazer
modificações pontuais do URI, conforme mostrado no snippet abaixo:
Kotlin
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time URI resolution logic. dataSpec.withUri(resolveUri(dataSpec.uri)) }
Java
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time URI resolution logic. dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));
Personalizar o tratamento de erros
A implementação de um LoadErrorHandlingPolicy
personalizado permite que os apps personalizem a
maneira como o ExoPlayer reage a erros de carregamento. Por exemplo, um app pode querer falhar rápido
em vez de tentar muitas vezes ou personalizar a lógica de espera que
controla quanto tempo o jogador espera entre cada nova tentativa. O snippet a seguir
mostra como implementar a lógica de desistência personalizada:
Kotlin
val loadErrorHandlingPolicy: LoadErrorHandlingPolicy = object : DefaultLoadErrorHandlingPolicy() { override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorInfo): Long { // Implement custom back-off logic here. return 0 } } val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy) ) .build()
Java
LoadErrorHandlingPolicy loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy() { @Override public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) { // Implement custom back-off logic here. return 0; } }; ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context) .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)) .build();
O argumento LoadErrorInfo
contém mais informações sobre a carga com falha para
personalizar a lógica com base no tipo de erro ou na solicitação com falha.
Personalizar sinalizações de extrator
As flags do extrator podem ser usadas para personalizar como formatos individuais são extraídos
de mídias progressivas. Elas podem ser definidas no DefaultExtractorsFactory
fornecido
ao DefaultMediaSourceFactory
. O exemplo a seguir transmite uma flag
que ativa a busca baseada em índice para streams de MP3.
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING) val player = ExoPlayer.Builder(context) .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory)) .build()
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING); ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory(new DefaultMediaSourceFactory(context, extractorsFactory)) .build();
Como ativar a busca de taxa de bits constante
Para streams de MP3, ADTS e AMR, é possível ativar a busca aproximada usando uma
suposição de taxa de bits constante com flags FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
.
Essas sinalizações podem ser definidas para extratores individuais usando os métodos DefaultExtractorsFactory.setXyzExtractorFlags
individuais, conforme descrito acima. Para
ativar a busca de taxa de bits constante para todos os extratores compatíveis, use
DefaultExtractorsFactory.setConstantBitrateSeekingEnabled
.
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
O ExtractorsFactory
pode ser injetado por DefaultMediaSourceFactory
, conforme
descrito para personalizar as flags do extrator acima.
Como ativar a fila de buffer assíncrona
A fila de buffer assíncrona é uma melhoria no pipeline de renderização
do ExoPlayer, que opera instâncias MediaCodec
no modo assíncrono e
usa outras linhas de execução para programar a decodificação e a renderização de dados. Ativar esse recurso
pode reduzir a queda de frames e o underrun de áudio.
A fila de buffer assíncrona é ativada por padrão em dispositivos com o Android 12 (nível 31 da API) e versões mais recentes, e pode ser ativada manualmente a partir do Android 6.0 (nível 23 da API). Considere ativar o recurso para dispositivos específicos em que você observa frames eliminados ou underruns de áudio, especialmente ao reproduzir conteúdo protegido por DRM ou com alta taxa de frames.
No caso mais simples, é necessário injetar uma DefaultRenderersFactory
no
player da seguinte maneira:
Kotlin
val renderersFactory = DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing() val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()
Java
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing(); ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();
Se você estiver instanciando renderizadores diretamente, transmita um
AsynchronousMediaCodecAdapter.Factory
para os construtores MediaCodecVideoRenderer
e
MediaCodecAudioRenderer
.
Como interceptar chamadas de método com ForwardingPlayer
É possível personalizar parte do comportamento de uma instância Player
envolvendo-a em
uma subclasse de ForwardingPlayer
e substituindo métodos para fazer qualquer uma das
seguintes ações:
- Acesse os parâmetros antes de transmiti-los ao delegado
Player
. - Acesse o valor de retorno do delegado
Player
antes de retorná-lo. - Implemente o método novamente.
Ao substituir métodos ForwardingPlayer
, é importante garantir que a
implementação permaneça consistente e em conformidade com a interface
Player
, especialmente ao lidar com métodos que têm comportamento idêntico ou relacionado. Exemplo:
- Se você quiser substituir todas as operações de "reprodução", substitua
ForwardingPlayer.play
eForwardingPlayer.setPlayWhenReady
, porque um autor da chamada espera que o comportamento desses métodos seja idêntico quandoplayWhenReady = true
. - Se você quiser mudar o incremento de busca para frente, é necessário substituir
ForwardingPlayer.seekForward
para realizar uma busca com o incremento personalizado eForwardingPlayer.getSeekForwardIncrement
para informar o incremento personalizado correto ao autor da chamada. - Se você quiser controlar quais
Player.Commands
são anunciados por uma instância de player, substituaPlayer.getAvailableCommands()
ePlayer.isCommandAvailable()
e também detecte o callbackPlayer.Listener.onAvailableCommandsChanged()
para receber notificações sobre mudanças provenientes do player.
Personalização do MediaSource
Os exemplos acima injetam componentes personalizados para uso durante a reprodução de todos
os objetos MediaItem
transmitidos ao player. Quando a personalização detalhada é
necessária, também é possível injetar componentes personalizados em instâncias
MediaSource
individuais, que podem ser transmitidas diretamente ao player. O exemplo
abaixo mostra como personalizar um ProgressiveMediaSource
para usar um
DataSource.Factory
, ExtractorsFactory
e LoadErrorHandlingPolicy
personalizados:
Kotlin
val mediaSource = ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri))
Java
ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri));
Criar componentes personalizados
A biblioteca fornece implementações padrão dos componentes listados na parte de cima
desta página para casos de uso comuns. Um ExoPlayer
pode usar esses componentes, mas
também pode ser criado para usar implementações personalizadas se comportamentos não padrão forem
necessários. Alguns casos de uso para implementações personalizadas são:
Renderer
: é possível implementar umRenderer
personalizado para processar um tipo de mídia que não tem suporte nas implementações padrão fornecidas pela biblioteca.TrackSelector
: a implementação de umTrackSelector
personalizado permite que o desenvolvedor do app mude a forma como as faixas expostas por umMediaSource
são selecionadas para consumo por cada um dosRenderer
s disponíveis.LoadControl
: a implementação de umLoadControl
personalizado permite que um desenvolvedor de apps mude a política de armazenamento em buffer do jogador.Extractor
: se você precisar oferecer suporte a um formato de contêiner que não tem suporte no momento na biblioteca, implemente uma classeExtractor
personalizada.MediaSource
: a implementação de uma classeMediaSource
personalizada pode ser adequada se você quiser receber amostras de mídia para alimentar os renderizadores de forma personalizada ou se quiser implementar o comportamento de composiçãoMediaSource
personalizado.MediaSource.Factory
: a implementação de umMediaSource.Factory
personalizado permite que um aplicativo personalize a maneira como umaMediaSource
é criada a partir de umaMediaItem
.DataSource
: o pacote upstream do ExoPlayer já contém várias implementaçõesDataSource
para diferentes casos de uso. É possível implementar sua própria classeDataSource
para carregar dados de outra maneira, por exemplo, por meio de um protocolo personalizado, usando uma pilha HTTP personalizada ou de um cache permanente personalizado.
Ao criar componentes personalizados, recomendamos o seguinte:
- Se um componente personalizado precisar informar eventos de volta ao app, recomendamos
usar o mesmo modelo dos componentes do ExoPlayer, por
exemplo, usando classes
EventDispatcher
ou transmitindo umHandler
com um listener para o construtor do componente. - Recomendamos que os componentes personalizados usem o mesmo modelo dos componentes
existentes do ExoPlayer para permitir a reconfiguração pelo app durante a reprodução. Para fazer isso,
os componentes personalizados precisam implementar
PlayerMessage.Target
e receber mudanças de configuração no métodohandleMessage
. O código do aplicativo precisa transmitir mudanças de configuração chamando o métodocreateMessage
do ExoPlayer, configurando a mensagem e enviando-a ao componente usandoPlayerMessage.send
. O envio de mensagens para serem entregues na linha de execução garante que elas sejam executadas em ordem com qualquer outra operação realizada no player.