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. CastPlayer simplifica a adição de 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, você pode 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 Cast.

Adicionar um elemento combinável MediaRouteButton ao player

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

@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,
  )
  // ...
  return true
}

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);
  // ...
  return true;
}

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) {
  super.onCreate(savedInstanceState);
  // ...
  MediaRouteButton button = findViewById(R.id.media_route_button);
  ListenableFuture<Void> setUpFuture =
      MediaRouteButtonFactory.setUpMediaRouteButton(context, button);
}

Listener de atividade

Crie um Player.Listener no 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 limitar a atividade do listener apenas quando o app estiver visível, registre o listener em onStart e cancele o registro em onStop:

Kotlin

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

private final 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.