Visão geral do MediaRouteProvider

O framework de roteador de mídia do Android permite que fabricantes ativem a reprodução nos dispositivos por uma interface padronizada denominada MediaRouteProvider. O provedor do roteador define uma interface comum para abrir mídia em um dispositivo receptor. Assim, é possível abrir mídia no seu equipamento a partir de qualquer aplicativo para Android compatível com o roteamento.

Este guia explica como criar um provedor de roteamento de mídia para um dispositivo receptor e disponibilizá-lo para outros aplicativos de abertura de mídia executados no Android. Para usar essa API, familiarize-se com as principais classes MediaRouteProvider, MediaRouteProviderDescriptor e RouteController.

Visão geral

O framework do roteador de mídia do Android permite que desenvolvedores de apps de mídia e fabricantes de dispositivos de mídia se conectem por meio de uma API e interface de usuário comum. Os desenvolvedores de apps que implementam uma interface MediaRouter podem se conectar ao framework e abrir conteúdo para dispositivos que fazem parte do framework do roteador de mídia. Os fabricantes de dispositivos de mídia podem fazer parte do framework publicando um MediaRouteProvider que permita que outros aplicativos se conectem e abram mídia nos dispositivos receptores. A Figura 1 ilustra como um app se conecta a um dispositivo receptor por meio do framework do roteador de mídia.

Figura 1. Visão geral de como as classes do provedor de roteamento de mídia oferecem comunicação a partir de um app de mídia para um dispositivo receptor.

Quando você cria um provedor de roteamento de mídia para seu dispositivo receptor, o provedor tem as seguintes finalidades:

  • Descrever e publicar os recursos do dispositivo receptor para que outros apps possam descobri-los e usar os recursos de mídia.
  • Unir a interface de programação do dispositivo receptor e os mecanismos de transporte de comunicação dela para tornar o dispositivo compatível com o framework do roteador de mídia.

Distribuição de provedores de roteamento

Um provedor de roteamento de mídia é distribuído como parte de um app para Android. Seu provedor pode ser disponibilizado a outros apps estendendo MediaRouteProviderService ou unindo sua implementação de MediaRouteProvider ao seu serviço e declarando um filtro de intent para o provedor. Essas etapas permitem que outros apps descubram e usem seu roteamento de mídia.

Observação: o app que contém o provedor de roteamento de mídia também pode incluir uma interface MediaRouter para o provedor de roteamento, mas isso não é necessário.

Biblioteca do roteador de mídia

As APIs do roteador de mídia são definidas na Biblioteca de Suporte v7-mediarouter. É preciso adicionar essa biblioteca ao seu projeto de desenvolvimento de app. Para saber mais sobre como adicionar Bibliotecas de Suporte ao projeto, consulte Configuração da Biblioteca de Suporte.

Cuidado: use a implementação android.support.v7.media do framework do roteador de mídia. Não use o pacote android.media mais antigo.

Criar um serviço de provedor

O framework do roteador de mídia precisa ser capaz de descobrir e se conectar ao provedor de roteamento de mídia para permitir que outros aplicativos usem seu roteamento. Para fazer isso, o framework do roteador de mídia procura apps que declarem uma ação de intent do provedor de roteamento de mídia. Quando outro app quiser se conectar ao seu provedor, o framework precisará invocá-lo e se conectar a ele. Portanto, seu provedor precisa ser encapsulado em um Service.

O exemplo de código a seguir mostra a declaração de um serviço de provedor de roteamento de mídia e o filtro de intent em um manifesto, permitindo que ele seja descoberto e usado pelo framework do roteador de mídia:

    <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 exemplo de manifesto declara um serviço que une as classes reais do provedor de roteamento de mídia. O framework do roteador de mídia do Android oferece a classe MediaRouteProviderService para uso como um wrapper de serviço para provedores de roteamento de mídia. O exemplo de código a seguir demonstra como usar essa classe 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);
        }
    }
    

Especificar recursos de roteamento

Os apps conectados ao framework do roteador de mídia podem descobrir seu roteamento de mídia por meio das declarações no manifesto do seu app, mas eles também precisam conhecer os recursos dos roteamentos de mídia que você oferece. Os roteamentos de mídia podem ser de diferentes tipos e ter recursos distintos, e outros apps precisam descobrir esses detalhes para determinar se são compatíveis com seu roteamento.

O framework do roteador de mídia permite que você defina e publique os recursos do seu roteamento por meio de objetos IntentFilter, objetos MediaRouteDescriptor e um MediaRouteProviderDescriptor. Esta seção explica como usar essas classes para publicar os detalhes do seu roteamento de mídia para outros apps.

Categorias de roteamento

Como parte da descrição programática do seu provedor de roteamento de mídia, você precisa especificar se ele é compatível com reprodução remota, saída secundária ou ambas. Estas são as categorias de roteamento oferecidas pelo framework do roteador de mídia:

  • CATEGORY_LIVE_AUDIO: saída de áudio para um dispositivo de saída secundário, como um sistema de música sem fio.
  • CATEGORY_LIVE_VIDEO: saída de vídeo para um dispositivo de saída secundário, como dispositivos com "Display sem fio".
  • CATEGORY_REMOTE_PLAYBACK: abertura de vídeo ou áudio em um dispositivo separado que processa a recuperação, decodificação e reprodução de mídia, como dispositivos Chromecast.

Para incluir essas configurações em uma descrição do seu roteamento mídia, insira-as em um objeto IntentFilter, que será adicionado posteriormente a um 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);
        }
    }
    

Se você especificar o intent CATEGORY_REMOTE_PLAYBACK, também precisará definir quais tipos de mídia e controles de reprodução são compatíveis com seu provedor de roteamento de mídia. A seção a seguir descreve como especificar essas configurações para seu dispositivo.

Tipos de mídia e protocolos

Um provedor de roteamento de mídia para um dispositivo de reprodução remota precisa especificar os tipos de mídia e os protocolos de transferência compatíveis. Essas configurações são especificadas com a classe IntentFilter e os métodos addDataScheme() e addDataType() desse objeto. O snippet de código a seguir demonstra como definir um filtro de intent para oferecer compatibilidade com a reprodução remota de vídeo usando http, https e Protocolo de streaming em tempo real (RTSP, na sigla em inglês):

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 reprodução

Um provedor de roteamento de mídia que oferece reprodução remota precisa especificar os tipos de controle de mídia compatíveis. Estes são os tipos de controle gerais que o roteamento de mídia pode oferecer:

  • Controles de reprodução, como tocar, pausar, voltar e avançar.
  • Recursos de fila, que permitem que o app de envio adicione e remova itens de uma playlist do dispositivo receptor.
  • Recursos de sessão, que evitam que apps de envio interfiram uns nos outros fazendo o dispositivo receptor fornecer um ID de sessão ao app solicitante e verificar esse ID a cada solicitação de controle de reprodução.

O exemplo de código a seguir demonstra como criar um filtro de intent para oferecer compatibilidade com controles básicos de reprodução de roteamento de mídia:

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 saber mais sobre os intents de controle de reprodução disponíveis, consulte a classe MediaControlIntent.

MediaRouteProviderDescriptor

Depois de definir os recursos do seu roteamento de mídia com objetos IntentFilter, você pode criar um objeto descritor para publicação no framework do roteador de mídia do Android. Esse objeto descritor contém as especificações dos recursos do seu roteamento de mídia. Assim, outros aplicativos podem determinar como interagir com ele.

O exemplo de código a seguir demonstra como adicionar os filtros de intent criados anteriormente a um MediaRouteProviderDescriptor e definir o descritor que será usado pelo framework do roteador de mídia:

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 saber mais sobre as configurações de descritor disponíveis, consulte a documentação de referência de MediaRouteDescriptor e MediaRouteProviderDescriptor.

Controlar roteamentos

Quando um aplicativo se conecta ao seu provedor de roteamento de mídia, o provedor recebe comandos de reprodução enviados por outros apps por meio do framework do roteador. Para processar essas solicitações, é preciso fornecer uma implementação de uma classe MediaRouteProvider.RouteController, que processa os comandos e lida com a comunicação real para seu dispositivo receptor.

O framework do roteador de mídia chama o método onCreateRouteController() do seu provedor de roteamento para conseguir uma instância dessa classe e, em seguida, roteia solicitações para ela. Estes são os principais métodos da classe MediaRouteProvider.RouteController, que você precisa implementar para seu provedor de roteamento de mídia:

  • onSelect(): chamado quando um aplicativo seleciona seu roteamento para reprodução. Use este método para fazer qualquer trabalho de preparação que possa ser necessário antes do início da reprodução.
  • onControlRequest(): envia comandos de reprodução específicos para o dispositivo receptor.
  • onSetVolume(): envia uma solicitação ao dispositivo receptor para definir o volume de reprodução com um valor específico.
  • onUpdateVolume(): envia uma solicitação ao dispositivo receptor para modificar o volume de reprodução com um valor específico.
  • onUnselect(): chamado quando um aplicativo cancela a seleção de um roteamento.
  • onRelease(): chamado quando o roteamento não é mais necessário para o framework, permitindo a liberação dos recursos.

Todas as solicitações de controle de reprodução, exceto mudanças de volume, são direcionadas para o método onControlRequest(). Sua implementação desse método precisa analisar as solicitações de controle e responder a elas corretamente. Veja um exemplo de implementação desse método que processa comandos para um roteamento de mídia de reprodução 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;
        }
        ...
    }
    

É importante entender que o propósito da classe MediaRouteProvider.RouteController é atuar como um wrapper da API para seu equipamento de abertura de mídia. A implementação dos métodos nessa classe depende totalmente da interface programática fornecida pelo dispositivo receptor.

Código de amostra

A amostra MediaRouter (em inglês) ensina a criar um provedor de roteamento de mídia personalizado.