Les modifications apportées à l'état du lecteur (par exemple, le démarrage de la lecture, la mise en mémoire tampon ou les erreurs)
déclenchent des événements qui sont envoyés aux instances Player.Listener enregistrées. Ces
événements sont représentés par des constantes entières et sont définis par Player.Event
et Player.Events.
Enregistrer un Player.Listener
Les événements du lecteur sont signalés aux instances Player.Listener enregistrées. Pour enregistrer un écouteur afin de recevoir ces événements :
Kotlin
// Add a listener to receive events from the player. player.addListener(listener)
Java
// Add a listener to receive events from the player. player.addListener(listener);
Si vous utilisez Kotlin, vous pouvez également utiliser les fonctions d'extension de suspension fournies par le module media3-common-ktx pour écouter les événements à l'aide de coroutines.
Dans ce cas, vous n'aurez pas besoin d'enregistrer ni de désenregistrer explicitement un Player.Listener.
Écouter les événements de lecture à l'aide de Player.Listener
Player.Listener comporte des méthodes par défaut vides. Vous n'avez donc besoin d'implémenter que les méthodes qui vous intéressent. Consultez la documentation Javadoc pour obtenir une description complète des
méthodes et savoir quand elles sont appelées. Certaines des méthodes les plus importantes sont décrites plus en détail ci-dessous.
Les écouteurs peuvent choisir d'implémenter des rappels d'événements individuels ou un rappel onEvents générique qui est appelé après qu'un ou plusieurs événements se sont produits ensemble. Consultez la section Individual callbacks vs onEvents pour savoir quelle option
privilégier pour différents cas d'utilisation.
Changements d'état de la lecture
Les modifications de l'état du lecteur peuvent être reçues en implémentant onPlaybackStateChanged(@State int state) dans un Player.Listener enregistré.
Les états de lecture du lecteur sont au nombre de quatre :
Player.STATE_IDLE: il s'agit de l'état initial, de l'état lorsque le lecteur est arrêté et de l'état lorsque la lecture a échoué. Dans cet état, le lecteur ne détient que des ressources limitées.Player.STATE_BUFFERING: le lecteur ne peut pas lire immédiatement à partir de sa position actuelle. Cela se produit principalement parce que davantage de données doivent être chargées.Player.STATE_READY: le lecteur peut démarrer immédiatement la lecture depuis la position actuelle.Player.STATE_ENDED: le lecteur a terminé la lecture de tous les contenus multimédias.
En plus de ces états, le lecteur dispose d'un indicateur playWhenReady pour indiquer l'intention de l'utilisateur de lire. Les modifications apportées à cet indicateur peuvent être reçues en implémentant onPlayWhenReadyChanged(playWhenReady, @PlayWhenReadyChangeReason int reason).
Un lecteur est en cours de lecture (c'est-à-dire que sa position avance et que le contenu multimédia est présenté à l'utilisateur) lorsque les trois conditions suivantes sont remplies :
- Le lecteur est dans l'état
Player.STATE_READY. playWhenReadyesttrue.- La lecture n'est pas supprimée pour une raison renvoyée par
Player.getPlaybackSuppressionReason.
Au lieu de devoir vérifier ces propriétés individuellement, vous pouvez appeler Player.isPlaying. Les modifications apportées à cet état peuvent être reçues en implémentant onIsPlayingChanged(boolean isPlaying) :
Kotlin
player.addListener( object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { if (isPlaying) { // Active playback. } else { // Not playing because playback is paused, ended, suppressed, or the player // is buffering, stopped or failed. Check player.playWhenReady, // player.playbackState, player.playbackSuppressionReason and // player.playerError for details. } } } )
Java
player.addListener( new Player.Listener() { @Override public void onIsPlayingChanged(boolean isPlaying) { if (isPlaying) { // Active playback. } else { // Not playing because playback is paused, ended, suppressed, or the player // is buffering, stopped or failed. Check player.getPlayWhenReady, // player.getPlaybackState, player.getPlaybackSuppressionReason and // player.getPlaybackError for details. } } });
Erreurs de lecture
Les erreurs qui entraînent l'échec de la lecture peuvent être reçues en implémentant onPlayerError(PlaybackException error) dans un Player.Listener enregistré. En cas d'échec, cette méthode est appelée immédiatement avant que l'état de lecture ne passe à Player.STATE_IDLE. Vous pouvez réessayer les lectures ayant échoué ou ayant été arrêtées en appelant ExoPlayer.prepare.
Notez que certaines Player implémentations transmettent des instances de sous-classes de
PlaybackException pour fournir des informations supplémentaires sur l'échec. Par
exemple, ExoPlayer transmet ExoPlaybackException, qui comporte type,
rendererIndex, et d'autres champs spécifiques à ExoPlayer.
L'exemple suivant montre comment détecter l'échec d'une lecture en raison d'un problème de réseau HTTP :
Kotlin
player.addListener( object : Player.Listener { override fun onPlayerError(error: PlaybackException) { val cause = error.cause if (cause is HttpDataSourceException) { // An HTTP error occurred. val httpError = cause // It's possible to find out more about the error both by casting and by querying // the cause. if (httpError is InvalidResponseCodeException) { // Cast to InvalidResponseCodeException and retrieve the response code, message // and headers. } else { // Try calling httpError.getCause() to retrieve the underlying cause, although // note that it may be null. } } } } )
Java
player.addListener( new Player.Listener() { @Override public void onPlayerError(PlaybackException error) { @Nullable Throwable cause = error.getCause(); if (cause instanceof HttpDataSourceException) { // An HTTP error occurred. HttpDataSourceException httpError = (HttpDataSourceException) cause; // It's possible to find out more about the error both by casting and by querying // the cause. if (httpError instanceof HttpDataSource.InvalidResponseCodeException) { // Cast to InvalidResponseCodeException and retrieve the response code, message // and headers. } else { // Try calling httpError.getCause() to retrieve the underlying cause, although // note that it may be null. } } } });
Transitions de playlists
Chaque fois que le lecteur passe à un nouvel élément multimédia dans la playlist
onMediaItemTransition(MediaItem mediaItem, @MediaItemTransitionReason int
reason) est appelé sur les objets Player.Listener enregistrés. La raison indique s'il s'agit d'une transition automatique, d'une recherche (par exemple, après avoir appelé player.next()), d'une répétition du même élément ou d'une modification de la playlist (par exemple, si l'élément en cours de lecture est supprimé).
Métadonnées
Les métadonnées renvoyées par player.getCurrentMediaMetadata() peuvent changer pour de nombreuses raisons : transitions de playlists, mises à jour des métadonnées InStream ou mise à jour de l'élément MediaItem actuel en cours de lecture.
Si les modifications des métadonnées vous intéressent, par exemple pour mettre à jour une interface utilisateur qui affiche le titre actuel, vous pouvez écouter onMediaMetadataChanged.
Recherche…
L'appel des méthodes Player.seekTo entraîne une série de rappels aux instances Player.Listener enregistrées :
onPositionDiscontinuityavecreason=DISCONTINUITY_REASON_SEEK. Il s'agit du résultat direct de l'appel dePlayer.seekTo. Le rappel comporte des champsPositionInfopour la position avant et après la recherche.onPlaybackStateChangedavec toute modification d'état immédiate liée à la recherche. Notez qu'il peut ne pas y avoir de modification.
Rappels individuels par rapport à onEvents
Les écouteurs peuvent choisir d'implémenter des rappels individuels tels que
onIsPlayingChanged(boolean isPlaying), et le rappel générique onEvents(Player
player, Events events). Le rappel générique permet d'accéder à l'objet Player et spécifie l'ensemble des events qui se sont produits ensemble. Ce rappel est toujours appelé après les rappels qui correspondent aux événements individuels.
Kotlin
override fun onEvents(player: Player, events: Player.Events) { if ( events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED) ) { uiModule.updateUi(player) } }
Java
@Override public void onEvents(Player player, Events events) { if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) { uiModule.updateUi(player); } }
Les événements individuels doivent être privilégiés dans les cas suivants :
- L'écouteur s'intéresse aux raisons des modifications. Par exemple, les raisons fournies pour
onPlayWhenReadyChangedouonMediaItemTransition. - L'écouteur n'agit que sur les nouvelles valeurs fournies via les paramètres de rappel ou déclenche autre chose qui ne dépend pas des paramètres de rappel.
- L'implémentation de l'écouteur préfère une indication claire et lisible de ce qui a déclenché l'événement dans le nom de la méthode.
- L'écouteur signale à un système d'analyse qui doit connaître tous les événements individuels et les changements d'état.
Le rappel générique onEvents(Player player, Events events) doit être privilégié dans les cas suivants :
- L'écouteur souhaite déclencher la même logique pour plusieurs événements. Par exemple, la mise à jour d'une interface utilisateur pour
onPlaybackStateChangedetonPlayWhenReadyChanged. - L'écouteur doit accéder à l'objet
Playerpour déclencher d'autres événements, par exemple une recherche après une transition d'élément multimédia. - L'écouteur a l'intention d'utiliser plusieurs valeurs d'état signalées via des rappels distincts, ou en combinaison avec des méthodes getter
Player. Par exemple, l'utilisation dePlayer.getCurrentWindowIndex()avec leTimelinefourni dansonTimelineChangedn'est sécurisée que depuis leonEventsrappel. - L'écouteur souhaite savoir si les événements se sont produits ensemble de manière logique.
Par exemple,
onPlaybackStateChangedàSTATE_BUFFERINGen raison d'une transition d'élément multimédia.
Dans certains cas, les écouteurs peuvent avoir besoin de combiner les rappels individuels avec le rappel générique onEvents, par exemple pour enregistrer les raisons de modification des éléments multimédias avec onMediaItemTransition, mais n'agir qu'une fois que toutes les modifications d'état peuvent être utilisées ensemble dans onEvents.
Écouter les événements de lecture à l'aide de coroutines
Vous pouvez également lancer une coroutine Kotlin à l'aide de Player.listenTo et
spécifier le Player.Event approprié :
Notez que Player.listen et Player.listenTo peuvent être appelés à partir de n'importe quel
thread, tandis que le lambda de rappel est toujours appelé sur le thread associé
à Player.getApplicationLooper. Par conséquent, il est sûr d'accéder aux méthodes Player et aux propriétés d'état dans le lambda de rappel, même si la coroutine a été lancée sur un autre thread.
Changements d'état de la lecture
coroutineScope.launch { player.listenTo(Player.EVENT_IS_PLAYING_CHANGED) { // `Player` is a receiver scope for this trailing lambda if (isPlaying) { // Active playback. } else { // Not playing. } } }
Erreurs de lecture
coroutineScope.launch { player.listenTo(Player.EVENT_PLAYER_ERROR) { val error = playerError ?: return@listenTo val cause = error.cause if (cause is HttpDataSourceException) { // An HTTP error occurred. if (cause is InvalidResponseCodeException) { // Retrieve the response code, message and headers } else { // Try calling cause.cause to retrieve the underlying cause } } } }
Rappels individuels par rapport à onEvents
Lorsque vous écoutez des événements de lecteur dans une coroutine, vous fournissez toujours
l'implémentation du rappel onEvents, par opposition à un
rappel individuel. Vous pouvez choisir
entre Player.listen et Player.listenTo, en fonction des événements
qui doivent déclencher un appel de votre lambda. Les fonctions sont par ailleurs équivalentes :
écouter
coroutineScope.launch { player.listen { events -> // `Player` is a receiver scope for this trailing lambda if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) { // Access the player state directly from the receiver updateUi(playbackState) } if (events.contains(Player.EVENT_PLAYER_ERROR)) { // Access the error directly from the player handleError(playerError) } } }
listenTo
coroutineScope.launch { player.listenTo(Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYER_ERROR) { events -> // `Player` is a receiver scope for this trailing lambda if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) { // Access the player state directly from the receiver updateUi(playbackState) } if (events.contains(Player.EVENT_PLAYER_ERROR)) { // Access the error directly from the player handleError(playerError) } } }
Si plusieurs types d'événements vous intéressent, vous pouvez transmettre une liste d'événements à
Player.listenTo. Votre lambda sera appelé chaque fois que l'un de ces événements se produira, et vous pourrez inspecter le paramètre Events pour vérifier quels événements ont réellement été déclenchés :
coroutineScope.launch { player.listenTo(Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYER_ERROR) { events -> // Unclear which event got triggered without querying `events` parameter // The following function will fire whenever either one is caught updateUiAndHandleError(playbackState, playerError) } }
Étant donné que ces fonctions fonctionnent sur onEvents, elles ne permettent pas d'accéder aux arguments transitoires transmis aux rappels individuels, tels que la raison dans onMediaItemTransition(..., int reason) ou oldPosition dans onPositionDiscontinuity(...). Si votre logique repose sur ces arguments spécifiques
(et qu'ils ne sont pas disponibles en tant que propriétés d'état sur le Player), vous devez
utiliser l'interface Player.Listener standard.
Utiliser AnalyticsListener
Lorsque vous utilisez ExoPlayer, un AnalyticsListener peut être enregistré auprès du lecteur
en appelant addAnalyticsListener. Les implémentations AnalyticsListener peuvent écouter des événements détaillés qui peuvent être utiles à des fins d'analyse et de journalisation. Pour en savoir plus, consultez la page sur l'analyse.
Utiliser EventLogger
EventLogger est un AnalyticsListener fourni directement par la bibliothèque à des fins de journalisation. Ajoutez EventLogger à un ExoPlayer pour activer une journalisation supplémentaire utile en une seule ligne :
Kotlin
player.addAnalyticsListener(EventLogger())
Java
player.addAnalyticsListener(new EventLogger());
Pour en savoir plus, consultez la page sur la journalisation de débogage.
Déclencher des événements à des positions de lecture spécifiées
Certains cas d'utilisation nécessitent de déclencher des événements à des positions de lecture spécifiées. Cette fonctionnalité est compatible avec PlayerMessage. Un PlayerMessage peut être créé à l'aide de ExoPlayer.createMessage. La position de lecture à laquelle il doit être exécuté peut être définie à l'aide de PlayerMessage.setPosition. Les messages sont exécutés sur le thread de lecture par défaut, mais ce comportement peut être personnalisé à l'aide de PlayerMessage.setLooper. PlayerMessage.setDeleteAfterDelivery permet de contrôler si le message sera exécuté chaque fois que la position de lecture spécifiée est rencontrée (cela peut se produire plusieurs fois en raison des modes de recherche et de répétition) ou uniquement la première fois. Une fois le PlayerMessage configuré,
il peut être planifié à l'aide de PlayerMessage.send.
Kotlin
player .createMessage { messageType: Int, payload: Any? -> } .setLooper(Looper.getMainLooper()) .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 120000) .setPayload(customPayloadData) .setDeleteAfterDelivery(false) .send()
Java
player .createMessage( (messageType, payload) -> { // Do something at the specified playback position. }) .setLooper(Looper.getMainLooper()) .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 120_000) .setPayload(customPayloadData) .setDeleteAfterDelivery(false) .send();