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