Fire TV統合SDKについて
ここでは、Fire TV対応アプリにFire TV統合SDKを統合する場合に知っておくべきことを説明します。
前提条件
- Fire TV対応アプリのソースコードへのアクセス。
- この統合がサポートされているFire TVデバイス。現在サポートされているデバイスタイプのリストについては、Amazonの担当者に確認してください。
- アプリがカタログ統合プロセスに参加していること。これにより、アプリからコンテンツパーソナライゼーションデータと共に渡されたコンテンツIDがFire TVで認識されます。
- アプリがユーザーごとのエンタイトルメントを共有していること。これにより、コンテンツの検出機能の一部として、視聴権限のあるプロバイダーがFire TVで表示されます。詳細なドキュメントについては、Amazonの担当者にお問い合わせください。
リソース
- SDK Javadoc(英語のみ)
- SDKのダウンロード
統合の手順
手順1:Fire TV統合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の変更を同期して、Fire TV統合SDKの関数を使用できるようにします。
次に、AndroidManifest.xml
ファイルで、アプリのタグ内に以下のuses-library
行を追加します。
<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の一部であるため、Fire TV統合SDKを呼び出す前に、どのAndroid TVプラットフォームでも使用できます。
次のようにライブラリのチェック関数を実装します。
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:Fire TV統合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:統合を検証する
作成したサンプルイベントコードをトリガーして、アプリ内で実行します。コードが正常に実行されたらログを表示し、Fire TV統合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:バックグラウンドまたはデバイス外のデータ用にデータプルサービスを実装する
Amazonがアプリからデータをプルできるようにするには、Fire TV統合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"/>
サービスがデータ統合に使用されるように、meta-dataブロックを追加します。これにより、必要に応じて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:アプリ内機能の一部としてFire TV統合SDKの呼び出しを実装する
それぞれのデータの種類について、そのデータの種類の送信するタイミングセクションを確認して、コードのどこでFire TV統合SDKを呼び出す必要があるかを理解してください。
たとえば、ユーザーがウォッチリストにアイテムを追加したら、関連情報と共にaddCustomerListEntry
APIを呼び出すことが想定されています。まず、ウォッチリストへのアイテムの追加を処理するアプリ内のコードを見つけ、そのロジックの一部として、Fire TV統合SDKのAPI呼び出しを含める必要があります。
実装の詳細
インスタンスへのアクセス
Fire TV統合SDKは、種類の異なるデータに対応する5つのレシーバークラスで構成されています。
AmazonPlaybackReceiver
AmazonContentEntitlementReceiver
AmazonCustomerListReceiver
AmazonContentInteractionReceiver
AmazonSubscriptionEntitlementReceiver
いずれかのレシーバーのインスタンスにアクセスするには、それぞれの静的関数のgetInstance
を呼び出します。このインスタンスは、インラインでの関数呼び出しに使用することも(手順3の例を参照)、インスタンスを保存して必要時に再利用することもできます。
AmazonコンテンツID
Fire TV統合SDKは、異なる名前空間をまたいでコンテンツの識別をサポートしています。したがって、Amazonがコンテンツアイテムを正しく識別できるようにするために、コンテンツIDを渡すときは常に、関連付けられている名前空間を一緒に指定する必要があります。Amazonでは、次の名前空間がサポートされています。
名前空間 | 説明 |
---|---|
cdf_id | データがAmazonカタログ統合で共有されてからのID空間です。これらのIDは、開発者がそれぞれのコンテンツに指定したものです。具体的には、カタログデータ形式(CDF)のCommonWorkType/ID フィールドに対応します。 |
AmazonプロフィールID
どのデータの種類も、関連付けられているプロフィールIDを共有できるようになっています。プロフィールにはさまざまな種類があるため、名前空間を指定します。Amazonでは現在、以下のプロフィールID名前空間がサポートされています。
名前空間 | 説明 |
---|---|
app_internal | この名前空間は、アクティブなプロフィールの内部プロフィールIDの文字列表現を共有していることを示します。このIDはアプリ内の特定のプロフィールを識別するものとして機能し、それを通じてAmazonでは、アクティビティを適切なFire TVプロフィールに関連付けることができます。内部で使用している実際のIDは提供しないでください。また、提供した値からユーザーを特定することができないようにしてください。すべてのデバイス間で同一となる、内部プロフィールIDのハッシュ値を使用することをお勧めします。さらに、ユーザーから提供されたプロフィール名も送信しないでください。アプリでプロフィールを使用しない場合でも、一貫した値を提供してください。 |
リストの共有
Fire TV統合SDKには、ユーザーのリストとコンテンツエンタイトルメントのリストの両方を共有する機能が含まれています。リストの操作には次の3種類があります。
Add
(追加)- リストに新しいエントリを追加するためのインクリメンタル更新を表します。Remove
(削除)- リストからエントリを削除するためのデクリメンタル更新を表します。Set
(設定)- 既存の状態を、指定したエントリのリストに置き換えることを表します。
Add
とRemove
は、ユーザーがアプリ内のコンテンツを操作し、追加または削除を実行する場合に役立ちます。Set
による更新は、リストが同期されていない可能性がある場合にユーザーが初めてTVにログインしたときや、デバイス外のアクティビティを送信する場合に役立ちます。
リストを渡すときの制限
リストがメモリに安全に収まるほど小さい場合は(アイテム数が1万個未満)、リスト全体を問題なくFire TVデータサービスに渡すことができます。データをデータサービスに伝達するために必要なバッチ処理は、すべてFire TV統合SDKによって実行されます。データリストが大きすぎてメモリに収まらない場合や、メモリ使用量を制限する場合は、データを段階的に送信できます。これには、データを提供するバックエンドサービスとアプリの間のページングプロトコルが利用されます。リストを大きいチャンクに分けて共有する方法のコードサンプルを参照してください。
大きいデータセットの共有
リストのSet操作では、大量のデータを渡すことが必要になる場合があります。データが大きすぎてアプリ内のメモリプロファイルに収まらない場合は、AmazonSetListStatus
パラメーターを使用して大きいデータセットを処理します。
AmazonSetListStatus
パラメーターは、データの基本的なページングを可能にするもので、次の2つの静的な値を受け取ります。
ITEMS_PENDING
は、リストのデータがまだ続くことを示します。COMPLETE
は、リストの送信が完了し、ITEMS_PENDING
と共に受け取った最近のすべてのメッセージを使用して、ユーザーの完全なリストが構築されることを示します。
リストを大きいチャンクに分けて共有する方法
以下の例では、バックエンドからデータを複数のページに分けて取得します。データが最終ページに到達するまで、データはITEMS_PENDING
フラグを指定して共有され、最終ページでCOMPLETE
フラグが送信されます。
private int getIndividualEntitlementsFromServiceWithPages() {
// 個別のエンタイトルメントに対応するデータのページ数を取得する内部ロジック。
}
private void getIndividualEntitlementsFromServiceByPage(int i, ServiceCallback callback) {
// サービスを呼び出して1ページ分のデータを取得する内部ロジック。
callback.onReceiveData(data);
}
private void List <AmazonContentEntitlement> convertToAmznContentEntitlements(Data data) {
// バックエンドからのデータをAmazonの表現に変換します。
}
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) {
// サービスを呼び出して1ページ分のデータを取得する内部ロジック
callback.onReceiveData(data)
}
private fun convertToAmznContentEntitlements(data: Data): List<AmazonContentEntitlement> {
// バックエンドからのデータをAmazonの表現に変換します
}
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)
}
}
例外処理
Fire TV統合SDKの呼び出しは「撃ちっ放し」の処理であるため、データの送信時にエラーが発生することはありません。ただし、必要なデータがない場合やデータが無効な場合は、モデルオブジェクトの作成時にRuntimeException
を受け取る可能性があります。
次のステップ
次は、さまざまなデータの種類を見ていきますが、まず視聴アクティビティについて説明します。
Last updated: 2024年2月27日