O recurso "Continuar assistindo" usa o cluster de continuação para mostrar vídeos inacabados e os próximos episódios da mesma temporada de TV, de vários apps em um único grupo de interfaces. Você pode destacar as entidades deles neste cluster de continuação. Siga este guia para saber como aumentar o engajamento do usuário com a experiência "Continuar assistindo" usando o Engage SDK.
Pré-trabalho
Antes de começar, siga estas etapas:
atualizar para a API de destino 19 ou mais recente
Adicione a biblioteca
com.google.android.engage
ao app:Há SDKs separados para usar na integração: um para apps para dispositivos móveis e outro para apps de TV.
Dispositivos móveis
dependencies { implementation 'com.google.android.engage:engage-core:1.5.5 }
TV
dependencies { implementation 'com.google.android.engage:engage-tv:1.0.2 }
Defina o ambiente de serviço do Engage como produção no arquivo
AndroidManifest.xml
.Dispositivos móveis
<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" />
Adicionar permissão para
WRITE_EPG_DATA
para APK de TV<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
Verifique a publicação de conteúdo confiável usando um serviço em segundo plano, como o
androidx.work
, para programação.Para oferecer uma experiência de visualização perfeita, publique os dados de "Continuar assistindo" quando estes eventos ocorrerem:
- Primeiro login: quando um usuário faz login pela primeira vez, publique dados para garantir que o histórico de exibição esteja disponível imediatamente.
- Criação ou troca de perfil (apps com vários perfis): se o app for compatível com vários perfis, publique dados quando um usuário criar ou trocar de perfil.
- Interrupção da reprodução de vídeo: para ajudar os usuários a retomar de onde pararam, publique dados quando eles pausarem ou interromperem um vídeo ou quando o app for fechado durante a reprodução.
- Atualizações da seção "Continuar assistindo" (se compatível): quando um usuário remove um item da seção "Continuar assistindo", reflita essa mudança publicando dados atualizados.
- Conclusão do vídeo:
- Para filmes, remova o filme concluído da seção "Continuar assistindo". Se o filme fizer parte de uma série, adicione o próximo para manter o usuário engajado.
- Para episódios, remova o episódio concluído e adicione o próximo da série, se disponível, para incentivar a continuidade da visualização.
Integração
AccountProfile
Para permitir uma experiência personalizada de "continuar assistindo" no Google TV, forneça informações de conta e perfil. Use o AccountProfile para fornecer:
ID da conta: um identificador exclusivo que representa a conta do usuário no aplicativo. Pode ser o ID real da conta ou uma versão adequadamente ofuscada.
ID do perfil (opcional): se o aplicativo for compatível com vários perfis em uma única conta, forneça um identificador exclusivo para o perfil de usuário específico (real ou 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()
Criar entidades
O SDK definiu entidades diferentes para representar cada tipo de item. O cluster de continuação é compatível com as seguintes entidades:
Especifique os URIs e as imagens de pôster específicos da plataforma para essas entidades.
Além disso, crie URIs de reprodução para cada plataforma, como Android TV, Android ou iOS, se ainda não tiver feito isso. Assim, quando um usuário continua assistindo em cada plataforma, o app usa um URI de reprodução segmentada para tocar o conteúdo de vídeo.
// 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)
As imagens de pôster exigem um URI e dimensões de pixel (altura e largura). Para segmentar diferentes formatos, forneça várias imagens de pôster, mas verifique se todas mantêm uma proporção de 16:9 e uma altura mínima de 200 pixels para exibição correta da entidade "Continuar assistindo", principalmente no Espaço de entretenimento do Google. Imagens com altura menor que 200 pixels podem não ser mostradas.
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
Este exemplo mostra como criar um MovieEntity
com todos os campos obrigatórios:
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()
Ao fornecer detalhes como gêneros e classificações de conteúdo, o Google TV pode mostrar seu conteúdo de maneiras mais dinâmicas e conectá-lo aos espectadores certos.
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()
As entidades ficam disponíveis automaticamente por 60 dias, a menos que você especifique um tempo de expiração menor. Defina uma expiração personalizada apenas se precisar que a entidade seja removida antes desse período padrão.
// 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
Este exemplo mostra como criar um TvEpisodeEntity
com todos os campos
obrigatórios:
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()
A string do número do episódio (como "2"
) e a string do número da temporada (como "1"
) serão expandidas para a forma adequada antes de serem exibidas no card "Continuar assistindo". Use uma string numérica, não coloque "e2", "episódio 2", "s1" ou "temporada 1".
Se um programa de TV específico tiver uma única temporada, defina o número da temporada como 1.
Para aumentar as chances de os espectadores encontrarem seu conteúdo no Google TV, forneça dados adicionais, como gêneros, classificações de conteúdo e períodos de disponibilidade. Esses detalhes podem melhorar as opções de exibição e filtragem.
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
Confira um exemplo de como criar um VideoClipEntity
com todos os campos obrigatórios.
VideoClipEntity
representa um clipe gerado pelo usuário, como um vídeo do 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()
Você pode definir o criador, a imagem do criador, o tempo de criação em milissegundos ou o período de disponibilidade .
LiveStreamingVideoEntity
Confira um exemplo de como criar um LiveStreamingVideoEntity
com todos os campos obrigatórios.
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()
Se quiser, defina o horário de início, o ícone do broadcaster ou o período de disponibilidade da entidade de transmissão ao vivo.
Para informações detalhadas sobre atributos e requisitos, consulte a referência da API.
Fornecer dados do cluster de continuação
O AppEngagePublishClient
é responsável pela publicação do cluster de continuação.
Use o método publishContinuationCluster()
para publicar um objeto ContinuationCluster
.
Primeiro, use isServiceAvailable() para verificar se o serviço está disponível para integração.
client.publishContinuationCluster(
PublishContinuationClusterRequest
.Builder()
.setContinuationCluster(
ContinuationCluster.Builder()
.setAccountProfile(accountProfile)
.addEntity(movieEntity1)
.addEntity(movieEntity2)
.addEntity(tvEpisodeEntity1)
.addEntity(tvEpisodeEntity2)
.setSyncAcrossDevices(true)
.build()
)
.build()
)
Quando o serviço recebe a solicitação, as seguintes ações ocorrem em uma transação:
- Os dados do
ContinuationCluster
do parceiro do desenvolvedor são removidos. - Os dados da solicitação são analisados e armazenados no
ContinuationCluster
atualizado.
Em caso de erro, a solicitação inteira é rejeitada e o estado atual é mantido.
As APIs de publicação são APIs de inserção que substituem o conteúdo já existente. Se você precisar atualizar uma entidade específica no ContinuationCluster, será necessário publicar todas as entidades novamente.
Os dados de ContinuationCluster só devem ser fornecidos para contas de adultos. Publicar somente quando o AccountProfile pertencer a um adulto.
Sincronização entre dispositivos
A flag SyncAcrossDevices
controla se os dados ContinuationCluster
de um usuário são sincronizados em dispositivos como TV, smartphone, tablets etc. A sincronização entre dispositivos fica desativada por padrão.
Valores:
- true: os dados do ContinuationCluster são compartilhados em todos os dispositivos do usuário para uma experiência de visualização perfeita. Recomendamos essa opção para ter a melhor experiência em vários dispositivos.
- false: os dados do ContinuationCluster são restritos ao dispositivo atual.
Obter consentimento:
O aplicativo de mídia precisa oferecer uma configuração clara para ativar/desativar a sincronização entre dispositivos. Explicar os benefícios para o usuário e armazenar a preferência dele uma vez para aplicá-la no publishContinuationCluster.
// Example to allow cross device syncing.
client.publishContinuationCluster(
PublishContinuationClusterRequest
.Builder()
.setContinuationCluster(
ContinuationCluster.Builder()
.setAccountProfile(accountProfile)
.setSyncAcrossDevices(true)
.build()
)
.build()
)
Para aproveitar ao máximo nosso recurso entre dispositivos, verifique se o app recebe o consentimento do usuário e ative o SyncAcrossDevices
para true
. Isso permite que o conteúdo seja sincronizado perfeitamente em todos os dispositivos, resultando em uma melhor experiência do usuário e maior engajamento. Por exemplo, um parceiro que implementou isso teve um aumento de 40% nos cliques em "Continuar assistindo" porque o conteúdo dele apareceu em vários dispositivos.
Excluir os dados de descoberta de vídeo
Para excluir manualmente os dados de um usuário do servidor do Google TV antes do período padrão de retenção de 60 dias, use o método client.deleteClusters(). Ao receber a solicitação, o serviço vai excluir todos os dados de descoberta de vídeo do perfil da conta ou de toda a conta.
O enum DeleteReason
define o motivo da exclusão de dados.
O código a seguir remove os dados de "Continuar assistindo" ao sair.
// 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()
)
Teste
Use o app de verificação para conferir se a integração do SDK do Engage está funcionando corretamente. Esse aplicativo Android oferece ferramentas para ajudar você a verificar seus dados e confirmar se as intents de transmissão estão sendo processadas corretamente.
Depois de invocar a API Publish, confira se os dados estão sendo publicados corretamente no app de verificação. Seu cluster de continuação vai aparecer como uma linha distinta na interface do app.
- Defina a flag do serviço de engajamento apenas para builds de não produção no arquivo de manifesto do Android do app.
- Instale e abra o app Engage Verify.
- Se
isServiceAvailable
forfalse
, clique no botão "Alternar" para ativar. - Insira o nome do pacote do seu app para ver automaticamente os dados publicados assim que você começar a publicar.
- Teste estas ações no seu app:
- Faça login.
- Alternar entre perfis(se aplicável).
- Iniciar e pausar um vídeo ou voltar para a página inicial.
- Feche o app durante a reprodução de vídeo.
- Remover um item da linha "Continuar assistindo" (se disponível).
- Depois de cada ação, confirme se o app invocou a
API
publishContinuationClusters
e se os dados estão sendo exibidos corretamente no app de verificação. O app de verificação vai mostrar uma marca de seleção verde "Tudo certo" para entidades implementadas corretamente.
Figura 1. Sucesso do app de verificação O app de verificação vai sinalizar todas as entidades problemáticas.
Figura 2. Erro no app de verificação Para resolver problemas com entidades, use o controle remoto da TV para selecionar e clicar na entidade no app de verificação. Os problemas específicos vão aparecer e serão destacados em vermelho para sua análise (confira o exemplo abaixo).
Figura 3. Detalhes do erro do app de verificação
REST API
O SDK Engage oferece uma API REST para proporcionar uma experiência consistente de continuar assistindo em plataformas que não são Android, como iOS e Roku TV. A API permite que os desenvolvedores atualizem o status "Continuar assistindo" para usuários que ativaram a opção em plataformas que não são Android.
Pré-requisitos
- Primeiro, conclua a integração no dispositivo baseada no SDK do Engage.
Essa etapa essencial estabelece a associação necessária entre o ID de usuário do Google e o
AccountProfile
do seu app. - Acesso e autenticação da API: para ver e ativar a API no seu projeto do Google Cloud, você precisa passar por um processo de lista de permissões. Todas as solicitações de API exigem autenticação.
Conseguir acesso
Para ter acesso à API no console do Google Cloud, sua conta precisa estar inscrita.
- O ID do cliente do Google Workspace precisa estar disponível. Se não estiver disponível, talvez seja necessário configurar um Google Workspace e as Contas do Google que você quer usar para chamar a API.
- Configure uma conta com o console do Google Cloud usando um e-mail associado ao Google Workspace.
- Criar um novo projeto
- Crie uma conta de serviço para autenticação de API. Depois de
criar a conta de serviço, você terá dois itens:
- Um ID de conta de serviço.
- Um arquivo JSON com a chave da sua conta de serviço. Mantenha esse arquivo seguro, já que você precisará dele para autenticar seu cliente na API mais tarde.
- O Workspace e as Contas do Google associadas agora podem usar APIs REST. Depois que a mudança for propagada, você vai receber uma notificação informando se a API está pronta para ser chamada pelas suas contas de serviço.
- Siga estas etapas para se preparar para fazer uma chamada de API delegada.
Publicar cluster de continuação
Para publicar os dados de descoberta de vídeo, faça uma solicitação POST para a
API publishContinuationCluster
usando a seguinte sintaxe.
https://tvvideodiscovery.googleapis.com/v1/packages/{package_name}/accounts/{account_id}/profiles/{profile_id}/publishContinuationCluster
Em que:
package_name
: o nome do pacote do provedor de mídiaaccountId
: o ID exclusivo da conta do usuário no seu sistema. Ele precisa corresponder aoaccountId
usado no caminho do dispositivo.profileId
: o ID exclusivo do perfil do usuário na conta do seu sistema. Ele precisa corresponder ao profileId usado no caminho do dispositivo.
O URL da conta sem perfil é:
https://tvvideodiscovery.googleapis.com/v1/packages/{package_name}/accounts/{account_id}/publishContinuationCluster
O payload da solicitação é representado no campo entities
. entities
representa uma lista de entidades de conteúdo que podem ser MovieEntity
ou
TVEpisodeEntity
. Este campo é obrigatório.
Corpo da solicitação
Campo |
Tipo |
Obrigatório |
Descrição |
entidades |
Lista de objetos MediaEntity |
Sim |
Lista de entidades de conteúdo (máximo de 5). Somente as cinco principais serão mantidas, e o restante será descartado. Uma lista vazia é permitida para indicar que o usuário terminou de assistir todas as entidades. |
O campo entities
contém movieEntity
e tvEpisodeEntity
individuais.
Campo |
Tipo |
Obrigatório |
Descrição |
movieEntity |
MovieEntity |
Sim |
Um objeto que representa um filme no ContinuationCluster. |
tvEpisodeEntity |
TvEpisodeEntity |
Sim |
Um objeto que representa um episódio de TV no ContinuationCluster. |
Cada objeto na matriz de entidades precisa ser um dos tipos MediaEntity disponíveis,
ou seja, MovieEntity
e TvEpisodeEntity
, além de campos comuns e
específicos do tipo.
O snippet de código a seguir mostra o payload do corpo da solicitação para a
API 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"
}
}
]
}
Excluir os dados de descoberta de vídeo
Use a API clearClusters
para remover os dados de descoberta de vídeo.
Use o URL POST para remover as entidades dos dados de descoberta de vídeo.
Para excluir os dados do cluster de continuação, faça uma solicitação POST para a
API clearClusters
usando a seguinte sintaxe.
https://tvvideodiscovery.googleapis.com/v1/packages/{package_name}/accounts/{account_id}/profiles/{profile_id}/clearClusters
Em que:
package_name
: o nome do pacote do provedor de mídia.accountId
: o ID exclusivo da conta do usuário no seu sistema. Ele precisa corresponder aoaccountId
usado no caminho do dispositivo.profileId
: o ID exclusivo do perfil do usuário na conta do seu sistema. Ele precisa corresponder ao profileId usado no caminho do dispositivo.
O payload da API clearClusters
contém apenas um campo, reason
,
que contém um DeleteReason
que especifica o motivo da remoção
dos dados.
{
"reason": "DELETE_REASON_LOSS_OF_CONSENT"
}
Teste
Depois de postar os dados, use uma conta de teste de usuário para verificar se o conteúdo esperado aparece na linha "Continuar assistindo" nas plataformas do Google, como o Google TV e os apps Google TV para dispositivos móveis Android e iOS.
No teste, permita um atraso de propagação razoável de alguns minutos e siga os requisitos de exibição, como assistir parte de um filme ou terminar um episódio. Consulte as diretrizes do "Assistir a seguir" para desenvolvedores de apps para mais detalhes.