Обзор MediaRouteProvider

Платформа медиамаршрутизатора Android позволяет производителям включать воспроизведение на своих устройствах через стандартизированный интерфейс, называемый MediaRouteProvider . Поставщик маршрутов определяет общий интерфейс для воспроизведения мультимедиа на устройстве-приемнике, что позволяет воспроизводить мультимедиа на вашем оборудовании из любого приложения Android, поддерживающего медиамаршруты.

В этом руководстве обсуждается, как создать поставщика маршрутов мультимедиа для устройства-приемника и сделать его доступным для других приложений воспроизведения мультимедиа, работающих на Android. Чтобы использовать этот API, вы должны быть знакомы с ключевыми классами MediaRouteProvider , MediaRouteProviderDescriptor и RouteController .

Обзор

Платформа медиамаршрутизатора 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 для использования в качестве оболочки службы для поставщиков медиамаршрутов. В следующем примере кода показано, как использовать этот класс-оболочку:

class SampleMediaRouteProviderService : MediaRouteProviderService() {

   
override fun onCreateMediaRouteProvider(): MediaRouteProvider {
       
return SampleMediaRouteProvider(this)
   
}
}
public class SampleMediaRouteProviderService extends MediaRouteProviderService {

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

Определение возможностей маршрута

Приложения, подключающиеся к платформе медиа-маршрутизатора, могут обнаружить ваш медиа-маршрут через декларации манифеста вашего приложения, но им также необходимо знать возможности предоставляемых вами медиа-маршрутов. Медиа-маршруты могут быть разных типов и иметь разные функции, и другие приложения должны иметь возможность обнаруживать эти детали, чтобы определить, совместимы ли они с вашим маршрутом.

Платформа медиамаршрутизатора позволяет вам определять и публиковать возможности вашего медиамаршрута через объекты IntentFilter , объекты MediaRouteDescriptor и MediaRouteProviderDescriptor . В этом разделе объясняется, как использовать эти классы для публикации сведений о вашем медиа-маршруте для других приложений.

Категории маршрутов

В программном описании вашего поставщика маршрутов мультимедиа вы должны указать, поддерживает ли ваш поставщик удаленное воспроизведение, вторичный вывод или и то, и другое. Это категории маршрутов, предоставляемые платформой медиамаршрутизатора:

  • CATEGORY_LIVE_AUDIO — вывод звука на вторичное устройство вывода, например музыкальную систему с поддержкой беспроводной связи.
  • CATEGORY_LIVE_VIDEO — вывод видео на вторичное устройство вывода, например устройства беспроводного отображения.
  • CATEGORY_REMOTE_PLAYBACK — воспроизведение видео или аудио на отдельном устройстве, которое занимается поиском, декодированием и воспроизведением мультимедиа, например устройствах Chromecast .

Чтобы включить эти настройки в описание вашего медиа-маршрута, вы вставляете их в объект IntentFilter , который позже добавляете в объект MediaRouteDescriptor :

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

   
companion object {
       
private val CONTROL_FILTERS_BASIC: ArrayList<IntentFilter> = IntentFilter().run {
            addCategory
(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
            arrayListOf
(this)
       
}
   
}
}
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):

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

Элементы управления воспроизведением

Поставщик маршрутов мультимедиа, предлагающий удаленное воспроизведение, должен указать типы элементов управления мультимедиа, которые он поддерживает. Ниже приведены общие типы управления, которые могут обеспечивать медиамаршруты:

  • Элементы управления воспроизведением , такие как воспроизведение, пауза, перемотка назад и вперед.
  • Функции организации очередей , которые позволяют приложению-отправителю добавлять и удалять элементы из списка воспроизведения, который поддерживается устройством-получателем.
  • Функции сеанса , которые предотвращают взаимодействие отправляющих приложений друг с другом за счет того, что устройство-получатель предоставляет идентификатор сеанса запрашивающему приложению, а затем проверяет этот идентификатор при каждом последующем запросе на управление воспроизведением.

В следующем примере кода показано, как создать фильтр намерений для поддержки основных элементов управления воспроизведением маршрута мультимедиа:

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

Медиарутепровидердескриптор

После определения возможностей вашего медиа-маршрута с помощью объектов IntentFilter вы можете создать объект дескриптора для публикации в платформе медиа-маршрутизатора Android. Этот объект дескриптора содержит сведения о возможностях вашего медиа-маршрута, чтобы другие приложения могли определить, как взаимодействовать с вашим медиа-маршрутом.

В следующем примере кода показано, как добавить ранее созданные фильтры намерений в MediaRouteProviderDescriptor и установить дескриптор для использования платформой медиамаршрутизатора:

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

Дополнительные сведения о доступных настройках дескриптора см. в справочной документации для MediaRouteDescriptor и MediaRouteProviderDescriptor .

Управление маршрутами

Когда приложение подключается к вашему провайдеру медиа-маршрутизации, поставщик получает команды воспроизведения через структуру медиа-маршрутизатора, отправленные на ваш маршрут другими приложениями. Чтобы обрабатывать эти запросы, вы должны предоставить реализацию класса MediaRouteProvider.RouteController , который обрабатывает команды и обеспечивает фактическую связь с вашим устройством-получателем.

Платформа медиамаршрутизатора вызывает метод onCreateRouteController() вашего провайдера маршрутов, чтобы получить экземпляр этого класса, а затем направляет к нему запросы. Это ключевые методы класса MediaRouteProvider.RouteController , которые вы должны реализовать для своего поставщика медиамаршрутов:

  • onSelect() — вызывается, когда приложение выбирает маршрут для воспроизведения. Этот метод используется для выполнения любых подготовительных работ, которые могут потребоваться перед началом воспроизведения мультимедиа.
  • onControlRequest() — отправляет определенные команды воспроизведения на принимающее устройство.
  • onSetVolume() — отправляет запрос принимающему устройству на установку определенного значения громкости воспроизведения.
  • onUpdateVolume() — отправляет запрос принимающему устройству на изменение громкости воспроизведения на указанную величину.
  • onUnselect() — вызывается, когда приложение отменяет выбор маршрута.
  • onRelease() — вызывается, когда маршрут больше не нужен платформе, что позволяет ей освободить свои ресурсы.

Все запросы на управление воспроизведением, кроме изменения громкости, направляются в метод onControlRequest() . Ваша реализация этого метода должна анализировать запросы управления и отвечать на них соответствующим образом. Вот пример реализации этого метода, который обрабатывает команды для маршрута мультимедиа удаленного воспроизведения:

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
       
}
   
}
   
...
}
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 показано, как создать собственный поставщик маршрутов мультимедиа.