Интегрировать функцию «Продолжить просмотр» на Android TV

book_path: /distribute/other-docs/_book.yaml project_path: /distribute/other-docs/_project.yaml

В этом руководстве описано, как интегрировать функцию «Продолжить просмотр» в ваше приложение для Android TV с помощью SDK Engage.

Предварительная работа

Выполните предварительные инструкции в руководстве «Начало работы» .

Интеграция

Создание сущностей

В SDK определены различные сущности для представления каждого типа элементов. Кластер продолжения поддерживает следующие сущности:

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

Укажите URI и изображения-постеры, специфичные для каждой платформы, для этих объектов.

Кроме того, создайте URI воспроизведения для каждой платформы — например, Android TV, Android или iOS — если вы еще этого не сделали. Таким образом, когда пользователь продолжает просмотр на каждой платформе, приложение будет использовать целевой URI воспроизведения для воспроизведения видеоконтента.

// 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)

Для отображения изображений-постеров требуется URI и размеры в пикселях (высота и ширина). Для разных форматов используйте несколько изображений-постеров, но убедитесь, что все изображения имеют соотношение сторон 16:9 и минимальную высоту 200 пикселей для корректного отображения элемента «Продолжить просмотр», особенно в развлекательном пространстве Google. Изображения высотой менее 200 пикселей могут не отображаться.

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

В этом примере показано, как создать MovieEntity со всеми необходимыми полями:

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()

Предоставление таких деталей, как жанры и возрастные рейтинги, позволяет Google TV демонстрировать ваш контент более динамично и настраивать его под целевую аудиторию.

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()

Объекты автоматически остаются доступными в течение 60 дней, если вы не укажете более короткий срок действия. Устанавливайте пользовательский срок действия только в том случае, если вам необходимо удалить объект раньше этого периода по умолчанию.

// 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

В этом примере показано, как создать сущность TvEpisodeEntity со всеми необходимыми полями:

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()

Номер эпизода (например, "2" ) и номер сезона (например, "1" ) будут преобразованы в правильный формат перед отображением на карточке "Продолжить просмотр". Обратите внимание, что это должна быть числовая строка, не используйте "e2", "episode 2", "s1" или "season 1".

Если у телешоу всего один сезон, установите номер сезона равным 1.

Чтобы максимально увеличить шансы на то, что зрители найдут ваш контент на Google TV, рассмотрите возможность предоставления дополнительных данных, таких как жанры, рейтинги контента и временные интервалы доступности, поскольку эти детали могут улучшить отображение и параметры фильтрации.

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

Вот пример создания объекта VideoClipEntity со всеми необходимыми полями.

VideoClipEntity представляет собой созданный пользователем видеоролик, подобный видео на 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()

При желании можно указать создателя, изображение создателя, время создания в миллисекундах или временной интервал доступности.

LiveStreamingVideoEntity

Вот пример создания объекта LiveStreamingVideoEntity со всеми необходимыми полями.

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()

При желании вы можете задать время начала, вещателя, значок вещателя или временной интервал доступности для объекта прямой трансляции.

Подробную информацию об атрибутах и ​​требованиях см. в справочнике API .

Предоставьте данные кластера продолжения

AppEngagePublishClient отвечает за публикацию кластера Continuation. Для публикации объекта ContinuationCluster используется метод publishContinuationCluste .

Обязательно инициализируйте клиент и проверьте доступность сервиса, как описано в руководстве по началу работы .

client.publishContinuationCluster(
    PublishContinuationClusterRequest
        .Builder()
        .setContinuationCluster(
            ContinuationCluster.Builder()
                .setAccountProfile(accountProfile)
                .addEntity(movieEntity1)
                .addEntity(movieEntity2)
                .addEntity(tvEpisodeEntity1)
                .addEntity(tvEpisodeEntity2)
                .setSyncAcrossDevices(true)
                .build()
        )
        .build()
)

После получения запроса сервисом в рамках одной транзакции выполняются следующие действия:

  • Существующие данные ContinuationCluster от партнера-разработчика удаляются.
  • Данные из запроса анализируются и сохраняются в обновленном ContinuationCluster .

В случае ошибки весь запрос отклоняется, и сохраняется текущее состояние.

API-интерфейсы публикации — это API-интерфейсы обновления/вставки; они заменяют существующее содержимое. Если вам нужно обновить конкретную сущность в кластере продолжения, вам потребуется повторно опубликовать все сущности.

Данные о кластерах продолжения использования следует предоставлять только для учетных записей взрослых. Публиковать следует только в том случае, если профиль учетной записи принадлежит взрослому человеку.

Синхронизация между устройствами

Флаг SyncAcrossDevices определяет, синхронизируются ли данные ContinuationCluster пользователя между такими устройствами, как телевизор, телефон, планшет и т. д. Синхронизация между устройствами по умолчанию отключена.

Ценности:

  • true : Данные кластера продолжения отображаются на всех устройствах пользователя для обеспечения бесперебойного просмотра. Мы настоятельно рекомендуем этот вариант для наилучшего взаимодействия между устройствами.
  • false : Данные кластера продолжения доступны только для текущего устройства.

Медиаприложение должно предоставлять понятную настройку для включения или отключения синхронизации между устройствами. Объясните пользователю преимущества, сохраните его предпочтения один раз и примените их соответствующим образом в publishContinuationCluster .

// Example to allow cross device syncing.
client.publishContinuationCluster(
    PublishContinuationClusterRequest
        .Builder()
        .setContinuationCluster(
            ContinuationCluster.Builder()
                .setAccountProfile(accountProfile)
                .setSyncAcrossDevices(true)
                .build()
        )
        .build()
)

Чтобы максимально эффективно использовать функцию синхронизации между устройствами, убедитесь, что приложение получило согласие пользователя, и установите SyncAcrossDevices в значение true . Это позволит контенту беспрепятственно синхронизироваться между устройствами, что приведет к улучшению пользовательского опыта и повышению вовлеченности. Например, один из партнеров, внедривший эту функцию, отметил 40% увеличение количества кликов «продолжить просмотр», поскольку его контент отображался на нескольких устройствах.

Удалите данные обнаружения видео.

Для удаления данных пользователя с сервера Google TV вручную до истечения стандартного 60-дневного срока хранения используйте метод deleteClusters . После получения запроса сервис удалит все существующие данные о поиске видео для профиля учетной записи или для всей учетной записи.

Перечисление DeleteReason определяет причину удаления данных. Следующий код удаляет опцию "продолжать просмотр данных при выходе из системы".


// 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()
)

Тестирование

Используйте приложение для проверки , чтобы убедиться в корректной работе интеграции Engage SDK.

После вызова API публикации убедитесь, что ваши данные публикуются корректно, проверив их в приложении. Ваш кластер продолжения должен отображаться в виде отдельной строки в интерфейсе приложения.

  • Протестируйте эти действия в своем приложении:
    • Войти.
    • Переключайтесь между профилями (если применимо).
    • Начните воспроизведение видео, затем поставьте его на паузу или вернитесь на главную страницу.
    • Закрывайте приложение во время воспроизведения видео.
    • Удалите элемент из строки «Продолжить просмотр» (если поддерживается).
  • После каждого действия убедитесь, что ваше приложение вызвало API publishContinuationClusters и что данные корректно отображаются в приложении для проверки.
  • Приложение для проверки отображает зеленую галочку «Все в порядке» для корректно реализованных объектов.

    Скриншот успешной проверки приложения.
    Рисунок 1. Приложение успешно прошло проверку.
  • Приложение для проверки отметит все проблемные объекты.

    Скриншот ошибки приложения проверки
    Рисунок 2. Ошибка приложения проверки.
  • Для устранения неполадок с объектами, выдающими ошибки, используйте пульт от телевизора, чтобы выбрать и щелкнуть по объекту в приложении для проверки. Конкретные проблемы будут отображены и выделены красным цветом для вашего ознакомления (см. пример ниже).

    Подробности ошибки приложения проверки
    Рисунок 3. Подробная информация об ошибке приложения для проверки.