Integra Cast nella tua app Android

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

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

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

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

Flusso di app

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

  • Il framework Cast avvia automaticamente il MediaRouter rilevamento dei dispositivi in base al ciclo di vita di Activity.
  • Quando l'utente fa clic sul pulsante Trasmetti, il framework presenta nella finestra di dialogo Trasmetti con l'elenco dei dispositivi di trasmissione rilevati.
  • Quando l'utente seleziona un dispositivo di trasmissione, il framework tenta di avviare l'app Web ricevitore sul dispositivo di trasmissione.
  • Il framework richiama i callback nell'app del mittente per confermare che l'app Web Ricevitore sia stata avviata.
  • Il framework crea un canale di comunicazione tra il mittente e le app del destinatario web.
  • Il framework utilizza il canale di comunicazione per caricare e controllare la riproduzione multimediale sul web ricevitore.
  • Il framework sincronizza lo stato di riproduzione dei contenuti multimediali tra il mittente e il ricevitore web: quando l'utente esegue azioni nell'interfaccia utente del mittente, il framework le passa le richieste di controllo multimediali al ricevitore web e quando il ricevitore web 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 del mittente dal ricevitore web.

Per un elenco completo di tutti i corsi, i metodi e gli eventi presenti nell'SDK Google Cast per Android, consulta la documentazione di riferimento sull'API Google Cast Sender per Android. Le sezioni seguenti illustrano i passaggi per aggiungere Google Cast alla tua app Android.

Configurare il manifest di 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 minimi e target supportati dall'SDK Cast. Attualmente il livello API minimo è 21 e l'obiettivo è il livello API 28.

<uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="28" />

android:theme

Imposta il tema della tua app in base alla versione minima dell'SDK Android. Ad esempio, se non stai implementando un tuo tema, devi utilizzare una variante Theme.AppCompat quando scegli 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>

Inizializza il contesto di trasmissione

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

L'app deve implementare l'interfaccia OptionsProvider per fornire le opzioni necessarie per inizializzare il singolo CastContext. OptionsProvider fornisce un'istanza di CastOptions che contiene opzioni che influiscono sul comportamento del framework. Il più importante è l'ID applicazione Web ricevitore, che viene utilizzato per filtrare i risultati di rilevamento e per avviare l'app Web ricevitore quando viene avviata una sessione di trasmissione.

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'oggetto OptionsProvider implementato come campo di metadati nel file AndroidManifest.xml dell'app del 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 lentamente 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);
    }
}

Widget UX di Google Cast

Il framework Cast fornisce i widget conformi all'elenco di controllo per la progettazione di Cast:

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

  • Pulsante Trasmetti. Il pulsante Trasmetti è visibile indipendentemente dalla disponibilità dei dispositivi di trasmissione. Quando l'utente fa clic per la prima volta sul pulsante Trasmetti, viene visualizzata una finestra di dialogo Trasmetti in cui sono elencati i dispositivi rilevati. Quando l'utente fa clic sul pulsante Trasmetti mentre il dispositivo è connesso, vengono visualizzati i metadati multimediali attuali (ad esempio titolo, nome dello studio di registrazione e un'immagine in miniatura) o consente all'utente di disconnettersi dal dispositivo di trasmissione. Il pulsante Trasmetti a volte è chiamato "icona Trasmetti".

  • Mini controller: quando l'utente sta trasmettendo contenuti e esce dalla pagina dei contenuti corrente o dal controller espanso a un'altra schermata nell'app del mittente, il mini controller viene visualizzato nella parte inferiore dello schermo per consentire all'utente di vedere i metadati dei contenuti multimediali attualmente trasmessi e controllare la riproduzione.

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

  • Notifica: Solo su Android. Quando l'utente sta trasmettendo contenuti e esce dall'app del mittente, viene visualizzata una notifica relativa ai contenuti multimediali che mostra i metadati e i controlli di riproduzione in corso di trasmissione.

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

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

Aggiungi un pulsante Trasmetti

Le API Android MediaRouter 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 nell'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 molto l'aggiunta di MediaRouteButton come Cast button. Prima di tutto devi aggiungere una voce di menu o un elemento 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;
}

Quindi, se Activity eredita da FragmentActivity, puoi aggiungere una 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.

Configura il rilevamento dei dispositivi

Il rilevamento dei dispositivi è completamente gestito dalla CastContext. Durante l'inizializzazione di CastContext, l'app del mittente specifica l'ID applicazione del ricevitore web e, se necessario, può richiedere il filtro dello spazio dei nomi impostando supportedNamespaces in CastOptions. CastContext contiene internamente un riferimento a MediaRouter e avvierà il processo di rilevamento alle seguenti condizioni:

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

Il processo di rilevamento verrà interrotto quando la finestra di dialogo Trasmetti viene chiusa o l'app del mittente entra 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 per la connessione a un dispositivo, l'avvio (o l'unione) di un'app ricevitore web, la connessione a quell'app e l'inizializzazione di un canale di controllo multimediale. Per ulteriori informazioni sulle sessioni di trasmissione e sul ciclo di vita del ricevitore web, consulta la guida al ciclo di vita dell'applicazione del ricevitore web.

Le sessioni sono gestite dal corso SessionManager, a cui può accedere la tua app tramite CastContext.getSessionManager(). Le sessioni individuali sono rappresentate da sottoclassi del corso Session. Ad esempio, CastSession rappresenta le sessioni con dispositivi di trasmissione. La tua app può accedere alla sessione di trasmissione attualmente attiva tramite SessionManager.getCurrentCastSession().

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

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

Per comprendere meglio gli errori di avvio della trasmissione, 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 un comportamento previsto 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 elemento 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
    }

    override fun onResume() {
        super.onResume()
        mCastSession = mSessionManager.currentCastSession
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onPause() {
        super.onPause()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
        mCastSession = null
    }
}
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();
    }
    @Override
    protected void onResume() {
        super.onResume();
        mCastSession = mSessionManager.getCurrentCastSession();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
    @Override
    protected void onPause() {
        super.onPause();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
        mCastSession = null;
    }
}

Trasferimento dello streaming

La conservazione dello stato della sessione è alla base del trasferimento dello streaming, grazie al quale 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 viene interrotta su un dispositivo (la sorgente) 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 il trasferimento o l'espansione dello streaming, registra un Cast.Listener utilizzando CastSession#addCastListener. Quindi chiama il numero CastSession#getCastDevice() durante il callback onDeviceNameChanged.

Per ulteriori informazioni, consulta Trasferimento dello streaming su Web ricevitore.

Riconnessione automatica

Il framework fornisce un elemento ReconnectionService che può essere abilitato dall'app del mittente per gestire la riconnessione in molti casi angolari più discreti, ad esempio:

  • Recupera dopo una perdita temporanea di Wi-Fi
  • Ripristina dalla modalità di sospensione del dispositivo
  • Ripristinare dall'app in background
  • Recuperare se l'app ha subito un arresto anomalo

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

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

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

Come funziona il controllo dei contenuti multimediali

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

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

Tutti i metodi di RemoteMediaClient che inviano richieste al ricevitore web restituiranno un oggetto PendingResult che può essere utilizzato per monitorare la richiesta.

Si prevede che l'istanza di RemoteMediaClient possa essere condivisa da più parti dell'app e da alcuni componenti interni del framework, come i mini controller permanenti 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 classe MediaMetadata rappresenta le informazioni su un elemento multimediale che vuoi trasmettere. L'esempio riportato di seguito 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 la sezione Selezione delle immagini per l'utilizzo di immagini con metadati multimediali.

Carica contenuti multimediali

L'app può caricare un elemento multimediale, come mostrato nel codice che segue. Primo utilizzo di MediaInfo.Builder con i metadati dei contenuti multimediali per creare un'istanza MediaInfo. Scarica il RemoteMediaClient dall'attuale CastSession, quindi carica il MediaInfo in RemoteMediaClient. Usa RemoteMediaClient per riprodurre, mettere in pausa o controllare in altro modo un'app del media player in esecuzione sul web ricevitore.

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 relativa all'utilizzo delle tracce multimediali.

Formato video 4K

Per verificare il formato video del tuo contenuto multimediale, 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 di visualizzazione in pixel. Le varianti del formato 4K sono indicate da costanti HDR_TYPE_*.

Notifiche di controllo remoto a più dispositivi

Quando un utente trasmette, gli altri dispositivi Android sulla stessa rete ricevono una notifica che consente anche di controllare la riproduzione. Chiunque abbia il dispositivo riceve queste notifiche può disattivarle per il dispositivo nell'app Impostazioni di Google > Google Cast > Mostra notifiche di telecomando. Le notifiche includono una scorciatoia per l'app Impostazioni. Per maggiori dettagli, consulta la pagina Notifiche di controllo remoto della trasmissione.

Aggiungi mini controller

In base all'elenco di controllo per la progettazione di trasmissione, un'app del mittente dovrebbe fornire un controllo persistente noto come mini controller che dovrebbe essere visualizzato quando l'utente passa dalla pagina dei contenuti corrente a un'altra parte dell'app del mittente. Il mini controller fornisce un promemoria visibile all'utente della sessione di trasmissione corrente. Toccando il mini controller, l'utente può tornare alla visualizzazione espansa a schermo intero della trasmissione.

Il framework fornisce una vista personalizzata, MiniControllerFragment, che puoi aggiungere alla parte inferiore del file di layout di ciascuna 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 del mittente riproduce un live streaming audio o video, l'SDK mostra automaticamente un pulsante di riproduzione/interruzione al posto di quello nel mini controller.

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

Aggiungi controller espanso

L'elenco di controllo per la progettazione di Google Cast richiede che l'app del 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. Questa è una classe astratta che devi aggiungere a una sottoclasse per aggiungere un pulsante Trasmetti.

Innanzitutto, crea un nuovo file di risorse di menu per il controller espanso al fine di 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 un nuovo corso 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 tua nuova attività nel file 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 i CastOptionsProvider e cambia i NotificationOptions e CastMediaOptions per impostare l'attività target 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 LocalPlayerActivity per visualizzare la nuova attività quando viene caricato il contenuto multimediale remoto:

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 del mittente riproduce un live streaming audio o video, l'SDK mostra automaticamente un pulsante di riproduzione/interruzione al posto di quello di riproduzione/pausa nel controller espanso.

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

Controllo del volume

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

Controllo del volume dei pulsanti fisici

Su Android, i pulsanti fisici del dispositivo mittente possono essere utilizzati per modificare il volume della sessione di trasmissione sul web ricevitore per impostazione predefinita per qualsiasi dispositivo che utilizzi Jelly Bean o versioni successive.

Controllo del volume dei pulsanti fisici prima di Jelly Bean

Per usare i tasti fisici del volume per controllare il volume del dispositivo Web ricevitore sui dispositivi Android più vecchi di Jelly Bean, l'app del mittente deve sostituire dispatchKeyEvent nelle sue 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 controlli multimediali alle notifiche e alla schermata di blocco

Solo su Android, l'elenco di controllo per la progettazione di Google Cast richiede che un'app del mittente implementi i controlli multimediali in una notifica e nella schermata di blocco, in cui il mittente trasmette, ma l'app del mittente non è impostata sullo stato attivo. Il framework fornisce MediaNotificationService e MediaIntentReceiver per aiutare l'app del mittente a creare controlli multimediali in una notifica e nella schermata di blocco.

MediaNotificationService viene eseguito quando il mittente sta trasmettendo e mostra una notifica con la miniatura dell'immagine e 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.

L'app può configurare il controllo di notifiche e contenuti multimediali dalla schermata di blocco tramite NotificationOptions. L'app può configurare quali pulsanti di controllo mostrare nella notifica e quale Activity aprire quando l'utente tocca la notifica. Se le azioni non vengono fornite esplicitamente, 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 multimediali dalle notifiche e dalla schermata di blocco è attiva per impostazione predefinita e può essere disattivata chiamando il numero setNotificationOptions con valore null in CastMediaOptions.Builder. Attualmente, la funzionalità di schermata di blocco è attiva purché la notifica sia 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 del mittente sta riproducendo un live streaming video o audio, l'SDK mostra automaticamente un pulsante di riproduzione/interruzione al posto del pulsante di riproduzione/pausa sul controllo delle notifiche, ma non su quello 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.

Gestire gli errori

È molto importante che le app del mittente gestiscano tutti i callback di errore e decidano la risposta migliore per ogni fase del ciclo di vita della trasmissione. L'app può mostrare finestre di dialogo di errore all'utente o può decidere di interrompere la connessione al web ricevitore.