Actualités sur les produits

Améliorer la lecture de contenus multimédias : présentation du préchargement avec Media3 (1re partie)

8 minutes de lecture
Mayuri Khinvasara Khabya
Ingénieure en relations avec les développeurs

Dans les applications multimédias actuelles, offrir une expérience de lecture fluide et ininterrompue est essentiel pour une expérience utilisateur agréable. Les utilisateurs s'attendent à ce que leurs vidéos démarrent instantanément et soient lues de manière fluide, sans pause.

Le principal défi est la latence. Traditionnellement, un lecteur vidéo ne commence son travail (connexion, téléchargement, analyse, mise en mémoire tampon) qu'une fois que l'utilisateur a choisi un élément à lire. Cette approche réactive est lente pour le contexte actuel des vidéos courtes. La solution consiste à être proactif. Nous devons anticiper ce que l'utilisateur regardera ensuite et préparer le contenu à l'avance. C'est l'essence du préchargement.

Voici les principaux avantages du préchargement :

  • 🚀 Démarrage plus rapide de la lecture : les vidéos sont déjà prêtes, ce qui permet des transitions plus rapides entre les éléments et un démarrage plus immédiat.
  • 📉 Mise en mémoire tampon réduite : en chargeant les données de manière proactive, la lecture est beaucoup moins susceptible de s'interrompre, par exemple en raison de problèmes de réseau.
  • ✨ Expérience utilisateur plus fluide : la combinaison de démarrages plus rapides et d'une mise en mémoire tampon réduite crée une interaction plus fluide et transparente pour les utilisateurs.

Dans cette série en trois parties, nous allons présenter et examiner en détail les puissants utilitaires de Media3 pour le (pré)chargement de composants.

  • Dans la première partie, nous aborderons les bases : comprendre les différentes stratégies de préchargement disponibles dans Media3, activer PreloadConfiguration et configurer DefaultPreloadManager, ce qui permet à votre application de précharger des éléments. À la fin de cet article de blog, vous devriez être en mesure de précharger et de lire des éléments multimédias avec le classement et la durée configurés.
  • Dans la deuxième partie, nous aborderons des sujets plus avancés de DefaultPreloadManager : utiliser des écouteurs pour l'analyse, explorer les bonnes pratiques prêtes à l'emploi telles que le modèle de fenêtre glissante et les composants partagés personnalisés de DefaultPreloadManager et ExoPlayer.
  • Dans la troisième partie, nous examinerons en détail la mise en cache sur disque avec DefaultPreloadManager.

Le préchargement à la rescousse ! 🦸‍♀️

L'idée principale du préchargement est simple : charger le contenu multimédia avant d'en avoir besoin. Lorsque l'utilisateur balaye l'écran pour passer à la vidéo suivante, les premiers segments de la vidéo sont déjà téléchargés et disponibles, prêts à être lus immédiatement.

Pensez à un restaurant. Une cuisine très fréquentée n'attend pas qu'une commande soit passée pour commencer à couper des oignons. 🧅 Elle effectue ses préparatifs à l'avance. Le préchargement est le travail de préparation de votre lecteur vidéo.

Lorsqu'il est activé, le préchargement peut aider à réduire la latence de jointure lorsqu'un utilisateur passe à l'élément suivant avant que la mémoire tampon de lecture n'atteigne l'élément suivant. La première période de la fenêtre suivante est préparée, et des exemples de vidéo, d'audio et de texte sont mis en mémoire tampon. La période préchargée est ensuite mise en file d'attente dans le lecteur avec des exemples mis en mémoire tampon immédiatement disponibles et prêts à être transmis au codec pour le rendu.

Dans Media3, il existe deux API principales pour le préchargement, chacune adaptée à différents cas d'utilisation. Le choix de la bonne API est la première étape.

1. Précharger des éléments de playlist avec PreloadConfiguration

Il s'agit d'une approche simple, utile pour les contenus multimédias linéaires et séquentiels tels que les playlists où l'ordre de lecture est prévisible (comme une série d'épisodes). Vous fournissez au lecteur la liste complète des éléments multimédias à l'aide des API de playlist d'ExoPlayer et définissez la PreloadConfiguration pour le lecteur. Ensuite, il précharge automatiquement les éléments suivants dans la séquence telle qu'elle est configurée. Cette API tente d'optimiser la latence de jointure lorsqu'un utilisateur passe à l'élément suivant avant que la mémoire tampon de lecture ne se chevauche déjà dans l'élément suivant.

Le préchargement ne démarre que lorsqu'aucun contenu multimédia n'est chargé pour la lecture en cours, ce qui l'empêche de concurrencer la bande passante avec la lecture principale.

Si vous n'êtes toujours pas sûr d'avoir besoin du préchargement, cette API est une excellente option à faible effort pour l'essayer.

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

Avec la PreloadConfiguration ci-dessus, le lecteur tente de précharger cinq secondes de contenu multimédia pour l'élément suivant de la playlist.

Une fois activé, le préchargement de la playlist peut être désactivé à l'aide de PreloadConfiguration.DEFAULT :

player.preloadConfiguration = PreloadConfiguration.DEFAULT

2. Précharger des listes dynamiques avec PreloadManager

Pour les interfaces utilisateur dynamiques telles que les flux verticaux ou les carrousels, où l'élément "suivant" est déterminé par l'interaction de l'utilisateur, l'API PreloadManager est appropriée. Il s'agit d'un nouveau composant puissant et autonome de la bibliothèque Media3 ExoPlayer, spécialement conçu pour le préchargement proactif. Il gère une collection de MediaSources potentielles, en les classant par ordre de priorité en fonction de leur proximité avec la position actuelle de l'utilisateur, et offre un contrôle précis sur ce qu'il faut précharger, ce qui convient aux scénarios complexes tels que les flux dynamiques de vidéos courtes.

Configurer votre PreloadManager

Le DefaultPreloadManager est l'implémentation canonique de PreloadManager.

Le compilateur de DefaultPreloadManager peut compiler à la fois le DefaultPreloadManager et toutes les instances ExoPlayer qui liront son contenu préchargé. Pour créer un DefaultPreloadManager, vous devez transmettre un TargetPreloadStatusControl, que le gestionnaire de préchargement peut interroger pour savoir combien charger pour un élément. Nous allons expliquer et définir un exemple de TargetPreloadStatusControl dans la section ci-dessous.

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

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

Il est nécessaire d'utiliser le même compilateur pour ExoPlayer et DefaultPreloadManager, ce qui garantit que les composants sous-jacents sont correctement partagés.

Et voilà ! Vous disposez maintenant d'un gestionnaire prêt à recevoir des instructions.

Configurer la durée et le classement avec TargetPreloadStatusControl

Que faire si vous souhaitez précharger, par exemple, 10 secondes de vidéo ? Vous pouvez fournir la position de vos éléments multimédias dans le carrousel, et le DefaultPreloadManager priorise le chargement des éléments en fonction de leur proximité avec l'élément que l'utilisateur est en train de lire.

Si vous souhaitez contrôler la durée de l'élément à précharger, vous pouvez le faire avec DefaultPreloadManager.PreloadStatus que vous renvoyez.

Par exemple,

  • L'élément "A" est la priorité la plus élevée. Chargez 5 secondes de vidéo.
  • L'élément "B" est de priorité moyenne, mais lorsque vous y arrivez, chargez 3 secondes de vidéo.
  • L'élément "C" est moins prioritaire. Ne chargez que les pistes.
  • L'élément "D" est encore moins prioritaire. Préparez-le simplement.
  • Tous les autres éléments sont éloignés. Ne préchargez rien.

Ce contrôle précis peut vous aider à optimiser l'utilisation de vos ressources, ce qui est recommandé pour une lecture fluide.

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

Remarque : PreloadManager peut conserver les éléments précédents et suivants préchargés, tandis que PreloadConfiguration ne prend en compte que les éléments suivants.

Gérer les éléments de préchargement

Une fois votre gestionnaire créé, vous pouvez commencer à lui indiquer sur quoi travailler. Lorsque l'utilisateur fait défiler un flux, vous identifiez les vidéos à venir et les ajoutez au gestionnaire. L'interaction avec PreloadManager est une conversation axée sur l'état entre votre interface utilisateur et le moteur de préchargement.

1. Ajouter des éléments multimédias

Lorsque vous remplissez votre flux, vous devez informer le gestionnaire des contenus multimédias qu'il doit suivre. Si vous débutez, vous pouvez ajouter la liste complète que vous souhaitez précharger. Vous pouvez ensuite continuer à ajouter un seul élément à la liste en fonction des besoins. Vous avez un contrôle total sur les éléments de la liste de préchargement, ce qui signifie que vous devez également gérer ce qui est ajouté et supprimé du gestionnaire.

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

Le gestionnaire commence alors à extraire les données de ce MediaItem en arrière-plan.

Après l'ajout, demandez au gestionnaire de réévaluer sa nouvelle liste (en indiquant qu'un élément a été ajouté/ supprimé ou que l'utilisateur a changé d'élément à lire).

preloadManager.invalidate()

2. Récupérer et lire un élément

Voici la logique de lecture principale. Lorsque l'utilisateur décide de lire cette vidéo, vous n'avez pas besoin de créer un MediaSource. Au lieu de cela, vous demandez au PreloadManager celui qu'il a déjà préparé. Vous pouvez récupérer le MediaSource à partir du gestionnaire de préchargement à l'aide de MediaItem.

Si l'élément récupéré à partir de PreloadManager est nul, cela signifie que mediaItem n'est pas encore préchargé ni ajouté à PreloadMamager. Vous choisissez donc de définir mediaItem directement.

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

En préparant le MediaSource récupéré à partir de PreloadManager, vous passez de manière transparente du préchargement à la lecture, en utilisant les données déjà en mémoire. C'est ce qui accélère le temps de démarrage.

3. Synchroniser l'index actuel avec l'interface utilisateur

Étant donné que notre flux / liste peut être dynamique, il est important d'informer PreloadManager de votre index de lecture actuel afin qu'il puisse toujours donner la priorité aux éléments les plus proches de votre index actuel pour le préchargement.

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

4. Supprimer un élément

Pour que le gestionnaire reste efficace, vous devez supprimer les éléments qu'il n'a plus besoin de suivre, tels que les éléments éloignés de la position actuelle de l'utilisateur.

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

Si vous devez effacer tous les éléments en même temps, vous pouvez appeler preloadManager.reset().

5. Libérer le gestionnaire

Lorsque vous n'avez plus besoin de PreloadManager (par exemple, lorsque votre interface utilisateur est détruite), vous devez le libérer pour libérer ses ressources. Un bon endroit pour le faire est celui où vous libérez déjà les ressources de votre lecteur. Il est recommandé de libérer le gestionnaire avant le lecteur, car le lecteur peut continuer à lire si vous n'avez plus besoin de préchargement.

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

Démonstration

Découvrez-le en direct 👍

Dans la démonstration ci-dessous, nous voyons l'impact de PreloadManager sur le côté droit, qui présente des temps de chargement plus rapides, tandis que le côté gauche montre l'expérience existante. Vous pouvez également consulter l'exemple de code pour la démonstration. (Bonus : il affiche également la latence de démarrage pour chaque vidéo)

Demo-PreloadManager_2.webp

Et maintenant ?

C'est tout pour la première partie. Vous disposez maintenant des outils nécessaires pour créer un système de préchargement dynamique. Vous pouvez utiliser PreloadConfiguration pour précharger l'élément suivant d'une playlist dans ExoPlayer ou configurer un DefaultPreloadManager, ajouter et supprimer des éléments à la volée, configurer l'état de préchargement cible et récupérer correctement le contenu préchargé pour la lecture.

Dans la deuxième partie, nous examinerons plus en détail le DefaultPreloadManager. Nous verrons comment écouter les événements de préchargement, nous aborderons les bonnes pratiques telles que l'utilisation d'une fenêtre glissante pour éviter les problèmes de mémoire et nous examinerons les composants partagés personnalisés d'ExoPlayer et de DefaultPreloadManager.

Vous avez des commentaires à partager ? Nous sommes impatients de vous entendre.

Restez à l'écoute et accélérez votre application. 🚀

Écrit par :

Lire la suite