开始使用Fire TV集成SDK
下文说明了将Fire TV集成SDK集成到您的Fire TV应用中时的须知事项。
先决条件
- 访问您的Fire TV应用的源代码。
- 支持此集成的Fire TV设备。请咨询您的亚马逊联系人,获取当前支持的设备类型列表。
- 必须在目录引入流程中加入您的应用,以便Fire TV通过内容个性化数据识别您的应用传递的内容ID。
- 您的应用必须为每位客户共享权利,由此作为亚马逊内容发现体验的一部分,Fire TV会显示授权提供方。如需更多文档,请联系您的亚马逊联系人。
资源
集成步骤
步骤1:在您的应用中添加SDK
首先下载Fire TV集成SDK。这是一个存档,其中包含了Javadocs以及名为com.amazon.tv.developer.content.sdk.jar
的jar文件。将此jar文件复制到应用中的libs
目录。此目录必须与您的build.gradle
文件处于相同的程序包级别。复制或添加文件后,将以下行添加到build.gradle
文件的dependencies
部分。
dependencies {
...
compileOnly files('libs/com.amazon.tv.developer.content.sdk.jar')
}
将您的IDE与Gradle更改同步,以便您可以使用SDK函数。
接下来,将以下uses-library
行添加到应用标签内的AndroidManifest.xml
文件中。
<application
android:label="您的应用"
...>
<uses-library
android:name="com.amazon.tv.developer.sdk.content"
android:required="false" />
</application>
android:required="false"
;如果不这样做,则该应用将仅安装在其库所在的Fire TV设备上。现在将以下权限添加到您的AndroidManifest.xml
文件。
<uses-permission android:name="com.amazon.tv.developer.sdk.content.USE_SDK" />
步骤2:实现库检查函数
为确保设备具有正确的库,请在使用SDK代码之前实现以下函数以进行检查。为此,在Android的程序包管理器上调用hasSystemFeature
函数,它是标准Android API的一部分,您可以在任何Android电视平台上在尝试调用SDK之前使用它。
按如下方式实现库检查函数:
public boolean isFTVIntegrationSDKSupportedOnDevice(Context context) {
PackageManager pm = context.getPackageManager();
return pm.hasSystemFeature("com.amazon.tv.developer.sdk.content");
}
fun isFTVIntegrationSDKSupportedOnDevice(context: Context): Boolean {
val pm = context.packageManager
return pm.hasSystemFeature("com.amazon.tv.developer.sdk.content")
}
在任何功能中使用此SDK时,此方法都必须返回true。否则,当您尝试访问任何SDK类时,您将收到ClassNotFound
异常。
步骤3:调用SDK
从示例事件开始,该事件表示客户已开始观看某段内容。要构造事件并将其发送,请使用以下代码:
if (isFTVIntegrationSDKSupportedOnDevice(getContext())) {
AmazonPlaybackEvent playbackEvent = AmazonPlaybackEvent
.builder()
.playbackPositionMs(0)
.creditsPositionMs(1000)
.state(AmazonPlaybackState.PLAYING)
.durationMs(2000)
.contentId(AmazonContentId.builder()
.id("string")
.namespace(AmazonContentId.NAMESPACE_CDF_ID)
.build())
.profileId(AmazonProfileId.builder()
.id("myProfileId1")
.namespace(AmazonProfileId.NAMESPACE_APP_INTERNAL)
.build())
.buildActiveEvent();
AmazonPlaybackReceiver.getInstance(getContext()).addPlaybackEvent(playbackEvent);
}
if (isFTVIntegrationSDKSupportedOnDevice(context)) {
val playbackEvent = AmazonPlaybackEvent.Builder()
.setPlaybackPositionMs(0)
.setCreditsPositionMs(1000)
.setState(AmazonPlaybackState.PLAYING)
.setDurationMs(2000)
.setContentId(AmazonContentId.Builder("string").setNamespace(AmazonContentId.NAMESPACE_CDF_ID).build())
.setProfileId(AmazonProfileId.Builder("myProfileId1").setNamespace(AmazonProfileId.NAMESPACE_APP_INTERNAL).build())
.buildActiveEvent()
AmazonPlaybackReceiver.getInstance(context).addPlaybackEvent(playbackEvent)
}
步骤4:验证集成
触发您构建的示例事件代码以在应用中运行。成功运行代码后,查看日志以验证SDK已链接到您的应用并且正在处理消息。
您可以通过运行以下命令从命令行监控这些日志:
adb logcat | grep AmazonPlaybackReceiver
adb logcat | findstr AmazonPlaybackReceiver
您收到的有关步骤3中显示的事件的日志消息应与以下内容相似:
AmazonPlaybackReceiver: Fire TV Integration SDK function: addPlaybackEvent invoked by package: com.example.app with data: AmazonPlaybackEvent(durationMs=2000, playbackPositionMs=0, creditsPositionMs=1000, state=0, eventTimestampMs=0, profileId=AmazonProfileId(id=myProfileId1, namespace=app_internal), isOffDevice=false)
步骤5:为后台或设备外数据实现数据拉取服务
要允许亚马逊从您的应用提取数据,请从SDK实现抽象服务。该服务定义了服务设置和连接所需的一切,您只需要实现向接收器对象发送数据的函数。该对象允许您根据需要分块共享数据,以防止将大型列表加载到内存中。
您可以实现抽象服务以从您的应用中提取数据,如下所示:
public class MyAmznDataIntegrationService extends AmazonDataIntegrationService {
private List <CustomerSubscription> getCustomerSubscriptions() {
//用于检索客户活动的内部逻辑
}
private List < AmazonPlaybackEvent > convertToAmazonPlaybackEvents(List <CustomerTitleWatched> titlesWatched) {
List <AmazonPlaybackEvent> amznPlaybackEvents = new ArrayList<>();
for (CustomerTitleWatched titleWatched: titlesWatched) {
amznPlaybackEvents.add(AmazonPlaybackEvent.builder()
.contentId(AmazonContentId.builder()
.id(titleWatched.getAmznCatalogId())
.namespace(AmazonContentId.NAMESPACE_CDF_ID)
.build())
.playbackPositionMs(titleWatched.getCurrentPlaybackPosition())
.durationMs(titleWatched.getDuration())
.creditsPositionMs(titlewatched.getCreditPosition())
.state(AmazonPlaybackState.EXIT)
.profileId(AmazonProfileId.builder()
.id("myProfileId1")
.namespace(AmazonProfileId.NAMESPACE_APP_INTERNAL)
.build())
.eventTimestampMs(titleWatched.getLastWatchTime())
.buildOffDeviceEvent());
}
return amznPlaybackEvents;
}
@Override
public void getRecentPlaybackEventsSince(AmazonPlaybackReceiver playbackReceiver, long startingTimestampMs) {
List <CustomerTitleWatched> customerTitlesWatched = retrieveCustomerWatchActivity();
List <AmazonPlaybackEvent> amznPlaybackEvents = convertToAmazonPlaybackEvents(customerTitlesWatched);
playbackReceiver.addPlaybackEvents(amznPlaybackEvents);
}
@Override
public void getAllContentEntitlements(AmazonEntitlementReceiver entitlementReceiver) {
...
}
@Override
public void getAllSubscriptionEntitlements(AmazonEntitlementReceiver receiver) {
...
}
@Override
public void getAllCustomerListEntries(AmazonCustomerListReceiver customerListReceiver, int type) {
...
}
}
class MyAmznDataIntegrationService : AmazonDataIntegrationService {
private fun getCustomerSubscriptions(): List<CustomerSubscription> {
//用于检索客户活动的内部逻辑
}
private fun convertToAmazonPlaybackEvents(titlesWatched: List<CustomerTitleWatched>): List<AmazonPlaybackEvent> {
val amznPlaybackEvents = arrayListOf<AmazonPlaybackEvent>()
for (titleWatched in titlesWatched) {
amznPlaybackEvents.add(AmazonPlaybackEvent.Builder()
.contentId(AmazonContentId.Builder()
.id(titleWatched.amznCatalogId)
.namespace(AmazonContentId.NAMESPACE_CDF_ID)
.build())
.playbackPositionMs(titleWatched.currentPlaybackPosition)
.durationMs(titleWatched.duration)
.creditsPositionMs(titleWatched.creditPosition)
.state(AmazonPlaybackState.EXIT)
.profileId(AmazonProfileId.Builder()
.id("myProfileId1")
.namespace(AmazonProfileId.NAMESPACE_APP_INTERNAL)
.build())
.eventTimestampMs(titleWatched.lastWatchTime)
.buildOffDeviceEvent())
}
return amznPlaybackEvents
}
override fun getRecentPlaybackEventsSince(playbackReceiver: AmazonPlaybackReceiver, startingTimestampMs: Long) {
val customerTitlesWatched = retrieveCustomerWatchActivity()
val amznPlaybackEvents = convertToAmazonPlaybackEvents(customerTitlesWatched)
playbackReceiver.addPlaybackEvents(amznPlaybackEvents)
}
override fun getAllContentEntitlements(entitlementReceiver: AmazonEntitlementReceiver) {
// 用于检索所有内容权利的实现
}
override fun getAllSubscriptionEntitlements(receiver: AmazonEntitlementReceiver) {
// 用于检索所有订阅权利的实现
}
override fun getAllCustomerListEntries(customerListReceiver: AmazonCustomerListReceiver, type: Int) {
// 用于检索所有客户列表条目的实现
}
}
为确保您的服务只能由Fire TV访问,请使用com.amazon.tv.developer.sdk.content.READ_DATA_SERVICE
实施保护。这会仅允许Fire TV获得权限,并将防止其他组件调用您的服务。
为此,请将服务定义添加到AndroidManifest.xml
中:
<service
android:name="com.amazon.tv.developer.sdk.content.sample.SampleIntegrationService"
android:exported="true"
android:permission="com.amazon.tv.developer.sdk.content.READ_DATA_SERVICE"/>
添加元数据块,以便将服务用于数据集成,从而让Fire TV系统能够找到服务并根据需要连接该服务。此外,请确保该值是完全限定的服务名称。
<application
<meta-data android:name="com.amazon.tv.developer.sdk.content.data_integration_service_class"
android:value="com.amazon.tv.developer.sdk.content.sample.SampleIntegrationService"
/>
</application>
步骤6:将SDK调用作为应用内功能的一部分实现
对于每种数据类型,请查看数据类型的何时发送部分,以了解您需要在代码中的哪个位置调用该SDK。
例如,当客户将项目添加到他们的观看列表时,我们希望您通过相关信息调用addCustomerListEntry
API。您需要先在应用中找到用于将项目添加到观看列表的代码,然后将对Fire TV集成SDK的API调用作为此逻辑的一部分加入。
实现详情
访问实例
SDK由五个接收器类组成,用于接收不同类型的数据:
AmazonPlaybackReceiver
AmazonContentEntitlementReceiver
AmazonCustomerListReceiver
AmazonContentInteractionReceiver
AmazonSubscriptionEntitlementReceiver
要访问其中一个接收器的实例,请调用相应的静态函数getInstance
。此实例可用于内联调用函数(如步骤3中的示例所示),也可以存储该实例并根据需要重新使用。
亚马逊内容ID
该SDK支持跨不同命名空间进行内容识别,因此必须通过关联的命名空间传递所有内容ID,以便亚马逊正确识别内容项目。亚马逊支持以下命名空间:
命名空间 | 描述 |
---|---|
cdf_id | 这是ID空间,因为数据是在您的亚马逊目录集成中共享的。这些是您在每段内容上指定的ID。该命名空间特别对应于目录数据格式 (CDF) 的CommonWorkType/ID 字段。 |
亚马逊个人资料ID
所有数据类型都允许您共享关联的个人资料ID。由于存在不同的个人资料类型,请指定命名空间,亚马逊目前支持以下个人资料ID命名空间:
命名空间 | 描述 |
---|---|
app_internal | 此命名空间表示您正在共享有效个人资料的内部个人资料ID的字符串表示。此ID用于识别您应用中的特定个人资料,以便我们将活动归因于正确的Fire TV个人资料。请勿提供您在内部使用的实际ID,并且提供的值不得透露客户身份。我们建议使用您的内部个人资料ID的哈希值,该值在所有设备上都应相同。另外,请勿向我们发送客户提供的个人资料名称。即使您的应用不使用个人资料,也要提供一致的值。 |
列表共享
SDK包含共享客户列表和内容权利列表的功能。列表有三种操作类型:
Add
(添加) - 这表示向列表中新增条目的递增更新。Remove
(删除) - 这表示从列表中删除条目的递减更新。Set
(设置) - 这表示用您提供的条目列表替换现有状态。
当客户与应用中的内容进行交互并执行添加或删除操作时,可以使用Add
和Remove
。当客户首次登录电视(前提是您怀疑列表未同步)以及发送设备外活动时,可以使用Set
更新。
传递列表时的限制
如果列表足够小,可以安全地存入内存(少于10,000个项目),则可以毫无顾虑地将整个列表传递给Fire TV数据服务。SDK处理向数据服务传送数据所需的任何批处理。如果数据列表太大而无法放入内存,或者您想限制内存占用,则可按照递增的形式发送数据。这对您的应用和提供数据的后端服务之间的分页协议非常有效。参见分块共享列表部分中的代码示例。
共享大型数据集
Set列表操作可能需要传递大量数据。如果您的数据过多,无法合理地放入应用内的内存个人资料中,请使用AmazonSetListStatus
参数来处理大型数据集。
AmazonSetListStatus
参数可用于对数据进行基本分页,其中包含两个静态值:
ITEMS_PENDING
表示预计该列表会有更多数据。COMPLETE
表示该列表已完成,并且最近通过ITEMS_PENDING
收到的所有消息都将用于构建客户的完整列表。
划分成大块共享列表
此示例假设您从后端检索页面中的数据。系统会与ITEMS_PENDING
标记共享数据,直到我们到达数据的最后一页并发送COMPLETE
标记。
private int getIndividualEntitlementsFromServiceWithPages() {
// 用于检索相当于个人权利的数据页数的内部逻辑。
}
private void getIndividualEntitlementsFromServiceByPage(int i, ServiceCallback callback) {
//用于调用服务并接收单页数据的内部逻辑
callback.onReceiveData(data);
}
private void List <AmazonContentEntitlement> convertToAmznContentEntitlements(Data data) {
//在后端将数据转换为亚马逊形式
}
public void shareLargeList() {
AmazonContentEntitlementReceiver receiver = AmazonContentEntitlementReceiver.getInstance(getContext());
int pages = getIndividualEntitlementsFromServiceWithPages();
for (int i = 0; i < pages; i++) {
ServiceCallback callback = new ServiceCallback() {
onReceiveData(data) {
List < AmazonContentEntitlement > entitlements = convertToAmznContentEntitlements(data);
if (i == pages - 1) {
receiver.setContentEntitlements(AmazonSetListStatus.COMPLETE, entitlements);
} else {
receiver.setContentEntitlements(AmazonSetListStatus.ITEMS_PENDING, entitlements);
}
}
}
getIndividualEntitlementsFromServiceByPage(i, callback);
}
}
private fun getIndividualEntitlementsFromServiceWithPages(): Int {
// 用于检索相当于个人权利的数据页数的逻辑
}
private fun getIndividualEntitlementsFromServiceByPage(i: Int, callback: ServiceCallback) {
// 用于调用服务并接收单页数据的内部逻辑
callback.onReceiveData(data)
}
private fun convertToAmznContentEntitlements(data: Data): List<AmazonContentEntitlement> {
// 在后端将数据转换为亚马逊形式
}
public fun shareLargeList() {
val receiver = AmazonContentEntitlementReceiver.getInstance(context)
val pages = getIndividualEntitlementsFromServiceWithPages()
for (i in 0 until pages) {
val callback = ServiceCallback { data ->
val entitlements = convertToAmznContentEntitlements(data)
if (i == pages - 1) {
receiver.setContentEntitlements(AmazonSetListStatus.COMPLETE, entitlements)
} else {
receiver.setContentEntitlements(AmazonSetListStatus.ITEMS_PENDING, entitlements)
}
}
getIndividualEntitlementsFromServiceByPage(index, callback)
}
}
异常处理
对SDK的调用为 “即发即弃式”,这意味着您在发送数据时不会收到任何错误。但是,如果您缺少所需数据或数据无效,则在创建模型对象时可能会收到RuntimeException
。
后续步骤
接下来,让我们从观看活动开始,来看看各种数据类型。
Last updated: 2024年2月27日