Visão geral do MediaRouter

Para usar o framework do MediaRouter no seu app, você precisa acessar uma instância do objeto MediaRouter e anexar um objeto MediaRouter.Callback para detectar eventos de roteamento. O conteúdo enviado por um roteamento de mídia passa pelo MediaRouteProvider associado ao trajeto (exceto em alguns casos especiais, como um dispositivo de saída Bluetooth). A Figura 1 mostra uma visualização de alto nível 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 crie seu app como um remetente do Cast. Siga as instruçõ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 fornece uma interface padrão para o botão, o que ajuda 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.

Use uma AppCompatActivity.

Ao usar o framework do roteador de mídia em uma atividade, estenda a atividade de AppCompatActivity e importe o pacote androidx.appcompat.app. É preciso adicionar as bibliotecas de suporte androidx.appcompat:appcompat e androidx.mediarouter:mediarouter ao projeto de desenvolvimento do app. Para mais informações sobre como adicionar bibliotecas de suporte ao seu projeto, consulte Introdução ao Android Jetpack.

Cuidado:use a implementação androidx 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="androidx.mediarouter.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 a atividade do AppCompatActivity e crie o seletor quando a atividade for criada, chamando MediaRouteSelector.Builder do método onCreate(), conforme mostrado no exemplo de código a seguir. O seletor é salvo em uma variável de classe, e os tipos de trajeto permitidos são especificados adicionando 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 trajeto 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úsica recupera, renderiza e transmite vídeos ou músicas diretamente para a tela e/ou os 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, é necessário 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() em cada uma das atividades 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 para desenvolvedores sobre Barra de ações.

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 diretrizes sobre como incorporar o botão de roteamento de mídia no seu app.

Callbacks do MediaRouter

Todos os apps em execução no mesmo dispositivo compartilham uma única instância de MediaRouter e as rotas dela (filtradas por app pelo MediaRouteSelector do app). 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.

Há vários métodos no callback que você pode modificar para receber informações sobre eventos de roteamento. No mínimo, sua implementação da classe MediaRouter.Callback precisa modificar onRouteSelected() e onRouteUnselected().

Como o MediaRouter é um recurso compartilhado, seu app precisa gerenciar os callbacks do MediaRouter em resposta aos callbacks comuns 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 remova-os quando ela estiver oculta (onStop()).

O exemplo de código a seguir demonstra como criar e salvar o objeto de callback, como receber 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 o MediaRouteSelector atualize a lista de rotas 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 se encarrega de adicionar e remover o callback de uma atividade.

Observação:se você estiver criando um app de reprodução de música e quiser que ele toque música em segundo plano, crie um Service para reprodução e chame o framework do roteador de mídia usando os callbacks do 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 trajeto processa todas as funções de recuperação, decodificação e reprodução de dados de conteúdo. Os controles na interface do seu app se comunicam com o dispositivo receptor usando 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(): reproduz um arquivo de mídia específico, especificado por uma Uri.
  • pause(): pausa a faixa de mídia em reprodução no momento.
  • resume(): continua tocando a faixa atual após um comando de pausa.
  • seek(): move para uma posição específica na faixa atual.
  • release(): encerra a conexão do app com o 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 progresso da tarefa de reprodução ou da 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 gerenciamento da fila.

Exemplo de código

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