Integrar o recurso "Continuar assistindo" no Android TV

Este guia mostra como integrar o recurso Continuar Assistindo ao seu app para Android TV usando o SDK Engage.

Pré-trabalho

Siga as instruções de pré-requisitos no guia para iniciantes.

Integração

Criar entidades

O SDK definiu entidades diferentes para representar cada tipo de item. O cluster de continuação oferece suporte às seguintes entidades:

  1. MovieEntity
  2. TvEpisodeEntity
  3. LiveStreamingVideoEntity
  4. VideoClipEntity

Especifique os URIs específicos da plataforma e as imagens de pôster 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 segmentado para reproduzir o conteúdo do 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 em pixels (altura e largura). Segmente diferentes formatos fornecendo várias imagens de pôster, mas verifique se todas as imagens mantêm uma proporção de 16:9 e uma altura mínima de 200 pixels para a exibição correta da entidade "Continuar assistindo", especialmente no Espaço de entretenimento do Google. Imagens com altura inferior a 200 pixels podem não ser exibidas.

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")
   .addPlatformSpecificPlaybackUris(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
   .setDurationMillis(72000000)
   // Suppose last playback offset is 1 hour, 36000000 in milliseconds
   .setLastPlayBackPositionTimeMillis(36000000)
   .setCallToActionText("Resume")
   .addTag("Action")
   .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 permanecem disponíveis automaticamente por 60 dias, a menos que você especifique um prazo de validade menor. Defina uma expiração personalizada apenas se você 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")
    .addPlatformSpecificPlaybackUris(platformSpecificPlaybackUris)
    .addPosterImages(images)
    // Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
    .setLastEngagementTimeMillis(1701388800000)
    .setDurationMillis(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")
    .setCallToActionText("Resume")
    .addTag("Comedy")
    .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 o formato adequado antes de serem exibidas no card "Continuar assistindo". Eles precisam ser uma string numérica. Não coloque "e2", "episódio 2", "s1" ou "temporada 1".

Se uma série de TV tiver apenas uma temporada, defina o número como 1.

Para maximizar as chances de os espectadores encontrarem seu conteúdo no Google TV, considere fornecer dados adicionais, como gêneros, classificações de conteúdo e períodos de disponibilidade, já que 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

Veja 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")
    .addPlatformSpecificPlaybackUris(platformSpecificPlaybackUris)
    .addPosterImages(images)
    // Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
    .setLastEngagementTimeMillis(1701388800000)
    .setDurationMillis(600000) //10 minutes in milliseconds
    .setLastPlayBackPositionTimeMillis(300000) //5 minutes in milliseconds
    .setCallToActionText("Resume")
    .addTag("Vlog")
    .build()

Se quiser, você pode definir o criador, a imagem do criador, o horário de criação em milissegundos ou a janela de disponibilidade .

LiveStreamingVideoEntity

Veja 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")
    .addPlatformSpecificPlaybackUris(platformSpecificPlaybackUris)
    .addPosterImages(images)
    // Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
    .setLastEngagementTimeMillis(1701388800000)
    .setLastPlayBackPositionTimeMillis(36000000) //1 hour in milliseconds
    .addContentRating(contentRating)
    .setCallToActionText("Resume")
    .addTag("Live")
    .build()

Se quiser, você pode definir o horário de início, a emissora, o ícone da emissora 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

AppEngagePublishClient é responsável por publicar o cluster de continuação. Use o método publishContinuationCluster para publicar um objeto ContinuationCluster.

Inicialize o cliente e verifique a disponibilidade do serviço, conforme descrito no guia de iniciaçã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 cluster de continuação, será necessário publicar todas as entidades novamente.

Os dados do cluster de continuação só devem ser fornecidos para contas de adultos. Publique somente quando o perfil da conta pertencer a um adulto.

Sincronização entre dispositivos

setSyncAcrossDevices controla se os dados de ContinuationCluster de um usuário são sincronizados em dispositivos como TV, smartphone, tablets etc. A sincronização entre dispositivos é desativada por padrão.

Valores:

  • true: os dados do cluster de continuação são compartilhados em todos os dispositivos do usuário para uma experiência de visualização integrada. Recomendamos essa opção para ter a melhor experiência em vários dispositivos.
  • false: os dados do cluster de continuação são restritos ao dispositivo atual.

Além disso, é necessário fornecer um AccountProfile com um ID da conta para que o conteúdo seja sincronizado entre os dispositivos. Consulte Criar um perfil de conta.

O aplicativo de mídia precisa oferecer uma configuração clara para ativar ou desativar a sincronização entre dispositivos. Explique os benefícios ao usuário, armazene a preferência dele uma vez e aplique-a em publishContinuationCluster de acordo.

// 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 multidispositivo, verifique se o app recebe o consentimento do usuário e ative SyncAcrossDevices para true. Isso permite que o conteúdo seja sincronizado perfeitamente entre dispositivos, o que melhora a experiência do usuário e aumenta o 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 do Engage

Para excluir manualmente os dados de um usuário do servidor do Google TV antes do período de retenção padrão de 60 dias, use o método deleteClusters. Ao receber a solicitação, o serviço vai excluir todos os dados do Engage do perfil ou de toda a conta.

O tipo enumerado DeleteReason define o motivo da exclusão de dados. O código a seguir remove os dados de continuar assistindo ao sair da conta.


// 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)
        .setDeleteReason(DeleteReason.DELETE_REASON_USER_LOG_OUT)
        .setSyncAcrossDevices(true)
        .build()
)

Teste

Use o app de verificação para conferir se a integração do SDK Engage está funcionando corretamente.

Depois de invocar a API de publicação, confirme se os dados estão sendo publicados corretamente verificando o app de verificação. O cluster de continuação precisa ser exibido como uma linha distinta na interface do app.

  • Teste estas ações no seu app:
    • Faça login.
    • Alternar entre perfis(se aplicável).
    • Inicie e pause um vídeo ou volte para a página inicial.
    • Feche o app durante a reprodução do vídeo.
    • Remover um item da linha "Continuar assistindo" (se houver suporte).
  • Após cada ação, confirme se o app invocou a API publishContinuationClusters e se os dados são exibidos corretamente no app de verificação.
  • O app de verificação mostra uma marca de seleção verde "Tudo certo" para entidades implementadas corretamente.

    Captura de tela de sucesso do app de verificação
    Figura 1. Sucesso do app de verificação
  • O app de verificação vai sinalizar as entidades problemáticas.

    Captura de tela do erro do app de verificação
    Figura 2. Erro no app de verificação
  • Para resolver problemas com entidades que apresentam erros, use o controle remoto da TV para selecionar e clicar na entidade no app de verificação. Os problemas específicos serão exibidos e destacados em vermelho para sua análise (veja o exemplo abaixo).

    Detalhes do erro do app de verificação
    Figura 3. Detalhes do erro do app de verificação