La función Seguir mirando aprovecha el clúster de Continuación para mostrar videos sin terminar y los próximos episodios para mirar de la misma temporada de TV, desde varias apps en una sola agrupación de IU. Puedes destacar sus entidades en este clúster de continuación. Sigue esta guía para aprender a mejorar la participación del usuario a través de la experiencia de Seguir mirando con el SDK de Engage.
Trabajo previo
Antes de comenzar, completa los siguientes pasos:
Actualiza el nivel de API objetivo a 19 o uno superior.
Agrega la biblioteca
com.google.android.engage
a tu app:Existen SDKs separados para usar en la integración: uno para apps para dispositivos móviles y otro para apps para TV.
Dispositivo móvil
dependencies { implementation 'com.google.android.engage:engage-core:1.5.5 }
TV
dependencies { implementation 'com.google.android.engage:engage-tv:1.0.2 }
Establece el entorno del servicio de Engage como producción en el archivo
AndroidManifest.xml
.Dispositivo móvil
<meta-data android:name="com.google.android.engage.service.ENV" android:value="PRODUCTION" />
TV
<meta-data android:name="com.google.android.engage.service.ENV" android:value="PRODUCTION" />
Agrega permiso para
WRITE_EPG_DATA
para el APK de TV<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
Verifica la publicación de contenido confiable con un servicio en segundo plano, como
androidx.work
, para la programación.Para brindar una experiencia de visualización fluida, publica los datos de Continuar mirando cuando ocurran los siguientes eventos:
- Primer acceso: Cuando un usuario accede por primera vez, publica datos para asegurarte de que su historial de visualizaciones esté disponible de inmediato.
- Creación o cambio de perfil (apps con varios perfiles): Si tu app admite varios perfiles, publica datos cuando un usuario cree o cambie de perfil.
- Interrupción de la reproducción de video: Para ayudar a los usuarios a retomar la reproducción donde la dejaron, publica datos cuando pausen o detengan un video, o cuando la app se cierre durante la reproducción.
- Actualizaciones de la bandeja de Continuar mirando (si se admite): Cuando un usuario quite un elemento de su bandeja de Continuar mirando, publica datos actualizados para reflejar ese cambio.
- Finalización del video:
- En el caso de las películas, quita la película completada de la bandeja Seguir mirando. Si la película forma parte de una serie, agrega la siguiente para mantener la participación del usuario.
- En el caso de los episodios, quita el episodio completado y agrega el siguiente de la serie, si está disponible, para fomentar la visualización continua.
Integración
AccountProfile
Para permitir una experiencia personalizada de "seguir mirando" en Google TV, proporciona información de la cuenta y el perfil. Usa AccountProfile para proporcionar lo siguiente:
ID de la cuenta: Es un identificador único que representa la cuenta del usuario en tu aplicación. Puede ser el ID de la cuenta real o una versión ofuscada adecuada.
ID de perfil (opcional): Si tu aplicación admite varios perfiles en una sola cuenta, proporciona un identificador único para el perfil de usuario específico (nuevamente, real o ofuscado).
// If your app only supports account
val accountProfile = AccountProfile.Builder()
.setAccountId("your_users_account_id")
.build()
// If your app supports both account and profile
val accountProfile = AccountProfile.Builder()
.setAccountId("your_users_account_id")
.setProfileId("your_users_profile_id")
.build()
Crea entidades
El SDK definió distintas entidades para representar cada tipo de elemento. El clúster de continuación admite las siguientes entidades:
Especifica los URIs y las imágenes de póster específicos de la plataforma para estas entidades.
Además, si aún no lo hiciste, crea URIs de reproducción para cada plataforma, como Android TV, Android o iOS. Por lo tanto, cuando un usuario sigue mirando contenido en cada plataforma, la app usa un URI de reproducción segmentado para reproducir el contenido de video.
// Required. Set this when you want continue watching entities to show up on
// Google TV
val playbackUriTv = PlatformSpecificUri.Builder()
.setPlatformType(PlatformType.TYPE_ANDROID_TV)
.setActionUri(Uri.parse("https://www.example.com/entity_uri_for_tv"))
.build()
// Required. Set this when you want continue watching entities to show up on
// Google TV Android app, Entertainment Space, Playstore Widget
val playbackUriAndroid = PlatformSpecificUri.Builder()
.setPlatformType(PlatformType.TYPE_ANDROID_MOBILE)
.setActionUri(Uri.parse("https://www.example.com/entity_uri_for_android"))
.build()
// Optional. Set this when you want continue watching entities to show up on
// Google TV iOS app
val playbackUriIos = PlatformSpecificUri.Builder()
.setPlatformType(PlatformType.TYPE_IOS)
.setActionUri(Uri.parse("https://www.example.com/entity_uri_for_ios"))
.build()
val platformSpecificPlaybackUris =
Arrays.asList(playbackUriTv, playbackUriAndroid, playbackUriIos)
Las imágenes de póster requieren un URI y dimensiones en píxeles (alto y ancho). Proporciona varias imágenes de póster para segmentar diferentes factores de forma, pero verifica que todas las imágenes mantengan una relación de aspecto de 16:9 y una altura mínima de 200 píxeles para que la entidad "Seguir mirando" se muestre correctamente, en especial dentro del Espacio de entretenimiento de Google. Es posible que no se muestren las imágenes con una altura inferior a 200 píxeles.
val images = Arrays.asList(
Image.Builder()
.setImageUri(Uri.parse("http://www.example.com/entity_image1.png"))
.setImageHeightInPixel(300)
.setImageWidthInPixel(169)
.build(),
Image.Builder()
.setImageUri(Uri.parse("http://www.example.com/entity_image2.png"))
.setImageHeightInPixel(640)
.setImageWidthInPixel(360)
.build()
// Consider adding other images for different form factors
)
MovieEntity
En este ejemplo, se muestra cómo crear un objeto MovieEntity
con todos los campos obligatorios:
val movieEntity = MovieEntity.Builder()
.setWatchNextType(WatchNextType.TYPE_CONTINUE)
.setName("Movie name")
.addPlatformSpecificPlaybackUri(platformSpecificPlaybackUris)
.addPosterImages(images)
// Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
.setLastEngagementTimeMillis(1701388800000)
// Suppose the duration is 2 hours, it is 72000000 in milliseconds
.setDurationMills(72000000)
// Suppose last playback offset is 1 hour, 36000000 in milliseconds
.setLastPlayBackPositionTimeMillis(36000000)
.build()
Proporcionar detalles como los géneros y las clasificaciones de contenido le permite a Google TV mostrar tu contenido de formas más dinámicas y conectarlo con los usuarios adecuados.
val genres = Arrays.asList("Action", "Science fiction")
val rating1 = RatingSystem.Builder().setAgencyName("MPAA").setRating("PG-13").build()
val contentRatings = Arrays.asList(rating1)
val movieEntity = MovieEntity.Builder()
...
.addGenres(genres)
.addContentRatings(contentRatings)
.build()
Las entidades permanecen disponibles automáticamente durante 60 días, a menos que especifiques un tiempo de vencimiento más corto. Solo establece un vencimiento personalizado si necesitas que se quite la entidad antes de este período predeterminado.
// Set the expiration time to be now plus 30 days in milliseconds
val expirationTime = DisplayTimeWindow.Builder()
.setEndTimestampMillis(now().toMillis()+2592000000).build()
val movieEntity = MovieEntity.Builder()
...
.addAvailabilityTimeWindow(expirationTime)
.build()
TvEpisodeEntity
En este ejemplo, se muestra cómo crear un objeto TvEpisodeEntity
con todos los campos obligatorios:
val tvEpisodeEntity = TvEpisodeEntity.Builder()
.setWatchNextType(WatchNextType.TYPE_CONTINUE)
.setName("Episode name")
.addPlatformSpecificPlaybackUri(platformSpecificPlaybackUris)
.addPosterImages(images)
// Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
.setLastEngagementTimeMillis(1701388800000)
.setDurationMills(72000000) // 2 hours in milliseconds
// 45 minutes and 15 seconds in milliseconds is 2715000
.setLastPlayBackPositionTimeMillis(2715000)
.setEpisodeNumber("2")
.setSeasonNumber("1")
.setShowTitle("Title of the show")
.build()
La cadena del número de episodio (como "2"
) y la cadena del número de temporada (como "1"
) se expandirán a la forma adecuada antes de mostrarse en la tarjeta de continuar mirando. Ten en cuenta que deben ser cadenas numéricas. No ingreses "e2", "episodio 2", "s1" ni "temporada 1".
Si un programa de TV en particular tiene una sola temporada, establece el número de temporada como 1.
Para maximizar las posibilidades de que los usuarios encuentren tu contenido en Google TV, considera proporcionar datos adicionales, como géneros, calificaciones de contenido y períodos de disponibilidad, ya que estos detalles pueden mejorar las opciones de visualización y filtrado.
val genres = Arrays.asList("Action", "Science fiction")
val rating1 = RatingSystem.Builder().setAgencyName("MPAA").setRating("PG-13").build()
val contentRatings = Arrays.asList(rating1)
val tvEpisodeEntity = TvEpisodeEntity.Builder()
...
.addGenres(genres)
.addContentRatings(contentRatings)
.setSeasonTitle("Season Title")
.setShowTitle("Show Title")
.build()
VideoClipEntity
Este es un ejemplo de cómo crear un objeto VideoClipEntity
con todos los campos obligatorios.
VideoClipEntity
representa un clip generado por el usuario, como un video de YouTube.
val videoClipEntity = VideoClipEntity.Builder()
.setPlaybackUri(Uri.parse("https://www.example.com/uri_for_current_platform")
.setWatchNextType(WatchNextType.TYPE_CONTINUE)
.setName("Video clip name")
.addPlatformSpecificPlaybackUri(platformSpecificPlaybackUris)
.addPosterImages(images)
// Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
.setLastEngagementTimeMillis(1701388800000)
.setDurationMills(600000) //10 minutes in milliseconds
.setLastPlayBackPositionTimeMillis(300000) //5 minutes in milliseconds
.addContentRating(contentRating)
.build()
De manera opcional, puedes establecer el creador, la imagen del creador, la hora de creación en milisegundos o el período de disponibilidad .
LiveStreamingVideoEntity
Este es un ejemplo de cómo crear un objeto LiveStreamingVideoEntity
con todos los campos obligatorios.
val liveStreamingVideoEntity = LiveStreamingVideoEntity.Builder()
.setPlaybackUri(Uri.parse("https://www.example.com/uri_for_current_platform")
.setWatchNextType(WatchNextType.TYPE_CONTINUE)
.setName("Live streaming name")
.addPlatformSpecificPlaybackUri(platformSpecificPlaybackUris)
.addPosterImages(images)
// Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
.setLastEngagementTimeMillis(1701388800000)
.setDurationMills(72000000) //2 hours in milliseconds
.setLastPlayBackPositionTimeMillis(36000000) //1 hour in milliseconds
.addContentRating(contentRating)
.build()
De manera opcional, puedes establecer la hora de inicio, el emisor, el ícono del emisor o el período de disponibilidad de la entidad de transmisión en vivo.
Para obtener información detallada sobre los atributos y los requisitos, consulta la referencia de la API.
Proporciona datos del clúster de Continuation
AppEngagePublishClient
es responsable de publicar el clúster de Continuation.
Usas el método publishContinuationCluster()
para publicar un objeto ContinuationCluster
.
Primero, debes usar isServiceAvailable() para verificar si el servicio está disponible para la integración.
client.publishContinuationCluster(
PublishContinuationClusterRequest
.Builder()
.setContinuationCluster(
ContinuationCluster.Builder()
.setAccountProfile(accountProfile)
.addEntity(movieEntity1)
.addEntity(movieEntity2)
.addEntity(tvEpisodeEntity1)
.addEntity(tvEpisodeEntity2)
.setSyncAcrossDevices(true)
.build()
)
.build()
)
Cuando el servicio recibe la solicitud, se realizan las siguientes acciones en una transacción:
- Se quitan los datos existentes de
ContinuationCluster
del socio desarrollador. - Los datos de la solicitud se analizan y se almacenan en el
ContinuationCluster
actualizado.
En caso de error, se rechaza la solicitud completa y se mantiene el estado existente.
Las APIs de publicación son APIs de inserción y actualización. Reemplazan el contenido existente. Si necesitas actualizar una entidad específica en ContinuationCluster, deberás volver a publicar todas las entidades.
Los datos de ContinuationCluster solo se deben proporcionar para las cuentas de adultos. Se publica solo cuando el AccountProfile pertenece a un adulto.
Sincronización entre dispositivos
La marca SyncAcrossDevices
controla si los datos de ContinuationCluster
de un usuario se sincronizan en dispositivos como TVs, teléfonos, tablets, etcétera. La sincronización multidispositivo está inhabilitada de forma predeterminada.
Valores:
- verdadero: Los datos de ContinuationCluster se comparten en todos los dispositivos del usuario para brindar una experiencia de visualización fluida. Recomendamos esta opción para obtener la mejor experiencia en todos los dispositivos.
- Falso: Los datos de ContinuationCluster están restringidos al dispositivo actual.
Obtén el consentimiento:
La aplicación de medios debe proporcionar un parámetro de configuración claro para habilitar o inhabilitar la sincronización multidispositivo. Explicar los beneficios al usuario, almacenar su preferencia una vez y aplicarla en publishContinuationCluster según corresponda
// Example to allow cross device syncing.
client.publishContinuationCluster(
PublishContinuationClusterRequest
.Builder()
.setContinuationCluster(
ContinuationCluster.Builder()
.setAccountProfile(accountProfile)
.setSyncAcrossDevices(true)
.build()
)
.build()
)
Para aprovechar al máximo nuestra función multidispositivo, verifica que la app obtenga el consentimiento del usuario y habilita SyncAcrossDevices
para true
. Esto permite que el contenido se sincronice sin problemas en todos los dispositivos, lo que genera una mejor experiencia del usuario y una mayor participación. Por ejemplo, un socio que implementó esta función observó un aumento del 40% en los clics de "Seguir mirando" porque su contenido se mostró en varios dispositivos.
Borra los datos de la campaña de descubrimiento de video
Para borrar manualmente los datos de un usuario del servidor de Google TV antes del período de retención estándar de 60 días, usa el método client.deleteClusters(). Cuando reciba la solicitud, el servicio borrará todos los datos existentes de descubrimiento de videos del perfil de la cuenta o de toda la cuenta.
La enumeración DeleteReason
define el motivo de la eliminación de datos.
El siguiente código quita los datos de Continuar mirando cuando se cierra la sesión.
// If the user logs out from your media app, you must make the following call
// to remove continue watching data from the current google TV device,
// otherwise, the continue watching data will persist on the current
// google TV device until 60 days later.
client.deleteClusters(
DeleteClustersRequest.Builder()
.setAccountProfile(AccountProfile())
.setReason(DeleteReason.DELETE_REASON_USER_LOG_OUT)
.setSyncAcrossDevices(true)
.build()
)
Prueba
Usa la app de verificación para confirmar que la integración del SDK de Engage funcione correctamente. Esta aplicación para Android proporciona herramientas que te ayudan a verificar tus datos y confirmar que los intents de transmisión se controlan correctamente.
Después de invocar la API de publicación, confirma que tus datos se publiquen correctamente. Para ello, consulta la app de verificación. Tu clúster de continuación debe mostrarse como una fila distinta en la interfaz de la app.
- Configura Engage Service Flag solo para las compilaciones que no son de producción en el archivo de manifiesto de Android de tu app.
- Instala y abre la app de Engage Verify
- Si
isServiceAvailable
esfalse
, haz clic en el botón “Alternar” para habilitar la opción. - Ingresa el nombre del paquete de tu app para ver automáticamente los datos publicados una vez que comiences a publicar.
- Prueba estas acciones en tu app:
- Accede.
- Cambia entre perfiles(si corresponde).
- Iniciar y, luego, pausar un video, o volver a la página principal
- Cierra la app durante la reproducción de videos.
- Quitar un elemento de la fila "Seguir mirando" (si se admite)
- Después de cada acción, confirma que tu app invocó la API de
publishContinuationClusters
y que los datos se muestran correctamente en la app de verificación. La app de verificación mostrará una marca de verificación verde con el mensaje "Todo bien" para las entidades implementadas correctamente.
Figura 1: Éxito de la app de verificación La app de verificación marcará las entidades problemáticas.
Figura 2: Error de la app de verificación Para solucionar problemas relacionados con entidades que tienen errores, usa el control remoto de la TV para seleccionar y hacer clic en la entidad en la app de verificación. Los problemas específicos se mostrarán y destacarán en rojo para que los revises (consulta el ejemplo a continuación).
Figura 3: Detalles del error de la app de verificación
API de REST
El SDK de Engage ofrece una API de REST para proporcionar una experiencia coherente de seguir mirando en plataformas que no son de Android, como iOS y Roku TV. La API permite a los desarrolladores actualizar el estado de "Seguir mirando" para los usuarios que aceptaron participar desde plataformas que no son de Android.
Requisitos previos
- Primero debes completar la integración basada en el SDK de Engage en el dispositivo.
Este paso crítico establece la asociación necesaria entre el ID de usuario de Google y el
AccountProfile
de tu app. - Acceso y autenticación de la API: Para ver y habilitar la API en tu proyecto de Google Cloud, debes completar un proceso de lista de entidades permitidas. Todas las solicitudes a la API requieren autenticación.
Cómo obtener acceso
Para acceder a la API y habilitarla en tu consola de Google Cloud, debes inscribir tu cuenta.
- El ID de cliente de Google Workspace debe estar disponible. Si no está disponible, es posible que debas configurar un espacio de trabajo de Google Workspace y las Cuentas de Google que quieras usar para llamar a la API.
- Configura una cuenta con la consola de Google Cloud usando un correo electrónico asociado a Google Workspace.
- Crea un proyecto nuevo
- Crea una cuenta de servicio para la autenticación de la API. Una vez que crees la cuenta de servicio, tendrás dos elementos:
- ID de una cuenta de servicio
- Un archivo JSON con la clave de tu cuenta de servicio Mantén este archivo seguro, ya que lo necesitarás más adelante para autenticar tu cliente en la API.
- Ahora, Workspace y las Cuentas de Google asociadas pueden usar las APIs de REST. Una vez que se propague el cambio, recibirás una notificación para saber si tus cuentas de servicio pueden llamar a la API.
- Sigue estos pasos para prepararte para hacer una llamada a la API delegada.
Publica el clúster de Continuation
Para publicar los datos de Descubrimiento de videos, realiza una solicitud POST a la API de publishContinuationCluster
con la siguiente sintaxis.
https://tvvideodiscovery.googleapis.com/v1/packages/{package_name}/accounts/{account_id}/profiles/{profile_id}/publishContinuationCluster
En la que:
package_name
: Nombre del paquete del proveedor de contenido multimediaaccountId
: Es el ID único de la cuenta del usuario en tu sistema. Debe coincidir con elaccountId
que se usa en la ruta de acceso en el dispositivo.profileId
: Es el ID único del perfil del usuario dentro de la cuenta en tu sistema. Debe coincidir con el profileId que se usa en la ruta de acceso del dispositivo.
La URL de la cuenta sin perfil es la siguiente:
https://tvvideodiscovery.googleapis.com/v1/packages/{package_name}/accounts/{account_id}/publishContinuationCluster
La carga útil de la solicitud se representa en el campo entities
. entities
representa una lista de entidades de contenido que pueden ser MovieEntity
o TVEpisodeEntity
. Este es un campo obligatorio.
Cuerpo de la solicitud
Campo |
Tipo |
Obligatorio |
Descripción |
entidades |
Lista de objetos MediaEntity |
Sí |
Lista de entidades de contenido (máximo 5). Solo se conservarán las cinco primeras y se descartará el resto. Se permite una lista vacía para indicar que el usuario terminó de mirar todas las entidades. |
El campo entities
contiene movieEntity
y tvEpisodeEntity
individuales.
Campo |
Tipo |
Obligatorio |
Descripción |
movieEntity |
MovieEntity |
Sí |
Es un objeto que representa una película dentro de ContinuationCluster. |
tvEpisodeEntity |
TvEpisodeEntity |
Sí |
Es un objeto que representa un episodio de TV dentro de ContinuationCluster. |
Cada objeto del array de entidades debe ser uno de los tipos de MediaEntity disponibles, es decir, MovieEntity
y TvEpisodeEntity
, junto con campos comunes y específicos del tipo.
En el siguiente fragmento de código, se muestra la carga útil del cuerpo de la solicitud para la API de publishContinuationCluster
.
{
"entities": [
{
"movieEntity": {
"watch_next_type": "WATCH_NEXT_TYPE_CONTINUE",
"name": "Movie1",
"platform_specific_playback_uris": [
"https://www.example.com/entity_uri_for_android",
"https://www.example.com/entity_uri_for_iOS"
],
"poster_images": [
"http://www.example.com/movie1_img1.png",
"http://www.example.com/movie1_imag2.png"
],
"last_engagement_time_millis": 864600000,
"duration_millis": 5400000,
"last_play_back_position_time_millis": 3241111
}
},
{
"tvEpisodeEntity": {
"watch_next_type": "WATCH_NEXT_TYPE_CONTINUE",
"name": "TV SERIES EPISODE 1",
"platform_specific_playback_uris": [
"https://www.example.com/entity_uri_for_android",
"https://www.example.com/entity_uri_for_iOS"
],
"poster_images": [
"http://www.example.com/episode1_img1.png",
"http://www.example.com/episode1_imag2.png"
],
"last_engagement_time_millis": 864600000,
"duration_millis": 1800000,
"last_play_back_position_time_millis": 2141231,
"episode_display_number": "1",
"season_number": "1",
"show_title": "title"
}
}
]
}
Borra los datos de descubrimiento de videos
Usa la API de clearClusters
para quitar los datos de descubrimiento de videos.
Usa la URL de POST para quitar las entidades de los datos de descubrimiento de videos.
Para borrar los datos del clúster de continuación, realiza una solicitud POST a la API de clearClusters
con la siguiente sintaxis.
https://tvvideodiscovery.googleapis.com/v1/packages/{package_name}/accounts/{account_id}/profiles/{profile_id}/clearClusters
En la que:
package_name
: Es el nombre del paquete del proveedor de contenido multimedia.accountId
: Es el ID único de la cuenta del usuario en tu sistema. Debe coincidir con elaccountId
que se usa en la ruta de acceso en el dispositivo.profileId
: Es el ID único del perfil del usuario dentro de la cuenta en tu sistema. Debe coincidir con el profileId que se usa en la ruta de acceso del dispositivo.
La carga útil de la API de clearClusters
contiene solo un campo, reason
, que incluye un DeleteReason
que especifica el motivo de la eliminación de datos.
{
"reason": "DELETE_REASON_LOSS_OF_CONSENT"
}
Prueba
Después de publicar los datos correctamente, usa una cuenta de usuario de prueba para verificar que el contenido esperado aparezca en la fila "Seguir mirando" en las plataformas de Google objetivo, como Google TV y las apps para dispositivos móviles de Google TV para Android y iOS.
En las pruebas, permite una demora de propagación razonable de unos minutos y cumple con los requisitos de visualización, como mirar parte de una película o terminar un episodio. Consulta los lineamientos de Ver a continuación para desarrolladores de apps para obtener más detalles.