Eventos del jugador

Cómo escuchar eventos de reproducción

Los eventos, como cambios de estado y errores de reproducción, se informan a las instancias registradas de Player.Listener. Si deseas registrar un objeto de escucha para recibir esos eventos, sigue estos pasos:

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 tiene métodos predeterminados vacíos, por lo que solo debes implementar los métodos que te interesan. Consulta Javadoc para obtener una descripción completa de los métodos y el momento en que se los llama. Algunos de los métodos más importantes se describen con más detalle a continuación.

Los objetos de escucha tienen la opción de implementar devoluciones de llamada de eventos individuales o una devolución de llamada genérica de onEvents que se llama después de que ocurren uno o más eventos juntos. Consulta Individual callbacks vs onEvents para obtener una explicación de lo que se debería preferir para diferentes casos de uso.

Cambios en el estado de la reproducción

Los cambios en el estado del reproductor se pueden recibir mediante la implementación de onPlaybackStateChanged(@State int state) en un Player.Listener registrado. El reproductor puede estar en uno de los cuatro estados de reproducción:

  • Player.STATE_IDLE: Es el estado inicial, el estado en el que se detiene el reproductor y el momento en que falló la reproducción. El reproductor solo conservará los recursos limitados en este estado.
  • Player.STATE_BUFFERING: El jugador no puede jugar inmediatamente desde su posición actual. Esto ocurre principalmente porque se deben cargar más datos.
  • Player.STATE_READY: El jugador puede reproducir inmediatamente desde su posición actual.
  • Player.STATE_ENDED: El reproductor terminó de reproducir todo el contenido multimedia.

Además de estos estados, el jugador tiene una marca playWhenReady para indicar la intención del usuario de jugar. Los cambios en esta marca se pueden recibir mediante la implementación de onPlayWhenReadyChanged(playWhenReady, @PlayWhenReadyChangeReason int reason).

Un jugador está reproduciendo contenido (es decir, su posición avanza y se presenta el contenido multimedia al usuario) cuando se cumplen las tres condiciones siguientes:

  • El estado del reproductor es Player.STATE_READY.
  • playWhenReady es true.
  • No se suprime la reproducción por un motivo que muestra Player.getPlaybackSuppressionReason.

En lugar de verificar estas propiedades de forma individual, se puede llamar a Player.isPlaying. Se pueden recibir cambios en este estado si implementas 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.
        }
      }
    });

Errores de reproducción

Los errores que causan errores en la reproducción se pueden recibir mediante la implementación de onPlayerError(PlaybackException error) en un Player.Listener registrado. Si se produce un error, se llamará a este método inmediatamente antes de que el estado de reproducción pase a Player.STATE_IDLE. Las reproducciones fallidas o detenidas se pueden volver a intentar llamando a ExoPlayer.prepare.

Ten en cuenta que algunas implementaciones de Player pasan instancias de subclases de PlaybackException para proporcionar información adicional sobre la falla. Por ejemplo, ExoPlayer pasa ExoPlaybackException, que tiene type, rendererIndex y otros campos específicos de ExoPlayer.

En el siguiente ejemplo, se muestra cómo detectar cuándo falló una reproducción debido a un problema de red 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.
          }
        }
      }
    });

Transiciones de playlists

Cada vez que el reproductor cambia a un nuevo elemento multimedia de la lista de reproducción onMediaItemTransition(MediaItem mediaItem, @MediaItemTransitionReason int reason), se llama en objetos Player.Listener registrados. El motivo indica si se trata de una transición automática, una búsqueda (por ejemplo, después de llamar a player.next()), una repetición del mismo elemento o un cambio en la lista de reproducción (por ejemplo, si se quita el elemento que se está reproduciendo en ese momento).

Metadatos

Los metadatos que muestra player.getCurrentMediaMetadata() pueden cambiar por muchos motivos: las transiciones de listas de reproducción, las actualizaciones de metadatos in-stream o la actualización del MediaItem actual durante la reproducción.

Si te interesan los cambios de metadatos (por ejemplo, para actualizar una IU que muestre el título actual), puedes escuchar onMediaMetadataChanged.

Buscando

Llamar a los métodos Player.seekTo da como resultado una serie de devoluciones de llamada a instancias Player.Listener registradas:

  1. onPositionDiscontinuity con reason=DISCONTINUITY_REASON_SEEK. Este es el resultado directo de llamar a Player.seekTo. La devolución de llamada tiene campos PositionInfo para la posición antes y después de la búsqueda.
  2. onPlaybackStateChanged por cualquier cambio de estado inmediato relacionado con la búsqueda. Ten en cuenta que es posible que no se produzca ese cambio.

Devoluciones de llamada individuales en comparación con onEvents

Los objetos de escucha pueden elegir entre implementar devoluciones de llamada individuales, como onIsPlayingChanged(boolean isPlaying), y la devolución de llamada genérica onEvents(Player player, Events events). La devolución de llamada genérica proporciona acceso al objeto Player y especifica el conjunto de events que ocurrieron juntos. Esta devolución de llamada siempre se llama después de las devoluciones de llamada que corresponden a los eventos individuales.

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

Se deben preferir los eventos individuales en los siguientes casos:

  • El objeto de escucha está interesado en los motivos de los cambios. Por ejemplo, los motivos proporcionados para onPlayWhenReadyChanged o onMediaItemTransition
  • El objeto de escucha solo actúa sobre los valores nuevos proporcionados a través de parámetros de devolución de llamada o activa otra cosa que no depende de esos parámetros.
  • La implementación del objeto de escucha prefiere una indicación clara y legible de lo que activó el evento en el nombre del método.
  • El objeto de escucha informa a un sistema de estadísticas que necesita conocer todos los eventos individuales y los cambios de estado.

Se debería preferir el onEvents(Player player, Events events) genérico en los siguientes casos:

  • El objeto de escucha desea activar la misma lógica para varios eventos. Por ejemplo, actualizar una IU para onPlaybackStateChanged y onPlayWhenReadyChanged.
  • El objeto de escucha necesita acceder al objeto Player para activar más eventos, por ejemplo, la búsqueda después de una transición de un elemento multimedia.
  • El objeto de escucha pretende usar varios valores de estado que se informan juntos mediante devoluciones de llamada separadas o en combinación con métodos get Player. Por ejemplo, el uso de Player.getCurrentWindowIndex() con el Timeline proporcionado en onTimelineChanged solo es seguro desde la devolución de llamada onEvents.
  • Al receptor le interesa saber si los eventos ocurrieron juntos de forma lógica. Por ejemplo, onPlaybackStateChanged a STATE_BUFFERING debido a la transición de un elemento multimedia.

En algunos casos, es posible que los objetos de escucha necesiten combinar las devoluciones de llamada individuales con la devolución de llamada genérica de onEvents, por ejemplo, para grabar los motivos de cambio de elementos multimedia con onMediaItemTransition, pero solo actuar una vez que todos los cambios de estado se puedan usar juntos en onEvents.

Espacio en uso: AnalyticsListener

Cuando se usa ExoPlayer, se puede registrar un AnalyticsListener con el jugador llamando a addAnalyticsListener. Las implementaciones de AnalyticsListener pueden escuchar eventos detallados que pueden ser útiles para generar estadísticas y registros. Consulta la página de Analytics para obtener más detalles.

Espacio en uso: EventLogger

EventLogger es un AnalyticsListener que proporciona directamente la biblioteca para fines de registro. Agrega EventLogger a un ExoPlayer para habilitar el registro adicional útil con una sola línea:

Kotlin

player.addAnalyticsListener(EventLogger())

Java

player.addAnalyticsListener(new EventLogger());

Consulta la página de registro de depuración para obtener más detalles.

Cómo activar eventos en posiciones de reproducción especificadas

Algunos casos de uso requieren que se activen eventos en posiciones de reproducción especificadas. Es compatible con PlayerMessage. Se puede crear un PlayerMessage con ExoPlayer.createMessage. La posición de reproducción en la que se debe ejecutar se puede establecer con PlayerMessage.setPosition. Los mensajes se ejecutan en el subproceso de reproducción de forma predeterminada, pero esto se puede personalizar con PlayerMessage.setLooper. Se puede usar PlayerMessage.setDeleteAfterDelivery para controlar si el mensaje se ejecutará cada vez que se encuentre la posición de reproducción especificada (esto puede ocurrir varias veces debido a los modos de búsqueda y repetición) o solo la primera vez. Una vez que PlayerMessage está configurado, se puede programar con 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();