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 il 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 file binario della libreria di classi Cast e alle risorse associate presenti in fase di runtime sul mittente. Con app mittente o app Cast si intende un'app in esecuzione anche sul mittente. L'app Web ricevitore 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 consentire la transizione tra i vari stati del ciclo di vita dell'app di trasmissione.
Flusso di app
I passaggi seguenti descrivono il flusso di esecuzione tipico di alto livello per un'app Android del mittente:
- Il framework di trasmissione avvia automaticamente il rilevamento dei dispositivi
MediaRouter
in base al ciclo di vita diActivity
. - Quando l'utente fa clic sul pulsante Trasmetti, nella struttura viene visualizzata 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 Web ricevitore sul dispositivo di trasmissione.
- Il framework richiama i callback nell'app del mittente per confermare che l'app Ricevitore web è stata avviata.
- Il framework crea un canale di comunicazione tra il mittente e le app Web Ricevitore.
- 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 nell'interfaccia utente del mittente, il framework passa queste richieste di controllo multimediale al ricevitore web e, quando quest'ultimo invia aggiornamenti sullo 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 dell'SDK Google Cast per Android, consulta la pagina di riferimento sull'API Google Cast Sender per Android. Le sezioni che seguono descrivono i passaggi per aggiungere Cast all'app per Android.
Configurare il file manifest di Android
Il file AndroidManifest.xml dell'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 il target è 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 implementi il tuo 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>
Inizializzare 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 di CastContext
. OptionsProvider
fornisce un'istanza di
CastOptions
che contiene opzioni che influiscono sul comportamento del framework. Il più importante è l'ID applicazione WebRicevitore, che viene utilizzato per filtrare i risultati del rilevamento e per avviare l'app WebRicevitore all'avvio di una sessione di Cast.
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; } }
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 in modo lento quando viene chiamato 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); } }
Widget UX di Cast
Il framework Cast fornisce i widget conformi all'elenco di controllo per la progettazione della trasmissione:
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 mittente 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 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 titolo, nome dello studio di registrazione e un'immagine in miniatura) oppure consente all'utente di disconnettersi dal dispositivo di trasmissione. Il "pulsante Trasmetti" è talvolta chiamato "icona Trasmetti".
Mini controller: quando l'utente sta trasmettendo 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 per controllare la riproduzione.
Controller espanso: quando l'utente trasmette contenuti, se fa clic sulla notifica dei contenuti multimediali o sul mini controller, viene avviato il controller espanso, che mostra i metadati multimediali attualmente in riproduzione e fornisce diversi pulsanti per controllare la riproduzione dei contenuti multimediali.
Notifica: solo Android. Quando l'utente sta trasmettendo contenuti ed esce dall'app per il mittente, viene visualizzata una notifica relativa ai contenuti multimediali che mostra i metadati multimediali e i controlli di riproduzione attualmente trasmessi.
Schermata di blocco: solo su Android. Quando l'utente sta trasmettendo contenuti e va alla schermata di blocco (o il dispositivo va in timeout), 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 include le descrizioni su 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 rende molto facile aggiungere una MediaRouteButton
come Cast button
. 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" />
// 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 elemento
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.
Configura il rilevamento dei dispositivi
Il rilevamento dei dispositivi è completamente gestito da
CastContext
.
Durante l'inizializzazione di CastContext, l'app del mittente specifica l'ID applicazione Web ricevitore e può facoltativamente richiedere l'applicazione di filtri dello spazio dei nomi impostando supportedNamespaces
in CastOptions
.
CastContext
contiene internamente un riferimento a MediaRouter
e avvierà il processo di rilevamento nelle seguenti condizioni:
- In base a un algoritmo progettato per bilanciare la latenza del rilevamento dei dispositivi e l'utilizzo della batteria, il rilevamento verrà avviato automaticamente a volte quando l'app del mittente entra in primo piano.
- La finestra di dialogo Trasmetti è aperta.
- L'SDK Cast sta tentando di recuperare una sessione di Cast.
Il processo di rilevamento viene interrotto quando la finestra di dialogo Trasmetti viene chiusa o 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 stabilità combina i passaggi per connettersi a un dispositivo, avviare (o partecipare) a un'app ricevitore web, connettersi all'app e inizializzare 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 di Web receiver.
Le sessioni sono gestite dal corso SessionManager
, a cui la tua app può accedere tramite CastContext.getSessionManager()
.
Le singole sessioni sono rappresentate dalle sottoclassi della classe
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 chiusura. Il framework tenta automaticamente di riprendere da una chiusura anomala/improvvisa mentre una sessione era attiva.
Le sessioni vengono create e ridotte 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 comportamenti voluti e non devono essere registrati come errori.
Se devi essere a conoscenza delle modifiche dello stato della sessione, puoi implementare
un SessionManagerListener
. Questo esempio rimane in ascolto della disponibilità di un
CastSession
in un Activity
.
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 } }
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
Conservare lo stato della sessione è la base del trasferimento dello streaming, in cui gli utenti possono spostare gli stream audio e video esistenti tra i dispositivi utilizzando i comandi vocali, l'app Google Home o gli smart display. La riproduzione dei contenuti multimediali viene interrotta su un dispositivo (l'origine) e continua su un altro (la destinazione). Qualsiasi dispositivo di trasmissione con il firmware più recente può fungere da sorgente o destinazione in un trasferimento dello streaming.
Per ricevere il nuovo dispositivo di destinazione durante un trasferimento o un'espansione dello streaming, registra una Cast.Listener
utilizzando CastSession#addCastListener
.
Quindi chiama il numero
CastSession#getCastDevice()
durante il callback onDeviceNameChanged
.
Per ulteriori informazioni, vedi Trasferimento dello streaming su WebRicevitore.
Riconnessione automatica
Il framework fornisce un
ReconnectionService
che può essere abilitato dall'app del mittente per gestire la riconnessione in molti casi particolari
ad esempio:
- Recuperare dopo una perdita temporanea del Wi-Fi
- Ripristina dalla sospensione del dispositivo
- Recuperare l'app in background
- Ripristinare se l'app ha avuto un arresto anomalo
Questo servizio è attivo per impostazione predefinita e può essere disattivato in CastOptions.Builder
.
Questo servizio può essere unito automaticamente nel manifest della tua app se l'unione automatica è abilitata nel file Gradle.
Il framework avvierà il servizio quando è in corso una sessione multimediale e lo arresta al termine di quest'ultima.
Come funziona Controllo 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ù pratiche ed evita di dover passare in un GoogleApiClient.
Quando la tua app stabilisce un
CastSession
con un'app Web receiver che supporta lo spazio dei nomi multimediale, il framework crea automaticamente un'istanza di
RemoteMediaClient
; la tua app può
accedere chiamando il metodo getRemoteMediaClient()
sull'istanza
CastSession
.
Tutti i metodi di RemoteMediaClient
che inviano richieste al ricevitore web restituiranno un oggetto PendingResult che può essere utilizzato per tenere traccia della richiesta.
È previsto 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
.
Imposta metadati multimediali
La classe 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.
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 per informazioni sull'utilizzo delle immagini con metadati multimediali.
Carica contenuti multimediali
La tua app può caricare un elemento multimediale, come mostrato nel codice seguente. Utilizza innanzitutto MediaInfo.Builder
con i metadati dei contenuti multimediali per creare un'istanza MediaInfo
. Scarica
RemoteMediaClient
dall'elemento CastSession
corrente, quindi carica MediaInfo
in quel
RemoteMediaClient
. Utilizza RemoteMediaClient
per riprodurre, mettere in pausa e controllare in altro modo un'app di media player 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 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 da remoto per più dispositivi
Quando un utente trasmette, gli altri dispositivi Android sulla stessa rete riceveranno una notifica per consentire anche loro di controllare la riproduzione. Chiunque riceva queste notifiche dal dispositivo può disattivarle nell'app Impostazioni in Google > Google Cast > Mostra notifiche di controllo remoto. Le notifiche includono una scorciatoia per l'app Impostazioni. Per maggiori dettagli, vedi Notifiche di controllo remoto della trasmissione.
Aggiungi mini controller
Secondo l'elenco di controllo per la progettazione di trasmissione, l'app mittente deve fornire un controllo permanente noto come mini controller che dovrebbe essere visualizzato quando l'utente esce dalla pagina dei contenuti corrente per passare a un'altra parte dell'app 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 del controller a schermo intero.
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" />
Quando l'app del mittente riproduce un live streaming video o audio, 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 dei sottotitoli 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 mittente fornisca un controller espanso per i contenuti multimediali da trasmettere. Il controller espanso è una versione a schermo intero del mini controller.
L'SDK Cast fornisce un widget per il controller espanso chiamato
ExpandedControllerActivity
.
Si tratta di una classe astratta di cui devi eseguire una sottoclasse per aggiungere un pulsante Trasmetti.
Innanzitutto, crea un nuovo file di risorse di menu per il controller espanso in modo da 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
.
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 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 CastOptionsProvider
e cambia 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 vengono caricati i contenuti multimediali remoti:
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 l'app del mittente riproduce un live streaming video o audio, l'SDK mostra 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 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 del mittente e del ricevitore web in modo che l'interfaccia utente del mittente indichi sempre il volume specificato dal ricevitore web.
Controllo del volume tramite tasto fisico
Su Android, i pulsanti fisici sul dispositivo del mittente possono essere utilizzati per impostazione predefinita per regolare il volume della sessione di trasmissione sul ricevitore web per qualsiasi dispositivo che utilizza Jelly Bean o versioni successive.
Controllo del volume del tasto fisico prima di Jelly Bean
Per utilizzare i tasti fisici del volume per regolare il volume del ricevitore web sui dispositivi
Android precedenti a Jelly Bean, l'app del mittente deve eseguire l'override di
dispatchKeyEvent
nelle 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 a notifiche e 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 nella posizione in cui il mittente sta trasmettendo, 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 eseguito quando il mittente trasmette 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 utente
dalla notifica.
La tua app può configurare le notifiche e il controllo multimediale dalla schermata di blocco tramite
NotificationOptions
.
La tua app può configurare quali pulsanti di controllo mostrare nella notifica e
quali 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
.
// 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();
La visualizzazione dei controlli multimediali nelle notifiche e nella schermata di blocco è attiva per impostazione predefinita e può essere disattivata chiamando setNotificationOptions
con null in CastMediaOptions.Builder
.
Al momento, la funzionalità di schermata di blocco è attiva purché le notifiche siano attive.
// ... 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 l'app del mittente riproduce un live streaming video o audio, l'SDK mostra automaticamente un pulsante di riproduzione/interruzione al posto del pulsante Riproduci/Pausa nel controllo notifica, ma non in quello della schermata di blocco.
Nota: per visualizzare i controlli della schermata di blocco sui dispositivi con pre-Lollipop,
RemoteMediaClient
richiederà automaticamente lo stato attivo dell'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 della trasmissione. L'app può mostrare finestre di dialogo di errore all'utente o può decidere di interrompere la connessione al ricevitore web.