Ingeniera de Relaciones con Desarrolladores
El consumo de contenido multimedia representa una gran parte del tiempo que los usuarios pasan en dispositivos móviles, lo que requiere que la experiencia de reproducción sea impecable, altamente eficiente y que tenga en cuenta la duración de la batería. Además, los usuarios esperan que estas experiencias se transfieran sin problemas entre sus dispositivos, desde teléfonos hasta TVs de la sala de estar y mucho más.
Explora cómo las actualizaciones de arquitectura en Media3 y ExoPlayer permiten una reproducción fluida en todos los factores de forma. La conversación se centra en la creación de interfaces responsivas con el kit de herramientas de IU de Media3 Compose, la eliminación del almacenamiento en búfer en los feeds con el nuevo PreloadManager y la extensión de tu app a las pantallas de la sala de estar y Android XR con CastPlayer y audio espacial.
Transcripción
Bien.
Les damos la bienvenida.
Me llamo Toni.
Soy ingeniero del equipo de contenido multimedia de Android.
Junto con Kristina y Donovan, hablaremos sobre cómo Jetpack Media3 puede ayudarte con todas tus necesidades de contenido multimedia en Android.
Media3 es la biblioteca de compatibilidad de Android para todo lo relacionado con el contenido multimedia, ya sea la reproducción con ExoPlayer, el uso compartido de contenido multimedia con la sesión multimedia y Cast, la edición de contenido multimedia con Transformer o la inspección de contenido multimedia, como la extracción de fotogramas o metadatos.
Media3 abarca todo este espacio en un solo grupo de bibliotecas.
Como biblioteca de compatibilidad, se incluye en tu app.
Esto significa que podemos garantizar la coherencia, de modo que el mismo código se ejecute en todas las versiones del SO y en todos los dispositivos.
También ayuda con la capacidad de actualización.
Cada corrección de errores que hacemos llega hasta el nivel de API 24 y no está vinculada a ningún ciclo de lanzamiento del SO.
Y, como biblioteca de compatibilidad, podemos permitir opciones de personalización muy ricas, lo que no sería posible en el framework de Android.
Y, como biblioteca en GitHub, somos principalmente de código abierto.
Esto significa que cada cambio que hagamos estará disponible de inmediato en GitHub.
Tenemos un seguimiento de errores activo y aceptamos solicitudes de extracción.
Lo principal que hay que destacar aquí, en todos estos casos de uso, es que realmente nos importan todos los niveles de detalle, desde los elementos de la IU de alto nivel hasta las APIs listas para usar, pero también las opciones de personalización profunda, una amplia compatibilidad con varios formatos y estándares, y el rendimiento y la eficiencia que lo sustentan todo.
Media3 abarca todo este espacio.
Hoy, profundizaremos en algunos de estos temas.
Hablaremos sobre nuestro nuevo módulo de IU de Compose, el nuevo CastPlayer que facilita aún más el uso compartido con la sesión multimedia, una canalización de edición renovada con nuevos efectos avanzados, nuevas herramientas para recuperar metadatos y fotogramas, una nueva tecnología de transmisión, en particular, video y audio de Eclipsa, y una serie de utilidades de calidad de la experiencia para la carga previa, el arrastre y el ahorro de batería.
Ahora, profundicemos en el primer tema: cómo Media3 puede ayudar a compilar elementos de la IU para contenido multimedia.
Hace poco, publicamos dos módulos nuevos: Media3 UI Compose y Media3 UI Compose Material 3.
Aquí, puedes ver una función de componibilidad de reproductor de alto nivel, tal como se lanzó en Media3 1.10 sin más modificaciones.
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}Funciona de inmediato.
Solo necesita un reproductor de Media3.
En este ejemplo, es un ExoPlayer, que normalmente tendrías en tu modelo de vista.
Sin embargo, podría ser otra cosa, como un CastPlayer, un controlador multimedia o cualquier otro reproductor que tengas en tu app.
Vale la pena destacar que ExoPlayer es la fuente de información en este caso.
Contiene toda la información, y el elemento componible del reproductor se encarga de renderizar la IU y conectar todo.
Pero ¿qué sucede si quieres personalizar esta IU, agregar botones nuevos, cambiar las posiciones, los estilos o las superposiciones de texto?
El elemento componible del reproductor se compone de bloques de compilación.
Puedes reemplazarlos.
Puedes personalizar cada bloque individual o usarlos de forma independiente en cualquier otro lugar de tu app.
Por ejemplo, puedes personalizar la parte superior, el centro y la bottomControls por separado.
También puedes personalizar el aspecto del obturador, que se muestra cuando no se reproducen videos, o una superposición de error.
Pero esta es solo la primera capa.
El centro predeterminado y bottomControls también tienen espacios más detallados que puedes personalizar.
Por ejemplo, puedes cambiar el botón central y dejar todo lo demás como está, o agregar elementos por encima o por debajo del control deslizante.
Media3 Compose proporciona todos los componentes básicos que necesitas para hacerlo.
Por ejemplo, tenemos varios botones, como el de reproducción/pausa, el de modo de repetición, el de modo aleatorio, el de silencio y otros.
Luego, están los indicadores de progreso, como el control deslizante y el texto de posición que se muestran en la pantalla.
También hay una serie de contenedores y superposiciones.
El elemento componible de alto nivel del reproductor que te mostré es un ejemplo, pero hay más, como las superposiciones de errores o los subtítulos, entre muchos otros.
Además, tenemos otros componentes útiles, como este minicontrolador.
Puedes colocarlo en una hoja inferior mientras el usuario navega por tu app.
Y aún no terminamos.
Estamos ampliando activamente esta biblioteca, por ejemplo, con nuevos controles de edición, como un control deslizante de recorte que estará disponible próximamente.
Ahora, veamos esta personalización en acción para ver cómo se usan estos componentes básicos.
Player(viewModel.exoPlayer)Supongamos que queremos mover el texto de la posición y la duración, que actualmente están junto al control deslizante, por encima de él.
Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)Por lo tanto, lo que podemos hacer es anular estos bottomControls.
Podemos seguir usando el componente predeterminado, pero configuramos las ranuras izquierda y derecha como vacías para que desaparezcan.
En su lugar, colocamos el texto de la posición y la duración en el espacio superior con un separador personalizado para que se vea más interesante.
Ahora, supongamos que queremos quitar por completo la superposición de centerControls y, en su lugar, colocar un botón de reproducción justo encima del control deslizante con un control de velocidad y un botón para silenciar.
Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}Y esa es exactamente la misma idea de nuevo.
Podemos establecer el centerControls como vacío para que desaparezca y expandir el espacio sobre el control deslizante con un poco de espacio adicional y los tres botones que queremos mostrar.
Ten en cuenta que estos botones provienen de la biblioteca, por lo que no tienes que agregar ningún código adicional.
¡Eso es todo lo que necesitas!
Y puedes seguir este camino para aplicar diseño a estos elementos también.
Si quieres cambiar el ícono del botón para silenciar, por ejemplo, pero mantener su funcionalidad, también funciona de la misma manera.
Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)Como puedes ver aquí, puedes definir un pintor personalizado para el botón y elegir los íconos tú mismo.
El componente proporciona algunos metadatos, por lo que todo lo que debes hacer es, por ejemplo, mirar el estado, como showMuted en este ejemplo, para elegir el ícono correcto.
Y este es realmente el principal beneficio de Media3 Compose.
Puedes enfocarte en el aspecto de tu app, y Media3 se encargará del resto.
Lee el estado del reproductor, interactúa con él y controla todas las transiciones y animaciones intermedias.
Ahora, pasemos de la IU al uso compartido de contenido multimedia.
Puedes compartir tu estado de reproducción con otras apps en el mismo dispositivo o conectadas a dispositivos remotos.
La forma principal de publicar tu reproducción en curso es con MediaSession.
Hace que el estado y los metadatos de tu reproducción estén disponibles para otras personas y les permite controlar tu reproducción.
Así es como Gemini, la notificación de contenido multimedia de la IU del sistema, Cast, Wear OS o las apps complementarias de Android Auto se comunican con tu app.
MediaSession une un reproductor de Media3, y es muy fácil crear uno una vez que tienes un reproductor.
MediaSession.Builder(context, player).build()Nuevamente, podría ser un ExoPlayer o cualquier otro reproductor que tengas en tu app, al igual que con la IU de Compose.
Si reproduces contenido en segundo plano, por lo general, lo alojarías dentro de un MediaSessionService o un MediaLibraryService, que también controla automáticamente todo el ciclo de vida y elementos como las notificaciones.
class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}Pero si tienes una app de contenido multimedia, es probable que ya sepas todo esto.
La nueva y emocionante función que quiero mostrarte es nuestro CastPlayer recientemente reescrito.
Google Cast te permite transferir tu reproducción local a un dispositivo remoto, como Android TV, Chromecast o bocinas inteligentes.
Antes publicábamos un SDK de Cast independiente, que ahora está en modo de mantenimiento.
En cambio, Media3 Cast lo reemplaza por completo, ya que proporciona una integración mucho más sencilla y optimizada.
El elemento más importante aquí es CastPlayer, que implementa la misma interfaz de reproductor estándar que ExoPlayer.
Mientras que ExoPlayer es para la reproducción local, CastPlayer es una instancia de delegación que puede reproducir contenido de forma local a través de ExoPlayer o de forma remota a través del protocolo de Cast.
Y se encarga de todo.
Escucha los cambios en el dispositivo de salida, establece la conexión, sincroniza el estado y transfiere la reproducción por ti cuando es necesario.
Por ese motivo, integrar CastPlayer en tu app es muy sencillo.
val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()En lugar de crear un ExoPlayer y compartirlo con MediaSession de esta manera, usa un CastPlayer y listo.
Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Lo único que debes hacer es inicializar el sistema de Cast antes de usarlo en algún lugar de tu app.
Ten en cuenta que este CastPlayer compila un ExoPlayer de forma interna para la reproducción local, por lo que, para personalizar aún más el reproductor local, también puedes compilar tu propio ExoPlayer y pasarlo como reproductor local a este 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()Y este es, de nuevo, el mismo principio de Media3.
Proporcionamos los componentes básicos de la API listos para usar, pero también todas las opciones de personalización que necesitas para adaptarla aún más.
Y tal vez te preguntes cómo el usuario cambia entre la reproducción local y remota en esta configuración.
Ya funciona de inmediato con el diálogo del selector de salida del sistema en la notificación de contenido multimedia.
Es el botón pequeño que está en la esquina.
Pero ¿qué pasa si quieres mostrar un botón para transmitir en la pantalla de reproducción para que sea más visible?
¿Recuerdas esta IU de Compose personalizada que acabamos de compilar hace unos minutos?
Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)Media3 Cast también proporciona un botón listo para usar que puedes colocar allí.
Es tan simple como agregar el MediaRouteButton listo para usar al topControls, por ejemplo, y no necesitamos configurar ningún otro parámetro, eso es todo.
Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)Si haces clic en este botón, se abrirá el mismo diálogo del selector de salida del sistema y, luego, CastPlayer transferirá la reproducción por ti.
También puedes observar que conecté el CastPlayer a la IU aquí para asegurarme de que lo que suceda de forma remota se refleje en tu IU local.
Y, con eso, le doy la palabra a Kristina para que hable sobre los casos de uso de edición.
Gracias.
Hola, ahora pasemos a la edición y los efectos avanzados.
Las APIs de Media3 constan de varios componentes.
En primer lugar, Transformer es el punto de entrada principal para nuestras APIs de edición.
Transformer procesa, transcodifica y exporta contenido multimedia a un archivo.
A continuación, se encuentra CompositionPlayer, que renderiza vistas previas en tiempo real de tus composiciones multimedia.
Esto te permite obtener una vista previa de tus ediciones y ajustarlas antes de la exportación final.
Luego, tenemos los efectos.
Te brinda un control total sobre el procesamiento fotograma por fotograma cuando aplicas efectos visuales simples o agregas superposiciones gráficas.
Por último, tenemos Muxer.
Sirve como la etapa de salida final en nuestra canalización, ya que toma las pistas de audio, video y metadatos procesadas, y las escribe en contenedores MP4 estandarizados.
Durante el último año, repensamos y reconstruimos nuestro proceso de edición de videos desde cero y, hoy, me complace compartir las mejoras más importantes.
¿Por qué reconstruimos la canalización?
Por lo tanto, la arquitectura heredada presentaba varios desafíos.
Primero, no podía propagar metadatos en todos los pasos de procesamiento.
Estaba estrechamente vinculado a OpenGL, lo que limitaba la flexibilidad de las herramientas gráficas modernas.
Además, su compleja API hacía que la personalización y el mantenimiento fueran un gran obstáculo para los desarrolladores.
Para abordar estos desafíos, creamos una canalización de procesamiento completamente nueva.
En primer lugar, ahora es mucho más sencillo ensamblar composiciones complejas de varios recursos.
En segundo lugar, al desacoplarte de OpenGL, tienes total libertad para usar frameworks gráficos modernos, como WebGPU, por ejemplo.
Por último, Media3 ahora administra el decodificador y el codificador directamente, lo que garantiza una mayor coherencia y eficiencia en todos los dispositivos.
Veamos cómo funciona esta canalización.
Los decodificadores envían fotogramas directamente a los búferes de hardware que administra Media3.
El agregador de fotogramas coordina los fotogramas en varias secuencias, lo que le da a tu procesador de fotogramas personalizado acceso directo para leer y procesar esos búferes antes de enviarlos al codificador.
La interfaz FrameProcessor funciona como el punto de extensión principal para el procesamiento a nivel de la app.
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();
}A través de la interfaz de objeto de escucha, tu aplicación notifica a Media3 cada vez que se procesa un fotograma y pasa barreras de sincronización para que Media3 sepa exactamente cuándo se pueden reutilizar los búferes de forma segura.
Una nota sobre la memoria unificada: Si Media3 se apropia de los búferes de hardware, tu app puede leer y modificar datos directamente en el lugar.
Este enfoque de copia cero reduce la sobrecarga de memoria y mantiene el procesamiento ultrarrápido.
Adobe es uno de los desarrolladores que aprovecha las nuevas APIs de edición.
El equipo de Adobe Premiere usó las APIs de edición de Media3 como base de su infraestructura de edición de contenido multimedia para garantizar una mayor compatibilidad en todo el ecosistema Android, a la vez que mantuvo la flexibilidad que necesita para usar su propia canalización personalizada y WebGPU.
A continuación, me complace anunciar que lanzaremos nuevos efectos como parte de la versión 1.12 de Media3.
Comenzaremos con las mejoras de video.
De forma automática, nuestra arquitectura se basa en una configuración modular.
Tu aplicación se integra con los Servicios de Play de efectos de Media3, que interactúan con el módulo de mejora de los Servicios de Play.
El módulo de Video Enhance incluye funciones de asignación de tonos, reducción de ruido, eliminación de desenfoque y superresolución.
Integrar Video Enhance en tu canalización requiere solo unas pocas líneas de código.
val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)Con EnhancementSessionDecorator.Builder, puedes habilitar de forma selectiva funciones como la asignación de tonos, la corrección de desenfoque o el reescalado, y pasar los ayudantes de búfer de hardware y frameProcessorFactory resultantes a Transformer.Builder.
Otro efecto emocionante que llegará en Media3 1.12 es Studio Sound.
Sonido de estudio analiza automáticamente la pista de audio de tu video y la separa en pistas individuales, como voz, ruido ambiental y música.
Los creadores pueden reducir fácilmente las distracciones del viento o de la multitud, y, al mismo tiempo, destacar los diálogos y la música.
Para integrar Studio Sound, pasa el procesador de audio de Studio Sound dentro de un objeto de efecto.
val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()Esto adjunta el procesador de audio a tu elemento multimedia.
Cuando crees el procesador de audio de Studio Sound, adjunta la devolución de llamada del objeto de escucha para recibir la asignación de canales de audio analizados a través de 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))
}
}
},
)Itera por cada canal para extraer el índice del canal, el tipo de audio identificado y la etiqueta legible cuando el resultado de la operación esté disponible.
Escuchemos Sonido de estudio en acción.
Entonces, el primer efecto. Bebé, por favor, no llores.
Estamos cortando para tu cena.
Puedes escuchar el llanto del bebé de fondo y, ahora, con el efecto aplicado.
Como puedes escuchar, no se oye el llanto del bebé de fondo gracias al efecto de Sonido de estudio.
Ahora, analicemos la recuperación de metadatos.
Veremos cómo Media3 te facilita la lectura de los metadatos del contenedor y la extracción de fotogramas.
A partir de MetadataRetriever, se integra sin problemas con las corrutinas de Kotlin para obtener un código limpio y no bloqueador.
Tiene un diseño más limpio y menos propenso a errores, ya que usa un recuperador inmutable por elemento multimedia.
Funciona de manera coherente en todas las versiones de la API y en todos los dispositivos.
Además, admite una amplia variedad de formatos multimedia de forma predeterminada.
Aquí tienes un ejemplo práctico con 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()
}Con las corrutinas de Kotlin, puedes recuperar la duración de forma asíncrona, inspeccionar los formatos de las pistas y los metadatos, todo dentro de un solo bloque limpio.
Ahora, hablemos de FrameExtractor.
Permite extraer fotogramas decodificados de un elemento multimedia de forma rápida y eficiente.
Administra automáticamente los recursos subyacentes, como los decodificadores, te permite aplicar efectos visuales o asignación de tonos HDR, y te brinda un control de búsqueda detallado para que puedas obtener el fotograma exacto que necesitas.
Así es como se ve la extracción de fotogramas en el código.
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()
}Creas una instancia con opciones como parámetros de búsqueda, reducción de escala de GPU, efectos y compatibilidad con HDR, y, luego, esperas de forma asíncrona las miniaturas y los fotogramas específicos con llamadas de suspensión limpias.
Y ahora, a Donovan.
Gracias, Kristina.
Ahora, me gustaría hablar sobre algunas de las tecnologías de transmisión subyacentes que agregamos en Media3, incluida la compatibilidad con formatos como Eclipsa Video y Eclipsa Audio.
Por lo tanto, un esfuerzo central en Media3 es mantenerse a la vanguardia de los estándares de transmisión para que, cada vez que se lance una nueva especificación, no tengas que volver a escribir tu canalización de contenido multimedia.
Estos son algunos ejemplos de lo que lanzamos recientemente.
Tenemos la dirección de contenido, que permite cambiar de forma dinámica las rutas de CDN de backend durante la transmisión.
Ofrecemos compatibilidad con la inserción de anuncios guiada por el servidor para que la unión de anuncios personalizados sea fluida.
También presentamos la compatibilidad con AV1 en varios puntos de entrada, incluida la compatibilidad total con AV1 HDR.
Ofrecemos compatibilidad con videos profesionales de Android para capturas y ediciones de alta fidelidad.
También agregaremos compatibilidad con las fotos en movimiento en los formatos JPEG y HEIC.
Y, por supuesto, agregaremos compatibilidad con Eclipsa Video y Eclipsa Audio, de los que hablaré más adelante.
Y la buena noticia es que todo esto se admite automáticamente a medida que se lanza la compatibilidad con dispositivos.
Ahora, veamos la próxima evolución de HDR, Eclipsa Video, y el problema principal que intenta resolver.
Los formatos HDR anteriores proporcionaban metadatos descriptivos, por lo que determinaban cómo debía verse la escena, pero no le indicaban a la pantalla cómo renderizarla.
Primero, analicemos esto.
Para diferentes entornos, hay diferentes recomendaciones para el brillo promedio de la pantalla, de modo que puedas ver el contenido con claridad y comodidad.
Por lo tanto, en una habitación con poca luz, ese rango estará entre 100 y 300 nits.
Y, por supuesto, a medida que te encuentras en entornos cada vez más brillantes, la pantalla tiene que seguir el ritmo y aumentar el brillo para que puedas ver el contenido con claridad. Cuando miras un teléfono bajo la luz solar directa, es posible que la pantalla deba alcanzar los 1,000, 2,000 o incluso más nits para que puedas ver el contenido.
El SDR tradicional opera en ese rango de 100 a 300.
Y, por supuesto, el HDR no está diseñado para aumentar el brillo de la pantalla de forma global.
Quieres expandir el rango dinámico para tener luces localizadas para elementos como el fuego y la luz solar.
Sin embargo, el smartphone introduce algunas complicaciones, ya que lo llevamos con nosotros a todas partes, ya sea a una habitación oscura de nuestra casa o a una playa soleada.
Eso, sumado al hecho de que muchas apps mezclan elementos de la IU de SDR con contenido de SDR y HDR en feeds mixtos, puede provocar ese efecto cegador y molesto que a veces se produce, sobre todo si te desplazas por la pantalla tarde por la noche.
Y podemos ver por qué vale la pena el HDR, ¿verdad?
Hace que nuestro contenido se vea genial en más entornos.
Incluso hemos visto un aumento en la participación de los usuarios cuando se activa el HDR.
Pero el problema es que los formatos HDR existentes no tienen en cuenta estos entornos.
Ahí es donde Eclipsa Video realmente se destaca.
Pausa para las risas.
Gracias.
Gracias.
Por lo tanto, en lugar de metadatos descriptivos, Eclipsa Video proporciona metadatos prescriptivos.
Y eso se basa en tres pilares principales.
El primer pilar es un anclaje de blanco de referencia.
Esto bloquea el brillo estándar en el punto blanco de SDR ajustado al ambiente de la pantalla, lo que eliminará los picos de brillo repentinos.
El segundo pilar es la referencia de SDR para creadores.
Esto conserva la curva de asignación de tono exacta del creador en SDR.
En tercer lugar, tenemos curvas de ganancia adaptables de margen, que escalan de forma determinista las luces para que coincidan con el margen de HDR en tiempo real de la pantalla, de modo que el contenido coincida con el entorno en el que se visualiza.
Esta función está disponible en ExoPlayer de forma predeterminada en Media3 1.11 en dispositivos que ejecutan Android 17 y versiones posteriores.
Además, Eclipsa Video se basa en estándares abiertos para garantizar la compatibilidad entre diferentes industrias.
Por lo tanto, nunca hubo un mejor momento para admitir HDR.
En cuanto al audio, tenemos Eclipsa Audio, que es un formato de audio espacial 3D de código abierto y libre de regalías.
Se basa en la especificación de IAMF de Alliance for Open Media.
Combina Ambisonics basado en escenas con audio basado en canales en una sola transmisión con varias presentaciones mezcladas.
Esto permite manipulaciones similares a las de los objetos, para cosas como la mejora de los diálogos, y funcionará en todo, desde auriculares estéreo hasta configuraciones de sonido envolvente.
En realidad, hay dos rutas de reproducción en Media3.
En Android 17 y versiones posteriores, ExoPlayer y el framework de audio de Android renderizarán automáticamente Eclipsa Audio.
Y para Android 16 y versiones anteriores, aún tienes una opción.
Puedes habilitar la extensión de IAMF a través de una biblioteca de complementos opcional de Media3.
En cuanto a la creación, puedes escribir pistas de audio de IAMF con los multiplexores de Media3.
Sin embargo, admitir formatos de alta calidad es solo una parte de la historia.
Debemos ofrecer esa calidad de manera eficiente.
Ahora, me gustaría hablar sobre cómo Media3 hace que nuestras canalizaciones sean más rápidas y eficientes.
Al igual que muchas de las cosas de las que hablamos hoy, la mayoría de estas mejoras de rendimiento están disponibles de inmediato con solo mantener actualizada tu versión de Media3.
Un ejemplo de una función que tenemos es la descarga de audio PCM, que puede enrutar la reproducción de audio a una unidad de procesamiento de bajo consumo.
En nuestro análisis inicial, observamos un ahorro de energía de hasta un 20% en la reproducción de audio con la pantalla apagada.
Además de la descarga de audio PCM, tenemos la programación dinámica, que alinea las activaciones del bucle de renderización interno de ExoPlayer con la disponibilidad del búfer del decodificador y VSYNC.
También tenemos el precalentamiento del renderizador, que te permite precalentar un renderizador de video secundario, lo que permitirá transiciones instantáneas de la playlist y una inserción de anuncios más sencilla.
También contamos con detección de reproductores bloqueados y almacenamiento previo en caché, que detecta automáticamente los búferes y renderizadores detenidos, y limita la memoria almacenada previamente en caché de forma limpia.
Otro modo óptimo que tenemos es el modo de arrastre.
Esto es para apps en las que los usuarios arrastran el contenido con frecuencia.
Por lo tanto, cuando habilitas el modo de arrastre, arrastrar la barra de búsqueda es mucho más responsivo y fluido, como puedes ver aquí.
Por cierto, esto es con el modo de arrastre desactivado.
Y, a la derecha, vemos el modo de arrastre activado.
Gracias.
Gracias.
Bien.
Ahora, hablemos sobre el estado del sistema Android.
Debemos respetar los recursos compartidos de RAM, CPU y batería, por lo que, a medida que Android endurece sus reglas en torno al endurecimiento de audio y los servicios en primer plano, las apps que administran sus propios servicios sin procesar y mapas de bits pueden tener algunos errores.
Algunos de los problemas que podrías haber visto son excepciones de inicio de servicio en primer plano no permitido o excepciones de falta de memoria de Binder.
Por lo tanto, reforzamos la sesión del servicio multimedia y el administrador de notificaciones multimedia, así como nuestras canalizaciones de mapas de bits de varias maneras, como evitar intents de servicios en primer plano obsoletos y vincular la memoria de las ilustraciones, entre otras cosas.
Pero lo que debes tener en cuenta es que, si usas Media3 y sigues las instrucciones estándar, todas estas restricciones de estado del sistema se controlan por ti sin ningún impacto.
Por último, me gustaría hablar sobre una función que llegará próximamente a Media3.
Cuando llega el momento de codificar o exportar un video, la diversidad de hardware puede ser uno de los mayores desafíos.
Muchas apps usan una configuración de códec única, lo que, como sabes, genera una calidad y un rendimiento que no son óptimos.
Por eso, creamos CodecDB.
CodecDB se basa en mediciones de video extensas del mundo real con métricas de calidad estandarizadas y comparativas de hardware en dispositivos Android.
Veamos cómo funciona.
A la izquierda, tenemos un fotograma de un video que grabamos con un teléfono en el que usamos una configuración de video muy general que podríamos usar en muchos dispositivos.
Y, a la derecha, usamos una configuración óptima a nivel del dispositivo que obtuvimos de CodecDB.
Entonces, ¿dónde están las diferencias?
Si observas las áreas con muchos detalles, verás la mayor diferencia de calidad.
Observa la claridad en las ramas de los árboles y los arbustos, o la nitidez del borde del edificio.
Por lo tanto, lo que obtenemos aquí es una experiencia de visualización mucho mejor que aprovecha las capacidades de un dispositivo específico con CodecDB.
Y usar CodecDB en el código es bastante sencillo.
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()Usarás un patrón de compilador de etapas fluido que te guiará por cada parámetro obligatorio.
Por lo tanto, especificarás tu caso de uso.
Aquí, usamos la función para editar.
Esto le indica a CodecDB que estás usando la canalización de exportación o transcodificación de Media3.
Otros casos de uso que podrías configurar, por ejemplo, son la captura de la cámara si es para lo que la usas.
También pasarás la resolución y la velocidad de fotogramas objetivo.
Luego, en lugar de adivinar si ese video de 1080p necesita 8 megabits por segundo o 15 megabits por segundo, le dices a CodecDB qué códec quieres usar y le pides un objetivo de calidad perceptual.
En este caso, usaremos el nivel de calidad muy bueno.
De forma interna, CodecDB busca curvas de comparativas empíricas para ese SoC exacto y devuelve los parámetros ideales del códec y del codificador.
Esperamos que CodecDB se lance en Media3 muy pronto y, cuando lo haga, usará tipos nativos de Media3.
Por lo tanto, integrarlo con tu código de Media3 existente debería ser muy sencillo.
Bien.
Hoy vimos muchas funciones increíbles en Media3, así que hagamos un resumen.
Hoy aprendiste sobre nuestros nuevos módulos de IU de Compose, el nuevo CastPlayer, la canalización de edición renovada y muchos efectos nuevos que puedes usar en Media3.
También vimos herramientas nuevas para recuperar metadatos y fotogramas.
También analizamos la compatibilidad con nuevas tecnologías de transmisión, la mejora del rendimiento y las utilidades.
Media3 es una biblioteca de código abierto, por lo que puedes ver cómo implementamos todas estas funciones en nuestro repositorio de GitHub.
Hasta hoy, la versión estable más reciente de Media3 es la 1.11.1.
También puedes consultar la versión 1.12.0, que actualmente se encuentra en versión alfa.
Y sigue este vínculo corto, que te llevará a nuestra documentación para desarrolladores sobre Media3.
Muchas gracias y disfruta el resto de 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()