Mejora la reproducción de contenido multimedia: Presentamos la carga previa con Media3 (parte 1)
Lectura de 8 min
En las apps centradas en el contenido multimedia de hoy en día, brindar una experiencia de reproducción fluida y sin interrupciones es clave para una experiencia del usuario agradable. Los usuarios esperan que sus videos comiencen de inmediato y se reproduzcan sin problemas ni pausas.
El desafío principal es la latencia. Tradicionalmente, un reproductor de video solo comienza a funcionar (conectarse, descargar, analizar y almacenar en búfer) después de que el usuario elige un elemento para reproducir. Este enfoque reactivo es lento para el contexto actual de videos de formato corto. La solución es ser proactivo. Debemos anticipar lo que el usuario verá a continuación y preparar el contenido con anticipación. Esta es la esencia de la carga previa.
Entre los beneficios clave de la carga previa, se incluyen los siguientes:
- 🚀 Inicio de reproducción más rápido: Los videos ya están listos, lo que permite transiciones más rápidas entre los elementos y un inicio más inmediato.
- 📉 Almacenamiento en búfer reducido: Si se cargan los datos de forma proactiva, es mucho menos probable que la reproducción se detenga, por ejemplo, debido a problemas de red.
- ✨ Experiencia del usuario más fluida: La combinación de inicios más rápidos y menos almacenamiento en búfer crea una interacción más fluida y sin interrupciones para que los usuarios disfruten.
En esta serie de tres partes, presentaremos y analizaremos en detalle las potentes utilidades de Media3 para cargar (previamente) componentes.
- En la parte 1, abordaremos los aspectos básicos: comprender las diferentes estrategias de carga previa disponibles en Media3, habilitar PreloadConfiguration y configurar DefaultPreloadManager, lo que permite que tu app cargue previamente elementos. Al final de esta entrada de blog, deberías poder cargar previamente y reproducir elementos multimedia con la clasificación y la duración configuradas.
- En la parte 2, abordaremos temas más avanzados de DefaultPreloadManager: usar objetos de escucha para estadísticas, explorar prácticas recomendadas listas para producción, como el patrón de ventana deslizante y los componentes compartidos personalizados de DefaultPreloadManager y ExoPlayer.
- En la parte 3, analizaremos en detalle el almacenamiento en caché en el disco con DefaultPreloadManager.
¡La carga previa al rescate! 🦸♀️
La idea principal detrás de la carga previa es simple: cargar contenido multimedia antes de que lo necesites. Cuando un usuario desliza el dedo hacia el siguiente video, los primeros segmentos del video ya se descargaron y están disponibles para la reproducción inmediata.
Piensa en un restaurante. Una cocina ocupada no espera un pedido para comenzar a cortar cebollas. 🧅 Hacen el trabajo de preparación con anticipación. La carga previa es el trabajo de preparación para tu reproductor de video.
Cuando está habilitada, la carga previa puede ayudar a minimizar la latencia de unión cuando un usuario omite el siguiente elemento antes de que el búfer de reproducción llegue a él. Se prepara el primer período de la siguiente ventana y se almacenan en búfer muestras de video, audio y texto. El período precargado se pone en cola más tarde en el reproductor con muestras almacenadas en búfer disponibles de inmediato y listas para enviarse al códec para la renderización.
En Media3, hay dos APIs principales para la carga previa, cada una adecuada para diferentes casos de uso. Elegir la API correcta es el primer paso.
1. Carga previa de elementos de la playlist con PreloadConfiguration
Este es el enfoque simple, útil para contenido multimedia lineal y secuencial, como playlists en las que el orden de reproducción es predecible (como una serie de episodios). Le proporcionas al reproductor la lista completa de elementos multimedia con las APIs de playlist de ExoPlayer y configuras PreloadConfiguration para el reproductor. Luego, se cargan previamente de forma automática los siguientes elementos de la secuencia según la configuración. Esta API intenta optimizar la latencia de unión cuando un usuario omite el siguiente elemento antes de que el búfer de reproducción ya se superponga en el siguiente elemento.
La carga previa solo se inicia cuando no se carga contenido multimedia para la reproducción en curso, lo que evita que compita por el ancho de banda con la reproducción principal.
Si aún no estás seguro de si necesitas la carga previa, esta API es una excelente opción de bajo esfuerzo para probarla.
player.preloadConfiguration =
PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L)Con la PreloadConfiguration anterior, el reproductor intenta cargar previamente cinco segundos de contenido multimedia para el siguiente elemento de la playlist.
Una vez que se habilita, la carga previa de la playlist se puede volver a desactivar con PreloadConfiguration.DEFAULT para inhabilitar la carga previa de la playlist:
player.preloadConfiguration = PreloadConfiguration.DEFAULT
2. Carga previa de listas dinámicas con PreloadManager
Para las IUs dinámicas, como feeds verticales o carruseles, en las que el elemento "siguiente" se determina por la interacción del usuario, es adecuada la API de PreloadManager. Este es un componente nuevo, potente e independiente dentro de la biblioteca de Media3 ExoPlayer diseñado específicamente para precargar de forma proactiva. Administra una colección de MediaSources potenciales, priorizándolas según la proximidad a la posición actual del usuario y ofrece un control detallado sobre lo que se debe cargar previamente, adecuado para situaciones complejas, como feeds dinámicos de videos de formato corto.
Cómo configurar PreloadManager
El DefaultPreloadManager es la implementación canónica de PreloadManager.
El compilador de DefaultPreloadManager puede compilar DefaultPreloadManager y cualquier instancia de ExoPlayer que reproduzca su contenido precargado. Para crear un DefaultPreloadManager, deberás pasar un TargetPreloadStatusControl, que el administrador de carga previa puede consultar para averiguar cuánto cargar para un elemento. Explicaremos y definiremos un ejemplo de TargetPreloadStatusControl en la siguiente sección.
val preloadManagerBuilder = DefaultPreloadManager.Builder(context, targetPreloadStatusControl) val preloadManager = val preloadManagerBuilder.build() // Build ExoPlayer with DefaultPreloadManager.Builder val player = preloadManagerBuilder.buildExoPlayer()
Es necesario usar el mismo compilador para ExoPlayer y DefaultPreloadManager, lo que garantiza que los componentes internos de ellos se compartan correctamente.
Eso es todo. Ahora tienes un administrador listo para recibir instrucciones.
Cómo configurar la duración y la clasificación con TargetPreloadStatusControl
¿Qué sucede si quieres cargar previamente, por ejemplo, 10 segundos de video? Puedes proporcionar la posición de tus elementos multimedia en el carrusel, y DefaultPreloadManager prioriza la carga de los elementos según la proximidad al elemento que el usuario está reproduciendo en ese momento.
Si quieres controlar la duración del elemento que se cargará previamente, puedes indicarlo con DefaultPreloadManager.PreloadStatus que devuelves.
Por ejemplo:
- El elemento "A" es la prioridad más alta, carga 5 segundos de video.
- El elemento "B" es de prioridad media, pero cuando llegues a él, carga 3 segundos de video.
- El elemento "C" tiene menos prioridad, solo carga pistas.
- El elemento "D" tiene aún menos prioridad, solo prepáralo.
- Cualquier otro elemento está lejos, no cargues nada previamente.
Este control detallado puede ayudarte a optimizar el uso de recursos, lo que se recomienda para una reproducción sin problemas.
import androidx.media3.exoplayer.DefaultPreloadManager.PreloadStatus
class MyTargetPreloadStatusControl(
currentPlayingIndex: Int = C.INDEX_UNSET
) : TargetPreloadStatusControl<Int,PreloadStatus> {
// The app is responsible for updating this based on UI state
override fun getTargetPreloadStatus(index: Int): PreloadStatus? {
val distance = index - currentPlayingIndex
// Adjacent items (Next): preload 5 seconds
if (distance == 1) {
// Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED and suggest loading // 5000ms from the default start position
return PreloadStatus.specifiedRangeLoaded(5000L)
}
// Adjacent items (Previous): preload 3 seconds
else if (distance == -1) {
// Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED //and suggest loading 3000ms from the default start position
return PreloadStatus.specifiedRangeLoaded(3000L)
}
// Items two positions away: just select tracks
else if (distance) == 2) {
// Return a PreloadStatus that is labelled by STAGE_TRACKS_SELECTED
return PreloadStatus.TRACKS_SELECTED
}
// Items four positions away: just select prepare
else if (abs(distance) <= 4) {
// Return a PreloadStatus that is labelled by STAGE_SOURCE_PREPARED
return PreloadStatus.SOURCE_PREPARED
}
// All other items are too far away
return null
}
}Nota: PreloadManager puede mantener precargados los elementos anteriores y siguientes, mientras que PreloadConfiguration solo buscará los siguientes elementos.
Cómo administrar elementos de carga previa
Una vez que se crea la cuenta de administrador, puedes comenzar a indicarle en qué trabajar. A medida que el usuario se desplaza por un feed, identificarás los próximos videos y los agregarás al administrador. La interacción con PreloadManager es una conversación basada en el estado entre tu IU y el motor de carga previa.
1. Agrega elementos multimedia
A medida que completas tu feed, debes informar al administrador sobre el contenido multimedia que debe hacer un seguimiento. Si estás comenzando, puedes agregar toda la lista que deseas cargar previamente. Posteriormente, puedes seguir agregando un solo elemento a la lista cuando sea necesario. Tienes control total sobre los elementos que se encuentran en la lista de carga previa, lo que significa que también debes administrar lo que se agrega y se quita del administrador.
val initialMediaItems = pullMediaItemsFromService(/* count= */ 20)
for (index in 0 until initialMediaItems.size) {
preloadManager.add(
initialMediaItems.get(index),index)
)
}El administrador ahora comenzará a recuperar datos para este MediaItem en segundo plano.
Después de agregar, dile al administrador que vuelva a evaluar su nueva lista (lo que indica que algo cambió, como agregar o quitar un elemento, o que el usuario cambia para reproducir un elemento nuevo).
preloadManager.invalidate()
2. Recupera y reproduce un elemento
Aquí viene la lógica de reproducción principal. Cuando el usuario decide reproducir ese video, no necesitas crear un MediaSource nuevo. En cambio, le pides a PreloadManager el que ya preparó. Puedes recuperar MediaSource del administrador de carga previa con MediaItem.
Si el elemento recuperado de PreloadManager es nulo, significa que el elemento multimedia aún no se cargó previamente ni se agregó a PreloadMamager, por lo que eliges configurar el elemento multimedia directamente.
// When a media item is about to display on the screen
val mediaSource = preloadManager.getMediaSource(mediaItem)
if (mediaSource!= null) {
player.setMediaSource(mediaSource)
} else {
// If mediaSource is null, that mediaItem hasn't been added yet.
// So, send it directly to the player.
player.setMediaItem(mediaItem)
}
player.prepare()
// When the media item is displaying at the center of the screen
player.play()Si preparas el MediaSource recuperado de PreloadManager, puedes realizar una transición sin problemas de la carga previa a la reproducción con los datos que ya están en la memoria. Esto es lo que hace que el tiempo de inicio sea más rápido.
3. Mantén el índice actual sincronizado con la IU
Dado que nuestro feed o lista podría ser dinámico, es importante notificarle a PreloadManager tu índice de reproducción actual para que siempre pueda priorizar los elementos más cercanos a tu índice actual para la carga previa.
preloadManager.setCurrentPlayingIndex(currentIndex) // Need to call invalidate() to update the priorities preloadManager.invalidate()
4. Quita un elemento
Para que el administrador sea eficiente, debes quitar los elementos que ya no necesita hacer un seguimiento, como los que están lejos de la posición actual del usuario.
// When an item is too far from the current playing index preloadManager.remove(mediaItem)
Si necesitas borrar todos los elementos a la vez, puedes llamar a preloadManager.reset().
5. Libera el administrador
Cuando ya no necesites PreloadManager (p.ej., cuando se destruye tu IU), debes liberarlo para liberar sus recursos. Un buen lugar para hacerlo es donde ya estás liberando los recursos de tu reproductor. Se recomienda liberar el administrador antes que el reproductor, ya que el reproductor puede seguir reproduciendo si no necesitas más carga previa.
// In your Activity's onDestroy() or Composable's onDispose preloadManager.release()
Es hora de la demostración
Míralo en vivo 👍
En la siguiente demostración, vemos el impacto de PreloadManager en el lado derecho, que tiene tiempos de carga más rápidos, mientras que el lado izquierdo muestra la experiencia existente. También puedes ver el ejemplo de código sample de la demostración. (Además, también muestra la latencia de inicio de cada video).
¿Qué sigue?
Y eso es todo para la parte 1. Ahora tienes las herramientas para compilar un sistema de carga previa dinámico. Puedes usar PreloadConfiguration para cargar previamente el siguiente elemento de una playlist en ExoPlayer o configurar un DefaultPreloadManager, agregar y quitar elementos sobre la marcha, configurar el estado de carga previa de destino y recuperar correctamente el contenido precargado para la reproducción.
En la parte 2, profundizaremos en el DefaultPreloadManager. Exploraremos cómo escuchar eventos de carga previa, analizaremos las prácticas recomendadas, como usar una ventana deslizante para evitar problemas de memoria, y analizaremos los componentes compartidos personalizados de ExoPlayer y DefaultPreloadManager.
¿Tienes algún comentario para compartir? Nos encantaría escucharte.
Mantente al tanto y haz que tu app sea más rápida. 🚀
-
Novedades de productosTe damos la bienvenida a la segunda entrega de nuestra serie de tres partes sobre la carga previa de contenido multimedia con Media3. Esta serie está diseñada para guiarte en el proceso de compilación de experiencias de contenido multimedia de baja latencia y alta capacidad de respuesta en tus apps para Android.
Mayuri Khinvasara Khabya • Lectura de 9 min -
Novedades de productosProporcionar una experiencia en línea segura y proteger a los usuarios de daños es una prioridad en Google Play.
Paul Feng • Lectura de 2 min -
Novedades de productosHoy, celebramos oficialmente cinco años desde el lanzamiento de Jetpack Compose 1.0. Desde la versión 1.0, anunciada el 28 de julio de 2021, hasta nuestra versión 1.11 más reciente, vimos que las APIs evolucionaron significativamente a lo largo de los años, y nos tomamos un momento para celebrarlo.
Rebecca Franks, Nick Butcher, Loryn Hairston • Lectura de 5 min
Recibe la información más reciente sobre el desarrollo de Android en tu bandeja de entrada todas las semanas.