開始使用 CastPlayer

CastPlayer 是 Jetpack Media3 Player 實作項目,支援在本機播放內容,以及投放到支援 Cast 的遠端裝置。CastPlayer 可簡化在應用程式中新增 Cast 功能的程序,並提供豐富的功能,讓使用者在本地和遠端播放之間順暢切換。本指南說明如何將 CastPlayer 整合至媒體應用程式。

如要將 Cast 與其他平台整合,請參閱 Cast SDK

取得支援 Cast 的裝置

如要測試 CastPlayer,您需要支援 Cast 的裝置。包括 Android TV、Chromecast、智慧音箱和智慧螢幕。確認裝置已設定完成,並連上與開發用行動裝置相同的 Wi-Fi 網路,以便探索裝置。

新增建構依附元件

如要開始使用 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,系統 UI 就能在網路上探索支援 Cast 的裝置,並重新導向媒體,無須開啟應用程式活動。舉例來說,使用者可以透過媒體通知,變更播放應用程式媒體的裝置。

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

新增 UI 元素

在應用程式的 UI 中新增 MediaRouteButton。輕觸 MediaRouteButton 會開啟對話方塊,顯示網路上可用的 Cast 裝置清單。使用者選取裝置後,媒體播放作業就會從行動裝置轉移至所選接收器裝置。本節說明如何新增按鈕及監聽事件,以便在播放作業於本機和遠端裝置之間切換時更新 UI。

設定 MediaRouteButton

MediaRouteButton 新增至活動 UI 的方式有四種。最適合的選擇取決於應用程式的設計和需求。

  • Compose UI:新增按鈕可組合函式。
  • 「檢視」使用者介面
    • 將按鈕新增至應用程式列選單。
    • PlayerView 中新增按鈕。
    • 將按鈕新增為標準 View
螢幕截圖:顯示 UI 中的 MediaRouteButton。
圖 2:(a) 位於選單列的 MediaRouteButton;(b) 位於 View;(c) 位於 PlayerView;(d) 位於支援 Cast 的裝置對話方塊。

將可組合函式 MediaRouteButton 新增至播放器

您可以將 MediaRouteButton 可組合函式新增至播放器的 UI。詳情請參閱 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 的 UI 控制項中直接新增 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 新增為檢視畫面

您可以在活動 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 中建立 Player.Listener,監聽媒體播放位置的變更。當 playbackTypePLAYBACK_TYPE_LOCALPLAYBACK_TYPE_REMOTE 之間變更時,您可以視需要調整 UI。為避免記憶體流失,並將監聽器活動限制在應用程式顯示時,請在 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);
}

如要進一步瞭解如何監聽及回應播放事件,請參閱播放器事件指南。