Actualités des produits
Améliorer la lecture de contenus multimédias : présentation détaillée de PreloadManager de Media3 – Partie 2
Temps de lecture : 9 min
Bienvenue dans le deuxième volet de notre série en trois parties sur le préchargement de contenu multimédia avec Media3. Cette série est conçue pour vous guider dans la création d'expériences multimédias très réactives et à faible latence dans vos applications Android.
- La partie 1 : Présentation du préchargement avec Media3 a abordé les principes de base. Nous avons exploré la distinction entre PreloadConfiguration pour les playlists simples et le DefaultPreloadManager plus puissant pour les interfaces utilisateur dynamiques. Vous avez appris à implémenter le cycle de vie de base de l'API : ajouter des éléments multimédias avec add(), récupérer une MediaSource préparée avec getMediaSource(), gérer les priorités avec setCurrentPlayingIndex() et invalidate(), et libérer les ressources avec remove() et release().
- Partie 2 (cet article) : dans cet article de blog, nous explorons les fonctionnalités avancées de DefaultPreloadManager. Nous vous expliquons comment obtenir des insights avec PreloadManagerListener, comment implémenter des bonnes pratiques prêtes pour la production, comme le partage de composants principaux avec ExoPlayer, et comment maîtriser le modèle de fenêtre glissante pour gérer efficacement la mémoire.
- Partie 3 : La dernière partie de cette série portera sur l'intégration de PreloadManager à un cache de disque persistant, ce qui vous permettra de réduire la consommation de données grâce à la gestion des ressources et d'offrir une expérience fluide.
Si vous débutez avec le préchargement dans Media3, nous vous recommandons vivement de lire la partie 1 avant de continuer. Pour ceux qui sont prêts à aller au-delà des bases, voyons comment améliorer l'implémentation de la lecture multimédia.
Écoute : récupérer les données analytiques avec PreloadManagerListener
Lorsque vous souhaitez lancer une fonctionnalité en production, vous devez également comprendre et capturer les données analytiques qui y sont associées. Comment pouvez-vous être sûr que votre stratégie de préchargement est efficace dans un environnement réel ? Pour répondre à cette question, vous avez besoin de données sur les taux de réussite, les échecs et les performances. L'interface PreloadManagerListener est le principal mécanisme de collecte de ces données.
PreloadManagerListener fournit deux rappels essentiels qui offrent des informations cruciales sur le processus et l'état du préchargement.
- onCompleted(MediaItem mediaItem) : ce rappel est appelé lorsque la requête de préchargement est exécutée avec succès, comme défini par votre TargetPreloadStatusControl.
- onError(erreur PreloadException) : ce rappel peut être utile pour le débogage et la surveillance. Elle est appelée lorsqu'un préchargement échoue et fournit l'exception associée.
Vous pouvez enregistrer un écouteur avec un seul appel de méthode, comme indiqué dans l'exemple de code suivant :
val preloadManagerListener = object : PreloadManagerListener {
override fun onCompleted(mediaItem: MediaItem) {
// Log success for analytics.
Log.d("PreloadAnalytics", "Preload completed for $mediaItem")
}
override fun onError( preloadError: PreloadException) {
// Log the specific error for debugging and monitoring.
Log.e("PreloadAnalytics", "Preload error ", preloadError)
}
}
preloadManager.addListener(preloadManagerListener)
Extraire des insights de l'auditeur
Ces rappels d'écouteur peuvent être associés à votre pipeline d'analyse. En transférant ces événements vers votre moteur d'analyse, vous pouvez répondre à des questions clés telles que :
- Quel est notre taux de réussite du préchargement ? (ratio des événements onCompleted par rapport au nombre total de tentatives de préchargement)
- Quels CDN ou formats vidéo présentent les taux d'erreur les plus élevés ? (en analysant les exceptions à partir de onError)
- Quel est notre taux d'erreur de préchargement ? (ratio des événements onError par rapport au nombre total de tentatives de préchargement)
Ces données peuvent vous fournir un feedback quantitatif sur votre stratégie de préchargement, ce qui vous permet d'effectuer des tests A/B et d'améliorer l'expérience utilisateur en fonction des données. Ces données peuvent également vous aider à ajuster intelligemment la durée de votre préchargement, le nombre de vidéos que vous souhaitez précharger ainsi que les tampons que vous allouez.
Au-delà du débogage : utiliser onError pour un retour à une interface utilisateur élégante
Un préchargement qui échoue est un bon indicateur d'un événement de mise en mémoire tampon à venir pour l'utilisateur. Le rappel onError vous permet de répondre de manière réactive. Au lieu de simplement consigner l'erreur, vous pouvez adapter l'UI. Par exemple, si la vidéo à venir ne parvient pas à se précharger, votre application peut désactiver la lecture automatique pour le prochain balayage, ce qui oblige l'utilisateur à appuyer sur l'écran pour lancer la lecture.
De plus, en inspectant le type PreloadException, vous pouvez définir une stratégie de nouvelle tentative plus intelligente. Une application peut choisir de supprimer immédiatement une source défaillante du gestionnaire en fonction du message d'erreur ou du code d'état HTTP. L'élément doit être supprimé du flux d'UI en conséquence pour éviter que les problèmes de chargement n'affectent l'expérience utilisateur. Vous pouvez également obtenir des données plus précises à partir de PreloadException, comme HttpDataSourceException, pour examiner plus en détail les erreurs. En savoir plus sur le dépannage d'ExoPlayer
Système de copains : pourquoi est-il nécessaire de partager des composants avec ExoPlayer ?
DefaultPreloadManager et ExoPlayer sont conçus pour fonctionner ensemble. Pour garantir la stabilité et l'efficacité, ils doivent partager plusieurs composants de base. S'ils fonctionnent avec des composants distincts et non coordonnés, cela peut avoir un impact sur la sécurité des threads et la facilité d'utilisation des pistes préchargées sur le lecteur, car nous devons nous assurer que les pistes préchargées sont lues sur le bon lecteur. Les différents composants peuvent également entrer en concurrence pour des ressources limitées telles que la bande passante du réseau et la mémoire, ce qui peut entraîner une dégradation des performances. Une partie importante du cycle de vie consiste à gérer l'élimination appropriée. L'ordre recommandé est de libérer d'abord PreloadManager, puis ExoPlayer.
DefaultPreloadManager.Builder est conçu pour faciliter ce partage et dispose d'API permettant d'instancier à la fois votre PreloadManager et une instance de lecteur associée. Voyons pourquoi des composants tels que BandwidthMeter, LoadControl, TrackSelector et Looper doivent être partagés. Consultez la représentation visuelle de la façon dont ces composants interagissent avec la lecture ExoPlayer.
Éviter les conflits de bande passante avec un BandwidthMeter partagé
BandwidthMeter fournit une estimation de la bande passante réseau disponible en fonction des débits de transfert historiques. Si PreloadManager et le lecteur utilisent des instances distinctes, ils ne sont pas conscients de l'activité réseau de l'autre, ce qui peut entraîner des scénarios d'échec. Prenons l'exemple d'un utilisateur qui regarde une vidéo, dont la connexion réseau se dégrade et dont le MediaSource de préchargement lance simultanément un téléchargement agressif pour une future vidéo. L'activité de préchargement de MediaSource consommerait la bande passante nécessaire au lecteur actif, ce qui entraînerait l'arrêt de la vidéo en cours. Un blocage pendant la lecture est un échec majeur de l'expérience utilisateur.
En partageant un seul BandwidthMeter, TrackSelector est en mesure de sélectionner les pistes de la meilleure qualité en fonction des conditions réseau actuelles et de l'état du tampon, lors du préchargement ou de la lecture. Il peut ensuite prendre des décisions intelligentes pour protéger la session de lecture active et garantir une expérience fluide.
preloadManagerBuilder.setBandwidthMeter(customBandwidthMeter)
Assurer la cohérence avec les composants LoadControl, TrackSelector et Renderer partagés d'ExoPlayer
- LoadControl : ce composant définit la règle de mise en mémoire tampon, par exemple la quantité de données à mettre en mémoire tampon avant de lancer la lecture et le moment où commencer ou arrêter de charger d'autres données. Le partage de LoadControl garantit que la consommation de mémoire du lecteur et de PreloadManager est guidée par une stratégie de mise en mémoire tampon unique et coordonnée pour les contenus multimédias préchargés et ceux en cours de lecture, ce qui évite la contention des ressources. Vous devrez allouer intelligemment la taille de la mémoire tampon en fonction du nombre d'éléments que vous préchargez et de leur durée, afin d'assurer la cohérence. En cas de conflit, le lecteur donne la priorité à la lecture de l'élément actuellement affiché à l'écran. Avec un LoadControl partagé, le gestionnaire de préchargement continue de précharger tant que les octets de mémoire tampon cibles alloués au préchargement n'ont pas atteint la limite supérieure. Il n'attend pas que le chargement pour la lecture soit terminé.
Remarque : Le partage de LoadControl dans la dernière version de Media3 (1.8) garantit que son Allocator peut être partagé correctement avec PreloadManager et le lecteur. L'utilisation de LoadControl pour contrôler efficacement le préchargement est une fonctionnalité qui sera disponible dans la prochaine version 1.9 de Media3.
preloadManagerBuilder.setLoadControl(customLoadControl)
- TrackSelector : ce composant est chargé de sélectionner les pistes (par exemple, la vidéo d'une certaine résolution, l'audio dans une langue spécifique) à charger et à lire. Le partage garantit que les pistes sélectionnées lors du préchargement sont les mêmes que celles que le lecteur utilisera. Cela évite un scénario inutile dans lequel une piste vidéo 480p est préchargée, pour que le lecteur la supprime immédiatement et récupère une piste 720p lors de la lecture.< br /> Le gestionnaire de préchargement ne doit PAS partager la même instance de TrackSelector avec le lecteur. À la place, ils doivent utiliser une instance TrackSelector différente, mais de la même implémentation. C'est pourquoi nous définissons TrackSelectorFactory plutôt qu'un TrackSelector dans DefaultPreloadManager.Builder.
preloadManagerBuilder.setTrackSelectorFactory(customTrackSelectorFactory)
- Renderer : ce composant est chargé de comprendre les capacités du lecteur sans créer les renderers complets. Il vérifie ce plan pour déterminer les formats vidéo, audio et de texte que le lecteur final prendra en charge. Cela lui permet de sélectionner et de télécharger de manière intelligente uniquement la piste multimédia compatible, et d'éviter ainsi de gaspiller de la bande passante sur du contenu que le lecteur ne peut pas lire.
preloadManagerBuilder.setRenderersFactory(customRenderersFactory)
En savoir plus sur les composants Exoplayer
La règle d'or : un lecteur en boucle commun pour tous
Le thread sur lequel une instance ExoPlayer est accessible peut être spécifié explicitement en transmettant un Looper lors de la création du lecteur. Le Looper du thread à partir duquel le lecteur doit être accessible peut être interrogé à l'aide de Player.getApplicationLooper. En conservant un Looper partagé entre le lecteur et PreloadManager, il est garanti que toutes les opérations sur ces objets multimédias partagés sont sérialisées dans la file d'attente des messages d'un seul thread. Cela peut réduire les bugs de simultanéité.
Toutes les interactions entre PreloadManager et le lecteur avec les sources multimédias à charger ou à précharger doivent se produire sur le même thread de lecture. Le partage du Looper est indispensable pour la sécurité des threads. Nous devons donc partager le PlaybackLooper entre le PreloadManager et le lecteur.
PreloadManager prépare un objet MediaSource avec état en arrière-plan. Lorsque votre code d'UI appelle player.setMediaSource(mediaSource), vous transférez cet objet complexe et avec état de MediaSource de la précharge au lecteur. Dans ce scénario, l'intégralité de PreloadMediaSource est déplacée du gestionnaire vers le lecteur. Toutes ces interactions et transferts doivent se produire sur le même PlaybackLooper.
Si PreloadManager et ExoPlayer fonctionnaient sur des threads différents, une condition de concurrence pourrait se produire. Le thread du PreloadManager peut modifier l'état interne de MediaSource (par exemple, en écrivant de nouvelles données dans un tampon) au moment précis où le thread du lecteur tente de le lire. Cela entraîne un comportement imprévisible et une exception IllegalStateException difficile à déboguer.
preloadManagerBuilder.setPreloadLooper(playbackLooper)
Voyons comment partager tous les composants ci-dessus entre ExoPlayer et DefaultPreloadManager dans la configuration elle-même.
val preloadManagerBuilder =
DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
// Optional - Share components between ExoPlayer and DefaultPreloadManager
preloadManagerBuilder
.setBandwidthMeter(customBandwidthMeter)
.setLoadControl(customLoadControl)
.setMediaSourceFactory(customMediaSourceFactory)
.setTrackSelectorFactory(customTrackSelectorFactory)
.setRenderersFactory(customRenderersFactory)
.setPreloadLooper(playbackLooper)
val preloadManager = val preloadManagerBuilder.build()
Conseil : Si vous utilisez les composants par défaut d'ExoPlayer, comme DefaultLoadControl, etc., vous n'avez pas besoin de les partager explicitement avec DefaultPreloadManager. Lorsque vous créez votre instance ExoPlayer via buildExoPlayer de DefaultPreloadManager.Builder, ces composants sont automatiquement référencés les uns avec les autres si vous utilisez les implémentations par défaut avec les configurations par défaut. Toutefois, si vous utilisez des composants ou des configurations personnalisés, vous devez en informer explicitement le DefaultPreloadManager à l'aide des API ci-dessus.
Préchargement prêt pour la production : le modèle de fenêtre coulissante
Dans un flux dynamique, un utilisateur peut faire défiler une quantité de contenu pratiquement infinie. Si vous ajoutez en permanence des vidéos au DefaultPreloadManager sans stratégie de suppression correspondante, vous provoquerez inévitablement une erreur OutOfMemoryError. Chaque MediaSource préchargé conserve une SampleQueue, qui alloue des tampons de mémoire. À mesure qu'ils s'accumulent, ils peuvent épuiser l'espace de tas de l'application. La solution est un algorithme que vous connaissez peut-être déjà, appelé "fenêtre glissante". Le modèle de fenêtre glissante conserve en mémoire un petit ensemble gérable d'éléments logiquement adjacents à la position actuelle de l'utilisateur dans le flux. À mesure que l'utilisateur fait défiler l'écran, cette "fenêtre" d'éléments gérés se déplace avec lui, ajoutant les nouveaux éléments qui apparaissent et supprimant ceux qui sont désormais éloignés.
Implémenter le modèle de fenêtre coulissante
Il est essentiel de comprendre que PreloadManager ne fournit pas de méthode setWindowSize() intégrée. La fenêtre glissante est un modèle de conception que vous, le développeur, devez implémenter à l'aide des méthodes primitives add() et remove(). La logique de votre application doit relier les événements d'interface utilisateur, tels qu'un défilement ou un changement de page, à ces appels d'API. Si vous souhaitez obtenir une référence de code, nous avons implémenté ce modèle de fenêtre coulissante dans l'exemple socialite, qui inclut également un PreloadManagerWrapper imitant une fenêtre coulissante.
N'oubliez pas d'ajouter preloadManager.remove(mediaItem) dans votre implémentation lorsque l'élément n'est plus susceptible d'être visionné prochainement par l'utilisateur. Le fait de ne pas supprimer les éléments qui ne sont plus à proximité de l'utilisateur est la principale cause des problèmes de mémoire dans les implémentations de préchargement. L'appel remove() garantit que les ressources sont libérées, ce qui vous aide à maintenir l'utilisation de la mémoire de votre application dans des limites stables.
Ajuster une stratégie de préchargement catégorisée avec TargetPreloadStatusControl
Maintenant que nous avons défini ce qu'il faut précharger (les éléments de notre fenêtre), nous pouvons appliquer une stratégie bien définie pour la quantité à précharger pour chaque élément. Nous avons déjà vu comment atteindre ce niveau de précision avec la configuration TargetPreloadStatusControl dans la partie 1.
Pour rappel, un élément en position +/- 1 peut avoir une probabilité de lecture plus élevée qu'un élément en position +/- 4. Vous pouvez allouer plus de ressources (réseau, processeur, mémoire) aux éléments que l'utilisateur est le plus susceptible de consulter ensuite. Cela crée une stratégie de "préchargement" basée sur la proximité, qui est essentielle pour équilibrer la lecture immédiate et l'utilisation efficace des ressources.
Vous pouvez utiliser les données analytiques via PreloadManagerListener, comme indiqué dans les sections précédentes, pour définir votre stratégie de durée de préchargement.
Conclusion et prochaines étapes
Vous disposez désormais des connaissances avancées nécessaires pour créer des flux multimédias rapides, stables et économes en ressources à l'aide de DefaultPreloadManager de Media3.
Récapitulons les points clés :
- Utilisez PreloadManagerListener pour recueillir des insights analytiques et implémenter une gestion des erreurs robuste.
- Utilisez toujours un seul DefaultPreloadManager.Builder pour créer vos instances de gestionnaire et de lecteur afin de vous assurer que les composants importants sont partagés.
- Implémentez le modèle de fenêtre coulissante en gérant activement les appels add() et remove() pour éviter l'erreur OutOfMemoryError.
- Utilisez TargetPreloadStatusControl pour créer une stratégie de préchargement intelligente et échelonnée qui équilibre les performances et la consommation de ressources.
Suite de la partie 3 : mise en cache avec des contenus multimédias préchargés
Le préchargement des données en mémoire offre un avantage immédiat en termes de performances, mais peut entraîner des compromis. Une fois l'application fermée ou le contenu multimédia préchargé supprimé du gestionnaire, les données disparaissent. Pour obtenir un niveau d'optimisation plus persistant, nous pouvons combiner le préchargement avec la mise en cache sur disque. Cette fonctionnalité est en cours de développement et sera disponible dans quelques mois.
Avez-vous des commentaires à partager ? Nous avons hâte de vous lire.
Restez à l'écoute et accélérez la lecture de vos vidéos ! 🚀
Lire la suite
-
Actualités des produits
Dans les applications multimédias d'aujourd'hui, il est essentiel de proposer une expérience de lecture fluide et ininterrompue pour offrir 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.
Mayuri Khinvasara Khabya • Temps de lecture : 8 min
-
Actualités des produits
Proposer l'expérience Google Play la plus sécurisée et la plus fiable possible. Aujourd'hui, nous annonçons un nouvel ensemble de modifications des règles et une fonctionnalité de transfert de compte pour renforcer la confidentialité des utilisateurs et protéger votre entreprise contre la fraude.
Bennet Manuel • Temps de lecture : 3 min
-
Actualités des produits
Il n'a jamais été aussi simple de tester les interactions multi-appareils avec l'émulateur Android.
Steven Jenkins • Temps de lecture : 2 min
Restez informé
Recevez chaque semaine les dernières informations sur le développement Android directement dans votre boîte de réception.