Questa pagina contiene snippet di codice e descrizioni delle funzionalità disponibili per personalizzare un'app del ricevitore Android TV.
Configurazione delle librerie
Per rendere disponibili le API Cast Connect alla tua app per Android TV:
-
Apri il file
build.gradle
all'interno della directory del modulo dell'applicazione. -
Verifica che
google()
sia incluso nell'elementorepositories
elencato.repositories { google() }
-
A seconda del tipo di dispositivo di destinazione per la tua app, aggiungi le versioni più recenti delle librerie alle tue dipendenze:
-
Per l'app Ricevitore Android:
dependencies { implementation 'com.google.android.gms:play-services-cast-tv:21.0.0' implementation 'com.google.android.gms:play-services-cast:21.3.0' }
-
Per l'app Mittente Android:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.0.0' implementation 'com.google.android.gms:play-services-cast-framework:21.3.0' }
-
Per l'app Ricevitore Android:
-
Salva le modifiche e fai clic su
Sync Project with Gradle Files
nella barra degli strumenti.
-
Assicurati che
Podfile
abbia come targetgoogle-cast-sdk
4.7.0 o versioni successive -
Scegli come target iOS 12 o versioni successive. Per ulteriori dettagli, consulta le note di rilascio.
platform: ios, '12' def target_pods pod 'google-cast-sdk', '~>4.7.0' end
- Richiede la versione del browser Chromium M87 o successiva.
-
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 AndroidX
Le nuove versioni di Google Play Services devono essere aggiornate in modo da utilizzare lo spazio dei nomi androidx
. Segui le istruzioni per
eseguire la migrazione ad AndroidX.
App per Android TV: prerequisiti
Per supportare Cast Connect nella tua app per Android TV, devi creare e supportare eventi da una sessione multimediale. I dati forniti dalla tua sessione multimediale forniscono le informazioni di base (ad esempio, posizione, stato di riproduzione e così via) per lo stato dei tuoi contenuti multimediali. La sessione multimediale viene utilizzata anche dalla libreria di Cast Connect per segnalare la ricezione di 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 di una sessione multimediale
L'app deve creare una sessione multimediale quando inizia la 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 l'opzione "Indietro" per sfogliare altri contenuti o eseguendo l'app in background. Se l'app è un'app di musica, dovresti rilasciarla quando l'app non riproduce più contenuti multimediali.
Aggiornamento stato sessione
I dati della sessione multimediale devono essere sempre aggiornati con lo stato del player. Ad esempio, quando la riproduzione è in pausa, devi aggiornare lo stato di riproduzione e le azioni supportate. Nelle tabelle seguenti sono elencati gli stati che devi tenere aggiornati.
MediaMediaCompat
Campo Metadati | Descrizione |
---|---|
METADATA_KEY_TITLE (obbligatorio) | Il titolo dell'elemento multimediale. |
METADATA_KEY_DISPLAY_SUBTITLE | Il sottotitolo. |
METADATA_KEY_DISPLAY_ICON_URI | L'URL dell'icona. |
METADATA_KEY_DURATION (obbligatorio) | Durata dei contenuti multimediali. |
META_KEY_MEDIA_URI | L'ID contenuto. |
ARTISTA_CHIAVE_METALLICA | L'artista. |
METADATA_KEY_ALBUM | L'album. |
RiproduzioneStatoCompatto
Metodo obbligatorio | Descrizione |
---|---|
setActions() | Consente di impostare i comandi multimediali supportati. |
setState() | Imposta lo stato di riproduzione e la posizione corrente. |
MediaMediaCompat
Metodo obbligatorio | Descrizione |
---|---|
setRipetiMode() | Imposta la modalità di ripetizione. |
setShuffleMode() | Imposta la modalità di riproduzione casuale. |
setMetadata() | Imposta i metadati dei contenuti multimediali. |
setRiproduzioneState() | Imposta lo stato della 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 del trasporto delle sessioni multimediali. La seguente tabella mostra quali azioni di controllo del trasporto devono gestire:
MediaSessionCompat.Callback
Azioni | Descrizione |
---|---|
onPlay() | Ripristina |
OnPausa() | 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 per Cast
Quando un'applicazione del mittente invia una richiesta di lancio, viene creato un intent con uno spazio dei nomi dell'applicazione. La tua applicazione è responsabile della sua gestione
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 all'applicazione TV
di accettare messaggi multimediali di trasmissione provenienti da qualsiasi mittente connesso.
Configurazione di Android TV
Aggiunta di un filtro per intent di lancio
Aggiungi un nuovo filtro per intent all'attività che vuoi gestire dall'intenzione di lancio 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 di ricezione
Devi implementare un elemento 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(); } }
Specifica quindi il fornitore di opzioni in 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
all'inizializzazione di CastReceiverContext
.
Contesto del ricevitore di trasmissione
Inizializzare CastReceiverContext
al momento della creazione dell'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 è stata messa in background per le app video o 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()
sul CastReceiverContext
quando smette di giocare in background.
Ti consigliamo vivamente di utilizzare l'osservatore della ciclo di vita
androidx.lifecycle
per gestire le chiamate
CastReceiverContext.start()
e
CastReceiverContext.stop()
,
soprattutto se la tua app nativa ha più attività. Questo evita
le condizioni di gara 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
attuale a
CastReceiverContext
in modo che sappia dove inviare i comandi e recuperare lo stato della 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 inattiva, 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 in background, anziché chiamare
CastReceiverContext.stop()
quando viene inviata in background, dovresti chiamarla solo quando
l'app è in background e non stai più riproducendo 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(); } }
Utilizzare l'esoplayer con Cast Connect
Se utilizzi Exoplayer
, puoi utilizzare MediaSessionConnector
per gestire automaticamente la sessione e tutte le informazioni correlate, incluso lo stato di riproduzione, anziché monitorare manualmente le modifiche.
MediaSessionConnector.MediaButtonEventHandler
può essere utilizzato per gestire gli eventi MediaButton chiamando
setMediaButtonEventHandler(MediaButtonEventHandler)
che altrimenti verrà gestito per impostazione predefinita
MediaSessionCompat.Callback
.
Per integrare
MediaSessionConnector
nella tua app, aggiungi quanto segue alla classe dell'attività del player o ovunque
gestisci la tua 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 del mittente
Attiva il supporto di Cast Connect
Una volta aggiornata l'app del mittente con il supporto di Cast Connect, puoi dichiararne l'idoneità impostando il flag androidReceiverCompatible
su LaunchOptions
su true.
Richiede la versione play-services-cast-framework
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 la versione v4.4.8
(google-cast-sdk
) 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 la versione del browser Chromium
M87
o successiva.
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
Configurare l'app Android TV
Aggiungi il nome del pacchetto dell'app Android TV in Console per gli sviluppatori di Google Cast per associarlo al tuo ID app di trasmissione.
Registra i dispositivi degli sviluppatori
Registra il numero di serie del dispositivo Android TV che utilizzerai per lo sviluppo in Google Play Console.
Senza registrazione, Cast Connect funziona soltanto per le app installate dal Google Play Store per motivi di sicurezza.
Per ulteriori informazioni sulla registrazione di un dispositivo di trasmissione o Android TV per lo sviluppo di Google Cast, consulta la pagina di registrazione.
Caricamento di contenuti multimediali in corso...
Se hai già implementato il supporto dei link diretti 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>
Carica per entità sul mittente
Sui mittenti, puoi passare il link diretto 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 la versione del browser Chromium
M87
o successiva.
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 tuo link diretto e il nome del pacchetto definito nella console per gli sviluppatori.
Impostazione delle credenziali ATV sul mittente
È possibile che la tua app del ricevitore web e la tua app Android TV supportino link diretti diversi e credentials
(ad esempio, se gestisci l'autenticazione in modo diverso sulle due piattaforme). Per risolvere questo 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 la versione del browser Chromium
M87
o successiva.
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 Web Receiver viene avviata, utilizza entity
e credentials
nella richiesta di caricamento. Tuttavia, se la tua app Android TV viene lanciata, l'SDK sostituisce
entity
e credentials
con atvEntity
e atvCredentials
(se specificato).
Caricamento tramite Content ID o MediaQueueData
Se non utilizzi entity
o atvEntity
e utilizzi Content ID o
URL dei contenuti nelle tue informazioni multimediali o usa dati più dettagliati delle richieste di caricamento dei media, devi aggiungere il seguente filtro per intent predefinito
nell'app 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>
Analogamente a quanto accade per l'opzione Carica per entità, puoi creare una richiesta di caricamento con le informazioni sui contenuti e chiamare il numero 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 la versione del browser Chromium
M87
o successiva.
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 carico
Nella tua attività, per gestire queste richieste di carico, devi gestire gli intent nel 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 sostituire questo metodo per gestire la richiesta di caricamento. Il callback deve essere registrato prima che MediaManager.onNewIntent()
venga richiamato (è consigliabile utilizzare il metodo onCreate()
Attività o Applicazione).
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 carico, puoi analizzarlo nelle strutture di dati che abbiamo definito (MediaLoadRequestData
per le richieste di caricamento).
Supporto dei comandi multimediali
Supporto di base del controllo di riproduzione
I comandi di integrazione di base includono i comandi compatibili con la sessione multimediale. Questi comandi vengono avvisati tramite callback di sessione multimediale. Devi registrare un callback alla sessione multimediale per supportare questa operazione (probabilmente lo stai già facendo).
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 per il controllo della trasmissione
Alcuni comandi di trasmissione non sono disponibili in
MediaSession
,
ad esempio
skipAd()
o
setActiveMediaTracks()
.
Alcuni comandi della coda devono inoltre essere implementati qui perché la coda di trasmissione non è completamente compatibile con la coda MediaSession
.
class MyMediaCommandCallback : MediaCommandCallback() { override fun onSkipAd(requestData: RequestData?): Task{ // 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 di trasmissione, l'app Android TV deve specificare i comandi
supportati affinché i mittenti possano attivare o disattivare determinati controlli dell'interfaccia utente. Per
i comandi che fanno parte di
MediaSession
,
specifica i comandi in
PlaybackStateCompat
.
I comandi aggiuntivi devono essere specificati nella 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 pulsanti non supportati
Se la tua app Android TV supporta solo il controllo dei contenuti multimediali di base, ma la tua app web ricevitore supporta un controllo più avanzato, devi assicurarti che l'app del mittente si comporti correttamente durante la trasmissione all'app Android TV. Ad esempio, se l'app Android TV non supporta la modifica della frequenza di riproduzione durante la trasmissione all'app web ricevitore, dovresti impostare correttamente le azioni supportate su ogni piattaforma e assicurarti che l'app del mittente mostri correttamente l'UI.
Modifica dello stato dei contenuti multimediali
Per supportare funzionalità avanzate come tracce, annunci, live streaming e coda, la tua app per Android TV deve fornire informazioni aggiuntive che non possano essere verificate tramite MediaSession
.
Ti offriamo il corso MediaStatusModifier
per consentirti di raggiungere questo obiettivo. MediaStatusModifier
funzionerà 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à MediaStatus
di base da MediaSession
. La tua app per Android TV può specificare stato aggiuntivo e sostituire lo 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
; si sconsiglia di farlo perché lo stato nel modificatore
ha sempre una priorità maggiore rispetto ai valori forniti da MediaSession
.
Intervento di MediaStatus prima dell'invio
Come con l'SDK Web Receiver, se vuoi apportare gli ultimi ritocchi prima di inviare, puoi specificare un elemento MediaStatusInterceptor
per elaborare MediaStatus
da inviare. Passiamo un
MediaStatusWriter
per manipolare il 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
L'app Android TV potrebbe consentire solo a determinati utenti di avviare la sessione dell'app o di parteciparvi. Ad esempio, consenti l'avvio o la partecipazione a un mittente solo se:
- L'app del mittente è collegata allo stesso account e allo stesso profilo dell'app ATV.
- L'app del mittente ha eseguito l'accesso allo stesso account, ma ha un profilo diverso rispetto all'app dell'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 per ATV deve gestire le proprie credenziali in modo da poter monitorare correttamente l'avanzamento e altri dati utente.
Quando l'app del mittente si avvia o si unisce all'app Android TV, l'app del mittente deve fornire le credenziali che rappresentano chi partecipa alla sessione.
Prima che un mittente si avvii e entri nella tua app 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 credenziali di lancio app mittente
Sul lato del mittente, puoi specificare CredentialsData
per indicare chi partecipa alla sessione.
credentials
è una stringa che può essere definita dall'utente, purché l'app ATV
la comprenda. credentialsType
definisce la piattaforma da cui proviene
CredentialsData
o può essere un valore personalizzato. Per impostazione predefinita,
è impostato sulla piattaforma da cui viene inviata.
L'app CredentialsData
viene trasferita alla tua app Android TV solo al momento del lancio o al momento della registrazione. Se lo imposti di nuovo durante la connessione, non verrà trasmesso all'app Android TV. Se il mittente cambia profilo mentre è connesso, potresti rimanere nella sessione oppure chiamare SessionManager.endCurrentCastSession(boolean stopCasting)
se ritieni che il nuovo profilo non sia compatibile con la sessione.
È possibile recuperare
CredentialsData
per ciascun mittente utilizzando
getSenders
su
CastReceiverContext
per ottenere il SenderInfo
,
getCastLaunchRequest()
per ottenere
CastLaunchRequest
, quindi
getCredentialsData()
.
Richiede la versione play-services-cast-framework
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 la versione v4.7.0
(google-cast-sdk
) o successiva.
Puoi chiamarlo in qualsiasi momento dopo aver impostato le opzioni:
GCKCastContext.setSharedInstanceWith(options)
.
GCKCastContext.sharedInstance().setLaunch( GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Richiede la versione del browser Chromium
M87
o successiva.
Puoi chiamarlo in qualsiasi momento dopo aver impostato le 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 su ATV
L'elemento CredentialsData
viene trasmesso all'app Android TV quando un mittente tenta di lanciare o di partecipare. Puoi implementare LaunchRequestChecker
.
per consentire o rifiutare la richiesta.
Se una richiesta viene rifiutata, il ricevitore web viene caricato anziché essere lanciato in modo nativo nell'app ATV. Dovresti rifiutare una richiesta se la tua ATV non è in grado di gestire l'utente che richiede il lancio o la partecipazione. Ad esempio, un utente diverso potrebbe aver eseguito l'accesso all'app ATV e richiedere che l'app non sia in grado di gestire le credenziali di trasferimento oppure che un utente non abbia eseguito l'accesso all'app ATV.
Se una richiesta è consentita, l'app ATV viene avviata. Puoi personalizzare questo comportamento a seconda che la tua app supporti l'invio di richieste di carico quando un utente non ha eseguito l'accesso all'app ATV o se si verifica una mancata corrispondenza dell'utente. Questo comportamento è completamente cusemizzabile nella sezione LaunchRequestChecker
.
Crea un corso che implementa 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 configuralo
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 soluzione true
in
LaunchRequestChecker
avvia l'app ATV e false
lancia la tua app Ricevitore web.
Inviare e ricevere messaggi personalizzati
Il protocollo di trasmissione consente di inviare messaggi stringa personalizzati tra i mittenti e
l'applicazione di ricezione. Devi registrare uno spazio dei nomi (canale) per l'invio dei messaggi prima di inizializzare CastReceiverContext
.
Android TV: specifica uno spazio dei nomi personalizzato
Durante la configurazione, devi specificare gli spazi dei nomi supportati nella CastReceiverOptions
:
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: ricevi messaggi personalizzati dello spazio dei nomi
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());