Personalizações da IU

O Media3 oferece um PlayerView padrão que oferece algumas opções de personalização.

Substituir drawables

PlayerView usa PlayerControlView para mostrar os controles de mídia e a barra de progresso. Os drawables usados por PlayerControlView podem ser substituídos por drawables com os mesmos nomes definidos no aplicativo. Consulte a documentação do PlayerControlView para ver uma lista de drawables de controle que podem ser substituídos.

Para qualquer outra personalização, os desenvolvedores de apps precisam implementar os próprios componentes de interface. No entanto, aqui estão algumas práticas recomendadas que podem ajudar você a começar.

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), recomendamos que os apps sigam estas práticas recomendadas para ter a melhor experiência de interface.

Botão para reproduzir ou pausar

O botão de iniciar e pausar não corresponde diretamente a um único estado do player. Por exemplo, um 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 utilitários 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 de IU precisa adicionar um Player.Listener para ser informado sobre mudanças de estado que exigem uma atualização correspondente da IU. Consulte Detectar eventos de reprodução para mais detalhes.

Atualizar a IU pode ser caro, e vários eventos do player geralmente chegam juntos. Para evitar a atualização da interface com muita frequência em um curto período, geralmente é melhor detectar apenas onEvents e acionar as atualizações da interface a partir daí:

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 de uso geral que precisa funcionar 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 incompatíveis:

Kotlin

nextButton.isEnabled = player.isCommandAvailable(COMMAND_SEEK_TO_NEXT)

Java

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

Obturador do primeiro frame e exibição de imagem

Quando um componente de interface mostra vídeos ou imagens, ele geralmente 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 mista de vídeo e imagem exige que a visualização da imagem seja ocultada e mostrada nos momentos apropriados.

Um padrão comum para lidar com essas atualizações é detectar Player.Listener.onEvents() para qualquer mudança nas faixas selecionadas (EVENT_TRACKS_CHANGED) e quando o primeiro frame de vídeo for renderizado (EVENT_RENDERED_FIRST_FRAME), bem como ImageOutput.onImageAvailable() 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, 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.
}