MediaRouteProvider 總覽

Android 媒體路由器架構可讓製造商透過名為 MediaRouteProvider 的標準化介面,在自家裝置上啟用播放功能。路徑供應商會定義在接收器裝置上播放媒體的通用介面,可讓您透過任何支援媒體路徑的 Android 應用程式,在設備上播放媒體內容。

本指南說明如何為接收器裝置建立媒體路徑供應器,讓其他在 Android 上執行的媒體播放應用程式使用。如要使用這個 API,您必須熟悉主要類別 MediaRouteProviderMediaRouteProviderDescriptorRouteController

總覽

Android 媒體路由器架構可讓媒體應用程式開發人員和媒體播放裝置製造商透過常見的 API 和常見的使用者介面進行連線。實作 MediaRouter 介面的應用程式開發人員即可連線至架構,並在採用媒體路由器架構的裝置上播放內容。媒體播放裝置製造商可以藉由發布 MediaRouteProvider,允許其他應用程式在接收器裝置上連接及播放媒體,藉此參與此架構。圖 1 說明應用程式如何透過媒體路由器架構連線至接收裝置。

圖 1 概略說明媒體路徑供應商類別如何為媒體應用程式提供訊息給接收器裝置。

您為接收器裝置建構媒體路徑供應器時,供應商有以下用途:

  • 說明及發布接收器裝置的功能,讓其他應用程式可以找到裝置並使用其播放功能。
  • 納入接收器裝置的程式設計介面及其通訊傳輸機制,讓裝置與媒體路由器架構相容。

路線供應商的分佈情形

媒體路徑供應器是隨附於 Android 應用程式的一部分。只要擴充 MediaRouteProviderService,或是以自己的服務納入 MediaRouteProvider 實作項目,並為媒體路徑供應商宣告意圖篩選器,就能提供給其他應用程式。這些步驟可讓其他應用程式探索及使用您的媒體路徑。

注意:內含媒體路徑提供者的應用程式也可以為路徑提供者加入 MediaRouter 介面,但這並非強制規定。

MediaRouter 支援資料庫

媒體路由器 API 是在 AndroidX MediaRouter 程式庫中定義。您必須將此程式庫新增至應用程式開發專案。如要進一步瞭解如何在專案中新增支援資料庫,請參閱「支援資料庫設定」。

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

建立提供者服務

媒體路由器架構必須能探索並連線至您的媒體路徑供應商,才能讓其他應用程式使用您的路徑。為此,媒體路由器架構會尋找宣告媒體路徑供應商意圖動作的應用程式。當其他應用程式想連線至您的供應器時,架構必須能夠叫用並連線,因此供應器必須封裝在 Service 中。

下列程式碼範例顯示媒體路徑供應商服務的宣告,以及資訊清單中的意圖篩選器,讓媒體路由器架構可以探索及使用:

<service android:name=".provider.SampleMediaRouteProviderService"
    android:label="@string/sample_media_route_provider_service"
    android:process=":mrp">
    <intent-filter>
        <action android:name="android.media.MediaRouteProviderService" />
    </intent-filter>
</service>

這個資訊清單範例會宣告納入實際媒體路徑提供者類別的服務。Android 媒體路由器架構提供 MediaRouteProviderService 類別,可做為媒體路徑供應商的服務包裝函式。下列程式碼範例說明如何使用此包裝函式類別:

Kotlin

class SampleMediaRouteProviderService : MediaRouteProviderService() {

    override fun onCreateMediaRouteProvider(): MediaRouteProvider {
        return SampleMediaRouteProvider(this)
    }
}

Java

public class SampleMediaRouteProviderService extends MediaRouteProviderService {

    @Override
    public MediaRouteProvider onCreateMediaRouteProvider() {
        return new SampleMediaRouteProvider(this);
    }
}

指定路徑功能

連線至媒體路由器架構的應用程式可透過應用程式資訊清單宣告探索媒體路徑,但也需要瞭解您提供的媒體路徑功能。媒體路徑可以是不同類型的和功能,其他應用程式必須能探索這些詳細資料,才能判斷是否與您的路線相容。

媒體路由器架構可讓您透過 IntentFilter 物件、MediaRouteDescriptor 物件和 MediaRouteProviderDescriptor 定義並發布媒體路徑的功能。本節說明如何使用這些類別,發布其他應用程式的媒體路徑詳細資料。

路線類別

關於媒體路徑供應商的程式輔助說明,您必須指定供應商是否支援遠端播放和/或次要輸出。以下是媒體路由器架構提供的路徑類別:

為了在媒體路徑的說明中加入這些設定,您必須將其插入 IntentFilter 物件中,您之後會將其加入 MediaRouteDescriptor 物件中:

Kotlin

class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

    companion object {
        private val CONTROL_FILTERS_BASIC: ArrayList<IntentFilter> = IntentFilter().run {
            addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
            arrayListOf(this)
        }
    }
}

Java

public final class SampleMediaRouteProvider extends MediaRouteProvider {
    private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
    static {
        IntentFilter videoPlayback = new IntentFilter();
        videoPlayback.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
        CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
        CONTROL_FILTERS_BASIC.add(videoPlayback);
    }
}

如果指定 CATEGORY_REMOTE_PLAYBACK 意圖,您也必須定義媒體路徑供應商支援的媒體類型和播放控制項。下一節將說明如何為裝置指定這些設定。

媒體類型和通訊協定

遠端播放裝置的媒體路徑供應商必須指定自身支援的媒體類型及傳輸通訊協定。您可以使用 IntentFilter 類別和該物件的 addDataScheme()addDataType() 方法指定這些設定。下列程式碼片段示範如何定義意圖篩選器,以使用 http、https 和即時串流通訊協定 (RTSP) 支援遠端影片播放:

Kotlin

class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

    companion object {

        private fun IntentFilter.addDataTypeUnchecked(type: String) {
            try {
                addDataType(type)
            } catch (ex: IntentFilter.MalformedMimeTypeException) {
                throw RuntimeException(ex)
            }
        }

        private val CONTROL_FILTERS_BASIC: ArrayList<IntentFilter> = IntentFilter().run {
            addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
            addAction(MediaControlIntent.ACTION_PLAY)
            addDataScheme("http")
            addDataScheme("https")
            addDataScheme("rtsp")
            addDataTypeUnchecked("video/*")
            arrayListOf(this)
        }
    }
    ...
}

Java

public final class SampleMediaRouteProvider extends MediaRouteProvider {

    private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;

    static {
        IntentFilter videoPlayback = new IntentFilter();
        videoPlayback.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
        videoPlayback.addAction(MediaControlIntent.ACTION_PLAY);
        videoPlayback.addDataScheme("http");
        videoPlayback.addDataScheme("https");
        videoPlayback.addDataScheme("rtsp");
        addDataTypeUnchecked(videoPlayback, "video/*");
        CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
        CONTROL_FILTERS_BASIC.add(videoPlayback);
    }
    ...

    private static void addDataTypeUnchecked(IntentFilter filter, String type) {
        try {
            filter.addDataType(type);
        } catch (MalformedMimeTypeException ex) {
            throw new RuntimeException(ex);
        }
    }
}

播放控制項

提供遠端播放的媒體路徑供應商必須指定支援的媒體控制項類型。以下是媒體路徑提供的一般控制項類型:

  • 播放控制項,例如播放、暫停、倒轉和快轉。
  • 將功能排入佇列:可讓傳送應用程式新增及移除接收器裝置所維護播放清單中的項目。
  • 工作階段功能:透過讓接收器裝置向提出要求的應用程式提供工作階段 ID,然後透過每個後續的播放控制要求檢查該 ID,以防止傳送應用程式彼此幹擾。

以下程式碼範例說明如何建構意圖篩選器,以支援基本媒體路徑播放控制項:

Kotlin

class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

    companion object {
        ...
        private val CONTROL_FILTERS_BASIC: ArrayList<IntentFilter> = run {
            val videoPlayback: IntentFilter = ...
            ...
            val playControls = IntentFilter().apply {
                addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                addAction(MediaControlIntent.ACTION_SEEK)
                addAction(MediaControlIntent.ACTION_GET_STATUS)
                addAction(MediaControlIntent.ACTION_PAUSE)
                addAction(MediaControlIntent.ACTION_RESUME)
                addAction(MediaControlIntent.ACTION_STOP)
            }
            arrayListOf(videoPlayback, playControls)
        }
    }
    ...
}

Java

public final class SampleMediaRouteProvider extends MediaRouteProvider {
    private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
    static {
        ...
        IntentFilter playControls = new IntentFilter();
        playControls.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
        playControls.addAction(MediaControlIntent.ACTION_SEEK);
        playControls.addAction(MediaControlIntent.ACTION_GET_STATUS);
        playControls.addAction(MediaControlIntent.ACTION_PAUSE);
        playControls.addAction(MediaControlIntent.ACTION_RESUME);
        playControls.addAction(MediaControlIntent.ACTION_STOP);
        CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
        CONTROL_FILTERS_BASIC.add(videoPlayback);
        CONTROL_FILTERS_BASIC.add(playControls);
    }
    ...
}

如要進一步瞭解可用的播放控制項意圖,請參閱 MediaControlIntent 類別。

MediaRouteProviderDescriptor

使用 IntentFilter 物件定義媒體路徑的功能後,您便可建立描述元物件,以便發布至 Android 媒體路由器架構。這個描述元物件包含媒體路徑功能的具體細節,可讓其他應用程式判斷如何與媒體路徑互動。

下列程式碼範例說明如何將先前建立的意圖篩選器新增至 MediaRouteProviderDescriptor,並設定供媒體路由器架構使用的描述元:

Kotlin

class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

    init {
        publishRoutes()
    }

    private fun publishRoutes() {
        val resources = context.resources
        val routeName: String = resources.getString(R.string.variable_volume_basic_route_name)
        val routeDescription: String = resources.getString(R.string.sample_route_description)
        // Create a route descriptor using previously created IntentFilters
        val routeDescriptor: MediaRouteDescriptor =
                MediaRouteDescriptor.Builder(VARIABLE_VOLUME_BASIC_ROUTE_ID, routeName)
                        .setDescription(routeDescription)
                        .addControlFilters(CONTROL_FILTERS_BASIC)
                        .setPlaybackStream(AudioManager.STREAM_MUSIC)
                        .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
                        .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
                        .setVolumeMax(VOLUME_MAX)
                        .setVolume(mVolume)
                        .build()
        // Add the route descriptor to the provider descriptor
        val providerDescriptor: MediaRouteProviderDescriptor =
                MediaRouteProviderDescriptor.Builder()
                        .addRoute(routeDescriptor)
                        .build()

        // Publish the descriptor to the framework
        descriptor = providerDescriptor
    }
    ...
}

Java

public SampleMediaRouteProvider(Context context) {
    super(context);
    publishRoutes();
}

private void publishRoutes() {
    Resources r = getContext().getResources();
    // Create a route descriptor using previously created IntentFilters
    MediaRouteDescriptor routeDescriptor = new MediaRouteDescriptor.Builder(
            VARIABLE_VOLUME_BASIC_ROUTE_ID,
            r.getString(R.string.variable_volume_basic_route_name))
            .setDescription(r.getString(R.string.sample_route_description))
            .addControlFilters(CONTROL_FILTERS_BASIC)
            .setPlaybackStream(AudioManager.STREAM_MUSIC)
            .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
            .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
            .setVolumeMax(VOLUME_MAX)
            .setVolume(mVolume)
            .build();
    // Add the route descriptor to the provider descriptor
    MediaRouteProviderDescriptor providerDescriptor =
            new MediaRouteProviderDescriptor.Builder()
            .addRoute(routeDescriptor)
            .build();

    // Publish the descriptor to the framework
    setDescriptor(providerDescriptor);
}

如要進一步瞭解可用的描述元設定,請參閱 MediaRouteDescriptorMediaRouteProviderDescriptor 的參考說明文件。

控制路徑

當應用程式連線至媒體路徑供應商時,供應商會透過媒體路由器架構,接收由其他應用程式傳送至您的路徑的播放指令。如要處理這些要求,您必須提供 MediaRouteProvider.RouteController 類別的實作,該類別會處理指令並處理與接收器裝置的實際通訊。

媒體路由器架構會呼叫路徑提供者的 onCreateRouteController() 方法,取得這個類別的執行個體,然後將要求轉送至該類別。以下是 MediaRouteProvider.RouteController 類別的主要方法,您必須為媒體路徑供應器實作這些方法:

  • onSelect() - 應用程式選擇播放路徑時呼叫。使用此方法執行媒體播放開始前,可能需要的任何準備工作。
  • onControlRequest():將特定播放指令傳送至接收裝置。
  • onSetVolume():向接收裝置傳送要求,將播放音量設為特定值。
  • onUpdateVolume():向接收裝置傳送要求,以指定金額修改播放音量。
  • onUnselect():在應用程式取消選取路徑時呼叫。
  • onRelease():在架構不再需要路徑時呼叫,可釋放資源。

所有播放控制要求 (音量變更除外) 都會導向 onControlRequest() 方法。這個方法的實作必須剖析控制要求,並適當回應。這個方法的實作範例能夠處理遠端播放媒體路徑的指令:

Kotlin

private class SampleRouteController : MediaRouteProvider.RouteController() {
    ...

    override fun onControlRequest(
            intent: Intent,
            callback: MediaRouter.ControlRequestCallback?
    ): Boolean {
        return if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
            val action = intent.action
            when (action) {
                MediaControlIntent.ACTION_PLAY -> handlePlay(intent, callback)
                MediaControlIntent.ACTION_ENQUEUE -> handleEnqueue(intent, callback)
                MediaControlIntent.ACTION_REMOVE -> handleRemove(intent, callback)
                MediaControlIntent.ACTION_SEEK -> handleSeek(intent, callback)
                MediaControlIntent.ACTION_GET_STATUS -> handleGetStatus(intent, callback)
                MediaControlIntent.ACTION_PAUSE -> handlePause(intent, callback)
                MediaControlIntent.ACTION_RESUME -> handleResume(intent, callback)
                MediaControlIntent.ACTION_STOP -> handleStop(intent, callback)
                MediaControlIntent.ACTION_START_SESSION -> handleStartSession(intent, callback)
                MediaControlIntent.ACTION_GET_SESSION_STATUS ->
                    handleGetSessionStatus(intent, callback)
                MediaControlIntent.ACTION_END_SESSION -> handleEndSession(intent, callback)
                else -> false
            }.also {
                Log.d(TAG, sessionManager.toString())
            }
        } else {
            false
        }
    }
    ...
}

Java

private final class SampleRouteController extends
        MediaRouteProvider.RouteController {
    ...

    @Override
    public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {

        String action = intent.getAction();

        if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
            boolean success = false;
            if (action.equals(MediaControlIntent.ACTION_PLAY)) {
                success = handlePlay(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_ENQUEUE)) {
                success = handleEnqueue(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_REMOVE)) {
                success = handleRemove(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_SEEK)) {
                success = handleSeek(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_GET_STATUS)) {
                success = handleGetStatus(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_PAUSE)) {
                success = handlePause(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_RESUME)) {
                success = handleResume(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_STOP)) {
                success = handleStop(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_START_SESSION)) {
                success = handleStartSession(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_GET_SESSION_STATUS)) {
                success = handleGetSessionStatus(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_END_SESSION)) {
                success = handleEndSession(intent, callback);
            }

            Log.d(TAG, sessionManager.toString());
            return success;
        }
        return false;
    }
    ...
}

請務必瞭解 MediaRouteProvider.RouteController 類別的用途是做為 API 給媒體播放設備的包裝函式。此類別中的方法實作方式完全取決於接收裝置提供的程式輔助介面。

程式碼範例

MediaRouter 範例顯示如何建立自訂媒體路徑提供者。