Der Medienkonsum macht einen großen Teil der Zeit aus, die Nutzer auf Mobilgeräten verbringen. Daher muss die Wiedergabe einwandfrei, hocheffizient und akkuschonend sein. Außerdem erwarten Nutzer, dass diese Erlebnisse nahtlos auf allen ihren Geräten funktionieren, von Smartphones bis hin zu Fernsehern im Wohnzimmer und darüber hinaus.
Hier erfährst du, wie Architekturupdates in Media3 und ExoPlayer eine nahtlose Wiedergabe auf verschiedenen Formfaktoren ermöglichen. Im Mittelpunkt der Diskussion stehen die Erstellung responsiver Benutzeroberflächen mit dem Media3 Compose UI-Toolkit, die Vermeidung von Zwischenspeichern in Feeds mit dem neuen PreloadManager und die Erweiterung Ihrer App auf Wohnzimmerbildschirme und Android XR mit CastPlayer und Spatial Audio.
Transkript
Okay.
Hallo zusammen!
Ich heiße Toni.
Ich bin Engineer im Android-Medienteam.
Zusammen mit Kristina und Donovan werde ich darüber sprechen, wie Jetpack Media3 euch bei allen Medienanforderungen unter Android unterstützen kann.
Media3 ist die Android-Supportbibliothek für alles, was mit Medien zu tun hat, sei es die Wiedergabe mit ExoPlayer, das Teilen von Medien mit MediaSession und Cast, das Bearbeiten von Medien mit Transformer oder das Prüfen von Medien, z. B. das Extrahieren von Frames oder Metadaten.
Media3 deckt diesen gesamten Bereich in einer einzigen Bibliotheksgruppe ab.
Als Support-Bibliothek ist sie in Ihrer App enthalten.
Das bedeutet, dass wir für Konsistenz sorgen können, sodass derselbe Code auf allen Betriebssystemversionen und allen Geräten ausgeführt wird.
Außerdem wird die Aktualisierbarkeit verbessert.
Jede Fehlerkorrektur, die wir vornehmen, reicht bis zu API 24 und ist nicht an einen Betriebssystem-Releasezyklus gebunden.
Und als Support-Bibliothek können wir sehr umfangreiche Anpassungsoptionen zulassen, die im Android-Framework nicht möglich wären.
Als Bibliothek auf GitHub sind wir in erster Linie Open Source.
Das bedeutet, dass jede Änderung, die wir vornehmen, sofort auf GitHub verfügbar ist.
Wir haben einen aktiven Issue Tracker und akzeptieren Pull-Anfragen.
Das Wichtigste bei all diesen Anwendungsfällen ist, dass wir uns um alle Detailebenen kümmern, von allgemeinen UI-Elementen bis hin zu einfachen Standard-APIs, aber auch umfassende Anpassungsoptionen, eine breite Unterstützung für verschiedene Formate und Standards sowie die Leistung und Effizienz, die dem Ganzen zugrunde liegen.
Media3 deckt diesen gesamten Bereich ab.
Heute möchten wir einige dieser Themen vertiefen.
Wir sprechen über unser neues Compose UI-Modul, den neuen CastPlayer, der das Teilen mit Mediensitzungen noch einfacher macht, eine überarbeitete Bearbeitungspipeline mit neuen erweiterten Effekten, neue Tools zum Abrufen von Metadaten und Frames, neue Streamingtechnologien, insbesondere Eclipsa-Video und ‑Audio, und eine Reihe von Dienstprogrammen zur Verbesserung der Nutzerfreundlichkeit für das Vorabladen, Scrubbing und den Energiesparmodus.
Sehen wir uns jetzt an, wie Media3 beim Erstellen von UI-Elementen für Medien helfen kann.
Wir haben vor Kurzem zwei neue Module veröffentlicht: Media3 UI Compose und Media3 UI Compose Material 3.
Hier sehen Sie ein allgemeines Player-Composable, wie es in Media3 1.10 ohne weitere Änderungen eingeführt wurde.
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}Umgehend Einsatzbereit.
Es ist nur ein Media3-Player erforderlich.
In diesem Beispiel ist es ein ExoPlayer, den Sie normalerweise in Ihrem Ansichtsmodell haben.
Es könnte aber auch etwas anderes sein, z. B. ein CastPlayer, ein Medien-Controller oder ein anderer Player in deiner App.
Es ist wichtig zu betonen, dass ExoPlayer hier die Quelle der Wahrheit ist.
Es enthält alle Informationen und die komponierbare Player-Funktion übernimmt das Rendern der Benutzeroberfläche und das Verknüpfen aller Elemente.
Was aber, wenn Sie diese Benutzeroberfläche anpassen möchten, z. B. mit neuen Schaltflächen, anderen Positionen, anderen Stilen oder Text-Overlays?
Das Player-Composable besteht aus Bausteinen.
Sie können sie ersetzen.
Sie können jeden einzelnen Block anpassen oder ihn eigenständig an anderer Stelle in Ihrer App verwenden.
Sie können beispielsweise die Oberseite, die Mitte und die bottomControls separat anpassen.
Du kannst auch das Aussehen des Verschlusses anpassen, der angezeigt wird, wenn keine Videos abgespielt werden, oder ein Fehler-Overlay.
Aber das ist nur die erste Ebene.
Die Standard-Mitte und bottomControls haben auch detailliertere Slots, die du anpassen kannst.
Sie können beispielsweise die mittlere Schaltfläche austauschen und alles andere so lassen, wie es ist, oder Elemente über oder unter dem Schieberegler hinzufügen.
Media3 Compose bietet alle erforderlichen Bausteine dafür.
Wir haben zum Beispiel eine Reihe von Schaltflächen, darunter eine Schaltfläche für Wiedergabe/Pause, den Wiederholungsmodus, den Zufallsmodus und die Taste „Stummschalten“.
Außerdem gibt es Fortschrittsanzeigen, z. B. den Schieberegler und den Positionstext auf dem Bildschirm.
Außerdem gibt es eine Reihe von Containern und Overlays.
Das von mir gezeigte übergeordnete Player-Composable ist nur ein Beispiel. Es gibt noch viele weitere, z. B. Fehler-Overlays oder Untertitel.
Außerdem gibt es weitere nützliche Komponenten, wie diesen Mini-Controller hier.
Sie können sie in einer Ansicht am unteren Rand platzieren, während der Nutzer in Ihrer App navigiert.
Und wir sind noch nicht fertig.
Wir erweitern diese Bibliothek ständig, z. B. mit neuen Bearbeitungsfunktionen wie einem Schieberegler zum Zuschneiden, der demnächst verfügbar sein wird.
Sehen wir uns nun diese Anpassung in Aktion an, um zu sehen, wie diese Bausteine verwendet werden.
Player(viewModel.exoPlayer)Angenommen, wir möchten den Text für Position und Dauer, der sich derzeit neben dem Schieberegler befindet, darüber verschieben.
Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)Wir können diese bottomControls also überschreiben.
Wir können weiterhin die Standardkomponente verwenden, aber wir setzen den linken und den rechten Slot auf „leer“, damit sie nicht mehr angezeigt werden.
Stattdessen platzieren wir den Text für Position und Dauer darüber im oberen Slot mit einem benutzerdefinierten Trennzeichen, um das Design interessanter zu gestalten.
Angenommen, wir möchten das Overlay centerControls vollständig entfernen und stattdessen direkt über dem Schieberegler einen Wiedergabe-Button mit einer Geschwindigkeitssteuerung und einer Taste „Stummschalten“ platzieren.
Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}Das ist genau dasselbe Prinzip.
Wir können die centerControls auf „leer“ setzen, damit sie nicht mehr angezeigt werden. Außerdem können wir den Slot über dem Schieberegler mit etwas zusätzlichem Platz und den drei Schaltflächen, die wir anzeigen möchten, erweitern.
Diese Schaltflächen stammen aus der Bibliothek selbst, Sie müssen also keinen zusätzlichen Code einfügen.
Das ist alles, was Sie brauchen.
Und Sie können diese Elemente auf die gleiche Weise gestalten.
Wenn Sie beispielsweise das Symbol für die Taste „Stummschalten“ ändern, aber die Funktion beibehalten möchten, funktioniert das genauso.
Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)Wie Sie hier sehen, können Sie einen benutzerdefinierten Painter für die Schaltfläche definieren und die Symbole selbst auswählen.
Die Komponente stellt einige Metadaten bereit. Du musst also nur den Status, wie showMuted in diesem Beispiel, prüfen, um das richtige Symbol auszuwählen.
Das ist der Hauptvorteil von Media3 Compose.
Du kannst dich auf das Design deiner App konzentrieren und Media3 kümmert sich um den Rest.
Sie liest den Status des Players, interagiert mit ihm und verarbeitet alle Übergänge und Animationen dazwischen.
Kommen wir nun zum Teilen von Medien.
Sie können Ihren Wiedergabestatus mit anderen Apps auf demselben Gerät oder mit verbundenen Remote-Geräten teilen.
Die zentrale Möglichkeit, die laufende Wiedergabe zu veröffentlichen, ist die MediaSession.
Dadurch werden der Status und die Metadaten deiner Wiedergabe für andere verfügbar gemacht und sie können deine Wiedergabe steuern.
So kommunizieren Gemini, die Medienbenachrichtigung der System-UI, Cast, Wear OS oder Android Auto-Companion-Apps mit Ihrer App.
MediaSession umschließt einen Media3-Player und lässt sich ganz einfach erstellen, sobald Sie einen Player haben.
MediaSession.Builder(context, player).build()Wie bei der Compose-UI kann dies ein ExoPlayer oder ein anderer Player in Ihrer App sein.
Wenn Sie im Hintergrund abspielen, hosten Sie dies normalerweise in einem MediaSessionService oder einem MediaLibraryService, der auch den gesamten Lebenszyklus und Dinge wie Benachrichtigungen automatisch für Sie übernimmt.
class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}Wenn Sie eine Medien-App haben, wissen Sie das wahrscheinlich bereits.
Ich möchte euch heute unseren neu geschriebenen CastPlayer vorstellen.
Mit Google Cast kannst du die lokale Wiedergabe auf ein Remote-Gerät wie Android TV, Chromecast oder Smart Speaker übertragen.
Früher haben wir ein separates Cast SDK veröffentlicht, das sich jetzt im Wartungsmodus befindet.
Stattdessen wird sie vollständig durch Media3 Cast ersetzt, da diese Integration viel einfacher und optimierter ist.
Das wichtigste Element ist hier der CastPlayer, der dieselbe Standard-Player-Schnittstelle wie ExoPlayer implementiert.
ExoPlayer ist für die lokale Wiedergabe gedacht, CastPlayer ist eine delegierende Instanz, die entweder lokal über ExoPlayer oder remote über das Cast-Protokoll wiedergeben kann.
Und es kümmert sich um alles.
Sie überwacht Änderungen am Ausgabegerät, stellt die Verbindung her, synchronisiert den Status und überträgt die Wiedergabe bei Bedarf.
Deshalb ist die Einbindung von CastPlayer in Ihre App ganz einfach.
val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Anstatt einen ExoPlayer zu erstellen und ihn wie hier mit MediaSession zu teilen, verwendest du stattdessen einen CastPlayer. Das ist alles.
Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Du musst nur noch das Cast-System initialisieren, bevor es in deiner App verwendet wird.
Beachte, dass dieser CastPlayer intern einen ExoPlayer für die lokale Wiedergabe erstellt. Wenn du den lokalen Player weiter anpassen möchtest, kannst du auch einen eigenen ExoPlayer erstellen und ihn als lokalen Player an diesen CastPlayer übergeben.
Cast.getSingletonInstance(context).initialize()
val exoPlayer = ExoPlayer.Builder(context).build()
val player = CastPlayer.Builder(context)
.setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context,player)
.build()Und das ist wieder dasselbe Media3-Prinzip.
Wir stellen die einfachen, sofort einsatzbereiten API-Bausteine bereit, aber auch alle Anpassungsoptionen, die Sie für eine weitere Anpassung benötigen.
Du fragst dich vielleicht, wie der Nutzer in dieser Konfiguration zwischen lokaler und Remote-Wiedergabe wechselt.
Die Funktion ist bereits über das Dialogfeld zur Auswahl der Systemausgabe in der Medienbenachrichtigung verfügbar.
Das ist die kleine Taste in der Ecke.
Aber was, wenn du ein Cast-Symbol auf dem Wiedergabebildschirm anzeigen möchtest, damit es leichter zu finden ist?
Erinnern Sie sich noch an die angepasste Compose-UI, die wir vor ein paar Minuten erstellt haben?
Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)Media3 Cast bietet auch eine Standard-Schaltfläche, die Sie dort platzieren können.
Sie müssen nur die vorgefertigte MediaRouteButton dort hinzufügen, z. B. zur topControls. Weitere Parameter müssen nicht eingerichtet werden. Das ist alles.
Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)Wenn du auf diese Schaltfläche klickst, wird dasselbe Dialogfeld für die Systemausgabe geöffnet und CastPlayer überträgt die Wiedergabe für dich.
Außerdem habe ich den CastPlayer mit der Benutzeroberfläche verbunden, damit alles, was remote passiert, in der lokalen Benutzeroberfläche widergespiegelt wird.
Und damit übergebe ich an Kristina, die über Anwendungsfälle für die Bearbeitung sprechen wird.
Vielen Dank.
Hallo, jetzt geht es um die Bearbeitung und erweiterte Effekte.
Media3 APIs bestehen aus mehreren Komponenten.
Erstens: Transformer ist der Haupteinstiegspunkt für unsere Bearbeitungs-APIs.
Transformer verarbeitet, transcodiert und exportiert Medien in eine Datei.
Als Nächstes kommt der CompositionPlayer, der Echtzeitvorschauen Ihrer Medienkompositionen rendert.
So können Sie sich eine Vorschau Ihrer Änderungen ansehen und sie vor dem endgültigen Export optimieren.
Als Nächstes kommen die Effekte.
Sie haben die volle Kontrolle über die Verarbeitung einzelner Frames, wenn Sie einfache visuelle Effekte anwenden oder grafische Overlays hinzufügen.
Schließlich gibt es noch Muxer.
Er dient als letzte Ausgabestufe in unserer Pipeline, nimmt verarbeitete Audio-, Video- und Metadatenspuren auf und schreibt sie in standardisierte MP4-Container.
Wir haben im letzten Jahr unsere Videobearbeitungspipeline von Grund auf überarbeitet und neu aufgebaut. Heute möchte ich euch die wichtigsten Verbesserungen vorstellen.
Warum haben wir die Pipeline neu erstellt?
Die Legacy-Architektur stellte also mehrere Herausforderungen dar.
Erstens konnten Metadaten nicht über alle Verarbeitungsschritte hinweg weitergegeben werden.
Sie war eng mit OpenGL verknüpft, was die Flexibilität für moderne Grafiktools einschränkte.
Die komplexe API machte die Anpassung und Wartung für Entwickler zu einer großen Hürde.
Um diese Herausforderungen zu bewältigen, haben wir eine völlig neue Verarbeitungspipeline entwickelt.
Erstens ist das Zusammenstellen komplexer Kompositionen mit mehreren Assets jetzt viel einfacher.
Zweitens haben Sie durch die Entkopplung von OpenGL die volle Freiheit, moderne Grafik-Frameworks wie WebGPU zu verwenden.
Und schließlich verwaltet Media3 jetzt den Decoder und Encoder direkt, was für mehr Konsistenz und Effizienz auf allen Geräten sorgt.
Sehen wir uns an, wie diese Pipeline funktioniert.
Decoder geben Frames direkt in Hardwarepuffer aus, die von Media3 verwaltet werden.
Der Frame-Aggregator koordiniert Frames über mehrere Sequenzen hinweg, sodass Ihr benutzerdefinierter Frame-Prozessor direkt auf diese Puffer zugreifen und sie verarbeiten kann, bevor sie an den Encoder gesendet werden.
Die Schnittstelle FrameProcessor dient als primärer Erweiterungspunkt für die Verarbeitung auf Anwendungsebene.
public interface FrameProcessor
extends AutoCloseable {
interface Listener {
void onWakeup();
void onError(
VideoFrameProcessingException exception);
void onFrameProcessed(
Frame frame,
@Nullable SyncFenceWrapper onCompleteFence);
}
interface Factory {
FrameProcessor create(
FrameWriter output,
Executor listenerExecutor,
Listener listener);
}
boolean queue(List<AsyncFrame> frames);
void signalEndOfStream();
}Über die Listener-Schnittstelle benachrichtigt Ihre Anwendung Media3, wenn ein Frame verarbeitet wurde, und übergibt Synchronisierungs-Fences. So weiß Media3 genau, wann Puffer sicher wiederverwendet werden können.
Hinweis zum Unified Memory: Wenn Media3 die Hardwarepuffer übernimmt, kann Ihre App Daten direkt an Ort und Stelle lesen und ändern.
Dieser Zero-Copy-Ansatz reduziert den Speicheraufwand und sorgt für eine ultraschnelle Verarbeitung.
Einer der Entwickler, die die neuen Bearbeitungs-APIs nutzen, ist Adobe.
Das Adobe Premiere-Team hat die Media3-Bearbeitungs-APIs als Grundlage für seine Infrastruktur zur Medienbearbeitung verwendet, um eine bessere Kompatibilität im Android-Ökosystem zu erreichen. Gleichzeitig wurde die Flexibilität beibehalten, die für die Verwendung einer eigenen benutzerdefinierten Pipeline und WebGPU erforderlich ist.
Als Nächstes möchte ich ankündigen, dass wir im Rahmen des Media3 1.12-Releases neue Effekte veröffentlichen.
Beginnen wir mit der Video-Optimierung.
Unsere Architektur basiert auf einem modularen Setup.
Ihre Anwendung ist in die Media3-Effekt-Play-Dienste eingebunden, die mit dem Play-Dienste-Erweiterungsmodul interagieren.
Das Modul „Video-Optimierung“ umfasst Funktionen für Tonemapping, Rauschunterdrückung, Unschärfe entfernen und verbesserte Auflösung.
Die Einbindung von Video Enhance in Ihre Pipeline erfordert nur wenige Codezeilen.
val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)Mit EnhancementSessionDecorator.Builder kannst du Funktionen wie Tonemapping, Unschärfe entfernen oder Hochskalieren selektiv aktivieren und die resultierenden frameProcessorFactory- und Hardwarepuffer-Hilfsprogramme an Transformer.Builder übergeben.
Ein weiterer interessanter Effekt, der in Media3 1.12 eingeführt wird, ist Studio Sound.
Studio Sound analysiert automatisch den Audiotrack Ihres Videos und trennt ihn in einzelne Stems wie Sprache, Umgebungsgeräusche und Musik.
Creator können Wind- oder Hintergrundgeräusche ganz einfach reduzieren und gleichzeitig Dialoge und Musik in den Vordergrund rücken.
Wenn Sie Studio-Sound integrieren möchten, übergeben Sie den Studio-Sound-Audioprozessor in einem Effektobjekt.
val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()Dadurch wird der Audioprozessor an Ihre Mediendatei angehängt.
Hängen Sie beim Erstellen des Audioprozessors für Studio-Sound den Listener-Callback an, um die analysierte Audiokanalzuordnung über onResult zu erhalten.
val soundSeparationAudioProcessor =
SoundSeparationAudioProcessor(
getApplication(),
mainExecutor,
object : SoundSeparationAudioProcessor.Listener {
override fun onResult(channelToAudioType: SparseIntArray) {
for (i in 0 until channelToAudioType.size()) {
val channelIndex = channelToAudioType.keyAt(i)
val audioType = channelToAudioType.valueAt(i)
val label = toAudioTypeName(audioType)
detectedChannelVolumes.add(ChannelVolume(channelIndex, audioType, label))
}
}
},
)Gehen Sie jeden Kanal durch, um den Kanalindex, den identifizierten Audiotyp und das lesbare Label zu extrahieren, wenn das Ergebnis des Vorgangs verfügbar ist.
Hören wir uns den Studiosound einmal an.
Der erste Effekt: Baby, bitte weine nicht.
Wir schneiden für dein Abendessen.
Im Hintergrund ist ein weinendes Baby zu hören. Jetzt wenden wir den Effekt an.
Ja, wie ihr hören könnt, war im Hintergrund kein weinendes Baby zu hören – dank des Studio-Sound-Effekts.
Sehen wir uns nun den Abruf von Metadaten an.
Wir sehen uns an, wie Media3 das Lesen von Container-Metadaten und das Extrahieren von Frames vereinfacht.
Ab MetadataRetriever lässt sich die API nahtlos in Kotlin-Koroutinen einbinden, um sauberen, nicht blockierenden Code zu ermöglichen.
Das Design ist übersichtlicher und weniger fehleranfällig, da pro Mediendatei ein unveränderlicher Retriever verwendet wird.
Sie funktioniert auf allen API-Versionen und Geräten gleich.
Außerdem werden zahlreiche Medienformate unterstützt.
Hier ein praktisches Beispiel mit MetadataRetriever:
MetadataRetriever.Builder(context, mediaItem).build().use { retriever ->
// 1. Duration (in microseconds)
val durationUs = retriever.retrieveDurationUs().await()
// 2. Track formats & container metadata (reuses the same preparation pass)
val trackGroups = retriever.retrieveTrackGroups().await()
for (i in 0 until trackGroups.length) {
val format = trackGroups[i].getFormat(0)
// Video/Audio properties: format.width, format.height, format.colorInfo …
// Rich metadata: SlowMotionData, MotionPhotoMetadata, Mp4TimestampData …
val metadata = format.metadata
}
// 3. Stream structure & manifest info
val timeline = retriever.retrieveTimeline().await()
}Mit Kotlin-Koroutinen können Sie die Dauer asynchron abrufen und Trackformate und Metadaten prüfen – alles in einem einzigen, übersichtlichen Block.
Kommen wir nun zu FrameExtractor.
Damit lassen sich decodierte Videoframes schnell und effizient aus Mediendateien abrufen.
Sie verwaltet zugrunde liegende Ressourcen wie Decoder automatisch, ermöglicht das Anwenden visueller Effekte oder HDR-Tone-Mapping und bietet eine detaillierte Suchsteuerung, damit Sie genau den gewünschten Frame finden.
So sieht die Frame-Extraktion im Code aus.
FrameExtractor.Builder(context, mediaItem)
.setSeekParameters(SeekParameters.CLOSEST_SYNC) // Fast keyframe seeking
.setEffects(listOf(Presentation.createForHeight(360))) // GPU downscaling
.setExtractHdrFrames(true) // Optional: HLG HDR or UltraHDR
.build()
.use { extractor ->
// Representative thumbnail
val thumbnail = extractor.getThumbnail().await()
// Frame at specific timestamp (bitmap + actual presentationTimeMs)
val frame = extractor.getFrame(positionMs = 5_000L).await()
}Sie erstellen eine Instanz mit Optionen wie Suchparametern, GPU-Downscaling, Effekten und HDR-Unterstützung und warten dann asynchron auf Thumbnails und bestimmte Frames, indem Sie saubere, aussetzende Aufrufe verwenden.
Und jetzt zu Donovan.
Viele Grüße Kristina
Als Nächstes möchte ich über die zugrunde liegende Streaming-Technologie sprechen, die wir in Media3 hinzugefügt haben, einschließlich der Unterstützung für Formate wie Eclipsa Video und Eclipsa Audio.
Ein zentrales Ziel von Media3 ist es daher, den Streamingstandards immer einen Schritt voraus zu sein, damit Sie Ihre Medienpipeline nicht jedes Mal neu schreiben müssen, wenn eine neue Spezifikation veröffentlicht wird.
Hier sind einige Beispiele für Funktionen, die wir vor Kurzem eingeführt haben.
Wir haben Content Steering, mit dem die Backend-CDN-Pfade während des Streams dynamisch gewechselt werden können.
Wir unterstützen die servergesteuerte Anzeigenbereitstellung für eine nahtlose Einbindung personalisierter Anzeigen.
Außerdem haben wir die AV1-Unterstützung für mehrere Einstiegspunkte eingeführt, einschließlich der vollständigen AV1-HDR-Unterstützung.
Wir unterstützen Android Professional Video für die Aufnahme und Bearbeitung in hoher Qualität.
Außerdem werden jetzt Fotos mit Bewegtbild im JPEG- und HEIC-Format unterstützt.
Und natürlich fügen wir Unterstützung für Eclipsa Video und Eclipsa Audio hinzu, worauf ich gleich noch näher eingehen werde.
Die gute Nachricht ist, dass all dies automatisch unterstützt wird, sobald die Geräteunterstützung eingeführt wird.
Sehen wir uns nun die nächste Entwicklungsstufe von HDR an, Eclipsa Video, und das Kernproblem, das damit gelöst werden soll.
Frühere HDR-Formate lieferten beschreibende Metadaten, die festlegten, wie die Szene aussehen sollte, aber dem Display nicht wirklich mitteilten, wie sie gerendert werden sollte.
Lassen Sie uns das zuerst verdeutlichen.
Für verschiedene Umgebungen gibt es unterschiedliche Empfehlungen für die durchschnittliche Bildschirmhelligkeit, damit du deine Inhalte gut und angenehm sehen kannst.
In einem dunklen Raum liegt dieser Bereich zwischen 100 und 300 cd/m².
Und wenn du dich in hellere Umgebungen begibst, muss das Display natürlich mithalten und heller werden, damit du deine Inhalte klar erkennen kannst. Wenn du dein Smartphone in direktem Sonnenlicht verwendest, muss das Display möglicherweise auf 1.000, 2.000 oder sogar mehr Nits eingestellt werden, damit du deine Inhalte sehen kannst.
Herkömmliche SDR-Inhalte liegen im Bereich von 100 bis 300 cd/m².
Und natürlich ist HDR nicht dazu gedacht, das Display insgesamt aufzuhellen.
Sie möchten den Dynamikbereich erweitern, um lokalisierte Highlights für Dinge wie Feuer und Sonnenlicht zu erhalten.
Das Smartphone bringt jedoch einige Komplikationen mit sich, da wir es überallhin mitnehmen – in einen dunklen Raum zu Hause oder an einen hellen Strand.
Außerdem werden in vielen Apps SDR-UI-Elemente mit SDR- und HDR-Inhalten in gemischten Feeds kombiniert. Das kann zu einem unangenehmen, blendenden Effekt führen, besonders wenn man nachts scrollt.
Jetzt ist klar, warum sich HDR lohnt, oder?
Dadurch sehen unsere Inhalte in mehr Umgebungen gut aus.
Wir haben sogar eine höhere Nutzerinteraktion festgestellt, wenn HDR aktiviert ist.
Der Haken ist jedoch, dass bestehende HDR-Formate diese Umgebungen nicht berücksichtigen.
Hier kommt Eclipsa Video ins Spiel.
Pause für Gelächter.
Vielen Dank.
Vielen Dank.
Anstelle von beschreibenden Metadaten stellt Eclipsa Video also präskriptive Metadaten bereit.
Und das basiert auf drei Hauptsäulen.
Die erste Säule ist ein Referenzweiß-Anker.
Dadurch wird die Standardhelligkeit auf den an die Umgebung angepassten SDR-Weißpunkt des Displays festgelegt, wodurch plötzliche Helligkeitsspitzen vermieden werden.
Die zweite Säule ist die SDR-Baseline für Creator.
So bleibt die genaue Tone-Mapping-Kurve des Creators bis hin zu SDR erhalten.
Drittens gibt es adaptive Verstärkungskurven für den Headroom, die Highlights deterministisch skalieren, um sie an den Echtzeit-HDR-Headroom des Displays anzupassen. So werden die Inhalte an die Umgebung angepasst, in der sie angesehen werden.
Diese Funktion ist in ExoPlayer in Media3 1.11 auf Geräten mit Android 17 und höher standardmäßig verfügbar.
Eclipsa Video basiert auf offenen Standards für branchenübergreifende Kompatibilität.
Es gab also noch nie einen besseren Zeitpunkt, HDR zu unterstützen.
Im Audiobereich gibt es Eclipsa Audio, ein lizenzfreies Open-Source-Format für 3D-Spatial Audio.
Es basiert auf der IAMF-Spezifikation der Alliance for Open Media.
Dabei werden szenenbasiertes Ambisonics und kanalbasierte Audioinhalte in einem einzigen Stream mit mehreren gemischten Präsentationen kombiniert.
Das ermöglicht objektähnliche Manipulationen, z. B. die Verbesserung von Dialogen, und funktioniert mit allen Geräten, von Stereo-Kopfhörern bis hin zu Surround-Sound-Systemen.
In Media3 gibt es zwei Wiedergabepfade.
Ab Android 17 wird Eclipsa Audio automatisch von ExoPlayer und dem Android Audio Framework gerendert.
Für Android 16 und ältere Versionen gibt es aber eine Option.
Du kannst die IAMF-Erweiterung über eine optionale Media3-Add-on-Bibliothek aktivieren.
Und auf der Erstellungsseite können Sie IAMF-Audiotracks mit Media3-Muxern schreiben.
Die Unterstützung hochwertiger Formate ist aber nur die halbe Miete.
Wir müssen diese Qualität effizient liefern.
Jetzt möchte ich darüber sprechen, wie Media3 unsere Pipelines schneller und effizienter macht.
Wie viele der Dinge, über die wir heute gesprochen haben, sind die meisten dieser Leistungsverbesserungen sofort verfügbar, wenn ihr eure Media3-Version auf dem neuesten Stand haltet.
Ein Beispiel für eine Funktion ist die PCM-Audioauslagerung, bei der die Audiowiedergabe an eine energiesparende Verarbeitungseinheit weitergeleitet werden kann.
In unserer ersten Analyse haben wir bei der Audiowiedergabe mit ausgeschaltetem Display eine Energieeinsparung von bis zu 20 % festgestellt.
Zusätzlich zur PCM-Audioauslagerung gibt es die dynamische Planung, bei der die Aktivierung der internen Rendering-Schleife von ExoPlayer an VSYNC und die Verfügbarkeit des Decoder-Puffers angepasst wird.
Außerdem gibt es die Funktion „Renderer Prewarming“, mit der du einen sekundären Video-Renderer vorwärmen kannst. Das ermöglicht sofortige Playlist-Übergänge und ein einfacheres Einfügen von Anzeigen.
Außerdem gibt es eine Erkennung für hängende Player und Pre-Caching, die automatisch blockierte Zwischenspeicher und Renderer erkennt und den Pre-Caching-Speicher sauber begrenzt.
Ein weiterer optimaler Modus ist der Scrubbing-Modus.
Das ist für Apps gedacht, in denen Nutzer häufig durch Inhalte scrollen.
Wenn du den Scrubbing-Modus aktivierst, lässt sich die Suchleiste viel besser und flüssiger ziehen, wie du hier sehen kannst.
Übrigens ist der Scrubbing-Modus hier deaktiviert.
Rechts ist der Scrubbing-Modus aktiviert.
Vielen Dank.
Vielen Dank.
Okay.
Kommen wir nun zu Android System Health.
Wir müssen respektvoll mit unseren gemeinsamen RAM-, CPU- und Akkuressourcen umgehen. Da Android die Regeln für die Audiohärtung und Dienste im Vordergrund verschärft, können bei Apps, die ihre eigenen Rohdienste und Bitmaps verwalten, häufig Fehler auftreten.
Möglicherweise sind Ihnen Ausnahmen wie „Start des Dienstes im Vordergrund nicht zulässig“ oder „Binder-Ausnahme: Nicht genügend Arbeitsspeicher“ aufgefallen.
Wir haben die MediaServiceSession und den MediaNotificationManager sowie unsere Bitmap-Pipelines auf verschiedene Weise gehärtet, unter anderem durch das Verhindern veralteter Intents für Dienste im Vordergrund und das Begrenzen des Arbeitsspeichers für Coverbilder.
Wenn ihr Media3 verwendet und die Standardanleitung befolgt, werden alle diese Einschränkungen für euch ohne Auswirkungen gehandhabt.
Zum Schluss möchte ich noch über eine Funktion sprechen, die bald in Media3 verfügbar sein wird.
Wenn es an der Zeit ist, Videos zu codieren oder zu exportieren, kann die Vielfalt der Hardware eine der größten Herausforderungen sein.
Viele Apps verwenden eine einheitliche Codec-Konfiguration, was zu einer nicht optimalen Qualität und Leistung führt.
Deshalb haben wir CodecDB entwickelt.
CodecDB basiert auf umfangreichen Videomessungen in der Praxis, bei denen standardisierte Qualitätsmesswerte und Hardware-Benchmarking auf Android-Geräten verwendet werden.
Sehen wir uns das einmal genauer an.
Links sehen Sie ein Bild aus einem Video, das wir mit einem Smartphone aufgenommen haben. Dabei haben wir eine sehr allgemeine Videokonfiguration verwendet, die wir auf vielen Geräten verwenden könnten.
Rechts haben wir eine optimale Konfiguration auf Geräteebene verwendet, die wir von CodecDB erhalten haben.
Wo liegen also die Unterschiede?
In den Bereichen mit vielen Details ist der Qualitätsunterschied am größten.
Achte auf die Klarheit der Äste und Sträucher oder die Schärfe der Gebäudekanten.
Wir erhalten also eine viel bessere Wiedergabequalität, die die Funktionen eines bestimmten Geräts mithilfe von CodecDB nutzt.
Die Verwendung von CodecDB im Code ist ziemlich einfach.
val config = CodecDb.onDevice(context)
.forEncodingFallingBackTo {
constraints -> createFallbackConfig(constraints)
}
.findOptimalSettings()
.forEditing()
.withResolution(1920, 1080, /* frameRate= */ 30.0)
.withAnyCodec()
.targetingQuality(VideoEncoder.QUALITY_LEVEL_VERY_GOOD)
.get()Sie verwenden ein Fluent-Stage-Builder-Muster, das Sie durch alle erforderlichen Parameter führt.
Sie geben also Ihren Anwendungsfall an.
Hier verwenden wir sie zum Bearbeiten.
Dadurch wird CodecDB mitgeteilt, dass Sie die Media3-Export- oder Transcodierungspipeline verwenden.
Andere Anwendungsfälle, die du festlegen kannst, sind beispielsweise Kameraaufnahmen, wenn du die Taste dafür verwendest.
Außerdem gibst du die Zielauflösung und die Framerate an.
Anstatt zu raten, ob für ein 1080p-Video 8 Mbit/s oder 15 Mbit/s erforderlich sind, teilt ihr CodecDB mit, welchen Codec ihr verwenden möchtet, und fordert ein Ziel für die wahrgenommene Qualität an.
In diesem Fall verwenden wir die Qualitätsstufe „Sehr gut“.
Im Hintergrund sucht CodecDB nach empirischen Benchmark-Kurven für diesen genauen SOC und gibt die idealen Codec- und Encoder-Parameter zurück.
Wir gehen davon aus, dass CodecDB sehr bald in Media3 eingeführt wird. Dann werden native Media3-Typen verwendet.
Die Einbindung in Ihren vorhandenen Media3-Code sollte also ein Kinderspiel sein.
Okay.
Wir haben heute viele tolle Funktionen in Media3 gesehen. Fassen wir sie noch einmal zusammen.
Heute haben Sie unsere neuen Compose-UI-Module, den neuen CastPlayer, die überarbeitete Bearbeitungspipeline und viele neue Effekte kennengelernt, die Sie in Media3 verwenden können.
Außerdem haben wir neue Tools zum Abrufen von Metadaten und Frames gesehen.
Außerdem haben wir uns die Unterstützung neuer Streaming-Technologien sowie Leistungsverbesserungen und Dienstprogramme angesehen.
Media3 ist eine Open-Source-Bibliothek. Sie können sich also in unserem GitHub-Repository ansehen, wie wir alle diese Funktionen implementiert haben.
Die aktuelle stabile Version von Media3 ist 1.11.1.
Sie können sich auch 1.12.0 ansehen, das sich derzeit in der Alphaphase befindet.
Über diesen Kurzlink gelangen Sie zu unserer Entwicklerdokumentation zu Media3.
Vielen Dank und viel Spaß beim Rest des Kurses.
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}Player(viewModel.exoPlayer)Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)MediaSession.Builder(context, player).build()class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Cast.getSingletonInstance(context).initialize()
val exoPlayer = ExoPlayer.Builder(context).build()
val player = CastPlayer.Builder(context)
.setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context,player)
.build()Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)public interface FrameProcessor
extends AutoCloseable {
interface Listener {
void onWakeup();
void onError(
VideoFrameProcessingException exception);
void onFrameProcessed(
Frame frame,
@Nullable SyncFenceWrapper onCompleteFence);
}
interface Factory {
FrameProcessor create(
FrameWriter output,
Executor listenerExecutor,
Listener listener);
}
boolean queue(List<AsyncFrame> frames);
void signalEndOfStream();
}val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()val soundSeparationAudioProcessor =
SoundSeparationAudioProcessor(
getApplication(),
mainExecutor,
object : SoundSeparationAudioProcessor.Listener {
override fun onResult(channelToAudioType: SparseIntArray) {
for (i in 0 until channelToAudioType.size()) {
val channelIndex = channelToAudioType.keyAt(i)
val audioType = channelToAudioType.valueAt(i)
val label = toAudioTypeName(audioType)
detectedChannelVolumes.add(ChannelVolume(channelIndex, audioType, label))
}
}
},
)MetadataRetriever.Builder(context, mediaItem).build().use { retriever ->
// 1. Duration (in microseconds)
val durationUs = retriever.retrieveDurationUs().await()
// 2. Track formats & container metadata (reuses the same preparation pass)
val trackGroups = retriever.retrieveTrackGroups().await()
for (i in 0 until trackGroups.length) {
val format = trackGroups[i].getFormat(0)
// Video/Audio properties: format.width, format.height, format.colorInfo …
// Rich metadata: SlowMotionData, MotionPhotoMetadata, Mp4TimestampData …
val metadata = format.metadata
}
// 3. Stream structure & manifest info
val timeline = retriever.retrieveTimeline().await()
}FrameExtractor.Builder(context, mediaItem)
.setSeekParameters(SeekParameters.CLOSEST_SYNC) // Fast keyframe seeking
.setEffects(listOf(Presentation.createForHeight(360))) // GPU downscaling
.setExtractHdrFrames(true) // Optional: HLG HDR or UltraHDR
.build()
.use { extractor ->
// Representative thumbnail
val thumbnail = extractor.getThumbnail().await()
// Frame at specific timestamp (bitmap + actual presentationTimeMs)
val frame = extractor.getFrame(positionMs = 5_000L).await()
}val config = CodecDb.onDevice(context)
.forEncodingFallingBackTo {
constraints -> createFallbackConfig(constraints)
}
.findOptimalSettings()
.forEditing()
.withResolution(1920, 1080, /* frameRate= */ 30.0)
.withAnyCodec()
.targetingQuality(VideoEncoder.QUALITY_LEVEL_VERY_GOOD)
.get()