Questa guida per gli sviluppatori descrive come aggiungere il supporto di Google Cast alla tua app per mittenti Android utilizzando l'SDK Android Sender.
Il dispositivo mobile o laptop è il mittente che controlla la riproduzione, mentre il dispositivo Google Cast è il destinatario che mostra i contenuti sulla TV.
Il framework del mittente si riferisce al programma binario della libreria di classi di trasmissione 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 vari stati del ciclo di vita dell'app di trasmissione.
Flusso dell'app
I seguenti passaggi descrivono il tipico flusso di esecuzione di alto livello per un'app Android per il mittente:
- Il framework di trasmissione avvia automaticamente il rilevamento del dispositivo
MediaRouter
in base al ciclo di vita diActivity
. - 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 l'app web Receiver è stata avviata.
- Il framework crea un canale di comunicazione tra le app di invio e di ricezione 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 trasferisce le richieste di controllo dei contenuti multimediali al destinatario web e, quando quest'ultimo invia gli aggiornamenti dello 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 la documentazione relativa al riferimento dell'API Google Cast Sender per Android. Le sezioni che seguono descrivono i passaggi per aggiungere Cast all'app Android.
Configura il manifest di Android
Il file AndroidManifest.xml della tua app richiede la configurazione dei seguenti elementi per l'SDK Cast:
usa-sdk
Imposta il livello API minimo 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 della tua app in base alla versione minima dell'SDK Android. Ad esempio, se non implementi il tuo tema, devi utilizzare una variante di Theme.AppCompat
quando scegli come target una versione minima dell'SDK Android pre-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.
La tua 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 influiscono sul comportamento del framework. Il più importante è l'ID applicazione Web Receiver, che viene utilizzato per filtrare i risultati del rilevamento e lanciare l'app Web Receiver all'avvio di una sessione di trasmissione.
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 } }
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; } }
Nel file AndroidManifest.xml dell'app del mittente devi dichiarare il nome completo dell'elemento OptionsProvider
implementato come campo di metadati:
<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 chiamata l'elemento CastContext.getSharedInstance()
.
class MyActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { val castContext = CastContext.getSharedInstance(this) } }
public class MyActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { CastContext castContext = CastContext.getSharedInstance(this); } }
I widget dell'esperienza di trasmissione
Il framework di Cast fornisce i widget conformi all'elenco di controllo per la progettazione di Cast:
Overlay di lancio: 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, che elenca i dispositivi rilevati. Quando l'utente fa clic sul pulsante Trasmetti mentre il dispositivo è connesso, visualizza i metadati multimediali correnti (ad esempio titolo, 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 e si è allontanato dalla pagina dei contenuti corrente o da un 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 dei contenuti 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 ed esce dall'app del mittente, viene visualizzata una notifica multimediale con i metadati della trasmissione e i controlli di riproduzione attualmente in fase di trasmissione.
Schermata di blocco: solo Android. Quando l'utente trasmette contenuti e si sposta (o scade il tempo sul dispositivo) alla schermata di blocco, viene visualizzato un controllo della schermata di blocco dei contenuti multimediali 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 all'app.
Aggiungi un pulsante Trasmetti
Le API Android MediaRouter
sono progettate per consentire la visualizzazione e la riproduzione di contenuti multimediali sui dispositivi secondari.
Le app Android che utilizzano l'API MediaRouter
dovrebbero 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 MediaRouteButton
come Cast button
molto semplice. Devi prima aggiungere una voce al 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" />
// 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 }
// 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>
// 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) }
// 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 la pagina Personalizzare il pulsante Trasmetti.
Configurare il rilevamento dei dispositivi
La scoperta dei dispositivi è completamente gestita da CastContext
.
Durante l'inizializzazione di CastContext, l'app del mittente specifica l'ID applicazione del ricevitore web e, facoltativamente, può richiedere un filtro dello spazio dei nomi impostando supportedNamespaces
in CastOptions
.
CastContext
contiene un riferimento al MediaRouter
internamente e avvierà il processo di rilevamento quando l'app del mittente entra in primo piano e si interrompe quando l'app del mittente entra in background.
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 } }
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 una sessione di trasmissione, la cui definizione combina i passaggi per connettersi a un dispositivo, lanciare (o partecipare) un'app Web Receiver, connettersi a quell'app e inizializzare un canale di controllo dei contenuti multimediali. 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 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 i 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 di sessione, come creazione, sospensione, ripresa e terminazione. Il framework tenta automaticamente di riprendere da una terminazione anomala/improvvisa durante la sessione in corso.
Le sessioni vengono create e eliminate automaticamente in risposta ai gesti degli utenti dalle finestre di dialogo di MediaRouter
.
Se vuoi conoscere i cambiamenti di stato della sessione, puoi implementare un SessionManagerListener
. Questo esempio ascolta la disponibilità di un elemento CastSession
in un elemento Activity
.
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 } }
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 streaming, durante il quale gli utenti possono spostare gli stream audio e video esistenti sui 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 origini o destinazioni in un trasferimento di flussi.
Per utilizzare il nuovo dispositivo di destinazione durante un trasferimento o un'espansione del flusso, registra un 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 servizio ReconnectionService
che può essere abilitato dall'app del mittente per gestire la riconnessione in molti casi d'angolo, ad esempio:
- Esegui il ripristino in seguito a una perdita temporanea del Wi-Fi
- Recupera dopo il sonno del dispositivo
- Recupera l'app in background
- Ripristinare l'app se si è arrestato in modo anomalo
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 è attivata nel file Gradle.
Il framework avvierà il servizio quando è in corso una sessione multimediale e lo interromperà al termine della sessione.
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 insieme di API più comode ed evita di passare in un client GoogleApi.
Quando la tua app stabilisce una risorsa CastSession
con un'app Ricevitore web che supporta lo spazio dei nomi dei contenuti multimediali, verrà creata automaticamente un'istanza di RemoteMediaClient
dal framework, la tua app potrà accedervi chiamando il metodo getRemoteMediaClient()
sull'istanza CastSession
.
Tutti i metodi RemoteMediaClient
che emettono richieste al destinatario web restituiranno un oggetto PendingResult che può essere utilizzato per monitorare la richiesta.
Si prevede che l'istanza di RemoteMediaClient
potrebbe essere condivisa da più parti dell'app e da alcuni componenti interni del framework, ad esempio 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 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 di MediaMetadata di un film e imposta il titolo, il sottotitolo e due immagini.
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))))
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 i metadati multimediali.
Carica elemento multimediale
La tua app può caricare un elemento multimediale, come mostrato nel codice seguente. Per prima cosa, utilizza MediaInfo.Builder
con i metadati dei media per creare un'istanza MediaInfo
. Recupera il codice RemoteMediaClient
dall'attuale CastSession
, quindi carica MediaInfo
in quel RemoteMediaClient
. Usa RemoteMediaClient
per riprodurre, mettere in pausa e controllare
l'app di un lettore multimediale in esecuzione sul ricevitore Web.
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())
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 del video, utilizza getVideoInfo()
in MediaStatus per ottenere l'istanza corrente di VideoInfo
.
Questa istanza contiene il tipo di formato HDR TV e l'altezza e la larghezza di visualizzazione in pixel. Le varianti del formato 4K sono indicate dalle costanti
HDR_TYPE_*
.
Notifiche del 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 ricevuto queste notifiche sul tuo dispositivo può disattivarle nell'app Impostazioni di Google > 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 Google Cast.
Aggiungi mini controller
In base all'elenco di controllo per la trasmissione dei contenuti, un'app di invio deve fornire un controllo permanente noto come mini controller che dovrebbe essere visualizzato quando l'utente esce dalla pagina di contenuti corrente in un'altra parte dell'app di trasmissione. Il mini controller fornisce un promemoria visibile all'utente della sessione di trasmissione corrente. Dopo aver toccato il mini controller, l'utente può tornare alla visualizzazione a schermo intero del controller Cast.
Il framework fornisce una visualizzazione 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 la tua app mittente sta riproducendo un video stream o un live streaming audio, 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.
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
.
Questa è una classe astratta che devi aggiungere a una sottoclasse per aggiungere un pulsante Trasmetti.
Crea innanzitutto 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 estende ExpandedControllerActivity
.
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 } }
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 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 NotificationOptions
e CastMediaOptions
per impostare l'attività target sulla nuova attività:
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() }
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:
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() ) }
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 la tua app del mittente sta riproducendo un video stream o un live streaming 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 i pulsanti da visualizzare e aggiungi pulsanti personalizzati, consulta Personalizzare il controller espanso.
Controllo del volume
Il framework gestisce automaticamente il volume dell'app del mittente, che sincronizza automaticamente le app del mittente e del ricevitore web in modo che l'interfaccia utente del mittente registri sempre il volume specificato dal ricevitore web.
Controllo del volume del pulsante fisico
Su Android, i pulsanti fisici sul dispositivo del mittente possono essere utilizzati per regolare 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 del ricevitore web sui dispositivi Android più vecchi di Jelly Bean, l'app del mittente deve eseguire l'override di dispatchKeyEvent
nella propria Attività e chiamare CastContext.onDispatchVolumeKeyEventBeforeJellyBean()
:
class MyActivity : FragmentActivity() { override fun dispatchKeyEvent(event: KeyEvent): Boolean { return (CastContext.getSharedInstance(this) .onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event)) } }
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 di mittenti di implementare i controlli multimediali in una notifica e nella schermata di blocco, in cui il mittente trasmette, ma l'app del mittente non è attiva. Il
framework fornisce
MediaNotificationService
e
MediaIntentReceiver
per aiutare l'app dei mittenti a creare controlli multimediali in una notifica e nella
schermata di blocco.
MediaNotificationService
viene eseguito 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 degli utenti dalla notifica.
La tua app può configurare le notifiche e il controllo 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 vengono esplicitamente specificate, verranno utilizzati i valori predefiniti: MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK
e MediaIntentReceiver.ACTION_STOP_CASTING
.
// 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()
// 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 delle notifiche e della schermata di blocco sono attivi per
impostazione predefinita e possono essere disattivati chiamando
setNotificationOptions
con null in
CastMediaOptions.Builder
.
Attualmente la funzionalità di blocco schermo viene attivata se la notifica è attiva.
// ... 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()
// ... 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 la tua app mittente sta riproducendo un video stream o un live streaming audio, l'SDK visualizza 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 pre-Lollipop,
RemoteMediaClient
richiederà automaticamente la messa a fuoco audio per tuo conto.
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 Cast. L'app può mostrare finestre di dialogo di errore all'utente oppure decidere di interrompere la connessione al ricevitore web.