Introdução ao CastPlayer

O CastPlayer é uma implementação do Player do Jetpack Media3 que oferece suporte à reprodução local e à transmissão para um dispositivo remoto compatível com Cast. O CastPlayer simplifica a adição da funcionalidade de transmissão ao seu app e oferece recursos avançados para alternar entre a reprodução local e remota sem problemas. Este guia mostra como integrar CastPlayer ao seu app de mídia.

Para integrar o Cast a outras plataformas, consulte o SDK do Cast.

Ter um dispositivo compatível com Cast

Para testar o CastPlayer, você precisa de um dispositivo compatível com Cast. As opções incluem Android TV, Chromecast, alto-falantes e smart displays. Verifique se o dispositivo está configurado e conectado à mesma rede Wi-Fi que o dispositivo móvel de desenvolvimento para descoberta.

Adicionar dependências de compilação

Para começar a usar CastPlayer, adicione as dependências do AndroidX Media3 e CastPlayer ao arquivo build.gradle do módulo do app.

Kotlin

implementation("androidx.media3:media3-exoplayer:1.9.2")
implementation("androidx.media3:media3-ui:1.9.2")
implementation("androidx.media3:media3-session:1.9.2")
implementation("androidx.media3:media3-cast:1.9.2")

Groovy

implementation "androidx.media3:media3-exoplayer:1.9.2"
implementation "androidx.media3:media3-ui:1.9.2"
implementation "androidx.media3:media3-session:1.9.2"
implementation "androidx.media3:media3-cast:1.9.2"

Configurar o CastPlayer

Para configurar o CastPlayer, atualize o arquivo AndroidManifest.xml com um provedor de opções.

Provedor de opções

O CastPlayer exige um provedor de opções para configurar o comportamento dele. Para uma configuração básica, use o DefaultCastOptionsProvider adicionando-o ao arquivo AndroidManifest.xml. Isso usa as configurações padrão, incluindo o aplicativo receptor padrão.

<application>
  ...
  <meta-data
    android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
    android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
  ...
</application>

Para personalizar a configuração, implemente seu próprio OptionsProvider personalizado. Consulte o guia CastOptions para saber como.

Adicionar um receptor para transferências de mídia

Adicionar um MediaTransferReceiver ao manifesto permite que a interface do sistema descubra dispositivos compatíveis com Cast na rede e redirecione a mídia sem abrir a atividade do app. Por exemplo, um usuário pode mudar o dispositivo que está reproduzindo a mídia do seu app na notificação de mídia.

<application>
  ...
  <receiver android:name="androidx.mediarouter.media.MediaTransferReceiver" />
  ...
</application>

Criar um CastPlayer

Para reprodução remota com o Cast, o app precisa gerenciar a reprodução mesmo quando o usuário não está interagindo com uma atividade do app, como por meio da notificação de mídia do sistema. Por isso, crie suas instâncias ExoPlayer (para reprodução local) e CastPlayer (para reprodução remota) em um serviço, como MediaSessionService ou MediaLibraryService. Primeiro, crie a instância ExoPlayer e, ao criar a instância CastPlayer, defina ExoPlayer como a instância do jogador local. Depois, é possível alternar a reprodução de mídia entre o dispositivo móvel e o dispositivo compatível com Cast na notificação de mídia ou na tela de bloqueio. A Media3 usa o recurso Output Switcher para processar transferências de player quando a rota de saída muda de local para remota ou de remota para local.

Captura de tela mostrando a interface do seletor de saída nas notificações.
Figura 1: (a) Chip do dispositivo na notificação de mídia (b) Dispositivos compatíveis com Cast mostrados ao tocar no chip do dispositivo (c) Chip do dispositivo na notificação da tela de bloqueio

Kotlin

override fun onCreate() {
  super.onCreate()

  val exoPlayer = ExoPlayer.Builder(context).build()
  val castPlayer = CastPlayer.Builder(context)
      .setLocalPlayer(exoPlayer)
      .build()

  mediaSession = MediaSession.Builder(context, castPlayer).build()
}

Java

@Override
public void onCreate() {
  super.onCreate();

  ExoPlayer exoPlayer = new ExoPlayer.Builder(context).build();
  CastPlayer castPlayer = new CastPlayer.Builder(context)
      .setLocalPlayer(exoPlayer)
      .build();

  mediaSession = new MediaSession.Builder(
    /* context= */ context, /* player= */ castPlayer).build();
}

Adicionar elementos da interface

Adicione um MediaRouteButton à interface do app. Ao tocar em MediaRouteButton, uma caixa de diálogo será aberta com uma lista de dispositivos compatíveis com Cast disponíveis na rede. Quando o usuário seleciona um dispositivo, a reprodução de mídia é transferida do dispositivo móvel para o receptor selecionado. Esta seção mostra como adicionar o botão e detectar eventos para atualizar a interface quando a reprodução muda entre dispositivos locais e remotos.

Definir o MediaRouteButton

Há quatro maneiras de adicionar o MediaRouteButton à interface da sua atividade. A melhor escolha depende do design e dos requisitos do seu app.

  • Compose UI: adicione um elemento combinável de botão.
  • Interface de visualizações:
    • Adicione o botão ao menu da barra de apps.
    • Adicione o botão dentro de PlayerView.
    • Adicione o botão como um View padrão.
Captura de tela mostrando o MediaRouteButton na interface.
Figura 2: (a) MediaRouteButton na barra de menus, (b) como uma visualização, (c) em PlayerView e (d) caixa de diálogo de dispositivos compatíveis com o Google Cast.

Adicionar um elemento combinável MediaRouteButton ao jogador

Você pode adicionar o elemento combinável MediaRouteButton à interface do player. Para mais informações, consulte o guia do Compose.

Kotlin

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.media3.cast.MediaRouteButton

@Composable
fun PlayerComposeView(player: Player, modifier: Modifier = Modifier) {
  var controlsVisible by remember { mutableStateOf(false) }

  Box(
    modifier = modifier.clickable { controlsVisible = true },
    contentAlignment = Alignment.Center,
  ) {
    PlayerSurface(player = player, modifier = modifier)
    AnimatedVisibility(visible = controlsVisible, enter = fadeIn(), exit = fadeOut()) {
      Box(modifier = Modifier.fillMaxSize()) {
        MediaRouteButton(modifier = Modifier.align(Alignment.TopEnd))
        PrimaryControls(player = player, modifier = Modifier.align(Alignment.Center))
      }
    }
  }
}

@Composable
fun PrimaryControls(player: Player, modifier: Modifier = Modifier) {
  ...
}

Adicionar o MediaRouteButton ao PlayerView

Você pode adicionar o MediaRouteButton diretamente nos controles da interface do PlayerView. Depois de definir o MediaController como o player do seu PlayerView, forneça um MediaRouteButtonViewProvider para mostrar o botão Cast no player.

Kotlin

override fun onStart() {
  super.onStart()

  playerView.player = mediaController
  playerView.setMediaRouteButtonViewProvider(MediaRouteButtonViewProvider())
}

Java

@Override
public void onStart() {
  super.onStart();

  playerView.setPlayer(mediaController);
  playerView.setMediaRouteButtonViewProvider(new MediaRouteButtonViewProvider());
}

Adicionar o MediaRouteButton ao menu da barra de apps

Para configurar um MediaRouteButton no menu da barra de apps, crie um menu XML e substitua onCreateOptionsMenu no seu Activity.

<menu xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:app="http://schemas.android.com/apk/res-auto">
  <item android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:showAsAction="always"
    app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"/>
</menu>

Kotlin

override fun onCreateOptionsMenu(menu: Menu): Boolean {
    ...
    menuInflater.inflate(R.menu.sample_media_route_button_menu, menu)
    val menuItemFuture: ListenableFuture<MenuItem> =
        MediaRouteButtonFactory.setUpMediaRouteButton(
            context, menu, R.id.media_route_menu_item)
    Futures.addCallback(
        menuItemFuture,
        object : FutureCallback<MenuItem> {
            override fun onSuccess(menuItem: MenuItem?) {
                // Do something with the menu item.
            }

            override fun onFailure(t: Throwable) {
                // Handle the failure.
            }
        },
        executor)
    ...
}

Java

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    ...
    getMenuInflater().inflate(R.menu.sample_media_route_button_menu, menu);
    ListenableFuture<MenuItem> menuItemFuture =
        MediaRouteButtonFactory.setUpMediaRouteButton(
          context, menu, R.id.media_route_menu_item);
    Futures.addCallback(
        menuItemFuture,
        new FutureCallback<MenuItem>() {
          @Override
          public void onSuccess(MenuItem menuItem) {
            // Do something with the menu item.
          }

          @Override
          public void onFailure(Throwable t) {
            // Handle the failure.
          }
        },
        executor);
    ...
}

Adicione MediaRouteButton como uma visualização

Você pode configurar um MediaRouteButton no layout.xml da sua atividade.

  <androidx.mediarouter.app.MediaRouteButton
      android:id="@+id/media_route_button"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      app:mediaRouteButtonTint="@android:color/white" />

Para concluir a configuração do MediaRouteButton, use o MediaRouteButtonFactory do Media3 Cast no seu código Activity.

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)

  findViewById<MediaRouteButton>(R.id.media_route_button)?.also {
    val unused = MediaRouteButtonFactory.setUpMediaRouteButton(context, it)
  }
}

Java

@Override
public void onCreate(Bundle savedInstanceState) {
    ...
    MediaRouteButton button = findViewById(R.id.media_route_button);
    ListenableFuture<Void> setUpFuture =
        MediaRouteButtonFactory.setUpMediaRouteButton(context, button);
}

Listener de atividade

Crie um Player.Listener no seu Activity para detectar mudanças no local de reprodução de mídia. Quando o playbackType muda entre PLAYBACK_TYPE_LOCAL e PLAYBACK_TYPE_REMOTE, você pode ajustar a interface conforme necessário. Para evitar vazamentos de memória e restringir a atividade do listener apenas quando o app estiver visível, registre o listener em onStart e cancele o registro em onStop:

Kotlin

import androidx.media3.common.DeviceInfo
import androidx.media3.common.Player

private val playerListener: Player.Listener =
  object : Player.Listener {
    override fun onDeviceInfoChanged(deviceInfo: DeviceInfo) {
      if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_LOCAL) {
        // Add UI changes for local playback.
      } else if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_REMOTE) {
        // Add UI changes for remote playback.
      }
    }
  }

override fun onStart() {
  super.onStart()
  mediaController.addListener(playerListener)
}

override fun onStop() {
  super.onStop()
  mediaController.removeListener(playerListener)
}

Java

import androidx.media3.common.DeviceInfo;
import androidx.media3.common.Player;

private Player.Listener playerListener =
    new Player.Listener() {
      @Override
      public void onDeviceInfoChanged(DeviceInfo deviceInfo) {
        if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_LOCAL) {
          // Add UI changes for local playback.
        } else if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_REMOTE) {
          // Add UI changes for remote playback.
        }
      }
    };

@Override
protected void onStart() {
  super.onStart();
  mediaController.addListener(playerListener);
}

@Override
protected void onStop() {
  super.onStop();
  mediaController.removeListener(playerListener);
}

Para mais informações sobre como detectar e responder a eventos de reprodução, consulte o guia de eventos do player.