Eventi giocatore

Ascolto degli eventi di riproduzione

Gli eventi, come le modifiche dello stato e gli errori di riproduzione, 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);

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

Gli ascoltatori possono scegliere tra l'implementazione di callback di eventi individuali o un callback onEvents generico chiamato dopo che si sono verificati uno o più eventi insieme. Consulta Individual callbacks vs onEvents per una spiegazione di quale deve 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: è lo stato iniziale, lo stato in cui il giocatore è in pausa e quando la riproduzione non è riuscita. In questo stato, il giocatore conterrà solo risorse limitate.
  • Player.STATE_BUFFERING: Il lettore non è in grado di riprodurre immediatamente dalla posizione attuale. Ciò accade principalmente perché è necessario caricare più dati.
  • Player.STATE_READY: Il lettore è in grado di riprodurre immediatamente dalla posizione corrente.
  • Player.STATE_ENDED: Il lettore ha terminato la riproduzione di tutti i contenuti multimediali.

Oltre a questi stati, il giocatore ha un flag playWhenReady per indicare 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 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 è 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. Le riproduzioni non riuscite o interrotte possono essere riprovate chiamando il numero ExoPlayer.prepare.

Tieni presente che alcune implementazioni di Player passano istanze di sottoclassi di PlaybackException per fornire ulteriori informazioni sull'errore. Ad esempio, ExoPlayer passa ExoPlaybackException, che contiene type, rendererIndex e altri campi specifici di ExoPlayer.

L'esempio seguente mostra come rilevare quando la 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 tra le 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 di una modifica della playlist (ad esempio, se l'elemento attualmente in riproduzione viene rimosso).

Metadati

I metadati restituiti da player.getCurrentMediaMetadata() possono cambiare per molti motivi: transizioni delle playlist, aggiornamenti dei metadati in-stream o aggiornamento di MediaItem corrente a metà riproduzione.

Se ti interessano le modifiche ai metadati, ad esempio per aggiornare un'interfaccia utente che mostra il titolo attuale, puoi ascoltare onMediaMetadataChanged.

Attivazione dello spostamento in corso

La chiamata ai 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 cambiamento di stato immediato correlato alla ricerca. Tieni presente che potrebbe non esserci alcuna modifica.

Callback individuali e onEvents

Gli ascoltatori 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 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);
  }
}

Gli eventi individuali sono preferibili nei seguenti casi:

  • L'ascoltatore è interessato ai motivi dei cambiamenti. 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 per un sistema di analisi che deve conoscere tutti i singoli eventi e le modifiche dello stato.

Il segnaposto generico onEvents(Player player, Events events) è preferibile nei seguenti casi:

  • L'ascoltatore vuole attivare la stessa logica per più eventi. Ad esempio, l'aggiornamento di un'interfaccia utente 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, l'utilizzo di Player.getCurrentWindowIndex() con Timeline fornito in onTimelineChanged è sicuro solo dall'interno del callback onEvents.
  • Il listener è interessato a sapere se gli eventi si sono verificati logicamente insieme. Ad esempio, da onPlaybackStateChanged a STATE_BUFFERING a causa di una transizione di un elemento multimediale.

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

In uso: AnalyticsListener

Quando si utilizza ExoPlayer, è possibile registrare un AnalyticsListener con il giocatore chiamando addAnalyticsListener. Le implementazioni di AnalyticsListener sono in grado di ascoltare eventi dettagliati che possono essere utili per l'analisi e la registrazione dei log. Per maggiori dettagli, consulta la pagina di analisi.

In uso: EventLogger

EventLogger è un AnalyticsListener fornito direttamente dalla libreria per scopi di logging. Aggiungi EventLogger a un ExoPlayer per attivare un logging aggiuntivo utile con una sola riga:

Kotlin

player.addAnalyticsListener(EventLogger())

Java

player.addAnalyticsListener(new EventLogger());

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

Attivazione di eventi in posizioni di riproduzione specificate

Alcuni casi d'uso richiedono l'attivazione di eventi in posizioni di riproduzione specifiche. Questa operazione è supportata tramite PlayerMessage. È possibile creare un PlayerMessage utilizzando ExoPlayer.createMessage. La posizione di riproduzione in cui deve essere eseguito può essere impostata utilizzando PlayerMessage.setPosition. I messaggi vengono eseguiti sul thread di riproduzione per impostazione predefinita, ma possono essere personalizzati 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 (ciò può verificarsi 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();