Обзор медиамаршрутизатора

Чтобы использовать платформу MediaRouter в своем приложении, вы должны получить экземпляр объекта MediaRouter и присоединить объект MediaRouter.Callback для прослушивания событий маршрутизации. Содержимое, отправленное по медиа-маршруту, проходит через связанный с маршрутом MediaRouteProvider (за исключением некоторых особых случаев, таких как устройство вывода Bluetooth). На рис. 1 представлено общее представление классов, используемых для маршрутизации контента между устройствами.

Рисунок 1. Обзор ключевых классов медиамаршрутизаторов, используемых приложениями.

Примечание. Если вы хотите, чтобы ваше приложение поддерживало устройства Google Cast , вам следует использовать Cast SDK и создать свое приложение в качестве отправителя Cast. Следуйте инструкциям в документации по Cast вместо прямого использования платформы MediaRouter.

Кнопка маршрута мультимедиа

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

Рисунок 2. Кнопка маршрута мультимедиа на панели действий.

Когда пользователь нажимает кнопку медиа-маршрута, доступные медиа-маршруты появляются в списке, как показано на рисунке 3.

Рисунок 3. Список доступных медиамаршрутов, отображаемый после нажатия кнопки медиамаршрута.

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

  1. Используйте AppCompatActivity
  2. Определить пункт меню кнопки маршрута мультимедиа
  3. Создайте MediaRouteSelector.
  4. Добавьте кнопку маршрутизации мультимедиа на панель действий.
  5. Создавайте методы MediaRouter.Callback и управляйте ими в жизненном цикле вашей деятельности.

В этом разделе описаны первые четыре шага. В следующем разделе описаны методы обратного вызова.

Используйте AppCompatActivity

Когда вы используете платформу медиамаршрутизатора в действии, вам следует расширить действие из AppCompatActivity и импортировать пакет androidx.appcompat.app . Вы должны добавить библиотеки поддержки androidx.appcompat:appcompat и androidx.mediarouter:mediarouter в свой проект разработки приложения. Дополнительные сведения о добавлении вспомогательных библиотек в проект см. в разделе Начало работы с Android Jetpack .

Внимание: обязательно используйте реализацию платформы медиамаршрутизатора на базе androidx . Не используйте старый пакет android.media .

Создайте XML-файл, который определяет пункт меню для кнопки маршрутизации мультимедиа. Действием элемента должен быть класс MediaRouteActionProvider . Вот пример файла:

// myMediaRouteButtonMenuItem.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      >

    <item android:id="@+id/media_route_menu_item"
        android:title="@string/media_route_menu_title"
        app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
        app:showAsAction="always"
    />
</menu>

Создайте MediaRouteSelector.

Маршруты, отображаемые в меню кнопки маршрута мультимедиа, определяются MediaRouteSelector . Расширьте свою активность из AppCompatActivity и создайте селектор при создании активности, вызывая MediaRouteSelector.Builder из метода onCreate(), как показано в следующем примере кода. Обратите внимание, что селектор сохраняется в переменной класса, а допустимые типы маршрутов указываются путем добавления объектов MediaControlIntent :

Котлин

class MediaRouterPlaybackActivity : AppCompatActivity() {

    private var mSelector: MediaRouteSelector? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Create a route selector for the type of routes your app supports.
        mSelector = MediaRouteSelector.Builder()
                // These are the framework-supported intents
                .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                .build()
    }
}

Ява

public class MediaRouterPlaybackActivity extends AppCompatActivity {
    private MediaRouteSelector mSelector;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Create a route selector for the type of routes your app supports.
        mSelector = new MediaRouteSelector.Builder()
                // These are the framework-supported intents
                .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                .build();
    }
}

Для большинства приложений единственным необходимым типом маршрута является CATEGORY_REMOTE_PLAYBACK . Этот тип маршрута рассматривает устройство, на котором работает ваше приложение, как удаленное управление. Подключенное приемное устройство выполняет поиск, декодирование и воспроизведение всех данных контента. Именно так работают приложения, поддерживающие Google Cast , например Chromecast .

Некоторые производители поддерживают специальную опцию маршрутизации, называемую «вторичный выход». Благодаря такой маршрутизации ваше мультимедийное приложение извлекает, визуализирует и передает потоковое видео или музыку непосредственно на экран и/или динамики выбранного удаленного приемного устройства. Используйте дополнительный выход для отправки контента на беспроводные музыкальные системы или видеодисплеи. Чтобы включить обнаружение и выбор этих устройств, вам необходимо добавить категории управления CATEGORY_LIVE_AUDIO или CATEGORY_LIVE_VIDEO в MediaRouteSelector. Вам также необходимо создать и обработать собственный диалог Presentation .

Добавьте кнопку маршрутизации мультимедиа на панель действий.

Определив меню маршрута мультимедиа и MediaRouteSelector, вы можете добавить кнопку маршрута мультимедиа к действию. Переопределите метод onCreateOptionsMenu() для каждого вашего действия, чтобы добавить меню параметров.

Котлин

override fun onCreateOptionsMenu(menu: Menu): Boolean {
    super.onCreateOptionsMenu(menu)

    // Inflate the menu and configure the media router action provider.
    menuInflater.inflate(R.menu.sample_media_router_menu, menu)

    // Attach the MediaRouteSelector to the menu item
    val mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item)
    val mediaRouteActionProvider =
            MenuItemCompat.getActionProvider(mediaRouteMenuItem) as MediaRouteActionProvider

    // Attach the MediaRouteSelector that you built in onCreate()
    selector?.also(mediaRouteActionProvider::setRouteSelector)

    // Return true to show the menu.
    return true
}

Ява

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);

    // Inflate the menu and configure the media router action provider.
    getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);

    // Attach the MediaRouteSelector to the menu item
    MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
    MediaRouteActionProvider mediaRouteActionProvider =
            (MediaRouteActionProvider)MenuItemCompat.getActionProvider(
            mediaRouteMenuItem);
    // Attach the MediaRouteSelector that you built in onCreate()
    mediaRouteActionProvider.setRouteSelector(selector);

    // Return true to show the menu.
    return true;
}

Дополнительные сведения о реализации панели действий в вашем приложении см. в руководстве разработчика панели действий .

Вы также можете добавить кнопку маршрутизации мультимедиа в качестве MediaRouteButton в любом представлении. Вы должны прикрепить MediaRouteSelector к кнопке с помощью метода setRouteSelector() . Рекомендации по включению кнопки маршрутизации мультимедиа в ваше приложение см. в контрольном списке Google Cast Design.

Обратные вызовы MediaRouter

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

В обратном вызове есть несколько методов, которые можно переопределить для получения информации о событиях маршрутизации. Как минимум, ваша реализация класса MediaRouter.Callback должна переопределять onRouteSelected() и onRouteUnselected() .

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

  • Когда действие создано ( onCreate(Bundle) ), захватите указатель на MediaRouter и удерживайте его на протяжении всего времени существования приложения.
  • Прикрепите обратные вызовы к MediaRouter, когда активность станет видимой ( onStart() ), и отсоедините их, когда она скрыта ( onStop() ).

В следующем примере кода показано, как создать и сохранить объект обратного вызова, как получить экземпляр MediaRouter и как управлять обратными вызовами. Обратите внимание на использование флага CALLBACK_FLAG_REQUEST_DISCOVERY при присоединении обратных вызовов в onStart() . Это позволит вашему MediaRouteSelector обновить список доступных маршрутов кнопки медиа-маршрута.

Котлин

class MediaRouterPlaybackActivity : AppCompatActivity() {

    private var mediaRouter: MediaRouter? = null
    private var mSelector: MediaRouteSelector? = null

    // Variables to hold the currently selected route and its playback client
    private var mRoute: MediaRouter.RouteInfo? = null
    private var remotePlaybackClient: RemotePlaybackClient? = null

    // Define the Callback object and its methods, save the object in a class variable
    private val mediaRouterCallback = object : MediaRouter.Callback() {

        override fun onRouteSelected(router: MediaRouter, route: MediaRouter.RouteInfo) {
            Log.d(TAG, "onRouteSelected: route=$route")
            if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
                // Stop local playback (if necessary)
                // ...

                // Save the new route
                mRoute = route

                // Attach a new playback client
                remotePlaybackClient =
                    RemotePlaybackClient(this@MediaRouterPlaybackActivity, mRoute)

                // Start remote playback (if necessary)
                // ...
            }
        }

        override fun onRouteUnselected(
                router: MediaRouter,
                route: MediaRouter.RouteInfo,
                reason: Int
        ) {
            Log.d(TAG, "onRouteUnselected: route=$route")
            if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {

                // Changed route: tear down previous client
                mRoute?.also {
                    remotePlaybackClient?.release()
                    remotePlaybackClient = null
                }

                // Save the new route
                mRoute = route

                when (reason) {
                    MediaRouter.UNSELECT_REASON_ROUTE_CHANGED -> {
                        // Resume local playback (if necessary)
                        // ...
                    }
                }
            }
        }
    }


    // Retain a pointer to the MediaRouter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Get the media router service.
        mediaRouter = MediaRouter.getInstance(this)
        ...
    }

    // Use this callback to run your MediaRouteSelector to generate the
    // list of available media routes
    override fun onStart() {
        mSelector?.also { selector ->
            mediaRouter?.addCallback(selector, mediaRouterCallback,
                    MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY)
        }
        super.onStart()
    }

    // Remove the selector on stop to tell the media router that it no longer
    // needs to discover routes for your app.
    override fun onStop() {
        mediaRouter?.removeCallback(mediaRouterCallback)
        super.onStop()
    }
    ...
}

Ява

public class MediaRouterPlaybackActivity extends AppCompatActivity {
    private MediaRouter mediaRouter;
    private MediaRouteSelector mSelector;

    // Variables to hold the currently selected route and its playback client
    private MediaRouter.RouteInfo mRoute;
    private RemotePlaybackClient remotePlaybackClient;

    // Define the Callback object and its methods, save the object in a class variable
    private final MediaRouter.Callback mediaRouterCallback =
            new MediaRouter.Callback() {

        @Override
        public void onRouteSelected(MediaRouter router, RouteInfo route) {
            Log.d(TAG, "onRouteSelected: route=" + route);

            if (route.supportsControlCategory(
                MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)){
                // Stop local playback (if necessary)
                // ...

                // Save the new route
                mRoute = route;

                // Attach a new playback client
                remotePlaybackClient = new RemotePlaybackClient(this, mRoute);

                // Start remote playback (if necessary)
                // ...
            }
        }

        @Override
        public void onRouteUnselected(MediaRouter router, RouteInfo route, int reason) {
            Log.d(TAG, "onRouteUnselected: route=" + route);

            if (route.supportsControlCategory(
                MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)){

                // Changed route: tear down previous client
                if (mRoute != null && remotePlaybackClient != null) {
                    remotePlaybackClient.release();
                    remotePlaybackClient = null;
                }

                // Save the new route
                mRoute = route;

                if (reason != MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
                    // Resume local playback  (if necessary)
                    // ...
                }
            }
        }
    }


    // Retain a pointer to the MediaRouter
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Get the media router service.
        mediaRouter = MediaRouter.getInstance(this);
        ...
    }

    // Use this callback to run your MediaRouteSelector to generate the list of available media routes
    @Override
    public void onStart() {
        mediaRouter.addCallback(mSelector, mediaRouterCallback,
                MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
        super.onStart();
    }

    // Remove the selector on stop to tell the media router that it no longer
    // needs to discover routes for your app.
    @Override
    public void onStop() {
        mediaRouter.removeCallback(mediaRouterCallback);
        super.onStop();
    }
    ...
}

Платформа медиамаршрутизатора также предоставляет класс MediaRouteDiscoveryFragment , который обеспечивает добавление и удаление обратного вызова для действия.

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

Управление маршрутом удаленного воспроизведения

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

Класс RemotePlaybackClient предоставляет дополнительные методы для управления воспроизведением контента. Вот несколько ключевых методов воспроизведения из класса RemotePlaybackClient :

  • play() — Воспроизведение определенного медиафайла, указанного Uri .
  • pause() — приостановить воспроизводимую в данный момент мультимедийную дорожку.
  • resume() — Продолжить воспроизведение текущего трека после команды паузы.
  • seek() — Переход к определенной позиции на текущей дорожке.
  • release() — Разорвите соединение вашего приложения с удаленным устройством воспроизведения.

Вы можете использовать эти методы для прикрепления действий к элементам управления воспроизведением, которые вы предоставляете в своем приложении. Большинство этих методов также позволяют вам включать объект обратного вызова, чтобы вы могли отслеживать ход выполнения задачи воспроизведения или запроса управления.

Класс RemotePlaybackClient также поддерживает постановку в очередь нескольких мультимедийных элементов для воспроизведения и управление очередью мультимедиа.

Пример кода

Примеры Android BasicMediaRouter и MediaRouter дополнительно демонстрируют использование API MediaRouter.