Integración de contenido con canales de la pantalla principal de Android TV (Kotlin)

En este codelab, aprenderás a compilar una app que agregue canales y programas a la pantalla principal de Android TV usando las bibliotecas de Kotlin y AndroidX. La pantalla principal tiene más funciones de las que se abordan en este codelab. Consulta la documentación para obtener más información sobre todas las funciones de la pantalla principal.

Conceptos

La pantalla principal de Android TV (pantalla principal) proporciona una IU que muestra el contenido recomendado en una tabla de canales y programas. Cada fila corresponde a un canal. Un canal, a su vez, tiene tarjetas para cada uno de sus programas. Tu app puede ofrecer cualquier número de canales para que el usuario los agregue a la pantalla principal. Para ello, debe seleccionar y aprobar cada uno.

Sin embargo, las apps tienen la opción de crear un canal predeterminado. El canal predeterminado es especial porque se muestra automáticamente en la pantalla principal; el usuario no tiene que solicitarlo de forma explícita.

aa0471dc91b5f815.png

Descripción general

En este codelab, se muestra cómo crear, agregar y actualizar canales y programas en la pantalla principal. Utiliza una base de datos ficticia de colecciones y películas. Por cuestiones de simplicidad, se usa la misma lista de películas para todas las suscripciones.

Clona el repositorio del proyecto inicial

Este codelab usa Android Studio, un IDE para desarrollar apps para Android.

Si aún no lo tienes, descárgalo y, luego, instálalo.

Puedes descargar el código fuente del repositorio de GitHub:

git clone https://github.com/googlecodelabs/tv-recommendations-kotlin.git

También puedes descargarlo como archivo ZIP.

Descargar ZIP

Abre Android Studio y haz clic en File > Open en la barra de menú, o bien ve a Open an Existing Android Studio Project en la pantalla de presentación y selecciona la carpeta clonada recientemente.

c0e57864138c1248.png

Información sobre el proyecto inicial

bd4f805254260df7.png

El proyecto consta de cuatro pasos. En cada paso, agregarás cada vez más código a la app y, después de completar todas las instrucciones de cada sección, podrás comparar el resultado con el código en el paso siguiente.

Estos son los componentes principales de la app:

  • MainActivity es la actividad inicial del proyecto.
  • model/TvMediaBackground es un objeto para las imágenes de fondo que se muestran durante la búsqueda de películas.
  • model/TvMediaCollection es un objeto para colecciones de películas.
  • model/TvMediaMetadata es un objeto para almacenar información sobre las películas.
  • model/TvMediaDatabase contiene la base de datos y sirve como punto de acceso principal para los datos de películas subyacentes.
  • fragments/NowPlayingFragment reproduce películas.
  • fragments/MediaBrowserFragment es el fragmento del navegador de contenido multimedia.
  • workers/TvMediaSynchronizer es una clase de sincronizador de datos que contiene código para recuperar feeds, construir objetos y actualizar canales.
  • utils/TvLauncherUtils es una clase auxiliar para administrar canales y vistas previas de programas usando la biblioteca de AndroidX y los proveedores de TV.

Ejecuta el proyecto inicial

Intenta ejecutar el proyecto. Si tienes problemas, consulta la documentación sobre cómo comenzar.

  1. Conecta tu Android TV o inicia el emulador.
  1. Selecciona la configuración de step_1, luego tu dispositivo Android y presiona el botón run en la barra de menú. ba443677e48e0f00.png
  2. Deberías ver un esquema simple de app para TV con tres colecciones de videos.

364574330c4e90a5.png

Qué aprendiste

En esta introducción, aprendiste sobre lo siguiente:

  • La pantalla principal de la TV y sus canales
  • La estructura del código del proyecto y las clases principales de este codelab

¿Qué sigue?

Agrega canales a la pantalla principal

Para comenzar, agrega canales a la pantalla principal. Una vez que lo hagas, podrás insertar programas en ellos. Los usuarios pueden descubrir tus canales en el panel de configuración de canales y seleccionar los que quieren que aparezcan en la IU de la pantalla principal. Este codelab crea canales para cada una de las colecciones de contenido multimedia:

  • Historical Feature Films (Películas históricas)
  • 1910's Feature Films (Películas destacadas de 1910)
  • Charlie Chaplin Collection (Colección Charlie Chaplin)

En la siguiente sección, se explica cómo cargar datos y usarlos para canales.

El método synchronize() en TvMediaSynchronizer hace lo siguiente:

  1. Recupera el feed de contenido multimedia, que incluye imágenes de fondo, colecciones multimedia y metadatos de video. Esta información se define en assets/media-feed.json.
  2. Actualiza la instancia TvMediaDatabase, que almacena imágenes de fondo, colecciones de contenido multimedia y datos de video en sus respectivos objetos.
  3. Usa TvLauncherUtils para crear o actualizar canales y programas.

No te preocupes por la carga de datos en este codelab. El objetivo es comprender cómo usar la biblioteca de AndroidX para crear canales. Para ello, agregarás código a algunos métodos de la clase TvLauncherUtils.

Crea un canal

Después de recuperar datos multimedia y guardarlos en una base de datos local, el código del proyecto convierte un Collection multimedia en un canal. El código crea y actualiza canales en el método upsertChannel() de la clase TvLauncherUtils.

  1. Crea una instancia de PreviewChannel.Builder(). Para evitar la duplicación de canales, este codelab verifica la existencia de un canal y solo lo actualiza si existe. Cada colección de videos tiene un ID asociado, que puedes usar como internalProviderId de un canal. Para identificar un canal existente, compara su internalProviderId con el ID de la colección. Copia y pega el siguiente código en upsertChannel(), en el comentario del código // TODO: Step 1 create or find an existing channel..
val channelBuilder = if (existingChannel == null) {
   PreviewChannel.Builder()
} else {
   PreviewChannel.Builder(existingChannel)
}
  1. Establece en el Builder los atributos de un canal (por ejemplo, su nombre y su logotipo o ícono). El nombre visible aparece en la pantalla de inicio justo debajo del ícono del canal. Android TV usa appLinkIntentUri para dirigir a los usuarios cuando hacen clic en el ícono de un canal. En este codelab, se usa este URI para dirigir a los usuarios a la colección correspondiente en la app. Copia y pega el siguiente código en el comentario de código // TODO: Paso 2 agrega metadatos de colección y compila un objeto de canal.
val updatedChannel = channelBuilder
       .setInternalProviderId(collection.id)
       .setLogo(channelLogoUri)
       .setAppLinkIntentUri(appUri)
       .setDisplayName(collection.title)
       .setDescription(collection.description)
       .build()
  1. Llama a funciones de la clase PreviewChannelHelper para insertar el canal en el proveedor de TV o actualizarlo. La llamada a publishChannel() inserta los valores del contenido del canal en el proveedor de TV. updatePreviewChannel actualiza los canales existentes. Inserta el siguiente código en el comentario de código // TODO: Paso 3.1 actualiza un canal existente.
PreviewChannelHelper(context)
       .updatePreviewChannel(existingChannel.id, updatedChannel)
Log.d(TAG, "Updated channel ${existingChannel.id}")

Inserta el siguiente código para crear un canal nuevo en el comentario de código // TODO: Paso 3.2 publica un canal.

val channelId = PreviewChannelHelper(context).publishChannel(updatedChannel)
Log.d(TAG, "Published channel $channelId")
channelId
  1. Revisa el método upsertChannel() para ver cómo se crean y actualizan los canales.

Haz que el canal predeterminado sea visible

Cuando agregas canales al proveedor de TV, estos son invisibles. Un canal no aparece en la pantalla de inicio hasta que el usuario lo solicita. Para ello, debe seleccionarlo y aprobarlo. Sin embargo, las apps tienen la opción de crear un canal predeterminado. Este es especial porque se muestra automáticamente en la pantalla principal; el usuario no tiene que aprobarlo de forma explícita.

Agrega el siguiente código al método upsertChannel() (en TODO: Paso 4 haz que un canal sea visible):

if(allChannels.none { it.isBrowsable }) {
   TvContractCompat.requestChannelBrowsable(context, channelId)
}

Si llamas a requestChannelBrowsable() para canales no predeterminados, aparecerá un diálogo en el que se solicitará el consentimiento del usuario.

Programa actualizaciones del canal

Después de agregar el código de creación o actualización de canales, los desarrolladores deben invocar el método synchronize() para crear el canal o actualizarlo.

El mejor momento para crear los canales de tu app es justo después de que el usuario la instala. Puedes crear un receptor de emisión para escuchar el mensaje de emisión de android.media.tv.action.INITIALIZE_PROGRAMS. Esta emisión se enviará después de que el usuario haya instalado la app de TV, y los desarrolladores podrán realizar parte de la inicialización del programa allí.

Revisa el archivo AndroidManifest.xml en el código de muestra y busca la sección del receptor de emisión. Intenta ubicar el nombre de clase correcto para el receptor de emisión (se abordará a continuación).

<action
   android:name="android.media.tv.action.INITIALIZE_PROGRAMS" />

Abre la clase TvLauncherReceiver y observa el siguiente bloque de código para ver cómo la app de ejemplo crea canales para la pantalla principal.

TvContractCompat.ACTION_INITIALIZE_PROGRAMS -> {
   Log.d(TAG, "Handling INITIALIZE_PROGRAMS broadcast")
   // Synchronizes all program and channel data
   WorkManager.getInstance(context).enqueue(
           OneTimeWorkRequestBuilder<TvMediaSynchronizer>().build())
}

Debes actualizar tus canales con regularidad. Este codelab crea tareas en segundo plano con la biblioteca de WorkManager. En la clase MainActivity, se usa TvMediaSynchronizer, para programar actualizaciones periódicas del canal.

// Syncs the home screen channels hourly
// NOTE: It's very important to keep our content fresh in the user's home screen
WorkManager.getInstance(baseContext).enqueue(
       PeriodicWorkRequestBuilder<TvMediaSynchronizer>(1, TimeUnit.HOURS)
               .setInitialDelay(1, TimeUnit.HOURS)
               .setConstraints(Constraints.Builder()
                       .setRequiredNetworkType(NetworkType.CONNECTED)
                       .build())
               .build())

Ejecuta la app

Ejecuta la app. Ve a la pantalla principal. Aparece el canal predeterminado (My TV App Default), pero este no tiene programas. Si ejecutas el código en un dispositivo real y no en un emulador, es posible que el canal no aparezca.

f14e903b0505a281.png

Agrega más canales

El feed contiene tres colecciones. En la clase TvMediaSynchronizer, agrega otros canales para estas colecciones (en TODO: Paso 5 agrega más canales).

feed.collections.subList(1, feed.collections.size).forEach {
   TvLauncherUtils.upsertChannel(
           context, it, database.metadata().findByCollection(it.id))
}

Vuelve a ejecutar la app

Verifica que se hayan creado los tres canales. Haz clic en el botón Customize channels y, luego, en TV Classics. Activa o desactiva el botón para ocultar y mostrar en el panel de canales a fin de que estos aparezcan en la pantalla principal o desaparezcan de allí.

faac02714aa36ab6.png

Borra un canal

Si la app ya no mantiene un canal, puedes quitarlo de la pantalla principal.

Ve al paso 6 y busca la función removeChannel. Agrega allí la siguiente sección (en TODO: Paso 6 borra un canal). Para ver cómo funciona este código, quita la colección titulada "Charlie Chaplin Collection" en media-feed.json (asegúrate de quitarla completa). Vuelve a ejecutar la app y, después de unos segundos, verás que se quitó el canal.

// First, get all the channels added to the home screen
val allChannels = PreviewChannelHelper(context).allChannels

// Now find the channel with the matching content ID for our collection
val foundChannel = allChannels.find { it.internalProviderId == collection.id }
if (foundChannel == null) Log.e(TAG, "No channel with ID ${collection.id}")

// Use the found channel's ID to delete it from the content resolver
return foundChannel?.let {
   PreviewChannelHelper(context).deletePreviewChannel(it.id)
   Log.d(TAG, "Channel successfully removed from home screen")

   // Remove all of the channel programs as well
   val channelPrograms =
           TvContractCompat.buildPreviewProgramsUriForChannel(it.id)
   context.contentResolver.delete(channelPrograms, null, null)

   // Return the ID of the channel removed
   it.id
}

Después de completar todas las instrucciones anteriores, puedes comparar el código de la app con step_2.

Qué aprendiste

  • Cómo buscar canales
  • Cómo agregar o borrar canales de la pantalla principal
  • Cómo configurar un logotipo o título en un canal
  • Cómo hacer que un canal predeterminado sea visible
  • Cómo programar un WorkManager para actualizar canales

¿Qué sigue?

En la siguiente sección, se muestra cómo agregar programas a un canal.

El proceso para agregar un programa a un canal es similar al de crear un canal, excepto porque se usa PreviewProgram.Builder en lugar de PreviewChannel.Builder.

Continuarás trabajando con el método upsertChannel() en la clase TvLauncherUtils.

Crea un programa de vista previa

Agregaremos código a step_2 en la siguiente sección. Asegúrate de hacer cambios en los archivos de origen de ese módulo, en el proyecto de Android Studio.

e096c4d12a3d0a01.png

Después de confirmar que el canal es visible, crea un objeto PreviewProgram con objetos Metadata, con PreviewProgram.Builder. Y, como no querrás insertar el mismo programa dos veces en un canal, la muestra asigna metadata.id a contentId de PreviewProgram para anular la duplicación. Agrega el siguiente código en TODO: Paso 7 crea o busca un programa de vista previa existente.

val existingProgram = existingProgramList.find { it.contentId == metadata.id }
val programBuilder = if (existingProgram == null) {
   PreviewProgram.Builder()
} else {
   PreviewProgram.Builder(existingProgram)
}

Crea el compilador con metadatos multimedia y publícalo o actualízalo en el canal. (TODO: Paso 8 crea un programa de vista previa y publícalo).

val updatedProgram = programBuilder.also { metadata.copyToBuilder(it) }
       // Set the same channel ID in all programs
       .setChannelId(channelId)
       // This must match the desired intent filter in the manifest for VIEW action
       .setIntentUri(Uri.parse("https://$host/program/${metadata.id}"))
       // Build the program at once
       .build()

Algunos datos para tener en cuenta:

  1. El código de muestra vincula los metadatos con el programa de vista previa a través de su contentId.
  2. Para insertar el programa de vista previa en un canal, llama a setChannelId() en PreviewProgram.Builder().
  3. El sistema de Android TV inicia el intentUri de un programa cuando el usuario lo selecciona en un canal. El Uri debe incluir el ID del programa para que la app pueda encontrar y reproducir el contenido multimedia desde la base de datos cuando el usuario selecciona el programa.

Agrega programas

En este codelab, se usa PreviewChannelHelper de la biblioteca de AndroidX para insertar programas en canales.

Usa PreviewChannelHelper.publishPreviewProgram() o PreviewChannelHelper.updatePreviewProgram() para guardar el programa en el canal (en TODO: Paso 9 agrega el programa de vista previa al canal).

try {
   if (existingProgram == null) {
       PreviewChannelHelper(context).publishPreviewProgram(updatedProgram)
       Log.d(TAG, "Inserted program into channel: $updatedProgram")
   } else {
       PreviewChannelHelper(context)
               .updatePreviewProgram(existingProgram.id, updatedProgram)
       Log.d(TAG, "Updated program in channel: $updatedProgram")
   }
} catch (exc: IllegalArgumentException) {
   Log.e(TAG, "Unable to add program: $updatedProgram", exc)
}

¡Buen trabajo! Ahora, la app agrega programas a los canales. Puedes comparar el código con step_3.

Ejecuta la app

Selecciona step_2 en la configuración y ejecuta la app.

200e69351ce6a530.png

Cuando se ejecute la app, haz clic en el botón Customize Channels ubicado en la parte inferior de la pantalla principal y busca la app "TV Classics". Activa los tres canales y observa los registros para ver qué está sucediendo. La creación de canales y programas se ejecuta en segundo plano, por lo que puedes agregar instrucciones de registro adicionales para realizar el seguimiento de los eventos que se activaron.

Qué aprendiste

  • Cómo agregar programas a un canal
  • Cómo actualizar los atributos de un programa

¿Qué sigue?

Lo siguiente es agregar programas al canal Ver a continuación.

El canal Ver a continuación se encuentra cerca de la parte superior de la pantalla principal, debajo de Apps y sobre todos los otros canales.

44b6a6f24e4420e3.png

Conceptos

El canal Ver a continuación permite que tu app genere participación de los usuarios. Tu app puede agregar los siguientes programas al canal Ver a continuación: aquellos que el usuario marcó como interesantes, los que no terminó de ver y aquellos relacionados con el contenido que está mirando (como el siguiente episodio de una serie o la próxima temporada de un programa). Existen 4 tipos de casos de uso para el canal Ver a continuación:

  • Continuar mirando un video que el usuario no terminó
  • Sugerir el siguiente video para mirar (por ejemplo, si el usuario terminó de mirar el episodio 1, puedes sugerir el episodio 2)
  • Mostrar contenido nuevo para aumentar la participación
  • Mantener una lista para ver de videos interesantes que el usuario haya agregado

En esta lección, se muestra cómo usar el canal Ver a continuación para continuar mirando un video y, específicamente, incluir un video en este canal cuando el usuario hace una pausa. Una vez que el video se reproduce hasta el final, debe quitarse del canal Ver a continuación.

Cómo actualizar la posición de reproducción

Hay varias formas de realizar el seguimiento de la posición de reproducción de contenido. En este codelab, se usa un subproceso para guardar la última posición de reproducción de forma regular en la base de datos y actualizar los metadatos del programa Ver a continuación. Abre step_3 y sigue las instrucciones a continuación para agregar este código.

En NowPlayingFragment, agrega el siguiente código en el método run() de updateMetadataTask. (en TODO: Paso 10 actualiza el progreso):

val contentDuration = player.duration
val contentPosition = player.currentPosition

// Updates metadata state
val metadata = args.metadata.apply {
   playbackPositionMillis = contentPosition
}

El código solo guarda metadatos cuando la posición de reproducción es inferior al 95% de la duración total.

Agrega el siguiente código (en TODO: Paso 11 actualiza los metadatos en la base de datos).

val programUri = TvLauncherUtils.upsertWatchNext(requireContext(), metadata)
lifecycleScope.launch(Dispatchers.IO) {
   database.metadata().update(
           metadata.apply { if (programUri != null) watchNext = true })
}

Si la posición de reproducción supera el 95% del video, se quitará este programa para permitir la priorización de otro contenido.

En NowPlayingFragment, agrega el siguiente código para quitar el video terminado de la fila Ver a continuación (en TODO: Paso 12 quita Ver a continuación).

val programUri = TvLauncherUtils.removeFromWatchNext(requireContext(), metadata)
if (programUri != null) lifecycleScope.launch(Dispatchers.IO) {
   database.metadata().update(metadata.apply { watchNext = false })
}

updateMetadataTask se programa cada 10 segundos para garantizar que se haga un seguimiento de la última posición de reproducción. Se programa en onResume() y se detiene en onPause() de NowPlayingFragment, por lo que los datos solo se actualizan cuando el usuario está mirando un video.

Agrega o actualiza un programa de Ver a continuación

TvLauncherUtils interactúa con el proveedor de TV. En el paso anterior, se llamó a removeFromWatchNext y upsertWatchNext en TvLauncherUtils. Ahora, debes implementar estos dos métodos. La biblioteca de AndroidX proporciona la clase PreviewChannelHelper, que hace que esta tarea sea muy simple.

Primero, crea o encuentra una instancia existente de WatchNextProgram.Builder y, luego, actualiza el objeto con la reproducción más reciente de metadata. Agrega el siguiente código en el método upsertWatchNext() (en el paso TODO: Paso 13 compila un programa para Ver a continuación):

programBuilder.setLastEngagementTimeUtcMillis(System.currentTimeMillis())

programBuilder.setWatchNextType(metadata.playbackPositionMillis?.let { position ->
   if (position > 0 && metadata.playbackDurationMillis?.let { it > 0 } == true) {
       Log.d(TAG, "Inferred watch next type: CONTINUE")
       TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE
   } else {
       Log.d(TAG, "Inferred watch next type: UNKNOWN")
       WatchNextProgram.WATCH_NEXT_TYPE_UNKNOWN
   }
} ?: TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_NEXT)

// This must match the desired intent filter in the manifest for VIEW intent action
programBuilder.setIntentUri(Uri.parse(
       "https://${context.getString(R.string.host_name)}/program/${metadata.id}"))

// Build the program with all the metadata
val updatedProgram = programBuilder.build()

Después de llamar al método build() en un WatchNextProgram.Builder, se crea un objeto WatchNextProgam. Puedes publicarlo en la fila Ver a continuación con PreviewChannelHelper.

Agrega el siguiente código (en TODO: Paso 14.1 crea un programa para Ver a continuación):

val programId = PreviewChannelHelper(context)
       .publishWatchNextProgram(updatedProgram)
Log.d(TAG, "Added program to watch next row: $updatedProgram")
programId

O, si el programa existe, solo tienes que actualizarlo (en TODO: Paso 14.2 actualiza el programa de Ver a continuación).

PreviewChannelHelper(context)
       .updateWatchNextProgram(updatedProgram, existingProgram.id)
Log.d(TAG, "Updated program in watch next row: $updatedProgram")
existingProgram.id

Quita un programa de Ver a continuación

Cuando un usuario termina de reproducir el video, debes limpiar el canal Ver a continuación. El proceso es casi el mismo que para quitar un PreviewProgram.

Usa buildWatchNextProgramUri() para crear un Uri que realice la acción de borrar (no hay ninguna API que podamos usar en PreviewChannelHelper para quitar un programa de Ver a continuación).

Reemplaza el código existente en el método removeFromWatchNext() de la clase TvLauncherUtils por las siguientes afirmaciones (en TODO: Paso 15 quita el programa):

val programUri = TvContractCompat.buildWatchNextProgramUri(it.id)
val deleteCount = context.contentResolver.delete(
       programUri, null, null)

Ejecuta la app

Selecciona step_3 en la configuración y ejecuta la app.

6e43dc24a1ef0273.png

Mira un video de cualquiera de tus colecciones durante unos segundos y pausa el reproductor (barra espaciadora si usas el emulador). Cuando regreses a la pantalla principal, deberías ver que este se agregó al canal Ver a continuación. Selecciona el mismo video del canal Ver a continuación y debería continuar desde donde lo pausaste. Una vez que lo completes, debería desaparecer del canal Ver a continuación. Experimenta con el canal Ver a continuación creando escenarios para diferentes usuarios.

Qué aprendiste

  • Cómo agregar programas al canal Ver a continuación para generar participación
  • Cómo actualizar un programa del canal Ver a continuación
  • Cómo quitar un programa del canal Ver a continuación

¿Qué sigue?

Luego de completar el codelab, debes personalizar tu app. Reemplaza los modelos de feed multimedia y datos con los tuyos propios a fin de convertirlos en canales y programas para el proveedor de TV.

Para obtener más información, consulta la documentación.