L'interface Player
est au cœur de la bibliothèque ExoPlayer. Un Player
présente des fonctionnalités traditionnelles de lecteur multimédia de haut niveau, telles que la mise en mémoire tampon, la lecture, la mise en pause et la recherche. L'implémentation par défaut ExoPlayer
est conçue pour faire peu d'hypothèses (et donc imposer peu de restrictions) sur le type de contenu multimédia lu, comment, où il est stocké et comment il est affiché. Plutôt que d'implémenter directement le chargement et l'affichage des contenus multimédias, les implémentations ExoPlayer
délèguent cette tâche aux composants injectés lors de la création d'un lecteur ou lorsque de nouvelles sources multimédias lui sont transmises.
Les composants communs à toutes les implémentations de ExoPlayer
sont les suivants:
- Instances
MediaSource
qui définissent le contenu multimédia à lire, chargent le contenu et à partir duquel le contenu multimédia chargé peut être lu. Une instanceMediaSource
est créée à partir d'unMediaItem
par unMediaSource.Factory
à l'intérieur du lecteur. Elles peuvent également être transmises directement au lecteur à l'aide de l'API de playlist basée sur la source multimédia. - Une instance
MediaSource.Factory
qui convertit unMediaItem
enMediaSource
. LeMediaSource.Factory
est injecté lors de la création du lecteur. - Instances
Renderer
qui affichent des composants individuels du contenu multimédia. Ceux-ci sont injectés lors de la création du lecteur. - Un
TrackSelector
qui sélectionne les pistes fournies par leMediaSource
à utiliser par chaqueRenderer
disponible. Un élémentTrackSelector
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, ainsi que la quantité de contenus multimédias mis en mémoire tampon. Un élémentLoadControl
est injecté lors de la création du lecteur.- Un
LivePlaybackSpeedControl
qui contrôle la vitesse de lecture pendant les lectures en direct afin de permettre au lecteur de rester proche d'un décalage en direct configuré. UnLivePlaybackSpeedControl
est injecté lors de la création du lecteur.
Le concept d'injection de composants qui implémentent les fonctionnalités du lecteur est présent dans toute la bibliothèque. Les implémentations par défaut de certains composants délèguent le travail à d'autres composants injectés. Cela permet de remplacer individuellement de nombreux sous-composants par des implémentations configurées de manière personnalisée.
Personnalisation du lecteur
Vous trouverez ci-dessous des exemples courants de personnalisation du lecteur via l'injection de composants.
Configurer la pile réseau
Nous avons une page sur la personnalisation de la pile réseau utilisée par ExoPlayer.
Mettre en cache des données chargées depuis le réseau
Consultez les guides sur la mise en cache temporaire à la volée et le téléchargement de contenus multimédias.
Personnaliser les interactions avec le serveur
Certaines applications peuvent souhaiter intercepter les requêtes et les réponses HTTP. Vous pouvez injecter des en-têtes de requêtes personnalisés, lire les en-têtes de réponse du serveur, modifier les URI des requêtes, etc. Par exemple, votre application peut s'authentifier en injectant un jeton en tant qu'en-tête lorsqu'elle demande les segments multimédias.
L'exemple suivant montre comment mettre en œuvre ces comportements 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 fixé pour chaque interaction 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 des 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 des modifications de l'URI juste à temps, 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 la manière dont ExoPlayer réagit aux erreurs de chargement. Par exemple, une application peut vouloir échouer rapidement au lieu de réessayer plusieurs fois, ou personnaliser la logique d'attente qui contrôle le temps d'attente du joueur entre chaque nouvelle tentative. L'extrait de code suivant montre comment mettre en œuvre une logique personnalisée d'intervalle 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 pour personnaliser la logique en fonction du type d'erreur ou de la requête ayant échoué.
Personnaliser les indicateurs 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
fourni à DefaultMediaSourceFactory
. L'exemple suivant transmet un indicateur qui active 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 à débit constant
Pour les flux MP3, ADTS et AMR, vous pouvez activer la recherche approximative en utilisant une hypothèse de débit constante avec des indicateurs FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
.
Ces indicateurs peuvent être définis pour des extracteurs individuels à l'aide des méthodes DefaultExtractorsFactory.setXyzExtractorFlags
individuelles, comme décrit ci-dessus. Pour activer la recherche à 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
, comme 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 pipeline de rendu d'ExoPlayer, qui exploite les instances MediaCodec
en mode asynchrone et utilise des threads supplémentaires pour planifier le décodage et le rendu des données. Son activation permet de réduire les pertes de frames et les sous-diffusions audio.
La mise en file d'attente de tampon asynchrone est activée par défaut sur les appareils équipés d'Android 12 (niveau d'API 31) ou version ultérieure. Elle peut être activée manuellement à partir d'Android 6.0 (niveau d'API 23). Pensez à activer cette fonctionnalité pour des appareils spécifiques sur lesquels vous constatez des pertes de frames ou des sous-diffusions audio, en particulier lorsque vous lisez du contenu protégé par DRM ou à fréquence d'images élevée.
Dans le cas le plus simple, vous devez injecter un DefaultRenderersFactory
au joueur 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
aux constructeurs MediaCodecVideoRenderer
et MediaCodecAudioRenderer
.
Interception des appels de méthode avec ForwardingPlayer
Vous pouvez personnaliser une partie du comportement d'une instance Player
en l'encapsulant dans une sous-classe de ForwardingPlayer
et en remplaçant les méthodes afin d'effectuer l'une des opérations suivantes:
- 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 l'implémentation reste cohérente et conforme à l'interface Player
, en particulier lorsque vous traitez des méthodes destinées à avoir un comportement identique ou associé. Par exemple :
- Si vous souhaitez ignorer chaque opération de lecture, vous devez remplacer
ForwardingPlayer.play
etForwardingPlayer.setPlayWhenReady
, car un appelant s'attend à ce que le comportement de ces méthodes soit identique lorsqueplayWhenReady = true
. - Si vous souhaitez modifier l'incrément de recherche avancée, vous devez remplacer à la fois
ForwardingPlayer.seekForward
pour effectuer une recherche avec votre incrément personnalisé etForwardingPlayer.getSeekForwardIncrement
afin de signaler l'incrément personnalisé correct à l'appelant. - Si vous souhaitez contrôler les
Player.Commands
annoncés par une instance de joueur, vous devez ignorerPlayer.getAvailableCommands()
etPlayer.isCommandAvailable()
, et écouter le rappelPlayer.Listener.onAvailableCommandsChanged()
pour être averti des modifications provenant du lecteur sous-jacent.
Personnalisation de la MediaSource
Les exemples ci-dessus injectent des composants personnalisés à utiliser lors de la lecture de tous les objets MediaItem
transmis au lecteur. Lorsqu'une personnalisation ultraprécise est requise, il est également possible d'injecter des composants personnalisés dans des instances MediaSource
individuelles, qui peuvent être transmises directement au lecteur. L'exemple ci-dessous montre comment personnaliser un ProgressiveMediaSource
pour utiliser des éléments DataSource.Factory
, ExtractorsFactory
et LoadErrorHandlingPolicy
personnalisés:
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 cette page pour les cas d'utilisation courants. Un élément ExoPlayer
peut utiliser ces composants, mais peut également être créé pour utiliser des implémentations personnalisées si des comportements non standards sont requis. Voici quelques cas d'utilisation des implémentations personnalisées:
Renderer
: vous pouvez implémenter unRenderer
personnalisé pour gérer un type de contenu non compatible avec les implémentations par défaut fournies par la bibliothèque.TrackSelector
: l'implémentation d'unTrackSelector
personnalisé permet à un développeur d'applications de modifier la façon dont les pistes exposées par unMediaSource
sont sélectionnées pour être utilisées par chacune desRenderer
disponibles.LoadControl
: l'implémentation d'unLoadControl
personnalisé permet à un développeur d'applications de modifier la règle de mise en mémoire tampon du lecteur.Extractor
: si vous devez accepter un format de conteneur qui n'est actuellement pas compatible avec la bibliothèque, envisagez d'implémenter une classeExtractor
personnalisée.MediaSource
: l'implémentation d'une classeMediaSource
personnalisée peut être appropriée si vous souhaitez obtenir des exemples de contenus multimédias pour alimenter les moteurs de rendu de manière personnalisée, ou si vous souhaitez implémenter un comportement de compositionMediaSource
personnalisé.MediaSource.Factory
: implémenter unMediaSource.Factory
personnalisé permet à une application de personnaliser la façon dont unMediaSource
est créé à partir d'unMediaItem
.DataSource
: le package en amont d'ExoPlayer contient déjà un certain nombre d'implémentationsDataSource
pour différents cas d'utilisation. Vous pouvez implémenter votre propre classeDataSource
pour charger des données d'une autre manière, par exemple via un protocole personnalisé, une pile HTTP personnalisée ou un cache persistant personnalisé.
Lorsque vous créez des composants personnalisés, nous vous recommandons de suivre ces recommandations:
- Si un composant personnalisé doit signaler des événements à l'application, nous vous recommandons d'utiliser le même modèle que les composants ExoPlayer existants, par exemple en utilisant des classes
EventDispatcher
ou en transmettant unHandler
avec un écouteur au constructeur du composant. - Nous vous recommandons d'utiliser le même modèle que les composants ExoPlayer existants pour permettre la reconfiguration par l'application lors de la lecture. Pour ce faire, les composants personnalisés doivent implémenter
PlayerMessage.Target
et recevoir les modifications de configuration dans la méthodehandleMessage
. Le code d'application doit transmettre les modifications de configuration en appelant la méthodecreateMessage
d'ExoPlayer, en configurant le message et en l'envoyant au composant à l'aide dePlayerMessage.send
. L'envoi de messages à distribuer sur le thread de lecture garantit qu'ils sont exécutés dans l'ordre avec toutes les autres opérations effectuées sur le lecteur.