Cómo habilitar la transmisión de contenido en una app de Android TV

1. Descripción general

Logotipo de Google Cast

En este codelab, aprenderás a modificar una app de Android TV existente para admitir 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

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.

Imagen de una serie de miniaturas de video (una de las cuales está destacada) que se superponen sobre una vista previa en pantalla completa de un video; las palabras "Cast Connect" aparecen en la parte superior derecha

Cómo registrar los dispositivos de desarrollador

Si quieres habilitar las funciones de Cast Connect para el desarrollo de aplicaciones, debes registrar el número de serie del Chromecast integrado del dispositivo Android TV que usarás en la Consola para desarrolladores 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.

Imagen de la pantalla de un Android TV en la que aparece la pantalla "Chromecast integrado", el número de versión y el número de serie

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 simple llamada Cast Videos como archivo mobile-sender-0629.apk en la descarga 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.

  1. Habilita las opciones para desarrolladores y la depuración por USB en tu teléfono Android.
  2. Conecta un cable de datos USB de modo que conectes tu teléfono Android a tu computadora de desarrollo.
  3. Instala mobile-sender-0629.apk en tu teléfono Android.

Imagen de una ventana de terminal que ejecuta el comando adb install para instalar mobile-sender.apk

  1. Encuentra la app emisora de Cast Videos en tu teléfono Android. Ícono de la app emisora de Cast Videos

Imagen de la app emisora de videos de Cast que se ejecuta en la pantalla de un 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:

  1. Selecciona Import Project en la pantalla de bienvenida o las opciones del menú File > New > Import Project....
  2. Selecciona el directorio ícono de carpetaapp-done de la carpeta del código de muestra y haz clic en OK.
  3. Haz clic en File > Botón Sync Project with Gradle de Android App Studio Sync Project with Gradle Files.
  4. Habilita las opciones para desarrolladores y la depuración por USB en tu dispositivo Android TV.
  5. ADB se conectará con tu dispositivo Android TV, y este debería aparecer en Android Studio. Imagen en la que se muestra el dispositivo Android TV en la barra de herramientas de Android Studio
  6. Haz clic en el botón Botón Run de Android Studio, un triángulo verde que apunta a la derechaRun. Deberías ver que la app de ATV llamada Cast Connect Codelab aparecerá después de unos segundos.

Reproduzcamos Cast Connect con la app de ATV

  1. Ve a la pantalla principal de Android TV.
  2. Abre la app emisora Cast Videos en tu teléfono Android. Haz clic en el botón para transmitir Ícono del botón para transmitir y selecciona tu dispositivo ATV.
  3. La app de ATV del codelab de Cast Connect se iniciará en tu ATV, y el botón para transmitir de la emisora indicará que está conectada Ícono del botón para transmitir con colores invertidos.
  4. Selecciona un video de la app de ATV. Este comenzará a reproducirse en tu ATV.
  5. 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.
  6. 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.
  7. 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.

Imagen de una sección de la pantalla de un teléfono Android con un reproductor en miniatura que reproduce un video

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:

  1. Selecciona Import Project en la pantalla de bienvenida o las opciones del menú File > New > Import Project....
  2. Selecciona el directorio ícono de carpetaapp-start de la carpeta del código de muestra y haz clic en OK.
  3. Haz clic en File > Botón Sync Project with Gradle de Android Studio Sync Project with Gradle Files.
  4. Selecciona un dispositivo ATV y haz clic en el botón Botón Run de Android Studio, un triángulo verde que apunta a la derechaRun para ejecutar la app y explorar la IU. Barra de herramientas de Android Studio que muestra el dispositivo Android TV seleccionado

Imagen de una serie de miniaturas de video (una de las cuales está destacada) que se superponen sobre una vista previa en pantalla completa de un video; las palabras "Cast Connect" aparecen en la parte superior derecha

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 tu app de ATV se actualice para usar el espacio de nombres AndroidX.

Para admitir Cast Connect en tu app de Android TV, 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 LifecycleObserver de la biblioteca androidx.lifecycle para administrar las llamadas a CastReceiverContext.start() y CastReceiverContext.stop().

Abre el archivo MyApplication.kt e inicializa el contexto de transmisión llamando 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 configurar 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 Botón Run de Android Studio, un triángulo verde que apunta a la derechaRun para implementar la app en tu dispositivo ATV, cierra la app y regresa a la pantalla principal de ATV. En la emisora, haz clic en el botón para transmitir Ícono del 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 Botón Run de Android Studio, un triángulo verde que apunta a la derechaRun para implementar la app en tu dispositivo ATV. En la emisora, haz clic en el botón para transmitir Ícono del 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 del receptor web, puedes especificar un MediaStatusWriter en tu MediaManager para realizar modificaciones adicionales en tu MediaStatus antes de que se transmita a las emisoras conectadas.

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.

Consulta la guía para desarrolladores a fin de obtener más detalles: /cast/docs/android_tv_receiver.