MediaRouter 總覽

如要在應用程式中使用 MediaRouter 架構,您必須取得 MediaRouter 物件的執行個體,並附加 MediaRouter.Callback 物件來監聽轉送事件。透過媒體路徑傳送的內容會通過路線的關聯 MediaRouteProvider (除了少數特殊情況,例如藍牙輸出裝置)。圖 1 提供用於在裝置之間轉送內容的類別總覽。

圖 1 應用程式使用的主要媒體路由器類別總覽。

注意:如要讓應用程式支援 Google Cast 裝置,請使用 Cast SDK,並且建構應用程式做為投放傳送者。請按照 Cast 說明文件中的指示操作,而不要直接使用 MediaRouter 架構。

媒體路徑按鈕

Android 應用程式應使用媒體路徑按鈕控制媒體路由。MediaRouter 架構提供按鈕的標準介面,可協助使用者辨識及使用有可用的轉送功能。媒體路徑按鈕通常位於應用程式動作列的右側,如圖 2 所示。

圖 2. 動作列中的媒體路徑按鈕。

當使用者按下媒體路徑按鈕時,可用的媒體路徑會顯示在清單中,如圖 3 所示。

圖 3. 按下媒體路徑按鈕後會顯示可用媒體路徑的清單。

請按照下列步驟建立媒體路徑按鈕:

  1. 使用 AppCompatActivity
  2. 定義媒體路徑按鈕選單項目
  3. 建立 MediaRouteSelector
  4. 將媒體路徑按鈕新增至動作列
  5. 在活動生命週期中建立及管理 MediaRouter.Callback 方法

本節說明前四個步驟。下一節將說明回呼方法。

使用 AppCompatActivity

在活動中使用媒體路由器架構時,您應從 AppCompatActivity 擴充活動,並匯入 androidx.appcompat.app 套件。您必須在應用程式開發專案中新增 androidx.appcompat:appcompatandroidx.mediarouter:mediarouter。如要進一步瞭解如何在專案中新增支援程式庫,請參閱「開始使用 Android Jetpack」。

注意:請務必使用媒體路由器架構的 androidx 實作。請勿使用舊版 android.media 套件。

建立 xml 檔案,用於定義媒體路徑按鈕的選單項目。該項目的動作應為 MediaRouteActionProvider 類別。以下是範例檔案:

// myMediaRouteButtonMenuItem.xml
<?xml version="1.0" encoding="utf-8"?>
<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:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
        app:showAsAction="always"
    />
</menu>

建立 MediaRouteSelector

媒體路徑按鈕選單中顯示的路徑由 MediaRouteSelector 決定。從 AppCompatActivity 擴充活動,並在透過 onCreate() 方法呼叫 MediaRouteSelector.Builder 時建構選取器,如以下程式碼範例所示。請注意,選取器會儲存在類別變數中,而可以加入 MediaControlIntent 物件來指定允許的路線類型:

Kotlin

class MediaRouterPlaybackActivity : AppCompatActivity() {

    private var mSelector: MediaRouteSelector? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Create a route selector for the type of routes your app supports.
        mSelector = MediaRouteSelector.Builder()
                // These are the framework-supported intents
                .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                .build()
    }
}

Java

public class MediaRouterPlaybackActivity extends AppCompatActivity {
    private MediaRouteSelector mSelector;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Create a route selector for the type of routes your app supports.
        mSelector = new MediaRouteSelector.Builder()
                // These are the framework-supported intents
                .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                .build();
    }
}

對大多數應用程式而言,唯一需要的路線類型為 CATEGORY_REMOTE_PLAYBACK。這個路徑類型會將執行應用程式的裝置視為遙控器。已連結的接收器裝置會處理所有內容資料的擷取、解碼和播放作業。這是支援 Google Cast (例如 Chromecast) 的應用程式運作方式。

部分製造商支援名為「次要輸出」的特殊轉送選項。透過這項轉送,您的媒體應用程式可以擷取和呈現影片或音樂,並直接串流至所選遠端接收器裝置的螢幕和/或喇叭。使用次要輸出內容,將內容傳送至支援無線功能的音樂系統或視訊螢幕。如要啟用這類裝置的探索及選取功能,您必須在 MediaRouteSelector 中加入 CATEGORY_LIVE_AUDIOCATEGORY_LIVE_VIDEO 控制項類別。此外,您也必須建立及處理自己的 Presentation 對話方塊。

將媒體路徑按鈕新增至動作列

定義媒體路徑選單和 MediaRouteSelector 後,您現在可以將媒體路徑按鈕新增至活動。覆寫每個活動的 onCreateOptionsMenu() 方法,以便新增選項選單。

Kotlin

override fun onCreateOptionsMenu(menu: Menu): Boolean {
    super.onCreateOptionsMenu(menu)

    // Inflate the menu and configure the media router action provider.
    menuInflater.inflate(R.menu.sample_media_router_menu, menu)

    // Attach the MediaRouteSelector to the menu item
    val mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item)
    val mediaRouteActionProvider =
            MenuItemCompat.getActionProvider(mediaRouteMenuItem) as MediaRouteActionProvider

    // Attach the MediaRouteSelector that you built in onCreate()
    selector?.also(mediaRouteActionProvider::setRouteSelector)

    // Return true to show the menu.
    return true
}

Java

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);

    // Inflate the menu and configure the media router action provider.
    getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);

    // Attach the MediaRouteSelector to the menu item
    MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
    MediaRouteActionProvider mediaRouteActionProvider =
            (MediaRouteActionProvider)MenuItemCompat.getActionProvider(
            mediaRouteMenuItem);
    // Attach the MediaRouteSelector that you built in onCreate()
    mediaRouteActionProvider.setRouteSelector(selector);

    // Return true to show the menu.
    return true;
}

如要進一步瞭解如何在應用程式中實作動作列,請參閱「動作列」開發人員指南。

您也可以在任何檢視畫面中將媒體路徑按鈕新增為 MediaRouteButton。您必須使用 setRouteSelector() 方法,將 MediaRouteSelector 附加至按鈕。請參閱 Google Cast 設計檢查清單,瞭解將媒體路徑按鈕整合到應用程式的相關規範。

MediaRouter 回呼

在同一部裝置上執行的所有應用程式都會共用單一 MediaRouter 執行個體及其路徑 (由應用程式的 MediaRouteSelector 篩選每個應用程式)。每個活動都會使用自己的 MediaRouter.Callback 方法實作方式與 MediaRouter 通訊。每當使用者選取、變更或中斷路線時,MediaRouter 就會呼叫回呼方法。

回呼中有幾種方法可以覆寫,以接收轉送事件的相關資訊。實作的 MediaRouter.Callback 類別至少應覆寫 onRouteSelected()onRouteUnselected()

由於 MediaRouter 是共用資源,因此您的應用程式需要管理 MediaRouter 回呼,以回應一般的活動生命週期回呼:

  • 建立活動時 (onCreate(Bundle)) 擷取至 MediaRouter 的指標,並在應用程式的生命週期內保留該指標。
  • 在活動顯示時將回呼附加至 MediaRouter (onStart()),並在活動處於隱藏狀態時將其卸離 (onStop())。

下列程式碼範例示範如何建立及儲存回呼物件、如何取得 MediaRouter 的執行個體,以及如何管理回呼。請注意,在 onStart() 中附加回呼時使用了 CALLBACK_FLAG_REQUEST_DISCOVERY 標記。這樣您的 MediaRouteSelector 即可重新整理媒體路徑按鈕的可用路徑清單。

Kotlin

class MediaRouterPlaybackActivity : AppCompatActivity() {

    private var mediaRouter: MediaRouter? = null
    private var mSelector: MediaRouteSelector? = null

    // Variables to hold the currently selected route and its playback client
    private var mRoute: MediaRouter.RouteInfo? = null
    private var remotePlaybackClient: RemotePlaybackClient? = null

    // Define the Callback object and its methods, save the object in a class variable
    private val mediaRouterCallback = object : MediaRouter.Callback() {

        override fun onRouteSelected(router: MediaRouter, route: MediaRouter.RouteInfo) {
            Log.d(TAG, "onRouteSelected: route=$route")
            if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
                // Stop local playback (if necessary)
                // ...

                // Save the new route
                mRoute = route

                // Attach a new playback client
                remotePlaybackClient =
                    RemotePlaybackClient(this@MediaRouterPlaybackActivity, mRoute)

                // Start remote playback (if necessary)
                // ...
            }
        }

        override fun onRouteUnselected(
                router: MediaRouter,
                route: MediaRouter.RouteInfo,
                reason: Int
        ) {
            Log.d(TAG, "onRouteUnselected: route=$route")
            if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {

                // Changed route: tear down previous client
                mRoute?.also {
                    remotePlaybackClient?.release()
                    remotePlaybackClient = null
                }

                // Save the new route
                mRoute = route

                when (reason) {
                    MediaRouter.UNSELECT_REASON_ROUTE_CHANGED -> {
                        // Resume local playback (if necessary)
                        // ...
                    }
                }
            }
        }
    }


    // Retain a pointer to the MediaRouter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Get the media router service.
        mediaRouter = MediaRouter.getInstance(this)
        ...
    }

    // Use this callback to run your MediaRouteSelector to generate the
    // list of available media routes
    override fun onStart() {
        mSelector?.also { selector ->
            mediaRouter?.addCallback(selector, mediaRouterCallback,
                    MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY)
        }
        super.onStart()
    }

    // Remove the selector on stop to tell the media router that it no longer
    // needs to discover routes for your app.
    override fun onStop() {
        mediaRouter?.removeCallback(mediaRouterCallback)
        super.onStop()
    }
    ...
}

Java

public class MediaRouterPlaybackActivity extends AppCompatActivity {
    private MediaRouter mediaRouter;
    private MediaRouteSelector mSelector;

    // Variables to hold the currently selected route and its playback client
    private MediaRouter.RouteInfo mRoute;
    private RemotePlaybackClient remotePlaybackClient;

    // Define the Callback object and its methods, save the object in a class variable
    private final MediaRouter.Callback mediaRouterCallback =
            new MediaRouter.Callback() {

        @Override
        public void onRouteSelected(MediaRouter router, RouteInfo route) {
            Log.d(TAG, "onRouteSelected: route=" + route);

            if (route.supportsControlCategory(
                MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)){
                // Stop local playback (if necessary)
                // ...

                // Save the new route
                mRoute = route;

                // Attach a new playback client
                remotePlaybackClient = new RemotePlaybackClient(this, mRoute);

                // Start remote playback (if necessary)
                // ...
            }
        }

        @Override
        public void onRouteUnselected(MediaRouter router, RouteInfo route, int reason) {
            Log.d(TAG, "onRouteUnselected: route=" + route);

            if (route.supportsControlCategory(
                MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)){

                // Changed route: tear down previous client
                if (mRoute != null && remotePlaybackClient != null) {
                    remotePlaybackClient.release();
                    remotePlaybackClient = null;
                }

                // Save the new route
                mRoute = route;

                if (reason != MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
                    // Resume local playback  (if necessary)
                    // ...
                }
            }
        }
    }


    // Retain a pointer to the MediaRouter
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Get the media router service.
        mediaRouter = MediaRouter.getInstance(this);
        ...
    }

    // Use this callback to run your MediaRouteSelector to generate the list of available media routes
    @Override
    public void onStart() {
        mediaRouter.addCallback(mSelector, mediaRouterCallback,
                MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
        super.onStart();
    }

    // Remove the selector on stop to tell the media router that it no longer
    // needs to discover routes for your app.
    @Override
    public void onStop() {
        mediaRouter.removeCallback(mediaRouterCallback);
        super.onStop();
    }
    ...
}

媒體路由器架構也提供 MediaRouteDiscoveryFragment 類別,可處理和移除活動的回呼。

注意:如要編寫音樂播放應用程式,且希望應用程式在背景執行時播放音樂,您必須建構用於播放的 Service,並從服務的生命週期回呼中呼叫媒體路由器架構。

控制遠端播放路徑

指定遠端播放路徑後,應用程式即為遙控器。路徑另一端的裝置會處理所有內容資料擷取、解碼和播放功能。應用程式 UI 中的控制項使用 RemotePlaybackClient 物件與接收器裝置通訊。

RemotePlaybackClient 類別提供其他管理內容播放的方法。以下是 RemotePlaybackClient 類別的幾個按鍵播放方法:

  • play():播放由 Uri 指定的媒體檔案。
  • pause():暫停目前正在播放的媒體音軌。
  • resume():在暫停指令後繼續播放目前曲目。
  • seek():在目前測試群組中的特定位置。
  • release():拆除應用程式與遠端播放裝置之間的連線。

您可以使用這些方法,將動作附加到您在應用程式中提供的播放控制項。這些方法大多數也可讓您納入回呼物件,以便監控播放任務或控制要求的進度。

RemotePlaybackClient 類別也支援將多個媒體項目排入佇列,用於播放及管理媒體佇列。

程式碼範例

Android BasicMediaRouterMediaRouter 範例進一步示範如何使用 MediaRouter API。