Eventi giocatore

Ascolto di eventi di riproduzione

Gli eventi, come le variazioni di stato e gli errori di riproduzione, vengono segnalati alle istanze Player.Listener registrate. Per registrare un ascoltatore che riceva 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);

Player.Listener ha metodi predefiniti vuoti, quindi devi implementare solo i metodi che ti interessano. Consulta Javadoc per una descrizione completa dei metodi e quando vengono chiamati. Alcuni dei metodi più importanti sono descritti più dettagliatamente di seguito.

I listener possono scegliere tra l'implementazione di callback di singoli eventi o un callback onEvents generico che viene chiamato dopo che si verificano uno o più eventi insieme. Consulta la pagina Individual callbacks vs onEvents per una spiegazione di quali soluzioni dovrebbero essere preferite 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ò avere uno dei quattro stati di riproduzione disponibili:

  • Player.STATE_IDLE: questo è lo stato iniziale, lo stato in cui il player viene interrotto e quando la riproduzione non è riuscita. In questo stato, il player conserverà solo risorse limitate.
  • Player.STATE_BUFFERING: il player non è in grado di riprodurre immediatamente dalla posizione corrente. Ciò avviene principalmente perché è necessario caricare più dati.
  • Player.STATE_READY: il player è in grado di riprodurre immediatamente dalla sua 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 che indica l'intenzione dell'utente di giocare. 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 il contenuto multimediale viene presentato 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é controllare singolarmente queste proprietà, è 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 la mancata riuscita della riproduzione possono essere ricevuti implementando onPlayerError(PlaybackException error) in un Player.Listener registrato. In caso di errore, questo metodo viene chiamato subito prima che lo stato di riproduzione passi a Player.STATE_IDLE. È possibile riprovare a riprodurre le riproduzioni non riuscite o interrotte chiamando il numero ExoPlayer.prepare.

Tieni presente che alcune implementazioni Player superano le istanze delle 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 richiamato sugli oggetti Player.Listener registrati. Il motivo indica se si tratta di una transizione automatica, una ricerca (ad esempio dopo la chiamata a player.next()), una ripetizione dello stesso elemento o una modifica di una playlist (ad esempio, se l'elemento attualmente in riproduzione viene rimosso).

Metadati

I metadati restituiti da player.getCurrentMediaMetadata() possono cambiare per diversi motivi: transizioni alle playlist, aggiornamenti dei metadati in-stream o aggiornamento dell'attuale riproduzione di MediaItem durante la riproduzione.

Se vuoi apportare modifiche ai metadati, ad esempio per aggiornare una UI che mostra il titolo corrente, puoi ascoltare onMediaMetadataChanged.

Attivazione dello spostamento in corso

La chiamata dei metodi Player.seekTo genera una serie di callback alle istanze Player.Listener registrate:

  1. onPositionDiscontinuity con reason=DISCONTINUITY_REASON_SEEK. Questo è il risultato diretto della chiamata a Player.seekTo. Il callback ha PositionInfo campi per la posizione prima e dopo la ricerca.
  2. onPlaybackStateChanged con qualsiasi modifica immediata dello stato relativa alla ricerca. Tieni presente che questo cambiamento potrebbe non essere presente.

Confronto tra chiamate individuali e onEvents

I listener possono scegliere tra l'implementazione di singoli callback come onIsPlayingChanged(boolean isPlaying) e il callback onEvents(Player player, Events events) generico. Il callback generico consente di accedere all'oggetto Player e specifica il set di events che si sono verificati insieme. Questo callback viene sempre chiamato dopo i callback corrispondenti 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);
  }
}

È consigliabile preferire i singoli eventi nei seguenti casi:

  • L'ascoltatore è interessato ai motivi delle modifiche. Ad esempio, i motivi forniti per onPlayWhenReadyChanged o onMediaItemTransition.
  • 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 a un sistema di analisi che deve conoscere tutti i singoli eventi e le modifiche dello stato.

Il valore onEvents(Player player, Events events) generico dovrebbe essere preferibile nei seguenti casi:

  • Il listener vuole attivare la stessa logica per più eventi. Ad esempio, l'aggiornamento di una UI sia per onPlaybackStateChanged sia per onPlayWhenReadyChanged.
  • Il listener deve accedere all'oggetto Player per attivare ulteriori eventi, ad esempio la ricerca dopo la transizione di un elemento multimediale.
  • Il listener intende utilizzare più valori di stato segnalati tramite callback separati insieme o in combinazione con i metodi getter Player. Ad esempio, puoi usare Player.getCurrentWindowIndex() con Timeline fornito in onTimelineChanged solo all'interno del callback onEvents.
  • Il listener vuole sapere se gli eventi si sono verificati insieme logicamente. Ad esempio, onPlaybackStateChanged in STATE_BUFFERING a causa della transizione di un elemento multimediale.

In alcuni casi, i listener potrebbero dover combinare i singoli callback con il callback onEvents generico, ad esempio per registrare i motivi della modifica degli elementi multimediali con onMediaItemTransition, ma agire solo quando tutte le modifiche di stato possono essere utilizzate insieme in onEvents.

In uso: AnalyticsListener

Quando utilizzi ExoPlayer, è possibile registrare un AnalyticsListener con il player chiamando addAnalyticsListener. Le implementazioni AnalyticsListener sono in grado di ascoltare eventi dettagliati che possono essere utili per scopi di analisi e logging. Per ulteriori dettagli, consulta la pagina dei dati e analisi.

In uso: EventLogger

EventLogger è un AnalyticsListener fornito direttamente dalla libreria ai fini del logging. Aggiungi EventLogger a un ExoPlayer per consentire log aggiuntivi utili con una sola riga:

Kotlin

player.addAnalyticsListener(EventLogger())

Java

player.addAnalyticsListener(new EventLogger());

Per ulteriori dettagli, consulta la pagina del logging di debug.

Attivazione di eventi in posizioni di riproduzione specificate

Alcuni casi d'uso richiedono l'attivazione di eventi in posizioni di riproduzione specificate. Questa operazione è supportata utilizzando 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 nel thread di riproduzione, ma è possibile personalizzarli utilizzando PlayerMessage.setLooper. PlayerMessage.setDeleteAfterDelivery può essere utilizzato per controllare se il messaggio verrà eseguito ogni volta che viene raggiunta la posizione di riproduzione specificata (questo può accadere più volte a causa delle modalità di ricerca e ripetizione) o solo la prima volta. Una volta configurato PlayerMessage, puoi pianificarlo 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();