Guía de migración de AndroidX Media3

Las apps que actualmente usan la biblioteca com.google.android.exoplayer2 independiente y androidx.media deben migrar a androidx.media3. Usa la secuencia de comandos de migración para migrar archivos de compilación de Gradle, archivos de origen de Java y Kotlin, y archivos de diseño XML de ExoPlayer 2.19.1 a AndroidX Media3 1.1.1.

Descripción general

Antes de migrar, revisa las siguientes secciones para obtener más información sobre los beneficios de las APIs nuevas, las APIs para migrar y los requisitos previos que debe cumplir el proyecto de tu app.

Por qué migrar a Jetpack Media3

  • Es el nuevo hogar de ExoPlayer, mientras que com.google.android.exoplayer2 está descontinuado.
  • Accede a la API del reproductor en todos los componentes/procesos con MediaBrowser/MediaController.
  • Usa las capacidades extendidas de las APIs de MediaSession y MediaController.
  • Anuncia las capacidades de reproducción con un control de acceso detallado.
  • Simplifica tu app quitando MediaSessionConnector y PlayerNotificationManager.
  • Retrocompatible con las APIs cliente de media-compat (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

APIs de contenido multimedia para migrar a AndroidX Media3

  • ExoPlayer y sus extensiones
    Esto incluye todos los módulos del proyecto de ExoPlayer heredado, excepto el módulo mediasession que se descontinuó. Las apps o los módulos que dependen de paquetes en com.google.android.exoplayer2 se pueden migrar con la secuencia de comandos de migración.
  • MediaSessionConnector (según los paquetes androidx.media.* de androidx.media:media:1.4.3+)
    Quita MediaSessionConnector y usa androidx.media3.session.MediaSession en su lugar.
  • MediaBrowserServiceCompat (según los paquetes androidx.media.* de androidx.media:media:1.4.3+)
    Migra las subclases de androidx.media.MediaBrowserServiceCompat a androidx.media3.session.MediaLibraryService y el código con MediaBrowserCompat.MediaItem a androidx.media3.common.MediaItem.
  • MediaBrowserCompat (según los paquetes android.support.v4.media.* de androidx.media:media:1.4.3+)
    Migra el código del cliente con MediaBrowserCompat o MediaControllerCompat para usar androidx.media3.session.MediaBrowser con androidx.media3.common.MediaItem.

Requisitos previos

  1. Asegúrate de que el proyecto esté bajo el control del código fuente

    Asegúrate de poder revertir fácilmente los cambios aplicados por las herramientas de migración con secuencias de comandos. Si aún no tienes tu proyecto bajo el control del código fuente, ahora es un buen momento para empezar con él. Si por algún motivo no quieres hacerlo, crea una copia de seguridad de tu proyecto antes de comenzar la migración.

  2. Actualiza tu app

    • Te recomendamos que actualices tu proyecto para usar la versión más reciente de la biblioteca de ExoPlayer y quitar todas las llamadas a los métodos obsoletos. Si planeas usar la secuencia de comandos para la migración, debes hacer coincidir la versión a la que estás actualizando con la versión que controla la secuencia de comandos.

    • Aumenta la compileSdkVersion de tu app a 32 como mínimo.

    • Actualiza Gradle y el complemento de Android para Gradle a una versión reciente que funcione con las dependencias actualizadas. Por ejemplo:

      • Versión del complemento de Android para Gradle: 7.1.0
      • Versión de Gradle: 7.4
    • Reemplaza todas las declaraciones de importación de comodines que usan un asterisco (*) y usan declaraciones de importación completamente calificadas: Borra las declaraciones de importación del comodín y usa Android Studio para importar las declaraciones completamente calificadas (F2 - Alt/Enter, F2 - Alt/Enter, ...).

    • Migra de com.google.android.exoplayer2.PlayerView a com.google.android.exoplayer2.StyledPlayerView. Esto es necesario porque no hay un equivalente a com.google.android.exoplayer2.PlayerView en AndroidX Media3.

Migra ExoPlayer con compatibilidad con secuencias de comandos

La secuencia de comandos facilita el cambio de com.google.android.exoplayer2 a la nueva estructura de paquetes y módulos en androidx.media3. La secuencia de comandos aplica algunas verificaciones de validación en tu proyecto y, luego, imprime advertencias si falla la validación. De lo contrario, aplica las asignaciones de clases y paquetes a los que se les cambió el nombre en los recursos de un proyecto de Gradle para Android escrito en Java o Kotlin.

usage: ./media3-migration.sh [-p|-c|-d|-v]|[-m|-l [-x <path>] [-f] PROJECT_ROOT]
 PROJECT_ROOT: path to your project root (location of 'gradlew')
 -p: list package mappings and then exit
 -c: list class mappings (precedence over package mappings) and then exit
 -d: list dependency mappings and then exit
 -l: list files that will be considered for rewrite and then exit
 -x: exclude the path from the list of file to be changed: 'app/src/test'
 -m: migrate packages, classes and dependencies to AndroidX Media3
 -f: force the action even when validation fails
 -v: print the exoplayer2/media3 version strings of this script
 -h, --help: show this help text

Usa la secuencia de comandos de migración

  1. Descarga la secuencia de comandos de migración de la etiqueta del proyecto de ExoPlayer en GitHub correspondiente a la versión a la que actualizaste tu app:

    curl -o media3-migration.sh \
      "https://raw.githubusercontent.com/google/ExoPlayer/r2.19.1/media3-migration.sh"
    
  2. Haz que la secuencia de comandos sea ejecutable:

    chmod 744 media3-migration.sh
    
  3. Ejecuta la secuencia de comandos con --help para conocer las opciones.

  4. Ejecuta la secuencia de comandos con -l a fin de enumerar el conjunto de archivos seleccionados para la migración (usa -f a fin de forzar la lista sin advertencias):

    ./media3-migration.sh -l -f /path/to/gradle/project/root
    
  5. Ejecuta la secuencia de comandos con -m para asignar paquetes, clases y módulos a Media3. Si ejecutas la secuencia de comandos con la opción -m, se aplicarán los cambios a los archivos seleccionados.

    • Detener en el error de validación sin realizar cambios
    ./media3-migration.sh -m /path/to/gradle/project/root
    
    • Ejecución forzada

    Si la secuencia de comandos encuentra un incumplimiento de los requisitos previos, la migración se puede forzar con la marca -f:

    ./media3-migration.sh -m -f /path/to/gradle/project/root
    
 # list files selected for migration when excluding paths
 ./media3-migration.sh -l -x "app/src/test/" -x "service/" /path/to/project/root
 # migrate the selected files
 ./media3-migration.sh -m -x "app/src/test/" -x "service/" /path/to/project/root

Completa estos pasos manuales después de ejecutar la secuencia de comandos con la opción -m:

  1. Verifica cómo la secuencia de comandos cambió tu código: Usa una herramienta de diferencias y corrige posibles problemas (considera informar un error si crees que la secuencia de comandos tiene un problema general que se introdujo sin pasar la opción -f).
  2. Compila el proyecto: Usa ./gradlew clean build o, en Android Studio, selecciona File > Sync Project with Gradle Files, luego Build > Clean project y, luego, en Build > Rebuild project (supervisa tu compilación en la pestaña "Build - Build Output" de Android Studio.

Pasos adicionales recomendados:

  1. Soluciona los errores relacionados con el uso de APIs inestables.
  2. Reemplazar las llamadas a la API obsoletas: Usa la API de reemplazo sugerida. Mantén el puntero sobre la advertencia en Android Studio y consulta el JavaDoc del símbolo obsoleto para saber qué usar en lugar de una llamada determinada.
  3. Ordena las sentencias de importación: Abre el proyecto en Android Studio, haz clic con el botón derecho en el nodo de una carpeta de paquetes en el visor del proyecto y elige Optimize imports en los paquetes que contienen los archivos de origen modificados.

Reemplaza MediaSessionConnector con androidx.media3.session.MediaSession.

En el mundo heredado de MediaSessionCompat, MediaSessionConnector era responsable de sincronizar el estado del jugador con el estado de la sesión y de recibir comandos de los controles que necesitaban delegación a los métodos del jugador adecuados. Con AndroidX Media3, MediaSession lo hace directamente sin necesidad de un conector.

  1. Quita todas las referencias y el uso de MediaSessionConnector: Si usaste la secuencia de comandos automatizada para migrar clases y paquetes de ExoPlayer, es probable que la secuencia de comandos haya dejado tu código en un estado no compilable con respecto a MediaSessionConnector que no se puede resolver. Android Studio te mostrará el código roto cuando intentes compilar o iniciar la app.

  2. En el archivo build.gradle, en el que mantienes tus dependencias, agrega una dependencia de implementación al módulo de sesión de AndroidX Media3 y quita la dependencia heredada:

    implementation "androidx.media3:media3-session:1.2.1"
    
  3. Reemplaza MediaSessionCompat por androidx.media3.session.MediaSession.

  4. En el sitio de código en el que creaste el MediaSessionCompat heredado, usa androidx.media3.session.MediaSession.Builder para compilar un MediaSession. Pasa el jugador para crear el compilador de sesiones.

    val player = ExoPlayer.Builder(context).build()
    mediaSession = MediaSession.Builder(context, player)
        .setSessionCallback(MySessionCallback())
        .build()
    
  5. Implementa MySessionCallback según lo requiera tu app. Esto es opcional. Si quieres permitir que los controles agreguen elementos multimedia al reproductor, implementa MediaSession.Callback.onAddMediaItems(). Entrega varios métodos de API actuales y heredados que agregan elementos multimedia al reproductor para su reproducción de manera retrocompatible. Esto incluye los métodos MediaController.set/addMediaItems() del controlador de Media3, así como los métodos TransportControls.prepareFrom*/playFrom* de la API heredada. Puedes encontrar una implementación de ejemplo de onAddMediaItems en el elemento PlaybackService de la app de demostración de la sesión.

  6. Libera la sesión multimedia en el sitio de código en el que destruiste tu sesión antes de la migración:

    mediaSession?.run {
      player.release()
      release()
      mediaSession = null
    }
    

Funcionalidad MediaSessionConnector en Media3

En la siguiente tabla, se muestran las APIs de Media3 que controlan la funcionalidad que se implementó antes en MediaSessionConnector.

MediaSessionConnectorAndroidX Media3
CustomActionProvider MediaSession.Callback.onCustomCommand()/ MediaSession.setCustomLayout()
PlaybackPreparer MediaSession.Callback.onAddMediaItems() (prepare() se llama internamente)
QueueNavigator ForwardingPlayer
QueueEditor MediaSession.Callback.onAddMediaItems()
RatingCallback MediaSession.Callback.onSetRating()
PlayerNotificationManager DefaultMediaNotificationProvider/ MediaNotification.Provider

Migra MediaBrowserService a MediaLibraryService

AndroidX Media3 presenta MediaLibraryService que reemplaza a MediaBrowserServiceCompat. El JavaDoc de MediaLibraryService y su superclase MediaSessionService proporcionan una buena introducción a la API y al modelo de programación asíncrona del servicio.

MediaLibraryService es retrocompatible con MediaBrowserService. Una app cliente que usa MediaBrowserCompat o MediaControllerCompat sigue funcionando sin cambios de código cuando se conecta a un MediaLibraryService. Para un cliente, es transparente si tu app usa un MediaLibraryService o un MediaBrowserServiceCompat heredado.

Diagrama de componentes de apps con servicios, actividades y apps externas.
Figura 1: Descripción general de los componentes de apps de música
  1. Para que la retrocompatibilidad funcione, debes registrar ambas interfaces de servicio con tu servicio en AndroidManifest.xml. De esta manera, un cliente encontrará tu servicio mediante la interfaz de servicio requerida:

    <service android:name=".MusicService" android:exported="true">
        <intent-filter>
            <action android:name="androidx.media3.session.MediaLibraryService"/>
            <action android:name="android.media.browse.MediaBrowserService" />
        </intent-filter>
    </service>
    
  2. En el archivo build.gradle en el que mantienes tus dependencias, agrega una dependencia de implementación al módulo de sesión de AndroidX Media3 y quita la dependencia heredada:

    implementation "androidx.media3:media3-session:1.2.1"
    
  3. Cambia tu servicio para heredar de un MediaLibraryService en lugar de MediaBrowserService. Como se mencionó antes, MediaLibraryService es compatible con el MediaBrowserService heredado. En consecuencia, la API más amplia que el servicio ofrece a los clientes sigue siendo la misma. Por lo tanto, es probable que una app pueda conservar la mayor parte de la lógica necesaria para implementar MediaBrowserService y adaptarla al nuevo MediaLibraryService.

    Las principales diferencias en comparación con el MediaBrowserServiceCompat heredado son las siguientes:

    • Implementa los métodos del ciclo de vida del servicio: Los métodos que se deben anular en el servicio son onCreate/onDestroy, en el que una app asigna o actualiza la sesión de la biblioteca, el reproductor y otros recursos. Además de los métodos estándar de ciclo de vida de servicio, una app debe anular onGetSession(MediaSession.ControllerInfo) para mostrar el MediaLibrarySession que se compiló en onCreate.

    • Implementa MediaLibraryService.MediaLibrarySessionCallback: La compilación de una sesión requiere un MediaLibraryService.MediaLibrarySessionCallback que implemente los métodos de la API del dominio real. Por lo tanto, en lugar de anular los métodos de la API del servicio heredado, anularás los métodos de MediaLibrarySession.Callback.

      Luego, se usa la devolución de llamada para compilar el MediaLibrarySession:

      mediaLibrarySession =
            MediaLibrarySession.Builder(this, player, MySessionCallback())
               .build()
      

      Encuentra la API completa de MediaLibrarySessionCallback en la documentación de la API.

    • Implementa MediaSession.Callback.onAddMediaItems(): La devolución de llamada onAddMediaItems(MediaSession, ControllerInfo, List<MediaItem>) entrega varios métodos de API actuales y heredados que agregan elementos multimedia al reproductor para su reproducción de manera retrocompatible. Esto incluye los métodos MediaController.set/addMediaItems() del controlador de Media3, así como los métodos TransportControls.prepareFrom*/playFrom* de la API heredada. Puedes encontrar un ejemplo de la implementación de la devolución de llamada en el archivo PlaybackService de la app de demostración de la sesión.

    • AndroidX Media3 usa androidx.media3.common.MediaItem en lugar de MediaBrowserCompat.MediaItem y MediaMetadataCompat. Las partes de tu código vinculadas a las clases heredadas deben cambiarse según corresponda o asignarse al MediaItem de Media3.

    • El modelo de programación asíncrona general cambió a Futures, en contraste con el enfoque Result desmontable de MediaBrowserServiceCompat. La implementación del servicio puede mostrar un ListenableFuture asíncrono en lugar de separar un resultado o mostrar una interfaz Future inmediata para mostrar directamente un valor.

Quita PlayerNotificationManager

MediaLibraryService admite notificaciones multimedia automáticamente, y PlayerNotificationManager se puede quitar cuando se usa un MediaLibraryService o MediaSessionService.

Una app puede personalizar la notificación estableciendo una MediaNotification.Provider personalizada en onCreate() que reemplace la DefaultMediaNotificationProvider. Luego, MediaLibraryService se encarga de iniciar el servicio en primer plano según sea necesario.

Cuando se anula MediaLibraryService.updateNotification(), una app puede tomar la propiedad total de publicar una notificación y de iniciar o detener el servicio en primer plano según sea necesario.

Cómo migrar el código del cliente con un MediaBrowser

Con AndroidX Media3, un MediaBrowser implementa las interfaces MediaController/Player y se puede usar para controlar la reproducción de contenido multimedia además de explorar la biblioteca de contenido multimedia. Si tuvieras que crear un MediaBrowserCompat y un MediaControllerCompat en el mundo heredado, puedes hacer lo mismo con MediaBrowser en Media3.

Se puede compilar un MediaBrowser y esperar la conexión con el servicio que se establece:

scope.launch {
    val sessionToken =
        SessionToken(context, ComponentName(context, MusicService::class.java)
    browser =
        MediaBrowser.Builder(context, sessionToken))
            .setListener(BrowserListener())
            .buildAsync()
            .await()
    // Get the library root to start browsing the library.
    root = browser.getLibraryRoot(/* params= */ null).await();
    // Add a MediaController.Listener to listen to player state events.
    browser.addListener(playerListener)
    playerView.setPlayer(browser)
}

Consulta Cómo controlar la reproducción en la sesión multimedia para aprender a crear un MediaController que controle la reproducción en segundo plano.

Pasos adicionales y limpieza

Errores de API inestables

Después de migrar a Media3, es posible que veas errores de lint sobre los usos inestables de la API. Es seguro usar estas APIs, y los errores de lint son un producto secundario de nuestras nuevas garantías de compatibilidad binaria. Si no necesitas una compatibilidad binaria estricta, estos errores se pueden suprimir de forma segura con una anotación @OptIn.

Información general

Ni ExoPlayer v1 ni v2 proporcionaban garantías estrictas sobre la compatibilidad binaria de la biblioteca entre versiones posteriores. La superficie de la API de ExoPlayer es muy grande por diseño para permitir que las apps personalicen casi todos los aspectos de la reproducción. En ocasiones, las versiones posteriores de ExoPlayer introducen cambios rotundos o cambios rotundos (p.ej., nuevos métodos obligatorios en las interfaces). En la mayoría de los casos, para mitigar estas fallas, se agregó el símbolo nuevo y se dejó de usar en algunas versiones, a fin de darles tiempo a los desarrolladores de migrar sus usos, pero no siempre fue posible.

Estos cambios rotundos causaron dos problemas para los usuarios de las bibliotecas de ExoPlayer v1 y v2:

  1. Una actualización a la versión de ExoPlayer podría hacer que el código deje de compilarse.
  2. Una app que dependía de ExoPlayer tanto directamente como a través de una biblioteca intermedia debía asegurarse de que ambas dependencias fueran la misma versión. De lo contrario, las incompatibilidades binarias podían provocar fallas en el tiempo de ejecución.

Mejoras en Media3

Media3 garantiza la compatibilidad binaria para un subconjunto de la plataforma de la API. Las partes que no garantizan la compatibilidad binaria están marcadas con @UnstableApi. Para aclarar esta distinción, el uso de símbolos de API inestables genera un error de lint, a menos que estén anotados con @OptIn.

Después de migrar de ExoPlayer v2 a Media3, es posible que veas muchos errores de lint de la API inestables. Esto puede hacer que parezca que Media3 es "menos estable" que ExoPlayer v2. Este no es el caso. Las partes "inestable" de la API de Media3 tienen el mismo nivel de estabilidad que todo el conjunto de la superficie de la API de ExoPlayer v2, y las garantías de esta plataforma no están disponibles en ExoPlayer v2. La diferencia es que un error de lint ahora te alerta sobre los diferentes niveles de estabilidad.

Cómo controlar errores de lint de API inestables

Tienes dos opciones para manejar los errores de lint de API inestables:

  • Comienza a usar una API estable que logre el mismo resultado.
  • Sigue usando la API inestable y anota el uso con @OptIn.

    import androidx.annotation.OptIn
    import androidx.media3.common.util.UnstableApi
    
    @OptIn(UnstableApi::class)
    fun functionUsingUnstableApi() {
      // Do something useful.
    }
    

    Ten en cuenta que también hay una anotación kotlin.OptIn que no se debe usar. Para ello, es importante usar la anotación androidx.annotation.OptIn.

    Captura de pantalla: Cómo agregar la anotación de aceptación
    Figura 2: Cómo agregar una anotación @androidx.annotations.OptIn con Android Studio.

Para habilitar los paquetes enteros, agrega un package-info.java:

@OptIn(markerClass = UnstableApi.class)
package name.of.your.package;

import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;

Para habilitar proyectos enteros, se suprime el error específico de lint en su lint.xml. Consulta JavaDoc de la anotación de UnstableApi para obtener más información.

APIs obsoletas

Tal vez notes que las llamadas a APIs obsoletas aparecen tachadas en Android Studio. Te recomendamos reemplazar esas llamadas con la alternativa adecuada. Coloca el cursor sobre el símbolo para ver el JavaDoc que indica qué API debes usar en su lugar.

Captura de pantalla: Cómo mostrar JavaDoc con la alternativa del método obsoleto
Figura 3: La información sobre la herramienta JavaDoc en Android Studio sugiere una alternativa para cualquier símbolo obsoleto.

Muestras de código y apps de demostración

  • App de demostración de la sesión de AndroidX Media3 (para dispositivos móviles y Wear OS)
    • Acciones personalizadas
    • Notificación de IU del sistema, MediaButton/BT
    • Control de reproducción de Asistente de Google
  • UAMP: Android Media Player (rama 3) (dispositivo móvil, AutomotiveOS)
    • Notificación de IU del sistema, MediaButton/BT, reanudación de reproducción
    • Control de reproducción de Asistente de Google y Wear OS
    • AutomotiveOS: comando y acceso personalizados