Visão geral do MediaRouteProvider

O framework do roteador de mídia do Android permite que os fabricantes ativem a reprodução nos dispositivos usando uma interface padronizada chamada MediaRouteProvider. Um provedor de rotas define uma interface comum para reproduzir mídia em um dispositivo receptor, tornando tocar mídia em seu equipamento a partir de qualquer aplicativo Android compatível com mídia rotas de prioridade mais alta.

Este guia discute como criar um provedor de roteamento de mídia para um dispositivo receptor e torná-lo a outros aplicativos de reprodução de mídia executados no Android. Para usar essa API, você precisa: você precisa conhecer as classes principais 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 dispositivos de reprodução de mídia que os fabricantes se conectem usando uma API e uma interface de usuário comuns. Desenvolvedores de apps que implementar uma interface MediaRouter pode se conectar e reproduzir conteúdo nos dispositivos que participam do framework do roteador de mídia. Mídia os fabricantes de dispositivos de reprodução podem participar do framework publicando um MediaRouteProvider que permita que outros aplicativos se conectem e reproduzir mídia nos dispositivos receptores A Figura 1 ilustra como um app se conecta a um servidor usando o 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 seu dispositivo receptor, ele fornece o seguintes finalidades:

  • Descrever e publicar os recursos do dispositivo receptor para que outros apps possam descobri-lo e usar seus recursos de reprodução.
  • Unir a interface de programação do dispositivo receptor e a comunicação dele mecanismos de transporte 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 Android. Seu provedor de trajetos pode ser disponibilizados para outros aplicativos estendendo MediaRouteProviderService ou agrupando sua implementação de MediaRouteProvider pelo próprio serviço e declarar uma intent filtro para o provedor de roteamento de mídia. 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 um interface MediaRouter do provedor de rota, mas isso não é obrigatório.

Biblioteca de Suporte MediaRouter

As APIs do roteador de mídia são definidas na Biblioteca AndroidX MediaRouter É preciso adicionar essa biblioteca ao projeto de desenvolvimento de apps. Para mais informações sobre como adicionar bibliotecas de suporte aos seus projeto, consulte Configuração da Biblioteca de Suporte.

Cuidado:use o AndroidX implementação 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 seu provedor de roteamento de mídia. para permitir que outros aplicativos usem sua rota. Para fazer isso, o framework do roteador de mídia procura aplicativos que declarem uma ação de intent do provedor de roteamento de mídia. Quando outro app quiser conectar ao provedor, o framework precisa ser capaz de invocá-lo e se conectar a ele. Assim, o provedor precisam ser encapsulados 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 as filtro de intent em um manifesto, permitindo que ele seja descoberto e usado pelo roteador de mídia estrutural:

<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 esse wrapper classe:

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 o roteamento de mídia pelo seu declarações de manifesto do app, mas também precisam conhecer os recursos dos roteamentos de mídia estão fornecendo. Os roteamentos de mídia podem ser de diferentes tipos e ter recursos distintos, e outros apps precisa ser capaz de descobrir esses detalhes para determinar se são compatíveis com o trajeto.

O framework do roteador de mídia permite que você defina e publique os recursos dos seus trajeto por objetos IntentFilter, MediaRouteDescriptor e MediaRouteProviderDescriptor. Nesta seção, explicamos como usar essas para publicar os detalhes do seu roteamento de mídia para outros aplicativos.

Categorias de roteamento

Como parte da descrição programática de seu provedor de roteamento de mídia, você precisa especificar se o provedor oferece suporte a reprodução remota, saída secundária ou ambas. Estes são os trajetos Categorias fornecidas pelo framework do roteador de mídia:

Para incluir essas configurações em uma descrição do seu roteamento de mídia, é necessário inseri-las no um objeto IntentFilter, que você vai adicionar depois 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 a intent CATEGORY_REMOTE_PLAYBACK, também vai precisar definir quais tipos de mídia e são compatíveis com seu provedor de roteamento de mídia. A próxima seção descreve como especifique 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 as transferências os protocolos compatíveis. Você especifica essas configurações usando IntentFilter. a classe addDataScheme(), e addDataType() desse objeto. A snippet de código a seguir demonstra como definir um filtro de intent para oferecer suporte a vídeo remoto reprodução 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 com suporte. 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 enfileiramento, que permitem ao app de envio adicionar e remover itens. de uma lista de reprodução mantida pelo dispositivo receptor.
  • Recursos de sessão, que impedem o envio de apps de interferir nas outro fazendo com que o dispositivo receptor forneça um ID de sessão ao app solicitante e verificando esse ID a cada solicitação subsequente de controle de reprodução.

O exemplo de código a seguir demonstra como construir um filtro de intent para oferecer suporte controles básicos de reprodução do 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 mais informações sobre as intents de controle de reprodução disponíveis, consulte a MediaControlIntent.

MediaRouteProviderDescriptor

Depois de definir os recursos do seu roteamento de mídia usando objetos IntentFilter, você poderá criar um objeto descritor para publicar no o framework do roteador de mídia do Android. Esse objeto descritor contém as especificidades da sua mídia e os recursos do roteamento, para que outros aplicativos possam determinar como interagir com seus trajeto.

O código de exemplo a seguir demonstra como adicionar os filtros de intents criados anteriormente a um MediaRouteProviderDescriptor e definir o descritor para ser usado por o 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 de descritor disponíveis, 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 pelo framework do roteador de mídia enviados ao seu trajeto por outros apps. Para lidar com isso é necessário fornecer uma implementação de uma classe MediaRouteProvider.RouteController, que processa os comandos e lida com a comunicação real com o dispositivo receptor.

O framework do roteador de mídia chama o método onCreateRouteController() do seu provedor de rotas para obter uma instância dessa classe e, em seguida, roteia solicitações para ela. Esses 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. Você usa esse método para fazer qualquer trabalho de preparação que possa ser necessário antes do início da reprodução da 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 como um um valor específico.
  • onUpdateVolume(): envia uma solicitação ao dispositivo receptor para modificar a reprodução. volume por um valor especificado.
  • onUnselect(): chamado quando um aplicativo cancela a seleção de um roteamento.
  • onRelease() : chamado quando o trajeto não é mais necessário para a estrutura, permitindo que ele libere sua do Google Cloud.

Todas as solicitações de controle de mídia, exceto mudanças de volume, são direcionadas para o onControlRequest() . Sua implementação deste método deve analisar as solicitações de controle e respondê-las adequadamente. Aqui está um exemplo de implementação deste 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 ao seu equipamento de reprodução de mídia. A implementação dos métodos nessa classe é totalmente dependente da interface programática fornecida pelo dispositivo receptor.

Exemplo de código

O MediaRouter exemplo mostra como criar um provedor de roteamento de mídia personalizado.