Criar um app de player de mídia básico usando o ExoPlayer da Media3

O Jetpack Media3 define uma interface Player que descreve a funcionalidade básica para reprodução de arquivos de vídeo e áudio. ExoPlayer é a implementação padrão dessa interface na Media3. Recomendamos o uso do ExoPlayer, já que ele fornece um conjunto abrangente de recursos que abrangem a maioria dos casos de uso de reprodução e é personalizável para lidar com outros casos de uso que você possa ter. O ExoPlayer também abstrai a fragmentação de dispositivo e SO para que seu código funcione de forma consistente em todo o ecossistema Android. O ExoPlayer inclui:

Esta página mostra algumas das principais etapas para criar um app de reprodução. Para mais detalhes, consulte nossos guias completos sobre o ExoPlayer da Media3 (link em inglês).

Como começar

Para começar, adicione uma dependência ao ExoPlayer, a interface e os módulos comuns do Jetpack Media3:

implementation "androidx.media3:media3-exoplayer:1.3.1"
implementation "androidx.media3:media3-ui:1.3.1"
implementation "androidx.media3:media3-common:1.3.1"

Dependendo do seu caso de uso, talvez você também precise de módulos adicionais do Media3, como exoplayer-dash, para reproduzir streams no formato DASH.

Substitua 1.3.1 pela versão preferida da biblioteca. Consulte as notas da versão para conferir a versão mais recente.

Como criar um player de mídia

Com o Media3, você pode usar a implementação incluída da interface Player, ExoPlayer, ou criar sua própria implementação personalizada.

Como criar um ExoPlayer

A maneira mais simples de criar uma instância de ExoPlayer é a seguinte:

Kotlin

val player = ExoPlayer.Builder(context).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

Você pode criar seu player de mídia no método de ciclo de vida onCreate() do Activity, Fragment ou Service em que ele fica.

O Builder inclui várias opções de personalização que podem ser do seu interesse, como:

O Media3 fornece um componente de interface PlayerView que pode ser incluído no arquivo de layout do seu app. Esse componente encapsula um PlayerControlView para controles de reprodução, um SubtitleView para exibir legendas e Surface para renderizar vídeos.

Como preparar o player

Adicione itens de mídia a uma playlist para reproduzir com métodos como setMediaItem() e addMediaItem(). Em seguida, chame prepare() para começar a carregar mídia e acessar os recursos necessários.

Não realize essas etapas antes de o app estar em primeiro plano. Se o player estiver em um Activity ou Fragment, prepare-o no método de ciclo de vida onStart() na API de nível 24 e mais recentes ou no método onResume() no nível 23 da API e em versões anteriores. Para um jogador que está em um Service, é possível prepará-lo em onCreate().

Controlar o player

Depois que o player estiver preparado, será possível controlar a reprodução chamando métodos no player, como:

Os componentes da interface, como PlayerView ou PlayerControlView, são atualizados quando vinculados a um jogador.

Solte o player

A reprodução pode exigir recursos que estão em fornecimento limitado, como decodificadores de vídeo. Por isso, é importante chamar release() no player para liberar recursos quando ele não for mais necessário.

Se o player estiver em um Activity ou Fragment, libere-o no método de ciclo de vida onStop() no nível 24 da API e versões mais recentes ou no método onPause() no nível 23 da API e em versões anteriores. Para um player que está em um Service, é possível liberá-lo em onDestroy().

Gerenciar a reprodução com uma sessão de mídia

No Android, as sessões de mídia oferecem uma maneira padronizada de interagir com um player de mídia além dos limites de processo. Conectar uma sessão de mídia ao player permite anunciar a reprodução de mídia externamente e receber comandos de reprodução de fontes externas, por exemplo, para integração com controles de mídia do sistema em dispositivos móveis e de tela grande.

Para usar sessões de mídia, adicione uma dependência ao módulo de sessão da Media3:

implementation "androidx.media3:media3-session:1.3.1"

Criar uma sessão de mídia

Você pode criar um MediaSession após inicializar um player da seguinte maneira:

Kotlin

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

A Media3 sincroniza automaticamente o estado do Player com o estado do MediaSession. Isso funciona com qualquer implementação de Player, incluindo ExoPlayer, CastPlayer ou uma implementação personalizada.

Conceder controle a outros clientes

Os apps clientes podem implementar um controlador de mídia para controlar a reprodução da sessão de mídia. Para receber essas solicitações, defina um objeto de callback ao criar seu MediaSession.

Quando um controle está prestes a se conectar à sessão de mídia, o método onConnect() é chamado. É possível usar o ControllerInfo fornecido para decidir se quer aceitar ou rejeitar a solicitação. Confira um exemplo no app de demonstração Media3 Session (link em inglês).

Uma vez conectado, um controlador pode enviar comandos de reprodução para a sessão. Em seguida, a sessão delega esses comandos para o jogador. Os comandos de reprodução e playlist definidos na interface Player são processados automaticamente pela sessão.

Outros métodos de callback permitem processar, por exemplo, solicitações de comandos de reprodução personalizados e modificação da playlist. Da mesma forma, esses callbacks incluem um objeto ControllerInfo para que você possa determinar o controle de acesso em cada solicitação.

Tocando mídia em segundo plano

Para continuar a tocar mídia quando o app não estiver em primeiro plano, por exemplo, para tocar músicas, audiolivros ou podcasts, mesmo que o usuário não esteja com o app aberto, Player e MediaSession precisam ser encapsulados em um serviço em primeiro plano. A Media3 fornece a interface MediaSessionService para essa finalidade.

Como implementar um MediaSessionService

Crie uma classe que estenda MediaSessionService e instancie seu MediaSession no método de ciclo de vida onCreate().

Kotlin

class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null

    // Create your Player and MediaSession in the onCreate lifecycle event
    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
    }

    // Remember to release the player and media session in onDestroy
    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }
}

Java

public class PlaybackService extends MediaSessionService {
    private MediaSession mediaSession = null;

    @Override
    public void onCreate() {
        super.onCreate();
        ExoPlayer player = new ExoPlayer.Builder(this).build();
        mediaSession = new MediaSession.Builder(this, player).build();
    }

    @Override
    public void onDestroy() {
        mediaSession.getPlayer().release();
        mediaSession.release();
        mediaSession = null;
        super.onDestroy();
    }
}

No manifesto, sua classe Service com um filtro de intent MediaSessionService e solicita a permissão FOREGROUND_SERVICE para executar um serviço em primeiro plano:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

Por fim, na classe que você criou, substitua o método onGetSession() para controlar o acesso do cliente à sessão de mídia. Retorne um MediaSession para aceitar a solicitação de conexão ou um null para rejeitar a solicitação.

Kotlin

// This example always accepts the connection request
override fun onGetSession(
    controllerInfo: MediaSession.ControllerInfo
): MediaSession? = mediaSession

Java

@Override
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
  // This example always accepts the connection request
  return mediaSession;
}

Como se conectar à interface

Agora que a sessão de mídia está em uma Service separada da Activity ou do Fragment em que a interface do player fica, você pode usar um MediaController para vinculá-las. No método onStart() do Activity ou Fragment com sua interface, crie um SessionToken para o MediaSession e use o SessionToken para criar um MediaController. A criação de um MediaController acontece de forma assíncrona.

Kotlin

override fun onStart() {
  val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
  val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
  controllerFuture.addListener(
    {
        // Call controllerFuture.get() to retrieve the MediaController.
        // MediaController implements the Player interface, so it can be
        // attached to the PlayerView UI component.
        playerView.setPlayer(controllerFuture.get())
      },
    MoreExecutors.directExecutor()
  )
}

Java

@Override
public void onStart() {
  SessionToken sessionToken =
    new SessionToken(this, new ComponentName(this, PlaybackService.class));
  ListenableFuture<MediaController> controllerFuture =
    new MediaController.Builder(this, sessionToken).buildAsync();
  controllerFuture.addListener(() -> {
    // Call controllerFuture.get() to retrieve the MediaController.
    // MediaController implements the Player interface, so it can be
    // attached to the PlayerView UI component.
    playerView.setPlayer(controllerFuture.get());
  }, MoreExecutors.directExecutor())
}

MediaController implementa a interface Player para que você possa usar os mesmos métodos, como play() e pause(), para controlar a reprodução. Assim como em outros componentes, lembre-se de liberar o MediaController quando ele não for mais necessário, como o método de ciclo de vida onStop() de um Activity, chamando MediaController.releaseFuture().

Publicar uma notificação

Os serviços em primeiro plano precisam publicar uma notificação enquanto estão ativos. Um MediaSessionService cria automaticamente uma notificação MediaStyle para você na forma de uma MediaNotification. Para fornecer uma notificação personalizada, crie um MediaNotification.Provider com DefaultMediaNotificationProvider.Builder ou uma implementação personalizada da interface do provedor. Adicione seu provedor ao MediaSession com setMediaNotificationProvider.

Como anunciar sua biblioteca de conteúdo

Uma MediaLibraryService se baseia em uma MediaSessionService, permitindo que apps clientes naveguem pelo conteúdo de mídia fornecido pelo app. Esses apps implementam uma MediaBrowser para interagir com a MediaLibraryService.

A implementação de um MediaLibraryService é semelhante à implementação de um MediaSessionService. A diferença é que, em onGetSession(), você precisa retornar uma MediaLibrarySession em vez de uma MediaSession. Em comparação com um MediaSession.Callback, o MediaLibrarySession.Callback inclui outros métodos que permitem que um cliente de navegador navegue pelo conteúdo oferecido pelo serviço de biblioteca.

De forma semelhante à MediaSessionService, declare o MediaLibraryService no manifesto e solicite a permissão FOREGROUND_SERVICE para executar um serviço em primeiro plano:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

O exemplo acima inclui um filtro de intent para o MediaLibraryService e, para compatibilidade com versões anteriores, para o MediaBrowserService legado. O filtro de intent extra permite que apps clientes que usam a API MediaBrowserCompat reconheçam seu Service.

Um MediaLibrarySession permite exibir sua biblioteca de conteúdo em uma estrutura de árvore, com uma única MediaItem raiz. Cada MediaItem na árvore pode ter qualquer número de nós MediaItem filhos. É possível exibir uma raiz diferente ou uma árvore diferente, com base na solicitação do app cliente. Por exemplo, a árvore que você retorna a um cliente que procura uma lista de itens de mídia recomendados pode conter apenas a MediaItem raiz e um único nível de nós MediaItem filhos, enquanto a árvore que você retorna a outro app cliente pode representar uma biblioteca mais completa de conteúdo.

Como criar um MediaLibrarySession

Um MediaLibrarySession estende a API MediaSession para adicionar APIs de navegação de conteúdo. Em comparação com o callback MediaSession, o callback MediaLibrarySession adiciona métodos como:

  • onGetLibraryRoot() para quando um cliente solicita o MediaItem raiz de uma árvore de conteúdo.
  • onGetChildren() para quando um cliente solicita os filhos de um MediaItem na árvore de conteúdo.
  • onGetSearchResult(), para quando um cliente solicita resultados da pesquisa da árvore de conteúdo para uma determinada consulta.

Os métodos de callback relevantes vão incluir um objeto LibraryParams com outros indicadores sobre o tipo de árvore de conteúdo em que um app cliente tem interesse.