Descripción general de MediaRouteProvider

El framework 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 contenido multimedia en tu equipo desde cualquier aplicación para Android que admita rutas de contenido multimedia.

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

Descripción general

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

Figura 1: Descripción general de cómo las clases de proveedores de rutas de contenido multimedia proporcionan comunicación entre una app de música y un dispositivo receptor

Cuando compilas un proveedor de rutas de contenido multimedia para el dispositivo receptor, el proveedor cumple los siguientes propósitos:

  • Describe y publica las capacidades del dispositivo receptor para que otras apps puedan descubrirlo y usar sus funciones de reproducción.
  • Une la interfaz de programación del dispositivo receptor y sus mecanismos de transporte de comunicación para que el dispositivo sea compatible con el framework 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. Tu proveedor de rutas puede estar disponible para otras apps extendiendo MediaRouteProviderService o uniendo tu implementación de MediaRouteProvider con tu propio servicio y declarando un filtro de intents para el proveedor de rutas de contenido multimedia. Estos pasos permiten que otras apps descubran 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 MediaRouter para el proveedor de rutas, pero esto no es obligatorio.

Biblioteca de compatibilidad MediaRouter

Las APIs del router de contenido multimedia se definen en la biblioteca MediaRouter de AndroidX. 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 Cómo configurar bibliotecas de compatibilidad.

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.

Cómo crear un servicio de proveedor

El framework del router de contenido multimedia debe poder descubrir tu proveedor de rutas de contenido multimedia y conectarse a él para permitir que otras aplicaciones usen tu ruta. Para ello, el framework del router de contenido multimedia busca apps que declaren una acción de intent del proveedor de rutas de contenido multimedia. Cuando otra app quiere conectarse a tu proveedor, el framework debe poder invocarlo y conectarse a él, por lo que el proveedor 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 use:

<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 framework 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 al marco de trabajo del router de contenido multimedia pueden descubrir tu 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 funciones, y otras apps deben poder descubrir estos detalles para determinar si son compatibles con tu ruta.

El framework 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 rutas de contenido multimedia, debes especificar si este admite la reproducción remota, la salida secundaria o ambas. Estas son las categorías de ruta proporcionadas por el framework del router de contenido multimedia:

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

Para incluir estos parámetros de configuración en una descripción de tu ruta de contenido multimedia, insértalos en un objeto IntentFilter, que luego agregarás 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 son compatibles con tu proveedor de rutas de contenido multimedia. En la siguiente sección, se describe cómo especificar esta configuración 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. Debes especificar esta configuración con la clase IntentFilter y los métodos addDataScheme() y addDataType() de ese objeto. En el siguiente fragmento de código, se muestra cómo definir un filtro de intents para admitir la reproducción remota de videos 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 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 fila, que permiten que la app de envío agregue y quite 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 aplicaciones 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 configurar 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 una app se conecta a tu proveedor de rutas de contenido multimedia, el proveedor recibe comandos de reproducción a través del framework del router de contenido multimedia que otras apps envían a tu ruta. Para controlar estas solicitudes, debes proporcionar una implementación de una clase MediaRouteProvider.RouteController, que procesa los comandos y maneja la comunicación real con tu dispositivo receptor.

El framework 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. Puedes usar este método para los trabajos de preparación necesarios antes de que comience la reproducción de contenido multimedia.
  • 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 específica.
  • onUnselect(): Se llama cuando una app anula la selección de una ruta.
  • onRelease(): Se llama cuando el framework 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 responder a ellas de forma adecuada. A continuación, se muestra una implementación de ejemplo de este método que procesa comandos para una ruta de medios 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á diseñada para actuar como un wrapper para la API en 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 ejemplo

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