Visão geral do MediaRouteProvider

O framework do roteador de mídia do Android permite que os fabricantes ativem a reprodução nos dispositivos por uma interface padronizada chamada MediaRouteProvider. Um provedor de rotas define uma interface comum para reproduzir mídia em um dispositivo receptor, possibilitando a reprodução de mídia no seu equipamento a partir de qualquer app Android com suporte a rotas de mídia.

Este guia discute como criar um provedor de roteamento de mídia para um dispositivo receptor e disponibilizá-lo para outros apps de reprodução de mídia executados no Android. Para usar essa API, você precisa conhecer 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 reprodução de mídia se conectem usando uma API e uma interface de usuário comum. Os desenvolvedores de apps que implementam uma interface MediaRouter podem se conectar ao framework e reproduzir conteúdo nos dispositivos que participam do framework do roteador de mídia. Fabricantes de dispositivos de reprodução de mídia podem participar do framework publicando um MediaRouteProvider que permita que outros aplicativos se conectem e toquem mídia nos dispositivos receptores. A Figura 1 ilustra como um app se conecta a um dispositivo receptor pelo framework do roteador de mídia.

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

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

  • Descreva e publique os recursos do dispositivo receptor para que outros apps possam descobri-lo e usar os recursos de reprodução.
  • Encapsule a interface de programação do dispositivo receptor e os mecanismos de transporte de comunicação dele 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. Ele pode ser disponibilizado a outros apps estendendo MediaRouteProviderService ou unindo sua implementação de MediaRouteProvider com seu próprio serviço e declarando um filtro de intent para ele. Estas 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 rota, mas isso não é necessário.

Biblioteca de Suporte MediaRouter

As APIs do roteador de mídia são definidas na biblioteca AndroidX MediaRouter. Adicione essa biblioteca ao projeto de desenvolvimento do app. Para ver mais informações sobre como adicionar Bibliotecas de Suporte ao projeto, consulte Configuração da Biblioteca de Suporte.

Cuidado:use a implementação AndroidX 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 quer se conectar ao seu provedor, o framework precisa invocá-lo e se conectar a ele. Portanto, seu provedor precisa ser encapsulado em um Service.

O código de exemplo 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, o que permite 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 código de exemplo 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 que se conectam ao framework do roteador de mídia podem descobrir seu roteamento de mídia pelas declarações de manifesto, mas também precisam conhecer os recursos dos roteamentos de mídia oferecidos. 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 a rota.

O framework do roteador de mídia permite definir e publicar os recursos do seu roteamento de mídia por objetos IntentFilter, 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, é necessário especificar se ele é compatível com reprodução remota, saída secundária ou ambas. Estas são as categorias de rotas fornecidas 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 de Display sem fio.
  • CATEGORY_REMOTE_PLAYBACK: reproduza vídeo ou áudio em um dispositivo separado que processa a recuperação, a decodificação e a reprodução de mídia, como dispositivos Chromecast.

Para incluir essas configurações em uma descrição do seu roteamento de 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, será necessário definir também quais tipos de mídia e controles de reprodução são compatíveis com seu provedor de roteamento de mídia. A próxima seção 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. Para especificar essas configurações, use 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 suporte à reprodução remota de vídeo usando http, https e Real Time Streaming Protocol (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 mídia

Um provedor de roteamento de mídia que oferece reprodução remota precisa especificar os tipos de controles 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 ao app de envio adicionar e remover itens de uma playlist mantida pelo dispositivo receptor.
  • Recursos de sessão, que impedem que apps de envio interfiram uns nos outros, fazendo com que o dispositivo receptor forneça um ID de sessão ao app solicitante e, em seguida, verifique esse ID a cada solicitação de controle de reprodução subsequente.

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 rota 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 mais informações sobre as intents de controle de reprodução disponíveis, consulte a classe MediaControlIntent.

MediaRouteProviderDescriptor

Depois de definir os recursos do seu roteamento de mídia usando 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 especificidades dos recursos do seu roteamento de mídia, para que outros aplicativos possam determinar como interagir com ele.

O código de exemplo a seguir demonstra como adicionar os filtros de intent criados anteriormente a um MediaRouteProviderDescriptor e definir o descritor a 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 mais informações sobre as configurações disponíveis do descritor, consulte a documentação de referência para 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 pelo framework do roteador. Para processar essas solicitações, é necessário fornecer uma implementação de uma classe MediaRouteProvider.RouteController, que processa os comandos e processa a comunicação real com o dispositivo receptor.

O framework do roteador de mídia chama o método onCreateRouteController() do provedor de rota para receber uma instância dessa classe e, em seguida, encaminha solicitações para ela. Estes são os principais métodos da classe MediaRouteProvider.RouteController, que precisam ser implementados para seu provedor de roteamento de mídia:

  • onSelect(): chamado quando um aplicativo seleciona seu trajeto para reprodução. Use esse método para fazer qualquer trabalho de preparação que possa ser necessário antes do início da reprodução de mídia.
  • 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 especificado.
  • onUnselect(): chamado quando um aplicativo cancela a seleção de um roteamento.
  • onRelease(): chamado quando a rota não é mais necessária 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(). A implementação desse método precisa analisar as solicitações de controle e responder a elas de forma adequada. Veja um exemplo de implementação desse método que processa comandos para uma rota 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 objetivo da classe MediaRouteProvider.RouteController é atuar como um wrapper da API para seu equipamento de reprodução de mídia. A implementação dos métodos nessa classe depende totalmente da interface programática fornecida pelo dispositivo receptor.

Exemplo de código

O exemplo do MediaRouter (em inglês) mostra como criar um provedor de roteamento de mídia personalizado.