Integrare Cast Into nella tua app Android

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

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

Il framework mittente si riferisce al programma binario della libreria di classe Cast e alle risorse associate presenti in fase di esecuzione sul mittente. L'app mittente o l'app Cast si riferisce a un'app in esecuzione anche sul mittente. L'app Ricevitore web si riferisce all'applicazione HTML in esecuzione sul dispositivo compatibile con Google Cast.

Il framework del mittente utilizza un design di callback asincrono per informare l'app del mittente degli eventi e per passare da uno stato all'altro del ciclo di vita dell'app Cast.

Flusso dell'app

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

  • Il framework di Cast avvia automaticamente il rilevamento dei dispositivi MediaRouter in base al ciclo di vita di Activity.
  • Quando l'utente fa clic sul pulsante Trasmetti, il framework presenta la 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 Ricevitore web sul dispositivo di trasmissione.
  • Il framework richiama i callback nell'app del mittente per confermare che è stata avviata l'app del destinatario web.
  • Il framework crea un canale di comunicazione tra le app del mittente e del ricevitore web.
  • Il framework utilizza il canale di comunicazione per caricare e controllare la riproduzione dei contenuti multimediali sul ricevitore web.
  • Il framework sincronizza lo stato di riproduzione dei contenuti multimediali tra il mittente e il ricevitore web: quando l'utente esegue azioni dell'interfaccia utente del mittente, il framework trasmette le richieste di controllo dei contenuti multimediali al ricevitore web e, quando il ricevitore invia aggiornamenti sullo stato dei contenuti multimediali, il framework aggiorna lo stato della UI 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 nell'SDK Google Cast per Android, consulta il riferimento dell'API Google Cast Sender per Android. Le seguenti sezioni descrivono la procedura per aggiungere Google Cast alla tua app Android.

Configurare il manifest di Android

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

usa-sdk

Imposta i livelli API minimi e target che l'SDK Cast supporta. Attualmente il minimo è il livello API 19 e il target è il livello API 28.

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

android:tema

Imposta il tema dell'app in base alla versione minima dell'SDK Android. Ad esempio, se non implementi il tema, devi utilizzare una variante di 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 singleton CastContext. OptionsProvider fornisce un'istanza di CastOptions che contiene opzioni che influenzano il comportamento del framework. Il più importante è l'ID applicazione Ricevitore web, che viene utilizzato per filtrare i risultati del rilevamento e per lanciare l'app Ricevitore web all'avvio di 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 del codice 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 richiamato 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 dell'esperienza utente di Google Cast

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

  • Overlay introduttivo: il framework fornisce una vista personalizzata, IntroductoryOverlay, che viene mostrata all'utente per richiamare 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 quando viene rilevato un ricevitore che supporta la tua app. Quando l'utente fa clic per la prima volta sul pulsante Trasmetti, 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 multimediali correnti (ad esempio il titolo, il nome dello studio di registrazione e un'immagine in miniatura) oppure consente all'utente di disconnettersi dal dispositivo di trasmissione.

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

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

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

  • Schermata di blocco: solo Android. Quando l'utente trasmette contenuti e naviga (o si verifica il timeout del dispositivo) nella schermata di blocco, viene visualizzato un controllo per la schermata di blocco dei contenuti multimediali che mostra i metadati della trasmissione e i controlli di riproduzione attualmente trasmessi.

La seguente guida include 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 Android che utilizzano l'API MediaRouter devono includere un pulsante Trasmetti nella propria interfaccia utente, per consentire agli utenti di selezionare un percorso multimediale per riprodurre i contenuti multimediali su un dispositivo secondario, ad esempio un dispositivo di trasmissione.

Il framework rende l'aggiunta di una MediaRouteButton come Cast button molto facile. Devi prima 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;
}

Quindi, se il tuo Activity eredita da FragmentActivity, puoi aggiungere un MediaRouteButton al tuo 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 usando un tema, consulta la pagina Personalizzare il pulsante Trasmetti.

Configurare 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 Ricevitore web e, facoltativamente, può richiedere un filtro dello spazio dei nomi impostando supportedNamespaces in CastOptions. CastContext contiene un riferimento interno a MediaRouter, avvia il processo di rilevamento quando l'app del mittente entra in primo piano e si interrompe quando 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 di trasmissione, la cui combinazione combina i passaggi per connettersi a un dispositivo, lanciare (o partecipare) a un'app web Ricevitore, connettersi a quell'app e inizializzare un canale di controllo multimediale. Consulta la Guida al ciclo di vita dell'applicazione per il ricevitore web per ulteriori informazioni sulle sessioni di trasmissione e sul ciclo di vita del ricevitore web.

Le sessioni sono gestite dal corso SessionManager, a cui la tua app può accedere tramite CastContext.getSessionManager(). Le singole sessioni sono rappresentate dalle 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 della sessione, come creazione, sospensione, ripresa e terminazione. Il framework tenta automaticamente di riprendere da una terminazione anomala/improvvisa mentre una sessione era attiva.

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

Se devi conoscere i cambiamenti di stato per la sessione, puoi implementare SessionManagerListener. Questo esempio ascolta la disponibilità di un CastSession in un Activity.

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

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            invalidateOptionsMenu()
        }

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

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

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mSessionManager = CastContext.getSharedInstance(this).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 CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionEnded(CastSession session, int error) {
            finish();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSessionManager = CastContext.getSharedInstance(this).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

Conservare lo stato della sessione è la base del trasferimento dello stream, in cui gli utenti possono spostare stream audio e video esistenti su più dispositivi utilizzando i comandi vocali, l'app Google Home o gli smart display. La riproduzione dei contenuti multimediali viene interrotta su un dispositivo (l'origine) e continua su un altro (la destinazione). Tutti i dispositivi di trasmissione con il firmware più recente possono essere utilizzati come origini o destinazioni in un trasferimento di streaming.

Per acquistare il nuovo dispositivo di destinazione durante un trasferimento o un'espansione del flusso, registra una Cast.Listener utilizzando CastSession#addCastListener. Quindi chiama CastSession#getCastDevice() durante il callback onDeviceNameChanged.

Per ulteriori informazioni, consulta la pagina Trasferimento dello streaming sul ricevitore web.

Riconnessione automatica

Il framework fornisce un elemento ReconnectionService che può essere attivato dall'app del mittente per gestire la riconnessione in molti casi d'angolo, ad esempio:

  • Esegui il ripristino da una perdita temporanea della rete Wi-Fi
  • Ripristina dal sonno dispositivo
  • Esegui il ripristino dall'app in background
  • Ripristino in caso di arresto anomalo dell'app

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

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

Il framework avvia il servizio al termine di 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 client GoogleApiClient.

Quando la tua app stabilisce una CastSession con un'app ricevitore web che supporta lo spazio dei nomi dei contenuti multimediali, viene creata automaticamente un'istanza di RemoteMediaClient dal framework, che può accedere alla tua app chiamando il metodo getRemoteMediaClient() sull'istanza CastSession.

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

Si prevede che l'istanza di RemoteMediaClient possa essere condivisa da più parti della tua app e in effetti 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.

Imposta metadati multimediali

Il corso MediaMetadata 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 la sezione Selezione delle immagini sull'utilizzo delle immagini con metadati multimediali.

Carica contenuti multimediali

La tua app può caricare un elemento multimediale, come mostrato nel codice seguente. Prima di tutto, utilizza MediaInfo.Builder con i metadati dei media per creare un'istanza MediaInfo. Prendi la RemoteMediaClient dall'attuale CastSession, quindi carica MediaInfo in tale RemoteMediaClient. Utilizza RemoteMediaClient per riprodurre, mettere in pausa e controllare in altro modo l'app di un lettore multimediale in esecuzione sul ricevitore web.

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 di tracce multimediali.

Formato video 4K

Per verificare il formato video dei tuoi contenuti multimediali, utilizza getVideoInfo() in MediaStatus per ottenere l'istanza attuale 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 su più dispositivi

Quando un utente trasmette contenuti, altri dispositivi Android sulla stessa rete ricevono una notifica che consente anche di controllare la riproduzione. Chiunque abbia un dispositivo che riceve tali notifiche può disattivarle nell'app Impostazioni su Google & Cast; Mostra notifiche di controllo remoto. Le notifiche includono una scorciatoia all'app Impostazioni. Per maggiori dettagli, consulta la pagina Notifiche di controllo remoto di trasmissione.

Aggiungi mini controller

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

Il framework fornisce una vista personalizzata, MiniControllerFragment, che puoi aggiungere alla fine 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" />

Durante la riproduzione di un live streaming di video o audio da parte dell'app del mittente, l'SDK mostra 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 la sezione Personalizzare il mini controller.

Aggiungi controller espanso

L'elenco di controllo per la progettazione di Google Cast richiede che un'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. Si tratta di una classe astratta che devi aggiungere per aggiungere un pulsante Trasmetti.

Innanzitutto, crea un nuovo file di risorse di menu affinché il controller espanso fornisca 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 includa 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 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 il campo CastOptionsProvider, quindi modifica 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 LocalPlayerActivity loadRemoteMedia per visualizzare la nuova attività quando viene caricato il supporto 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 di un video o audio, 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, scegli quali pulsanti visualizzare, e aggiungi pulsanti personalizzati, consulta la sezione Personalizzare il controller espanso.

Controllo del volume

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

Controllo fisico del pulsante dei volumi

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

Controllo fisico del volume dei pulsanti prima di Jelly Bean

Per utilizzare i tasti del volume fisico per controllare il volume del dispositivo ricevitore web su dispositivi Android più vecchi di Jelly Bean, l'app del mittente deve sostituire dispatchKeyEvent nella propria 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);
    }
}

Aggiungi controlli multimediali alla notifica e alla schermata di blocco

Solo su Android, l'elenco di controllo per la progettazione di Google Cast richiede a un'app del mittente di implementare i controlli multimediali in una notifica e nella schermata di blocco, dove il mittente trasmette, ma l'app del mittente non è attiva. 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 eseguita durante la trasmissione del mittente e mostra una notifica con miniatura dell'immagine e informazioni sull'elemento corrente della trasmissione, un pulsante di riproduzione/pausa e un pulsante di interruzione.

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

L'app può configurare il controllo delle notifiche e dei contenuti multimediali dalla schermata di blocco tramite NotificationOptions. L'app può configurare i pulsanti di controllo da mostrare nella notifica e quale Activity aprire quando la notifica viene toccata dall'utente. Se le azioni non sono indicate 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();

I controlli multimediali dalla notifica e dalla schermata di blocco sono attivi per impostazione predefinita e possono essere disattivati chiamando setNotificationOptions con null in CastMediaOptions.Builder. Al momento, la funzionalità di blocco schermo viene attivata 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();

Durante la riproduzione dell'audio in live streaming da parte dell'app del mittente, l'SDK mostra automaticamente un pulsante di riproduzione/interruzione al posto del pulsante di riproduzione/pausa sul controllo notifiche, ma non sul controllo della schermata di blocco.

Nota: per visualizzare i controlli della schermata di blocco sui dispositivi pre-Lollipop, RemoteMediaClient richiederà automaticamente l'audio per te.

Gestire gli errori

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