La consommation de contenus multimédias représente une grande partie du temps que les utilisateurs passent sur leurs appareils mobiles. L'expérience de lecture doit donc être impeccable, très efficace et économe en batterie. De plus, les utilisateurs s'attendent à ce que ces expériences se fassent de manière fluide sur leurs appareils, des téléphones aux téléviseurs du salon, et plus encore.
Découvrez comment les mises à jour architecturales de Media3 et ExoPlayer permettent une lecture fluide sur différents facteurs de forme. La discussion porte sur la création d'interfaces responsives avec le kit d'interface utilisateur Media3 Compose, l'élimination de la mise en mémoire tampon dans les flux à l'aide du nouveau PreloadManager, et l'extension de votre application aux écrans de salon et à Android XR avec CastPlayer et l'audio spatial.
Transcription
OK.
Bienvenue à tous.
Je m'appelle Toni.
Je suis ingénieur dans l'équipe Android Media.
Avec Kristina et Donovan, nous allons voir comment Jetpack Media3 peut vous aider à répondre à tous vos besoins multimédias sur Android.
Media3 est la bibliothèque Support d'Android pour tout ce qui concerne les contenus multimédias, qu'il s'agisse de la lecture avec ExoPlayer, du partage de contenus multimédias avec une session multimédia et Cast, de la modification de contenus multimédias avec Transformer ou de l'inspection de contenus multimédias, comme l'extraction d'images ou de métadonnées.
Media3 couvre l'ensemble de cet espace dans un seul groupe de bibliothèques.
En tant que bibliothèque d'assistance, elle est intégrée à votre application.
Cela signifie que nous pouvons garantir la cohérence, de sorte que le même code s'exécute sur toutes les versions d'OS et tous les appareils.
Cela facilite également les mises à jour.
Chaque correction de bug que nous effectuons est appliquée jusqu'à l'API 24 et n'est liée à aucun cycle de publication d'OS.
En tant que bibliothèque Support, nous pouvons proposer des options de personnalisation très riches, ce qui ne serait pas possible dans le framework Android.
En tant que bibliothèque sur GitHub, nous sommes avant tout Open Source.
Cela signifie que chaque modification que nous apportons est immédiatement disponible sur GitHub.
Nous disposons d'un outil de suivi des problèmes actif et nous acceptons les demandes d'extraction.
Le point principal à souligner ici, dans tous ces cas d'utilisation, est que nous nous soucions vraiment de tous les niveaux de détail, des éléments d'interface utilisateur de haut niveau aux API simples prêtes à l'emploi, mais aussi des options de personnalisation approfondies, d'une prise en charge étendue de divers formats et normes, ainsi que des performances et de l'efficacité qui sous-tendent le tout.
Media3 couvre l'ensemble de cet espace.
Aujourd'hui, nous allons approfondir certains de ces sujets.
Nous allons parler de notre nouveau module d'interface utilisateur Compose, du nouveau CastPlayer qui facilite encore plus le partage avec une session multimédia, d'un pipeline de montage repensé avec de nouveaux effets avancés, de nouveaux outils pour récupérer les métadonnées et les images, d'une nouvelle technologie de streaming, en particulier la vidéo et l'audio Eclipsa, et d'un certain nombre d'utilitaires de qualité d'expérience pour le préchargement, l'utilisation de la barre de lecture et l'économie de batterie.
Passons maintenant au premier sujet : comment Media3 peut vous aider à créer des éléments d'interface utilisateur pour les contenus multimédias.
Nous avons récemment publié deux nouveaux modules : Media3 UI Compose et Media3 UI Compose Material 3.
Ici, vous pouvez voir un composable de lecteur de haut niveau, tel qu'il a été lancé dans Media3 1.10 sans autre modification.
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}La solution est prête à l'emploi.
Il n'a besoin que d'un lecteur Media3.
Dans l'exemple présenté ici, il s'agit d'un ExoPlayer, que vous auriez généralement dans votre modèle de vue.
Mais il peut s'agir d'autre chose, comme un CastPlayer, un contrôleur multimédia ou tout autre lecteur que vous avez dans votre application.
Il est important de souligner qu'ExoPlayer est ici la source de vérité.
Il contient toutes les informations, et le composable du lecteur se charge d'afficher l'UI et de tout connecter.
Mais que faire si vous souhaitez personnaliser cette UI, ajouter des boutons, modifier les positions, les styles ou les superpositions de texte ?
Le composable du lecteur est constitué de composants.
Vous pouvez les remplacer.
Vous pouvez personnaliser chaque bloc individuel ou les utiliser de manière autonome ailleurs dans votre application.
Par exemple, vous pouvez personnaliser le haut, le centre et le bottomControls séparément.
Vous pouvez également personnaliser l'apparence de l'obturateur, qui s'affiche lorsque les vidéos ne sont pas lues, ou une superposition d'erreur.
Mais ce n'est que la première couche.
Le centre par défaut et bottomControls disposent également d'emplacements plus détaillés que vous pouvez personnaliser.
Par exemple, vous pouvez remplacer le bouton central tout en laissant le reste tel quel, ou ajouter des éléments au-dessus ou en dessous du curseur.
Media3 Compose fournit tous les composants dont vous avez besoin pour cela.
Par exemple, nous avons plusieurs boutons : un bouton de lecture/pause, un bouton de mode de répétition, un bouton de mode aléatoire, un bouton du micro, etc.
Ensuite, il y a les indicateurs de progression, comme le curseur et le texte de position affichés à l'écran.
Il existe également un certain nombre de conteneurs et de superpositions.
Le composable de lecteur de haut niveau que je vous ai montré en est un exemple, mais il en existe d'autres, comme les superpositions d'erreurs ou les sous-titres, et bien d'autres encore.
Nous disposons également d'autres composants utiles, comme ce mini-contrôleur.
Vous pouvez placer une bottom sheet pendant que l'utilisateur navigue dans votre application.
Et ce n'est pas tout.
Nous continuons d'enrichir cette bibliothèque, par exemple avec de nouvelles commandes de montage, comme un curseur de découpage qui sera bientôt disponible.
Voyons maintenant cette personnalisation en action pour comprendre comment ces composants sont utilisés.
Player(viewModel.exoPlayer)Imaginons que nous voulions déplacer le texte de la position et de la durée, qui se trouve actuellement à côté du curseur, au-dessus de celui-ci.
Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)Nous pouvons donc remplacer ces bottomControls.
Nous pouvons toujours utiliser le composant par défaut, mais nous définissons les emplacements de gauche et de droite sur "vide" pour les faire disparaître.
À la place, nous plaçons le texte de la position et de la durée au-dessus, dans l'emplacement supérieur, avec un séparateur personnalisé pour le rendre plus intéressant.
Maintenant, imaginons que nous voulions supprimer complètement la superposition centerControls et placer un bouton de lecture juste au-dessus du curseur, avec un contrôle de la vitesse et un bouton de coupure du son.
Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}C'est exactement la même idée.
Nous pouvons définir centerControls sur "vide" pour les faire disparaître, et nous pouvons agrandir l'emplacement au-dessus du curseur avec un peu d'espace supplémentaire et les trois boutons que nous voulons afficher.
Notez que ces boutons proviennent de la bibliothèque elle-même. Vous n'avez donc pas besoin d'y ajouter de code supplémentaire.
C'est tout ce dont vous avez besoin.
Vous pouvez continuer à suivre cette procédure pour styliser ces éléments également.
Si vous souhaitez modifier l'icône du bouton du micro, par exemple, mais conserver sa fonctionnalité, cela fonctionne de la même manière.
Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)Comme vous pouvez le voir ici, vous pouvez définir un peintre personnalisé pour le bouton et choisir vous-même les icônes.
Le composant fournit des métadonnées. Il vous suffit donc d'examiner l'état, comme showMuted dans cet exemple, pour choisir la bonne icône.
C'est le principal avantage de Media3 Compose.
Vous pouvez vous concentrer sur l'apparence de votre application, et Media3 s'occupe du reste.
Il lit l'état du lecteur, interagit avec lui et gère toutes les transitions et animations entre les deux.
Passons maintenant de l'UI au partage de contenus multimédias.
Vous pouvez partager l'état de lecture avec d'autres applications sur le même appareil ou connectées à des appareils distants.
Le principal moyen de publier votre lecture en cours est d'utiliser MediaSession.
Elle permet à d'autres personnes de voir l'état et les métadonnées de votre lecture, et de la contrôler.
C'est ainsi que Gemini, la notification multimédia de l'UI du système, Cast, Wear OS ou les applications associées Android Auto communiquent avec votre application.
MediaSession encapsule un lecteur Media3, et il est très facile d'en créer un une fois que vous disposez d'un lecteur.
MediaSession.Builder(context, player).build()Il peut s'agir d'ExoPlayer ou de tout autre lecteur de votre application, comme avec l'UI Compose.
Si vous lisez du contenu en arrière-plan, vous l'hébergez généralement dans un MediaSessionService ou un MediaLibraryService, qui gère automatiquement l'ensemble du cycle de vie, ainsi que les notifications, par exemple.
class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}Mais si vous possédez une application multimédia, vous savez probablement déjà tout cela.
La nouveauté que je souhaite vous présenter est notre CastPlayer, qui a été réécrit.
Google Cast vous permet de transférer votre lecture locale vers un appareil distant, comme un téléviseur Android TV, un Chromecast ou une enceinte intelligente.
Nous publiions auparavant un SDK Cast distinct, qui est désormais en mode maintenance.
Media3 Cast le remplace entièrement, car il offre une intégration beaucoup plus simple et plus fluide.
L'élément le plus important ici est CastPlayer, qui implémente la même interface de lecteur standard qu'ExoPlayer.
Alors qu'ExoPlayer est destiné à la lecture locale, CastPlayer est une instance de délégation qui peut lire du contenu localement via ExoPlayer ou à distance via le protocole Cast.
Et il s'occupe de tout.
Il écoute les changements de périphérique de sortie, établit la connexion, synchronise l'état et transfère la lecture pour vous si nécessaire.
C'est pourquoi il est très simple d'intégrer CastPlayer à votre application.
val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Au lieu de créer un ExoPlayer et de le partager avec MediaSession, vous utilisez un CastPlayer, et c'est tout.
Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()La seule autre chose à faire est d'initialiser le système Cast avant de l'utiliser quelque part dans votre application.
Notez que ce CastPlayer crée un ExoPlayer en interne pour la lecture locale. Pour personnaliser davantage le lecteur local, vous pouvez également créer votre propre ExoPlayer et le transmettre en tant que lecteur local à ce CastPlayer.
Cast.getSingletonInstance(context).initialize()
val exoPlayer = ExoPlayer.Builder(context).build()
val player = CastPlayer.Builder(context)
.setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context,player)
.build()Et c'est encore une fois le même principe Media3.
Nous fournissons des composants d'API simples et prêts à l'emploi, mais aussi toutes les options de personnalisation dont vous avez besoin pour aller plus loin.
Vous vous demandez peut-être comment l'utilisateur passe de la lecture locale à la lecture à distance dans cette configuration.
Il fonctionne déjà de manière native avec la boîte de dialogue du commutateur de sortie du système dans la notification multimédia.
C'est le petit bouton dans l'angle.
Mais que faire si vous souhaitez afficher une icône Cast sur votre écran de lecture pour la rendre plus visible ?
Vous vous souvenez de l'UI Compose personnalisée que nous venons de créer il y a quelques minutes ?
Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)Media3 Cast fournit également un bouton prêt à l'emploi que vous pouvez placer à cet endroit.
Il suffit d'ajouter le MediaRouteButton prêt à l'emploi au topControls, par exemple. Aucun autre paramètre n'est nécessaire. C'est tout.
Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)En cliquant sur ce bouton, vous ouvrez la même boîte de dialogue de sélecteur de sortie du système, puis CastPlayer transfère la lecture pour vous.
Vous remarquerez également que j'ai connecté le CastPlayer à l'UI ici pour m'assurer que tout ce qui se passe à distance est reflété dans votre UI locale.
Je laisse maintenant la parole à Kristina, qui va vous parler des cas d'utilisation de modification.
Merci.
Bonjour, passons maintenant au montage et aux effets avancés.
Les API Media3 se composent de plusieurs composants.
Tout d'abord, Transformer est le point d'entrée principal de nos API de montage.
Transformer traite, transcode et exporte des contenus multimédias dans un fichier.
Ensuite, CompositionPlayer affiche des aperçus en temps réel de vos compositions multimédias.
Vous pouvez ainsi prévisualiser et affiner vos modifications avant l'exportation finale.
Ensuite, nous avons les effets.
Il vous donne un contrôle total sur le traitement image par image lorsque vous appliquez des effets visuels simples ou ajoutez des superpositions graphiques.
Enfin, nous avons le multiplexeur.
Il s'agit de l'étape de sortie finale de notre pipeline, qui prend les pistes audio, vidéo et de métadonnées traitées, et les écrit dans des conteneurs MP4 standardisés.
Nous avons passé l'année dernière à repenser et à reconstruire entièrement notre pipeline de montage vidéo. Aujourd'hui, je suis ravi de vous présenter les principales améliorations.
Pourquoi avons-nous recréé le pipeline ?
L'ancienne architecture présentait donc plusieurs problèmes.
Tout d'abord, il ne pouvait pas propager les métadonnées à toutes les étapes de traitement.
Il était étroitement lié à OpenGL, ce qui limitait la flexibilité des outils graphiques modernes.
De plus, son API complexe rendait la personnalisation et la maintenance difficiles pour les développeurs.
Pour relever ces défis, nous avons créé un tout nouveau pipeline de traitement.
Tout d'abord, il est désormais beaucoup plus simple d'assembler des compositions complexes multi-assets.
Ensuite, en vous dissociant d'OpenGL, vous êtes totalement libre d'utiliser des frameworks graphiques modernes, comme WebGPU, par exemple.
Enfin, Media3 gère désormais directement le décodeur et l'encodeur, ce qui garantit une plus grande cohérence et une meilleure efficacité sur tous les appareils.
Voyons comment fonctionne ce pipeline.
Les décodeurs envoient les images directement dans les tampons matériels gérés par Media3.
L'agrégateur de frames coordonne les frames sur plusieurs séquences, ce qui permet à votre processeur de frames personnalisé de lire et de traiter directement ces tampons avant de les envoyer à l'encodeur.
L'interface FrameProcessor sert de point d'extension principal pour le traitement au niveau de l'application.
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();
}Via l'interface de l'écouteur, votre application avertit Media3 chaque fois qu'une image a été traitée, en transmettant des barrières de synchronisation, afin que Media3 sache exactement quand les tampons peuvent être réutilisés en toute sécurité.
Remarque sur la mémoire unifiée : en permettant à Media3 de prendre le contrôle des tampons matériels, votre application peut lire et modifier les données directement sur place.
Cette approche sans copie réduit la surcharge de mémoire et permet un traitement ultra-rapide.
Adobe fait partie des développeurs qui profitent des nouvelles API de montage.
L'équipe Adobe Premiere a utilisé les API d'édition Media3 comme base de son infrastructure d'édition multimédia pour assurer une meilleure compatibilité avec l'écosystème Android, tout en conservant la flexibilité nécessaire pour utiliser son propre pipeline personnalisé et WebGPU.
Ensuite, j'ai le plaisir de vous annoncer que nous lançons de nouveaux effets dans la version 1.12 de Media3.
Commençons par les améliorations vidéo.
En arrière-plan, notre architecture repose sur une configuration modulaire.
Votre application s'intègre aux services Play pour les effets Media3, qui s'interfacent avec le module d'amélioration des services Play.
Le module d'amélioration vidéo inclut des fonctionnalités de mappage des tons, de suppression du bruit, de défloutage et de super-résolution.
L'intégration de Video Enhance à votre pipeline ne nécessite que quelques lignes de code.
val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)À l'aide de EnhancementSessionDecorator.Builder, vous pouvez activer de manière sélective des capacités telles que le mappage de tons, le défloutage ou l'upscaling, et transmettre les frameProcessorFactory et les assistants de tampon matériel obtenus à Transformer.Builder.
Un autre effet intéressant de Media3 1.12 est le son studio.
Studio Sound analyse automatiquement la piste audio de votre vidéo et la sépare en différentes pistes, comme la voix, le bruit ambiant et la musique.
Les créateurs peuvent facilement réduire les bruits de vent ou de foule tout en mettant en avant les dialogues et la musique.
Pour intégrer Studio Sound, transmettez le processeur audio Studio Sound dans un objet d'effet.
val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()Le processeur audio est alors associé à votre élément multimédia.
Lorsque vous créez le processeur audio Studio Sound, associez le rappel de l'écouteur pour recevoir le mappage des canaux audio analysés via onResult.
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))
}
}
},
)Parcourez chaque canal pour extraire l'index du canal, le type d'audio identifié et l'étiquette lisible lorsque le résultat de l'opération est disponible.
Écoutons maintenant le Son studio en action.
Voici le premier effet. S'il te plaît, ne pleure pas.
Nous coupons pour votre dîner.
On entend le bébé pleurer en arrière-plan, et maintenant avec l'effet appliqué.
Comme vous pouvez l'entendre, on n'entend pas de bébé pleurer en arrière-plan grâce à l'effet Son studio.
Voyons maintenant comment récupérer les métadonnées.
Nous verrons comment Media3 vous permet de lire facilement les métadonnées des conteneurs et d'extraire des images.
À partir de la version MetadataRetriever, il s'intègre parfaitement aux coroutines Kotlin pour un code propre et non bloquant.
Il présente une conception plus propre et moins sujette aux erreurs, en utilisant un récupérateur immuable par élément multimédia.
Elle fonctionne de manière cohérente sur toutes les versions d'API et tous les appareils.
Il est compatible avec un large éventail de formats multimédias dès sa sortie de l'emballage.
Voici un exemple concret utilisant 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()
}Les coroutines Kotlin vous permettent de récupérer la durée de manière asynchrone, et d'inspecter les formats de pistes et les métadonnées, le tout dans un seul bloc propre.
Parlons maintenant de FrameExtractor.
Il permet d'extraire rapidement et efficacement les images vidéo décodées d'un élément multimédia.
Il gère automatiquement les ressources sous-jacentes telles que les décodeurs, vous permet d'appliquer des effets visuels ou un mappage des tons HDR, et vous offre un contrôle précis de la recherche pour que vous puissiez obtenir l'image exacte dont vous avez besoin.
Voici à quoi ressemble l'extraction d'images dans le code.
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()
}Vous créez une instance avec des options telles que les paramètres de recherche, la réduction de la résolution du GPU, les effets et la compatibilité HDR, puis vous attendez de manière asynchrone les vignettes et les images spécifiques à l'aide d'appels de suspension propres.
Et maintenant, Donovan.
Merci, Kristina.
Passons maintenant à certaines des technologies de streaming sous-jacentes que nous avons ajoutées à Media3, y compris la prise en charge de formats tels qu'Eclipsa Video et Eclipsa Audio.
L'un des principaux objectifs de Media3 est donc de garder une longueur d'avance sur les normes de streaming afin que vous n'ayez pas à réécrire votre pipeline multimédia chaque fois qu'une nouvelle spécification est publiée.
Voici quelques exemples de fonctionnalités que nous avons lancées récemment.
Nous disposons d'une fonctionnalité de pilotage du contenu, qui permet de changer dynamiquement de chemin CDN de backend en cours de diffusion.
Nous proposons l'insertion d'annonces guidée par le serveur pour une intégration fluide des annonces personnalisées.
Nous avons également introduit la prise en charge d'AV1 sur plusieurs points d'entrée, y compris la prise en charge complète d'AV1 HDR.
Nous prenons en charge la vidéo professionnelle Android pour la capture et le montage haute fidélité.
Nous ajoutons également la prise en charge des photos animées aux formats JPEG et HEIC.
Et bien sûr, nous ajoutons la compatibilité avec Eclipsa Video et Eclipsa Audio, dont je vous parlerai plus en détail bientôt.
La bonne nouvelle, c'est que tout cela est pris en charge automatiquement à mesure que la compatibilité des appareils est déployée.
Examinons maintenant la prochaine évolution du HDR, Eclipsa Video, et le problème principal qu'il tente de résoudre.
Les formats HDR précédents fournissaient des métadonnées descriptives. Ils déterminaient donc l'aspect de la scène, mais n'indiquaient pas vraiment à l'écran comment la restituer.
Commençons par ancrer cela.
Pour différents environnements, il existe différentes recommandations pour la luminosité moyenne de votre écran afin que vous puissiez voir votre contenu clairement et confortablement.
Dans une pièce sombre, cette plage se situe entre 100 et 300 nits.
Et bien sûr, lorsque vous vous trouvez dans des environnements de plus en plus lumineux, l'écran doit s'adapter et devenir plus lumineux pour que vous puissiez voir clairement votre contenu. Lorsque vous regardez un téléphone en plein soleil, l'écran peut avoir besoin d'atteindre 1 000, 2 000, voire plus de nits pour que vous puissiez voir votre contenu.
La SDR traditionnelle fonctionne dans cette plage de 100 à 300.
Et bien sûr, le HDR n'est pas destiné à éclaircir l'écran de manière globale.
Vous souhaitez étendre la plage dynamique afin d'obtenir des zones lumineuses localisées pour des éléments tels que le feu et la lumière du soleil.
Cependant, le smartphone complique un peu les choses, car nous l'emportons partout avec nous, que ce soit dans une pièce sombre de notre maison ou sur une plage ensoleillée.
De plus, de nombreuses applications mélangent des éléments d'interface utilisateur SDR avec du contenu SDR et du contenu HDR dans des flux mixtes, ce qui peut provoquer un effet d'éblouissement désagréable, en particulier si vous faites défiler l'écran tard le soir.
Vous comprenez maintenant pourquoi le HDR est si intéressant ?
Il permet d'améliorer l'apparence de nos contenus dans davantage d'environnements.
Nous avons même constaté une augmentation de l'engagement utilisateur lorsque le HDR est activé.
Le problème, c'est que les formats HDR existants ne tiennent pas compte de ces environnements.
C'est là qu'Eclipsa Video révèle tout son potentiel.
(Pause pour les rires)
Merci.
Merci.
Ainsi, au lieu de métadonnées descriptives, Eclipsa Video fournit des métadonnées prescriptives.
Cette approche repose sur trois piliers principaux.
Le premier pilier est un point d'ancrage blanc de référence.
Cela verrouille la luminosité standard sur le point blanc SDR de l'écran, ajusté en fonction de la luminosité ambiante, ce qui élimine les pics de luminosité soudains.
Le deuxième pilier est la référence SDR des créateurs.
Cela permet de préserver la courbe de mappage des tons exacte du créateur jusqu'au SDR.
Troisièmement, nous disposons de courbes de gain adaptatives de marge, qui mettent à l'échelle de manière déterministe les zones claires pour qu'elles correspondent à la marge HDR en temps réel de l'écran, afin que le contenu corresponde à l'environnement dans lequel il est visionné.
Cette fonctionnalité est disponible dans ExoPlayer dès sa sortie dans Media3 1.11 sur les appareils équipés d'Android 17 ou version ultérieure.
De plus, Eclipsa Video est basé sur des normes ouvertes pour une compatibilité intersectorielle.
C'est donc le moment idéal pour prendre en charge le HDR.
Côté audio, nous avons Eclipsa Audio, un format audio spatial 3D Open Source et libre de droits.
Il est basé sur la spécification IAMF de l'Alliance for Open Media.
Il combine l'ambisonie basée sur la scène et l'audio basé sur les canaux en un seul flux avec plusieurs présentations mixtes.
Cela permet de manipuler les objets, par exemple pour améliorer les dialogues, et fonctionne sur tous les types d'appareils, des casques stéréo aux configurations de son surround.
Il existe en fait deux chemins de lecture dans Media3.
Pour Android 17 et versions ultérieures, ExoPlayer et le framework audio Android afficheront automatiquement Eclipsa Audio.
Pour Android 16 et les versions antérieures, vous avez toujours une option.
Vous pouvez activer l'extension IAMF via une bibliothèque de modules complémentaires Media3 facultative.
Pour ce qui est de la création, vous pouvez écrire des pistes audio IAMF à l'aide des multiplexeurs Media3.
Mais la prise en charge de formats de haute qualité n'est qu'une partie de l'histoire.
Nous devons fournir cette qualité de manière efficace.
Je vais maintenant vous expliquer comment Media3 rend nos pipelines plus rapides et plus efficaces.
Comme beaucoup de choses dont nous avons parlé aujourd'hui, la plupart de ces améliorations des performances sont disponibles d'emblée, simplement en maintenant votre version de Media3 à jour.
Par exemple, nous proposons le déchargement audio PCM, qui permet de rediriger la lecture audio vers une unité de traitement à faible consommation d'énergie.
D'après nos premières analyses, nous avons constaté jusqu'à 20 % d'économies d'énergie pour la lecture audio avec l'écran éteint.
En plus du déchargement audio PCM, nous disposons d'une planification dynamique, qui aligne les réveils de la boucle de rendu interne d'ExoPlayer sur la disponibilité de VSYNC et du tampon du décodeur.
Nous proposons également le préchauffage du moteur de rendu, qui vous permet de préchauffer un moteur de rendu vidéo secondaire. Cela permet d'effectuer des transitions instantanées dans les playlists et d'insérer des annonces plus facilement.
Nous disposons également d'une fonctionnalité de détection des lecteurs bloqués et de mise en cache préalable, qui détecte automatiquement les tampons et les moteurs de rendu bloqués, et limite proprement la mémoire mise en cache préalable.
Le mode d'utilisation de la barre de lecture est un autre mode optimal.
Cela s'applique aux applications dans lesquelles les utilisateurs parcourent fréquemment le contenu.
Lorsque vous activez le mode d'utilisation de la barre de lecture, le déplacement de la barre de recherche est beaucoup plus réactif et fluide, comme vous pouvez le voir ici.
Notez que le mode de nettoyage est désactivé.
À droite, le mode d'utilisation de la barre de lecture est activé.
Merci.
Merci.
Très bien.
Parlons maintenant de l'état du système Android.
Nous devons respecter les ressources partagées de RAM, de processeur et de batterie. Comme Android renforce ses règles concernant le renforcement audio et les services de premier plan, les applications qui gèrent leurs propres services bruts et bitmaps peuvent souvent rencontrer des erreurs.
Vous avez peut-être déjà rencontré des exceptions de type "foreground service start not allowed" (démarrage du service de premier plan non autorisé) ou "binder out-of-memory" (mémoire insuffisante pour le binder).
Nous avons donc renforcé la session du service multimédia et le gestionnaire de notifications multimédias, ainsi que nos pipelines de bitmaps, de plusieurs manières. Par exemple, nous empêchons les intents de service de premier plan obsolètes et nous limitons la mémoire des illustrations, entre autres.
Mais ce qu'il faut retenir, c'est que si vous utilisez Media3 et suivez les consignes standards, toutes ces restrictions d'état du système sont gérées pour vous sans impact.
Enfin, j'aimerais vous parler d'une fonctionnalité qui sera bientôt disponible dans Media3.
Lorsqu'il s'agit d'encoder ou d'exporter une vidéo, la diversité du matériel peut être l'un des plus grands défis.
De nombreuses applications utilisent une configuration de codec universelle, ce qui, comme vous le savez, entraîne une qualité et des performances non optimales.
C'est pourquoi nous avons créé CodecDB.
CodecDB est basé sur des mesures vidéo réelles et complètes, qui utilisent des métriques de qualité standardisées et des analyses comparatives matérielles sur les appareils Android.
Voyons cela en action.
À gauche, nous avons une image d'une vidéo que nous avons prise sur un téléphone, où nous avons simplement utilisé une configuration vidéo très générale que nous pourrions utiliser sur de nombreux appareils.
À droite, nous avons utilisé une configuration optimale au niveau de l'appareil, que nous avons obtenue à partir de CodecDB.
Quelles sont donc les différences ?
Si vous regardez les zones très détaillées, vous verrez la plus grande différence de qualité.
Regardez la clarté des branches des arbres et des arbustes, ou la netteté du bord du bâtiment.
Nous obtenons ainsi une expérience de visionnage bien meilleure, qui tire parti des capacités d'un appareil spécifique en exploitant CodecDB.
L'utilisation de CodecDB dans le code est assez simple.
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()Vous utiliserez un modèle de compilateur d'étapes fluide qui vous guidera à travers chaque paramètre requis.
Vous allez donc spécifier votre cas d'utilisation.
Ici, nous l'utilisons pour la modification.
Cela indique à CodecDB que vous utilisez le pipeline d'exportation ou de transcodage Media3.
Vous pouvez par exemple définir d'autres cas d'utilisation, comme la capture de l'appareil photo si vous l'utilisez.
Vous allez également transmettre la résolution et la fréquence d'images cibles.
Ensuite, au lieu de deviner si cette vidéo 1080p nécessite 8 Mbit/s ou 15 Mbit/s, vous indiquez à CodecDB le codec que vous souhaitez utiliser et vous demandez une cible de qualité perceptuelle.
Dans ce cas, nous utilisons le niveau de qualité "Très bon".
En arrière-plan, CodecDB recherche les courbes de benchmark empiriques pour ce SOC exact et renvoie les paramètres de codec et d'encodeur idéaux.
Nous prévoyons de lancer CodecDB dans Media3 très prochainement. Lorsque ce sera le cas, il utilisera les types Media3 natifs.
L'intégrer à votre code Media3 existant devrait donc être un jeu d'enfant.
OK.
Nous avons vu aujourd'hui de nombreuses fonctionnalités Media3 incroyables. Récapitulons.
Aujourd'hui, vous avez découvert nos nouveaux modules d'interface utilisateur Compose, le nouveau CastPlayer, le pipeline de montage repensé et de nombreux nouveaux effets que vous pouvez utiliser dans Media3.
Nous avons également vu de nouveaux outils pour récupérer des métadonnées et des images.
Nous avons également examiné la prise en charge des nouvelles technologies de streaming, l'amélioration des performances et les utilitaires.
Media3 est une bibliothèque Open Source. Vous pouvez donc voir comment nous avons implémenté toutes ces fonctionnalités dans notre dépôt GitHub.
À ce jour, la dernière version stable de Media3 est la 1.11.1.
Vous pouvez également consulter la version 1.12.0, qui est actuellement en version alpha.
Suivez ce lien court pour accéder à notre documentation pour les développeurs sur Media3.
Merci beaucoup et profitez bien du reste de l'ADS.
@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()