Visão geral do MediaRouter

Para usar o framework do MediaRouter no seu app, é preciso conseguir uma instância do objeto MediaRouter e anexar um objeto MediaRouter.Callback para ouvir eventos de roteamento. Exceto em alguns casos especiais, como um dispositivo de saída Bluetooth, o conteúdo enviado por um roteamento de mídia passa pelo MediaRouteProvider associado. A Figura 1 mostra uma visualização detalhada das classes usadas para rotear conteúdo entre dispositivos.

Figura 1. Visão geral das principais classes de roteador de mídia usadas por apps.

Observação: se você quiser que seu app seja compatível com dispositivos Google Cast, use o SDK do Cast e compile seu app como um remetente do Cast. Siga as orientações na documentação do Cast em vez de usar o framework do MediaRouter diretamente.

Botão de roteamento de mídia

Apps para Android devem usar um botão de roteamento de mídia para fins de controle. O framework do MediaRouter oferece uma interface padrão para o botão, o que ajudará os usuários a reconhecer e usar o roteamento quando ele estiver disponível. O botão de roteamento de mídia geralmente é colocado no lado direito da barra de ações do app, como mostrado na Figura 2.

Figura 2. Botão de roteamento de mídia na barra de ações.

Quando o usuário pressiona o botão de roteamento de mídia, os roteamentos disponíveis aparecem em uma lista, como mostrado na Figura 3.

Figura 3. Uma lista de roteamentos de mídia disponíveis, exibida depois de pressionar o botão correspondente.

Siga estas etapas para criar um botão de roteamento de mídia:

  1. Use uma AppCompatActivity.
  2. Defina o item de menu do botão de roteamento de mídia.
  3. Crie um MediaRouteSelector.
  4. Adicione o botão de roteamento de mídia à barra de ações.
  5. Crie e processe os métodos MediaRouter.Callback no ciclo de vida da sua atividade.

Essa seção descreve as quatro primeiras etapas. A seção seguinte descreve os métodos de callback.

Usar uma AppCompatActivity

Ao usar o framework do roteador de mídia em uma atividade, estenda a atividade da AppCompatActivity e importe o pacote android.support.v7.media. É preciso adicionar as Bibliotecas de Suporte v7-appcompat e v7-mediarouter ao projeto de desenvolvimento do seu app. Para saber mais sobre como adicionar Bibliotecas de Suporte ao projeto, consulte a 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.

Crie um arquivo XML que defina um item menu para o botão de rota de mídia. A ação do item deve ser a classe MediaRouteActionProvider. Veja um exemplo de arquivo:

    // myMediaRouteButtonMenuItem.xml
    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          >

        <item android:id="@+id/media_route_menu_item"
            android:title="@string/media_route_menu_title"
            app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
            app:showAsAction="always"
        />
    </menu>
    

Criar um MediaRouteSelector

Os roteamentos que aparecem no menu do botão de roteamento de mídia são determinados por um MediaRouteSelector. Estenda sua atividade da AppCompatActivity e, quando a atividade for criada, compile o seletor chamando MediaRouteSelector.Builder a partir do método onCreate(), conforme visto na amostra de código a seguir. Observe que o seletor é salvo em uma variável de classe, e os tipos de roteamento permitidos são especificados ao adicionar objetos MediaControlIntent:

Kotlin

    class MediaRouterPlaybackActivity : AppCompatActivity() {

        private var mSelector: MediaRouteSelector? = null

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            // Create a route selector for the type of routes your app supports.
            mSelector = MediaRouteSelector.Builder()
                    // These are the framework-supported intents
                    .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                    .build()
        }
    }
    

Java

    public class MediaRouterPlaybackActivity extends AppCompatActivity {
        private MediaRouteSelector mSelector;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            // Create a route selector for the type of routes your app supports.
            mSelector = new MediaRouteSelector.Builder()
                    // These are the framework-supported intents
                    .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                    .build();
        }
    }
    

Para a maioria dos aplicativos, o único tipo de roteamento necessário é CATEGORY_REMOTE_PLAYBACK. Esse tipo de roteamento trata o dispositivo que está executando seu app como um controle remoto. O dispositivo receptor conectado processa toda a recuperação, decodificação e reprodução dos dados de conteúdo. É assim que apps compatíveis com o Google Cast, como o Chromecast, funcionam.

Alguns fabricantes oferecem compatibilidade com uma opção de roteamento especial conhecida como "saída secundária". Com esse roteamento, seu app de mídia recupera, renderiza e faz streaming de vídeo ou música diretamente na tela e/ou nos alto-falantes do dispositivo receptor remoto selecionado. Use a saída secundária para enviar conteúdo para sistemas de música ou telas de vídeo sem fio. Para ativar a descoberta e a seleção desses dispositivos, é preciso adicionar as categorias de controle CATEGORY_LIVE_AUDIO ou CATEGORY_LIVE_VIDEO ao MediaRouteSelector. Também é necessário criar e processar a própria caixa de diálogo Presentation.

Adicionar o botão de roteamento de mídia à barra de ações

Com o menu de roteamento de mídia e o MediaRouteSelector definidos, é possível adicionar o botão de roteamento de mídia a uma atividade. Modifique o método onCreateOptionsMenu() de cada atividade para adicionar um menu de opções.

Kotlin

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)

        // Inflate the menu and configure the media router action provider.
        menuInflater.inflate(R.menu.sample_media_router_menu, menu)

        // Attach the MediaRouteSelector to the menu item
        val mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item)
        val mediaRouteActionProvider =
                MenuItemCompat.getActionProvider(mediaRouteMenuItem) as MediaRouteActionProvider

        // Attach the MediaRouteSelector that you built in onCreate()
        selector?.also(mediaRouteActionProvider::setRouteSelector)

        // Return true to show the menu.
        return true
    }
    

Java

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        // Inflate the menu and configure the media router action provider.
        getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);

        // Attach the MediaRouteSelector to the menu item
        MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
        MediaRouteActionProvider mediaRouteActionProvider =
                (MediaRouteActionProvider)MenuItemCompat.getActionProvider(
                mediaRouteMenuItem);
        // Attach the MediaRouteSelector that you built in onCreate()
        mediaRouteActionProvider.setRouteSelector(selector);

        // Return true to show the menu.
        return true;
    }
    

Para saber mais sobre como implementar a barra de ações no seu app, consulte o guia do desenvolvedor Adicionar barra de apps.

Também é possível adicionar um botão de roteamento de mídia como um MediaRouteButton em qualquer visualização. Você precisa anexar um MediaRouteSelector ao botão por meio do método setRouteSelector(). Consulte a Lista de verificação de design do Google Cast para ver orientações de como incorporar o botão de roteamento de mídia no seu aplicativo.

Callbacks do MediaRouter

Todos os apps em execução no mesmo dispositivo compartilham uma única instância MediaRouter e as rotas relacionadas, que são filtradas por app pelo MediaRouteSelector. Cada atividade se comunica com o MediaRouter usando a própria implementação de métodos MediaRouter.Callback. O MediaRouter chama os métodos de callback sempre que o usuário seleciona, muda ou desconecta um roteamento.

Você pode modificar vários métodos no callback para receber informações sobre eventos de roteamento. Sua implementação da classe MediaRouter.Callback deve modificar no mínimo onRouteSelected() e onRouteUnselected().

Como o MediaRouter é um recurso compartilhado, seu app precisa gerenciar os callbacks do MediaRouter em resposta aos callbacks habituais do ciclo de vida da atividade:

  • Quando a atividade for criada (onCreate(Bundle)), pegue um ponteiro para MediaRouter e segure-o por todo o ciclo de vida do app.
  • Anexe callbacks ao MediaRouter quando a atividade se tornar visível (onStart()) e desanexe-os quando ela estiver oculta (onStop()).

A amostra de código a seguir demonstra como criar e salvar o objeto de callback, como conseguir uma instância de MediaRouter e como gerenciar callbacks. Observe o uso da sinalização CALLBACK_FLAG_REQUEST_DISCOVERY ao anexar os callbacks em onStart(). Isso permite que seu MediaRouteSelector atualize a lista de roteamentos disponíveis do botão de roteamento de mídia.

Kotlin

    class MediaRouterPlaybackActivity : AppCompatActivity() {

        private var mediaRouter: MediaRouter? = null
        private var mSelector: MediaRouteSelector? = null

        // Variables to hold the currently selected route and its playback client
        private var mRoute: MediaRouter.RouteInfo? = null
        private var remotePlaybackClient: RemotePlaybackClient? = null

        // Define the Callback object and its methods, save the object in a class variable
        private val mediaRouterCallback = object : MediaRouter.Callback() {

            override fun onRouteSelected(router: MediaRouter, route: MediaRouter.RouteInfo) {
                Log.d(TAG, "onRouteSelected: route=$route")
                if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
                    // Stop local playback (if necessary)
                    // ...

                    // Save the new route
                    mRoute = route

                    // Attach a new playback client
                    remotePlaybackClient =
                        RemotePlaybackClient(this@MediaRouterPlaybackActivity, mRoute)

                    // Start remote playback (if necessary)
                    // ...
                }
            }

            override fun onRouteUnselected(
                    router: MediaRouter,
                    route: MediaRouter.RouteInfo,
                    reason: Int
            ) {
                Log.d(TAG, "onRouteUnselected: route=$route")
                if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {

                    // Changed route: tear down previous client
                    mRoute?.also {
                        remotePlaybackClient?.release()
                        remotePlaybackClient = null
                    }

                    // Save the new route
                    mRoute = route

                    when (reason) {
                        MediaRouter.UNSELECT_REASON_ROUTE_CHANGED -> {
                            // Resume local playback (if necessary)
                            // ...
                        }
                    }
                }
            }
        }

        // Retain a pointer to the MediaRouter
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            // Get the media router service.
            mediaRouter = MediaRouter.getInstance(this)
            ...
        }

        // Use this callback to run your MediaRouteSelector to generate the
        // list of available media routes
        override fun onStart() {
            mSelector?.also { selector ->
                mediaRouter?.addCallback(selector, mediaRouterCallback,
                        MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY)
            }
            super.onStart()
        }

        // Remove the selector on stop to tell the media router that it no longer
        // needs to discover routes for your app.
        override fun onStop() {
            mediaRouter?.removeCallback(mediaRouterCallback)
            super.onStop()
        }
        ...
    }
    

Java

    public class MediaRouterPlaybackActivity extends AppCompatActivity {
        private MediaRouter mediaRouter;
        private MediaRouteSelector mSelector;

        // Variables to hold the currently selected route and its playback client
        private MediaRouter.RouteInfo mRoute;
        private RemotePlaybackClient remotePlaybackClient;

        // Define the Callback object and its methods, save the object in a class variable
        private final MediaRouter.Callback mediaRouterCallback =
                new MediaRouter.Callback() {

            @Override
            public void onRouteSelected(MediaRouter router, RouteInfo route) {
                Log.d(TAG, "onRouteSelected: route=" + route);

                if (route.supportsControlCategory(
                    MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)){
                    // Stop local playback (if necessary)
                    // ...

                    // Save the new route
                    mRoute = route;

                    // Attach a new playback client
                    remotePlaybackClient = new RemotePlaybackClient(this, mRoute);

                    // Start remote playback (if necessary)
                    // ...
                }
            }

            @Override
            public void onRouteUnselected(MediaRouter router, RouteInfo route, int reason) {
                Log.d(TAG, "onRouteUnselected: route=" + route);

                if (route.supportsControlCategory(
                    MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)){

                    // Changed route: tear down previous client
                    if (mRoute != null && remotePlaybackClient != null) {
                        remotePlaybackClient.release();
                        remotePlaybackClient = null;
                    }

                    // Save the new route
                    mRoute = route;

                    if (reason != MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
                        // Resume local playback  (if necessary)
                        // ...
                    }
                }
            }
        }

        // Retain a pointer to the MediaRouter
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            // Get the media router service.
            mediaRouter = MediaRouter.getInstance(this);
            ...
        }

        // Use this callback to run your MediaRouteSelector to generate the list of available media routes
        @Override
        public void onStart() {
            mediaRouter.addCallback(mSelector, mediaRouterCallback,
                    MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
            super.onStart();
        }

        // Remove the selector on stop to tell the media router that it no longer
        // needs to discover routes for your app.
        @Override
        public void onStop() {
            mediaRouter.removeCallback(mediaRouterCallback);
            super.onStop();
        }
        ...
    }
    

O framework do roteador de mídia também oferece uma classe MediaRouteDiscoveryFragment, que cuida da adição e da remoção do callback de uma atividade.

Observação: se você estiver desenvolvendo um app de música e quiser que ele toque música no segundo plano, precisará criar um Service para reprodução e chamar o framework do roteador de mídia a partir dos callbacks de ciclo de vida do serviço.

Controlar um roteamento de reprodução remota

Quando você seleciona um roteamento de reprodução remota, seu app funciona como um controle remoto. O dispositivo na outra extremidade do roteamento processa todas as funções de recuperação, decodificação e reprodução de dados de conteúdo. Os controles na IU do seu app se comunicam com o dispositivo receptor por meio de um objeto RemotePlaybackClient.

A classe RemotePlaybackClient oferece outros métodos para gerenciar a reprodução de conteúdo. Veja alguns dos principais métodos de reprodução da classe RemotePlaybackClient:

  • play(): abre um arquivo de mídia especificado por um Uri.
  • pause(): pausa a faixa de mídia em reprodução no momento.
  • resume(): continua reproduzindo a faixa atual depois de um comando de pausa.
  • seek(): move para uma posição específica na faixa atual.
  • release(): encerra a conexão do seu app ao dispositivo de reprodução remota.

Você pode usar esses métodos para anexar ações aos controles de reprodução oferecidos no app. A maioria desses métodos também permite incluir um objeto de callback para monitorar o andamento da tarefa de reprodução ou solicitação de controle.

A classe RemotePlaybackClient também é compatível com o enfileiramento de vários itens de mídia para reprodução e com o gerenciamento da fila de mídia.

Amostra de código

As amostras BasicMediaRouter do Android e MediaRouter (links em inglês) demonstram em mais detalhes o uso da API MediaRouter.