Personalização

No centro da biblioteca do ExoPlayer está a interface Player. Um Player expõe a funcionalidade tradicional de player de mídia de alto nível, como a capacidade de armazenar mídia em buffer, reproduzir, pausar e procurar. A implementação padrão ExoPlayer é projetados para fazer poucas suposições sobre (e, portanto, impor poucas restrições) o tipo de mídia sendo reproduzida, como e onde ela está armazenada e como é renderizado. Em vez de implementar o carregamento e a renderização de mídia diretamente, As implementações ExoPlayer delegam esse trabalho aos componentes injetados quando um player é criado ou quando novas fontes de mídia são passadas para o 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 do qual a mídia carregada pode ser lida. Uma instância MediaSource é criada de um MediaItem por um MediaSource.Factory dentro do player. Eles também podem ser passados diretamente para o player usando a API de playlist baseada na fonte de mídia.
  • Uma instância de MediaSource.Factory que converte um MediaItem em um MediaSource. A A MediaSource.Factory é injetada quando o player é criado.
  • Instâncias de Renderer que renderizam componentes individuais da mídia. São injetada quando o player é criado.
  • Um TrackSelector que seleciona as faixas fornecidas pelo MediaSource a serem consumida por cada Renderer disponível. Um TrackSelector é injetado quando o player é criado.
  • Um LoadControl que controla quando o MediaSource armazena mais mídias em buffer e a quantidade de mídia armazenada em buffer. Uma LoadControl é injetada quando o jogador criados.
  • Um LivePlaybackSpeedControl que controla a velocidade do vídeo ao vivo é reproduzido para permitir que o player fique próximo a um deslocamento ao vivo configurado. Um A LivePlaybackSpeedControl é injetada quando o player é criado.

Conceito de injetar componentes que implementam peças do jogador está presente em toda a biblioteca. As implementações padrão alguns componentes delegam trabalho a outros componentes injetados. Isso permite que muitos subcomponentes sejam substituídos individualmente por implementações que são e configurá-la de modo personalizado.

Personalização do player

Alguns exemplos comuns de personalização do player injetando componentes são descritas abaixo.

Configurar a pilha de rede

Temos uma página sobre como personalizar a pilha de rede usada pelo ExoPlayer (links em inglês).

armazenar em cache os dados carregados da rede;

Consulte os guias armazenamento em cache temporário e temporário e fazer o download de mídia.

Como 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 solicitações" URIs etc. Por exemplo, o app pode se autenticar injetando um token como cabeçalho ao solicitar os segmentos de mídia.

O exemplo a seguir 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, a HttpDataSource injetada inclui o cabeçalho "Header: Value" em cada solicitação HTTP. Esse comportamento é corrigido para cada com uma origem HTTP.

Para uma abordagem mais granular, é possível injetar um comportamento no momento certo usando um ResolvingDataSource: O snippet de código a seguir mostra como injetar cabeçalhos de solicitação logo antes de interagir com uma fonte 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)));

Você também pode usar um ResolvingDataSource para realizar modificações no momento certo do URI, conforme mostrado no snippet a seguir:

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)));

Como personalizar o tratamento de erros

A implementação de uma LoadErrorHandlingPolicy personalizada permite que os apps personalizem o forma como o ExoPlayer reage aos erros de carregamento. Por exemplo, um app pode querer falhar rapidamente 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 uma lógica personalizada de espera:

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 o carregamento com falha para personalizar a lógica com base no tipo de erro ou na solicitação com falha.

Personalizar sinalizações do extrator

As sinalizações do extrator podem ser usadas para personalizar como formatos individuais são extraídos. da mídia progressiva. Elas podem ser definidas no DefaultExtractorsFactory fornecido ao DefaultMediaSourceFactory. O exemplo a seguir transmite uma sinalização que ativa a busca baseada em índice para streams 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 constante da taxa de bits

Para streams de MP3, ADTS e AMR, você pode ativar a busca aproximada usando um suposição constante da taxa de bits com as sinalizações FLAG_ENABLE_CONSTANT_BITRATE_SEEKING. Essas sinalizações podem ser definidas para extratores individuais usando o método DefaultExtractorsFactory.setXyzExtractorFlags, conforme descrito acima. Para possibilitar uma busca constante por taxa de bits para todos os extratores que a suportam, usar DefaultExtractorsFactory.setConstantBitrateSeekingEnabled:

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);

O ExtractorsFactory pode ser injetado via DefaultMediaSourceFactory como descritas para personalizar as sinalizações do extrator acima.

Como ativar o enfileiramento do buffer assíncrono

O enfileiramento assíncrono de buffer é uma melhoria na renderização do ExoPlayer pipeline, que opera instâncias MediaCodec em modo assíncrono e usa linhas de execução extras para programar a decodificação e a renderização de dados. Como ativar pode reduzir a queda de frames e underruns de áudio.

O enfileiramento assíncrono de buffer é ativado por padrão em dispositivos com o Android 12 (nível 31 da API) e versões mais recentes. Eles podem ser ativados manualmente a partir do Android 6.0 (nível 23 da API). Considere ativar o recurso para dispositivos específicos em que você percebeu quedas frames ou underruns de áudio, especialmente ao reproduzir imagens protegidas por DRM ou com frame rate alto conteúdo.

No caso mais simples, é necessário injetar um DefaultRenderersFactory no player da seguinte forma:

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, passe um AsynchronousMediaCodecAdapter.Factory para MediaCodecVideoRenderer e Construtores MediaCodecAudioRenderer.

Como interceptar chamadas de método com ForwardingPlayer

Você pode personalizar parte do comportamento de uma instância Player envolvendo-a uma subclasse de ForwardingPlayer e a substituição de métodos para realizar qualquer uma o seguinte:

  • Acesse os parâmetros antes de transmiti-los ao Player delegado.
  • Acesse o valor de retorno do Player delegado antes de retorná-lo.
  • Reimplementar o método completamente.

Ao substituir métodos ForwardingPlayer, é importante garantir que a implementação permanece consistente e em conformidade com a Player. interface do usuário, especialmente ao lidar com métodos que se destinam a ter idênticos ou relacionados. Exemplo:

  • Se você deseja substituir cada reprodução operação, é preciso substituir ForwardingPlayer.play e ForwardingPlayer.setPlayWhenReady, porque uma vai esperar que o comportamento desses métodos seja idêntico ao playWhenReady = true
  • Se você quiser mudar o incremento de avanço para a próxima, substitua ambos ForwardingPlayer.seekForward para fazer uma busca com seu incremento, e ForwardingPlayer.getSeekForwardIncrement para informar incremento personalizado correto de volta para o autor da chamada.
  • Se você quiser controlar quais Player.Commands são anunciadas pelos jogadores é necessário substituir Player.getAvailableCommands() e Player.isCommandAvailable() e também ouvir os Player.Listener.onAvailableCommandsChanged() para receber as notificações alterações vindas do player subjacente.

Personalização do MediaSource

Os exemplos acima injetam componentes personalizados para uso durante a reprodução de todos Objetos MediaItem que são transmitidos ao jogador. Onde a personalização refinada está é possível, também é possível injetar componentes personalizados em Instâncias de MediaSource, que podem ser transmitidas diretamente ao player. O exemplo abaixo mostra como personalizar um ProgressiveMediaSource para usar um DataSource.Factory, ExtractorsFactory e LoadErrorHandlingPolicy:

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 oferece implementações padrão dos componentes listados no topo para casos de uso comuns. Um ExoPlayer pode usar esses componentes, mas também podem ser criados para usar implementações personalizadas se comportamentos não padrão forem obrigatórios. Alguns casos de uso para implementações personalizadas são:

  • Renderer: implemente um Renderer personalizado para processar uma tipo de mídia incompatível com as implementações padrão fornecidas pelo biblioteca.
  • TrackSelector: implementar um TrackSelector personalizado permite que um app o desenvolvedor mude a forma como as faixas expostas por uma MediaSource são selecionados para consumo por cada um dos Renderers disponíveis.
  • LoadControl: implementar um LoadControl personalizado permite que um app desenvolvedor altere a política de armazenamento em buffer do player.
  • Extractor: se você precisar de suporte a um formato de contêiner que não é atualmente com suporte da biblioteca, implemente uma classe Extractor personalizada.
  • MediaSource: a implementação de uma classe MediaSource personalizada pode ser apropriado se você deseja obter amostras de mídia para alimentar os renderizadores em um de maneira personalizada ou caso você queira implementar a composição de MediaSource personalizada. do seu modelo.
  • MediaSource.Factory: como implementar um MediaSource.Factory personalizado permite que um aplicativo personalize a forma como uma MediaSource é criada. de um MediaItem.
  • DataSource: o pacote upstream do ExoPlayer já contém vários elementos. Implementações de DataSource para diferentes casos de uso. Talvez você queira implementar sua própria classe DataSource para carregar dados de outra forma, como um protocolo personalizado, usando uma pilha HTTP personalizada ou uma cache.

Ao criar componentes personalizados, recomendamos o seguinte:

  • Se um componente personalizado precisar relatar eventos de volta ao app, recomendamos que você faça isso usando o mesmo modelo dos componentes existentes do ExoPlayer, para exemplo usando classes EventDispatcher ou transmitindo um Handler junto com um listener para o construtor do componente.
  • Recomendamos que componentes personalizados usem o mesmo modelo que o ExoPlayer já existente para permitir a reconfiguração do app durante a reprodução. Para isso, os componentes personalizados precisam implementar PlayerMessage.Target e receber mudanças de configuração no método handleMessage. O código do aplicativo deve transmitir mudanças de configuração chamando o método createMessage do ExoPlayer; a configuração da mensagem e o envio dela ao componente PlayerMessage.send. Enviar mensagens a serem entregues na sequência de reprodução garante que eles sejam executados em ordem, com outras operações sendo realizada no player.