Medienwiedergabe verbessern: Ein genauer 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 hoher Reaktionsfähigkeit und niedriger Latenz erstellen.
- In Teil 1: Vorabladen mit Media3 wurden die Grundlagen behandelt. 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 Informationen erhalten, produktionsreife Best Practices wie die gemeinsame Nutzung von Kernkomponenten mit ExoPlayer implementieren und das Sliding-Window-Muster beherrschen, um den Arbeitsspeicher effektiv zu verwalten.
- Teil 3: Im letzten Teil dieser Reihe geht es um die Einbindung von PreloadManager in einen nichtflüchtigen Datenträger-Cache. So können Sie den Datenverbrauch durch Ressourcenverwaltung reduzieren und eine nahtlose Nutzung ermöglichen.
Wenn Sie neu beim Vorabladen in Media3 sind, empfehlen wir Ihnen dringend, Teil 1 zu lesen, bevor Sie fortfahren. Wenn du bereit bist, über die Grundlagen hinauszugehen, sehen wir uns an, wie du deine Implementierung der Medienwiedergabe verbessern kannst.
Zuhören: Analysedaten mit PreloadManagerListener abrufen
Wenn Sie eine Funktion in der Produktion einführen möchten, möchten Sie als App-Entwickler auch die Analysen dahinter verstehen und erfassen. Wie können Sie sicher sein, dass Ihre Preloading-Strategie in einer realen Umgebung effektiv ist? Um diese Frage zu beantworten, sind Daten zu Erfolgsquoten, Fehlern und Leistung erforderlich. Die Schnittstelle PreloadManagerListener ist der primäre Mechanismus zum Erfassen dieser Daten.
Der PreloadManagerListener bietet zwei wichtige Callbacks, die wichtige Informationen zum Vorabladen und zum Status liefern.
- onCompleted(MediaItem mediaItem): Dieser Callback wird aufgerufen, wenn eine Vorab-Ladeanfrage erfolgreich abgeschlossen wurde, wie in TargetPreloadStatusControl definiert.
- onError(PreloadException error): Dieser Callback kann für die Fehlerbehebung und das Monitoring nützlich sein. Sie wird aufgerufen, wenn ein Vorabladen fehlschlägt, und stellt die zugehörige Ausnahme bereit.
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)Erkenntnisse aus dem Zuhörer gewinnen
Diese Listener-Callbacks können mit Ihrer Analyse-Pipeline verknüpft werden. Wenn Sie diese Ereignisse an Ihre Analyse-Engine weiterleiten, können Sie wichtige Fragen wie diese beantworten:
- Wie hoch ist unsere Erfolgsquote beim Vorabladen? (Verhältnis von onCompleted-Ereignissen zu den Vorabladeversuchen insgesamt)
- Welche CDNs oder Videoformate weisen 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 Vorabladeversuchen)
Diese Daten können Ihnen quantitatives Feedback zu Ihrer Preloading-Strategie liefern und so A/B-Tests und datengestützte Verbesserungen der Nutzerfreundlichkeit ermöglichen. Anhand dieser Daten können Sie die Dauer des Vorabladens, die Anzahl der vorab zu ladenden Videos und die zugewiesenen Zwischenspeicher intelligent optimieren.
Über das Debugging hinaus: „onError“ für einen reibungslosen UI-Fallback verwenden
Ein fehlgeschlagenes Vorabladen ist ein starker Indikator für ein bevorstehendes Zwischenspeichern 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 das nächste Video beispielsweise nicht vorab geladen werden kann, könnte Ihre Anwendung die automatische Wiedergabe für das nächste Wischen deaktivieren, sodass der Nutzer tippen muss, um die Wiedergabe zu starten.
Außerdem können Sie durch Prüfen des Typs PreloadException eine intelligentere Wiederholungsstrategie definieren. Eine App kann eine fehlerhafte Quelle anhand der Fehlermeldung oder des HTTP-Statuscodes sofort aus dem Manager entfernen. Das Element müsste entsprechend aus dem UI-Stream entfernt werden, damit Ladeprobleme nicht die Nutzerfreundlichkeit beeinträchtigen. Sie können auch detailliertere Daten aus „PreloadException“ wie HttpDataSourceException abrufen, um die Fehler genauer zu untersuchen. Weitere Informationen zur Fehlerbehebung bei ExoPlayer
Das Buddy-System: Warum ist die Freigabe von Komponenten für ExoPlayer erforderlich?
Der DefaultPreloadManager und ExoPlayer sind so konzipiert, dass sie zusammenarbeiten. Um Stabilität und Effizienz zu gewährleisten, müssen sie mehrere zentrale Komponenten gemeinsam nutzen. Wenn sie mit separaten, unkoordinierten Komponenten arbeiten, kann dies die Thread-Sicherheit und die Nutzbarkeit von vorab geladenen Titeln auf dem Player beeinträchtigen, da wir sicherstellen müssen, dass vorab geladene Titel auf dem richtigen Player abgespielt werden. Die separaten Komponenten könnten auch um begrenzte Ressourcen wie Netzwerkbandbreite und Arbeitsspeicher konkurrieren, was zu Leistungseinbußen führen könnte. Ein wichtiger Teil des Lebenszyklus ist die ordnungsgemäße Entsorgung. Die empfohlene Reihenfolge ist, zuerst den PreloadManager und dann den ExoPlayer freizugeben.
Der DefaultPreloadManager.Builder wurde entwickelt, um diese Freigabe zu erleichtern. Er bietet APIs, mit denen sowohl der PreloadManager als auch eine verknüpfte Player-Instanz instanziiert werden können. 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 der PreloadManager und der Player separate Instanzen verwenden, sind sie sich der Netzwerkaktivität des jeweils anderen nicht bewusst, was zu Fehlern führen kann. Stellen Sie sich beispielsweise ein Szenario vor, in dem ein Nutzer ein Video ansieht, die Netzwerkverbindung schlechter wird und die MediaSource für das Vorabladen gleichzeitig einen aggressiven Download für ein zukünftiges Video initiiert. Die Aktivität der vorab geladenen MediaSource würde Bandbreite verbrauchen, die vom aktiven Player benötigt wird, was dazu führen würde, dass das aktuelle Video ins Stocken gerät. Ein Stocken während der Wiedergabe ist ein erheblicher Fehler bei der Nutzererfahrung.
Durch die gemeinsame Nutzung eines einzelnen BandwidthMeter kann der TrackSelector während des Vorabladens oder der Wiedergabe die Tracks mit der höchsten Qualität auswählen, die den aktuellen Netzwerkbedingungen und dem Status des Puffers entsprechen. Anschließend kann es intelligente Entscheidungen treffen, um die aktive Wiedergabesitzung zu schützen und für eine reibungslose Wiedergabe zu sorgen.
preloadManagerBuilder.setBandwidthMeter(customBandwidthMeter)
Konsistenz mit freigegebenen LoadControl-, TrackSelector- und Renderer-Komponenten von ExoPlayer sicherstellen
- LoadControl: Diese Komponente bestimmt die Zwischenspeicherungsrichtlinie, z. B. wie viele Daten vor dem Start der Wiedergabe zwischengespeichert werden sollen und wann das Laden weiterer Daten gestartet oder beendet werden soll. Durch die gemeinsame Nutzung von LoadControl wird sichergestellt, dass der Arbeitsspeicherverbrauch des Players und des PreloadManagers durch eine einzige, koordinierte Zwischenspeicherungsstrategie für vorab geladene und aktiv wiedergegebene Medien gesteuert wird. So werden Ressourcenkonflikte vermieden. Sie müssen die Puffergröße so festlegen, dass sie mit der Anzahl der vorab geladenen Elemente und der Dauer übereinstimmt, um Konsistenz zu gewährleisten. In Zeiten von Konflikten priorisiert der Player die Wiedergabe des aktuellen Elements, das auf dem Bildschirm angezeigt wird. Bei einer freigegebenen LoadControl wird der Preload-Manager so lange vorab geladen, bis die für das Vorabladen zugewiesenen Zielpufferbytes die Obergrenze erreicht haben. Er wartet nicht, bis das Laden für die Wiedergabe abgeschlossen ist.
Hinweis: Durch die Freigabe von LoadControl in der aktuellen Version von Media3 (1.8) wird sichergestellt, dass der Allocator korrekt mit PreloadManager und dem Player geteilt werden kann. Die Möglichkeit, das Vorladen mit „LoadControl“ effektiv zu steuern, wird in der kommenden Media3-Version 1.9 verfügbar sein.
preloadManagerBuilder.setLoadControl(customLoadControl)
- TrackSelector: Diese Komponente ist für die Auswahl der zu ladenden und abzuspielenden Tracks verantwortlich, z. B. Video mit einer bestimmten Auflösung, Audio in einer bestimmten Sprache. Durch die Freigabe wird sichergestellt, dass die beim Vorabladen ausgewählten Tracks mit denen übereinstimmen, die der Player verwendet. So wird vermieden, dass ein 480p-Videotrack vorab geladen wird, nur damit der Player ihn sofort verwirft und bei der Wiedergabe einen 720p-Track abruft.< br /> Der PreloadManager sollte NICHT dieselbe Instanz von TrackSelector wie der Player verwenden. Stattdessen sollten sie eine andere 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. Anhand dieses Blueprints wird geprüft, welche Video-, Audio- und Textformate der endgültige Player unterstützt. So kann er auf intelligente Weise nur den kompatiblen Medientrack auswählen und herunterladen und verhindert, dass Bandbreite für Inhalte verschwendet wird, die der Player nicht abspielen kann.
preloadManagerBuilder.setRenderersFactory(customRenderersFactory)
Weitere Informationen zu ExoPlayer-Komponenten
Die goldene Regel: Ein gemeinsamer Playback Looper für alle
Der Thread, über den auf eine ExoPlayer-Instanz zugegriffen werden kann, kann explizit angegeben werden, indem beim Erstellen des Players ein Looper übergeben wird. Der Looper des Threads, über den auf den Player zugegriffen werden muss, kann mit Player.getApplicationLooper abgefragt werden. Durch die Verwendung eines gemeinsamen Loopers für den Player und den PreloadManager wird sichergestellt, dass alle Vorgänge für diese freigegebenen Medienobjekte in der Nachrichtenwarteschlange eines einzelnen Threads serialisiert werden. Dadurch können Nebenläufigkeitsfehler reduziert werden.
Alle Interaktionen zwischen dem PreloadManager und dem Player mit Medienquellen, die geladen oder vorab geladen werden sollen, müssen im selben Wiedergabe-Thread erfolgen. Die Freigabe des Looper ist für die Thread-Sicherheit erforderlich. Daher müssen wir den PlaybackLooper zwischen dem PreloadManager und dem Player freigeben.
Der PreloadManager bereitet im Hintergrund ein zustandsorientiertes MediaSource-Objekt vor. Wenn Ihr UI-Code „player.setMediaSource(mediaSource)“ aufruft, wird dieses komplexe, zustandsorientierte Objekt von der vorab geladenen MediaSource an den Player übergeben. In diesem Szenario wird die gesamte „PreloadMediaSource“ vom Manager zum Player verschoben. Alle diese Interaktionen und Übergaben sollten auf demselben PlaybackLooper erfolgen.
Wenn der PreloadManager und der ExoPlayer in verschiedenen Threads ausgeführt werden, kann es zu einer Race-Bedingung kommen. Der Thread des PreloadManager könnte den internen Status der MediaSource ändern (z. B. neue Daten in einen Zwischenspeicher schreiben), während der Thread des Players versucht, daraus zu lesen. Dies führt zu unvorhersehbarem Verhalten und einer IllegalStateException, die schwer zu beheben ist.
preloadManagerBuilder.setPreloadLooper(playbackLooper)
Sehen wir uns an, wie Sie alle oben genannten Komponenten zwischen ExoPlayer und DefaultPreloadManager in der Einrichtung selbst freigeben 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 wie DefaultLoadControl usw. verwenden, müssen Sie sie nicht explizit mit DefaultPreloadManager teilen. Wenn Sie Ihre ExoPlayer-Instanz über buildExoPlayer von 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 über die oben genannten APIs explizit darüber informieren.
Produktionsreifes Vorabladen: Das Muster des gleitenden Fensters
In einem dynamischen Feed können Nutzer durch eine nahezu unendliche Menge an Inhalten scrollen. Wenn Sie dem DefaultPreloadManager kontinuierlich Videos hinzufügen, ohne eine entsprechende Entfernungsstrategie zu implementieren, führt dies unweigerlich zu einem OutOfMemoryError. Jede vorab geladene „MediaSource“ enthält eine SampleQueue, die Speicherpuffer zuweist. Wenn sich diese ansammeln, kann der Heap-Speicher der Anwendung erschöpft werden. Die Lösung ist ein Algorithmus, den Sie vielleicht schon kennen: das gleitende Fenster. Beim Sliding-Window-Muster wird eine kleine, überschaubare Anzahl von Elementen im Speicher gehalten, die logisch an die aktuelle Position des Nutzers im Feed angrenzen. Wenn der Nutzer scrollt, wird dieses „Fenster“ mit den verwalteten Elementen mitbewegt. Neue Elemente, die sichtbar werden, werden hinzugefügt und Elemente, die jetzt weit entfernt sind, werden entfernt.
Muster für gleitende Zeitfenster 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. In der Anwendungslogik müssen UI-Ereignisse wie Scrollen oder Seitenwechsel mit diesen API-Aufrufen verknüpft werden. Wenn Sie eine Codereferenz dafür benötigen, finden Sie dieses Sliding-Window-Muster im Beispiel socialite, das auch einen PreloadManagerWrapper enthält, der ein Sliding Window imitiert.
Vergessen Sie nicht, preloadManager.remove(mediaItem) in Ihrer Implementierung hinzuzufügen, wenn es unwahrscheinlich ist, dass das Element bald vom Nutzer angesehen wird. Wenn Elemente, die sich nicht mehr in der Nähe des Nutzers befinden, nicht entfernt werden, ist das die Hauptursache für Speicherprobleme bei Vorablade-Implementierungen. Durch den Aufruf von „remove()“ werden Ressourcen freigegeben, damit die Arbeitsspeichernutzung Ihrer App begrenzt und stabil bleibt.
Kategorisierte Vorabladestrategie mit TargetPreloadStatusControl optimieren
Nachdem wir definiert haben, was vorab geladen werden soll (die Elemente in unserem Fenster), können wir eine klar definierte Strategie dafür anwenden, wie viel für jedes Element vorab geladen werden soll. Wir haben bereits in Teil 1 gesehen, wie diese Granularität mit der TargetPreloadStatusControl-Einrichtung erreicht werden kann.
Zur Erinnerung: Ein Element an Position +/- 1 hat möglicherweise eine höhere Wahrscheinlichkeit, abgespielt zu werden, als ein Element an Position +/- 4. Sie könnten Elementen, die sich der Nutzer wahrscheinlich als Nächstes ansieht, mehr Ressourcen (Netzwerk, CPU, Arbeitsspeicher) zuweisen. Dadurch wird eine „Preloading“-Strategie basierend auf der Nähe erstellt, die der Schlüssel zum Ausgleich zwischen sofortiger Wiedergabe und effizienter Ressourcennutzung ist.
Sie können Analysedaten über PreloadManagerListener verwenden, wie in den vorherigen Abschnitten beschrieben, um Ihre Strategie für die Vorabladedauer festzulegen.
Fazit und weitere Informationen
Sie verfügen jetzt über das nötige Fachwissen, um mit dem DefaultPreloadManager von Media3 schnelle, stabile und ressourceneffiziente Medienfeeds zu erstellen.
Fassen wir die wichtigsten Punkte 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. So wird sichergestellt, dass wichtige Komponenten gemeinsam genutzt werden.
- Implementieren Sie das Sliding-Window-Muster, indem Sie die Aufrufe von „add()“ und „remove()“ aktiv verwalten, um einen OutOfMemoryError zu vermeiden.
- Mit TargetPreloadStatusControl können Sie eine intelligente, abgestufte Vorabladestrategie erstellen, die Leistung und Ressourcenverbrauch in Einklang bringt.
Nächster Teil 3: Caching mit vorab geladenen Medien
Das Vorabladen von Daten in den Arbeitsspeicher bietet einen sofortigen Leistungsvorteil, kann aber auch Nachteile haben. Sobald die Anwendung geschlossen oder die vorab geladenen Medien aus dem Manager entfernt werden, sind die Daten weg. Um eine dauerhaftere Optimierung zu erreichen, können wir das Vorabladen mit dem Datenträger-Caching kombinieren. Diese Funktion wird aktiv weiterentwickelt und ist in einigen Monaten verfügbar.
Möchten Sie uns Feedback geben? Wir freuen uns auf Ihre Rückmeldung.
Bleib dran und mach deine Videowiedergabe schneller! 🚀
-
ProduktneuigkeitenIn den heutigen medienorientierten Apps ist eine reibungslose, unterbrechungsfreie Wiedergabe entscheidend für eine gute Nutzererfahrung. Nutzer erwarten, dass Videos sofort starten und ohne Unterbrechungen wiedergegeben werden.
Mayuri Khinvasara Khabya • Lesezeit: 8 Minuten -
ProduktneuigkeitenApps, die auf die kommende Android-Version ausgerichtet sind, können mit der Berechtigung WRITE_CONTACTS nicht mehr direkt in Kontakte schreiben.
Roxanna Aliabadi Walker • Lesezeit: 6 Minuten -
ProduktneuigkeitenDie Entwicklung von KI-Agenten schreitet rasant voran und die Android-Entwicklung erreicht einen spannenden neuen Meilenstein: von der unterstützenden Codevervollständigung hin zu KI-Agenten, die unter Ihrer Anleitung End-to-End-Aufgaben ausführen. Sie haben die volle Auswahl und Flexibilität bei den Modellen und Agents, die Sie verwenden.
Jason Buchel • Lesezeit: 3 Minuten
Lassen Sie sich Woche für Woche die neuesten Informationen zur Android-Entwicklung zusenden.