Panoramica di MediaRouteProvider

Il framework del router multimediale Android consente ai produttori di abilitare la riproduzione sui propri dispositivi tramite un'interfaccia standardizzata chiamata MediaRouteProvider. Un provider di route definisce un'interfaccia comune per la riproduzione di contenuti multimediali su un dispositivo ricevitore, che consente di riprodurre contenuti multimediali sull'apparecchiatura da qualsiasi applicazione Android che supporti i percorsi multimediali.

Questa guida illustra come creare un fornitore di percorsi multimediali per un dispositivo ricevente e renderlo disponibile ad altre applicazioni di riproduzione multimediale eseguite su Android. Per poter usare questa API, devi conoscere le classi chiave MediaRouteProvider, MediaRouteProviderDescriptor e RouteController.

Panoramica

Il framework del router multimediale Android consente agli sviluppatori di app multimediali e ai produttori di dispositivi per la riproduzione di contenuti multimediali di connettersi tramite un'API e un'interfaccia utente comuni. Gli sviluppatori di app che implementano un'interfaccia MediaRouter possono quindi connettersi al framework e riprodurre contenuti sui dispositivi che partecipano al framework del router multimediale. I produttori di dispositivi per la riproduzione di contenuti multimediali possono partecipare al framework pubblicando un MediaRouteProvider che consenta ad altre applicazioni di connettersi ai dispositivi ricevitori e riprodurli. La Figura 1 mostra come un'app si connette a un dispositivo ricevente tramite il framework del router multimediale.

Figura 1. Panoramica di come le classi di provider di route multimediali forniscono la comunicazione da un'app multimediale a un dispositivo ricevente.

Quando crei un fornitore di route multimediali per il dispositivo ricevitore, il provider si occupa dei seguenti scopi:

  • Descrivi e pubblica le funzionalità del dispositivo ricevitore in modo che altre app possano rilevarlo e utilizzarne le funzionalità di riproduzione.
  • Avvolgi l'interfaccia di programmazione del dispositivo ricevitore e i suoi meccanismi di trasporto di comunicazione per rendere il dispositivo compatibile con il framework del router multimediale.

Distribuzione di provider di route

Un fornitore di route multimediali viene distribuito come parte di un'app per Android. Il fornitore di route può essere reso disponibile ad altre app estendendo MediaRouteProviderService o eseguendo il wrapping della tua implementazione di MediaRouteProvider con il tuo servizio e dichiarando un filtro per intent per il fornitore di route multimediali. Questi passaggi consentono ad altre app di rilevare e utilizzare il tuo percorso multimediale.

Nota: l'app contenente il fornitore di routing multimediale può anche includere un'interfaccia MediaRouter con il fornitore di route, ma non è necessario.

Libreria di supporto MediaRouter

Le API del router multimediale sono definite nella libreria MediaRouter di AndroidX. Devi aggiungere questa libreria al tuo progetto di sviluppo dell'app. Per ulteriori informazioni sull'aggiunta di librerie di supporto al progetto, consulta Configurazione della libreria di supporto.

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

Creazione di un servizio del provider

Il framework del router multimediale deve essere in grado di rilevare e connettersi al provider di route multimediale per consentire ad altre applicazioni di utilizzarlo. A questo scopo, il framework del router multimediale cerca le app che dichiarano un'azione intent del provider di route multimediale. Quando un'altra app vuole connettersi al tuo provider, il framework deve essere in grado di richiamarla e connettersi, quindi il tuo provider deve essere incapsulato in un Service.

Il codice di esempio seguente mostra la dichiarazione di un servizio di provider di route multimediale e il filtro per intent in un manifest, che ne consente il rilevamento e l'utilizzo da parte del framework del router multimediale:

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

Questo esempio di file manifest dichiara un servizio che esegue il wrapping delle classi di provider di route multimediali effettive. Il framework del router multimediale di Android fornisce la classe MediaRouteProviderService da utilizzare come wrapper di servizio per i provider di route multimediali. Il codice di esempio seguente mostra come utilizzare questa classe 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);
    }
}

Specificare le funzionalità di route

Le app che si connettono al framework del router multimediale possono rilevare la tua route multimediale tramite le dichiarazioni del file manifest dell'app, ma devono anche conoscere le funzionalità delle route multimediali fornite. I percorsi multimediali possono essere di tipi diversi e avere funzionalità diverse; le altre app devono essere in grado di rilevare questi dettagli per stabilire se sono compatibili con il percorso.

Il framework del router multimediale ti consente di definire e pubblicare le funzionalità della route multimediale tramite oggetti IntentFilter, oggetti MediaRouteDescriptor e MediaRouteProviderDescriptor. Questa sezione spiega come utilizzare queste classi per pubblicare i dettagli della route multimediale per altre app.

Categorie di percorsi

Come parte della descrizione programmatica del fornitore del routing multimediale, devi specificare se supporta la riproduzione remota, l'output secondario o entrambi. Queste sono le categorie di route fornite dal framework del router multimediale:

  • CATEGORY_LIVE_AUDIO - Uscita di audio su un dispositivo di output secondario, ad esempio un sistema musicale wireless.
  • CATEGORY_LIVE_VIDEO - Output video su un dispositivo di output secondario, ad esempio dispositivi con visualizzazione wireless.
  • CATEGORY_REMOTE_PLAYBACK: riproduci video o audio su un dispositivo separato che gestisce il recupero, la decodifica e la riproduzione di contenuti multimediali, ad esempio i dispositivi Chromecast.

Per includere queste impostazioni in una descrizione della route multimediale, devi inserirle in un oggetto IntentFilter, che in seguito aggiungerai a un oggetto 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 specifichi l'intent CATEGORY_REMOTE_PLAYBACK, devi anche definire quali tipi di contenuti multimediali e controlli di riproduzione sono supportati dal fornitore del percorso multimediale. La sezione successiva descrive come specificare queste impostazioni per il tuo dispositivo.

Protocolli e tipi di contenuti multimediali

Un fornitore di percorsi multimediali per un dispositivo di riproduzione remota deve specificare i tipi di contenuti multimediali e i protocolli di trasferimento che supporta. Puoi specificare queste impostazioni utilizzando la classe IntentFilter e i metodi addDataScheme() e addDataType() dell'oggetto. Il seguente snippet di codice mostra come definire un filtro di intent per il supporto della riproduzione di video da remoto utilizzando 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);
        }
    }
}

Controlli di riproduzione

Un fornitore di percorsi multimediali che offre la riproduzione remota deve specificare i tipi di controlli multimediali supportati. Di seguito sono riportati i tipi generali di controlli che le route multimediali possono offrire:

  • Controlli di riproduzione, come riproduzione, pausa, riavvolgimento e avanzamento veloce.
  • Funzionalità di coda, che consentono all'app di invio di aggiungere e rimuovere elementi da una playlist gestita dal dispositivo ricevente.
  • Funzionalità di sessione, che impediscono alle app di inviare interferenze tra loro facendo in modo che il dispositivo ricevente fornisca un ID sessione all'app richiedente e poi lo controlli con ogni successiva richiesta di controllo della riproduzione.

Il seguente esempio di codice mostra come creare un filtro per intent per supportare i controlli di riproduzione delle route multimediali di base:

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

Per maggiori informazioni sugli intent di controllo della riproduzione disponibili, consulta la classe MediaControlIntent.

MediaRouteProviderDescriptor

Dopo aver definito le funzionalità della tua route multimediale utilizzando gli oggetti IntentFilter, puoi creare un oggetto descrittore da pubblicare nel framework del router multimediale Android. Questo oggetto descrittore contiene le specifiche delle funzionalità della route multimediale, in modo che altre applicazioni possano determinare come interagire con la route multimediale.

Il codice di esempio seguente mostra come aggiungere i filtri per intent creati in precedenza a un MediaRouteProviderDescriptor e impostare il descrittore per l'utilizzo da parte del framework del router multimediale:

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

Per maggiori informazioni sulle impostazioni disponibili per i descrittori, consulta la documentazione di riferimento per MediaRouteDescriptor e MediaRouteProviderDescriptor.

Controllo delle route

Quando un'applicazione si connette al provider di routing multimediale, quest'ultimo riceve comandi di riproduzione tramite il framework del router multimediale inviati al tuo percorso da altre app. Per gestire queste richieste, devi fornire un'implementazione di una classe MediaRouteProvider.RouteController, che elabora i comandi e gestisce la comunicazione effettiva con il dispositivo ricevitore.

Il framework del router multimediale chiama il metodo onCreateRouteController() del provider di route per ottenere un'istanza di questa classe, quindi instrada le richieste a questa. Questi sono i metodi principali della classe MediaRouteProvider.RouteController, che devi implementare per il tuo fornitore di route multimediali:

  • onSelect() - Richiamato quando un'applicazione seleziona il percorso per la riproduzione. Questo metodo può essere utilizzato per svolgere eventuali attività di preparazione prima dell'inizio della riproduzione dei contenuti multimediali.
  • onControlRequest(): invia comandi di riproduzione specifici al dispositivo ricevente.
  • onSetVolume(): invia al dispositivo ricevente una richiesta di impostazione del volume di riproduzione su un valore specifico.
  • onUpdateVolume(): invia al dispositivo ricevente una richiesta di modifica del volume di riproduzione di una quantità specificata.
  • onUnselect() - Richiamato quando un'applicazione deseleziona una route.
  • onRelease() - Richiamato quando la route non è più necessaria dal framework, il che consente di liberare risorse.

Tutte le richieste di controllo della riproduzione, ad eccezione delle modifiche del volume, vengono indirizzate al metodo onControlRequest(). L'implementazione di questo metodo deve analizzare le richieste di controllo e rispondere in modo appropriato. Di seguito è riportato un esempio di implementazione di questo metodo, che elabora i comandi per una route multimediale di riproduzione 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 capire che la classe MediaRouteProvider.RouteController ha lo scopo di fungere da wrapper per l'API all'apparecchiatura di riproduzione multimediale. L'implementazione dei metodi in questa classe dipende completamente dall'interfaccia programmatica fornita dal dispositivo di destinazione.

Codice di esempio

L'esempio MediaRouter mostra come creare un provider di route multimediale personalizzato.