1. Descripción general
En este codelab, aprenderás a modificar una app de Android TV existente para que admita la transmisión y la comunicación desde tus apps emisoras de Cast existentes.
¿Qué son Google Cast y Cast Connect?
Google Cast permite a los usuarios transmitir contenido desde un dispositivo móvil a una TV. Una sesión típica de Google Cast consta de dos componentes: una aplicación emisora y una receptora. Las aplicaciones emisoras, como una app para dispositivos móviles o un sitio web tal como YouTube.com, inician y controlan la reproducción de una aplicación receptora compatible con Cast. Estas últimas son apps HTML 5 que se ejecutan en dispositivos Chromecast y Android TV.
Casi todo el estado de una sesión de Cast se almacena en la aplicación receptora. Cuando el estado se actualiza (por ejemplo, si se carga un nuevo elemento multimedia), se transmite un estado de contenido multimedia a todas las emisoras. Estas transmisiones contienen el estado actual de la sesión de Cast. Las aplicaciones emisoras usan este estado de contenido multimedia a fin de mostrar la información de reproducción en su IU.
Cast Connect compilará sobre esta infraestructura, y tu app de Android TV actuará como receptora. La biblioteca de Cast Connect permite que la app de Android TV reciba mensajes y transmita el estado del contenido multimedia como si fuera una aplicación receptora de transmisión.
¿Qué compilaremos?
Cuando completes este codelab, podrás usar las apps emisoras de Cast para transmitir videos a una app de Android TV. Esta app también puede comunicarse con las apps emisoras mediante el protocolo de Cast.
Qué aprenderás
- Cómo agregar la biblioteca de Cast Connect a una app de ATV de muestra
- Cómo conectar una emisora de Cast e iniciar la app de ATV
- Cómo iniciar la reproducción de contenido multimedia en la app de ATV desde una app emisora de Cast
- Cómo enviar el estado del contenido multimedia desde la app de ATV a las apps emisoras de Cast
Requisitos
- La versión más reciente del SDK de Android
- La versión más reciente de Android Studio Específicamente, en
Chipmunk | 2021.2.1
o versiones posteriores. - Un dispositivo Android TV que tenga habilitadas las opciones para desarrolladores y la depuración por USB
- Un teléfono Android que tenga habilitadas las opciones para desarrolladores y la depuración por USB
- Un cable de datos USB para conectar tu teléfono Android y dispositivos Android TV a la computadora de desarrollo
- Conocimientos básicos sobre el desarrollo de aplicaciones para Android con Kotlin
2. Obtén el código de muestra
Puedes descargar el código de muestra completo a tu computadora…
y descomprimir el archivo ZIP que se descargó.
3. Ejecuta la app de muestra
Primero, veamos el aspecto de la app de muestra completa. La app de Android TV usa la IU de Leanback y un reproductor de video básico. El usuario puede seleccionar un video de una lista y, a continuación, reproducirlo en la TV. Con la app emisora para dispositivos móviles complementaria, un usuario también podrá transmitir un video a la app de Android TV.
Cómo registrar los dispositivos de desarrollador
Para habilitar las capacidades de Cast Connect para el desarrollo de aplicaciones, debes registrar el número de serie del Chromecast integrado en el dispositivo Android TV que usarás en la Play Console de Cast. Puedes encontrar el número de serie en Settings > Device Preferences > Chromecast built-in > Serial number, en tu Android TV. Ten en cuenta que este número de serie es diferente del correspondiente al dispositivo físico, y debe obtenerse del método descrito anteriormente.
Por razones de seguridad, sin el registro, Cast Connect solo funcionará para apps instaladas desde Google Play Store. Reinicia el dispositivo después de 15 minutos desde que se inició el proceso de registro.
Cómo instalar la app emisora de Android
Para probar el envío de solicitudes desde un dispositivo móvil, proporcionamos una aplicación emisora sencilla llamada Cast Videos como archivo mobile-sender-0629.apk
en la descarga del archivo ZIP del código fuente. Aprovecharemos ADB para instalar el APK. Si ya instalaste una versión distinta de Cast Videos, antes de continuar, desinstala esa versión de los perfiles del dispositivo.
- Habilita las opciones para desarrolladores y la depuración por USB en tu teléfono Android.
- Conecta un cable de datos USB de modo que conectes tu teléfono Android a tu computadora de desarrollo.
- Instala
mobile-sender-0629.apk
en tu teléfono Android.
- Puedes encontrar la app emisora Cast Videos en tu teléfono Android.
Cómo instalar la app de Android TV
Las siguientes instrucciones describen cómo abrir y ejecutar la app de muestra completa en Android Studio:
- Selecciona Import Project en la pantalla de bienvenida o las opciones de menú File > New > Import Project....
- Selecciona el directorio
app-done
de la carpeta del código de muestra y haz clic en OK. - Haz clic en File > Sync Project with Gradle Files.
- Habilita las opciones para desarrolladores y la depuración por USB en tu dispositivo Android TV.
- ADB se conectará con tu dispositivo Android TV, y este último debería aparecer en Android Studio.
- Haz clic en el botón Run. Deberías ver que aparece la app de ATV llamada Cast Connect Codelab después de unos segundos.
Reproduzcamos Cast Connect con la app de ATV
- Ve a la pantalla principal de Android TV.
- Abre la app emisora Cast Videos en tu teléfono Android. Haz clic en el botón para transmitir y selecciona tu dispositivo ATV.
- La app de ATV del codelab Cast Connect del codelab se iniciará en tu ATV, y el botón para transmitir del remitente indicará que está conectada .
- Selecciona un video de la app de ATV. Este comenzará a reproducirse en tu ATV.
- En tu teléfono celular, ahora aparecerá un minicontrolador en la parte inferior de la app emisora. Puedes usar el botón de reproducción/pausa a los efectos de controlar la reproducción.
- Selecciona un video del teléfono celular y presiona botón de reproducción. El video comenzará a reproducirse en tu ATV, y el control expandido se mostrará en el dispositivo móvil emisor.
- Bloquea el teléfono. Cuando lo desbloquees, deberías ver una notificación en la pantalla de bloqueo que permita controlar la reproducción de contenido multimedia o detener la transmisión.
4. Prepara el proyecto inicial
Ahora que verificamos la integración con Cast Connect de la app completa, debemos agregar compatibilidad con Cast Connect a la app de inicio que descargaste. Ahora está todo listo para compilar sobre el proyecto inicial usando Android Studio:
- Selecciona Import Project en la pantalla de bienvenida o las opciones del menú File > New > Import Project….
- Selecciona el directorio
app-start
en la carpeta del código de muestra y haz clic en Aceptar. - Haz clic en File > Sync Project with Gradle Files.
- Selecciona un dispositivo ATV y haz clic en el botón Run para ejecutar la app y explorar la IU.
Diseño de apps
La app proporciona una lista de videos que el usuario puede explorar. Los usuarios pueden seleccionar un video y reproducirlo en Android TV. La app consta de dos actividades principales: MainActivity
y PlaybackActivity
.
MainActivity
Esta actividad contiene un Fragmento (MainFragment
). La configuración de la lista de videos y sus metadatos asociados se encuentran en la clase MovieList
, y el método setupMovies()
se llama para compilar una lista de objetos Movie
.
Un objeto Movie
representa una entidad de video con un título, una descripción, miniaturas de imágenes y la URL del video. Cada objeto Movie
está vinculado a un CardPresenter
a fin de presentar la miniatura del video con el título y estudio, y se pasa a ArrayObjectAdapter
.
Cuando se seleccione un elemento, el objeto Movie
correspondiente se pasará a la PlaybackActivity
.
PlaybackActivity
Esta actividad contiene un Fragmento (PlaybackVideoFragment
) que aloja una VideoView
con ExoPlayer
, algunos controles de contenido multimedia y un área de texto que muestra la descripción del video seleccionado y le permite al usuario reproducirlo en Android TV. El usuario puede utilizar el control remoto para reproducir, pausar o saltar la reproducción de los videos.
Requisitos previos de Cast Connect
Cast Connect usa versiones nuevas de los Servicios de Google Play que requieren que se actualice tu app de ATV para usar el espacio de nombres AndroidX.
Para que tu app de Android TV admita Cast Connect, debes crear y admitir eventos desde una sesión multimedia. La biblioteca de Cast Connect generará el estado de contenido multimedia en función del estado de la sesión multimedia. La biblioteca de Cast Connect también usará esta sesión para indicar que recibió determinados mensajes de una emisora, como un evento de pausa.
5. Cómo configurar la compatibilidad con Cast
Dependencias
Actualiza el archivo build.gradle
de la app para incluir las dependencias de biblioteca necesarias:
dependencies {
....
// Cast Connect libraries
implementation 'com.google.android.gms:play-services-cast-tv:20.0.0'
implementation 'com.google.android.gms:play-services-cast:21.1.0'
}
Sincroniza el proyecto a fin de confirmar que se compile sin errores.
Inicialización
CastReceiverContext
es un objeto singleton que coordina todas las interacciones de Cast. Debes implementar la interfaz de ReceiverOptionsProvider
de modo que proporciones las CastReceiverOptions
cuando se inicialice CastReceiverContext
.
Crea el archivo CastReceiverOptionsProvider.kt
y agrega la siguiente clase al proyecto:
package com.google.sample.cast.castconnect
import android.content.Context
import com.google.android.gms.cast.tv.ReceiverOptionsProvider
import com.google.android.gms.cast.tv.CastReceiverOptions
class CastReceiverOptionsProvider : ReceiverOptionsProvider {
override fun getOptions(context: Context): CastReceiverOptions {
return CastReceiverOptions.Builder(context)
.setStatusText("Cast Connect Codelab")
.build()
}
}
Luego, especifica el proveedor de opciones de la receptora en la etiqueta <application>
del archivo AndroidManifest.xml
de la app:
<application>
...
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.sample.cast.castconnect.CastReceiverOptionsProvider" />
</application>
Para conectarte con la app de ATV desde la emisora de Cast, selecciona la actividad que quieras iniciar. En este codelab, iniciaremos el MainActivity
de la app cuando se inicie una sesión de transmisión. En el archivo AndroidManifest.xml
, agrega el filtro de intents de inicio en la MainActivity
.
<activity android:name=".MainActivity">
...
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Ciclo de vida del contexto del receptor de transmisión
Debes iniciar el CastReceiverContext
cuando se inicie tu app. Debes detener el CastReceiverContext
cuando la app pase a segundo plano. Te recomendamos que uses el LifecycleObserver
de la biblioteca de androidx.lifecycle para administrar las llamadas a CastReceiverContext.start()
y CastReceiverContext.stop()
.
Abre el archivo MyApplication.kt
, inicializa el contexto de transmisión mediante una llamada a initInstance()
en el método onCreate
de la aplicación. En la clase AppLifeCycleObserver
, ejecuta start()
en relación con el CastReceiverContext
cuando se reanude la aplicación y stop()
cuando se detenga:
package com.google.sample.cast.castconnect
import com.google.android.gms.cast.tv.CastReceiverContext
...
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
CastReceiverContext.initInstance(this)
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())
}
class AppLifecycleObserver : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
Log.d(LOG_TAG, "onResume")
CastReceiverContext.getInstance().start()
}
override fun onPause(owner: LifecycleOwner) {
Log.d(LOG_TAG, "onPause")
CastReceiverContext.getInstance().stop()
}
}
}
Cómo conectar MediaSession a MediaManager
MediaManager
es una propiedad del singleton CastReceiverContext
, que administra el estado de contenido multimedia, controla el intent de carga, convierte los mensajes del espacio de nombres del contenido multimedia de las emisoras en comandos multimedia y envía el estado de dicho contenido a las emisoras.
Cuando creas una MediaSession
, también debes proporcionar el token de MediaSession
actual a MediaManager
de manera que sepa a dónde enviar los comandos y recuperar el estado de reproducción del contenido multimedia. En el archivo PlaybackVideoFragment.kt
, asegúrate de que se inicialice MediaSession
antes de establecer el token en MediaManager
.
import com.google.android.gms.cast.tv.CastReceiverContext
import com.google.android.gms.cast.tv.media.MediaManager
...
class PlaybackVideoFragment : VideoSupportFragment() {
private var castReceiverContext: CastReceiverContext? = null
...
private fun initializePlayer() {
if (mPlayer == null) {
...
mMediaSession = MediaSessionCompat(getContext(), LOG_TAG)
...
castReceiverContext = CastReceiverContext.getInstance()
if (castReceiverContext != null) {
val mediaManager: MediaManager = castReceiverContext!!.getMediaManager()
mediaManager.setSessionCompatToken(mMediaSession!!.getSessionToken())
}
}
}
}
Cuando liberes tu MediaSession
por tener una reproducción inactiva, deberás establecer un token nulo en MediaManager
:
private fun releasePlayer() {
mMediaSession?.release()
castReceiverContext?.mediaManager?.setSessionCompatToken(null)
...
}
Ejecutemos la app de muestra
Haz clic en el botón Run para implementar la app en tu dispositivo ATV, cierra la app y vuelve a la pantalla principal de ATV. En la emisora, haz clic en el botón para transmitir y selecciona tu dispositivo ATV. Verás que la app de ATV se inició en el dispositivo ATV y que el estado del botón para transmitir es el de conectado.
6. Carga de contenido multimedia
El comando de carga se enviará mediante un intent con el nombre de paquete que definiste en la Play Console. Deberás agregar el siguiente filtro de intents predefinido en tu app de Android TV a los efectos de especificar la actividad de destino que recibirá este intent. En el archivo AndroidManifest.xml
, agrega el filtro de intents de carga a PlayerActivity
:
<activity android:name="com.google.sample.cast.castconnect.PlaybackActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Cómo administrar las solicitudes de carga en Android TV
Ahora que la actividad está configurada para recibir este intent que contiene una solicitud de carga, necesitaremos administrarla.
La app llamará a un método privado llamado processIntent
cuando se inicie la actividad. Este método contiene la lógica para procesar intents entrantes. A fin de controlar una solicitud de carga, modificaremos este método y enviaremos el intent para que se procese llamando al método onNewIntent
de la instancia de MediaManager
. Si MediaManager
detecta que el intent es una solicitud de carga, extraerá el objeto MediaLoadRequestData
del intent e invocará a MediaLoadCommandCallback.onLoad()
. Modifica el método processIntent
en el archivo PlaybackVideoFragment.kt
para controlar el intent que contiene la solicitud de carga:
fun processIntent(intent: Intent?) {
val mediaManager: MediaManager = CastReceiverContext.getInstance().getMediaManager()
// Pass intent to Cast SDK
if (mediaManager.onNewIntent(intent)) {
return
}
// Clears all overrides in the modifier.
mediaManager.getMediaStatusModifier().clear()
// If the SDK doesn't recognize the intent, handle the intent with your own logic.
...
}
A continuación, extenderemos la clase abstracta MediaLoadCommandCallback
, que anulará el método onLoad()
que MediaManager
llama. Este método recibirá los datos de la solicitud de carga y los convertirá en un objeto Movie
. Una vez convertida, la película se reproducirá localmente. Luego, el MediaManager
se actualizará con la MediaLoadRequest
y transmitirá el MediaStatus
a las emisoras conectadas. Crea una clase privada anidada llamada MyMediaLoadCommandCallback
en el archivo PlaybackVideoFragment.kt
:
import com.google.android.gms.cast.MediaLoadRequestData
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.cast.MediaError
import com.google.android.gms.cast.tv.media.MediaException
import com.google.android.gms.cast.tv.media.MediaCommandCallback
import com.google.android.gms.cast.tv.media.QueueUpdateRequestData
import com.google.android.gms.cast.tv.media.MediaLoadCommandCallback
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.Tasks
import android.widget.Toast
...
private inner class MyMediaLoadCommandCallback : MediaLoadCommandCallback() {
override fun onLoad(
senderId: String?, mediaLoadRequestData: MediaLoadRequestData): Task<MediaLoadRequestData> {
Toast.makeText(activity, "onLoad()", Toast.LENGTH_SHORT).show()
return if (mediaLoadRequestData == null) {
// Throw MediaException to indicate load failure.
Tasks.forException(MediaException(
MediaError.Builder()
.setDetailedErrorCode(MediaError.DetailedErrorCode.LOAD_FAILED)
.setReason(MediaError.ERROR_REASON_INVALID_REQUEST)
.build()))
} else Tasks.call {
play(convertLoadRequestToMovie(mediaLoadRequestData)!!)
// Update media metadata and state
val mediaManager = castReceiverContext!!.mediaManager
mediaManager.setDataFromLoad(mediaLoadRequestData)
mediaLoadRequestData
}
}
}
private fun convertLoadRequestToMovie(mediaLoadRequestData: MediaLoadRequestData?): Movie? {
if (mediaLoadRequestData == null) {
return null
}
val mediaInfo: MediaInfo = mediaLoadRequestData.getMediaInfo() ?: return null
var videoUrl: String = mediaInfo.getContentId()
if (mediaInfo.getContentUrl() != null) {
videoUrl = mediaInfo.getContentUrl()
}
val metadata: MediaMetadata = mediaInfo.getMetadata()
val movie = Movie()
movie.videoUrl = videoUrl
movie.title = metadata?.getString(MediaMetadata.KEY_TITLE)
movie.description = metadata?.getString(MediaMetadata.KEY_SUBTITLE)
if(metadata?.hasImages() == true) {
movie.cardImageUrl = metadata.images[0].url.toString()
}
return movie
}
Ahora que se definió la devolución de llamada, deberemos registrarla en el MediaManager
. Se deberá realizar este registro antes de llamar a MediaManager.onNewIntent()
. Agrega setMediaLoadCommandCallback
cuando se inicialice el reproductor:
private fun initializePlayer() {
if (mPlayer == null) {
...
mMediaSession = MediaSessionCompat(getContext(), LOG_TAG)
...
castReceiverContext = CastReceiverContext.getInstance()
if (castReceiverContext != null) {
val mediaManager: MediaManager = castReceiverContext.getMediaManager()
mediaManager.setSessionCompatToken(mMediaSession.getSessionToken())
mediaManager.setMediaLoadCommandCallback(MyMediaLoadCommandCallback())
}
}
}
Ejecutemos la app de muestra
Haz clic en el botón Run para implementar la app en tu dispositivo ATV. En la emisora, haz clic en el botón para transmitir y selecciona tu dispositivo ATV. La app de ATV se iniciará en el dispositivo ATV. Selecciona un video en el dispositivo móvil. Este comenzará a reproducirse en ATV. Comprueba si recibes una notificación en el teléfono en la que tengas controles de reproducción. Usa los controles, por ejemplo, la pausa: el video deberá pausarse en el dispositivo ATV.
7. Cómo agregar compatibilidad con los comandos de control de Cast
La aplicación actual ahora admite comandos básicos compatibles con una sesión multimedia, como reproducir, pausar y saltar. Sin embargo, hay algunos comandos de control de Cast que no están disponibles en la sesión multimedia. Debes registrar una MediaCommandCallback
para admitir esos comandos.
Agrega MyMediaCommandCallback
a la instancia de MediaManager
con setMediaCommandCallback
cuando se inicialice el reproductor:
private fun initializePlayer() {
...
castReceiverContext = CastReceiverContext.getInstance()
if (castReceiverContext != null) {
val mediaManager = castReceiverContext!!.mediaManager
...
mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
}
}
Crea la clase MyMediaCommandCallback
de modo que se anulen los métodos, como onQueueUpdate()
a fin de admitir esos comandos de control de Cast:
private inner class MyMediaCommandCallback : MediaCommandCallback() {
override fun onQueueUpdate(
senderId: String?,
queueUpdateRequestData: QueueUpdateRequestData
): Task<Void> {
Toast.makeText(getActivity(), "onQueueUpdate()", Toast.LENGTH_SHORT).show()
// Queue Prev / Next
if (queueUpdateRequestData.getJump() != null) {
Toast.makeText(
getActivity(),
"onQueueUpdate(): Jump = " + queueUpdateRequestData.getJump(),
Toast.LENGTH_SHORT
).show()
}
return super.onQueueUpdate(senderId, queueUpdateRequestData)
}
}
8. Cómo trabajar con el estado del contenido multimedia
Cómo modificar el estado del contenido multimedia
Cast Connect obtiene el estado básico del contenido de la sesión multimedia. Para admitir funciones avanzadas, tu app de Android TV puede especificar y anular propiedades de estado adicionales mediante un MediaStatusModifier
. MediaStatusModifier
siempre funcionará en la MediaSession
que configuraste en CastReceiverContext
.
Por ejemplo, si quieres especificar setMediaCommandSupported
cuando se active la devolución de llamada onLoad
, incluye lo siguiente:
import com.google.android.gms.cast.MediaStatus
...
private class MyMediaLoadCommandCallback : MediaLoadCommandCallback() {
fun onLoad(
senderId: String?,
mediaLoadRequestData: MediaLoadRequestData
): Task<MediaLoadRequestData> {
Toast.makeText(getActivity(), "onLoad()", Toast.LENGTH_SHORT).show()
...
return Tasks.call({
play(convertLoadRequestToMovie(mediaLoadRequestData)!!)
...
// Use MediaStatusModifier to provide additional information for Cast senders.
mediaManager.getMediaStatusModifier()
.setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT, true)
.setIsPlayingAd(false)
mediaManager.broadcastMediaStatus()
// Return the resolved MediaLoadRequestData to indicate load success.
mediaLoadRequestData
})
}
}
Cómo interceptar el MediaStatus antes de su envío
Al igual que con el MessageInterceptor
del SDK de receptor web, puedes especificar un MediaStatusWriter
en tu MediaManager
para realizar modificaciones adicionales en tu MediaStatus
antes de que se transmita a los remitentes conectados.
Por ejemplo, puedes configurar datos personalizados en el MediaStatus
antes de enviarlos a emisoras móviles:
import com.google.android.gms.cast.tv.media.MediaManager.MediaStatusInterceptor
import com.google.android.gms.cast.tv.media.MediaStatusWriter
import org.json.JSONObject
import org.json.JSONException
...
private fun initializePlayer() {
if (mPlayer == null) {
...
if (castReceiverContext != null) {
...
val mediaManager: MediaManager = castReceiverContext.getMediaManager()
...
// Use MediaStatusInterceptor to process the MediaStatus before sending out.
mediaManager.setMediaStatusInterceptor(
MediaStatusInterceptor { mediaStatusWriter: MediaStatusWriter ->
try {
mediaStatusWriter.setCustomData(JSONObject("{myData: 'CustomData'}"))
} catch (e: JSONException) {
Log.e(LOG_TAG,e.message,e);
}
})
}
}
}
9. Felicitaciones
Ahora sabes cómo habilitar la transmisión de contenido en una app de Android TV con la biblioteca de Cast Connect.
Si deseas obtener más información, consulta la guía para desarrolladores: /cast/docs/android_tv_receiver.