CastPlayer 使用入门

CastPlayer 是 Jetpack Media3 Player 实现,支持本地播放和投放到远程支持 Cast 的设备。CastPlayer可简化向应用添加投屏功能的过程,并提供丰富的功能,以便在本地播放和远程播放之间无缝切换。本指南介绍了如何将 CastPlayer 集成到媒体应用中。

如需将 Cast 与其他平台集成,请参阅 Cast SDK

获取支持 Cast 的设备

如需测试 CastPlayer,您需要支持 Cast 的设备。您可以选择 Android TV、Chromecast、智能音箱和智能显示屏。验证设备是否已设置并连接到与开发移动设备相同的 Wi-Fi 网络,以便进行发现。

添加 build 依赖项

如需开始使用 CastPlayer,请将 AndroidX Media3 和 CastPlayer 依赖项添加到应用模块的 build.gradle 文件中。

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"

配置 CastPlayer

如需配置 CastPlayer,请使用选项提供程序更新 AndroidManifest.xml 文件。

选项提供商

CastPlayer 需要一个选项提供程序来配置其行为。对于基本设置,您可以通过将 DefaultCastOptionsProvider 添加到 AndroidManifest.xml 文件中来使用它。此命令使用默认设置,包括默认接收器应用。

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

如需自定义配置,请实现您自己的自定义 OptionsProvider。如需了解具体方法,请参阅 CastOptions 指南。

添加了媒体传输接收器

向清单添加 MediaTransferReceiver 可让系统界面发现网络上支持 Cast 的设备,并在不打开应用 activity 的情况下重新路由媒体。例如,用户可以通过媒体通知更改播放应用媒体的设备。

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

构建 CastPlayer

对于使用 Cast 进行的远程播放,即使在用户未与您应用的 Activity 互动时(例如通过系统媒体通知),您的应用也应能够管理播放。因此,您应在服务(例如 MediaSessionServiceMediaLibraryService)中创建 ExoPlayer(用于本地播放)和 CastPlayer(用于远程播放)实例。首先,创建 ExoPlayer 实例,然后在构建 CastPlayer 实例时,将 ExoPlayer 设置为本地播放器实例。然后,您可以通过媒体通知或锁屏通知在移动设备和支持 Cast 的设备之间切换媒体播放。当输出路由从本地更改为远程或从远程更改为本地时,Media3 会使用输出切换器功能来处理播放器转移。

屏幕截图:显示通知中的“输出源切换器”界面。
图 1:(a)媒体通知中的设备芯片(b)点按设备芯片后显示的已启用 Cast 的设备(c)锁屏通知中的设备芯片

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

添加界面元素

向应用的界面添加 MediaRouteButton。点按 MediaRouteButton 会打开一个对话框,其中显示了网络上可用的支持 Cast 的设备列表。当用户选择设备后,媒体播放会从移动设备转移到所选的接收器设备。本部分将介绍如何添加按钮并监听事件,以便在播放设备在本地设备和远程设备之间切换时更新界面。

设置 MediaRouteButton

您可以通过四种方式将 MediaRouteButton 添加到 activity 的界面中。最佳选择取决于应用的设计和要求。

  • Compose 界面:添加按钮可组合项。
  • 观看次数界面
    • 将按钮添加到应用栏菜单。
    • PlayerView 内添加按钮。
    • 将按钮添加为标准 View
屏幕截图:显示界面中的 MediaRouteButton。
图 2:(a) 菜单栏中的 MediaRouteButton,(b) 作为视图,(c) 在 PlayerView 中,以及 (d) 支持 Cast 的设备的对话框。

向播放器添加可组合项 MediaRouteButton

您可以将 MediaRouteButton 可组合项添加到播放器的界面中。如需了解详情,请参阅 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) {
  ...
}

MediaRouteButton 添加到 PlayerView

您可以直接在 PlayerView 的界面控件中添加 MediaRouteButton。将 MediaController 设置为 PlayerView 的播放器后,提供 MediaRouteButtonViewProvider 以在播放器上显示 Cast 按钮。

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

MediaRouteButton 添加到应用栏菜单中

如需在应用栏菜单中设置 MediaRouteButton,请创建 XML 菜单并在 Activity 中替换 onCreateOptionsMenu

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

MediaRouteButton 添加为视图

您可以在 activity 的 layout.xml 中设置 MediaRouteButton

  <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" />

如需完成 MediaRouteButton 的设置,请在 Activity 代码中使用 Media3 Cast MediaRouteButtonFactory

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

Activity Listener

Activity 中创建 Player.Listener 以监听媒体播放位置的变化。当 playbackTypePLAYBACK_TYPE_LOCALPLAYBACK_TYPE_REMOTE 之间变化时,您可以根据需要调整界面。为防止内存泄漏,并将监听器活动限制在仅当应用可见时进行,请在 onStart 中注册监听器,并在 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);
}

如需详细了解如何监听和响应播放事件,请参阅播放器事件指南。