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.10.0")
implementation("androidx.media3:media3-ui:1.10.0")
implementation("androidx.media3:media3-session:1.10.0")
implementation("androidx.media3:media3-cast:1.10.0")

Groovy

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

配置 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) 作为 View,(c) 在 PlayerView 中,以及 (d) 支持 Cast 的设备的对话框。

向播放器添加可组合项 MediaRouteButton

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

MediaRouteButton 添加到 PlayerView

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

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 菜单并 替换 onCreateOptionsMenu 在您的 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;
}

MediaRouteButton 添加为 View

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

Activity 监听器

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

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