Descripción general de MediaRouteProvider

El marco de trabajo del router de contenido multimedia de Android permite a los fabricantes habilitar la reproducción en sus dispositivos a través de una interfaz estandarizada llamada MediaRouteProvider. Un proveedor de rutas define una interfaz común para reproducir contenido multimedia en un dispositivo receptor, lo que permite reproducir este tipo de contenido desde cualquier app para Android que admita rutas de contenido multimedia.

En esta guía, se describe cómo crear un proveedor de ruta de contenido multimedia para un dispositivo receptor y ponerlo a disposición de otras apps de reproducción de contenido multimedia que se ejecutan en Android. Para usar esta API, debes conocer las clases clave MediaRouteProvider, MediaRouteProviderDescriptor y RouteController.

Descripción general

El marco de trabajo del router de contenido multimedia de Android permite a los desarrolladores de apps de contenido multimedia y fabricantes de dispositivos de reproducción multimedia conectarse a través de una API y una interfaz de usuario en común. Los desarrolladores de apps que implementan una interfaz de MediaRouter pueden conectarse al marco y reproducir contenido en dispositivos que son parte del marco de trabajo del router de contenido multimedia. Los fabricantes de dispositivos de reproducción multimedia pueden participar en el marco de trabajo publicando un MediaRouteProvider que permita que se conecten otras apps y reproduzcan contenido multimedia en los dispositivos receptores. En la figura 1, se muestra cómo se conecta una app a un dispositivo receptor a través del marco de trabajo del router de contenido multimedia.

Figura 1: Descripción general del modo en que las clases de proveedor de rutas de contenido multimedia proporcionan comunicación entre una app de contenido multimedia y un dispositivo receptor

Cuando compilas un proveedor de rutas de contenido multimedia para tu dispositivo receptor, el proveedor cumple los siguientes objetivos:

  • Describir y publicar las capacidades del dispositivo receptor para que otras apps puedan detectarlo y usar sus funciones de reproducción.
  • Unir la interfaz de programación del dispositivo receptor y sus mecanismos de transporte de comunicación para que el dispositivo sea compatible con el marco de trabajo del router de contenido multimedia.

Distribución de proveedores de rutas

Se distribuye un proveedor de rutas de contenido multimedia como parte de una app para Android. Puedes hacer que este proveedor esté disponible para otras apps si extiendes MediaRouteProviderService o unes tu implementación de MediaRouteProvider con tu propio servicio y declaras un filtro de intents para el proveedor de rutas de contenido multimedia. Estos pasos permiten que otras apps detecten y usen tu ruta de contenido multimedia.

Nota: La app que contiene el proveedor de rutas de contenido multimedia también puede incluir una interfaz de MediaRouter del proveedor de rutas, pero esto no es obligatorio.

Biblioteca del router de contenido multimedia

Se definen las API del router de contenido multimedia en la biblioteca de compatibilidad v7-mediarouter. Debes agregar esta biblioteca al proyecto de desarrollo de tu app. Para obtener más información sobre cómo agregar bibliotecas de compatibilidad a tu proyecto, consulta Configuración de la biblioteca de compatibilidad.

Precaución: Asegúrate de utilizar la implementación android.support.v7.media del marco de trabajo del router de contenido multimedia. No uses el paquete android.media más antiguo.

Cómo crear un servicio de proveedor

El marco de trabajo del router de contenido multimedia debe poder descubrir y conectarse a tu proveedor de rutas de contenido multimedia para permitir que otras apps usen tu ruta. Para hacer esto, el marco del router de contenido multimedia busca apps que declaran una acción de intent del proveedor de rutas de contenido multimedia. Cuando otra app quiere conectarse a tu proveedor, el marco de trabajo debe poder invocarlo y conectarse a él, por lo que debe estar encapsulado en un Service.

En el siguiente código de ejemplo, se muestra la declaración de un servicio de proveedor de rutas de contenido multimedia y el filtro de intents en un manifiesto, que permite que el marco de trabajo del router de contenido multimedia lo detecte y lo utilice:

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

Este ejemplo de manifiesto declara un servicio que une las clases de proveedores de rutas de contenido multimedia reales. El marco de trabajo del router de contenido multimedia de Android proporciona la clase MediaRouteProviderService para usar como wrapper de servicio para proveedores de rutas de contenido multimedia. En el siguiente código de ejemplo, se muestra cómo usar esta clase de wrapper:

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

Cómo especificar las capacidades de las rutas

Las apps que se conectan con el router de contenido multimedia pueden detectar la ruta de contenido multimedia a través de las declaraciones del manifiesto de la app, pero también deben conocer las capacidades de las rutas de contenido multimedia que proporcionas. Las rutas de contenido multimedia pueden ser de diferentes tipos y tener diferentes características, y otras apps deben poder detectar estos detalles para determinar si son compatibles con tu ruta.

El marco de trabajo del router de contenido multimedia te permite definir y publicar las capacidades de tu ruta de contenido multimedia a través de objetos IntentFilter, objetos MediaRouteDescriptor y un MediaRouteProviderDescriptor. En esta sección, se explica cómo usar estas clases para publicar los detalles de tu ruta de contenido multimedia para otras apps.

Categorías de rutas

Como parte de la descripción programática de tu proveedor de ruta de contenido multimedia, debes especificar si este admite la reproducción remota, la salida secundaria o ambas. Las siguientes son las categorías de rutas proporcionadas por el marco de trabajo del router de contenido multimedia:

  • CATEGORY_LIVE_AUDIO: Salida de audio para un dispositivo de salida secundario, como un sistema de música inalámbrico.
  • CATEGORY_LIVE_VIDEO: La salida de video para un dispositivo de salida secundario, como dispositivos de pantalla inalámbrica.
  • CATEGORY_REMOTE_PLAYBACK: Reproduce video o audio en un dispositivo separado que controla la recuperación, decodificación y reproducción de contenido multimedia, como dispositivos Chromecast.

Para incluir estas configuraciones en una descripción de tu ruta de contenido multimedia, insértalas en un objeto IntentFilter, que luego debes agregar a un objeto 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);
        }
    }
    

Si especificas el intent CATEGORY_REMOTE_PLAYBACK, también debes definir qué tipos de contenido multimedia y controles de reproducción admite tu proveedor de rutas de contenido multimedia. En la siguiente sección, se describe cómo especificar estas configuraciones para tu dispositivo.

Tipos de contenido multimedia y protocolos

Un proveedor de rutas de contenido multimedia para un dispositivo de reproducción remoto debe especificar los tipos de contenido multimedia y los protocolos de transferencia compatibles. Especifica estas configuraciones con la clase IntentFilter y los métodos addDataScheme() y addDataType() de ese objeto. En el siguiente fragmento de código, se demuestra cómo definir un filtro de intents para admitir la reproducción remota de video mediante http, https y el protocolo de transmisión en tiempo real (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);
            }
        }
    }
    

Controles de reproducción

Un proveedor de rutas de contenido multimedia que ofrece reproducción remota debe especificar los tipos de controles de contenido multimedia que admite. A continuación, se enumeran los tipos generales de control que las rutas de contenido multimedia pueden proporcionar:

  • Controles de reproducción, como reproducción, pausa, retroceso y avance rápido.
  • Funciones de cola, que permiten a la app de envío agregar y quitar elementos de una lista de reproducción mantenida por el dispositivo receptor.
  • Funciones de sesión, que evitan que las apps de envío interfieran entre sí haciendo que el dispositivo receptor proporcione un ID de sesión a la app solicitante y, luego, verifique ese ID en cada solicitud de control de reproducción posterior.

En el siguiente ejemplo de código, se muestra cómo construir un filtro de intents para admitir controles básicos de reproducción de rutas de contenido multimedia:

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

Para obtener más información sobre los intents de control de reproducción disponibles, consulta la clase MediaControlIntent.

MediaRouteProviderDescriptor

Después de definir las capacidades de tu ruta de contenido multimedia con objetos IntentFilter, puedes crear un objeto descriptor para publicar en el marco de trabajo del router de contenido multimedia de Android. Este objeto descriptor contiene los detalles de las capacidades de tu ruta de contenido multimedia para que otras apps puedan determinar cómo interactuar con ella.

En el siguiente código de ejemplo, se muestra cómo agregar los filtros de intents creados previamente a MediaRouteProviderDescriptor y establecer el descriptor para que lo use el marco de trabajo del router de contenido multimedia:

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

Para obtener más información sobre la configuración del descriptor disponible, consulta la documentación de referencia de MediaRouteDescriptor y MediaRouteProviderDescriptor.

Cómo controlar rutas

Cuando se conecta una app a tu proveedor de rutas de contenido multimedia, este recibe comandos de reproducción a través del marco de trabajo del router de contenido multimedia que otras apps envían a tu ruta. Para administrar estas solicitudes, debes proporcionar una implementación de una clase MediaRouteProvider.RouteController, que procesa los comandos y controla la comunicación real con el dispositivo receptor.

El marco de trabajo del router de contenido multimedia llama al método onCreateRouteController() de tu proveedor de rutas para obtener una instancia de esta clase y, luego, enrutar las solicitudes hacia ella. Estos son los métodos clave de la clase MediaRouteProvider.RouteController, que debes implementar para tu proveedor de rutas de contenido multimedia:

  • onSelect(): Se llama cuando una app selecciona tu ruta para reproducción. Usa este método para los trabajos de preparación necesarios antes de que comience la reproducción.
  • onControlRequest(): Envía comandos de reproducción específicos al dispositivo receptor.
  • onSetVolume(): Envía una solicitud al dispositivo receptor para establecer el volumen de reproducción en un valor específico.
  • onUpdateVolume(): Envía una solicitud al dispositivo receptor para modificar el volumen de reproducción en una cantidad especificada.
  • onUnselect(): Se llama cuando una app anula la selección de una ruta.
  • onRelease(): Se llama cuando el marco de trabajo ya no necesita la ruta, lo que le permite liberar sus recursos.

Todas las solicitudes de control de reproducción, excepto los cambios de volumen, se dirigen al método onControlRequest(). La implementación de este método debe analizar las solicitudes de control y responderlas de manera adecuada. A continuación, se incluye un ejemplo de implementación de este método que procesa comandos para una ruta de contenido multimedia de reproducción remota:

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

Es importante comprender que la clase MediaRouteProvider.RouteController está destinada a funcionar como un contenedor para la API de tu equipo de reproducción de contenido multimedia. La implementación de los métodos en esta clase depende por completo de la interfaz programática proporcionada por tu dispositivo receptor.

Código de muestra

En el ejemplo de MediaRouter, se muestra cómo crear un proveedor de rutas de contenido multimedia personalizado.