Descripción general de MediaRouter

Para usar el framework de MediaRouter en tu app, debes obtener una instancia del objeto MediaRouter y adjuntar un objeto MediaRouter.Callback para escuchar los eventos de enrutamiento. El contenido enviado a través de una ruta de contenido multimedia pasa por el MediaRouteProvider asociado a la ruta (excepto en algunos casos especiales, como un dispositivo de salida Bluetooth). En la figura 1, se proporciona una vista de alto nivel de las clases utilizadas para enrutar contenido entre dispositivos.

Figura 1: Descripción general de las clases clave de routers de contenido multimedia que utilizan las apps

Nota: Si quieres que tu app sea compatible con dispositivos Google Cast, debes usar el SDK de Cast y compilarla como dispositivo de envío de Cast. Sigue las instrucciones en la documentación de Cast en lugar de usar directamente el framework de MediaRouter.

El botón de ruta de contenido multimedia

Las apps para Android deben usar un botón de ruta de contenido multimedia para controlar el enrutamiento de este tipo de contenido. El framework de MediaRouter proporciona una interfaz estándar para el botón, que ayuda a los usuarios a reconocer y usar el enrutamiento cuando está disponible. Por lo general, el botón de ruta de contenido multimedia se coloca en el lado derecho de la barra de acciones de la app, como se muestra en la Figura 2.

Figura 2: Botón de ruta de contenido multimedia en la barra de acciones.

Cuando el usuario presiona el botón de ruta de contenido multimedia, las rutas disponibles aparecen en una lista, como se muestra en la figura 3.

Figura 3: Una lista de las rutas de contenido multimedia disponibles, que se muestra después de presionar el botón de ruta de contenido multimedia

Sigue estos pasos para crear un botón de ruta de contenido multimedia:

  1. Usa una AppCompatActivity
  2. Define el elemento de menú del botón de la ruta de contenido multimedia.
  3. Crea un MediaRouteSelector.
  4. Agrega el botón de ruta de contenido multimedia a la barra de acciones.
  5. Crea y administra los métodos de MediaRouter.Callback en el ciclo de vida de tu actividad.

En esta sección, se describen los primeros cuatro pasos. En la siguiente sección, se describen los métodos de devolución de llamada.

Usa una AppCompatActivity

Cuando usas el framework del router de contenido multimedia en una actividad, debes extender la actividad desde AppCompatActivity e importar el paquete androidx.appcompat.app. Debes agregar las bibliotecas de compatibilidad androidx.appcompat:appcompat y androidx.mediarouter:mediarouter al proyecto de desarrollo de tu app. Si quieres obtener más información para agregar bibliotecas de compatibilidad a tu proyecto, consulta Cómo comenzar a usar Android Jetpack.

Precaución: Asegúrate de usar la implementación androidx del framework del router de contenido multimedia. No uses el paquete android.media más antiguo.

Crea un archivo xml en el que se defina un elemento de menú para el botón de la ruta de contenido multimedia. La acción del elemento debería ser de la clase MediaRouteActionProvider. A continuación, se muestra un archivo de ejemplo:

// 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>

Crea un MediaRouteSelector

Las rutas que aparecen en el menú del botón de la ruta de contenido multimedia están determinadas por un MediaRouteSelector. Extiende tu actividad desde AppCompatActivity y compila el selector cuando se cree la actividad llamando a MediaRouteSelector.Builder desde el método onCreate(), como se muestra en la siguiente muestra de código. Ten en cuenta que el selector se guarda en una variable de clase y que los tipos de ruta permitidos se especifican agregando objetos MediaControlIntent:

Kotlin

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

Java

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

Para la mayoría de las aplicaciones, el único tipo de ruta necesario es CATEGORY_REMOTE_PLAYBACK. Este tipo de ruta trata el dispositivo que ejecuta tu app como un control remoto. El dispositivo receptor conectado controla toda la recuperación, la decodificación y la reproducción de datos de contenido. Así funcionan las apps compatibles con Google Cast, como Chromecast.

Algunos fabricantes ofrecen compatibilidad con una opción de enrutamiento especial denominada "salida secundaria". Con este enrutamiento, tu app de música recupera, procesa y transmite video o música directamente a la pantalla o las bocinas del dispositivo receptor remoto seleccionado. Usa la salida secundaria para enviar contenido a sistemas de música o pantallas de video inalámbricos. Para habilitar el descubrimiento y la selección de estos dispositivos, debes agregar las categorías de control CATEGORY_LIVE_AUDIO o CATEGORY_LIVE_VIDEO al MediaRouteSelector. Además, debes crear y controlar tu propio diálogo de Presentation.

Agrega el botón de ruta de contenido multimedia a la barra de acciones

Si ya definiste el menú de ruta de contenido multimedia y MediaRouteSelector, puedes agregar el botón de ruta de contenido multimedia a una actividad. Anula el método onCreateOptionsMenu() para cada una de tus actividades si quieres agregar un menú de opciones.

Kotlin

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
}

Java

@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;
}

Para obtener más información sobre la implementación de la barra de acciones en tu app, consulta la guía para desarrolladores sobre la barra de acciones.

También puedes agregar un botón de ruta de contenido multimedia como MediaRouteButton en cualquier vista. Debes adjuntar un MediaRouteSelector al botón usando el método setRouteSelector(). Consulta la Lista de tareas de diseño de Google Cast para obtener lineamientos sobre cómo incorporar el botón de ruta de contenido multimedia en tu aplicación.

Devoluciones de llamada de MediaRouter

Todas las apps que se ejecutan en el mismo dispositivo comparten una sola instancia de MediaRouter y sus rutas (que el MediaRouteSelector filtra por app). Cada actividad se comunica con el MediaRouter mediante su propia implementación de métodos MediaRouter.Callback. El MediaRouter llama a los métodos de devolución de llamada cada vez que el usuario selecciona, cambia o desconecta una ruta.

Existen varios métodos en la devolución de llamada que puedes anular para recibir información sobre los eventos de enrutamiento. Como mínimo, la implementación de la clase MediaRouter.Callback debe anular a onRouteSelected() y onRouteUnselected().

Como el MediaRouter es un recurso compartido, tu app debe administrar sus devoluciones de llamada de MediaRouter en respuesta a las devoluciones de llamada habituales del ciclo de vida de la actividad:

  • Cuando se crea la actividad (onCreate(Bundle)), toma un puntero al MediaRouter y mantenlo durante todo el ciclo de vida de la app.
  • Adjunta devoluciones de llamada a MediaRouter cuando la actividad se vuelva visible (onStart()) y desconéctalas cuando esté oculta (onStop()).

En la siguiente muestra de código, se indica cómo crear y guardar el objeto de devolución de llamada, cómo obtener una instancia de MediaRouter y cómo administrar devoluciones de llamada. Ten en cuenta el uso de la marca CALLBACK_FLAG_REQUEST_DISCOVERY cuando adjuntes las devoluciones de llamada en onStart(). Esto permite que tu MediaRouteSelector actualice la lista de rutas disponibles del botón de ruta de contenido multimedia.

Kotlin

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

Java

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

El framework del router de contenido multimedia también proporciona una clase MediaRouteDiscoveryFragment, que se encarga de agregar y quitar la devolución de llamada de una actividad.

Nota: Si estás creando una app de reproducción de música y quieres que reproduzca música mientras está en segundo plano, debes compilar un Service para la reproducción y llamar al framework del router de contenido multimedia desde las devoluciones de llamada del ciclo de vida del servicio.

Cómo controlar una ruta de reproducción remota

Cuando seleccionas una ruta de reproducción remota, tu app funciona como un control remoto. El dispositivo del otro extremo de la ruta maneja todas las funciones de recuperación, decodificación y reproducción de datos de contenido. Los controles de la IU de tu app se comunican con el dispositivo receptor mediante un objeto RemotePlaybackClient.

La clase RemotePlaybackClient proporciona métodos adicionales para administrar la reproducción de contenido. A continuación, se incluyen algunos de los métodos de reproducción clave de la clase RemotePlaybackClient:

  • play(): Reproduce un archivo multimedia específico, especificado por un Uri.
  • pause(): Pausa la pista de contenido multimedia que se está reproduciendo.
  • resume(): Continúa reproduciendo la pista actual después de un comando de pausa.
  • seek(): Se desplaza a una posición específica en la pista actual.
  • release(): Elimina la conexión entre la app y el dispositivo de reproducción remota.

Puedes usar estos métodos para adjuntar acciones a los controles de reproducción que proporcionas en tu app. La mayoría de estos métodos también te permiten incluir un objeto de devolución de llamada para poder supervisar el progreso de la tarea de reproducción o la solicitud de control.

La clase RemotePlaybackClient también admite la puesta en cola de varios elementos multimedia para la reproducción y la administración de la cola de contenido multimedia.

Código de muestra

Los ejemplos de Android BasicMediaRouter y MediaRouter demuestran aún más el uso de la API de MediaRouter.