Le modifiche allo stato del player (ad esempio l'avvio della riproduzione, il buffering o gli errori)
attivano eventi che vengono inviati alle istanze Player.Listener registrate. Questi
eventi sono rappresentati da costanti intere e sono definiti da Player.Event
e Player.Events.
Registrare un Player.Listener
Gli eventi del player vengono segnalati alle istanze Player.Listener registrate. Per registrare un listener per ricevere questi eventi:
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);
Se utilizzi Kotlin, puoi anche utilizzare le funzioni di estensione di sospensione fornite dal modulo media3-common-ktx per ascoltare gli eventi utilizzando le coroutine.
In questo caso, non dovrai registrare o annullare la registrazione di un Player.Listener in modo esplicito.
Ascoltare gli eventi di riproduzione utilizzando Player.Listener
Player.Listener ha metodi predefiniti vuoti, quindi devi implementare solo i metodi di tuo interesse. Consulta il Javadoc per una descrizione completa dei
metodi e di quando vengono chiamati. Alcuni dei metodi più importanti sono descritti in modo più dettagliato di seguito.
I listener possono scegliere di implementare i singoli callback degli eventi o un callback onEvents generico chiamato dopo che si sono verificati uno o più eventi contemporaneamente. Consulta la sezione Individual callbacks vs onEvents per una spiegazione di quale
dovrebbe essere preferito per i diversi casi d'uso.
Modifiche dello stato di riproduzione
Le modifiche allo stato del player possono essere ricevute implementando onPlaybackStateChanged(@State int state) in un Player.Listener registrato.
Il player può trovarsi in uno dei quattro stati di riproduzione:
Player.STATE_IDLE: questo è lo stato iniziale, lo stato in cui il player è arrestato e quando la riproduzione non è riuscita. In questo stato, il player manterrà solo risorse limitate.Player.STATE_BUFFERING: il player non è in grado di riprodurre immediatamente dalla posizione corrente. Questo accade principalmente perché è necessario caricare altri dati.Player.STATE_READY: il player è in grado di riprodurre immediatamente dalla posizione corrente.Player.STATE_ENDED: il player ha terminato la riproduzione di tutti i contenuti multimediali.
Oltre a questi stati, il player ha un flag playWhenReady per indicare l'intenzione dell'utente di riprodurre. Le modifiche a questo flag possono essere ricevute implementando onPlayWhenReadyChanged(playWhenReady, @PlayWhenReadyChangeReason int reason).
Un player è in riproduzione (ovvero la sua posizione sta avanzando e i contenuti multimediali vengono presentati all'utente) quando sono soddisfatte tutte e tre le seguenti condizioni:
- Il player è nello stato
Player.STATE_READY playWhenReadyètrue- La riproduzione non viene soppressa per un motivo restituito da
Player.getPlaybackSuppressionReason
Anziché dover controllare queste proprietà singolarmente, è possibile chiamare Player.isPlaying. Le modifiche a questo stato possono essere ricevute implementando 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. } } });
Errori di riproduzione
Gli errori che causano il fallimento della riproduzione possono essere ricevuti implementando onPlayerError(PlaybackException error) in un Player.Listener registrato. Quando si verifica un errore, questo metodo viene chiamato immediatamente prima che lo stato di riproduzione passi a Player.STATE_IDLE. È possibile riprovare a riprodurre le riproduzioni non riuscite o interrotte chiamando ExoPlayer.prepare.
Tieni presente che alcune Player implementazioni passano istanze di sottoclassi di
PlaybackException per fornire ulteriori informazioni sull'errore. Ad
esempio, ExoPlayer passa ExoPlaybackException, che ha type,
rendererIndex, e altri campi specifici di ExoPlayer.
L'esempio seguente mostra come rilevare quando una riproduzione non è riuscita a causa di un problema di rete 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. } } } });
Transizioni delle playlist
Ogni volta che il player passa a un nuovo elemento multimediale nella playlist
onMediaItemTransition(MediaItem mediaItem, @MediaItemTransitionReason int
reason) viene chiamato sugli oggetti Player.Listener registrati. Il motivo indica se si è trattato di una transizione automatica, di una ricerca (ad esempio dopo aver chiamato player.next()), di una ripetizione dello stesso elemento o causata da una modifica della playlist (ad esempio, se l'elemento in riproduzione corrente viene rimosso).
Metadati
I metadati restituiti da player.getCurrentMediaMetadata() possono cambiare per molti motivi: transizioni delle playlist, aggiornamenti dei metadati in-stream o aggiornamento dell'elemento MediaItem corrente durante la riproduzione.
Se ti interessano le modifiche ai metadati, ad esempio per aggiornare un'interfaccia utente che mostra il titolo corrente, puoi ascoltare onMediaMetadataChanged.
Attivazione dello spostamento in corso
La chiamata ai metodi Player.seekTo genera una serie di callback alle istanze Player.Listener registrate:
onPositionDiscontinuityconreason=DISCONTINUITY_REASON_SEEK. Questo è il risultato diretto della chiamata aPlayer.seekTo. Il callback ha campiPositionInfoper la posizione prima e dopo la ricerca.onPlaybackStateChangedcon qualsiasi modifica immediata dello stato correlata alla ricerca. Tieni presente che potrebbe non esserci una modifica di questo tipo.
Callback individuali rispetto a onEvents
I listener possono scegliere tra l'implementazione di callback individuali come
onIsPlayingChanged(boolean isPlaying), e il callback generico onEvents(Player
player, Events events). Il callback generico fornisce l'accesso all'oggetto Player e specifica l'insieme di events che si sono verificati contemporaneamente. Questo callback viene sempre chiamato dopo i callback che corrispondono ai singoli eventi.
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); } }
I singoli eventi devono essere preferiti nei seguenti casi:
- Il listener è interessato ai motivi delle modifiche. Ad esempio, i motivi forniti per
onPlayWhenReadyChangedoonMediaItemTransition. - Il listener agisce solo sui nuovi valori forniti tramite i parametri di callback o attiva qualcos'altro che non dipende dai parametri di callback.
- L'implementazione del listener preferisce un'indicazione chiara e leggibile di ciò che ha attivato l'evento nel nome del metodo.
- Il listener genera report per un sistema di analisi che deve conoscere tutti i singoli eventi e le modifiche dello stato.
Il callback generico onEvents(Player player, Events events) deve essere preferito nei seguenti casi:
- Il listener vuole attivare la stessa logica per più eventi. Ad esempio, aggiornare un'interfaccia utente sia per
onPlaybackStateChangedsia peronPlayWhenReadyChanged. - Il listener deve accedere all'oggetto
Playerper attivare altri eventi, ad esempio la ricerca dopo una transizione di un elemento multimediale. - Il listener intende utilizzare più valori di stato segnalati tramite callback separati o in combinazione con i metodi getter
Player. Ad esempio, l'utilizzo diPlayer.getCurrentWindowIndex()conTimelinefornito inonTimelineChangedè sicuro solo all'interno del callbackonEvents. - Il listener è interessato a sapere se gli eventi si sono verificati logicamente insieme.
Ad esempio,
onPlaybackStateChangedaSTATE_BUFFERINGa causa di una transizione di un elemento multimediale.
In alcuni casi, i listener potrebbero dover combinare i singoli callback con il callback generico onEvents, ad esempio per registrare i motivi della modifica dell'elemento multimediale con onMediaItemTransition, ma agire solo quando tutti i cambiamenti di stato possono essere utilizzati insieme in onEvents.
Ascoltare gli eventi di riproduzione utilizzando le coroutine
In alternativa, puoi avviare una coroutine Kotlin utilizzando Player.listenTo e
specificare il Player.Event pertinente:
Tieni presente che Player.listen e Player.listenTo possono essere chiamati da qualsiasi
thread, mentre la lambda di callback viene sempre richiamata sul thread associato
a Player.getApplicationLooper. Pertanto, è sicuro accedere ai metodi Player e alle proprietà di stato all'interno della lambda di callback anche se la coroutine è stata avviata su un thread diverso.
Modifiche dello stato di riproduzione
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. } } }
Errori di riproduzione
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 } } } }
Callback individuali rispetto a onEvents
Quando ascolti gli eventi del player all'interno di una coroutine, fornisci sempre
l'implementazione per il callback onEvents, anziché un
callback individuale. Puoi scegliere
tra Player.listen e Player.listenTo, a seconda degli eventi
che devono attivare una chiamata della lambda. Tuttavia, le funzioni sono equivalenti:
ascolta
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) } } }
ascolta
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) } } }
Se ti interessano più tipi di eventi, puoi passare un elenco di eventi a
Player.listenTo. La lambda verrà richiamata ogni volta che si verifica uno di questi eventi e puoi esaminare il parametro Events per verificare quali eventi sono stati effettivamente attivati:
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) } }
Poiché queste funzioni operano su onEvents, non forniscono l'accesso agli argomenti temporanei passati ai singoli callback, come il motivo in onMediaItemTransition(..., int reason) o oldPosition in onPositionDiscontinuity(...). Se la tua logica si basa su questi argomenti specifici
(e non sono disponibili come proprietà di stato in Player), devi
utilizzare l'interfaccia Player.Listener standard.
Utilizzare AnalyticsListener
Quando utilizzi ExoPlayer, puoi registrare un AnalyticsListener con il player
chiamando addAnalyticsListener. Le implementazioni di AnalyticsListener sono in grado di ascoltare eventi dettagliati che possono essere utili per l'analisi e la registrazione. Per maggiori dettagli, consulta la pagina relativa all'analisi.
Utilizzare EventLogger
EventLogger è un AnalyticsListener fornito direttamente dalla libreria per la registrazione. Aggiungi EventLogger a un ExoPlayer per attivare la registrazione aggiuntiva utile con una sola riga:
Kotlin
player.addAnalyticsListener(EventLogger())
Java
player.addAnalyticsListener(new EventLogger());
Per maggiori dettagli, consulta la pagina relativa alla registrazione di debug.
Attivare gli eventi in posizioni di riproduzione specifiche
Alcuni casi d'uso richiedono l'attivazione di eventi in posizioni di riproduzione specifiche. Questa funzionalità è supportata tramite PlayerMessage. È possibile creare un PlayerMessage utilizzando ExoPlayer.createMessage. La posizione di riproduzione in cui deve essere eseguita può essere impostata utilizzando PlayerMessage.setPosition. Per impostazione predefinita, i messaggi vengono eseguiti sul thread di riproduzione, ma questa impostazione può essere personalizzata utilizzando PlayerMessage.setLooper. Puoi utilizzare PlayerMessage.setDeleteAfterDelivery per controllare se il messaggio verrà eseguito ogni volta che viene rilevata la posizione di riproduzione specificata (ciò può accadere più volte a causa delle modalità di ricerca e ripetizione) o solo la prima volta. Una volta configurato PlayerMessage,
può essere pianificato utilizzando 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();