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 da funcionalidade de transmissão ao app e oferece recursos avançados para mudar de dispositivo entre a reprodução local e remota. Este guia mostra como integrar o CastPlayer ao seu app de música.

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 inteligentes 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 o CastPlayer, adicione as dependências do AndroidX Media3 e do CastPlayer ao arquivo build.gradle do módulo do app.

Kotlin

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

Groovy

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

Configurar o CastPlayer

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

Provedor de opções

O CastPlayer requer um provedor de opções para configurar o comportamento. 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 fazer isso.

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 a reprodução remota com o Cast, seu app precisa gerenciar a reprodução mesmo quando o usuário não estiver interagindo com uma atividade do app, como pela notificação de mídia do sistema. Por isso, crie as instâncias ExoPlayer (para reprodução local) e CastPlayer (para reprodução remota) em um serviço, como MediaSessionService ou MediaLibraryService. Primeiro, crie sua instância de ExoPlayer e, ao criar a instância de CastPlayer, defina ExoPlayer como a instância do player local. Em seguida, 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 notificação da tela de bloqueio. O Media3 usa o recurso Seletor de saída 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) ícone do dispositivo na notificação de mídia (b) dispositivos compatíveis com Cast mostrados ao tocar no ícone do dispositivo (c) ícone 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. Tocar em MediaRouteButton abre uma caixa de diálogo 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. Nesta seção, mostramos como adicionar o botão e detectar eventos para atualizar a interface quando a reprodução alternar 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 app.

  • Compose UI: adicione um botão combinável.
  • 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 menu, (b) como uma visualização, (c) no PlayerView e (d) caixa de diálogo de dispositivos compatíveis com Cast.

Adicionar um MediaRouteButton combinável ao jogador

É possível adicionar o elemento combinável MediaRouteButton à interface do player. Para mais informações, consulte o guia do 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

É possível adicionar o MediaRouteButton diretamente nos controles de interface da PlayerView. Depois de definir o MediaController como o player do seu PlayerView, forneça um MediaRouteButtonViewProvider para mostrar o botão Transmitir 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 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;
}

Adicionar o MediaRouteButton como uma visualização

Você pode configurar um MediaRouteButton no layout.xml da 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 Cast do Media3 no código do 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 seu Activity para detectar mudanças no local de reprodução de mídia. Quando o playbackType mudar entre PLAYBACK_TYPE_LOCAL e PLAYBACK_TYPE_REMOTE, você poderá 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.