Integra Cast nella tua app Android

Questa guida per gli sviluppatori descrive come aggiungere il supporto di Google Cast alla tua app mittente Android utilizzando l'SDK mittente Android.

Il dispositivo mobile o il laptop è il mittente che controlla la riproduzione, mentre il dispositivo Google Cast è il ricevitore che visualizza i contenuti sulla TV.

Il framework mittente si riferisce al file binario della libreria di classi Cast e alle risorse associate presenti in fase di runtime sul mittente. L'app mittente o app Cast si riferisce a un'app in esecuzione anche sul mittente. L'app Web Receiver si riferisce all'applicazione HTML in esecuzione sul dispositivo compatibile con Google Cast.

Il framework mittente utilizza una progettazione di callback asincrona per informare l'app mittente degli eventi e per passare tra i vari stati del ciclo di vita dell'app Cast.

Flusso dell'app

I passaggi seguenti descrivono il flusso di esecuzione di alto livello tipico per un'app per Android mittente:

  • Il framework Cast avvia automaticamente MediaRouter il rilevamento dei dispositivi in base al ciclo di vita Activity.
  • Quando l'utente fa clic sul pulsante Trasmetti, il framework presenta la finestra di dialogo Trasmetti con l'elenco dei dispositivi Cast rilevati.
  • Quando l'utente seleziona un dispositivo di trasmissione, il framework tenta di avviare l'app Web Receiver sul dispositivo di trasmissione.
  • Il framework richiama i callback nell'app mittente per confermare che l'app Web Receiver è stata avviata.
  • Il framework crea un canale di comunicazione tra le app mittente e Web Receiver.
  • Il framework utilizza il canale di comunicazione per caricare e controllare la riproduzione dei contenuti multimediali sul Web Receiver.
  • Il framework sincronizza lo stato di riproduzione dei contenuti multimediali tra il mittente e il Web Receiver: quando l'utente esegue azioni dell'interfaccia utente del mittente, il framework passa queste richieste di controllo dei contenuti multimediali al Web Receiver e, quando il Web Receiver invia aggiornamenti dello stato dei contenuti multimediali, il framework aggiorna lo stato dell'interfaccia utente del mittente.
  • Quando l'utente fa clic sul pulsante Trasmetti per disconnettersi dal dispositivo di trasmissione, il framework disconnette l'app mittente dal Web Receiver.

Per un elenco completo di tutte le classi, i metodi e gli eventi dell'SDK Android di Google Cast, consulta i riferimenti delle API mittente di Google Cast per Android. Le sezioni seguenti illustrano i passaggi per aggiungere Cast alla tua app per Android.

Configurare il manifest Android

Il file AndroidManifest.xml della tua app richiede la configurazione dei seguenti elementi per l'SDK Cast:

uses-sdk

Imposta i livelli API Android minimo e di destinazione supportati dall'SDK Cast. Al momento, il livello minimo è il livello API 23 e il livello di destinazione è il livello API 34.

<uses-sdk
        android:minSdkVersion="23"
        android:targetSdkVersion="34" />

android:theme

Imposta il tema della tua app in base alla versione minima dell'SDK Android. Ad esempio, se non implementi un tuo tema, devi utilizzare una variante di Theme.AppCompat quando hai come target una versione minima dell'SDK Android precedente a Lollipop.

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat" >
       ...
</application>

Inizializzare il contesto Cast

Il framework ha un oggetto singleton globale, CastContext, che coordina tutte le interazioni del framework.

La tua app deve implementare l' OptionsProvider per fornire le opzioni necessarie per inizializzare il singleton CastContext. OptionsProvider fornisce un'istanza di CastOptions che contiene opzioni che influiscono sul comportamento del framework. La più importante è l'ID applicazione Web Receiver, che viene utilizzato per filtrare i risultati del rilevamento e per avviare l'app Web Receiver all'avvio di una sessione Cast.

Kotlin
class CastOptionsProvider : OptionsProvider {
    override fun getCastOptions(context: Context): CastOptions {
        return Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
Java
public class CastOptionsProvider implements OptionsProvider {
    @Override
    public CastOptions getCastOptions(Context context) {
        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build();
        return castOptions;
    }
    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

Devi dichiarare il nome completo dell'interfaccia OptionsProvider implementata come campo di metadati nel file AndroidManifest.xml dell'app mittente:

<application>
    ...
    <meta-data
        android:name=
            "com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
        android:value="com.foo.CastOptionsProvider" />
</application>

CastContext viene inizializzato in modo differito quando viene chiamato CastContext.getSharedInstance().

Kotlin
class MyActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val castContext = CastContext.getSharedInstance(this)
    }
}
Java
public class MyActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        CastContext castContext = CastContext.getSharedInstance(this);
    }
}

I widget dell'esperienza utente di Cast

Il framework Cast fornisce i widget conformi alla checklist di progettazione di Cast:

  • Overlay introduttivo: Il framework fornisce una View personalizzata, IntroductoryOverlay, che viene mostrata all'utente per attirare l'attenzione sul pulsante Trasmetti la prima volta che è disponibile un ricevitore. L'app mittente può personalizzare il testo e la posizione del testo del titolo.

  • Pulsante Trasmetti: il pulsante Trasmetti è visibile indipendentemente dalla disponibilità dei dispositivi Cast. Quando l'utente fa clic sul pulsante Trasmetti per la prima volta, viene visualizzata una finestra di dialogo Trasmetti che elenca i dispositivi rilevati. Quando l'utente fa clic sul pulsante Trasmetti mentre il dispositivo è connesso, vengono visualizzati i metadati dei contenuti multimediali correnti (ad esempio titolo, nome dello studio di registrazione e un'immagine miniatura) oppure l'utente può disconnettersi dal dispositivo di trasmissione. Il "pulsante Trasmetti" viene a volte chiamato "icona Trasmetti".

  • Mini controller: quando l'utente trasmette contenuti e ha abbandonato la pagina dei contenuti corrente o il controller espanso a un'altra schermata dell'app mittente, il mini controller viene visualizzato nella parte inferiore dello schermo per consentire all'utente di visualizzare i metadati dei contenuti multimediali attualmente trasmessi e di controllare la riproduzione.

  • Controller espanso: quando l'utente trasmette contenuti, se fa clic sulla notifica dei contenuti multimediali o sul mini controller, viene avviato il controller espanso, che mostra i metadati dei contenuti multimediali attualmente riprodotti e fornisce diversi pulsanti per controllare la riproduzione dei contenuti multimediali.

  • Notifica: solo Android. Quando l'utente trasmette contenuti e abbandona l'app mittente, viene visualizzata una notifica dei contenuti multimediali che mostra i metadati dei contenuti multimediali attualmente trasmessi e i controlli di riproduzione.

  • Schermata di blocco: solo Android. Quando l'utente trasmette contenuti e passa (o il dispositivo va in timeout) alla schermata di blocco, viene visualizzato un controllo dei contenuti multimediali nella schermata di blocco che mostra i metadati dei contenuti multimediali attualmente trasmessi e i controlli di riproduzione.

La guida seguente include le descrizioni di come aggiungere questi widget alla tua app.

Aggiungere un pulsante Trasmetti

Le API MediaRouter di Android sono progettate per consentire la visualizzazione e la riproduzione di contenuti multimediali su dispositivi secondari. Le app per Android che utilizzano l'API MediaRouter devono includere un pulsante Trasmetti come parte dell'interfaccia utente, per consentire agli utenti di selezionare un percorso multimediale per riprodurre contenuti multimediali su un dispositivo secondario, ad esempio un dispositivo di trasmissione.

Il framework semplifica l'aggiunta di un MediaRouteButton come Cast button. Innanzitutto, devi aggiungere una voce di menu o un MediaRouteButton nel file XML che definisce il menu e utilizzare CastButtonFactory per collegarlo al framework.

// To add a Cast button, add the following snippet.
// menu.xml
<item
    android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
    app:showAsAction="always" />
Kotlin
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.kt
override fun onCreateOptionsMenu(menu: Menu): Boolean {
    super.onCreateOptionsMenu(menu)
    menuInflater.inflate(R.menu.main, menu)
    CastButtonFactory.setUpMediaRouteButton(
        applicationContext,
        menu,
        R.id.media_route_menu_item
    )
    return true
}
Java
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.java
@Override public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    getMenuInflater().inflate(R.menu.main, menu);
    CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
                                            menu,
                                            R.id.media_route_menu_item);
    return true;
}

Poi, se il tuo Activity eredita da FragmentActivity, puoi aggiungere un MediaRouteButton al layout.

// activity_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:gravity="center_vertical"
   android:orientation="horizontal" >

   <androidx.mediarouter.app.MediaRouteButton
       android:id="@+id/media_route_button"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_weight="1"
       android:mediaRouteTypes="user"
       android:visibility="gone" />

</LinearLayout>
Kotlin
// MyActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_layout)

    mMediaRouteButton = findViewById<View>(R.id.media_route_button) as MediaRouteButton
    CastButtonFactory.setUpMediaRouteButton(applicationContext, mMediaRouteButton)

    mCastContext = CastContext.getSharedInstance(this)
}
Java
// MyActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_layout);

   mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
   CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton);

   mCastContext = CastContext.getSharedInstance(this);
}

Per impostare l'aspetto del pulsante Trasmetti utilizzando un tema, consulta Personalizzare il pulsante Trasmetti.

Configurare il rilevamento dei dispositivi

Il rilevamento dei dispositivi è completamente gestito da CastContext. Quando inizializza CastContext, l'app mittente specifica l'ID applicazione Web Receiver e, facoltativamente, può richiedere il filtro dello spazio dei nomi impostando supportedNamespaces in CastOptions. CastContext contiene internamente un riferimento a MediaRouter e avvia la procedura di rilevamento nelle seguenti condizioni:

  • In base a un algoritmo progettato per bilanciare la latenza del rilevamento dei dispositivi e l'utilizzo della batteria, il rilevamento viene avviato automaticamente di tanto in tanto quando l'app mittente passa in primo piano.
  • La finestra di dialogo Trasmetti è aperta.
  • L'SDK Cast sta tentando di recuperare una sessione Cast.

La procedura di rilevamento viene interrotta quando la finestra di dialogo Cast viene chiusa o l'app mittente passa in background.

Kotlin
class CastOptionsProvider : OptionsProvider {
    companion object {
        const val CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace"
    }

    override fun getCastOptions(appContext: Context): CastOptions {
        val supportedNamespaces: MutableList<String> = ArrayList()
        supportedNamespaces.add(CUSTOM_NAMESPACE)

        return CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
Java
class CastOptionsProvider implements OptionsProvider {
    public static final String CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace";

    @Override
    public CastOptions getCastOptions(Context appContext) {
        List<String> supportedNamespaces = new ArrayList<>();
        supportedNamespaces.add(CUSTOM_NAMESPACE);

        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build();
        return castOptions;
    }

    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

Come funziona la gestione delle sessioni

L'SDK Cast introduce il concetto di sessione Cast, la cui creazione combina i passaggi di connessione a un dispositivo, avvio (o partecipazione) di un'app Web Receiver, connessione a quell'app e inizializzazione di un canale di controllo dei contenuti multimediali. Per saperne di più sulle sessioni Cast e sul ciclo di vita di Web Receiver, consulta la guida al ciclo di vita dell'applicazione Web Receiver .

Le sessioni sono gestite dalla classe SessionManager, a cui la tua app può accedere tramite CastContext.getSessionManager(). Le singole sessioni sono rappresentate da sottoclassi della classe Session. Ad esempio, CastSession rappresenta le sessioni con i dispositivi Cast. La tua app può accedere alla sessione Cast attualmente attiva tramite SessionManager.getCurrentCastSession().

La tua app può utilizzare la SessionManagerListener classe per monitorare gli eventi della sessione, come la creazione, la sospensione, la ripresa e la terminazione. Il framework tenta automaticamente di riprendere da una terminazione anomala/improvvisa mentre una sessione era attiva.

Le sessioni vengono create e interrotte automaticamente in risposta ai gesti dell'utente dalle finestre di dialogo MediaRouter.

Per comprendere meglio gli errori di avvio di Cast, le app possono utilizzare CastContext#getCastReasonCodeForCastStatusCode(int) per convertire l'errore di avvio della sessione in CastReasonCodes. Tieni presente che alcuni errori di avvio della sessione (ad es. CastReasonCodes#CAST_CANCELLED) sono comportamenti previsti e non devono essere registrati come errori.

Se devi essere a conoscenza delle modifiche dello stato della sessione, puoi implementare un SessionManagerListener. Questo esempio ascolta la disponibilità di un CastSession in un Activity.

Kotlin
class MyActivity : Activity() {
    private var mCastSession: CastSession? = null
    private lateinit var mCastContext: CastContext
    private lateinit var mSessionManager: SessionManager
    private val mSessionManagerListener: SessionManagerListener<CastSession> =
        SessionManagerListenerImpl()

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarting(session: CastSession?) {}

        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            invalidateOptionsMenu()
        }

        override fun onSessionStartFailed(session: CastSession?, error: Int) {
            val castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error)
            // Handle error
        }

        override fun onSessionSuspended(session: CastSession?, reason Int) {}

        override fun onSessionResuming(session: CastSession?, sessionId: String) {}

        override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) {
            invalidateOptionsMenu()
        }

        override fun onSessionResumeFailed(session: CastSession?, error: Int) {}

        override fun onSessionEnding(session: CastSession?) {}

        override fun onSessionEnded(session: CastSession?, error: Int) {
            finish()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mCastContext = CastContext.getSharedInstance(this)
        mSessionManager = mCastContext.sessionManager
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onResume() {
        super.onResume()
        mCastSession = mSessionManager.currentCastSession
    }

    override fun onDestroy() {
        super.onDestroy()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }
}
Java
public class MyActivity extends Activity {
    private CastContext mCastContext;
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarting(CastSession session) {}
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionStartFailed(CastSession session, int error) {
            int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error);
            // Handle error
        }
        @Override
        public void onSessionSuspended(CastSession session, int reason) {}
        @Override
        public void onSessionResuming(CastSession session, String sessionId) {}
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionResumeFailed(CastSession session, int error) {}
        @Override
        public void onSessionEnding(CastSession session) {}
        @Override
        public void onSessionEnded(CastSession session, int error) {
            finish();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCastContext = CastContext.getSharedInstance(this);
        mSessionManager = mCastContext.getSessionManager();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mCastSession = mSessionManager.getCurrentCastSession();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
}

Trasferimento dello streaming

La conservazione dello stato della sessione è la base del trasferimento dello streaming, in cui gli utenti possono spostare gli stream audio e video esistenti tra i dispositivi utilizzando i comandi vocali, l'app Google Home o gli smart display. La riproduzione dei contenuti multimediali si interrompe su un dispositivo (l'origine) e continua su un altro (la destinazione). Qualsiasi dispositivo di trasmissione con il firmware più recente può fungere da origine o destinazione in un trasferimento dello streaming.

Per ottenere il nuovo dispositivo di destinazione durante un trasferimento dello streaming o un'espansione, registra un Cast.Listener utilizzando CastSession#addCastListener. Poi chiama CastSession#getCastDevice() durante il onDeviceNameChanged callback.

Per saperne di più, consulta Trasferimento dello streaming su Web Receiver.

Riconnessione automatica

Il framework fornisce un ReconnectionService che può essere abilitato dall'app mittente per gestire la riconnessione in molti casi limite, ad esempio:

  • Recupero da una perdita temporanea del Wi-Fi
  • Recupero dalla modalità di sospensione del dispositivo
  • Recupero dall'esecuzione dell'app in background
  • Recupero in caso di arresto anomalo dell'app

Questo servizio è attivo per impostazione predefinita e può essere disattivato in CastOptions.Builder.

Questo servizio può essere unito automaticamente al manifest dell'app se l'unione automatica è abilitata nel file Gradle.

Il framework avvia il servizio quando è presente una sessione multimediale e lo interrompe al termine della sessione multimediale.

Come funziona il controllo dei contenuti multimediali

Il framework Cast ritira la RemoteMediaPlayer classe di Cast 2.x a favore di una nuova classe RemoteMediaClient, che fornisce la stessa funzionalità in un insieme di API più pratiche ed evita di dover passare un GoogleApiClient.

Quando la tua app stabilisce un CastSession con un'app Web Receiver che supporta lo spazio dei nomi dei contenuti multimediali, il framework crea automaticamente un'istanza di RemoteMediaClient ; la tua app può accedervi chiamando getRemoteMediaClient() sull'istanza CastSession.

Tutti i metodi di RemoteMediaClient che inviano richieste al Web Receiver restituiscono un oggetto PendingResult che può essere utilizzato per monitorare la richiesta.

È previsto che l'istanza di RemoteMediaClient possa essere condivisa da più parti dell'app e, in effetti, da alcuni componenti interni del framework, come i mini controller persistenti e il servizio di notifica. A questo scopo, questa istanza supporta la registrazione di più istanze di RemoteMediaClient.Listener.

Impostare i metadati dei contenuti multimediali

La MediaMetadata classe rappresenta le informazioni su un elemento multimediale che vuoi trasmettere. L'esempio seguente crea una nuova istanza MediaMetadata di un film e imposta il titolo, il sottotitolo e due immagini.

Kotlin
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle())
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio())
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(0))))
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(1))))
Java
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle());
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio());
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0))));
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));

Consulta Selezione delle immagini sull'utilizzo delle immagini con i metadati dei contenuti multimediali.

Caricare contenuti multimediali

La tua app può caricare un elemento multimediale, come mostrato nel seguente codice. Innanzitutto, utilizza MediaInfo.Builder con i metadati dei contenuti multimediali per creare un'istanza MediaInfo. Ottieni il RemoteMediaClient dall'attuale CastSession, quindi carica il MediaInfo in quel RemoteMediaClient. Utilizza RemoteMediaClient per riprodurre, mettere in pausa e controllare in altro modo un'app di riproduzione di contenuti multimediali in esecuzione sul Web Receiver.

Kotlin
val mediaInfo = MediaInfo.Builder(mSelectedMedia.getUrl())
    .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
    .setContentType("videos/mp4")
    .setMetadata(movieMetadata)
    .setStreamDuration(mSelectedMedia.getDuration() * 1000)
    .build()
val remoteMediaClient = mCastSession.getRemoteMediaClient()
remoteMediaClient.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())
Java
MediaInfo mediaInfo = new MediaInfo.Builder(mSelectedMedia.getUrl())
        .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
        .setContentType("videos/mp4")
        .setMetadata(movieMetadata)
        .setStreamDuration(mSelectedMedia.getDuration() * 1000)
        .build();
RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
remoteMediaClient.load(new MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build());

Consulta anche la sezione sull' utilizzo delle tracce multimediali.

Formato video 4K

Per verificare il formato video dei tuoi contenuti multimediali, utilizza getVideoInfo() in MediaStatus per ottenere l'istanza corrente di VideoInfo. Questa istanza contiene il tipo di formato TV HDR e l'altezza e la larghezza del display in pixel. Le varianti del formato 4K sono indicate dalle costanti HDR_TYPE_*.

Notifiche di controllo remoto a più dispositivi

Quando un utente trasmette contenuti, gli altri dispositivi Android sulla stessa rete ricevono una notifica che consente loro di controllare la riproduzione. Chiunque riceva queste notifiche può disattivarle per il proprio dispositivo nell'app Impostazioni in Google > Google Cast > Mostra notifiche di controllo remoto. (Le notifiche includono un collegamento all'app Impostazioni.) Per maggiori dettagli, consulta Notifiche di controllo remoto di Cast.

Aggiungere il mini controller

Secondo la Cast Design Checklist, un'app mittente deve fornire un controllo persistente noto come mini controller che deve essere visualizzato quando l'utente abbandona la pagina dei contenuti corrente per un'altra parte dell'app mittente. Il mini controller fornisce all'utente un promemoria visibile della sessione Cast corrente. Toccando il mini controller, l'utente può tornare alla visualizzazione del controller espanso a schermo intero di Cast.

Il framework fornisce una View personalizzata, MiniControllerFragment, che puoi aggiungere alla parte inferiore del file di layout di ogni attività in cui vuoi mostrare il mini controller.

<fragment
    android:id="@+id/castMiniController"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />

Quando l'app mittente riproduce uno stream live audio o video, l'SDK visualizza automaticamente un pulsante di riproduzione/interruzione al posto del pulsante di riproduzione/pausa nel mini controller.

Per impostare l'aspetto del testo del titolo e del sottotitolo di questa visualizzazione personalizzata, e per scegliere i pulsanti, consulta Personalizzare il mini controller.

Aggiungere il controller espanso

La checklist di progettazione di Google Cast richiede che un'app mittente fornisca un controller espanso per i contenuti multimediali trasmessi. Il controller espanso è una versione a schermo intero del mini controller.

L'SDK Cast fornisce un widget per il controller espanso chiamato ExpandedControllerActivity. Si tratta di una classe astratta di cui devi creare una sottoclasse per aggiungere un pulsante Trasmetti.

Innanzitutto, crea un nuovo file di risorse del menu per il controller espanso per fornire il pulsante Trasmetti:

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

Crea una nuova classe che estende ExpandedControllerActivity.

Kotlin
class ExpandedControlsActivity : ExpandedControllerActivity() {
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)
        menuInflater.inflate(R.menu.expanded_controller, menu)
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
        return true
    }
}
Java
public class ExpandedControlsActivity extends ExpandedControllerActivity {
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.expanded_controller, menu);
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item);
        return true;
    }
}

Ora dichiara la nuova attività nel manifest dell'app all'interno del tag application:

<application>
...
<activity
        android:name=".expandedcontrols.ExpandedControlsActivity"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:theme="@style/Theme.CastVideosDark"
        android:screenOrientation="portrait"
        android:parentActivityName="com.google.sample.cast.refplayer.VideoBrowserActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
    </intent-filter>
</activity>
...
</application>

Modifica CastOptionsProvider e cambia NotificationOptions e CastMediaOptions per impostare l'attività di destinazione sulla nuova attività:

Kotlin
override fun getCastOptions(context: Context): CastOptions? {
    val notificationOptions = NotificationOptions.Builder()
        .setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()
    val mediaOptions = CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()

    return CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build()
}
Java
public CastOptions getCastOptions(Context context) {
    NotificationOptions notificationOptions = new NotificationOptions.Builder()
            .setTargetActivityClassName(ExpandedControlsActivity.class.getName())
            .build();
    CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
            .setNotificationOptions(notificationOptions)
            .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName())
            .build();

    return new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setCastMediaOptions(mediaOptions)
            .build();
}

Aggiorna il metodo loadRemoteMedia di LocalPlayerActivity per visualizzare la nuova attività quando vengono caricati i contenuti multimediali remoti:

Kotlin
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
    val remoteMediaClient = mCastSession?.remoteMediaClient ?: return

    remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() {
        override fun onStatusUpdated() {
            val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java)
            startActivity(intent)
            remoteMediaClient.unregisterCallback(this)
        }
    })

    remoteMediaClient.load(
        MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position.toLong()).build()
    )
}
Java
private void loadRemoteMedia(int position, boolean autoPlay) {
    if (mCastSession == null) {
        return;
    }
    final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
    if (remoteMediaClient == null) {
        return;
    }
    remoteMediaClient.registerCallback(new RemoteMediaClient.Callback() {
        @Override
        public void onStatusUpdated() {
            Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class);
            startActivity(intent);
            remoteMediaClient.unregisterCallback(this);
        }
    });
    remoteMediaClient.load(new MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position).build());
}

Quando l'app mittente riproduce uno stream live audio o video, l'SDK visualizza automaticamente un pulsante di riproduzione/interruzione al posto del pulsante di riproduzione/pausa nel controller espanso.

Per impostare l'aspetto utilizzando i temi, scegliere i pulsanti da visualizzare, e aggiungere pulsanti personalizzati, consulta Personalizzare il controller espanso.

Controllo del volume

Il framework gestisce automaticamente il volume per l'app mittente. Il framework sincronizza automaticamente le app mittente e Web Receiver in modo che l'interfaccia utente del mittente riporti sempre il volume specificato dal Web Receiver.

Controllo del volume tramite pulsanti fisici

Su Android, i pulsanti fisici del dispositivo mittente possono essere utilizzati per modificare il volume della sessione Cast sul Web Receiver per impostazione predefinita per qualsiasi dispositivo che utilizza Jelly Bean o versioni successive.

Controllo del volume tramite pulsanti fisici prima di Jelly Bean

Per utilizzare i tasti del volume fisici per controllare il volume del dispositivo Web Receiver sui dispositivi Android precedenti a Jelly Bean, l'app mittente deve sostituire dispatchKeyEvent nelle proprie attività e chiamare CastContext.onDispatchVolumeKeyEventBeforeJellyBean():

Kotlin
class MyActivity : FragmentActivity() {
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        return (CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
                || super.dispatchKeyEvent(event))
    }
}
Java
class MyActivity extends FragmentActivity {
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        return CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
            || super.dispatchKeyEvent(event);
    }
}

Aggiungere i controlli multimediali alla notifica e alla schermata di blocco

Solo su Android, la checklist di progettazione di Google Cast richiede che un'app mittente implementi i controlli multimediali in una notifica e nella schermata di blocco, dove il mittente trasmette contenuti, ma l'app mittente non ha lo stato attivo. Il framework fornisce MediaNotificationService e MediaIntentReceiver per aiutare l'app mittente a creare controlli multimediali in una notifica e nella schermata di blocco.

MediaNotificationService viene eseguito quando il mittente trasmette contenuti e mostra una notifica con la miniatura dell'immagine e le informazioni sull'elemento di trasmissione corrente, un pulsante di riproduzione/pausa e un pulsante di interruzione.

MediaIntentReceiver è un BroadcastReceiver che gestisce le azioni dell'utente dalla notifica.

La tua app può configurare la notifica e il controllo dei contenuti multimediali dalla schermata di blocco tramite NotificationOptions. La tua app può configurare i pulsanti di controllo da mostrare nella notifica e l'Activity da aprire quando l'utente tocca la notifica. Se le azioni non vengono fornite in modo esplicito, verranno utilizzati i valori predefiniti, MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK e MediaIntentReceiver.ACTION_STOP_CASTING.

Kotlin
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
val buttonActions: MutableList<String> = ArrayList()
buttonActions.add(MediaIntentReceiver.ACTION_REWIND)
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK)
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD)
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING)

// Showing "play/pause" and "stop casting" in the compat view of the notification.
val compatButtonActionsIndices = intArrayOf(1, 3)

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
val notificationOptions = NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity::class.java.name)
    .build()
Java
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
List<String> buttonActions = new ArrayList<>();
buttonActions.add(MediaIntentReceiver.ACTION_REWIND);
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK);
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD);
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING);

// Showing "play/pause" and "stop casting" in the compat view of the notification.
int[] compatButtonActionsIndices = new int[]{1, 3};

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
NotificationOptions notificationOptions = new NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity.class.getName())
    .build();

La visualizzazione dei controlli dei contenuti multimediali dalla notifica e dalla schermata di blocco è attiva per impostazione predefinita e può essere disattivata chiamando setNotificationOptions con null in CastMediaOptions.Builder. Al momento, la funzionalità della schermata di blocco è attiva finché la notifica è attiva.

Kotlin
// ... continue with the NotificationOptions built above
val mediaOptions = CastMediaOptions.Builder()
    .setNotificationOptions(notificationOptions)
    .build()
val castOptions: CastOptions = Builder()
    .setReceiverApplicationId(context.getString(R.string.app_id))
    .setCastMediaOptions(mediaOptions)
    .build()
Java
// ... continue with the NotificationOptions built above
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .build();
CastOptions castOptions = new CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build();

Quando l'app mittente riproduce uno stream live audio o video, l'SDK visualizza automaticamente un pulsante di riproduzione/interruzione al posto del pulsante di riproduzione/pausa nel controllo delle notifiche, ma non nel controllo della schermata di blocco.

Nota: per visualizzare i controlli della schermata di blocco sui dispositivi precedenti a Lollipop, RemoteMediaClient richiederà automaticamente lo stato attivo dell'audio per tuo conto.

Gestisci gli errori

È molto importante che le app mittenti gestiscano tutti i callback degli errori e decidano la risposta migliore per ogni fase del ciclo di vita di Cast. L'app può visualizzare finestre di dialogo di errore per l'utente oppure può decidere di interrompere la connessione al Web Receiver.