O Media3 fornece um PlayerView
padrão que oferece algumas opções de
personalização.
Substituir drawables
PlayerView
usa PlayerControlView
para mostrar os controles de reprodução 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 de PlayerControlView
para conferir 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, 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
), é 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 a um estado de jogador único. 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 de 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 da 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. }