Esta página contiene fragmentos de código y descripciones de las funciones disponibles para personalizar una app receptora 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.gradle
dentro del directorio del módulo de tu aplicación. -
Verifica que
google()
esté incluido en elrepositories
que se muestra.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 receptora de Android:
dependencies { implementation 'com.google.android.gms:play-services-cast-tv:21.1.0' implementation 'com.google.android.gms:play-services-cast:21.5.0' }
-
Para la app de Android Sender:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.1.0' implementation 'com.google.android.gms:play-services-cast-framework:21.5.0' }
-
Para la app receptora de Android:
-
Guarda los cambios y haz clic en
Sync Project with Gradle Files
. en la barra de herramientas.
-
Asegúrate de que
Podfile
se oriente agoogle-cast-sdk
4.8.3 o una superior -
Orienta la app a iOS 14 o versiones posteriores. Consulta las Notas de la versión.
para obtener más información.
platform: ios, '14' def target_pods pod 'google-cast-sdk', '~>4.8.3' end
- Se requiere el navegador Chromium M87 o una versión posterior.
-
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
Para usar las nuevas versiones de los Servicios de Google Play, es necesario actualizar una app
el espacio de nombres androidx
Sigue las instrucciones para
Cómo migrar a AndroidX
Requisitos previos de la app para Android TV
Para que tu app de Android TV admita Cast Connect, debes crear y eventos de soporte de una sesión multimedia. Los datos que proporcionó tu sesión multimedia proporciona la información básica (por ejemplo, posición, estado de reproducción, etc.) para el estado de tus medios. La biblioteca de Cast Connect también usa tu sesión multimedia para indicar que recibió determinados mensajes de un remitente, como una pausa.
Para obtener más información sobre la sesión multimedia y cómo inicializar una sesión multimedia, consulta la cómo trabajar con una guía de sesiones multimedia.
Ciclo de vida de la sesión multimedia
Tu app debe crear una sesión multimedia cuando se inicie la reproducción y liberarla cuando se inicie la reproducción ya no se puede controlar. Por ejemplo, si tu app es una app de video, debe liberar la sesión cuando el usuario salga de la actividad de reproducción, ya sea mediante seleccionando "atrás" para explorar otro contenido o dejar la app en segundo plano. Si el es una app de música, debes lanzarla cuando tu app ya no se esté reproduciendo medios de comunicación.
Actualizando el estado de la sesión
Los datos de tu sesión multimedia deben mantenerse actualizados con el estado de de fútbol favorito. Por ejemplo, cuando se pausa la reproducción, debes actualizar la reproducción así como de las acciones admitidas. En las siguientes tablas, se enumeran los estados eres responsable de mantenerte al día.
MediaMetadataCompat
Campo de metadatos | Descripción |
---|---|
METADATA_KEY_TITLE (obligatorio) | Es el título del contenido multimedia. |
METADATA_KEY_DISPLAY_SUBTITLE | El subtítulo. |
METADATA_KEY_DISPLAY_ICON_URI | La URL del ícono. |
METADATA_KEY_DURATION (obligatorio) | Duración de medios |
METADATA_KEY_MEDIA_URI | Es el ID de 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() | Configura 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 manejar el control de transporte
Tu app debe implementar la devolución de llamada de control de transporte de sesiones multimedia. El En la siguiente tabla, se muestran las acciones de control de transporte que debe administrar:
MediaSessionCompat.Callback
Acciones | Descripción |
---|---|
onPlay() | Reanudar |
onPause() | Pausar |
onSeekTo() | Busca 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());
Cómo configurar 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 aplicaciones. Tu aplicación es responsable de manejarlo.
y crear una instancia del
CastReceiverContext
cuando se inicia la app de TV. Se necesita el objeto CastReceiverContext
para interactuar con Cast mientras se ejecuta la app de TV. Este objeto habilita tu TV
para aceptar los mensajes multimedia de Cast provenientes de cualquier remitente conectado.
Configuración de Android TV
Cómo agregar un filtro de intents de inicio
Agrega un filtro de intents nuevo a la actividad para la que quieras controlar el inicio. intent de la 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" />
ReceiverOptionsProvider
se usa para proporcionar CastReceiverOptions
cuando
Se inicializó CastReceiverContext
.
Contexto del receptor de transmisiones
Inicializa el
CastReceiverContext
cuando se crea tu app:
override fun onCreate() { CastReceiverContext.initInstance(this) ... }
@Override public void onCreate() { CastReceiverContext.initInstance(this); ... }
Inicia el CastReceiverContext
cuando tu app se mueva al primer plano:
CastReceiverContext.getInstance().start()
CastReceiverContext.getInstance().start();
Llamada
stop()
en la
CastReceiverContext
Después de que la app pasa a segundo plano en el caso de apps de video o que no son compatibles
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 deja de reproducirse mientras se ejecuta en segundo plano
Te recomendamos que uses LifecycleObserver desde el
androidx.lifecycle
biblioteca para administrar las llamadas
CastReceiverContext.start()
y
CastReceiverContext.stop()
,
en especial si tu app nativa tiene varias actividades. Esto evita una carrera
condiciones 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 del contenido multimedia:
val mediaManager: MediaManager = receiverContext.getMediaManager() mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
MediaManager mediaManager = receiverContext.getMediaManager(); mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());
Cuando liberes tu MediaSession
por tener una reproducción inactiva, deberás establecer una
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, es posible que
de llamadas
CastReceiverContext.stop()
cuando tu app se envía a segundo plano, debes llamarla solo cuando esta
está en segundo plano y ya no reproduce 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 utilizas
Exoplayer
, puedes usar el
MediaSessionConnector
para mantener automáticamente la sesión y toda la información relacionada, incluida la
el estado de reproducción en lugar de
hacer un seguimiento manual de los cambios.
MediaSessionConnector.MediaButtonEventHandler
se puede usar para controlar eventos de MediaButton llamando
setMediaButtonEventHandler(MediaButtonEventHandler)
que, de otro modo, se gestionan
MediaSessionCompat.Callback
de forma predeterminada.
Para realizar la integración
MediaSessionConnector
en tu app, agrega lo siguiente a tu clase de actividad de jugador o a donde sea que
administrar 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 del remitente
Habilitar la compatibilidad con Cast Connect
Una vez que hayas actualizado tu app emisora con compatibilidad con Cast Connect, podrás declarar
su preparación estableciendo la
androidReceiverCompatible
marca activada
LaunchOptions
como true.
Requiere la versión play-services-cast-framework
19.0.0
o superior.
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(); } }
Requiere google-cast-sdk
versión v4.4.8
o
mayores.
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)
Requiere la versión del navegador Chromium
M87
o una versión posterior.
const context = cast.framework.CastContext.getInstance(); const castOptions = new cast.framework.CastOptions(); castOptions.receiverApplicationId = kReceiverAppID; castOptions.androidReceiverCompatible = true; context.setOptions(castOptions);
Configuración de Cast Developer Console
Cómo configurar la app para Android TV
Agrega el nombre del paquete de tu app para Android TV en Consola para desarrolladores 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 vas a utilizar. para el desarrollo en la Consola para desarrolladores de Cast.
Sin el registro, Cast Connect solo funcionará para las apps instaladas desde el Google Play Store por motivos de seguridad.
Obtén más información sobre cómo registrar un dispositivo Cast o Android TV para 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 tu 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>
Cargar por entidad en el remitente
En los remitentes, puedes pasar el vínculo directo si configuras entity
en el 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)
Requiere la versión del navegador Chromium
M87
o una versión posterior.
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 la consola para desarrolladores.
Configura credenciales de ATV en el remitente
Es posible que la app del receptor web y la app de Android TV sean compatibles con diferentes
vínculos directos y credentials
(por ejemplo, si manejas la autenticación
de forma diferente en ambas plataformas). Para solucionarlo, puedes proporcionar
entity
y credentials
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)
Requiere la versión del navegador Chromium
M87
o una versión posterior.
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 del receptor web, esta usa entity
y credentials
en
la solicitud de carga. Sin embargo, si se inicia la app para Android TV, el SDK anula
la entity
y la credentials
con tu atvEntity
y atvCredentials
(si se especifica).
Cómo cargar por Content ID o MediaQueueData
Si no usas entity
ni atvEntity
, y usas Content ID o
URL del contenido en tu información de medios o usar la carga de medios más detallada
para solicitar datos, debes agregar el siguiente filtro de intents predefinido
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 cargar por entidad, puedes realizar lo siguiente:
puede 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)
Requiere la versión del navegador Chromium
M87
o una versión posterior.
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);
Maneja 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 tu 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 es MediaManager
detecta que el intent es un intent de carga, extrae un
MediaLoadRequestData
objeto del intent e invocar
MediaLoadCommandCallback.onLoad()
Debes anular este método para controlar la solicitud de carga. La devolución de llamada debe
deben registrarse antes del
MediaManager.onNewIntent()
se llama (se recomienda estar en un 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.
definimos
(MediaLoadRequestData
para las solicitudes de carga).
Compatibilidad con comandos multimedia
Compatibilidad básica con los controles de reproducción
Los comandos básicos de integración incluyen los que son compatibles con el contenido multimedia. sesión. Estos comandos reciben notificaciones mediante devoluciones de llamada de sesiones multimedia. Debes registra una devolución de llamada a la sesión multimedia para respaldar esto (es posible que ).
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 transmisión
Hay algunos comandos de Cast que no se encuentran disponibles en
MediaSession
:
como, por ejemplo,
skipAd()
o
setActiveMediaTracks()
Además, algunos comandos de la cola se deben implementar aquí, ya que la cola de Cast
no es completamente compatible con la cola 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());
Cómo especificar comandos de contenido multimedia compatibles
Al igual que con tu receptor de transmisiones, tu app de Android TV debe especificar los comandos
son compatibles, por lo que los remitentes pueden habilitar o inhabilitar ciertos controles de la IU. Para
comandos que forman parte del
MediaSession
:
especificar 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 receptor web admite un control más avanzado, debes asegurarte de que tu app emisora se comporte correctamente cuando transmitas a la app de Android TV. Por ejemplo, si tu dispositivo Android TV La app no admite el cambio de la velocidad de reproducción, mientras que la app del receptor web sí lo admite. debes configurar correctamente las acciones admitidas en cada plataforma y asegurarte de que tu app emisora renderiza la IU correctamente.
Cómo modificar MediaStatus
Para admitir funciones avanzadas como segmentos, anuncios, transmisiones en vivo y filas, tu dispositivo Android
La app de TV debe proporcionar información adicional que no se pueda determinar mediante
MediaSession
Te proporcionamos
MediaStatusModifier
para lograrlo. MediaStatusModifier
siempre operará en la
MediaSession
que configuraste en
CastReceiverContext
Para crear y transmitir
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();
Nuestra biblioteca cliente obtendrá la MediaStatus
base de MediaSession
, tu
La app para Android TV puede especificar un estado adicional y anular el estado mediante un
Modificador MediaStatus
.
Algunos estados y metadatos pueden establecerse tanto en MediaSession
como en
MediaStatusModifier
Te recomendamos que solo las configures
MediaSession
Aún puedes usar el modificador para anular los estados en
MediaSession
: No es aconsejable porque el estado en el modificador siempre
tienen una prioridad más alta que los valores proporcionados por MediaSession
.
Cómo interceptar el MediaStatus antes de enviarlo
Igual que el SDK del receptor web, si quieres realizar algunos toques finales antes
de envío, puedes especificar un
MediaStatusInterceptor
para procesar los
MediaStatus
a
que se enviará. Pasamos un
MediaStatusWriter
para manipular 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\"}")); } });
Administra las credenciales de los usuarios
Es posible que tu app para Android TV solo permita que ciertos usuarios la inicien o se unan a ella sesión. Por ejemplo, permite que un remitente inicie la llamada o se una a ella solo si se cumplen las siguientes condiciones:
- La app emisora accedió a la misma cuenta y perfil que la app de ATV.
- La app emisora accedió a la misma cuenta, pero con un perfil diferente que a la app de ATV.
Si tu app puede administrar múltiples usuarios o usuarios anónimos, puedes permitir cualquier acceso usuario se una a la sesión de ATV. Si el usuario proporciona las credenciales, la app de ATV necesita administrar sus credenciales para que su progreso y otros datos del usuario puedan un seguimiento adecuado.
Cuando tu app emisora se inicia o se une a tu app para Android TV, la app emisora Debes proporcionar las credenciales que representen quién se unirá a la sesión.
Antes de que un remitente inicie tu app para Android TV y se una a ella, puedes especificar una Iniciar el verificador para ver si las credenciales de remitente están permitidas. De lo contrario, la herramienta Cast El SDK de Connect recurre a la ejecución de tu receptor web.
Datos de credenciales de inicio de apps emisoras
En el lado del remitente, puedes especificar el CredentialsData
para representar quién es
unirte a la sesión.
credentials
es una cadena que el usuario puede definir, siempre y cuando tu ATV
la aplicación puedan comprenderla. credentialsType
define la plataforma
CredentialsData
proviene o puede ser un valor personalizado. De forma predeterminada, se establece
a la plataforma desde la que se envían.
El CredentialsData
solo se pasa a tu app para Android TV durante el inicio o
hora de ingreso. Si la vuelves a configurar mientras tienes conexión, no se enviará a
la app para Android TV. Si el remitente cambia de perfil mientras está conectado, tú
puede permanecer en la sesión o llamar
SessionManager.endCurrentCastSession(boolean stopCasting)
si cree que el perfil nuevo no es compatible con la sesión.
El
CredentialsData
de cada remitente se pueden recuperar
getSenders
en la
CastReceiverContext
para obtener el SenderInfo
,
getCastLaunchRequest()
para obtener la
CastLaunchRequest
:
y luego
getCredentialsData()
.
Requiere la versión play-services-cast-framework
19.0.0
o superior.
CastContext.getSharedInstance().setLaunchCredentialsData( CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build() )
CastContext.getSharedInstance().setLaunchCredentialsData( new CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build());
Requiere google-cast-sdk
versión v4.8.3
o
mayores.
Se puede llamar en cualquier momento una vez que se hayan configurado las siguientes opciones:
GCKCastContext.setSharedInstanceWith(options)
GCKCastContext.sharedInstance().setLaunch( GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Requiere la versión del navegador Chromium
M87
o una versión posterior.
Se puede llamar en cualquier momento una vez que se hayan configurado las siguientes 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 envía a tu app para Android TV cuando un remitente intenta iniciarla o unirse. Puedes
implementar una
LaunchRequestChecker
para permitir o rechazar esta solicitud.
Si se rechaza una solicitud, se cargará el receptor web en lugar de iniciarse. de forma nativa en la app de ATV. Debes rechazar una solicitud si tu ATV no puede hacerlo. el usuario que solicita iniciar una reunión o unirse a ella. Los ejemplos podrían ser que un cambio El usuario accedió a la app de ATV de lo que solicitaba y la app no puede administrar el cambio de credenciales o no hay ningún usuario conectado App de ATV.
Si se permite una solicitud, se inicia la app de ATV. Puedes personalizarlo
según si tu app admite el envío de solicitudes de carga cuando un usuario
No se accedió a la app de ATV o si hay una discrepancia de usuarios. Este comportamiento es
por completo personalizables en LaunchRequestChecker
.
Crea una clase que implemente la
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úrala 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(); } }
Resuelve true
en la
LaunchRequestChecker
inicia la app de ATV y false
inicia la app del receptor web.
Envío y Cómo recibir mensajes personalizados
El protocolo de transmisión te permite enviar mensajes de cadena personalizados entre remitentes y
la aplicación receptora. Debes registrar un espacio de nombres (canal) para enviar
los 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() } }
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: Cómo enviar 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 personalizados
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());