Questa pagina contiene snippet di codice e descrizioni delle funzionalità disponibili per personalizzare un'app per Android TV Receiver.
Configurazione delle librerie
Per rendere disponibili le API Cast Connect per la tua app per Android TV:
-
Apri il file
build.gradlenella directory del modulo dell'applicazione. -
Verifica che
google()sia incluso nell'elencorepositories.repositories { google() } -
A seconda del tipo di dispositivo di destinazione della tua app, aggiungi le ultime versioni delle librerie alle tue dipendenze:
-
Per l'app ricevitore Android:
dependencies { implementation 'com.google.android.gms:play-services-cast-tv:21.1.1' implementation 'com.google.android.gms:play-services-cast:23.0.0' }
-
Per l'app mittente per Android:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.1.1' implementation 'com.google.android.gms:play-services-cast-framework:23.0.0' }
-
Per l'app ricevitore Android:
-
Salva le modifiche e fai clic su
Sync Project with Gradle Filesnella barra degli strumenti.
-
Assicurati che
Podfileabbia come targetgoogle-cast-sdk4.8.6 o versioni successive -
Target iOS 16 o versioni successive. Per maggiori dettagli, consulta le note di rilascio.
platform: ios, '16' def target_pods pod 'google-cast-sdk', '~>4.8.6' end
- Richiede il browser Chromium versione M87 o successive.
-
Aggiungi la libreria dell'API Web Sender al tuo progetto
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
Requisito di AndroidX
Le nuove versioni di Google Play Services richiedono che un'app sia stata aggiornata per utilizzare lo spazio dei nomi androidx. Segui le istruzioni per
eseguire la migrazione ad AndroidX.
Prerequisiti per l'app per Android TV
Per supportare Cast Connect nella tua app per Android TV, devi creare e supportare gli eventi di una sessione multimediale. I dati forniti dalla sessione multimediale forniscono le informazioni di base, ad esempio posizione, stato di riproduzione e così via, per lo stato dei contenuti multimediali. La sessione multimediale viene utilizzata anche dalla libreria Cast Connect per segnalare quando ha ricevuto determinati messaggi da un mittente, ad esempio la pausa.
Per ulteriori informazioni sulla sessione multimediale e su come inizializzarla, consulta la guida all'utilizzo di una sessione multimediale.
Ciclo di vita della sessione multimediale
L'app deve creare una sessione multimediale all'inizio della riproduzione e rilasciarla quando non può più essere controllata. Ad esempio, se la tua app è un'app video, devi rilasciare la sessione quando l'utente esce dall'attività di riproduzione, selezionando "Indietro" per sfogliare altri contenuti o mettendo l'app in background. Se la tua app è un'app musicale, devi rilasciarla quando non riproduce più contenuti multimediali.
Aggiornamento dello stato della sessione in corso…
I dati della sessione multimediale devono essere aggiornati con lo stato del player. Ad esempio, quando la riproduzione è in pausa, devi aggiornare lo stato della riproduzione e le azioni supportate. Le tabelle seguenti elencano gli stati che devi mantenere aggiornati.
MediaMetadataCompat
| Campo dei metadati | Descrizione |
|---|---|
| METADATA_KEY_TITLE (obbligatorio) | Il titolo del contenuto multimediale. |
| METADATA_KEY_DISPLAY_SUBTITLE | Il sottotitolo. |
| METADATA_KEY_DISPLAY_ICON_URI | L'URL dell'icona. |
| METADATA_KEY_DURATION (obbligatorio) | Durata dei contenuti multimediali. |
| METADATA_KEY_MEDIA_URI | Content ID. |
| METADATA_KEY_ARTIST | L'artista. |
| METADATA_KEY_ALBUM | L'album. |
PlaybackStateCompat
| Metodo obbligatorio | Descrizione |
|---|---|
| setActions() | Imposta i comandi multimediali supportati. |
| setState() | Imposta lo stato di riproduzione e la posizione corrente. |
MediaSessionCompat
| Metodo obbligatorio | Descrizione |
|---|---|
| setRepeatMode() | Imposta la modalità repeat. |
| setShuffleMode() | Imposta la modalità shuffle. |
| setMetadata() | Imposta i metadati dei contenuti multimediali. |
| setPlaybackState() | Imposta lo stato di riproduzione. |
private fun updateMediaSession() { val metadata = MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, mMovie.getCardImageUrl()) .build() val playbackState = PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, player.getPosition(), player.getPlaybackSpeed(), System.currentTimeMillis() ) .build() mediaSession.setMetadata(metadata) mediaSession.setPlaybackState(playbackState) }
private void updateMediaSession() {
MediaMetadataCompat metadata =
new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title")
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle")
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,mMovie.getCardImageUrl())
.build();
PlaybackStateCompat playbackState =
new PlaybackStateCompat.Builder()
.setState(
PlaybackStateCompat.STATE_PLAYING,
player.getPosition(),
player.getPlaybackSpeed(),
System.currentTimeMillis())
.build();
mediaSession.setMetadata(metadata);
mediaSession.setPlaybackState(playbackState);
}Gestione del controllo del trasporto
La tua app deve implementare il callback del controllo di trasporto della sessione multimediale. La tabella seguente mostra le azioni di controllo del trasporto che devono gestire:
MediaSessionCompat.Callback
| Azioni | Descrizione |
|---|---|
| onPlay() | Riprendi |
| onPause() | Metti in pausa |
| onSeekTo() | Vai a una posizione |
| onStop() | Interrompi i contenuti multimediali correnti |
class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. ... } override fun onPlay() { // Resume the player and update the play state. ... } override fun onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback( MyMediaSessionCallback() );
public MyMediaSessionCallback extends MediaSessionCompat.Callback { public void onPause() { // Pause the player and update the play state. ... } public void onPlay() { // Resume the player and update the play state. ... } public void onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback(new MyMediaSessionCallback());
Configurazione del supporto di Cast
Quando un'applicazione mittente invia una richiesta di avvio, viene creato un intent con uno spazio dei nomi dell'applicazione. La tua applicazione è responsabile della gestione dell'intent e della creazione di un'istanza dell'oggetto CastReceiverContext all'avvio dell'app TV. L'oggetto CastReceiverContext è necessario per interagire con Cast mentre l'app TV è in esecuzione. Questo oggetto consente alla tua applicazione TV di accettare i messaggi multimediali di Cast provenienti da qualsiasi mittente connesso.
Configurazione di Android TV
Aggiunta di un filtro per intent di avvio
Aggiungi un nuovo filtro per intent all'attività che vuoi che gestisca l'intent di avvio dalla tua app mittente:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Specifica il provider di opzioni del ricevitore
Devi implementare un
ReceiverOptionsProvider
per fornire
CastReceiverOptions:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setStatusText("My App") .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) .setStatusText("My App") .build(); } }
Poi specifica il fornitore di opzioni nel tuo AndroidManifest:
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.example.mysimpleatvapplication.MyReceiverOptionsProvider" />
ReceiverOptionsProvider viene utilizzato per fornire CastReceiverOptions quando CastReceiverContext viene inizializzato.
Contesto del ricevitore di trasmissione
Inizializza
CastReceiverContext
quando viene creata l'app:
override fun onCreate() { CastReceiverContext.initInstance(this) ... }
@Override public void onCreate() { CastReceiverContext.initInstance(this); ... }
Avvia CastReceiverContext quando l'app passa in primo piano:
CastReceiverContext.getInstance().start()
CastReceiverContext.getInstance().start();
Chiama
stop()
su
CastReceiverContext
dopo che l'app passa in background per le app video o le app che non supportano
la riproduzione in background:
// Player has stopped. CastReceiverContext.getInstance().stop()
// Player has stopped. CastReceiverContext.getInstance().stop();
Inoltre, se la tua app supporta la riproduzione in background, chiama stop()
su CastReceiverContext quando interrompe la riproduzione in background.
Ti consigliamo vivamente di utilizzare LifecycleObserver della libreria
androidx.lifecycle
per gestire le chiamate
CastReceiverContext.start()
e
CastReceiverContext.stop(),
soprattutto se la tua app nativa ha più attività. In questo modo si evitano race condition quando chiami start() e stop() da attività diverse.
// Create a LifecycleObserver class. class MyLifecycleObserver : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start() } override fun onStop(owner: LifecycleOwner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop() } } // Add the observer when your application is being created. class MyApplication : Application() { fun onCreate() { super.onCreate() // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */) // Register LifecycleObserver ProcessLifecycleOwner.get().lifecycle.addObserver( MyLifecycleObserver()) } }
// Create a LifecycleObserver class. public class MyLifecycleObserver implements DefaultLifecycleObserver { @Override public void onStart(LifecycleOwner owner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start(); } @Override public void onStop(LifecycleOwner owner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop(); } } // Add the observer when your application is being created. public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */); // Register LifecycleObserver ProcessLifecycleOwner.get().getLifecycle().addObserver( new MyLifecycleObserver()); } }
// In AndroidManifest.xml set MyApplication as the application class
<application
...
android:name=".MyApplication">
Collegamento di MediaSession a MediaManager
Quando crei un
MediaSession,
devi anche fornire il token MediaSession corrente a
CastReceiverContext
in modo che sappia dove inviare i comandi e recuperare lo stato di riproduzione dei contenuti multimediali:
val mediaManager: MediaManager = receiverContext.getMediaManager() mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
MediaManager mediaManager = receiverContext.getMediaManager(); mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());
Quando rilasci MediaSession a causa di una riproduzione non attiva, devi impostare un token null su MediaManager:
myPlayer.stop() mediaSession.release() mediaManager.setSessionCompatToken(null)
myPlayer.stop(); mediaSession.release(); mediaManager.setSessionCompatToken(null);
Se la tua app supporta la riproduzione di contenuti multimediali mentre è in background, anziché chiamare CastReceiverContext.stop() quando l'app viene inviata in background, devi chiamarla solo quando l'app è in background e non riproduce più contenuti multimediali. Ad esempio:
class MyLifecycleObserver : DefaultLifecycleObserver { ... // App has moved to the background. override fun onPause(owner: LifecycleOwner) { mIsBackground = true myStopCastReceiverContextIfNeeded() } } // Stop playback on the player. private fun myStopPlayback() { myPlayer.stop() myStopCastReceiverContextIfNeeded() } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private fun myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop() } }
public class MyLifecycleObserver implements DefaultLifecycleObserver { ... // App has moved to the background. @Override public void onPause(LifecycleOwner owner) { mIsBackground = true; myStopCastReceiverContextIfNeeded(); } } // Stop playback on the player. private void myStopPlayback() { myPlayer.stop(); myStopCastReceiverContextIfNeeded(); } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private void myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop(); } }
Utilizzo di ExoPlayer con Cast Connect
Se utilizzi
Exoplayer, puoi utilizzare
MediaSessionConnector
per mantenere automaticamente la sessione e tutte le informazioni correlate, incluso lo
stato di riproduzione, anziché monitorare le modifiche manualmente.
MediaSessionConnector.MediaButtonEventHandler
può essere utilizzato per gestire gli eventi MediaButton chiamando
setMediaButtonEventHandler(MediaButtonEventHandler)
che altrimenti vengono gestiti da
MediaSessionCompat.Callback
per impostazione predefinita.
Per integrare
MediaSessionConnector
nella tua app, aggiungi quanto segue alla classe di attività del player o ovunque gestisci la sessione multimediale:
class PlayerActivity : Activity() { private var mMediaSession: MediaSessionCompat? = null private var mMediaSessionConnector: MediaSessionConnector? = null private var mMediaManager: MediaManager? = null override fun onCreate(savedInstanceState: Bundle?) { ... mMediaSession = MediaSessionCompat(this, LOG_TAG) mMediaSessionConnector = MediaSessionConnector(mMediaSession!!) ... } override fun onStart() { ... mMediaManager = receiverContext.getMediaManager() mMediaManager!!.setSessionCompatToken(currentMediaSession.getSessionToken()) mMediaSessionConnector!!.setPlayer(mExoPlayer) mMediaSessionConnector!!.setMediaMetadataProvider(mMediaMetadataProvider) mMediaSession!!.isActive = true ... } override fun onStop() { ... mMediaSessionConnector!!.setPlayer(null) mMediaSession!!.release() mMediaManager!!.setSessionCompatToken(null) ... } }
public class PlayerActivity extends Activity { private MediaSessionCompat mMediaSession; private MediaSessionConnector mMediaSessionConnector; private MediaManager mMediaManager; @Override protected void onCreate(Bundle savedInstanceState) { ... mMediaSession = new MediaSessionCompat(this, LOG_TAG); mMediaSessionConnector = new MediaSessionConnector(mMediaSession); ... } @Override protected void onStart() { ... mMediaManager = receiverContext.getMediaManager(); mMediaManager.setSessionCompatToken(currentMediaSession.getSessionToken()); mMediaSessionConnector.setPlayer(mExoPlayer); mMediaSessionConnector.setMediaMetadataProvider(mMediaMetadataProvider); mMediaSession.setActive(true); ... } @Override protected void onStop() { ... mMediaSessionConnector.setPlayer(null); mMediaSession.release(); mMediaManager.setSessionCompatToken(null); ... } }
Configurazione dell'app mittente
Attiva il supporto di Cast Connect
Dopo aver aggiornato l'app mittente con il supporto di Cast Connect, puoi dichiarare
la sua idoneità impostando
il flag
androidReceiverCompatible
su
LaunchOptions
su true.
Richiede play-services-cast-framework versione 19.0.0 o successive.
Il flag androidReceiverCompatible è impostato in
LaunchOptions (che fa parte di CastOptions):
class CastOptionsProvider : OptionsProvider { override fun getCastOptions(context: Context?): CastOptions { val launchOptions: LaunchOptions = Builder() .setAndroidReceiverCompatible(true) .build() return CastOptions.Builder() .setLaunchOptions(launchOptions) ... .build() } }
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { LaunchOptions launchOptions = new LaunchOptions.Builder() .setAndroidReceiverCompatible(true) .build(); return new CastOptions.Builder() .setLaunchOptions(launchOptions) ... .build(); } }
Richiede google-cast-sdk versione v4.4.8 o
successiva.
Il flag androidReceiverCompatible è impostato in GCKLaunchOptions (che fa parte di GCKCastOptions):
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID)) ... let launchOptions = GCKLaunchOptions() launchOptions.androidReceiverCompatible = true options.launchOptions = launchOptions GCKCastContext.setSharedInstanceWith(options)
Richiede un browser Chromium versione M87 o successive.
const context = cast.framework.CastContext.getInstance(); const castOptions = new cast.framework.CastOptions(); castOptions.receiverApplicationId = kReceiverAppID; castOptions.androidReceiverCompatible = true; context.setOptions(castOptions);
Configurazione di Cast Developer Console
Configura l'app per Android TV
Aggiungi il nome del pacchetto della tua app per Android TV nella Console per sviluppatori Cast per associarlo all'ID app Cast.

Registra i dispositivi per sviluppatori
Registra il numero di serie del dispositivo Android TV che utilizzerai per lo sviluppo nella Cast Developer Console.

Senza registrazione, Cast Connect funzionerà solo per le app installate dal Google Play Store per motivi di sicurezza.
Per ulteriori informazioni sulla registrazione di un dispositivo Cast o Android TV per lo sviluppo di Cast, consulta la pagina di registrazione.
Caricamento di contenuti multimediali in corso…
Se hai già implementato il supporto dei deep link nella tua app per Android TV, dovresti avere una definizione simile configurata nel file manifest di Android TV:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https"/>
<data android:host="www.example.com"/>
<data android:pathPattern=".*"/>
</intent-filter>
</activity>
Carico per entità sul mittente
Sui mittenti, puoi passare il deep link impostando entity nelle informazioni
multimediali per la richiesta di caricamento:
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Richiede un browser Chromium versione M87 o successive.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
Il comando di caricamento viene inviato tramite un intent con il deep link e il nome del pacchetto definito nella Console per sviluppatori.
Impostazione delle credenziali ATV sul mittente
È possibile che l'app Web Receiver e l'app per Android TV supportino deep link e credentials diversi (ad esempio se gestisci l'autenticazione in modo diverso sulle due piattaforme). Per risolvere il problema, puoi fornire entity e credentials alternativi per Android TV:
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") .setAtvEntity("myscheme://example.com/atv/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") .setAtvCredentials("atv-user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") .setAtvEntity("myscheme://example.com/atv/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") .setAtvCredentials("atv-user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") mediaInfoBuilder.atvEntity = "myscheme://example.com/atv/some-id" ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" mediaLoadRequestDataBuilder.atvCredentials = "atv-user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Richiede un browser Chromium versione M87 o successive.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; mediaInfo.atvEntity = 'myscheme://example.com/atv/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; request.atvCredentials = 'atv-user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
Se l'app ricevitore web viene avviata, utilizza entity e credentials nella richiesta di caricamento. Tuttavia, se l'app per Android TV viene avviata, l'SDK sostituisce
entity e credentials con atvEntity e atvCredentials
(se specificati).
Caricamento tramite Content ID o MediaQueueData
Se non utilizzi entity o atvEntity e utilizzi Content ID o Content URL nelle informazioni sui contenuti multimediali o utilizzi i dati più dettagliati della richiesta di caricamento dei contenuti multimediali, devi aggiungere il seguente filtro per intent predefinito nella tua app per Android TV:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Sul lato mittente, analogamente al caricamento per entità, puoi creare una richiesta di caricamento con le informazioni sui contenuti e chiamare load().
val mediaToLoad = MediaInfo.Builder("some-id").build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id").build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(contentId: "some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Richiede un browser Chromium versione M87 o successive.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); ... let request = new chrome.cast.media.LoadRequest(mediaInfo); ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
Gestione delle richieste di caricamento
Nella tua attività, per gestire queste richieste di caricamento, devi gestire gli intent nei callback del ciclo di vita dell'attività:
class MyActivity : Activity() { override fun onStart() { super.onStart() val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). override fun onNewIntent(intent: Intent) { val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
public class MyActivity extends Activity { @Override protected void onStart() { super.onStart(); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(getIntent())) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). @Override protected void onNewIntent(Intent intent) { MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
Se MediaManager
rileva che l'intent è un intent di caricamento, estrae un
oggetto MediaLoadRequestData
dall'intent e richiama
MediaLoadCommandCallback.onLoad().
Devi eseguire l'override di questo metodo per gestire la richiesta di caricamento. Il callback deve essere registrato prima della chiamata di MediaManager.onNewIntent() (è consigliabile che sia su un metodo onCreate() di Activity o Application).
class MyActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.setMediaLoadCommandCallback(MyMediaLoadCommandCallback()) } } class MyMediaLoadCommandCallback : MediaLoadCommandCallback() { override fun onLoad( senderId: String?, loadRequestData: MediaLoadRequestData ): Task{ return Tasks.call { // Resolve the entity into your data structure and load media. val mediaInfo = loadRequestData.getMediaInfo() if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw MediaException( MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build() ) } myFillMediaInfo(MediaInfoWriter(mediaInfo)) myPlayerLoad(mediaInfo.getContentUrl()) // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData) ... castReceiverContext.getMediaManager().broadcastMediaStatus() // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData } } private fun myPlayerLoad(contentURL: String) { myPlayer.load(contentURL) // Update the MediaSession state. val playbackState: PlaybackStateCompat = Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis() ) ... .build() mediaSession.setPlaybackState(playbackState) }
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaLoadCommandCallback(new MyMediaLoadCommandCallback()); } } public class MyMediaLoadCommandCallback extends MediaLoadCommandCallback { @Override public TaskonLoad(String senderId, MediaLoadRequestData loadRequestData) { return Tasks.call(() -> { // Resolve the entity into your data structure and load media. MediaInfo mediaInfo = loadRequestData.getMediaInfo(); if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw new MediaException( new MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build()); } myFillMediaInfo(new MediaInfoWriter(mediaInfo)); myPlayerLoad(mediaInfo.getContentUrl()); // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData); ... castReceiverContext.getMediaManager().broadcastMediaStatus(); // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData; }); } private void myPlayerLoad(String contentURL) { myPlayer.load(contentURL); // Update the MediaSession state. PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis()) ... .build(); mediaSession.setPlaybackState(playbackState); }
Per elaborare l'intent di caricamento, puoi analizzarlo nelle strutture di dati che abbiamo definito (MediaLoadRequestData per le richieste di caricamento).
Supporto dei comandi multimediali
Supporto del controllo di riproduzione di base
I comandi di integrazione di base includono i comandi compatibili con la sessione multimediale. Questi comandi vengono notificati tramite callback della sessione multimediale. Per supportare questa funzionalità, devi registrare un callback alla sessione multimediale (potresti averlo già fatto).
private class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. myPlayer.pause() } override fun onPlay() { // Resume the player and update the play state. myPlayer.play() } override fun onSeekTo(pos: Long) { // Seek and update the play state. myPlayer.seekTo(pos) } ... } mediaSession.setCallback(MyMediaSessionCallback())
private class MyMediaSessionCallback extends MediaSessionCompat.Callback { @Override public void onPause() { // Pause the player and update the play state. myPlayer.pause(); } @Override public void onPlay() { // Resume the player and update the play state. myPlayer.play(); } @Override public void onSeekTo(long pos) { // Seek and update the play state. myPlayer.seekTo(pos); } ... } mediaSession.setCallback(new MyMediaSessionCallback());
Supporto dei comandi di controllo di Cast
Alcuni comandi di trasmissione non sono disponibili in
MediaSession,
ad esempio
skipAd()
o
setActiveMediaTracks().
Inoltre, qui è necessario implementare alcuni comandi della coda perché la coda di trasmissione non è completamente compatibile con la coda MediaSession.
class MyMediaCommandCallback : MediaCommandCallback() { override fun onSkipAd(requestData: RequestData?): Task<Void?> { // Skip your ad ... return Tasks.forResult(null) } } val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
public class MyMediaCommandCallback extends MediaCommandCallback { @Override public TaskonSkipAd(RequestData requestData) { // Skip your ad ... return Tasks.forResult(null); } } MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaCommandCallback(new MyMediaCommandCallback());
Specifica i comandi multimediali supportati
Come per il ricevitore Cast, l'app per Android TV deve specificare quali comandi
sono supportati, in modo che i mittenti possano attivare o disattivare determinati controlli UI. Per i comandi che fanno parte di MediaSession, specifica i comandi in PlaybackStateCompat.
I comandi aggiuntivi devono essere specificati nel file MediaStatusModifier.
// Set media session supported commands val playbackState: PlaybackStateCompat = PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE) .setState(PlaybackStateCompat.STATE_PLAYING) .build() mediaSession.setPlaybackState(playbackState) // Set additional commands in MediaStatusModifier val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.getMediaStatusModifier() .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT)
// Set media session supported commands PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE) .setState(PlaybackStateCompat.STATE_PLAYING) .build(); mediaSession.setPlaybackState(playbackState); // Set additional commands in MediaStatusModifier MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.getMediaStatusModifier() .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT);
Nascondi i pulsanti non supportati
Se la tua app per Android TV supporta solo il controllo multimediale di base, ma la tua app Web Receiver supporta un controllo più avanzato, devi assicurarti che l'app mittente si comporti correttamente durante la trasmissione all'app per Android TV. Ad esempio, se la tua app per Android TV non supporta la modifica della velocità di riproduzione, mentre la tua app Web Receiver sì, devi impostare correttamente le azioni supportate su ogni piattaforma e assicurarti che l'app mittente esegua il rendering della UI in modo corretto.
Modifica di MediaStatus
Per supportare funzionalità avanzate come tracce, annunci, live e coda, l'app per Android TV deve fornire informazioni aggiuntive che non possono essere accertate tramite MediaSession.
Per farlo, forniamo la classe MediaStatusModifier. MediaStatusModifier opererà sempre sulla
MediaSession che hai impostato in
CastReceiverContext.
Per creare e trasmettere
MediaStatus:
val mediaManager: MediaManager = castReceiverContext.getMediaManager() val statusModifier: MediaStatusModifier = mediaManager.getMediaStatusModifier() statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData) mediaManager.broadcastMediaStatus()
MediaManager mediaManager = castReceiverContext.getMediaManager();
MediaStatusModifier statusModifier = mediaManager.getMediaStatusModifier();
statusModifier
.setLiveSeekableRange(seekableRange)
.setAdBreakStatus(adBreakStatus)
.setCustomData(customData);
mediaManager.broadcastMediaStatus();La nostra libreria client riceverà lo stato MediaStatus di base da MediaSession, la tua app per Android TV può specificare uno stato aggiuntivo ed eseguire l'override dello stato tramite un modificatore MediaStatus.
Alcuni stati e metadati possono essere impostati sia in MediaSession che in MediaStatusModifier. Ti consigliamo vivamente di impostarli solo in MediaSession. Puoi comunque utilizzare il modificatore per eseguire l'override degli stati in
MediaSession. Questa operazione è sconsigliata perché lo stato nel modificatore ha sempre
una priorità più alta rispetto ai valori forniti da MediaSession.
Intercettazione di MediaStatus prima dell'invio
Come per l'SDK Web Receiver, se vuoi apportare alcuni ritocchi finali prima dell'invio, puoi specificare un MediaStatusInterceptor per elaborare l'MediaStatus da inviare. Passiamo un MediaStatusWriter per manipolare MediaStatus prima che venga inviato.
mediaManager.setMediaStatusInterceptor(object : MediaStatusInterceptor { override fun intercept(mediaStatusWriter: MediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(JSONObject("{data: \"my Hello\"}")) } })
mediaManager.setMediaStatusInterceptor(new MediaStatusInterceptor() { @Override public void intercept(MediaStatusWriter mediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(new JSONObject("{data: \"my Hello\"}")); } });
Gestione delle credenziali utente
La tua app per Android TV potrebbe consentire solo a determinati utenti di avviare o partecipare alla sessione dell'app. Ad esempio, consenti a un mittente di avviare o partecipare solo se:
- L'app mittente ha eseguito l'accesso allo stesso account e profilo dell'app ATV.
- L'app mittente ha eseguito l'accesso allo stesso account, ma a un profilo diverso rispetto all'app ATV.
Se la tua app può gestire più utenti o utenti anonimi, puoi consentire a qualsiasi altro utente di partecipare alla sessione ATV. Se l'utente fornisce le credenziali, l'app ATV deve gestirle in modo che i suoi progressi e altri dati utente possano essere monitorati correttamente.
Quando l'app mittente avvia o si unisce all'app per Android TV, l'app mittente deve fornire le credenziali che rappresentano chi si unisce alla sessione.
Prima che un mittente avvii e si connetta alla tua app per Android TV, puoi specificare un controllo di avvio per verificare se le credenziali del mittente sono consentite. In caso contrario, l'SDK Cast Connect torna ad avviare il ricevitore web.
Dati delle credenziali di avvio dell'app mittente
Sul lato mittente, puoi specificare CredentialsData per rappresentare chi partecipa alla sessione.
credentials è una stringa che può essere definita dall'utente, purché la tua app ATV
possa comprenderla. credentialsType definisce la piattaforma da cui proviene CredentialsData o può essere un valore personalizzato. Per impostazione predefinita, è impostato sulla piattaforma da cui viene inviato.
CredentialsData viene passato all'app per Android TV solo durante il lancio o
l'accesso. Se lo imposti di nuovo mentre sei connesso, non verrà passato alla tua app per Android TV. Se il mittente cambia il profilo mentre è connesso, puoi rimanere nella sessione o chiamare SessionManager.endCurrentCastSession(boolean stopCasting) se ritieni che il nuovo profilo non sia compatibile con la sessione.
Il valore
CredentialsData
per ogni mittente può essere recuperato utilizzando
getSenders
su
CastReceiverContext
per ottenere SenderInfo,
getCastLaunchRequest()
per ottenere
CastLaunchRequest,
e poi
getCredentialsData().
Richiede play-services-cast-framework versione 19.0.0 o successive.
CastContext.getSharedInstance().setLaunchCredentialsData( CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build() )
CastContext.getSharedInstance().setLaunchCredentialsData(
new CredentialsData.Builder()
.setCredentials("{\"userId\": \"abc\"}")
.build());Richiede google-cast-sdk versione v4.8.6 o
successiva.
Può essere chiamato in qualsiasi momento dopo l'impostazione delle opzioni:
GCKCastContext.setSharedInstanceWith(options).
GCKCastContext.sharedInstance().setLaunch( GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Richiede un browser Chromium versione M87 o successive.
Può essere chiamato in qualsiasi momento dopo l'impostazione delle opzioni:
cast.framework.CastContext.getInstance().setOptions(options);.
let credentialsData = new chrome.cast.CredentialsData("{\"userId\": \"abc\"}"); cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);
Implementazione del controllo delle richieste di lancio di ATV
CredentialsData viene passato alla tua app per Android TV quando un mittente tenta di avviare o partecipare. Puoi implementare un LaunchRequestChecker.
per consentire o rifiutare questa richiesta.
Se una richiesta viene rifiutata, viene caricato il ricevitore web anziché avviare l'app ATV in modo nativo. Devi rifiutare una richiesta se la tua ATV non è in grado di gestire l'utente che richiede l'avvio o l'accesso. Ad esempio, un utente diverso da quello che effettua la richiesta ha eseguito l'accesso all'app ATV e la tua app non è in grado di gestire il cambio di credenziali oppure nessun utente ha eseguito l'accesso all'app ATV.
Se una richiesta viene consentita, l'app ATV viene avviata. Puoi personalizzare questo comportamento a seconda che la tua app supporti l'invio di richieste di caricamento quando un utente non ha eseguito l'accesso all'app ATV o se esiste una mancata corrispondenza dell'utente. Questo comportamento è completamente personalizzabile nel LaunchRequestChecker.
Crea una classe che implementi l'interfaccia CastReceiverOptions.LaunchRequestChecker:
class MyLaunchRequestChecker : LaunchRequestChecker { override fun checkLaunchRequestSupported(launchRequest: CastLaunchRequest): Task{ return Tasks.call { myCheckLaunchRequest( launchRequest ) } } } private fun myCheckLaunchRequest(launchRequest: CastLaunchRequest): Boolean { val credentialsData = launchRequest.getCredentialsData() ?: return false // or true if you allow anonymous users to join. // The request comes from a mobile device, e.g. checking user match. return if (credentialsData.credentialsType == CredentialsData.CREDENTIALS_TYPE_ANDROID) { myCheckMobileCredentialsAllowed(credentialsData.getCredentials()) } else false // Unrecognized credentials type. }
public class MyLaunchRequestChecker implements CastReceiverOptions.LaunchRequestChecker { @Override public TaskcheckLaunchRequestSupported(CastLaunchRequest launchRequest) { return Tasks.call(() -> myCheckLaunchRequest(launchRequest)); } } private boolean myCheckLaunchRequest(CastLaunchRequest launchRequest) { CredentialsData credentialsData = launchRequest.getCredentialsData(); if (credentialsData == null) { return false; // or true if you allow anonymous users to join. } // The request comes from a mobile device, e.g. checking user match. if (credentialsData.getCredentialsType().equals(CredentialsData.CREDENTIALS_TYPE_ANDROID)) { return myCheckMobileCredentialsAllowed(credentialsData.getCredentials()); } // Unrecognized credentials type. return false; }
Poi impostala nel tuo
ReceiverOptionsProvider:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) ... .setLaunchRequestChecker(MyLaunchRequestChecker()) .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) ... .setLaunchRequestChecker(new MyLaunchRequestChecker()) .build(); } }
La risoluzione di true in
LaunchRequestChecker
avvia l'app ATV e false avvia l'app Web Receiver.
Invio e ricezione di messaggi personalizzati
Il protocollo Cast ti consente di inviare messaggi di stringa personalizzati tra i mittenti e l'applicazione ricevitore. Devi registrare uno spazio dei nomi (canale) per inviare messaggi prima di inizializzare CastReceiverContext.
Android TV: specifica lo spazio dei nomi personalizzato
Devi specificare gli spazi dei nomi supportati nel tuo
CastReceiverOptions
durante la configurazione:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setCustomNamespaces( Arrays.asList("urn:x-cast:com.example.cast.mynamespace") ) .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) .setCustomNamespaces( Arrays.asList("urn:x-cast:com.example.cast.mynamespace")) .build(); } }
Android TV: invio di messaggi
// If senderId is null, then the message is broadcasted to all senders. CastReceiverContext.getInstance().sendMessage( "urn:x-cast:com.example.cast.mynamespace", senderId, customString)
// If senderId is null, then the message is broadcasted to all senders. CastReceiverContext.getInstance().sendMessage( "urn:x-cast:com.example.cast.mynamespace", senderId, customString);
Android TV - Ricezione di messaggi con spazio dei nomi personalizzato
class MyCustomMessageListener : MessageReceivedListener { override fun onMessageReceived( namespace: String, senderId: String?, message: String ) { ... } } CastReceiverContext.getInstance().setMessageReceivedListener( "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());
class MyCustomMessageListener implements CastReceiverContext.MessageReceivedListener { @Override public void onMessageReceived( String namespace, String senderId, String message) { ... } } CastReceiverContext.getInstance().setMessageReceivedListener( "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());