Personalizações da IU

O Media3 oferece um PlayerView padrão com algumas opções de personalização. Para qualquer outra personalização, os desenvolvedores de apps precisam implementar os próprios componentes de interface.

Práticas recomendadas

Ao implementar uma interface de mídia que se conecta a um Player do Media3 (por exemplo, ExoPlayer, MediaController ou uma implementação personalizada de Player), é recomendável que os apps sigam estas práticas recomendadas para ter a melhor experiência de interface.

Botão "Reproduzir/pausar"

O botão de reprodução e pausa não corresponde diretamente ao estado de um único jogador. Por exemplo, o usuário precisa conseguir reiniciar a reprodução depois que ela terminar ou falhar, mesmo que o player não esteja pausado.

Para simplificar a implementação, o Media3 oferece métodos úteis para decidir qual botão mostrar (Util.shouldShowPlayButton) e processar pressionamentos de botão (Util.handlePlayPauseButtonAction):

Kotlin

val shouldShowPlayButton: Boolean = Util.shouldShowPlayButton(player)
playPauseButton.setImageDrawable(if (shouldShowPlayButton) playDrawable else pauseDrawable)
playPauseButton.setOnClickListener { Util.handlePlayPauseButtonAction(player) }

Java

boolean shouldShowPlayButton = Util.shouldShowPlayButton(player);
playPauseButton.setImageDrawable(shouldShowPlayButton ? playDrawable : pauseDrawable);
playPauseButton.setOnClickListener(view -> Util.handlePlayPauseButtonAction(player));

Detectar atualizações de estado

O componente da interface precisa adicionar um Player.Listener para ser informado sobre mudanças de estado que exigem uma atualização correspondente da interface. Consulte Ouvir eventos de reprodução para mais detalhes.

A atualização da interface pode ser cara, e vários eventos de jogadores geralmente chegam juntos. Para evitar a atualização da interface com muita frequência em um curto período, geralmente é melhor ouvir apenas onEvents e acionar as atualizações da interface:

Kotlin

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

Java

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

Processar comandos disponíveis

Um componente de interface geral que pode precisar trabalhar com diferentes implementações de Player precisa verificar os comandos do player disponíveis para mostrar ou ocultar botões e evitar chamar métodos sem suporte:

Kotlin

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

Java

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

Obturador do primeiro frame e exibição de imagem

Quando um componente da interface exibe vídeos ou imagens, ele normalmente usa uma visualização de obturador de marcador de posição até que o primeiro frame ou imagem real esteja disponível. Além disso, a reprodução de vídeo e imagem mista exige que a visualização de imagem seja ocultada e mostrada nos momentos adequados.

Um padrão comum para processar essas atualizações é detectar Player.Listener.onEvents para qualquer mudança nas faixas selecionadas (EVENT_TRACKS_CHANGED) e quando o primeiro frame do vídeo foi renderizado (EVENT_RENDERED_FIRST_FRAME), além de ImageOutput.onImageAvailable para quando uma nova imagem estiver disponível:

Kotlin

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.
}

Java

@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.
}