Платформа медиамаршрутизатора Android позволяет производителям включать воспроизведение на своих устройствах через стандартизированный интерфейс, называемый MediaRouteProvider
. Поставщик маршрутов определяет общий интерфейс для воспроизведения мультимедиа на устройстве-приемнике, что позволяет воспроизводить мультимедиа на вашем оборудовании из любого приложения Android, поддерживающего медиамаршруты.
В этом руководстве обсуждается, как создать поставщика маршрутов мультимедиа для устройства-приемника и сделать его доступным для других приложений воспроизведения мультимедиа, работающих на Android. Чтобы использовать этот API, вы должны быть знакомы с ключевыми классами MediaRouteProvider
, MediaRouteProviderDescriptor
и RouteController
.
Обзор
Платформа медиамаршрутизатора Android позволяет разработчикам мультимедийных приложений и производителям устройств воспроизведения мультимедиа подключаться через общий API и общий пользовательский интерфейс. Разработчики приложений, реализующие интерфейс MediaRouter
, могут затем подключиться к платформе и воспроизводить контент на устройствах, которые участвуют в платформе медиамаршрутизатора. Производители устройств воспроизведения мультимедиа могут участвовать в этой платформе, опубликовав MediaRouteProvider
, который позволяет другим приложениям подключаться и воспроизводить мультимедиа на устройствах-приемниках. На рис. 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 показано, как создать собственный поставщик маршрутов мультимедиа.