本指南說明如何使用 Engage SDK,將「繼續觀看」功能整合至 Android TV 應用程式。
事前作業
完成入門指南中的事前準備操作說明。
整合
建立實體
SDK 定義了不同實體,用來代表各種項目類型。接續叢集支援下列實體:
指定這些實體的平台專屬 URI 和代表圖片。
此外,如果您尚未為每個平台 (例如 Android TV、Android 或 iOS) 建立播放 URI,請先建立。因此,當使用者在各平台繼續觀看時,應用程式會使用指定的播放 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")
.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()
提供類型和內容分級等詳細資料,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")
.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()
集數編號字串 (例如 "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")
.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()
您可以視需要設定建立者、建立者圖片、建立時間 (以毫秒為單位) 或可用時間範圍。
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")
.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()
您也可以視需要設定直播實體的開始時間、播送者、播送者圖示或可用時間範圍。
如要進一步瞭解屬性和規定,請參閱 API 參考資料。
提供接續叢集資料
AppEngagePublishClient 負責發布「接續」叢集。您可以使用 publishContinuationCluster 方法發布 ContinuationCluster 物件。
請務必按照入門指南所述,初始化用戶端並檢查服務可用性。
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,會取代現有內容。如要更新接續叢集中的特定實體,則必須重新發布所有實體。
只有成人帳戶才能提供接續叢集資料。只有在帳戶設定檔屬於成人時,才能發布。
跨裝置同步
setSyncAcrossDevices 可控制是否要在電視、手機、平板電腦等裝置之間同步處理使用者的 ContinuationCluster 資料。跨裝置同步功能預設為停用。
值:
true:接續叢集資料會在使用者的所有裝置上共用,提供流暢的觀看體驗。我們強烈建議您選擇這個選項,以獲得最佳跨裝置體驗。false:接續叢集資料僅限用於目前裝置。
此外,您必須提供含有帳戶 ID 的 AccountProfile,才能在裝置之間同步處理內容。請參閱「建立帳戶設定檔」。
取得同意
媒體應用程式必須提供明確的設定,以便啟用或停用跨裝置同步功能。向使用者說明好處,並儲存使用者的偏好設定,然後在 publishContinuationCluster 中套用。
// Example to allow cross device syncing.
client.publishContinuationCluster(
PublishContinuationClusterRequest
.Builder()
.setContinuationCluster(
ContinuationCluster.Builder()
.setAccountProfile(accountProfile)
.setSyncAcrossDevices(true)
.build()
)
.build()
)
為充分發揮跨裝置功能,請確認應用程式已取得使用者同意,並啟用 SyncAcrossDevices 至 true。這可讓內容在不同裝置間流暢同步,進而提升使用者體驗和參與度。舉例來說,某位合作夥伴導入這項功能後,由於內容在多部裝置上顯示,「繼續觀看」點擊次數增加了 40%。
刪除 Engage 資料
如要在標準 60 天保留期限前,手動刪除 Google TV 伺服器中的使用者資料,請使用 deleteClusters 方法。收到要求後,服務會刪除帳戶設定檔或整個帳戶的所有現有 Engage 資料。
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)
.setDeleteReason(DeleteReason.DELETE_REASON_USER_LOG_OUT)
.setSyncAcrossDevices(true)
.build()
)
測試
使用驗證應用程式,確認 Engage SDK 整合功能是否正常運作。
叫用發布 API 後,請檢查驗證應用程式,確認資料是否正確發布。接續叢集應會顯示在應用程式介面的獨立資料列中。
- 在應用程式中測試以下動作:
- 登入。
- 切換設定檔(如適用)。
- 開始播放影片後暫停,或返回首頁。
- 在影片播放期間關閉應用程式。
- 從「繼續觀看」列中移除項目 (如支援)。
- 完成每項動作後,請確認應用程式已叫用
publishContinuationClustersAPI,且資料正確顯示在驗證應用程式中。 如果實體導入正確,驗證應用程式會顯示綠色的「一切正常」勾號。
圖 1. 驗證應用程式成功 驗證應用程式會標出任何有問題的實體。
圖 2. 驗證應用程式錯誤 如要排解實體錯誤,請使用電視遙控器,在驗證應用程式中選取並點選實體。系統會顯示特定問題,並以紅色醒目顯示,方便你查看 (請見下方範例)。
圖 3. 驗證應用程式錯誤詳細資料