Melhorando a reprodução de mídia: introdução ao pré-carregamento com a Media3 – Parte 1
Leitura de 8 minutos
Nos apps atuais focados em mídia, oferecer uma experiência de reprodução tranquila e ininterrupta é fundamental para uma experiência do usuário agradável. Os usuários esperam que os vídeos comecem instantaneamente e sejam reproduzidos sem pausas.
O principal desafio é a latência. Tradicionalmente, um player de vídeo só começa a trabalhar (conectando, baixando, analisando, armazenando em buffer) depois que o usuário escolhe um item para reprodução. Essa abordagem reativa é lenta para o contexto atual de vídeos curtos. A solução é ser proativo. Precisamos antecipar o que o usuário vai assistir em seguida e preparar o conteúdo com antecedência. Essa é a essência da pré-carga.
Os principais benefícios da pré-carga incluem:
- 🚀 Início mais rápido da reprodução:os vídeos já estão prontos para serem assistidos, o que leva a transições mais rápidas entre os itens e um início mais imediato.
- 📉 Redução do buffer:ao carregar dados de forma proativa, é muito menos provável que a reprodução seja interrompida, por exemplo, devido a falhas na rede.
- ✨ Experiência do usuário mais suave:a combinação de inicializações mais rápidas e menos armazenamento em buffer cria uma interação mais fluida e integrada para os usuários.
Nesta série de três partes, vamos apresentar e analisar as ferramentas avançadas da Media3 para pré-carregar componentes.
- Na Parte 1, vamos abordar os fundamentos: entender as diferentes estratégias de pré-carregamento disponíveis no Media3, ativar o PreloadConfiguration e configurar o DefaultPreloadManager, permitindo que seu app pré-carregue itens. Ao final deste blog, você poderá pré-carregar e reproduzir itens de mídia com a classificação e a duração configuradas.
- Na Parte 2, vamos abordar tópicos mais avançados do DefaultPreloadManager: usar listeners para análise, explorar práticas recomendadas prontas para produção, como o padrão de janela deslizante e componentes compartilhados personalizados do DefaultPreloadManager e do ExoPlayer.
- Na Parte 3, vamos analisar o cache de disco com o DefaultPreloadManager.
Pré-carregamento ao resgate! 🦸♀️
A ideia principal por trás do pré-carregamento é simples: carregar o conteúdo de mídia antes de precisar dele. Quando um usuário desliza para o próximo vídeo, os primeiros segmentos já estão baixados e disponíveis para reprodução imediata.
Pense nisso como um restaurante. Uma cozinha movimentada não espera um pedido para começar a cortar cebolas. 🧅 Eles fazem o trabalho de preparação com antecedência. O pré-carregamento é o trabalho de preparação do seu player de vídeo.
Quando ativada, a pré-carga ajuda a minimizar a latência de junção quando um usuário pula para o próximo item antes que o buffer de reprodução chegue a ele. O primeiro período da próxima janela é preparado, e as amostras de vídeo, áudio e texto são armazenadas em buffer. O período pré-carregado é enfileirado posteriormente no player com amostras armazenadas em buffer imediatamente disponíveis e prontas para serem enviadas ao codec para renderização.
No Media3, há duas APIs principais para pré-carregamento, cada uma adequada para diferentes casos de uso. A primeira etapa é escolher a API certa.
1. Pré-carregar itens de playlist com PreloadConfiguration
Essa é a abordagem simples, útil para mídia linear e sequencial, como playlists em que a ordem de reprodução é previsível (como uma série de episódios). Você dá ao player a lista completa de itens de mídia usando as APIs de playlist do ExoPlayer e define a PreloadConfiguration para o player. Assim, ele faz o pré-carregamento automático dos próximos itens na sequência conforme configurado. Essa API tenta otimizar a latência de junção quando um usuário pula para o próximo item antes que o buffer de reprodução já se sobreponha ao próximo item.
O pré-carregamento só é iniciado quando nenhuma mídia está sendo carregada para a reprodução em andamento, o que evita que ele dispute a largura de banda com a reprodução principal.
Se você ainda não tem certeza se precisa do pré-carregamento, essa API é uma ótima opção de baixo custo para testar.
player.preloadConfiguration =
PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L)Com a PreloadConfiguration acima, o player tenta pré-carregar cinco segundos de mídia para o próximo item na playlist.
Depois de ativar, é possível desativar o pré-carregamento de playlists usando PreloadConfiguration.DEFAULT:
player.preloadConfiguration = PreloadConfiguration.DEFAULT
2. Pré-carregar listas dinâmicas com o PreloadManager
Para interfaces dinâmicas, como feeds verticais ou carrosséis, em que o próximo item é determinado pela interação do usuário, a API PreloadManager é adequada. Esse é um novo componente independente e eficiente na biblioteca Media3 ExoPlayer, projetado especificamente para pré-carregar de forma proativa. Ele gerencia uma coleção de MediaSources em potencial, priorizando-as com base na proximidade da posição atual do usuário e oferece controle granular sobre o que pré-carregar, adequado para cenários complexos, como feeds dinâmicos de vídeos curtos.
Como configurar o PreloadManager
O DefaultPreloadManager é a implementação canônica do PreloadManager.
O criador de DefaultPreloadManager pode criar o DefaultPreloadManager e qualquer instância do ExoPlayer que vai reproduzir o conteúdo pré-carregado. Para criar um DefaultPreloadManager, é necessário transmitir um TargetPreloadStatusControl, que o gerenciador de pré-carregamento pode consultar para descobrir quanto carregar de um item. Vamos explicar e definir um exemplo de TargetPreloadStatusControl na seção abaixo.
val preloadManagerBuilder = DefaultPreloadManager.Builder(context, targetPreloadStatusControl) val preloadManager = val preloadManagerBuilder.build() // Build ExoPlayer with DefaultPreloadManager.Builder val player = preloadManagerBuilder.buildExoPlayer()
É necessário usar o mesmo criador para o ExoPlayer e o DefaultPreloadManager, o que garante que os componentes deles sejam compartilhados corretamente.
Pronto. Agora você tem um gerente pronto para receber instruções.
Como configurar duração e classificação com TargetPreloadStatusControl
E se você quiser pré-carregar, digamos, 10 segundos de vídeo? Você pode informar a posição dos itens de mídia no carrossel, e o DefaultPreloadManager prioriza o carregamento dos itens com base na proximidade do item que o usuário está reproduzindo.
Se você quiser controlar a duração do item a ser pré-carregado, informe isso com DefaultPreloadManager.PreloadStatus.
Por exemplo,
- O item "A" é a prioridade mais alta. Carregue 5 segundos de vídeo.
- O item "B" é de prioridade média, mas, quando chegar nele, carregue 3 segundos de vídeo.
- O item "C" tem menos prioridade, carregue apenas faixas.
- O item "D" é ainda menos prioritário, apenas se prepare.
- Qualquer outro item está longe. Não faça pré-carregamento de nada.
Esse controle granular ajuda a otimizar o uso de recursos, o que é recomendado para uma reprodução sem problemas.
import androidx.media3.exoplayer.DefaultPreloadManager.PreloadStatus
class MyTargetPreloadStatusControl(
currentPlayingIndex: Int = C.INDEX_UNSET
) : TargetPreloadStatusControl<Int,PreloadStatus> {
// The app is responsible for updating this based on UI state
override fun getTargetPreloadStatus(index: Int): PreloadStatus? {
val distance = index - currentPlayingIndex
// Adjacent items (Next): preload 5 seconds
if (distance == 1) {
// Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED and suggest loading // 5000ms from the default start position
return PreloadStatus.specifiedRangeLoaded(5000L)
}
// Adjacent items (Previous): preload 3 seconds
else if (distance == -1) {
// Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED //and suggest loading 3000ms from the default start position
return PreloadStatus.specifiedRangeLoaded(3000L)
}
// Items two positions away: just select tracks
else if (distance) == 2) {
// Return a PreloadStatus that is labelled by STAGE_TRACKS_SELECTED
return PreloadStatus.TRACKS_SELECTED
}
// Items four positions away: just select prepare
else if (abs(distance) <= 4) {
// Return a PreloadStatus that is labelled by STAGE_SOURCE_PREPARED
return PreloadStatus.SOURCE_PREPARED
}
// All other items are too far away
return null
}
}Dica: o PreloadManager pode manter os itens anteriores e seguintes pré-carregados, enquanto o PreloadConfiguration só procura os próximos itens.
Como gerenciar itens de pré-carregamento
Com o gerente criado, você pode começar a dizer a ele o que fazer. À medida que o usuário rola um feed, você identifica os próximos vídeos e os adiciona ao gerenciador. A interação com o PreloadManager é uma conversa orientada por estado entre a interface e o mecanismo de pré-carregamento.
1. Adicionar itens de mídia
Ao preencher o feed, informe ao gerente a mídia que precisa ser rastreada. Se você estiver começando, adicione toda a lista que quer pré-carregar. Depois, você pode continuar adicionando um único item à lista conforme necessário. Você tem controle total sobre os itens na lista de pré-carregamento, o que significa que também precisa gerenciar o que é adicionado e removido do gerenciador.
val initialMediaItems = pullMediaItemsFromService(/* count= */ 20)
for (index in 0 until initialMediaItems.size) {
preloadManager.add(
initialMediaItems.get(index),index)
)
}O gerenciador vai começar a buscar dados para esse MediaItem em segundo plano.
Depois de adicionar, peça ao gerente para reavaliar a nova lista, sugerindo que algo mudou, como a adição ou remoção de um item, ou que o usuário mudou para jogar um novo item.
preloadManager.invalidate()
2. Recuperar e reproduzir um item
Aqui está a principal lógica de reprodução. Quando o usuário decidir assistir o vídeo, não será necessário criar um novo MediaSource. Em vez disso, peça ao PreloadManager o que ele já preparou. É possível recuperar o MediaSource do PreloadManager usando o MediaItem.
Se o item recuperado do PreloadManager for nulo, isso significa que o mediaItem ainda não foi pré-carregado ou adicionado ao PreloadMamager. Portanto, você pode definir o mediaItem diretamente.
// When a media item is about to display on the screen
val mediaSource = preloadManager.getMediaSource(mediaItem)
if (mediaSource!= null) {
player.setMediaSource(mediaSource)
} else {
// If mediaSource is null, that mediaItem hasn't been added yet.
// So, send it directly to the player.
player.setMediaItem(mediaItem)
}
player.prepare()
// When the media item is displaying at the center of the screen
player.play()Ao preparar o MediaSource recuperado do PreloadManager, você faz a transição perfeita do pré-carregamento para a reprodução, usando os dados que já estão na memória. Isso acelera o tempo de inicialização.
3. Manter o índice atual sincronizado com a interface
Como nosso feed / lista pode ser dinâmico, é importante notificar o PreloadManager do seu índice de reprodução atual para que ele sempre priorize os itens mais próximos do seu índice atual para pré-carregamento.
preloadManager.setCurrentPlayingIndex(currentIndex) // Need to call invalidate() to update the priorities preloadManager.invalidate()
4. Remover um item
Para manter a eficiência do gerenciador, remova os itens que ele não precisa mais rastrear, como os que estão muito longe da posição atual do usuário.
// When an item is too far from the current playing index preloadManager.remove(mediaItem)
Se você precisar limpar todos os itens de uma vez, chame preloadManager.reset().
5. Solte o gerente
Quando você não precisar mais do PreloadManager (por exemplo, quando a interface for destruída), libere-o para liberar os recursos. Um bom lugar para fazer isso é onde você já está liberando os recursos do player. Recomendamos liberar o gerenciador antes do player, já que o jogador pode continuar jogando se você não precisar de mais pré-carregamento.
// In your Activity's onDestroy() or Composable's onDispose preloadManager.release()
Demonstração
Confira ao vivo 👍
Na demonstração abaixo, vemos o impacto do PreloadManager no lado direito, que tem tempos de carregamento mais rápidos, enquanto o lado esquerdo mostra a experiência atual. Você também pode conferir o exemplo de código da demonstração. Bônus: também mostra a latência de inicialização de cada vídeo.
A seguir
E terminamos a Parte 1! Agora você tem as ferramentas para criar um sistema de pré-carregamento dinâmico. Você pode usar PreloadConfiguration para pré-carregar o próximo item de uma playlist no ExoPlayer ou configurar um DefaultPreloadManager, adicionar e remover itens na hora, configurar o status de pré-carregamento de destino e recuperar corretamente o conteúdo pré-carregado para reprodução.
Na Parte 2, vamos nos aprofundar no DefaultPreloadManager. Vamos explorar como detectar eventos de pré-carregamento, discutir práticas recomendadas, como usar uma janela deslizante para evitar problemas de memória, e analisar os componentes compartilhados personalizados do ExoPlayer e do DefaultPreloadManager.
Quer compartilhar algum feedback? Queremos saber sua opinião.
Fique por dentro e acelere seu app! 🚀
-
Novidades sobre produtosEsta é a segunda parte da nossa série de três partes sobre pré-carregamento de mídia com a Media3. Esta série foi criada para orientar você no processo de criação de experiências de mídia altamente responsivas e de baixa latência nos seus apps Android.
Mayuri Khinvasara Khabya • Leitura de 9 minutos -
Novidades sobre produtosHoje, temos o prazer de anunciar o lançamento estável das bibliotecas AndroidX Security State versão 1.1.0 e Security State Provider versão 1.0.0.
Maunik Shah, Alec Garcia, Joseph Yong • Leitura de 4 minutos -
Novidades sobre produtosHoje, estamos lançando o primeiro conjunto de tarefas de longo prazo (LHT, na sigla em inglês), que são tarefas de grande complexidade que levam vários dias ou até uma semana para serem concluídas por um engenheiro. Também estamos lançando a avaliação com agentes, começando com agentes dos provedores de modelos correspondentes.
Matthew McCullough • Leitura de 3 minutos
Receba os insights mais recentes sobre desenvolvimento Android na sua caixa de entrada semanalmente.