Personalizaciones de la IU

Media3 proporciona un PlayerView predeterminado que ofrece algunas opciones de personalización.

Cómo anular elementos de diseño

PlayerView usa PlayerControlView para mostrar los controles de reproducción y la barra de progreso. Los elementos de diseño que usa PlayerControlView se pueden anular con elementos de diseño que tengan los mismos nombres definidos en tu aplicación. Consulta la documentación de PlayerControlView para obtener una lista de elementos de diseño de control que se pueden anular.

Para cualquier otra personalización, se espera que los desarrolladores de apps implementen sus propios componentes de IU. Sin embargo, aquí tienes algunas prácticas recomendadas que pueden ayudarte a comenzar.

Prácticas recomendadas

Cuando se implementa una IU multimedia que se conecta a un Player de Media3 (por ejemplo, ExoPlayer, MediaController o una implementación personalizada de Player), se recomienda que las apps sigan estas prácticas recomendadas para obtener la mejor experiencia de IU.

Botón Reproducir/Pausar

El botón de reproducción y pausa no corresponde directamente a un estado de un solo jugador. Por ejemplo, un usuario debe poder reiniciar la reproducción después de que finalice o falle, incluso si el reproductor no está pausado.

Para simplificar la implementación, Media3 proporciona métodos útiles para decidir qué botón mostrar (Util.shouldShowPlayButton) y controlar las presiones de botones (Util.handlePlayPauseButtonAction):

val shouldShowPlayButton: Boolean = Util.shouldShowPlayButton(player)
playPauseButton.setImageDrawable(if (shouldShowPlayButton) playDrawable else pauseDrawable)
playPauseButton.setOnClickListener { Util.handlePlayPauseButtonAction(player) }
boolean shouldShowPlayButton = Util.shouldShowPlayButton(player);
playPauseButton.setImageDrawable(shouldShowPlayButton ? playDrawable : pauseDrawable);
playPauseButton.setOnClickListener(view -> Util.handlePlayPauseButtonAction(player));

Escucha las actualizaciones de estado

El componente de la IU debe agregar un Player.Listener para recibir información sobre los cambios de estado que requieren una actualización de la IU correspondiente. Consulta Cómo escuchar eventos de reproducción para obtener más información.

La actualización de la IU puede ser costosa y, a menudo, llegan varios eventos de jugadores juntos. Para evitar actualizar la IU con demasiada frecuencia en un período breve, por lo general, es mejor escuchar solo onEvents y activar las actualizaciones de la IU desde allí:

player.addListener(object : Player.Listener{
  override fun onEvents(player: Player, events: Player.Events){
    if (events.containsAny(
        Player.EVENT_PLAY_WHEN_READY_CHANGED,
        Player.EVENT_PLAYBACK_STATE_CHANGED,
        Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED)) {
      updatePlayPauseButton()
    }
    if (events.containsAny(Player.EVENT_REPEAT_MODE_CHANGED)) {
      updateRepeatModeButton()
    }
  }
})
player.addListener(new Player.Listener() {
  @Override
  public void onEvents(Player player, Player.Events events) {
    if (events.containsAny(
        Player.EVENT_PLAY_WHEN_READY_CHANGED,
        Player.EVENT_PLAYBACK_STATE_CHANGED,
        Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED)) {
      updatePlayPauseButton();
    }
    if (events.containsAny(Player.EVENT_REPEAT_MODE_CHANGED)) {
      updateRepeatModeButton();
    }
  }
});

Controla los comandos disponibles

Un componente de IU de uso general que pueda necesitar trabajar con diferentes implementaciones de Player debe verificar los comandos de jugador disponibles para mostrar o ocultar botones y evitar llamar a métodos no compatibles:

nextButton.isEnabled = player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT)
nextButton.setEnabled(player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT));

Obturador del primer fotograma y visualización de imágenes

Cuando un componente de la IU muestra imágenes o videos, suele usar una vista del obturador del marcador de posición hasta que el primer fotograma o la primera imagen reales estén disponibles. Además, la reproducción de imágenes y videos mezclados requiere ocultar y mostrar la vista de imagen en los momentos adecuados.

Un patrón común para controlar estas actualizaciones es escuchar Player.Listener.onEvents() para detectar cualquier cambio en las pistas seleccionadas (EVENT_TRACKS_CHANGED) y cuando se renderiza el primer fotograma de video (EVENT_RENDERED_FIRST_FRAME), así como ImageOutput.onImageAvailable() para cuando haya una imagen nueva disponible:

override fun onEvents(player: Player, events: Player.Events) {
  if (events.contains(Player.EVENT_TRACKS_CHANGED)) {
    // If no video or image track: show shutter, hide image view.
    // Otherwise: do nothing to wait for first frame or image.
  }
  if (events.contains(Player.EVENT_RENDERED_FIRST_FRAME)) {
    // Hide shutter, hide image view.
  }
}

override fun onImageAvailable(presentationTimeUs: Long, bitmap: Bitmap) {
  // Show shutter, set image and show image view.
}
@Override
public void onEvents(Player player, Events events) {
  if (events.contains(Player.EVENT_TRACKS_CHANGED)) {
    // If no video or image track: show shutter, hide image view.
    // Otherwise: do nothing to wait for first frame or image.
  }
  if (events.contains(Player.EVENT_RENDERED_FIRST_FRAME)) {
    // Hide shutter, hide image view.
  }
}

@Override
public void onImageAvailable(long presentationTimeUs, Bitmap bitmap) {
  // Show shutter, set image and show image view.
}