Guia de migração do AndroidX Media3

Os apps que estão usando a biblioteca com.google.android.exoplayer2 independente e o androidx.media precisam migrar para o androidx.media3. Use o script de migração para migrar arquivos de build do Gradle, arquivos de origem Java e Kotlin e arquivos de layout XML do ExoPlayer 2.19.1 para o 1.1.1 do AndroidX Media3.

Informações gerais

Antes de fazer a migração, leia as seções a seguir para saber mais sobre os benefícios das novas APIs, as que serão migradas e os pré-requisitos que o projeto do app precisa atender.

Por que migrar para o Jetpack Media3

  • Ele é a nova página inicial do ExoPlayer (links em inglês), enquanto o com.google.android.exoplayer2 foi descontinuado.
  • Acesse a API Player em todos os componentes/processos com MediaBrowser/MediaController.
  • Use os recursos estendidos da API MediaSession e MediaController.
  • Anuncie recursos de reprodução com controle de acesso detalhado.
  • Simplifique o app removendo MediaSessionConnector e PlayerNotificationManager.
  • Compatível com versões anteriores das APIs de cliente media-compatibilidade (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

APIs de mídia para migrar para o AndroidX Media3

  • ExoPlayer e as extensões dele
    Isso inclui todos os módulos do projeto ExoPlayer legado, exceto o módulo mediasession, que foi descontinuado. Apps ou módulos que dependem de pacotes em com.google.android.exoplayer2 podem ser migrados com o script de migração.
  • MediaSessionConnector (dependendo dos pacotes androidx.media.* de androidx.media:media:1.4.3+)
    Remova a MediaSessionConnector e use a androidx.media3.session.MediaSession.
  • MediaBrowserServiceCompat (dependendo dos pacotes androidx.media.* de androidx.media:media:1.4.3+)
    Migre subclasses de androidx.media.MediaBrowserServiceCompat para androidx.media3.session.MediaLibraryService e código usando MediaBrowserCompat.MediaItem para androidx.media3.common.MediaItem.
  • MediaBrowserCompat (dependendo dos pacotes android.support.v4.media.* da androidx.media:media:1.4.3+)
    Migre o código do cliente usando MediaBrowserCompat ou MediaControllerCompat para usar o androidx.media3.session.MediaBrowser com androidx.media3.common.MediaItem.

Pré-requisitos

  1. Verifique se o projeto está sob controle de origem

    Verifique se é possível reverter com facilidade as alterações aplicadas pelas ferramentas de migração com script. Se o projeto ainda não está sob controle de origem, agora é um bom momento para começar. Se, por algum motivo, você não quiser fazer isso, faça uma cópia de backup do projeto antes de iniciar a migração.

  2. Atualizar o app

    • Recomendamos que você atualize seu projeto para usar a versão mais recente da biblioteca do ExoPlayer e remova todas as chamadas para métodos descontinuados. Se você pretende usar o script na migração, associe a versão que está sendo atualizada à versão processada pelo script.

    • Aumente a compileSdkVersion do app para pelo menos 32.

    • Faça upgrade do Gradle e do Plug-in do Android Studio para Gradle para uma versão recente que funcione com as dependências atualizadas acima. Por exemplo:

      • Versão do Plug-in do Android para Gradle: 7.1.0
      • Gradle: versão 7.4
    • Substitua todas as instruções de importação de caractere curinga que estejam usando um asterisco (*) e use instruções de importação totalmente qualificadas: exclua as instruções de importação de caractere curinga e use o Android Studio para importar as instruções totalmente qualificadas (F2 - Alt/Enter, F2 - Alt/Enter, ...).

    • Migre de com.google.android.exoplayer2.PlayerView para com.google.android.exoplayer2.StyledPlayerView. Isso é necessário porque não há equivalente de com.google.android.exoplayer2.PlayerView no AndroidX Media3.

Migrar o ExoPlayer com suporte a script

O script facilita a mudança de com.google.android.exoplayer2 para a nova estrutura de pacote e módulo em androidx.media3. O script aplica algumas verificações de validação ao projeto e imprime avisos em caso de falha. Caso contrário, ela aplicará os mapeamentos de classes e pacotes renomeados nos recursos de um projeto do Gradle do Android escrito em Java ou 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

Como usar o script de migração

  1. Faça o download do script de migração na tag do projeto ExoPlayer no GitHub correspondente à versão para a qual você atualizou seu app:

    curl -o media3-migration.sh \
      "https://raw.githubusercontent.com/google/ExoPlayer/r2.19.1/media3-migration.sh"
    
  2. Torne o script executável:

    chmod 744 media3-migration.sh
    
  3. Execute o script com --help para saber mais sobre as opções.

  4. Execute o script com -l para listar o conjunto de arquivos selecionados para migração. Use -f para forçar a listagem sem avisos:

    ./media3-migration.sh -l -f /path/to/gradle/project/root
    
  5. Execute o script com -m para mapear pacotes, classes e módulos para a Media3. A execução do script com a opção -m aplicará as alterações aos arquivos selecionados.

    • Parar no erro de validação sem fazer alterações
    ./media3-migration.sh -m /path/to/gradle/project/root
    
    • Execução forçada

    Se o script encontrar uma violação dos pré-requisitos, a migração poderá ser forçada com a sinalização -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

Conclua estas etapas manuais depois de executar o script com a opção -m:

  1. Verifique como o script mudou seu código: use uma ferramenta de diferenças e corrija possíveis problemas. Registre um bug se você acha que o script tem um problema geral que foi introduzido sem transmitir a opção -f.
  2. Crie o projeto: use ./gradlew clean build ou, no Android Studio, escolha File > Sync Project with Gradle Files, Build > Clean project e depois Build > Rebuild project. Monitore seu build na guia "Build - Build Output" do Android Studio.

Etapas de acompanhamento recomendadas:

  1. Resolva a ativação de erros relacionados ao uso de APIs instáveis.
  2. Substituir chamadas de API descontinuadas: use a API de substituição sugerida. Mantenha o ponteiro do mouse sobre o aviso no Android Studio e consulte o JavaDoc do símbolo descontinuado para descobrir o que usar em vez de uma determinada chamada.
  3. Classificar as instruções de importação: abra o projeto no Android Studio e clique com o botão direito do mouse em um nó da pasta de pacotes no visualizador do projeto e escolha Otimizar importações nos pacotes que contêm os arquivos de origem modificados.

Substitua MediaSessionConnector por androidx.media3.session.MediaSession.

No mundo legado da MediaSessionCompat, o MediaSessionConnector era responsável por sincronizar o estado do jogador com o estado da sessão e receber comandos dos controladores que precisavam de delegação para os métodos adequados do jogador. Com o AndroidX Media3, isso é feito diretamente pelo MediaSession, sem a necessidade de um conector.

  1. Remova todas as referências e o uso do MediaSessionConnector:se você usou o script automatizado para migrar classes e pacotes do ExoPlayer, o script provavelmente deixou o código em um estado descompilável em relação à MediaSessionConnector, que não pode ser resolvida. O Android Studio vai mostrar o código corrompido quando você tentar criar ou iniciar o app.

  2. No arquivo build.gradle em que você mantém as dependências, adicione uma dependência de implementação ao módulo de sessão do AndroidX Media3 e remova a dependência legada:

    implementation "androidx.media3:media3-session:1.3.1"
    
  3. Substitua MediaSessionCompat por androidx.media3.session.MediaSession.

  4. No site de código em que você criou o MediaSessionCompat legado, use androidx.media3.session.MediaSession.Builder para criar um MediaSession. Passe o jogador para criar o criador de sessões.

    val player = ExoPlayer.Builder(context).build()
    mediaSession = MediaSession.Builder(context, player)
        .setSessionCallback(MySessionCallback())
        .build()
    
  5. Implemente MySessionCallback conforme exigido pelo app. Isso é opcional. Se você quiser permitir que os controles adicionem itens de mídia ao player, implemente MediaSession.Callback.onAddMediaItems(). Ele disponibiliza vários métodos de API atuais e legados que adicionam itens de mídia ao player para reprodução de maneira compatível com versões anteriores. Isso inclui os métodos MediaController.set/addMediaItems() do controle Media3, bem como os métodos TransportControls.prepareFrom*/playFrom* da API legada. Um exemplo de implementação de onAddMediaItems pode ser encontrado no PlaybackService do app de demonstração da sessão.

  6. Libere a sessão de mídia no site de código em que você destruiu a sessão antes da migração:

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

Funcionalidade MediaSessionConnector na Media3

A tabela abaixo mostra as APIs Media3 que processam a funcionalidade implementada anteriormente em MediaSessionConnector.

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

Migrar MediaBrowserService para MediaLibraryService

O AndroidX Media3 introduz MediaLibraryService, que substitui o MediaBrowserServiceCompat. O JavaDoc de MediaLibraryService e a superclasse MediaSessionService fornecem uma boa introdução à API e ao modelo de programação assíncrona do serviço.

O MediaLibraryService é compatível com versões anteriores do MediaBrowserService. Um app cliente que usa MediaBrowserCompat ou MediaControllerCompat continua funcionando sem mudanças de código ao se conectar a um MediaLibraryService. Para um cliente, é transparente se o app está usando um MediaLibraryService ou um MediaBrowserServiceCompat legado.

Diagrama de componentes do app com serviços, atividades e apps externos.
Figura 1: visão geral do componente de app de mídia.
  1. Para que a compatibilidade com versões anteriores funcione, você precisa registrar as duas interfaces de serviço com seu serviço no AndroidManifest.xml. Dessa forma, o cliente encontra seu serviço pela interface de serviço necessária:

    <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. No arquivo build.gradle em que você mantém as dependências, adicione uma dependência de implementação ao módulo de sessão AndroidX Media3 e remova a dependência legada:

    implementation "androidx.media3:media3-session:1.3.1"
    
  3. Mude seu serviço para herdar de um MediaLibraryService em vez de MediaBrowserService. Como dito anteriormente, o MediaLibraryService é compatível com o MediaBrowserService legado. Dessa forma, a API mais ampla que o serviço oferece aos clientes ainda é a mesma. Portanto, é provável que um app possa manter a maior parte da lógica necessária para implementar o MediaBrowserService e adaptá-lo ao novo MediaLibraryService.

    As principais diferenças em comparação com o MediaBrowserServiceCompat legado são as seguintes:

    • Implemente os métodos do ciclo de vida do serviço:os métodos que precisam ser substituídos no próprio serviço são onCreate/onDestroy, em que um app aloca/libera a sessão da biblioteca, o player e outros recursos. Além dos métodos de ciclo de vida do serviço padrão, um app precisa substituir onGetSession(MediaSession.ControllerInfo) para retornar a MediaLibrarySession criada na onCreate.

    • Implementar MediaLibraryService.MediaLibrarySessionCallback: a criação de uma sessão requer um MediaLibraryService.MediaLibrarySessionCallback que implemente os métodos reais da API do domínio. Portanto, em vez de substituir os métodos de API do serviço legado, você vai substituir os métodos do MediaLibrarySession.Callback.

      O callback é usado para criar o MediaLibrarySession:

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

      Encontre a API completa da MediaLibrarySessionCallback na documentação da API.

    • Implementar MediaSession.Callback.onAddMediaItems(): o callback onAddMediaItems(MediaSession, ControllerInfo, List<MediaItem>) exibe vários métodos de API atuais e legados que adicionam itens de mídia ao player para reprodução de maneira compatível com versões anteriores. Isso inclui os métodos MediaController.set/addMediaItems() do controle Media3, bem como os métodos TransportControls.prepareFrom*/playFrom* da API legada. Um exemplo de implementação do callback pode ser encontrado no PlaybackService do app de demonstração da sessão.

    • O AndroidX Media3 está usando androidx.media3.common.MediaItem em vez de MediaBrowserCompat.MediaItem e MediaMetadataCompat. Partes do seu código vinculadas às classes legadas precisam ser modificadas adequadamente ou mapeadas para o MediaItem da Media3.

    • O modelo de programação assíncrona geral mudou para Futures, em contraste com a abordagem Result destacável do MediaBrowserServiceCompat. A implementação do serviço pode retornar um ListenableFuture assíncrono em vez de remover um resultado ou retornar um Future imediato para retornar diretamente um valor.

Remoção do PlayerNotificationManager

O MediaLibraryService oferece suporte a notificações de mídia automaticamente e o PlayerNotificationManager pode ser removido ao usar um MediaLibraryService ou MediaSessionService.

Um app pode personalizar a notificação definindo uma MediaNotification.Provider personalizada em onCreate() que substitui a DefaultMediaNotificationProvider. Em seguida, o MediaLibraryService vai cuidar da inicialização do serviço em primeiro plano, conforme necessário.

Ao substituir MediaLibraryService.updateNotification(), um app pode assumir a propriedade total de postar uma notificação e iniciar/interromper o serviço em primeiro plano, conforme necessário.

Migrar código do cliente usando um MediaBrowser

Com o AndroidX Media3, um MediaBrowser implementa as interfaces MediaController/Player e pode ser usado para controlar a reprodução de mídia além de navegar pela biblioteca de mídia. Se você tiver que criar um MediaBrowserCompat e um MediaControllerCompat no mundo legado, faça o mesmo usando MediaBrowser no Media3.

Um MediaBrowser pode ser criado e aguardar a conexão com o serviço ser estabelecida:

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)
}

Consulte Controlar a reprodução na sessão de mídia para saber como criar um MediaController para controlar a reprodução em segundo plano.

Outras etapas e limpeza

Erros de API instável

Após a migração para a Media3, talvez você encontre erros de lint sobre usos instáveis da API. Essas APIs são seguras para o uso, e os erros de lint são um subproduto das nossas novas garantias de compatibilidade binária. Se você não precisar de compatibilidade binária estrita, esses erros poderão ser suprimidos com segurança usando uma anotação @OptIn.

Contexto

Nenhuma das versões do ExoPlayer v1 ou v2 fornecia garantias rigorosas sobre a compatibilidade binária da biblioteca entre as versões subsequentes. A superfície da API do ExoPlayer é muito grande por padrão para permitir que os apps personalizem quase todos os aspectos da reprodução. As versões subsequentes do ExoPlayer ocasionalmente introduziriam nomes de símbolos ou outras mudanças interruptivas (por exemplo, novos métodos obrigatórios nas interfaces). Na maioria dos casos, essas falhas foram atenuadas com a introdução do novo símbolo e a descontinuação do símbolo antigo em algumas versões, para permitir que os desenvolvedores tivessem tempo para migrar os usos, mas isso nem sempre foi possível.

Essas alterações interruptivas resultaram em dois problemas para os usuários das bibliotecas ExoPlayer v1 e v2:

  1. Um upgrade para a versão do ExoPlayer pode fazer com que a compilação do código seja interrompida.
  2. Um app que dependia do ExoPlayer diretamente e por uma biblioteca intermediária precisava garantir que as duas dependências fossem a mesma versão. Caso contrário, incompatibilidades binárias poderiam resultar em falhas na execução.

Melhorias na Media3

A Media3 garante a compatibilidade binária para um subconjunto da superfície da API. As partes que não garantem a compatibilidade binária são marcadas com @UnstableApi. Para esclarecer essa distinção, o uso de símbolos de API instáveis gera um erro de lint, a menos que eles sejam anotados com @OptIn.

Depois de migrar do ExoPlayer v2 para o Media3, você pode encontrar muitos erros de lint instáveis da API. Isso pode fazer parecer que a Media3 é "menos estável" que o ExoPlayer v2. Esse não é o caso. As partes "insstáveis" da API Media3 têm o mesmo nível de estabilidade que toda a superfície da API ExoPlayer v2, e as garantias da superfície estável da API Media3 não estão disponíveis no ExoPlayer v2. A diferença é que um erro do lint agora alerta sobre os diferentes níveis de estabilidade.

Processar erros de lint de API instáveis

Consulte a seção de solução de problemas desses erros de lint para mais detalhes sobre como fazer anotações de usos de APIs instáveis em Java e Kotlin com @OptIn.

APIs descontinuadas

As chamadas para APIs descontinuadas aparecem com desconto no Android Studio. Recomendamos substituir essas chamadas pela alternativa apropriada. Passe o cursor sobre o símbolo para ver o JavaDoc que informa qual API usar.

Captura de tela: como exibir o JavaDoc com a alternativa do método descontinuado
Figura 3: a dica do JavaDoc no Android Studio sugere uma alternativa para qualquer símbolo descontinuado.

Exemplos de código e apps de demonstração

  • App de demonstração da sessão AndroidX Media3 (dispositivos móveis e WearOS)
    • Ações personalizadas
    • Notificação da interface do sistema, MediaButton/BT
    • Controle de mídia do Google Assistente
  • UAMP: Android Media Player (branch media3) (dispositivos móveis, AutomotiveOS)
    • Notificação da interface do sistema, MediaButton/BT, retomada da reprodução
    • Controle de reprodução do Google Assistente/Wear OS
    • AutomotiveOS: comando e login personalizados