Neuigkeiten zum Produkt
Optimierte Medienwiedergabe: Ein detaillierter Blick auf den PreloadManager von Media3 – Teil 2
Lesezeit: 9 Minuten
Willkommen zum zweiten Teil unserer dreiteiligen Reihe zum Vorabladen von Medien mit Media3. In dieser Reihe erfahren Sie, wie Sie in Ihren Android-Apps Medien mit geringer Latenz und hoher Reaktionsfähigkeit bereitstellen können.
- Teil 1: Einführung in das Vorabladen mit Media3 behandelte die Grundlagen. Wir haben den Unterschied zwischen PreloadConfiguration für einfache Playlists und dem leistungsstärkeren DefaultPreloadManager für dynamische Benutzeroberflächen erläutert. Sie haben gelernt, wie Sie den grundlegenden API-Lebenszyklus implementieren: Medien mit add() hinzufügen, eine vorbereitete MediaSource mit getMediaSource() abrufen, Prioritäten mit setCurrentPlayingIndex() und invalidate() verwalten und Ressourcen mit remove() und release() freigeben.
- Teil 2 (dieser Beitrag): In diesem Blogbeitrag werden die erweiterten Funktionen des DefaultPreloadManager vorgestellt. Wir zeigen Ihnen, wie Sie mit PreloadManagerListener Statistiken abrufen, Best Practices für die Produktion implementieren, z. B. die Freigabe von Kernkomponenten mit ExoPlayer, und das Muster des gleitenden Fensters verwenden, um den Arbeitsspeicher effektiv zu verwalten.
- Teil 3: Im letzten Teil dieser Reihe geht es um die Integration von PreloadManager mit einem nichtflüchtigen Datenträger-Cache. So können Sie den Datenverbrauch mit der Ressourcenverwaltung reduzieren und eine nahtlose Nutzererfahrung bieten.
Wenn Sie noch keine Erfahrung mit dem Vorabladen in Media3 haben, empfehlen wir Ihnen, zuerst Teil 1 zu lesen. Wenn Sie die Grundlagen bereits beherrschen, erfahren Sie hier, wie Sie die Medienwiedergabe weiter optimieren können.
Statistiken abrufen mit PreloadManagerListener
Wenn Sie eine Funktion in der Produktion einführen möchten, möchten Sie als App-Entwickler auch die entsprechenden Statistiken erfassen und analysieren. Wie können Sie sicher sein, dass Ihre Strategie zum Vorabladen in einer realen Umgebung effektiv ist? Um diese Frage zu beantworten, benötigen Sie Daten zu Erfolgsraten, Fehlern und Leistung. Die Schnittstelle PreloadManagerListener ist der primäre Mechanismus zum Erfassen dieser Daten.
PreloadManagerListener bietet zwei wichtige Callbacks, die wichtige Einblicke in den Vorabladeprozess und -status liefern.
- onCompleted(MediaItem mediaItem): Dieser Callback wird aufgerufen, wenn eine Vorabladenanfrage erfolgreich abgeschlossen wurde, wie in Ihrem TargetPreloadStatusControl definiert.
- onError(PreloadException error): Dieser Callback kann für die Fehlerbehebung und das Monitoring nützlich sein. Er wird aufgerufen, wenn eine Vorabladung fehlschlägt, und liefert die zugehörige Ausnahme.
Sie können einen Listener mit einem einzigen Methodenaufruf registrieren, wie im folgenden Beispielcode gezeigt:
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)
Statistiken aus dem Listener extrahieren
Diese Listener-Callbacks können mit Ihrer Analysepipeline verknüpft werden. Wenn Sie diese Ereignisse an Ihre Analyse-Engine weiterleiten, können Sie wichtige Fragen beantworten, z. B.:
- Wie hoch ist unsere Erfolgsrate beim Vorabladen? (Verhältnis von onCompleted-Ereignissen zu allen Vorabladungsversuchen)
- Bei welchen CDNs oder Videoformaten treten die höchsten Fehlerraten auf? (Durch Parsen der Ausnahmen von onError)
- Wie hoch ist unsere Fehlerrate beim Vorabladen? (Verhältnis von onError-Ereignissen zu allen Vorabladungsversuchen)
Diese Daten können Ihnen quantitatives Feedback zu Ihrer Strategie zum Vorabladen geben und A/B-Tests sowie datengesteuerte Verbesserungen der Nutzererfahrung ermöglichen. Außerdem können Sie damit die Dauer des Vorabladens und die Anzahl der Videos, die Sie vorabladen möchten, sowie die zugewiesenen Puffer intelligent optimieren.
Mehr als nur Fehlerbehebung: onError für einen reibungslosen UI-Fallback verwenden
Eine fehlgeschlagene Vorabladung ist ein starker Hinweis auf ein bevorstehendes Pufferereignis für den Nutzer. Mit dem onError-Callback können Sie reaktiv reagieren. Anstatt den Fehler nur zu protokollieren, können Sie die Benutzeroberfläche anpassen. Wenn beispielsweise das nächste Video nicht vorabgeladen werden kann, kann Ihre Anwendung die automatische Wiedergabe für den nächsten Swipe deaktivieren, sodass der Nutzer auf das Video tippen muss, um die Wiedergabe zu starten.
Außerdem können Sie durch Untersuchen des PreloadException-Typs eine intelligentere Strategie für Wiederholungsversuche definieren. Eine App kann eine fehlerhafte Quelle basierend auf der Fehlermeldung oder dem HTTP-Statuscode sofort aus dem Manager entfernen. Das Element muss entsprechend aus dem UI-Stream entfernt werden, damit keine Ladeprobleme die Nutzererfahrung beeinträchtigen. Sie können auch detailliertere Daten aus PreloadException abrufen, z. B. die HttpDataSourceException, um die Fehler genauer zu untersuchen. Weitere Informationen zur Fehlerbehebung in ExoPlayer.
Warum ist es notwendig, Komponenten mit ExoPlayer zu teilen?
Der DefaultPreloadManager und ExoPlayer sind so konzipiert, dass sie zusammenarbeiten. Um Stabilität und Effizienz zu gewährleisten, müssen sie mehrere Kernkomponenten gemeinsam nutzen. Wenn sie mit separaten, nicht koordinierten Komponenten arbeiten, kann dies die Threadsicherheit und die Nutzbarkeit vorabgeladener Titel im Player beeinträchtigen, da vorabgeladene Titel auf dem richtigen Player wiedergegeben werden müssen. Die separaten Komponenten können auch um begrenzte Ressourcen wie Netzwerkbandbreite und Arbeitsspeicher konkurrieren, was zu einer Leistungsminderung führen kann. Ein wichtiger Teil des Lebenszyklus ist die ordnungsgemäße Freigabe. Die empfohlene Reihenfolge ist, zuerst den PreloadManager und dann den ExoPlayer freizugeben.
Der DefaultPreloadManager.Builder wurde entwickelt, um diese Freigabe zu erleichtern, und bietet APIs zum Instanziieren sowohl Ihres PreloadManager als auch einer verknüpften Player-Instanz. Sehen wir uns an, warum Komponenten wie BandwidthMeter, LoadControl, TrackSelector und Looper gemeinsam genutzt werden müssen. Sehen Sie sich die visuelle Darstellung der Interaktion dieser Komponenten mit der ExoPlayer-Wiedergabe an.
Bandbreitenkonflikte mit einem gemeinsam genutzten BandwidthMeter vermeiden
Der BandwidthMeter liefert eine Schätzung der verfügbaren Netzwerkbandbreite basierend auf bisherigen Übertragungsraten. Wenn PreloadManager und Player separate Instanzen verwenden, sind sie sich der Netzwerkaktivität des jeweils anderen nicht bewusst, was zu Fehlerszenarien führen kann. Nehmen wir beispielsweise an, ein Nutzer sieht sich ein Video an, die Netzwerkverbindung wird schlechter und die vorabgeladene MediaSource startet gleichzeitig einen aggressiven Download für ein zukünftiges Video. Die Aktivität der vorabgeladenen MediaSource würde die Bandbreite verbrauchen, die vom aktiven Player benötigt wird, wodurch das aktuelle Video angehalten wird. Ein Anhalten während der Wiedergabe ist ein erhebliches Problem für die Nutzererfahrung.
Durch die gemeinsame Nutzung eines einzelnen BandwidthMeter kann der TrackSelector Titel in der höchsten Qualität auswählen, die unter den aktuellen Netzwerkbedingungen und dem Status des Zwischenspeichers möglich ist, sowohl während des Vorabladens als auch während der Wiedergabe. So kann er intelligente Entscheidungen treffen, um die aktive Wiedergabesitzung zu schützen und eine reibungslose Nutzererfahrung zu gewährleisten.
preloadManagerBuilder.setBandwidthMeter(customBandwidthMeter)
Konsistenz mit gemeinsam genutzten LoadControl-, TrackSelector- und Renderer-Komponenten von ExoPlayer gewährleisten
- LoadControl: Diese Komponente bestimmt die Pufferrichtlinie, z. B. wie viele Daten vor dem Start der Wiedergabe gepuffert werden sollen und wann weitere Daten geladen werden sollen. Durch die gemeinsame Nutzung von LoadControl wird sichergestellt, dass der Speicherverbrauch von Player und PreloadManager durch eine einheitliche, koordinierte Pufferstrategie für vorabgeladene und aktiv wiedergegebene Medien gesteuert wird, wodurch Ressourcenkonflikte vermieden werden. Sie müssen die Puffergröße intelligent zuweisen und dabei berücksichtigen, wie viele Elemente Sie vorabladen und wie lange das Vorabladen dauert, um Konsistenz zu gewährleisten. Bei Konflikten priorisiert der Player die Wiedergabe des aktuellen Elements, das auf dem Bildschirm angezeigt wird. Mit einem gemeinsam genutzten LoadControl lädt der PreloadManager so lange vorab, bis das für das Vorabladen zugewiesene Zielpufferlimit erreicht ist. Er wartet nicht, bis das Laden für die Wiedergabe abgeschlossen ist.
Hinweis: Durch die gemeinsame Nutzung von LoadControl in der neuesten Version von Media3 (1.8) wird sichergestellt, dass der Allocator korrekt mit PreloadManager und Player geteilt werden kann. Die Verwendung von LoadControl zur effektiven Steuerung des Vorabladens ist eine Funktion, die in der kommenden Version Media3 1.9 verfügbar sein wird.
preloadManagerBuilder.setLoadControl(customLoadControl)
- TrackSelector: Diese Komponente ist dafür verantwortlich, auszuwählen, welche Titel geladen und wiedergegeben werden sollen (z. B. Video mit einer bestimmten Auflösung, Audio in einer bestimmten Sprache). Durch die gemeinsame Nutzung wird sichergestellt, dass die während des Vorabladens ausgewählten Titel dieselben sind, die der Player verwenden wird. So wird ein Szenario vermieden, in dem ein 480p-Videotitel vorabgeladen wird, nur damit der Player ihn sofort verwirft und bei der Wiedergabe einen 720p-Titel abruft.< br /> Der PreloadManager sollte nicht dieselbe Instanz von TrackSelector mit dem Player teilen. Stattdessen sollten sie die unterschiedliche TrackSelector-Instanz, aber mit derselben Implementierung verwenden. Deshalb legen wir im DefaultPreloadManager.Builder die TrackSelectorFactory und nicht einen TrackSelector fest.
preloadManagerBuilder.setTrackSelectorFactory(customTrackSelectorFactory)
- Renderer: Diese Komponente ist dafür verantwortlich, die Funktionen des Players zu verstehen, ohne die vollständigen Renderer zu erstellen. Sie prüft anhand dieser Blaupause, welche Video-, Audio- und Textformate der endgültige Player unterstützt. So können nur die kompatiblen Medientitel ausgewählt und heruntergeladen werden. Außerdem wird Bandbreite für Inhalte gespart, die der Player nicht wiedergeben kann.
preloadManagerBuilder.setRenderersFactory(customRenderersFactory)
Weitere Informationen zu ExoPlayer-Komponenten
Die goldene Regel: Ein gemeinsamer PlaybackLooper für alle
Der Thread, auf dem auf eine ExoPlayer-Instanz zugegriffen werden kann, kann explizit angegeben werden, indem beim Erstellen des Players ein Looper übergeben wird. Der Looper des Threads, von dem aus auf den Player zugegriffen werden muss, kann mit Player.getApplicationLooper abgefragt werden. Durch die gemeinsame Nutzung eines Loopers zwischen Player und PreloadManager wird sichergestellt, dass alle Vorgänge für diese gemeinsam genutzten Medienobjekte in die Nachrichtenwarteschlange eines einzelnen Threads serialisiert werden. So können Fehler bei der Parallelität reduziert werden.
Alle Interaktionen zwischen PreloadManager und Player mit Medienquellen, die geladen oder vorabgeladen werden sollen, müssen im selben Wiedergabe-Thread erfolgen. Die gemeinsame Nutzung des Loopers ist für die Threadsicherheit unerlässlich. Daher müssen wir den PlaybackLooper zwischen PreloadManager und Player teilen.
Der PreloadManager bereitet im Hintergrund ein zustandsbehaftetes MediaSource-Objekt vor. Wenn Ihr UI-Code player.setMediaSource(mediaSource) aufruft, übergeben Sie dieses komplexe, zustandsorientierte Objekt von der vorabladenden MediaSource an den Player. In diesem Szenario wird die gesamte PreloadMediaSource vom Manager an den Player verschoben. Alle diese Interaktionen und Übergaben sollten im selben PlaybackLooper erfolgen.
Wenn PreloadManager und ExoPlayer in verschiedenen Threads ausgeführt werden, kann es zu einer Race-Bedingung kommen. Der Thread des PreloadManager könnte den internen Zustand der MediaSource ändern (z. B. neue Daten in einen Puffer schreiben), genau in dem Moment, in dem der Thread des Players versucht, daraus zu lesen. Dies führt zu unvorhersehbarem Verhalten und zu einer IllegalStateException, die schwer zu beheben ist.
preloadManagerBuilder.setPreloadLooper(playbackLooper)
Sehen wir uns an, wie Sie alle oben genannten Komponenten in der Einrichtung selbst zwischen ExoPlayer und DefaultPreloadManager teilen können.
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()
Tipp: Wenn Sie die Standardkomponenten in ExoPlayer verwenden, z. B. DefaultLoadControl, müssen Sie sie nicht explizit für DefaultPreloadManager freigeben. Wenn Sie Ihre ExoPlayer-Instanz über buildExoPlayer des DefaultPreloadManager.Builder erstellen, werden diese Komponenten automatisch miteinander verknüpft, wenn Sie die Standardimplementierungen mit Standardkonfigurationen verwenden. Wenn Sie jedoch benutzerdefinierte Komponenten oder Konfigurationen verwenden, sollten Sie den DefaultPreloadManager explizit über die oben genannten APIs darüber informieren.
Produktionsreifes Vorabladen: Das Muster des gleitenden Fensters
In einem dynamischen Feed kann ein Nutzer durch eine nahezu unendliche Menge an Inhalten scrollen. Wenn Sie dem DefaultPreloadManager kontinuierlich Videos hinzufügen, ohne eine entsprechende Strategie zum Entfernen zu verwenden, verursachen Sie unweigerlich einen OutOfMemoryError. Jede vorabgeladene MediaSource enthält eine SampleQueue, die Speicherpuffer zuweist. Wenn sich diese ansammeln, kann der Heap-Speicher der Anwendung erschöpft sein. Die Lösung ist ein Algorithmus, den Sie vielleicht schon kennen: das gleitende Fenster. Das Muster des gleitenden Fensters verwaltet eine kleine, überschaubare Menge von Elementen im Arbeitsspeicher, die sich logisch in der Nähe der aktuellen Position des Nutzers im Feed befinden. Wenn der Nutzer scrollt, gleitet dieses „Fenster“ verwalteter Elemente mit ihm. Dabei werden neue Elemente hinzugefügt, die in den Blick kommen, und Elemente entfernt, die sich jetzt weiter entfernt befinden.
Das Muster des gleitenden Fensters implementieren
Es ist wichtig zu wissen, dass PreloadManager keine integrierte setWindowSize()-Methode bietet. Das gleitende Fenster ist ein Designmuster, das Sie als Entwickler mit den primitiven Methoden add() und remove() implementieren müssen. Ihre Anwendungslogik muss UI-Ereignisse wie Scrollen oder Seitenwechsel mit diesen API-Aufrufen verknüpfen. Wenn Sie einen Codeverweis dafür benötigen, haben wir dieses Muster des gleitenden Fensters im Socialite-Beispiel implementiert. Es enthält auch einen PreloadManagerWrapper, der ein gleitendes Fenster imitiert.
Vergessen Sie nicht, preloadManager.remove(mediaItem) in Ihre Implementierung aufzunehmen, wenn das Element wahrscheinlich nicht mehr bald in der Ansicht des Nutzers angezeigt wird. Das Entfernen von Elementen, die sich nicht mehr in der Nähe des Nutzers befinden, ist die Hauptursache für Speicherprobleme bei Implementierungen zum Vorabladen. Der Aufruf von remove() sorgt dafür, dass Ressourcen freigegeben werden, mit denen Sie die Arbeitsspeichernutzung Ihrer App begrenzen und stabil halten können.
Eine kategorisierte Strategie zum Vorabladen mit TargetPreloadStatusControl optimieren
Nachdem wir definiert haben, was vorabgeladen werden soll (die Elemente in unserem Fenster), können wir eine genau definierte Strategie für die Menge an Daten anwenden, die für jedes Element vorabgeladen werden soll. Wie Sie diese Granularität mit der TargetPreloadStatusControl-Einrichtung erreichen, haben wir bereits in Teil 1 gesehen.
Zur Erinnerung: Ein Element an Position +/- 1 hat möglicherweise eine höhere Wahrscheinlichkeit, wiedergegeben zu werden, als ein Element an Position +/- 4. Sie können Elementen, die der Nutzer am wahrscheinlichsten als Nächstes ansehen wird, mehr Ressourcen (Netzwerk, CPU, Arbeitsspeicher) zuweisen. So entsteht eine Strategie zum Vorabladen basierend auf der Nähe, die der Schlüssel ist, um die sofortige Wiedergabe mit einer effizienten Ressourcennutzung in Einklang zu bringen.
Sie können Analysedaten über PreloadManagerListener verwenden, wie in den vorherigen Abschnitten beschrieben, um Ihre Strategie für die Dauer des Vorabladens zu bestimmen.
Fazit und nächste Schritte
Sie verfügen jetzt über das erweiterte Wissen, um mit dem DefaultPreloadManager von Media3 schnelle, stabile und ressourceneffiziente Media-Feeds zu erstellen.
Fassen wir die wichtigsten Erkenntnisse zusammen:
- Verwenden Sie PreloadManagerListener, um Analysedaten zu erfassen und eine robuste Fehlerbehandlung zu implementieren.
- Verwenden Sie immer einen einzelnen DefaultPreloadManager.Builder, um sowohl Ihre Manager- als auch Ihre Player-Instanzen zu erstellen, damit wichtige Komponenten gemeinsam genutzt werden.
- Implementieren Sie das Muster des gleitenden Fensters, indem Sie add()- und remove()-Aufrufe aktiv verwalten, um OutOfMemoryError zu vermeiden.
- Verwenden Sie TargetPreloadStatusControl, um eine intelligente, mehrstufige Strategie zum Vorabladen zu erstellen, die Leistung und Ressourcenverbrauch in Einklang bringt.
Nächster Schritt in Teil 3: Caching mit vorabgeladenen Medien
Das Vorabladen von Daten in den Arbeitsspeicher bietet sofortige Leistungsvorteile, kann aber auch Nachteile haben. Sobald die Anwendung geschlossen oder die vorabgeladenen Medien aus dem Manager entfernt werden, sind die Daten verloren. Um eine dauerhaftere Optimierung zu erreichen, können wir das Vorabladen mit dem Festplattencaching kombinieren. Diese Funktion wird aktiv entwickelt und wird in einigen Monaten verfügbar sein.
Haben Sie Feedback für uns, das Sie teilen möchten? Wir freuen uns auf Ihre Meinung.
Bleiben Sie auf dem Laufenden und beschleunigen Sie die Videowiedergabe! 🚀
Weiterlesen
-
Neuigkeiten zum Produkt
In den heutigen mediengestützten Apps ist eine reibungslose, unterbrechungsfreie Wiedergabe entscheidend für eine positive Nutzererfahrung. Nutzer erwarten, dass ihre Videos sofort starten und ohne Pausen wiedergegeben werden.
Mayuri Khinvasara Khabya • Lesezeit: 8 Minuten
-
Neuigkeiten zum Produkt
Android Studio Panda 4 ist jetzt stabil und kann für die Produktion verwendet werden. Diese Version bietet unter anderem den Planungsmodus und die Vorhersage der nächsten Bearbeitungsschritte, mit denen Sie hochwertige Android-Apps einfacher als je zuvor erstellen können.
Matt Dyor • Lesezeit: 5 Minuten
-
Neuigkeiten zum Produkt
Wenn Sie Android-Entwickler sind und innovative KI-Funktionen in Ihre App implementieren möchten, haben wir vor Kurzem leistungsstarke neue Updates veröffentlicht.
Thomas Ezan • Lesezeit: 3 Minuten
Auf dem Laufenden bleiben
Lassen Sie sich Woche für Woche die neuesten Informationen zur Android-Entwicklung zusenden.