Фреймворк Android Media Router позволяет производителям включать воспроизведение на своих устройствах через стандартизированный интерфейс, называемый MediaRouteProvider.
Поставщик маршрутов определяет общий интерфейс для воспроизведения медиаконтента на принимающем устройстве, что позволяет воспроизводить медиаконтент на вашем оборудовании из любого приложения для Android, поддерживающего медиамаршруты.
В этом руководстве рассказывается, как создать поставщика маршрутов мультимедиа для устройства-приемника и сделать его доступным для других приложений воспроизведения мультимедиа, работающих на Android. Чтобы использовать этот API, необходимо знать основные классы: MediaRouteProvider, MediaRouteProviderDescriptor и RouteController.
Обзор
Фреймворк Android Media Router позволяет разработчикам мультимедийных приложений и производителям устройств воспроизведения мультимедиа подключаться через общий API и общий пользовательский интерфейс. Разработчики приложений, которые реализуют интерфейс MediaRouter, могут подключаться к фреймворку и воспроизводить контент на устройствах, которые участвуют в работе фреймворка Media Router. Производители устройств для воспроизведения медиаконтента могут участвовать в этой платформе, публикуя MediaRouteProvider, который позволяет другим приложениям подключаться к устройствам-получателям и воспроизводить на них медиаконтент. На рисунке 1 показано, как приложение подключается к принимающему устройству через фреймворк Media Router.
Рисунок 1. Обзор того, как классы поставщиков маршрутов мультимедиа обеспечивают связь между мультимедийным приложением и устройством-получателем.
При создании поставщика маршрутов медиа для устройства-получателя он выполняет следующие функции:
- Опишите и опубликуйте возможности устройства-получателя, чтобы другие приложения могли обнаружить его и использовать его функции воспроизведения.
- Оберните программный интерфейс устройства-получателя и его механизмы передачи данных, чтобы сделать устройство совместимым с фреймворком маршрутизатора медиаконтента.
Распределение поставщиков маршрутов
Поставщик маршрутов медиаданных распространяется как часть приложения Android. Чтобы сделать его доступным для других приложений, расширьте класс MediaRouteProviderService или оберните реализацию MediaRouteProvider собственным сервисом и объявите фильтр интентов для поставщика маршрутов медиаданных. Эти шаги позволяют другим приложениям обнаруживать и использовать ваш медиамаршрут.
Примечание. Приложение, содержащее поставщика маршрутов мультимедиа, также может включать интерфейс MediaRouter для поставщика маршрутов, но это необязательно.
Библиотека поддержки MediaRouter
API маршрутизатора медиа определены в библиотеке AndroidX MediaRouter. Ее необходимо добавить в проект разработки приложения. Подробнее о том, как добавить библиотеки поддержки в проект…
Внимание! Обязательно используйте реализацию фреймворка Media Router из библиотеки AndroidX.
Не используйте старый пакет android.media.
Создание сервиса поставщика
Чтобы другие приложения могли использовать ваш маршрут, фреймворк Media Router должен иметь возможность обнаруживать поставщика маршрутов мультимедиа и подключаться к нему. Для этого фреймворк ищет приложения, в которых объявлено действие намерения поставщика маршрута медиаданных. Когда другое приложение захочет подключиться к вашему поставщику, фреймворк должен иметь возможность вызвать его и подключиться к нему, поэтому ваш поставщик должен быть инкапсулирован в 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 Media Router предоставляет класс 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); } }
Как указать возможности маршрута
Приложения, подключающиеся к платформе маршрутизатора мультимедиа, могут обнаруживать ваш маршрут мультимедиа через объявления манифеста вашего приложения, но им также необходимо знать возможности предоставляемых вами маршрутов мультимедиа. Маршруты медиа могут быть разных типов и иметь разные функции, и другие приложения должны иметь возможность обнаруживать эти детали, чтобы определить, совместимы ли они с вашим маршрутом.
Фреймворк Media Router позволяет определять и публиковать возможности маршрута мультимедиа с помощью объектов IntentFilter, MediaRouteDescriptor и MediaRouteProviderDescriptor. В этом разделе объясняется, как использовать эти классы для публикации сведений о маршруте медиа для других приложений.
Категории маршрутов
В рамках программного описания поставщика маршрутов мультимедиа вы должны указать, поддерживает ли ваш поставщик удаленное воспроизведение, вторичный вывод или и то, и другое. Ниже перечислены категории маршрутов, предоставляемые фреймворком Media Router.
CATEGORY_LIVE_AUDIO— Вывод аудио на дополнительное устройство вывода, например беспроводную музыкальную систему.CATEGORY_LIVE_VIDEO— Вывод видео на дополнительное устройство вывода, например беспроводной монитор.CATEGORY_REMOTE_PLAYBACK– воспроизведение видео или аудио на отдельном устройстве, которое обрабатывает получение, декодирование и воспроизведение медиаконтента, например на устройствах Chromecast.
Чтобы включить эти настройки в описание маршрута медиа, вставьте их в объект 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); } } }
Элементы управления воспроизведением
Поставщик маршрутов мультимедиа, который предлагает удаленное воспроизведение, должен указать типы поддерживаемых элементов управления мультимедиа. Ниже перечислены основные типы управления, которые могут предоставлять маршруты медиа.
- Элементы управления воспроизведением, такие как воспроизведение, пауза, перемотка назад и вперед.
- Функции очереди, которые позволяют приложению-отправителю добавлять и удалять элементы из плейлиста, поддерживаемого принимающим устройством.
- Функции сеанса, которые предотвращают конфликты между приложениями-отправителями. Устройство-получатель предоставляет запрашивающему приложению идентификатор сеанса, а затем проверяет его при каждом последующем запросе на управление воспроизведением.
В следующем примере кода показано, как создать фильтр интентов для поддержки основных элементов управления воспроизведением на маршруте медиаданных:
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 и задать дескриптор для использования фреймворком Media Router:
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); }
Дополнительную информацию о доступных настройках дескрипторов можно найти в справочной документации по MediaRouteDescriptor и MediaRouteProviderDescriptor.
Управление маршрутами
Когда приложение подключается к вашему поставщику маршрутов мультимедиа, поставщик получает команды воспроизведения, отправленные на ваш маршрут другими приложениями через фреймворк маршрутизатора мультимедиа. Чтобы обрабатывать эти запросы, необходимо реализовать класс 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 показывает, как создать собственный поставщик маршрутов медиа.