En esta página, se incluyen fragmentos de código y descripciones de las funciones disponibles para personalizar una app de receptor de Android TV.
Configura bibliotecas
Para que las APIs de Cast Connect estén disponibles en tu app para Android TV, haz lo siguiente:
-
Abre el archivo
build.gradledentro del directorio del módulo de tu aplicación. -
Verifica que
google()esté incluido en elrepositoriesde la lista.repositories { google() } -
Según el tipo de dispositivo de destino de tu app, agrega las versiones más recientes
de las bibliotecas a tus dependencias:
-
Para la app de receptor de 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' }
-
En el caso de la app de remitente para Android, haz lo siguiente:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.1.1' implementation 'com.google.android.gms:play-services-cast-framework:23.0.0' }
-
Para la app de receptor de Android:
-
Guarda los cambios y haz clic en
Sync Project with Gradle Filesen la barra de herramientas.
-
Asegúrate de que tu
Podfilese oriente agoogle-cast-sdk4.8.6 o una versión posterior. -
Tener como objetivo iOS 16 o una versión posterior Consulta las notas de la versión para obtener más detalles.
platform: ios, '16' def target_pods pod 'google-cast-sdk', '~>4.8.6' end
- Se requiere la versión M87 o posterior del navegador Chromium.
-
Agrega la biblioteca de la API de Web Sender a tu proyecto
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
Requisito de AndroidX
Las versiones nuevas de los Servicios de Google Play requieren que se actualice una app para usar
el espacio de nombres androidx. Sigue las instrucciones para
migrar a AndroidX.
Requisitos previos de la app para Android TV
Para admitir Cast Connect en tu app para Android TV, debes crear y admitir eventos de una sesión multimedia. Los datos que proporciona tu sesión multimedia brindan la información básica, por ejemplo, la posición, el estado de reproducción, etc., para el estado de tu contenido multimedia. La biblioteca de Cast Connect también usa tu sesión multimedia para indicar cuándo recibió ciertos mensajes de un remitente, como una pausa.
Para obtener más información sobre la sesión multimedia y cómo inicializarla, consulta la guía para trabajar con una sesión multimedia.
Ciclo de vida de la sesión multimedia
Tu app debería crear una sesión multimedia cuando se inicia la reproducción y liberarla cuando ya no se pueda controlar. Por ejemplo, si tu app es de video, debes liberar la sesión cuando el usuario salga de la actividad de reproducción, ya sea seleccionando "atrás" para explorar otro contenido o colocando la app en segundo plano. Si tu app es de música, debes liberarla cuando ya no reproduzca ningún contenido multimedia.
Actualizando el estado de la sesión
Los datos de tu sesión multimedia deben mantenerse actualizados con el estado de tu reproductor. Por ejemplo, cuando se pausa la reproducción, debes actualizar el estado de reproducción y las acciones admitidas. En las siguientes tablas, se enumeran los estados que debes mantener actualizados.
MediaMetadataCompat
| Campo de metadatos | Descripción |
|---|---|
| METADATA_KEY_TITLE (obligatorio) | Es el título del contenido multimedia. |
| METADATA_KEY_DISPLAY_SUBTITLE | Es el subtítulo. |
| METADATA_KEY_DISPLAY_ICON_URI | Es la URL del ícono. |
| METADATA_KEY_DURATION (obligatorio) | Duración del contenido multimedia |
| METADATA_KEY_MEDIA_URI | Es el ID del contenido. |
| METADATA_KEY_ARTIST | El artista |
| METADATA_KEY_ALBUM | El álbum |
PlaybackStateCompat
| Método obligatorio | Descripción |
|---|---|
| setActions() | Establece los comandos multimedia compatibles. |
| setState() | Establece el estado de reproducción y la posición actual. |
MediaSessionCompat
| Método obligatorio | Descripción |
|---|---|
| setRepeatMode() | Establece el modo de repetición. |
| setShuffleMode() | Establece el modo aleatorio. |
| setMetadata() | Establece los metadatos de contenido multimedia. |
| setPlaybackState() | Establece el estado de reproducción. |
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);
}Cómo controlar el transporte
Tu app debería implementar la devolución de llamada del control de transporte de la sesión multimedia. En la siguiente tabla, se muestran las acciones de control de transporte que deben controlar:
MediaSessionCompat.Callback
| Acciones | Descripción |
|---|---|
| onPlay() | Reanudar |
| onPause() | Pausar |
| onSeekTo() | Cómo buscar una posición |
| onStop() | Detener el contenido multimedia actual |
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());
Configura la compatibilidad con Cast
Cuando una aplicación emisora envía una solicitud de inicio, se crea un intent con un espacio de nombres de la aplicación. Tu aplicación es responsable de controlarlo y crear una instancia del objeto CastReceiverContext cuando se inicia la app para TVs. El objeto CastReceiverContext es necesario para interactuar con Cast mientras se ejecuta la app para TVs. Este objeto permite que tu aplicación para TV acepte mensajes multimedia de Cast provenientes de cualquier remitente conectado.
Configuración de Android TV
Agrega un filtro de intents de inicio
Agrega un nuevo filtro de intents a la actividad que deseas que controle el intent de inicio desde tu app emisora:
<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>
Especifica el proveedor de opciones del receptor
Debes implementar un ReceiverOptionsProvider para proporcionar 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(); } }
Luego, especifica el proveedor de opciones en tu AndroidManifest:
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.example.mysimpleatvapplication.MyReceiverOptionsProvider" />
El ReceiverOptionsProvider se usa para proporcionar el CastReceiverOptions cuando se inicializa CastReceiverContext.
Contexto del receptor de Cast
Inicializa el objeto CastReceiverContext cuando se cree tu app:
override fun onCreate() { CastReceiverContext.initInstance(this) ... }
@Override public void onCreate() { CastReceiverContext.initInstance(this); ... }
Inicia el CastReceiverContext cuando tu app pase a primer plano:
CastReceiverContext.getInstance().start()
CastReceiverContext.getInstance().start();
Llama a stop() en CastReceiverContext después de que la app pase a segundo plano para las apps de video o las que no admiten la reproducción en segundo plano:
// Player has stopped. CastReceiverContext.getInstance().stop()
// Player has stopped. CastReceiverContext.getInstance().stop();
Además, si tu app admite la reproducción en segundo plano, llama a stop() en CastReceiverContext cuando se detenga la reproducción en segundo plano.
Te recomendamos que uses LifecycleObserver de la biblioteca androidx.lifecycle para administrar las llamadas a CastReceiverContext.start() y CastReceiverContext.stop(), en especial si tu aplicación nativa tiene varias actividades. Esto evita condiciones de carrera cuando llamas a start() y stop() desde diferentes actividades.
// 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">
Cómo conectar MediaSession a MediaManager
Cuando creas un MediaSession, también debes proporcionar el token MediaSession actual a CastReceiverContext para que sepa dónde enviar los comandos y recuperar el estado de reproducción de contenido multimedia:
val mediaManager: MediaManager = receiverContext.getMediaManager() mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
MediaManager mediaManager = receiverContext.getMediaManager(); mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());
Cuando liberes tu MediaSession debido a una reproducción inactiva, debes establecer un token nulo en MediaManager:
myPlayer.stop() mediaSession.release() mediaManager.setSessionCompatToken(null)
myPlayer.stop(); mediaSession.release(); mediaManager.setSessionCompatToken(null);
Si tu app admite la reproducción de contenido multimedia mientras está en segundo plano, en lugar de llamar a CastReceiverContext.stop() cuando se envía a segundo plano, debes llamarla solo cuando esté en segundo plano y ya no reproduzca contenido multimedia. Por ejemplo:
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(); } }
Cómo usar ExoPlayer con Cast Connect
Si usas Exoplayer, puedes usar MediaSessionConnector para mantener automáticamente la sesión y toda la información relacionada, incluido el estado de reproducción, en lugar de hacer un seguimiento manual de los cambios.
MediaSessionConnector.MediaButtonEventHandler
se puede usar para controlar los eventos de MediaButton llamando a
setMediaButtonEventHandler(MediaButtonEventHandler),
que, de lo contrario, controla
MediaSessionCompat.Callback
de forma predeterminada.
Para integrar MediaSessionConnector en tu app, agrega lo siguiente a la clase de actividad del reproductor o donde administres tu sesión multimedia:
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); ... } }
Configuración de la app emisora
Habilita la compatibilidad con Cast Connect
Una vez que hayas actualizado tu app emisora con compatibilidad con Cast Connect, puedes declarar su preparación configurando la marca androidReceiverCompatible en LaunchOptions como verdadera.
Se requiere la versión 19.0.0 o una posterior de play-services-cast-framework.
La marca androidReceiverCompatible se establece en
LaunchOptions (que forma parte de 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(); } }
Se requiere la versión v4.4.8 o una posterior de google-cast-sdk.
La marca androidReceiverCompatible se establece en
GCKLaunchOptions (que forma parte de
GCKCastOptions):
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID)) ... let launchOptions = GCKLaunchOptions() launchOptions.androidReceiverCompatible = true options.launchOptions = launchOptions GCKCastContext.setSharedInstanceWith(options)
Se requiere la versión M87 o posterior del navegador Chromium.
const context = cast.framework.CastContext.getInstance(); const castOptions = new cast.framework.CastOptions(); castOptions.receiverApplicationId = kReceiverAppID; castOptions.androidReceiverCompatible = true; context.setOptions(castOptions);
Configuración de la consola para desarrolladores de Cast
Configura la app para Android TV
Agrega el nombre del paquete de tu app para Android TV en Play Console de Cast para asociarlo con el ID de tu app de Cast.

Cómo registrar los dispositivos de desarrollador
Registra el número de serie del dispositivo Android TV que usarás para el desarrollo en la Consola para desarrolladores de Cast.

Por motivos de seguridad, sin el registro, Cast Connect solo funcionará para las apps instaladas desde Google Play Store.
Para obtener más información sobre cómo registrar un dispositivo Cast o Android TV para el desarrollo de Cast, consulta la página de registro.
Carga de contenido multimedia
Si ya implementaste la compatibilidad con vínculos directos en tu app para Android TV, deberías tener una definición similar configurada en el manifiesto de 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>
Carga por entidad en el remitente
En los remitentes, puedes pasar el vínculo directo configurando entity en la información de contenido multimedia para la solicitud de carga:
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)
Se requiere la versión M87 o posterior del navegador Chromium.
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);
El comando de carga se envía a través de un intent con tu vínculo directo y el nombre del paquete que definiste en Play Console.
Configura las credenciales de ATV en el remitente
Es posible que tu app de receptor web y tu app para Android TV admitan diferentes vínculos directos y credentials (por ejemplo, si manejas la autenticación de manera diferente en las dos plataformas). Para solucionar este problema, puedes proporcionar un entity y un credentials alternativos para 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)
Se requiere la versión M87 o posterior del navegador Chromium.
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);
Si se inicia la app de receptor web, usa entity y credentials en la solicitud de carga. Sin embargo, si se inicia tu app para Android TV, el SDK anula
entity y credentials con atvEntity y atvCredentials
(si se especifican).
Carga por Content ID o MediaQueueData
Si no usas entity ni atvEntity, y usas el Content ID o la URL de contenido en tu información de contenido multimedia, o bien usas los datos de solicitud de carga de contenido multimedia más detallados, debes agregar el siguiente filtro de intents predefinido en tu app para 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>
En el lado del remitente, de manera similar a la carga por entidad, puedes crear una solicitud de carga con la información de tu contenido y llamar a 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)
Se requiere la versión M87 o posterior del navegador Chromium.
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);
Controla las solicitudes de carga
En tu actividad, para controlar estas solicitudes de carga, debes controlar los intents en las devoluciones de llamada del ciclo de vida de la actividad:
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. ... } }
Si MediaManager detecta que el intent es de carga, extrae un objeto MediaLoadRequestData del intent y, luego, invoca MediaLoadCommandCallback.onLoad().
Debes anular este método para controlar la solicitud de carga. La devolución de llamada se debe registrar antes de que se llame a MediaManager.onNewIntent() (se recomienda que esté en un método onCreate() de 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); }
Para procesar el intent de carga, puedes analizarlo en las estructuras de datos que definimos (MediaLoadRequestData para las solicitudes de carga).
Cómo admitir comandos multimedia
Compatibilidad con el control de reproducción básico
Los comandos de integración básicos incluyen los comandos que son compatibles con la sesión multimedia. Estos comandos se notifican a través de devoluciones de llamada de la sesión multimedia. Debes registrar una devolución de llamada en la sesión multimedia para admitir esta función (es posible que ya lo estés haciendo).
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());
Compatibilidad con los comandos de control de Cast
Hay algunos comandos de Cast que no están disponibles en MediaSession, como skipAd() o setActiveMediaTracks().
Además, aquí se deben implementar algunos comandos de fila, ya que la fila de Cast no es totalmente compatible con la fila de 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());
Especifica los comandos multimedia compatibles
Al igual que con el receptor de Cast, la app para Android TV debe especificar qué comandos se admiten para que los emisores puedan habilitar o inhabilitar ciertos controles de la IU. Para
los comandos que forman parte de
MediaSession,
especifica los comandos en
PlaybackStateCompat.
Los comandos adicionales se deben especificar en el
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);
Ocultar botones no compatibles
Si tu app para Android TV solo admite el control multimedia básico, pero tu app de receptor web admite un control más avanzado, debes asegurarte de que tu app emisora se comporte correctamente cuando transmita a la app para Android TV. Por ejemplo, si tu app para Android TV no admite el cambio de la velocidad de reproducción, pero tu app de receptor web sí, debes configurar las acciones admitidas correctamente en cada plataforma y asegurarte de que tu app emisora renderice la IU de forma adecuada.
Modifica MediaStatus
Para admitir funciones avanzadas, como pistas, anuncios, transmisiones en vivo y colas, tu app para Android TV debe proporcionar información adicional que no se puede determinar a través de MediaSession.
Proporcionamos la clase MediaStatusModifier para que puedas lograrlo. MediaStatusModifier siempre operará en la
MediaSession que hayas establecido en
CastReceiverContext.
Para crear y transmitir MediaStatus, haz lo siguiente:
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();Nuestra biblioteca cliente obtendrá el MediaStatus base de MediaSession, y tu
app para Android TV puede especificar un estado adicional y anular el estado a través de un
modificador MediaStatus.
Algunos estados y metadatos se pueden establecer en MediaSession y
MediaStatusModifier. Te recomendamos encarecidamente que solo los configures en MediaSession. Aún puedes usar el modificador para anular los estados en MediaSession, pero no se recomienda, ya que el estado en el modificador siempre tiene una prioridad más alta que los valores proporcionados por MediaSession.
Cómo interceptar MediaStatus antes de enviarlo
Al igual que con el SDK de Web Receiver, si quieres realizar algunos ajustes finales antes de enviar, puedes especificar un MediaStatusInterceptor para procesar el MediaStatus que se enviará. Pasamos un MediaStatusWriter para manipular el MediaStatus antes de que se envíe.
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\"}")); } });
Cómo controlar las credenciales del usuario
Es posible que tu app para Android TV solo permita que ciertos usuarios inicien la sesión de la app o se unan a ella. Por ejemplo, solo permite que un remitente inicie o se una si se cumple lo siguiente:
- La app emisora accedió a la misma cuenta y al mismo perfil que la app de ATV.
- La app emisora accedió a la misma cuenta, pero con un perfil diferente al de la app de ATV.
Si tu app puede controlar varios usuarios o usuarios anónimos, puedes permitir que cualquier usuario adicional se una a la sesión de ATV. Si el usuario proporciona credenciales, tu app para ATV debe controlarlas para que se pueda hacer un seguimiento correcto de su progreso y de otros datos del usuario.
Cuando tu app emisora se inicia o se une a tu app para Android TV, debe proporcionar las credenciales que representan a quien se une a la sesión.
Antes de que un remitente inicie tu app para Android TV y se una a ella, puedes especificar un verificador de inicio para ver si se permiten las credenciales del remitente. De lo contrario, el SDK de Cast Connect recurrirá al inicio del receptor web.
Datos de las credenciales de inicio de la app emisora
En el lado del remitente, puedes especificar el CredentialsData para representar quién se une a la sesión.
El credentials es una cadena que puede definir el usuario, siempre que tu app de ATV pueda entenderla. El credentialsType define de qué plataforma proviene el CredentialsData o puede ser un valor personalizado. De forma predeterminada, se establece
en la plataforma desde la que se envía.
El CredentialsData solo se pasa a tu app para Android TV durante el inicio o el momento de unirse. Si lo vuelves a configurar mientras estás conectado, no se pasará a tu app para Android TV. Si el remitente cambia el perfil mientras está conectado, puedes permanecer en la sesión o llamar a SessionManager.endCurrentCastSession(boolean stopCasting) si crees que el perfil nuevo no es compatible con la sesión.
El
CredentialsData
de cada remitente se puede recuperar con
getSenders
en el
CastReceiverContext
para obtener el SenderInfo,
getCastLaunchRequest()
para obtener el
CastLaunchRequest
y, luego,
getCredentialsData().
Se requiere la versión 19.0.0 o una posterior de play-services-cast-framework.
CastContext.getSharedInstance().setLaunchCredentialsData( CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build() )
CastContext.getSharedInstance().setLaunchCredentialsData(
new CredentialsData.Builder()
.setCredentials("{\"userId\": \"abc\"}")
.build());Se requiere la versión v4.8.6 o una posterior de google-cast-sdk.
Se puede llamar en cualquier momento después de que se configuren las opciones:
GCKCastContext.setSharedInstanceWith(options).
GCKCastContext.sharedInstance().setLaunch( GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Se requiere la versión M87 o posterior del navegador Chromium.
Se puede llamar en cualquier momento después de que se configuren las opciones:
cast.framework.CastContext.getInstance().setOptions(options);.
let credentialsData = new chrome.cast.CredentialsData("{\"userId\": \"abc\"}"); cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);
Implementa el verificador de solicitudes de inicio de ATV
El CredentialsData se pasa a tu app para Android TV cuando un remitente intenta iniciar o unirse. Puedes
implementar un
LaunchRequestChecker.
para permitir o rechazar esta solicitud.
Si se rechaza una solicitud, se carga el receptor web en lugar de iniciarse de forma nativa en la app de ATV. Debes rechazar una solicitud si tu ATV no puede controlar al usuario que solicita iniciar o unirse. Por ejemplo, es posible que un usuario diferente haya accedido a la app de ATV que está realizando la solicitud y que tu app no pueda cambiar las credenciales, o que no haya un usuario que haya accedido a la app de ATV.
Si se permite una solicitud, se inicia la app de ATV. Puedes personalizar este comportamiento según si tu app admite el envío de solicitudes de carga cuando un usuario no accedió a la app de ATV o si hay una discrepancia de usuarios. Este comportamiento se puede personalizar por completo en LaunchRequestChecker.
Crea una clase que implemente la interfaz 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; }
Luego, configúralo en tu 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 resolución de true en
LaunchRequestChecker
inicia la app de ATV, y false inicia la app de Web Receiver.
Envía y recibe mensajes personalizados
El protocolo de Cast te permite enviar mensajes de cadena personalizados entre los remitentes y
tu aplicación receptora. Debes registrar un espacio de nombres (canal) para enviar
mensajes antes de inicializar tu
CastReceiverContext.
Android TV: Especifica un espacio de nombres personalizado
Debes especificar los espacios de nombres admitidos en tu
CastReceiverOptions
durante la configuración:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setCustomNamespaces( Arrays.asList("urn:x-cast:com.example.cast.mynamespace") ) .build() } }
urn:x-cast:.
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: Envío de mensajes
// 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: Recibe mensajes de espacio de nombres personalizado
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());