Questa guida per gli sviluppatori descrive come aggiungere il supporto di Google Cast alla tua app mittente per Android utilizzando l'SDK mittente per Android.
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. L'app mittente o l'app Cast fa riferimento a un'app in esecuzione anche sul mittente. L'app Web Receiver fa riferimento 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 mittente degli eventi e per passare da uno stato all'altro del ciclo di vita dell'app Cast.
Flusso dell'app
I passaggi seguenti descrivono il tipico flusso di esecuzione di alto livello per un'app per Android mittente:
- Il framework Cast avvia automaticamente il rilevamento dei dispositivi
MediaRouterin 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 Web Receiver sul dispositivo di trasmissione.
- Il framework richiama i callback nell'app mittente per confermare che l'app Web Receiver è stata avviata.
- Il framework crea un canale di comunicazione tra le app mittente e 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 nell'interfaccia utente del mittente, il framework trasmette queste richieste di controllo multimediale al ricevitore web e, quando il ricevitore web invia aggiornamenti dello stato dei contenuti multimediali, il framework aggiorna lo stato dell'interfaccia utente del mittente.
- Quando l'utente fa clic sul pulsante Trasmetti per disconnettersi dal dispositivo di trasmissione, il framework disconnette l'app mittente dal ricevitore web.
Per un elenco completo di tutte le classi, i metodi e gli eventi nell'SDK Android di Google Cast, consulta il Riferimento API Google Cast Sender per Android. Le sezioni seguenti illustrano i passaggi per aggiungere Cast alla tua app per Android.
Configura il file manifest 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. Al momento, il livello API minimo è 24 e il livello API target è 35.
<uses-sdk
android:minSdkVersion="24"
android:targetSdkVersion="35" />
android:theme
Imposta il tema dell'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>
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 del ricevitore web, che viene utilizzato per filtrare i risultati del rilevamento e per avviare l'app del ricevitore web quando viene avviata 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; } }
Devi dichiarare il nome completo dell'interfaccia OptionsProvider implementata come campo dei metadati nel file AndroidManifest.xml dell'app mittente:
<application>
...
<meta-data
android:name=
"com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.foo.CastOptionsProvider" />
</application>
CastContext viene inizializzato in modo lazy 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); } }
I widget UX di Cast
Il framework Cast fornisce i widget conformi all'elenco di controllo per la progettazione di Cast:
Overlay introduttivo: il framework fornisce una visualizzazione personalizzata,
IntroductoryOverlay, che viene mostrata all'utente per attirare l'attenzione sul pulsante Trasmetti la prima volta che è disponibile un ricevitore. L'app mittente può personalizzare il testo e la posizione del testo del titolo.Pulsante Trasmetti: il pulsante Trasmetti è visibile indipendentemente dalla disponibilità di 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 dei contenuti multimediali correnti (ad esempio titolo, nome dello studio di registrazione e un'immagine in miniatura) o l'utente può disconnettersi dal dispositivo di trasmissione. Il "pulsante Trasmetti" viene talvolta chiamato "icona Trasmetti".
Mini controller: quando l'utente trasmette contenuti e si è allontanato dalla pagina dei contenuti corrente o dal controller espanso per passare a un'altra schermata nell'app mittente, il mini controller viene visualizzato nella parte inferiore dello schermo per consentire all'utente di visualizzare i metadati dei contenuti multimediali attualmente trasmessi e di controllare la riproduzione.
Controller espanso: quando l'utente trasmette contenuti, se fa clic sulla notifica multimediale o sul mini controller, viene avviato il controller espanso, che mostra i metadati dei contenuti multimediali in riproduzione e fornisce diversi pulsanti per controllare la riproduzione dei contenuti multimediali.
Notifica: solo Android. Quando l'utente trasmette contenuti e si allontana dall'app mittente, viene visualizzata una notifica multimediale che mostra i metadati dei contenuti multimediali attualmente trasmessi e i controlli di riproduzione.
Schermata di blocco: solo Android. Quando l'utente trasmette contenuti e passa 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 attualmente trasmessi e i controlli di riproduzione.
La seguente guida include 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 come parte della loro interfaccia utente, per consentire agli utenti di selezionare un percorso multimediale per riprodurre contenuti multimediali su un dispositivo secondario, ad esempio un dispositivo di trasmissione.
Il framework semplifica l'aggiunta di un
MediaRouteButton
come
Cast button. 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; }
Poi, 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 Personalizza il pulsante Trasmetti.
Configura il rilevamento dei dispositivi
Il rilevamento dei dispositivi è completamente gestito da
CastContext.
Durante l'inizializzazione di CastContext, l'app mittente specifica l'ID applicazione Web Receiver e, facoltativamente, può richiedere il filtro dello spazio dei nomi impostando supportedNamespaces in CastOptions.
CastContext contiene un riferimento a MediaRouter internamente e avvierà
il processo di rilevamento alle seguenti condizioni:
- In base a un algoritmo progettato per bilanciare la latenza di rilevamento del dispositivo e l'utilizzo della batteria, il rilevamento verrà avviato automaticamente di tanto in tanto quando l'app mittente entra in primo piano.
- La finestra di dialogo Trasmetti è aperta.
- L'SDK Cast sta tentando di recuperare una sessione di trasmissione.
Il processo di rilevamento verrà interrotto quando la finestra di dialogo di trasmissione viene chiusa o l'app 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 sessione di trasmissione, la cui creazione combina i passaggi di connessione a un dispositivo, avvio (o partecipazione) di un'app Web Receiver, connessione a quell'app e inizializzazione di un canale di controllo multimediale. Per saperne di più sulle sessioni di trasmissione e sul ciclo di vita del ricevitore web, consulta la guida al ciclo di vita delle applicazioni del ricevitore web.
Le sessioni sono gestite dalla classe
SessionManager,
a cui la tua app può accedere tramite
CastContext.getSessionManager().
Le singole sessioni sono rappresentate da sottoclassi della classe
Session.
Ad esempio,
CastSession
rappresenta le sessioni con dispositivi di trasmissione. La tua app può accedere alla sessione Cast attualmente attiva tramite SessionManager.getCurrentCastSession().
La tua app può utilizzare la classe SessionManagerListener per monitorare gli eventi della sessione, come la creazione, la sospensione, la ripresa e la terminazione. Il framework tenta automaticamente di riprendere la sessione dopo una terminazione anomala/improvvisa mentre era attiva.
Le sessioni vengono create e chiuse automaticamente in risposta alle attivazioni dall'utente
dalle finestre di dialogo di MediaRouter.
Per comprendere meglio gli errori di avvio di Cast, le app possono utilizzare
CastContext#getCastReasonCodeForCastStatusCode(int)
per convertire l'errore di avvio della sessione in
CastReasonCodes.
Tieni presente che alcuni errori di avvio della sessione (ad es. CastReasonCodes#CAST_CANCELLED)
sono comportamenti previsti e non devono essere registrati come errori.
Se devi conoscere i cambiamenti di stato della sessione, puoi implementare
un SessionManagerListener. Questo esempio ascolta la 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 mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java) } override fun onResume() { super.onResume() mCastSession = mSessionManager.currentCastSession } override fun onDestroy() { super.onDestroy() mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java) } }
public class MyActivity extends Activity { private CastContext mCastContext; private CastSession mCastSession; private SessionManager mSessionManager; private SessionManagerListener<CastSession> mSessionManagerListener = new SessionManagerListenerImpl(); private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> { @Override public void onSessionStarting(CastSession session) {} @Override public void onSessionStarted(CastSession session, String sessionId) { invalidateOptionsMenu(); } @Override public void onSessionStartFailed(CastSession session, int error) { int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error); // Handle error } @Override public void onSessionSuspended(CastSession session, int reason) {} @Override public void onSessionResuming(CastSession session, String sessionId) {} @Override public void onSessionResumed(CastSession session, boolean wasSuspended) { invalidateOptionsMenu(); } @Override public void onSessionResumeFailed(CastSession session, int error) {} @Override public void onSessionEnding(CastSession session) {} @Override public void onSessionEnded(CastSession session, int error) { finish(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCastContext = CastContext.getSharedInstance(this); mSessionManager = mCastContext.getSessionManager(); mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class); } @Override protected void onResume() { super.onResume(); mCastSession = mSessionManager.getCurrentCastSession(); } @Override protected void onDestroy() { super.onDestroy(); mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class); } }
Trasferimento dello streaming
La conservazione dello stato della sessione è alla base del trasferimento dello streaming, che consente agli utenti di spostare gli stream audio e video esistenti tra i dispositivi utilizzando i comandi vocali, l'app Google Home o gli smart display. La riproduzione dei contenuti multimediali si interrompe su un dispositivo (la sorgente) e continua su un altro (la destinazione). Qualsiasi dispositivo di trasmissione con il firmware più recente può fungere da origine o destinazione in un trasferimento dello streaming.
Per ottenere il nuovo dispositivo di destinazione durante un trasferimento o un'espansione dello streaming,
registra un
Cast.Listener
utilizzando
CastSession#addCastListener.
Poi chiama
CastSession#getCastDevice()
durante il callback onDeviceNameChanged.
Per saperne di più, consulta Trasferimento dello streaming su Web Receiver.
Riconnessione automatica
Il framework fornisce un
ReconnectionService
che può essere attivato dall'app mittente per gestire la riconnessione in molti casi limite
complessi, ad esempio:
- Eseguire il ripristino in caso di perdita temporanea del Wi-Fi
- Ripristino dalla modalità di sospensione del dispositivo
- Ripristino dall'esecuzione in background dell'app
- Eseguire il ripristino in caso di arresto anomalo dell'app
Questo servizio è attivo per impostazione predefinita e può essere disattivato in
CastOptions.Builder.
Questo servizio può essere unito automaticamente al manifest della tua app se l'unione automatica è abilitata nel file gradle.
Il framework avvierà il servizio quando è presente una sessione multimediale e lo interromperà 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 fornisce la stessa funzionalità in un insieme di API più pratiche ed
evita di dover passare un GoogleApiClient.
Quando la tua app stabilisce una
CastSession
con un'app Ricevitore web che supporta lo spazio dei nomi multimediale, un'istanza di
RemoteMediaClient verrà creata automaticamente dal framework; la tua app può
accedervi chiamando il metodo getRemoteMediaClient() nell'istanza
CastSession.
Tutti i metodi di RemoteMediaClient che inviano richieste al ricevitore web restituiranno un oggetto PendingResult che può essere utilizzato per monitorare la richiesta.
Si prevede che l'istanza di RemoteMediaClient possa essere condivisa da più parti della tua app e, di fatto, da alcuni componenti interni del framework, come i mini controller persistenti e il servizio di notifica.
A questo scopo, questa istanza supporta la registrazione di più istanze di
RemoteMediaClient.Listener.
Imposta i metadati dei contenuti 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 sull'utilizzo delle immagini con i metadati dei contenuti multimediali.
Carica contenuti multimediali
La tua app può caricare un elemento multimediale, come mostrato nel codice seguente. Per prima cosa, utilizza MediaInfo.Builder con i metadati dei contenuti multimediali per creare un'istanza MediaInfo. Recupera
RemoteMediaClient
da CastSession corrente, quindi carica MediaInfo in RemoteMediaClient. Utilizza RemoteMediaClient per riprodurre, mettere in pausa e controllare in altro modo un'app di media player in esecuzione sul Web Receiver.
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 corrente di
VideoInfo.
Questa istanza contiene il tipo di formato TV HDR e l'altezza e la larghezza del display in pixel. Le varianti del formato 4K sono indicate dalle costanti
HDR_TYPE_*.
Notifiche di controllo remoto su più dispositivi
Quando un utente trasmette, gli altri dispositivi Android sulla stessa rete riceveranno una notifica per consentire anche a loro di controllare la riproduzione. Chiunque riceva queste notifiche sul proprio dispositivo può disattivarle per quel dispositivo nell'app Impostazioni in Google > Google Cast > Mostra notifiche del telecomando. Le notifiche includono una scorciatoia per l'app Impostazioni. Per maggiori dettagli, consulta Notifiche di controllo remoto di Cast.
Aggiungi mini controller
In base all'elenco di controllo per la progettazione di Cast, un'app mittente deve fornire un controllo persistente noto come mini controller che deve essere visualizzato quando l'utente esce dalla pagina dei contenuti corrente per passare a un'altra parte dell'app mittente. Il mini controller fornisce all'utente un promemoria visibile della sessione di trasmissione corrente. Toccando il mini controller, l'utente può tornare alla visualizzazione del controller espanso a schermo intero di 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 l'app mittente riproduce un live streaming video o 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 Personalizza 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 trasmessi. Il controller espanso è una versione a schermo intero del mini controller.
L'SDK Cast fornisce un widget per il controller espanso chiamato
ExpandedControllerActivity.
Si tratta di una classe astratta di cui devi creare una sottoclasse per aggiungere un pulsante Trasmetti.
Innanzitutto, crea un nuovo file di risorse di menu per il controller espanso per fornire il pulsante Trasmetti:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
</menu>
Crea una nuova classe che estende ExpandedControllerActivity.
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 manifest dell'app all'interno del tag application:
<application>
...
<activity
android:name=".expandedcontrols.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.CastVideosDark"
android:screenOrientation="portrait"
android:parentActivityName="com.google.sample.cast.refplayer.VideoBrowserActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
...
</application>
Modifica CastOptionsProvider e cambia NotificationOptions e CastMediaOptions per impostare l'attività 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 mittente riproduce un live streaming 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, scegliere i pulsanti da visualizzare e aggiungere pulsanti personalizzati, consulta Personalizza il controller espanso.
Controllo del volume
Il framework gestisce automaticamente il volume per l'app mittente. Il framework sincronizza automaticamente le app mittente e ricevitore web in modo che l'UI del mittente indichi sempre il volume specificato dal ricevitore web.
Controllo del volume tramite pulsante fisico
Su Android, i pulsanti fisici sul 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 del volume tramite pulsante fisico prima di Jelly Bean
Per utilizzare i tasti del volume fisici per controllare il volume del dispositivo ricevitore web su
dispositivi Android precedenti a Jelly Bean, l'app mittente deve eseguire l'override di
dispatchKeyEvent
nelle sue 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 i controlli multimediali alla notifica e alla schermata di blocco
Solo su Android, l'elenco di controllo per la progettazione di Google Cast richiede che un'app mittente
implementi i controlli multimediali in una
notifica
e nella schermata di
blocco,
dove il mittente trasmette, ma l'app mittente non è attiva. Il framework fornisce MediaNotificationService e MediaIntentReceiver per aiutare l'app mittente a creare controlli multimediali in una notifica e nella schermata di blocco.
MediaNotificationService viene eseguito quando il mittente trasmette e mostra una notifica con la miniatura dell'immagine e le informazioni sull'elemento attualmente trasmesso, un pulsante di riproduzione/pausa e un pulsante di interruzione.
MediaIntentReceiver è un BroadcastReceiver che gestisce le azioni dell'utente dalla notifica.
La tua app può configurare il controllo delle notifiche e dei contenuti multimediali dalla schermata di blocco tramite
NotificationOptions.
La tua app può configurare i pulsanti di controllo da mostrare nella notifica e
quale Activity aprire quando l'utente tocca la notifica. Se le azioni non vengono fornite esplicitamente, verranno utilizzati i valori predefiniti MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK e MediaIntentReceiver.ACTION_STOP_CASTING.
// 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 dalla notifica e dalla schermata di blocco è attiva per impostazione predefinita e può essere disattivata chiamando setNotificationOptions con null in CastMediaOptions.Builder.
Al momento, la funzionalità della schermata di blocco è attiva 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 l'app 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 controllo della notifica, ma non nel controllo della schermata di blocco.
Nota: per visualizzare i controlli della schermata di blocco sui dispositivi precedenti a Lollipop,
RemoteMediaClient richiederà automaticamente l'audio focus per tuo conto.
Gestisci gli errori
È molto importante che le app mittente 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 o decidere di interrompere la connessione al ricevitore web.