Notícias sobre produtos

Melhorar a reprodução de mídia: introdução ao pré-carregamento com o Media3 – Parte 1

Leitura de 8 minutos
Mayuri Khinvasara Khabya
Engenheira de relações com desenvolvedores

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 problemas e sem pausas.

O principal desafio é a latência. Tradicionalmente, um player de vídeo só começa a funcionar (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 fluida:a combinação de inicializações mais rápidas e menos armazenamento em buffer cria uma interação mais fluida e perfeita para os usuários.

Nesta série de três partes, vamos apresentar e detalhar os utilitários avançados da Media3 para (pré-)carregar componentes.

  • Na Parte 1, vamos abordar os fundamentos: entender as diferentes estratégias de pré-carregamento disponíveis na 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 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 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 da 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 a ele.

O pré-carregamento só é iniciado quando nenhuma mídia está sendo carregada para a reprodução em andamento, o que evita que ele concorra por 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 ExoPlayer da Media3, 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 todas as instâncias do ExoPlayer que vão 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 builder 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 quanto tempo do item 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 as 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

Agora que você criou o gerente, pode começar a dizer o que ele precisa 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 gerente vai começar a buscar dados para esse MediaItem em segundo plano.

Depois de adicionar, peça ao gerente para reavaliar a nova lista, indicando 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 reproduzir esse 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 displ​​ay 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 sobre o índice de reprodução atual para que ele sempre priorize os itens mais próximos do í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 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: ele também mostra a latência de inicialização de cada vídeo.

Demo-PreloadManager_2.webp

Qual é a próxima etapa?

E chegamos ao fim da 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 rapidamente, 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! 🚀

Escrito por:

Continuar lendo