Panoramica di MediaRouter

Per utilizzare il framework MediaRouter all'interno della tua app, devi ottenere un'istanza dell'oggetto MediaRouter e collegare un oggetto MediaRouter.Callback per l'ascolto degli eventi di routing. I contenuti inviati attraverso un percorso multimediale passano attraverso il MediaRouteProvider associato al percorso (tranne in alcuni casi speciali, ad esempio un dispositivo di output Bluetooth). La figura 1 offre una visione generale delle classi utilizzate per il routing dei contenuti tra dispositivi.

Figura 1. Panoramica delle principali classi di router multimediali utilizzate dalle app.

Nota: se vuoi che la tua app supporti i dispositivi Google Cast, devi utilizzare l'SDK Cast e creare l'app come mittente di trasmissione. Segui le istruzioni nella documentazione di Cast anziché utilizzare direttamente il framework MediaRouter.

Il pulsante Route multimediale

Le app per Android devono utilizzare un pulsante Percorso multimediale per controllare il routing multimediale. Il framework MediaRouter fornisce un'interfaccia standard per il pulsante, che consente agli utenti di riconoscere e utilizzare il routing quando è disponibile. Il pulsante del percorso multimediale si trova in genere sul lato destro della barra delle azioni dell'app, come mostrato nella Figura 2.

Figura 2. Pulsante Route multimediale nella barra delle azioni.

Quando l'utente preme il pulsante Percorso multimediale, i percorsi multimediali disponibili vengono visualizzati in un elenco, come mostrato nella figura 3.

Figura 3. Un elenco di percorsi multimediali disponibili, mostrato dopo aver premuto il pulsante Percorso multimediale.

Per creare un pulsante Percorso multimediale:

  1. Utilizzo di AppCompatActivity
  2. Definisci la voce di menu del pulsante Percorso multimediale
  3. Creazione di un MediaRouteSelector
  4. Aggiungere il pulsante Percorso multimediale alla barra delle azioni
  5. Crea e gestisci i metodi MediaRouter.Callback nel ciclo di vita dell'attività

In questa sezione vengono descritti i primi quattro passaggi. La sezione successiva descrive i metodi di callback.

Utilizzo di AppCompatActivity

Quando utilizzi il framework del router multimediale in un'attività, devi estendere l'attività da AppCompatActivity e importare il pacchetto androidx.appcompat.app. Devi aggiungere le librerie di supporto androidx.appcompat:appcompat e androidx.mediarouter:mediarouter al progetto di sviluppo dell'app. Per ulteriori informazioni sull'aggiunta di librerie di supporto al progetto, consulta la guida introduttiva ad Android Jetpack.

Attenzione: assicurati di utilizzare l'implementazione androidx del framework del router multimediale. Non utilizzare il pacchetto android.media precedente.

Crea un file XML che definisca una voce di menu per il pulsante del percorso multimediale. L'azione dell'elemento deve essere la classe MediaRouteActionProvider. Ecco un file di esempio:

// 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>

Creazione di un MediaRouteSelector

I percorsi visualizzati nel menu del pulsante Percorso multimediale sono determinati da un MediaRouteSelector. Estendi l'attività da AppCompatActivity e crea il selettore quando viene creata l'attività chiamando MediaRouteSelector.Builder dal metodo onCreate() come mostrato nel seguente esempio di codice. Tieni presente che il selettore viene salvato in una variabile di classe e i tipi di route consentiti vengono specificati aggiungendo oggetti 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();
    }
}

Per la maggior parte delle applicazioni, l'unico tipo di route necessario è CATEGORY_REMOTE_PLAYBACK. Questo tipo di percorso considera il dispositivo che esegue la tua app come un telecomando. Il dispositivo ricevitore connesso gestisce il recupero, la decodifica e la riproduzione di tutti i dati dei contenuti. Ecco come funzionano le app che supportano Google Cast, come Chromecast.

Alcuni produttori supportano una speciale opzione di routing denominata "output secondario". Con questo routing, l'app multimediale recupera, esegue il rendering e trasmette in streaming video o musica direttamente sullo schermo e/o sugli altoparlanti sul dispositivo ricevitore remoto selezionato. Utilizza l'uscita secondaria per inviare contenuti a display video o sistemi musicali wireless. Per consentire il rilevamento e la selezione di questi dispositivi, devi aggiungere le categorie di controllo CATEGORY_LIVE_AUDIO o CATEGORY_LIVE_VIDEO a MediaRouteSelector. Devi anche creare e gestire la tua finestra di dialogo Presentation.

Aggiungere il pulsante Percorso multimediale alla barra delle azioni

Dopo aver definito il menu Percorso multimediale e MediaRouteSelector, ora puoi aggiungere il pulsante Percorso multimediale a un'attività. Esegui l'override del metodo onCreateOptionsMenu() per ogni tua attività per aggiungere un menu opzioni.

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;
}

Per ulteriori informazioni sull'implementazione della barra delle azioni nella tua app, consulta la guida per gli sviluppatori relativa alla barra delle azioni.

Puoi anche aggiungere un pulsante di route multimediale come MediaRouteButton in qualsiasi vista. Devi collegare un MediaRouteSelector al pulsante utilizzando il metodo setRouteSelector(). Consulta l'elenco di controllo per la progettazione di Google Cast per le linee guida sull'incorporamento del pulsante Percorso multimediale nell'applicazione.

Callback di MediaRouter

Tutte le app in esecuzione sullo stesso dispositivo condividono una singola istanza MediaRouter e le relative route (filtrate per app dal MediaRouteSelector dell'app). Ogni attività comunica con MediaRouter utilizzando la propria implementazione di metodi MediaRouter.Callback. MediaRouter chiama i metodi di callback ogni volta che l'utente seleziona, modifica o disconnette un percorso.

Nel callback sono disponibili diversi metodi che puoi ignorare per ricevere informazioni sugli eventi di routing. Come minimo, l'implementazione della classe MediaRouter.Callback deve sostituire onRouteSelected() e onRouteUnselected().

Poiché MediaRouter è una risorsa condivisa, la tua app deve gestire i propri callback MediaRouter in risposta ai normali callback del ciclo di vita delle attività:

  • Una volta creata l'attività (onCreate(Bundle)), seleziona un puntatore sul MediaRouter e tienilo premuto per l'intera durata dell'app.
  • Associa i callback a MediaRouter quando l'attività diventa visibile (onStart()) e scollegali quando è nascosta (onStop()).

Il seguente esempio di codice mostra come creare e salvare l'oggetto callback, ottenere un'istanza di MediaRouter e gestire i callback. Tieni presente l'utilizzo del flag CALLBACK_FLAG_REQUEST_DISCOVERY quando alleghi i callback in onStart(). Ciò consente a MediaRouteSelector di aggiornare l'elenco di route disponibili del pulsante del percorso multimediale.

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();
    }
    ...
}

Il framework del router multimediale fornisce anche una classe MediaRouteDiscoveryFragment, che si occupa di aggiungere e rimuovere il callback per un'attività.

Nota: se stai scrivendo un'app per la riproduzione di musica e vuoi che riproduca musica in background, devi creare un Service per la riproduzione e chiamare il framework del router multimediale dai callback del ciclo di vita del servizio.

Controllo di un percorso di riproduzione remoto

Quando selezioni un percorso di riproduzione da remoto, l'app funge da telecomando. Il dispositivo all'estremità opposta del percorso gestisce tutte le funzioni di recupero, decodifica e riproduzione dei dati dei contenuti. I controlli nella UI della tua app comunicano con il dispositivo ricevitore tramite un oggetto RemotePlaybackClient.

La classe RemotePlaybackClient fornisce metodi aggiuntivi per gestire la riproduzione dei contenuti. Ecco alcuni dei metodi di riproduzione dei tasti della classe RemotePlaybackClient:

  • play(): riproduci un file multimediale specifico, specificato da Uri.
  • pause(): metti in pausa la traccia multimediale attualmente in riproduzione.
  • resume(): continua la riproduzione della traccia corrente dopo un comando di pausa.
  • seek(): passa a una posizione specifica nella traccia corrente.
  • release(): elimina la connessione dall'app al dispositivo di riproduzione remoto.

Puoi utilizzare questi metodi per associare le azioni ai controlli di riproduzione forniti nella tua app. La maggior parte di questi metodi consente anche di includere un oggetto callback in modo da poter monitorare l'avanzamento dell'attività di riproduzione o della richiesta di controllo.

La classe RemotePlaybackClient supporta anche l'inserimento in coda di più elementi multimediali per la riproduzione e la gestione della coda di contenuti multimediali.

Codice di esempio

Gli esempi Android BasicMediaRouter e MediaRouter dimostrano ulteriormente l'uso dell'API MediaRouter.