Get Started with the Fire TV Integration SDK
Here’s what you need to know to integrate the Fire TV Integration SDK into your app for Fire TV.
- Prerequisites
- Resources
- Integration steps
- Implementation details
- Share large sets of data
- Next Steps
Prerequisites
- Access to the source code of your app for Fire TV.
- A Fire TV device that supports this integration. Check with your Amazon contact for a list of device types currently supported.
- Your app must participate in the Catalog Ingestion process, so Fire TV recognizes the content IDs that your app is passing with the Content Personalization data.
- Your app must share entitlements for each customer, so Fire TV shows the entitled provider as part of our content discovery experience. For further documentation reach out to your Amazon contact.
Resources
Integration steps
Step 1. Include the SDK in your app
Begin by downloading the Fire TV Integration SDK. This is an archive containing Javadocs and a jar file named com.amazon.tv.developer.content.sdk.jar
. Copy the jar file to the libs
directory in your app. This directory must be at the same package level as your build.gradle
file. After you have copied or added the file, add the following line to the dependencies
section in your build.gradle
file.
dependencies {
...
compileOnly files('libs/com.amazon.tv.developer.content.sdk.jar')
}
Sync your IDE with the Gradle changes, so you can use the SDK functions.
Next, add the following uses-library
line to your AndroidManifest.xml
file, inside the app tag.
<application
android:label="Your App"
...>
<uses-library
android:name="com.amazon.tv.developer.sdk.content"
android:required="false" />
</application>
android:required="false"
or the app will only install on Fire TV devices where the library is present.Now add the following permission to your AndroidManifest.xml
file.
<uses-permission android:name="com.amazon.tv.developer.sdk.content.USE_SDK" />
Step 2. Implement a library check function
To ensure the device has the correct library, implement the following function to check before using the SDK code. Do this by calling the hasSystemFeature
function on Android’s package manager, it is part of the standard Android API, and you can use it on any Android TV platform before attempting to make calls to the SDK.
Implement the library check function as follows:
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")
}
When using this SDK in any capacity, this method must return true. Otherwise, you will receive a ClassNotFound
exception when attempting to access any SDK class.
Step 3. Make a call to the SDK
Begin with a sample event, which indicates the customer has started watching a piece of content. To construct the event and send it, use the following code:
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)
}
Step 4. Validate the integration
Trigger the sample event code you constructed to run inside your app. After you run the code successfully, view the logs to validate the SDK has been linked to your app and is processing the message.
You can monitor these logs from the command line by running:
adb logcat | grep AmazonPlaybackReceiver
adb logcat | findstr AmazonPlaybackReceiver
The log message you receive for the event shown in Step 3 should be similar to this:
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)
Step 5. Implement the data pull service for background or off-device data
To allow Amazon to pull data from your app, implement the abstract service from the SDK. This service defines everything needed for service setup and connection, and only requires you to implement functions to send data to a receiver object. The object allows you to share data in chunks, as needed, to prevent loading large lists into memory.
You can implement the abstract service to pull data from your apps like this:
public class MyAmznDataIntegrationService extends AmazonDataIntegrationService {
private List <CustomerSubscription> getCustomerSubscriptions() {
// internal logic to retrieve customer activity
}
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> {
// internal logic to retrieve customer activity
}
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) {
// implementation for retrieving all content entitlements
}
override fun getAllSubscriptionEntitlements(receiver: AmazonEntitlementReceiver) {
// implementation for retrieving all subscription entitlements
}
override fun getAllCustomerListEntries(customerListReceiver: AmazonCustomerListReceiver, type: Int) {
// implementation for retrieving all customer list entries
}
}
To ensure your service can only be accessed by Fire TV, protect it with com.amazon.tv.developer.sdk.content.READ_DATA_SERVICE
. This allows permission for Fire TV only and will prevent other components from invoking it.
To do this, add the service definition to 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"/>
Add a meta-data block so the service is used for data integration, enabling the Fire TV system to locate the service and connect to it as needed. Also, make sure the value is the fully qualified service name.
<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>
Step 6. Implement SDK calls as part of in-app functionality
For each data type, review the data type's When to Send section to understand where in your code you will need to make calls to the SDK.
For example, when a customer adds an item to the watchlist we expect you call the addCustomerListEntry
API with the relevant information. You will need to first locate the code in your app that handles adding items to the watchlist and then include the API call to the Fire TV Integration SDK as part of this logic.
Implementation details
Access instances
The SDK consists of five receiver classes for different types of data:
AmazonPlaybackReceiver
AmazonContentEntitlementReceiver
AmazonCustomerListReceiver
AmazonContentInteractionReceiver
AmazonSubscriptionEntitlementReceiver
To access an instance of one of these receivers, call the respective static function getInstance
. This instance can be used to call a function in-line (as seen in the example in Step 3), or you can store the instance and reuse as needed.
Amazon content ID
The SDK supports content identification across different namespaces, therefore all content IDs must be passed with an associated namespace in order for Amazon to properly identify the content item. Amazon supports the following namespace:
Namespace | Description |
---|---|
cdf_id | This is the ID space since data is shared in your Amazon catalog integration. These are the IDs you have specified on each piece of content. This namespace corresponds specifically to the CommonWorkType/ID field in the Catalog Data Format (CDF). |
Amazon profile ID
All data types allow you to share an associated profile ID. As there are different profile types, specify the namespace, Amazon currently supports the following profile ID namespace:
Namespace | Description |
---|---|
app_internal | This namespace indicates you are sharing a string representation of your internal profile ID for the active profile. This ID serves to identify specific profiles in your app, so we can attribute activity to the right Fire TV profile. Do not provide the actual ID you use internally, and the provided value should not be capable of identifying the customer. We recommend taking a hash value of your internal profile ID, which should be the same across all devices. Also, do not send us the profile name provided by the customer. Even if your app doesn't use profiles, provide a consistent value. |
List sharing
The SDK contains functionality to share lists for both customer lists and lists of content entitlements. Lists have three types of operations:
Add
- This represents an incremental update to add new entries to the list.Remove
- This represents a decremental update to remove entries from the list.Set
- This represents replacing an existing state with a list of entries you provide.
Add
and Remove
are useful when the customer is interacting with content in the app and performing adds or removes. The Set
update is useful when a customer first signs into the TV if you suspect the list is out of sync, and when sending off-device activity.
Limits when passing a list
If a list is small enough to safely fit in memory (less than 10,000 items), you can pass the entire list to the Fire TV Data Service without issue. The SDK handles any batching necessary to communicate data to the data service. If the data list is too large to fit in memory, or you would like to limit the memory footprint, you may send data in increments. This works well with a paging protocol between your app and your backend services that provide data. See the code sample in the Share a list in large chunks section.
Share large sets of data
Set list operations might require large amounts of data to be passed. If you have too much data to reasonably fit into your memory profile within the app, use the AmazonSetListStatus
parameter to work with large data sets.
The AmazonSetListStatus
parameter allows for basic paging of data, and contains two static values:
ITEMS_PENDING
indicates more data for the list is expected.COMPLETE
indicates that the list is complete and that all recent messages received withITEMS_PENDING
will be used to construct the customer's full list.
Share a list in large chunks
This example assumes you retrieve data in pages from your backend. Data is shared with the ITEMS_PENDING
flag until we reach the final page of data and send the COMPLETE
flag.
private int getIndividualEntitlementsFromServiceWithPages() {
// Internal logic to retrieve number of pages of data worth of individual entitlements.
}
private void getIndividualEntitlementsFromServiceByPage(int i, ServiceCallback callback) {
// Internal logic to call service and receive a single page of data
callback.onReceiveData(data);
}
private void List <AmazonContentEntitlement> convertToAmznContentEntitlements(Data data) {
// convert data from backend to Amazon representation
}
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 {
// Logic to retrieve the number of pages of data worth of individual entitlements
}
private fun getIndividualEntitlementsFromServiceByPage(i: Int, callback: ServiceCallback) {
// Internal logic to call the service and receive a single page of data
callback.onReceiveData(data)
}
private fun convertToAmznContentEntitlements(data: Data): List<AmazonContentEntitlement> {
// Converts data from the backend to the Amazon representation
}
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)
}
}
Exception handling
Calls to the SDK are “fire-and-forget” meaning you will not receive any errors when sending data. However, it is possible to receive a RuntimeException
when creating the model objects if you are missing required data or have invalid data.
Next Steps
Next, let's take a look at the various data types, starting with Watch Activity.
Last updated: Feb 27, 2024