Personnalisation

L'interface Player est au cœur de la bibliothèque ExoPlayer. Player expose les fonctionnalités traditionnelles du lecteur multimédia de haut niveau, comme la possibilité mettre en mémoire tampon les contenus multimédias, les lire, les mettre en pause et rechercher. L'implémentation par défaut ExoPlayer est conçu pour faire peu d'hypothèses (et donc imposer peu de restrictions) Le type de contenu multimédia en cours de lecture, comment et où il est stocké, et comment il est rendu. Plutôt que d'implémenter directement le chargement et l'affichage des médias, Les implémentations ExoPlayer délèguent ce travail aux composants injectés Lors de la création d'un lecteur ou de la transmission de nouvelles sources multimédias au lecteur. Les composants communs à toutes les implémentations de ExoPlayer sont les suivants:

  • Instances MediaSource qui définissent le contenu multimédia à lire et à charger, et à partir duquel le média chargé peut être lu. Une instance MediaSource est créée à partir d'un MediaItem par un MediaSource.Factory dans le lecteur. Ils peuvent également être transmises directement au lecteur à l'aide de l'API de playlist basée sur les sources multimédias.
  • Une instance MediaSource.Factory qui convertit un MediaItem en MediaSource. La MediaSource.Factory est injecté lors de la création du lecteur.
  • Instances Renderer qui affichent des composants individuels du contenu multimédia. Il s'agit est injecté lors de la création du lecteur.
  • TrackSelector qui sélectionne les pistes fournies par le MediaSource consommée par chaque Renderer disponible. Un TrackSelector est injecté lors de la création du lecteur.
  • LoadControl qui contrôle le moment où MediaSource met davantage de contenus multimédias en mémoire tampon. la quantité de contenu multimédia mise en mémoire tampon. Un LoadControl est injecté lorsque le lecteur est créé.
  • LivePlaybackSpeedControl qui contrôle la vitesse de lecture pendant le direct pour maintenir le lecteur à proximité d'un décalage en direct configuré. A LivePlaybackSpeedControl est injecté lors de la création du lecteur.

Le concept d'injection de composants qui implémentent des éléments de lecteur la fonctionnalité est présente dans toute la bibliothèque. Les implémentations par défaut certains composants délèguent le travail à d'autres composants injectés. Cela permet à de nombreux à remplacer individuellement par des implémentations configurés de manière personnalisée.

Personnalisation du lecteur

Voici quelques exemples courants de personnalisation du lecteur en injectant des composants : décrites ci-dessous.

Configurer la pile réseau

Vous trouverez sur cette page une page expliquant comment personnaliser la pile réseau utilisée par ExoPlayer.

Mise en cache des données chargées depuis le réseau

Consultez les guides pour mise en cache temporaire à la volée et le téléchargement de contenus multimédias.

Personnaliser les interactions du serveur

Certaines applications peuvent vouloir intercepter les requêtes et les réponses HTTP. Vous voudrez peut-être injecter des en-têtes de requêtes personnalisés, lire les en-têtes de réponse du serveur, modifier le de requêtes URI, etc. Par exemple, votre application peut s'authentifier en injectant un jeton comme en-tête lors de la requête des segments multimédias.

L'exemple suivant montre comment implémenter ces comportements en en injectant un DataSource.Factory personnalisé dans 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();

Dans l'extrait de code ci-dessus, le HttpDataSource injecté inclut l'en-tête. "Header: Value" dans chaque requête HTTP. Ce comportement est corrigé pour chaque avec une source HTTP.

Pour une approche plus précise, vous pouvez injecter un comportement juste-à-temps à l'aide d'un ResolvingDataSource L'extrait de code suivant montre comment injecter en-têtes de requête juste avant d'interagir avec une source 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)));

Vous pouvez également utiliser un ResolvingDataSource pour effectuer juste-à-temps de l'URI, comme indiqué dans l'extrait de code suivant:

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

Personnaliser la gestion des erreurs

L'implémentation d'un LoadErrorHandlingPolicy personnalisé permet aux applications de personnaliser le comment ExoPlayer réagit aux erreurs de chargement. Par exemple, une application peut vouloir échouer rapidement au lieu d'effectuer de nombreuses tentatives, ou vous pouvez personnaliser la logique d'attente contrôle le délai d'attente du joueur entre chaque tentative. L'extrait suivant montre comment implémenter une logique d'intervalle personnalisé entre les tentatives:

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

L'argument LoadErrorInfo contient plus d'informations sur l'échec du chargement dans personnaliser la logique en fonction du type d'erreur ou de la requête ayant échoué.

Personnaliser les options d'extracteur

Les indicateurs d'extracteur peuvent être utilisés pour personnaliser la façon dont des formats individuels sont extraits. des médias progressifs. Elles peuvent être définies sur le DefaultExtractorsFactory fournies à DefaultMediaSourceFactory. L'exemple suivant transmet un indicateur qui permet la recherche basée sur un index pour les flux 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();

Activer la recherche de débit constante

Pour les flux MP3, ADTS et AMR, vous pouvez activer la recherche approximative à l'aide d'un une hypothèse de débit constante avec les options FLAG_ENABLE_CONSTANT_BITRATE_SEEKING. Ces indicateurs peuvent être définis pour des extracteurs individuels à l'aide de la méthode DefaultExtractorsFactory.setXyzExtractorFlags comme décrit ci-dessus. À activez la recherche de débit constant pour tous les extracteurs compatibles, utilisez DefaultExtractorsFactory.setConstantBitrateSeekingEnabled

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

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

Le ExtractorsFactory peut ensuite être injecté via DefaultMediaSourceFactory en tant que décrit ci-dessus pour personnaliser les indicateurs d'extracteur.

Activer la mise en file d'attente de tampon asynchrone

La mise en file d'attente de tampon asynchrone est une amélioration du rendu d'ExoPlayer qui exploite des instances MediaCodec en mode asynchrone et utilise des threads supplémentaires pour planifier le décodage et le rendu des données. Activation... peut réduire les pertes de frames et les sous-utilisations audio.

La mise en file d'attente asynchrone des tampons est activée par défaut sur les appareils exécutant Android 12 (niveau d'API 31) ou version ultérieure. Vous pouvez l'activer manuellement à partir d'Android 6.0 (niveau d'API 23). Envisagez d'activer la fonctionnalité pour les appareils spécifiques sur lesquels vous avez constaté une chute de trames ou de sous-utilisation du son, en particulier lors de la lecture protégée par DRM ou d'une fréquence d'images élevée contenus.

Dans le cas le plus simple, vous devez injecter un DefaultRenderersFactory dans comme suit:

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 vous instanciez directement des moteurs de rendu, transmettez un AsynchronousMediaCodecAdapter.Factory à MediaCodecVideoRenderer et Constructeurs MediaCodecAudioRenderer.

Interception des appels de méthode avec ForwardingPlayer

Vous pouvez personnaliser certains comportements d'une instance Player en l'encapsulant dans une sous-classe de ForwardingPlayer et de remplacer les méthodes afin d'effectuer l'une des les éléments suivants:

  • Accédez aux paramètres avant de les transmettre au délégué Player.
  • Accédez à la valeur renvoyée par le délégué Player avant de la renvoyer.
  • Réimplémentez complètement la méthode.

Lorsque vous remplacez des méthodes ForwardingPlayer, il est important de vous assurer que reste cohérente et conforme aux Player. en particulier lorsqu'il s'agit de méthodes destinées à avoir identiques ou connexes. Exemple :

  • Pour ignorer toutes les "lectures" vous devez remplacer ForwardingPlayer.play et ForwardingPlayer.setPlayWhenReady, car un l'appelant s'attendra à ce que ces méthodes se comportent de la même manière playWhenReady = true
  • Si vous voulez modifier l'incrément de recherche avancée, vous devez remplacer à la fois ForwardingPlayer.seekForward pour effectuer une recherche à l'aide de vos incrémenter, et ForwardingPlayer.getSeekForwardIncrement pour générer un rapport le bon incrément personnalisé à l'appelant.
  • Si vous souhaitez contrôler les Player.Commands qui sont promus par un joueur vous devez remplacer Player.getAvailableCommands() et Player.isCommandAvailable(), et écoutez aussi les Rappel Player.Listener.onAvailableCommandsChanged() pour recevoir une notification des modifications provenant du lecteur sous-jacent.

Personnalisation de MediaSource

Les exemples ci-dessus injectent des composants personnalisés à utiliser lors de la lecture de tous Objets MediaItem transmis au lecteur Pour une personnalisation ultraprécise Il est également possible d'injecter des composants personnalisés dans des Instances MediaSource, qui peuvent être transmises directement au joueur. Exemple ci-dessous montre comment personnaliser un ProgressiveMediaSource pour utiliser un DataSource.Factory, ExtractorsFactory et 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));

Créer des composants personnalisés

La bibliothèque fournit des implémentations par défaut des composants répertoriés en haut de la page. de cette page pour des cas d'utilisation courants. Un ExoPlayer peut utiliser ces composants, mais peuvent également être conçus pour utiliser des implémentations personnalisées si des comportements non standards obligatoire. Voici quelques cas d'utilisation des implémentations personnalisées:

  • Renderer : vous pouvez implémenter un Renderer personnalisé pour gérer une type de média non compatible avec les implémentations par défaut fournies par bibliothèque.
  • TrackSelector : l'implémentation d'un TrackSelector personnalisé autorise une application. développeur de modifier la façon dont les pistes exposées par un MediaSource sont sélectionné pour être consommé par chacun des objets Renderer disponibles.
  • LoadControl : l'implémentation d'un LoadControl personnalisé autorise une application. le développeur de modifier les règles de mise en mémoire tampon du lecteur.
  • Extractor : si vous devez prendre en charge un format de conteneur qui n'est pas actuellement compatible avec la bibliothèque, envisagez d'implémenter une classe Extractor personnalisée.
  • MediaSource : l'implémentation d'une classe MediaSource personnalisée peut se présenter comme suit : approprié si vous souhaitez obtenir des échantillons de médias pour alimenter les moteurs de rendu dans un de manière personnalisée, ou si vous souhaitez implémenter une composition MediaSource personnalisée comportemental.
  • MediaSource.Factory : implémenter un MediaSource.Factory personnalisé permet à une application de personnaliser la façon dont un MediaSource est créé à partir d'un MediaItem.
  • DataSource : le package en amont d'ExoPlayer contient déjà un certain nombre de Implémentations de DataSource pour différents cas d'utilisation. Vous voudrez peut-être implémenter votre propre classe DataSource pour charger des données d'une autre manière, par exemple via un protocole personnalisé, à l'aide d'une pile HTTP personnalisée ou à partir d'un serveur cache.

Voici quelques recommandations pour créer des composants personnalisés:

  • Si un composant personnalisé doit transmettre des événements à l'application, nous vous recommandons que de le faire en utilisant le même modèle que les composants ExoPlayer existants, par par exemple en utilisant les classes EventDispatcher ou en transmettant un Handler avec un écouteur pour le constructeur du composant.
  • Nous vous recommandons d'utiliser le même modèle que les composants personnalisés ExoPlayer existants. pour permettre à l'application de la reconfigurer pendant la lecture. Pour ce faire, les composants personnalisés doivent implémenter PlayerMessage.Target et recevoir modifications de configuration dans la méthode handleMessage. Le code de l'application doit transmettre les modifications de configuration en appelant la méthode createMessage d'ExoPlayer, en configurant le message et en l'envoyant au composant PlayerMessage.send Envoi des messages à distribuer dans le fil de lecture garantit qu'elles sont exécutées dans l'ordre et que toutes les autres opérations sont sur le lecteur.