Personalización

En el centro de la biblioteca de ExoPlayer, se encuentra la interfaz Player. Un Player expone la funcionalidad tradicional del reproductor multimedia de alto nivel, como la capacidad de almacenar contenido multimedia en búfer, reproducir, pausar y buscar contenido multimedia. La implementación predeterminada de ExoPlayer es la siguiente: diseñada para hacer pocas suposiciones (y, por lo tanto, imponer pocas restricciones sobre) el tipo de contenido multimedia que se reproduce, cómo y dónde se almacenan, y cómo se se renderizan. En lugar de implementar la carga y la renderización de contenido multimedia directamente, Las implementaciones de ExoPlayer delegan este trabajo a los componentes que se insertan. Cuando se crea un reproductor o cuando se pasan nuevas fuentes de contenido multimedia al reproductor. Los componentes comunes a todas las implementaciones de ExoPlayer son los siguientes:

  • Instancias de MediaSource que definen el contenido multimedia que se reproducirá, cargan el contenido multimedia y desde la cual se pueden leer los medios cargados. Se crea una instancia MediaSource desde un elemento MediaItem por un elemento MediaSource.Factory dentro del reproductor. También pueden pasar directamente al reproductor mediante la API de listas de reproducción basadas en fuentes de medios
  • Una instancia de MediaSource.Factory que convierta un MediaItem en un MediaSource El Se inserta MediaSource.Factory cuando se crea el reproductor.
  • Instancias Renderer que renderizan componentes individuales del contenido multimedia. Son insertar cuando se crea el reproductor.
  • Un TrackSelector que selecciona las pistas que proporciona el MediaSource que se consumidas por cada Renderer disponible. Se inserta un TrackSelector. cuando se crea el reproductor.
  • Un LoadControl que controla cuándo el MediaSource almacena en búfer más contenido multimedia la cantidad de contenido multimedia almacenado en búfer. Se inserta un LoadControl cuando el reproductor está crear.
  • Un objeto LivePlaybackSpeedControl que controla la velocidad de reproducción durante la transmisión en vivo reproducciones que permiten que el reproductor se mantenga cerca de un desplazamiento en vivo configurado R Se inserta LivePlaybackSpeedControl cuando se crea el reproductor.

El concepto de inyectar componentes que implementan partes del jugador está presente en toda la biblioteca. Las implementaciones predeterminadas algunos de los componentes delegan trabajo para insertar otros componentes. Esto permite que muchas subcomponentes que se reemplazarán individualmente por implementaciones que sean de forma personalizada.

Personalización del reproductor

Algunos ejemplos comunes de personalización del reproductor a través de la inserción de componentes son que se describe a continuación.

Configura la pila de red

Tenemos una página sobre cómo personalizar la pila de red que usa ExoPlayer.

Almacenar datos en caché cargados desde la red

Consulta las guías para almacenamiento temporal en caché sobre la marcha y descarga de contenido multimedia.

Personaliza las interacciones del servidor

Es posible que algunas apps quieran interceptar solicitudes y respuestas HTTP. Te recomendamos insertar encabezados de solicitud personalizados, leer los encabezados de respuesta del servidor, modificar solicitudes URI, etc. Por ejemplo, tu app puede autenticarse mediante la inyección un token como encabezado cuando se solicitan segmentos multimedia.

En el siguiente ejemplo, se muestra cómo implementar estos comportamientos Cómo insertar un DataSource.Factory personalizado en 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();

En el fragmento de código anterior, el HttpDataSource insertado incluye el encabezado. "Header: Value" en cada solicitud HTTP. Este comportamiento se corrige para cada interacción con una fuente HTTP.

Para obtener un enfoque más detallado, puedes incorporar un comportamiento justo a tiempo con un ResolvingDataSource En el siguiente fragmento de código, se muestra cómo insertar de solicitud de acceso justo antes de interactuar con una fuente 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)));

También puedes usar un ResolvingDataSource para realizar modificaciones oportunas del URI, como se muestra en el siguiente fragmento:

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

Personaliza el manejo de errores

La implementación de un LoadErrorHandlingPolicy personalizado permite que las apps personalicen la en la que ExoPlayer reacciona a los errores de carga. Por ejemplo, es posible que una app quiera fallar rápido en lugar de intentarlo muchas veces, o puede personalizar la lógica de retirada controla cuánto tiempo espera el jugador entre cada reintento. El siguiente fragmento se muestra cómo implementar una lógica de retirada 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();

El argumento LoadErrorInfo contiene más información sobre la carga con errores en personalizar la lógica según el tipo de error o la solicitud con errores

Personaliza las marcas del extractor

Las marcas de extractor se pueden usar para personalizar la forma en que se extraen los formatos individuales de los medios progresivos. Se pueden configurar en el DefaultExtractorsFactory que es proporcionados a DefaultMediaSourceFactory. En el siguiente ejemplo, se pasa una marca que permite la búsqueda basada en índices para transmisiones 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();

Cómo habilitar la búsqueda de tasa de bits constante

Para transmisiones MP3, ADTS y AMR, puedes habilitar la búsqueda aproximada usando un Suposición de tasa de bits constante con marcas FLAG_ENABLE_CONSTANT_BITRATE_SEEKING. Estas marcas se pueden configurar para extractores individuales que DefaultExtractorsFactory.setXyzExtractorFlags, como se describió más arriba. Para habilita la búsqueda de tasa de bits constante para todos los extractores compatibles, usa DefaultExtractorsFactory.setConstantBitrateSeekingEnabled

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

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

Luego, el ExtractorsFactory se puede insertar a través de DefaultMediaSourceFactory como que se describieron para personalizar las marcas de extractor antes mencionadas.

Habilita la cola de búfer asíncrona

La cola de búfer asíncrona es una mejora en la renderización de ExoPlayer. por lotes, que opera instancias MediaCodec en modo asíncrono y usa subprocesos adicionales para programar la decodificación y renderización de datos. Habilitando puede reducir la pérdida de fotogramas y los subdesbordamientos de audio.

La cola de búfer asíncrona está habilitada de forma predeterminada en dispositivos que ejecutan Android 12 (nivel de API 31) y versiones posteriores, y se puede habilitar manualmente a partir de Android 6.0 (nivel de API 23). Considera habilitar la función para dispositivos específicos en los que observes caídas fotogramas o subdesbordamiento de audio, particularmente al reproducir contenido protegido por DRM o de una velocidad de fotogramas alta contenido.

En el caso más simple, debes insertar un DefaultRenderersFactory en el reproductor de la siguiente manera:

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

Si creas instancias de renderizadores directamente, pasa un AsynchronousMediaCodecAdapter.Factory a MediaCodecVideoRenderer y MediaCodecAudioRenderer.

Interceptación de llamadas a métodos con ForwardingPlayer

Puedes personalizar parte del comportamiento de una instancia de Player si la unes una subclase de ForwardingPlayer y métodos de anulación para realizar cualquiera de lo siguiente:

  • Accede a los parámetros antes de pasarlos al delegado Player.
  • Accede al valor que se muestra del delegado Player antes de mostrarlo.
  • Vuelve a implementar el método por completo.

Cuando anules métodos ForwardingPlayer, es importante garantizar que se cumpla lo siguiente: implementación sigue siendo autocoherente y cumple con el Player especialmente cuando se trata de métodos destinados a lograr comportamiento idéntico o relacionado. Por ejemplo:

  • Si quieres anular cada operación de reproducción una única operación, debes anular ambas ForwardingPlayer.play y ForwardingPlayer.setPlayWhenReady, ya que un llamador esperará que el comportamiento de estos métodos sea idéntico cuando playWhenReady = true
  • Si quieres cambiar el incremento de avance, debes anular ambas ForwardingPlayer.seekForward para realizar un salto con la función y ForwardingPlayer.getSeekForwardIncrement para informar el incremento personalizado correcto al emisor.
  • Si quieres controlar los Player.Commands que promociona un jugador debes anular Player.getAvailableCommands() y Player.isCommandAvailable() y también escuchar Es la devolución de llamada Player.Listener.onAvailableCommandsChanged() para recibir notificaciones. cambios provenientes del reproductor subyacente.

Personalización de MediaSource

Con los ejemplos anteriores, se insertan componentes personalizados para usar durante la reproducción de todos Objetos MediaItem que se pasan al reproductor Dónde es la personalización detallada se requiere, también es posible insertar componentes personalizados en objetos Instancias de MediaSource, que se pueden pasar directamente al reproductor. El ejemplo a continuación, se muestra cómo personalizar un ProgressiveMediaSource para usar un DataSource.Factory, ExtractorsFactory y 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));

Crea componentes personalizados

La biblioteca proporciona implementaciones predeterminadas de los componentes enumerados en la parte superior. de esta página para casos de uso comunes. Un ExoPlayer puede usar estos componentes, pero pueden compilarse para usar implementaciones personalizadas si no se cumplen como en los productos necesarios. Estos son algunos casos de uso para las implementaciones personalizadas:

  • Renderer: Te recomendamos que implementes un Renderer personalizado para controlar un El tipo de medio no es compatible con las implementaciones predeterminadas que proporciona el biblioteca.
  • TrackSelector: La implementación de un TrackSelector personalizado permite que una app desarrollador que cambia la forma en que los segmentos expuestos por un MediaSource se seleccionado para consumo por cada uno de los Renderer disponibles.
  • LoadControl: La implementación de un LoadControl personalizado permite que una app desarrollador para cambiar la política de almacenamiento en búfer del jugador.
  • Extractor: si necesitas admitir un formato de contenedor que no está disponible en este momento que admite la biblioteca, considera implementar una clase Extractor personalizada.
  • MediaSource: Implementar una clase MediaSource personalizada puede ser apropiado si deseas obtener muestras de contenido multimedia para enviar a los renderizadores de una de forma personalizada o si quieres implementar la composición MediaSource personalizada. el comportamiento de los usuarios.
  • MediaSource.Factory: Cómo implementar un MediaSource.Factory personalizado permite que una aplicación personalice la forma en que se crea un MediaSource desde un MediaItem.
  • DataSource: El paquete upstream de ExoPlayer ya contiene varias Implementaciones de DataSource para diferentes casos de uso Te recomendamos implementas tu propia clase DataSource para cargar datos de otra manera, como en un protocolo personalizado, con una pila HTTP personalizada la caché.

Cuando compiles componentes personalizados, te recomendamos lo siguiente:

  • Si un componente personalizado necesita informar eventos a la aplicación, recomendamos hacerlo con el mismo modelo que los componentes existentes de ExoPlayer, por ejemplo con clases EventDispatcher o pasando un Handler junto con un objeto de escucha al constructor del componente.
  • Recomendamos que los componentes personalizados usen el mismo modelo que el ExoPlayer existente. componentes para permitir la reconfiguración de la app durante la reproducción. Para ello, los componentes personalizados deben implementar PlayerMessage.Target y recibir cambios de configuración en el método handleMessage El código de la aplicación pasar cambios de configuración llamando al método createMessage de ExoPlayer configurar el mensaje y enviarlo al componente usando PlayerMessage.send Cómo enviar mensajes para que se entreguen en la conversación de reproducción garantiza que se ejecuten de acuerdo con que se ejecute cualquier otra operación en el reproductor.