Personalizações da IU

A Media3 oferece um PlayerView padrão com algumas opções de personalização.

Substituir drawables

O PlayerView usa PlayerControlView para mostrar os controles de reprodução e a barra de progresso. Os elementos gráficos usados por PlayerControlView podem ser substituídos por elementos gráficos com os mesmos nomes definidos no seu aplicativo. Consulte a documentação PlayerControlView para ver uma lista de elementos gráficos de controle que podem ser substituídos.

Para mais personalizações, os desenvolvedores de apps precisam implementar os próprios componentes de interface. No entanto, confira 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), os apps precisam seguir estas práticas recomendadas para ter a melhor experiência de UI.

Botão "Reproduzir/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, a Media3 oferece métodos utilitários para decidir qual botão mostrar (Util.shouldShowPlayButton) e para 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 UI precisa adicionar um Player.Listener para ser informado sobre mudanças de estado que exigem uma atualização correspondente da UI. Consulte Ouvir eventos de reprodução para mais detalhes.

Atualizar a interface pode ser caro, 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 de lá:

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 pode precisar trabalhar com diferentes implementações de Player deve 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 imagens

Quando um componente de UI mostra vídeo 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 ocultar e mostrar a visualização da imagem em momentos adequados.

Um padrão comum para processar essas atualizações é ficar atento a Player.Listener.onEvents() para qualquer mudança nas faixas selecionadas (EVENT_TRACKS_CHANGED) e quando o primeiro frame de vídeo é renderizado (EVENT_RENDERED_FIRST_FRAME), além de ImageOutput.onImageAvailable() para quando uma nova imagem está 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.
}