L'interface Player est au cœur de la bibliothèque ExoPlayer. Un Player expose les fonctionnalités de lecteur multimédia de haut niveau traditionnelles, telles que la mise en mémoire tampon, la lecture, la mise en pause et la recherche de contenus multimédias. L'implémentation par défaut ExoPlayer est conçue pour faire peu d'hypothèses sur le type de contenu multimédia lu, la manière dont il est stocké et l'endroit où il est stocké, ainsi que sur la manière dont il est affiché (et donc pour imposer peu de restrictions). Plutôt que d'implémenter directement le chargement et l'affichage des contenus multimédias, les implémentations ExoPlayer délèguent ce travail aux composants injectés lors de la création d'un lecteur ou lorsque de nouvelles sources multimédias sont transmises au lecteur.
Les composants communs à toutes les implémentations de ExoPlayer sont les suivants :
- Instances
MediaSourcequi définissent le contenu multimédia à lire, chargent le contenu multimédia et à partir desquelles le contenu multimédia chargé peut être lu. Une instanceMediaSourceest créée à partir d'unMediaItempar unMediaSource.Factorydans le lecteur. Ils peuvent également être transmis directement au lecteur à l'aide de l'API de playlist basée sur une source multimédia. - Instance
MediaSource.Factoryqui convertit unMediaItemenMediaSource. LeMediaSource.Factoryest injecté lors de la création du lecteur. Rendererinstances qui affichent les composants individuels du contenu multimédia. Ils sont injectés lors de la création du lecteur.- Un
TrackSelectorqui sélectionne les pistes fournies par leMediaSourcepour qu'elles soient utilisées par chaqueRendererdisponible. UnTrackSelectorest injecté lors de la création du lecteur. - Un
LoadControlqui contrôle quandMediaSourcemet en mémoire tampon plus de contenus multimédias et la quantité de contenus multimédias mis en mémoire tampon. UnLoadControlest injecté lors de la création du lecteur. - Un
LivePlaybackSpeedControlqui contrôle la vitesse de lecture pendant les lectures en direct pour permettre au lecteur de rester proche d'un décalage en direct configuré. UnLivePlaybackSpeedControlest injecté lors de la création du lecteur.
Le concept d'injection de composants qui implémentent des éléments de la fonctionnalité du lecteur est présent dans l'ensemble de 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
Voici quelques exemples courants de personnalisation du lecteur par 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 les 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 vouloir intercepter les requêtes et les réponses HTTP. Vous pouvez injecter des en-têtes de requête 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 lors de la demande des segments multimédias.
L'exemple suivant montre comment implémenter ces comportements en injectant un DataSource.Factory personnalisé dans le 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 précédent, l'HttpDataSource injecté inclut l'en-tête "Header: Value" dans chaque requête HTTP. Ce comportement est corrigé 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 modifier l'URI juste-à-temps, comme illustré dans l'extrait 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 façon 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'intervalle entre les tentatives qui contrôle le temps d'attente du lecteur entre chaque tentative. L'extrait de code suivant montre comment implémenter une logique d'intervalle exponentiel entre les tentatives personnalisée :
Kotlin
val loadErrorHandlingPolicy: LoadErrorHandlingPolicy = object : DefaultLoadErrorHandlingPolicy() { override fun getRetryDelayMsFor( loadErrorInfo: LoadErrorHandlingPolicy.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(LoadErrorHandlingPolicy.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 afin de personnaliser la logique en fonction du type d'erreur ou de la requête ayant échoué.
Personnaliser les options de l'extracteur
Les options d'extracteur peuvent être utilisées pour personnaliser la façon dont les formats individuels sont extraits des contenus multimédias progressifs. Elles peuvent être définies sur l'objet DefaultExtractorsFactory fourni à DefaultMediaSourceFactory. L'exemple suivant transmet un flag qui active la recherche basée sur l'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 binaire constant
Pour les flux MP3, ADTS et AMR, vous pouvez activer la recherche approximative en utilisant une hypothèse de débit constant avec les options FLAG_ENABLE_CONSTANT_BITRATE_SEEKING.
Ces options peuvent être définies pour des extracteurs individuels à l'aide des méthodes DefaultExtractorsFactory.setXyzExtractorFlags individuelles, comme décrit précédemment. Pour activer la recherche de débit binaire 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é à l'aide de DefaultMediaSourceFactory, comme décrit précédemment pour la personnalisation des options de l'extracteur.
Activer la mise en file d'attente asynchrone des tampons
La mise en file d'attente de tampons asynchrone est une amélioration du pipeline de rendu d'ExoPlayer, qui exécute des instances MediaCodec en mode asynchrone et utilise des threads supplémentaires pour planifier le décodage et l'affichage des données. L'activer peut réduire le nombre d'images perdues et de sous-exécutions audio.
La mise en file d'attente asynchrone des tampons est activée par défaut sur les appareils équipés d'Android 12 (niveau d'API 31) ou version ultérieure, et peut être activée manuellement à partir d'Android 6.0 (niveau d'API 23). Envisagez d'activer la fonctionnalité pour les appareils spécifiques sur lesquels vous observez des pertes d'images ou des sous-exécutions audio, en particulier lors de la lecture de contenus protégés par DRM ou à fréquence d'images élevée.
Dans le cas le plus simple, vous devez injecter un DefaultRenderersFactory dans le lecteur 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 new
DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous() aux constructeurs MediaCodecVideoRenderer et MediaCodecAudioRenderer.
Personnaliser les opérations avec ForwardingSimpleBasePlayer
Vous pouvez personnaliser certains comportements d'une instance Player en l'encapsulant dans une sous-classe de ForwardingSimpleBasePlayer. Cette classe vous permet d'intercepter des "opérations" spécifiques, plutôt que d'avoir à implémenter directement des méthodes Player. Cela garantit un comportement cohérent, par exemple, de play(), pause() et setPlayWhenReady(boolean). Il garantit également que tous les changements d'état sont correctement propagés aux instances Player.Listener enregistrées. Pour la plupart des cas d'utilisation de personnalisation, il est préférable d'utiliser ForwardingSimpleBasePlayer plutôt que ForwardingPlayer, qui est plus sujet aux erreurs, en raison de ces garanties de cohérence.
Par exemple, pour ajouter une logique personnalisée lorsque la lecture démarre ou s'arrête :
Kotlin
class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) { override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady) } }
Java
public static final class PlayerWithCustomPlay extends ForwardingSimpleBasePlayer { public PlayerWithCustomPlay(Player player) { super(player); } @Override protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady); } }
Ou pour interdire la commande SEEK_TO_NEXT (et vous assurer que Player.seekToNext est une opération sans effet) :
Kotlin
class PlayerWithoutSeekToNext(player: Player) : ForwardingSimpleBasePlayer(player) { override fun getState(): State { val state = super.getState() return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build() ) .build() } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
Java
public static final class PlayerWithoutSeekToNext extends ForwardingSimpleBasePlayer { public PlayerWithoutSeekToNext(Player player) { super(player); } @Override protected State getState() { State state = super.getState(); return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build()) .build(); } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
Personnalisation de MediaSource
Les exemples précédents injectent des composants personnalisés à utiliser lors de la lecture de tous les objets MediaItem transmis au lecteur. Lorsqu'une personnalisation pré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 suivant 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 listés en haut de cette page pour les cas d'utilisation courants. Un ExoPlayer peut utiliser ces composants, mais peut également être conçu pour utiliser des implémentations personnalisées si des comportements non standards sont requis. Voici quelques cas d'utilisation d'implémentations personnalisées :
Renderer: vous pouvez implémenter unRendererpersonnalisé pour gérer un type de contenu multimédia non compatible avec les implémentations par défaut fournies par la bibliothèque.TrackSelector: l'implémentation d'unTrackSelectorpersonnalisé permet à un développeur d'applications de modifier la façon dont les pistes exposées par unMediaSourcesont sélectionnées pour être utilisées par chacun desRendererdisponibles.LoadControl: l'implémentation d'unLoadControlpersonnalisé permet à un développeur d'applications de modifier la règle 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 classeExtractorpersonnalisée.MediaSource: l'implémentation d'une classeMediaSourcepersonnalisée peut être appropriée si vous souhaitez obtenir des échantillons multimédias à transmettre aux moteurs de rendu de manière personnalisée, ou si vous souhaitez implémenter un comportement de compositionMediaSourcepersonnalisé.MediaSource.Factory: l'implémentation d'unMediaSource.Factorypersonnalisé permet à une application de personnaliser la façon dont unMediaSourceest créé à partir d'unMediaItem.DataSource: le package en amont d'ExoPlayer contient déjà un certain nombre d'implémentationsDataSourcepour différents cas d'utilisation. Vous pouvez implémenter votre propre classeDataSourcepour 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 cache persistant personnalisé.
Lorsque vous créez des composants personnalisés, nous vous recommandons de suivre les conseils ci-dessous :
- Si un composant personnalisé doit signaler des événements à l'application, nous vous recommandons de le faire en utilisant le même modèle que les composants ExoPlayer existants, par exemple en utilisant des classes
EventDispatcherou en transmettant unHandleravec un écouteur au constructeur du composant. - Nous vous recommandons d'utiliser le même modèle pour les composants personnalisés que pour les composants ExoPlayer existants afin de permettre à l'application de les reconfigurer pendant la lecture. Pour ce faire, les composants personnalisés doivent implémenter
PlayerMessage.Targetet recevoir les modifications de configuration dans la méthodehandleMessage. Le code de l'application doit transmettre les modifications de configuration en appelant la méthodecreateMessaged'ExoPlayer, en configurant le message et en l'envoyant au composant à l'aide dePlayerMessage.send. L'envoi de messages à diffuser 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.