Novidades sobre produtos

Como melhorar a reprodução de mídia: apresentamos o pré-carregamento com a Media3 – Parte 1

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

Nos apps atuais com foco em mídia, oferecer uma experiência de reprodução suave 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 funcionar (conectando, fazendo download, analisando, armazenando em buffer) depois que o usuário escolhe um item para reprodução. Essa abordagem reativa é lenta para o contexto de vídeos curtos de hoje. 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 do pré-carregamento.

Os principais benefícios do pré-carregamento incluem:

  • 🚀 Início de reprodução mais rápido:os vídeos já estão prontos, o que leva a transições mais rápidas entre os itens e um início mais imediato.
  • 📉 Buffer reduzido:ao carregar dados de forma proativa, a reprodução tem muito menos probabilidade de parar, por exemplo, devido a problemas de rede.
  • ✨ Experiência do usuário mais suave:a combinação de inícios mais rápidos e menos buffer cria uma interação mais fluida e perfeita para os usuários.

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

  • Na Parte 1, vamos abordar os fundamentos: entender as diferentes estratégias de pré-carregamento disponíveis na Media3, ativar a 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 detalhadamente o armazenamento em cache de disco com o DefaultPreloadManager.

Pré-carregamento em ação! 🦸‍♀️

A ideia principal por trás do pré-carregamento é simples: carregar conteúdo de mídia antes de precisar dele. Quando um usuário desliza para o próximo vídeo, os primeiros segmentos do vídeo já são baixados e ficam disponíveis para reprodução imediata.

Pense nisso como um restaurante. Uma cozinha movimentada não espera um pedido para começar a picar 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 ativado, o pré-carregamento pode ajudar 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 ao próximo item. 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.

Na 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é-carregamento de itens de playlist com PreloadConfiguration

Essa é a abordagem simples, útil para mídias lineares e sequenciais, como playlists em que a ordem de reprodução é previsível (como uma série de episódios). Você fornece ao player a lista completa de itens de mídia usando as APIs de playlist do ExoPlayer e define a PreloadConfiguration para o player. Em seguida, ele pré-carrega automaticamente os 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 impede que ela concorra à largura de banda com a reprodução principal.

Se você ainda não tiver certeza se precisa de pré-carregamento, essa API é uma ótima opção de baixo custo para testá-la.

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 ativado, o pré-carregamento da playlist pode ser desativado novamente usando PreloadConfiguration.DEFAULT:

player.preloadConfiguration = PreloadConfiguration.DEFAULT

2. Pré-carregamento de listas dinâmicas com 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 avançado na biblioteca Media3 ExoPlayer, projetado especificamente para pré-carregamento proativo. 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 builder do 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, você precisa transmitir um TargetPreloadStatusControl, que o gerenciador de pré-carregamento pode consultar para descobrir quanto carregar para 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 internos deles sejam compartilhados corretamente.

Pronto. Agora você tem um gerenciador pronto para receber instruções.

Como configurar a duração e a classificação com TargetPreloadStatusControl

E se você quiser pré-carregar, digamos, 10 segundos de vídeo? Você pode fornecer 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, poderá informar isso com o DefaultPreloadManager.PreloadStatus retornado.

Por exemplo,

  • O item "A" é a maior prioridade, carregue 5 segundos de vídeo.
  • O item "B" é de prioridade média, mas, quando você chegar a ele, carregue 3 segundos de vídeo.
  • O item "C" tem menos prioridade, carregue apenas faixas.
  • O item "D" tem ainda menos prioridade, apenas prepare.
  • Quaisquer outros itens estão longe, não pré-carregue nada.

Esse controle granular pode ajudar a otimizar a utilização de recursos, o que é recomendado para uma reprodução perfeita.

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 a PreloadConfiguration só vai procurar os próximos itens.

Como gerenciar itens de pré-carregamento

Com o gerenciador criado, você pode começar a dizer o que trabalhar. À 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 gerenciador a mídia que ele precisa rastrear. Se você estiver começando, poderá adicionar toda a lista que quiser pré-carregar. Posteriormente, você pode continuar adicionando um único item à lista conforme necessário. Você tem controle total sobre quais itens estão 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 gerenciador para reavaliar a nova lista (indicando que algo mudou, como adicionar/ remover um item ou o usuário mudar para reproduzir um novo item).

preloadManager.invalidate()

2. Recuperar e reproduzir um item

Aqui está a lógica principal de reprodução. Quando o usuário decide reproduzir esse vídeo, não é necessário criar um novo MediaSource. Em vez disso, você pede ao PreloadManager o que ele já preparou. É possível recuperar o MediaSource do Preload Manager 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ê escolhe 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 que torna o tempo de início mais rápido.

3. Manter o índice atual sincronizado com a interface

Como nosso feed / lista pode ser dinâmico, é importante notificar o PreloadManager do índice de reprodução atual para que ele possa sempre priorizar 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 o gerenciador eficiente, remova os itens que ele não precisa mais rastrear, como itens 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 só vez, chame preloadManager.reset().

5. Liberar o gerenciador

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. É recomendável liberar o gerenciador antes do player, porque o player pode continuar reproduzindo 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. Também é possível conferir o exemplo de código sample 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?

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.

Você tem algum feedback para compartilhar? Queremos saber sua opinião.

Fique atento e deixe seu app mais rápido! 🚀

Escrito por:

Continuar lendo