Notizie sui prodotti

Migliorare la riproduzione dei contenuti multimediali: introduzione al precaricamento con Media3 - Parte 1

Lettura di 8 minuti
Mayuri Khinvasara Khabya
Ingegnere per le relazioni con gli sviluppatori

Nelle app incentrate sui contenuti multimediali di oggi, offrire un'esperienza di riproduzione fluida e senza interruzioni è fondamentale per un'esperienza utente piacevole. Gli utenti si aspettano che i video inizino immediatamente e vengano riprodotti senza interruzioni.

La sfida principale è la latenza. In genere, un video player inizia a funzionare (connessione, download, analisi, buffering) solo dopo che l'utente ha scelto un elemento da riprodurre. Questo approccio reattivo è lento per il contesto dei video nel formato breve di oggi. La soluzione è essere proattivi. Dobbiamo anticipare i contenuti che l'utente guarderà e prepararli in anticipo. Questa è l'essenza del precaricamento.

I vantaggi principali del precaricamento includono:

  • 🚀 Avvio della riproduzione più rapido: i video sono già pronti, il che porta a transizioni più rapide tra gli elementi e a un avvio più immediato.
  • 📉 Buffering ridotto: caricando i dati in modo proattivo, è molto meno probabile che la riproduzione si blocchi, ad esempio a causa di problemi di rete.
  • ✨ Esperienza utente più fluida: la combinazione di avvii più rapidi e meno buffering crea un'interazione più fluida e senza interruzioni per gli utenti.

In questa serie in tre parti, introdurremo e approfondiremo le potenti utilità di Media3 per il caricamento (precaricamento) dei componenti.

  • Nella Parte 1, tratteremo le nozioni di base: comprendere le diverse strategie di precaricamento disponibili in Media3, abilitare PreloadConfiguration e configurare DefaultPreloadManager, consentendo alla tua app di precaricare gli elementi. Al termine di questo post del blog, dovresti essere in grado di precaricare e riprodurre elementi multimediali con la classificazione e la durata configurate.
  • Nella Parte 2, approfondiremo argomenti più avanzati di DefaultPreloadManager: utilizzo dei listener per l'analisi, esplorazione delle best practice pronte per la produzione come il pattern della finestra scorrevole e componenti condivisi personalizzati di DefaultPreloadManager ed ExoPlayer.
  • Nella Parte 3, approfondiremo la memorizzazione nella cache su disco con DefaultPreloadManager.

Il precaricamento in soccorso 🦸‍♀️

L'idea di base del precaricamento è semplice: caricare i contenuti multimediali prima di averne bisogno. Quando un utente scorre per guardare il video successivo, i primi segmenti del video sono già stati scaricati e sono disponibili per la riproduzione immediata.

Pensa a un ristorante. Una cucina affollata non aspetta un ordine per iniziare a tagliare le cipolle. 🧅 I cuochi svolgono il lavoro di preparazione in anticipo. Il precaricamento è il lavoro di preparazione per il tuo video player.

Se abilitato, il precaricamento può contribuire a ridurre al minimo la latenza di join quando un utente passa all'elemento successivo prima che il buffer di riproduzione raggiunga l'elemento successivo. Viene preparato il primo periodo della finestra successiva e vengono memorizzati nel buffer i campioni di video, audio e testo. Il periodo precaricato viene messo in coda nel player con i campioni memorizzati nel buffer immediatamente disponibili e pronti per essere inviati al codec per il rendering.

In Media3 sono disponibili due API principali per il precaricamento, ognuna adatta a casi d'uso diversi. La scelta dell'API giusta è il primo passo.

1. Precaricare gli elementi della playlist con PreloadConfiguration

Questo è l'approccio semplice, utile per i contenuti multimediali lineari e sequenziali come le playlist in cui l'ordine di riproduzione è prevedibile (ad esempio una serie di episodi). Fornisci al player l'elenco completo degli elementi multimediali utilizzando le API della playlist di ExoPlayer e imposti PreloadConfiguration per il player, che precarica automaticamente gli elementi successivi nella sequenza come configurato. Questa API tenta di ottimizzare la latenza di join quando un utente passa all'elemento successivo prima che il buffer di riproduzione si sovrapponga già all'elemento successivo.

Il precaricamento viene avviato solo quando non vengono caricati contenuti multimediali per la riproduzione in corso, il che impedisce di competere per la larghezza di banda con la riproduzione principale.

Se non sei ancora sicuro di aver bisogno del precaricamento, questa API è un'ottima opzione a basso impegno per provarla.

  player.preloadConfiguration =
    PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L)

Con PreloadConfiguration sopra, il player tenta di precaricare cinque secondi di contenuti multimediali per l'elemento successivo nella playlist.

Una volta attivato, il precaricamento della playlist può essere disattivato di nuovo utilizzando PreloadConfiguration.DEFAULT:

  player.preloadConfiguration = PreloadConfiguration.DEFAULT

2. Precaricare gli elenchi dinamici con PreloadManager

Per le UI dinamiche come i feed verticali o i caroselli, in cui l'elemento "successivo" è determinato dall'interazione dell'utente, è appropriata l'API PreloadManager. Si tratta di un nuovo componente potente e autonomo all'interno della libreria Media3 ExoPlayer progettato specificamente per il precaricamento proattivo. Gestisce una raccolta di potenziali MediaSource, dando loro la priorità in base alla vicinanza alla posizione attuale dell'utente e offre un controllo granulare su cosa precaricare, adatto a scenari complessi come i feed dinamici di video nel formato breve.

Configurare PreloadManager

Il DefaultPreloadManager è l'implementazione canonica di PreloadManager.

Il builder di DefaultPreloadManager può creare sia DefaultPreloadManager sia le istanze di ExoPlayer che riprodurranno i contenuti precaricati. Per creare un DefaultPreloadManager, devi passare un TargetPreloadStatusControl, che il gestore del precaricamento può interrogare per scoprire la quantità di dati da caricare per un elemento. Spiegheremo e definiremo un esempio di TargetPreloadStatusControl nella sezione seguente.

  val preloadManagerBuilder =
DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
val preloadManager = val preloadManagerBuilder.build()

// Build ExoPlayer with DefaultPreloadManager.Builder
val player = preloadManagerBuilder.buildExoPlayer()

È necessario utilizzare lo stesso builder sia per ExoPlayer sia per DefaultPreloadManager, in modo che i componenti sottostanti vengano condivisi correttamente.

Ed è fatta. Ora hai un gestore pronto a ricevere istruzioni.

Configurare la durata e la classificazione con TargetPreloadStatusControl

Cosa succede se vuoi precaricare, ad esempio, 10 secondi di video? Puoi fornire la posizione degli elementi multimediali nel carosello e DefaultPreloadManager dà la priorità al caricamento degli elementi in base alla loro vicinanza all'elemento che l'utente sta riproducendo.

Se vuoi controllare la durata dell'elemento da precaricare, puoi farlo con DefaultPreloadManager.PreloadStatus che restituisci.

Ad esempio,

  • L'elemento "A" ha la priorità più alta, carica 5 secondi di video.
  • L'elemento "B" ha una priorità media, ma quando lo raggiungi, carica 3 secondi di video.
  • L'elemento "C" ha una priorità inferiore, carica solo le tracce.
  • L'elemento "D" ha una priorità ancora inferiore, preparalo soltanto.
  • Gli altri elementi sono lontani, non precaricare nulla.

Questo controllo granulare può aiutarti a ottimizzare l'utilizzo delle risorse, il che è consigliato per una riproduzione senza interruzioni.

  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
            }
}

Suggerimento: PreloadManager può mantenere precaricati sia gli elementi precedenti sia quelli successivi, mentre PreloadConfiguration esamina solo gli elementi successivi.

Gestire gli elementi di precaricamento

Una volta creato il gestore, puoi iniziare a dirgli su cosa lavorare. Man mano che l'utente scorre un feed, identifichi i video imminenti e li aggiungi al gestore. L'interazione con PreloadManager è una conversazione basata sullo stato tra la tua UI e il motore di precaricamento.

1. Aggiungere elementi multimediali

Quando popoli il feed, devi informare il gestore dei contenuti multimediali che deve monitorare. Se stai iniziando, puoi aggiungere l'intero elenco che vuoi precaricare. Successivamente, puoi continuare ad aggiungere un singolo elemento all'elenco quando e se necessario. Hai il pieno controllo sugli elementi presenti nell'elenco di precaricamento, il che significa che devi anche gestire gli elementi aggiunti e rimossi dal gestore.

  val initialMediaItems = pullMediaItemsFromService(/* count= */ 20)
for (index in 0 until initialMediaItems.size) {
    preloadManager.add(
        initialMediaItems.get(index),index)
    )
}

Il gestore inizierà a recuperare i dati per questo MediaItem in background.

Dopo l'aggiunta, chiedi al gestore di rivalutare il nuovo elenco (indicando che qualcosa è cambiato, ad esempio l'aggiunta o la rimozione di un elemento, oppure l'utente passa alla riproduzione di un nuovo elemento).

  preloadManager.invalidate()

2. Recuperare e riprodurre un elemento

Ecco la logica di riproduzione principale. Quando l'utente decide di riprodurre un video, non devi creare un nuovo MediaSource. Chiedi invece a PreloadManager quello che ha già preparato. Puoi recuperare MediaSource da Preload Manager utilizzando MediaItem.

Se l'elemento recuperato da PreloadManager è null, significa che mediaItem non è ancora stato precaricato o aggiunto a PreloadMamager, quindi scegli di impostare mediaItem direttamente.

  // 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()

Preparando MediaSource recuperato da PreloadManager, passi senza problemi dal precaricamento alla riproduzione, utilizzando i dati già presenti in memoria. Questo è ciò che rende più veloce il tempo di avvio.

3. Mantenere l'indice corrente sincronizzato con la UI

Poiché il feed / elenco potrebbe essere dinamico, è importante comunicare a PreloadManager l'indice di riproduzione corrente in modo che possa sempre dare la priorità agli elementi più vicini all'indice corrente per il precaricamento.

  preloadManager.setCurrentPlayingIndex(currentIndex)
// Need to call invalidate() to update the priorities
preloadManager.invalidate()

4. Rimuovere un elemento

Per mantenere efficiente il gestore, devi rimuovere gli elementi che non deve più monitorare, ad esempio gli elementi lontani dalla posizione attuale dell'utente.

  // When an item is too far from the current playing index
preloadManager.remove(mediaItem)

Se devi cancellare tutti gli elementi contemporaneamente, puoi chiamare preloadManager.reset().

5. Rilasciare il gestore

Quando non hai più bisogno di PreloadManager (ad esempio, quando la UI viene eliminata), devi rilasciarlo per liberare le sue risorse. Un buon punto per farlo è dove rilasci già le risorse del player. È consigliabile rilasciare il gestore prima del player, perché il player può continuare a riprodurre se non hai bisogno di altro precaricamento.

  // In your Activity's onDestroy() or Composable's onDispose
preloadManager.release()

Demo

Guarda la funzionalità in azione 👍

Nella demo riportata di seguito, vediamo l'impatto di PreloadManager sul lato destro, che ha tempi di caricamento più rapidi, mentre il lato sinistro mostra l'esperienza esistente. Puoi anche visualizzare l'esempio di codice sample per la demo. (Bonus: mostra anche la latenza di avvio per ogni video)

Demo-PreloadManager_2.webp

Qual è il passaggio successivo?

E questo è tutto per la Parte 1. Ora hai gli strumenti per creare un sistema di precaricamento dinamico. Puoi utilizzare PreloadConfiguration per precaricare l'elemento successivo di una playlist in ExoPlayer o configurare un DefaultPreloadManager, aggiungere e rimuovere elementi al volo, configurare lo stato di precaricamento di destinazione e recuperare correttamente i contenuti precaricati per la riproduzione.

Nella Parte 2, approfondiremo DefaultPreloadManager. Esploreremo come ascoltare gli eventi di precaricamento, discuteremo le best practice come l'utilizzo di una finestra scorrevole per evitare problemi di memoria e daremo un'occhiata ai componenti condivisi personalizzati di ExoPlayer e DefaultPreloadManager.

Hai feedback da condividere? Non vediamo l'ora di ricevere il tuo feedback.

Continua a seguirci e rendi la tua app più veloce. 🚀

Scritto da:

Continua a leggere