開発者コンソール

手順3: 最初のチャンネルを挿入する

手順3: 最初のチャンネルを挿入する

次は最初のチャンネルを挿入します。以下の図と、Android開発の基本にあるTIFアーキテクチャの図を参照してください。

チャンネルの挿入フロー

チャンネルと番組のメタデータは、TV入力によってTV入力フレームワーク(TIF)データベースに挿入されます。Fire TVはこのデータを使用して、Fire TVの [ライブ] セクションにサービスのライブコンテンツを表示します。TV入力のチャンネルと番組のメタデータは、常に最新の状態で、アプリ内のデータと一致している必要があります。手順3および手順4では、このデータを挿入して最新の状態に保つ方法について説明します。

マニフェストへのパーミッションの追加

<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />

アプリがTIFデータベースとやり取りできるようにするには、これらのパーミッションをAndroidManifest.xmlに追加する必要があります。

AndroidのTVデータベースへのチャンネルメタデータの挿入

AndroidのTVデータベースに基本的なチャンネルを挿入するには、2つの方法があります。次のクラスまたはオブジェクトのいずれかにチャンネルを挿入できます。

1つ目の方法: SetupActivityクラス

import android.content.ContentValues;
import android.media.tv.TvContract;
import android.util.Log;
import android.net.Uri;

private long insertChannel() {
    ContentValues values = new contentValues();
    values.put(TvContract.Channels.COLUMN_INPUT_ID, "com.example.android.sampletvinput/.RichTvInputService");
    values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, "My Test Channel");
    values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, "3");

    Uri uri = getApplicationContext().getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);
    Log.i("SetupActivity", "チャンネルを挿入しました。 URI:" + uri);
    long channelId = Long.parseLong(uri.getLastPathSegment());

    return channelId;
}
import android.app.Activity
import android.content.ContentValues
import android.media.tv.TvContract
import android.net.Uri
import android.util.Log

private fun insertChannel(): Long? {
    val values = ContentValues().apply {
        put(TvContract.Channels.COLUMN_INPUT_ID, "com.example.android.sampletvinput/.RichTvInputService")
        put(TvContract.Channels.COLUMN_DISPLAY_NAME, "My Test Channel")
        put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, "3")
    }
    val uri: Uri? = applicationContext.contentResolver.insert(TvContract.Channels.CONTENT_URI, values)
    Log.i("SetupActivity", "チャンネルを挿入しました。 URI:$uri")
    return uri?.lastPathSegment?.toLongOrNull()
}

2つ目の方法: AndroidXライブラリで提供されるChannelオブジェクト

import androidx.tvprovider.media.tv.Channel;
import android.media.tv.TvContract;
import android.util.Log;
import android.net.Uri;

private long insertChannel() {
    Channel testChannel = new Channel.Builder()
        .setDisplayName("My Test Channel")
        .setDisplayNumber("3")
        .setInputId("com.example.android.sampletvinput/.RichTvInputService")
        .build();

    Uri uri = getApplicationContext().getContentResolver().insert(TvContract.Channels.CONTENT_URI, testChannel.toContentValues());
    Log.i("SetupActivity", "チャンネルを挿入しました。 URI:" + uri);
    long channelId = Long.parseLong(uri.getLastPathSegment());

    return channelId;
}
import android.app.Activity
import android.content.Context
import android.media.tv.TvContract
import android.util.Log
import androidx.tvprovider.media.tv.Channel


private fun insertChannel(): Long? {
    val testChannel = Channel.Builder()
        .setDisplayName("My Test Channel")
        .setDisplayNumber("3")
        .setInputId("com.example.android.sampletvinput/.RichTvInputService")
        .build()
    val uri: Uri? =
        contentResolver.insert(
            TvContract.Channels.CONTENT_URI,
            testChannel.toContentValues()
        )
    Log.i("SetupActivity", "チャンネルを挿入しました。 URI:$uri")
    return uri?.lastPathSegment?.toLongOrNull()
}

次に、このメソッドをSetupActivity内のonCreate()メソッドで呼び出します(既存のコードを置き換えます)。

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.rich_setup);

    insertChannel();
}
public override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.rich_setup)
    
    insertChannel()
}
アクティビティ 必須・任意 入力 備考
COLUMN_INPUT_ID 必須 TvInputServiceへの完全なクラスパス 例: TvInputServiceがメインアプリパッケージにある場合、完全なクラスパスは、<アプリパッケージ>/<TvInputServiceへの相対パス>になります。 TvInputServiceが別のパッケージにある場合、入力IDは、<アプリパッケージ>/<別のパッケージのフルパス + TvInputServiceへのパス>になります。
TvContract.Channels.CONTENT_URI 必須 AndroidのTVデータベース内のチャンネルテーブルを指すURIです。
ContentResolver.bulkInsert()またはContentResolver.applyBatch() 必須(本番用コードの場合) いずれかを使用すると、1回のデータベース操作ですべてのチャンネルの挿入が行われます。

CDFステーションIDの挿入

カタログデータ形式(CDF)を使用していない場合は、このセクションをスキップしてください。

統合の技術的な基盤であるAmazonのリニアカタログ統合システムを通じて、リニアチャンネルのスケジュールと番組メタデータをAmazon Fire TVカタログに直接アップロードできます。CDFとの統合に関心がある場合、詳細についてはAmazonの担当者にお問い合わせください。

以下は、AmazonのコントラクトキーでタイプとIDを指定して、チャンネルの一意のCDFステーションIDをJSONオブジェクトに挿入する例を示しています。これは、SetupActivityのチャンネル挿入関数内に配置します。

/**
 * 外部IDのタイプを格納する変数。サービスメタデータのマッチングに使用されます。有効なタイプは
 * 「EXTERNAL_ID_TYPE_」で始まる名前の定数として以下で定義されます。
 * Nullまたは無効なデータを使用すると、サービスメタデータのマッチングに失敗します。
 */
private final static String EXTERNAL_ID_TYPE = "externalIdType";
 
/**
 * 外部IDの値を格納する変数。サービスメタデータのマッチングに使用されます。
 * Nullまたは無効なデータを使用すると、サービスメタデータのマッチングに失敗します。
 */
private final static String EXTERNAL_ID_VALUE = "externalIdValue";

public void fillStationId(ContentValues values) {
   try {
        String jsonString = new JSONObject()
            .put(EXTERNAL_ID_TYPE, "<実際のIDタイプ>") // Amazonカタログの一意の名前空間に置き換えてください(例:"test_cdf2")
            .put(EXTERNAL_ID_VALUE, "<実際のID値>") // チャンネルに関連付けられた一意のCDFステーションIDに置き換えてください(例:"station-001"、"station-child-001"、"station-002"、"station-child-002"、"station-003"、"station-child-003")
            .toString();
    
        // チャンネルのcontentValuesにJSON文字列を追加します。
        values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, jsonString.getBytes());
    } catch (JSONException e) {
        Log.e(TAG, "BLOBにデータを追加するときにエラーが発生しました " + e);
    }
}

public fun fillStationId(values: ContentValues) {
    try {
       val jsonString =  JSONObject()
        .put(
            EXTERNAL_ID_TYPE,
            "<実際のIDタイプ>"
        ) // Amazonカタログの一意の名前空間に置き換えてください(例:"test_cdf2")
        .put(
            EXTERNAL_ID_VALUE,
            "<実際のID値>"
        ) // チャンネルに関連付けられた一意のCDFステーションIDに置き換えてください(例:"station-001"、"station-child-001"、"station-002"、"station-child-002"、"station-003"、"station-child-003")
        .toString()
    
        // チャンネルのcontentValuesにJSON文字列を追加します。
        values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, jsonString.toByteArray())
    } catch (JSONException e) {
        Log.e(TAG, "BLOBにデータを追加するときにエラーが発生しました" + e)
    }
}

companion object {
    /**
    * 外部IDのタイプを格納する変数。サービスメタデータのマッチングに使用されます。有効なタイプは
    * 「EXTERNAL_ID_TYPE_」で始まる名前の定数として以下で定義されます。
    * Nullまたは無効なデータを使用すると、サービスメタデータのマッチングに失敗します。
    */
    private const val EXTERNAL_ID_TYPE: String = "externalIdType"
    
    /**
    * 外部IDの値を格納する変数。サービスメタデータのマッチングに使用されます。
    * Nullまたは無効なデータを使用すると、サービスメタデータのマッチングに失敗します。
    */
    private const val EXTERNAL_ID_VALUE: String = "externalIdValue"
}
アクティビティ 必須・任意 備考
externalIdTypeexternalIdValue 必須 これらのフィールド名は、Fire TVにCDF情報を提供するための、開発者とAmazonとのコントラクトの一部です。これらの文字列は変更しないでください。
TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA 必須 Fire TVにディープリンクとCDF情報を提供するための、開発者とAmazonとのコントラクトの一部です。

CDFステーションIDをまだ設定していない場合は、開発目的で一時的に使用できるIDが用意されています。プロバイダーの名前空間をtest_cdf2として、サンプルIDのstation-001station-002station-003を使用できます。

Gracenote IDの挿入

Gracenoteを使用していない場合は、このセクションをスキップしてください。

GracenoteはTVカタログのプロバイダーで、Fire TVと統合してクラウドからチャンネルや番組のメタデータを提供します。コンテンツがGracenoteと統合されている場合は、一意のIDを指定し、Fire TVがそのIDを使用してこのメタデータを収集できるようにすることができます。Gracenoteとの統合に関心がある場合、詳細についてはAmazonの担当者にお問い合わせください。

以下は、AmazonのコントラクトキーでタイプとIDを指定して、チャンネルの一意のGracenote IDをJSONオブジェクトに挿入する例を示しています。これは、SetupActivityのチャンネル挿入関数内に配置します。

/**
 * 外部IDのタイプを格納する変数。サービスメタデータのマッチングに使用されます。有効なタイプは
 * 「EXTERNAL_ID_TYPE_」で始まる名前の定数として以下で定義されます。
 * Nullまたは無効なデータを使用すると、サービスメタデータの
 * マッチングに失敗します
 */
private final static String EXTERNAL_ID_TYPE = "externalIdType";

/**
 * 外部IDの値を格納する変数。サービスメタデータのマッチングに使用されます。
 * Nullまたは無効なデータを使用すると、サービスメタデータのマッチングに失敗します。
 */
private final static String EXTERNAL_ID_VALUE = "externalIdValue";

/**
 * 外部プレーヤーへの再生のディープリンクURI。
 */
private final static String PLAYBACK_DEEP_LINK_URI = "playbackDeepLinkUri";

// Gracenote入力タイプのID
private final static String GRACENOTE_ID = "gracenote_ontv"; // onTV用Gracenote ID
private final static String GRACENOTE_GVD = "gracenote_gvd"; // GVD用Gracenote ID

// 再生ディープリンクURIのコントラクト​
// Intent.URI_INTENT_SCHEMEを使用して、インテントからURIを作成したり、URIを元のインテントに戻したりします
Intent playbackDeepLinkIntent = new Intent(); // アプリで作成されます
String playbackDeepLinkUri = playbackDeepLinkIntent.toUri(Intent.URI_INTENT_SCHEME);

// BLOBを作成します
ContentValues values = new ContentValues();  // すべてのチャンネルデータを格納します
ContentResolver resolver = context.getContentResolver();
values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, "<実際の表示名>");
values.put(TvContract.Channels.COLUMN_INPUT_ID, "<実際の入力ID>");
try {
    String jsonString = new JSONObject()
                  .put(EXTERNAL_ID_TYPE, "<実際のIDタイプ>") // GRACENOTE_XXXに置き換えます
                  .put(EXTERNAL_ID_VALUE, "<実際のID値>") // チャンネルに関連付けられたGracenote ID値に置き換えます
                  .put(PLAYBACK_DEEP_LINK_URI, playbackDeepLinkUri).toString();

    values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, jsonString.getBytes());
} catch (JSONException e) {
    Log.e(TAG, "BLOBにデータを追加するときにエラーが発生しました " + e);
}

Uri uri = resolver.insert(TvContract.Channels.CONTENT_URI, values);
import android.app.Activity
import android.content.ContentValues
import android.content.Intent
import android.media.tv.TvContract
import android.util.Log
import org.json.JSONException
import org.json.JSONObject

/**
 * 外部IDのタイプを格納する変数。サービスメタデータのマッチングに使用されます。有効な
 * タイプは「EXTERNAL_ID_TYPE_」で始まる名前の定数として以下で定義されます。Nullまたは無効なデータを使用すると、
 * サービスメタデータのマッチングに失敗します
 */
private const val EXTERNAL_ID_TYPE = "externalIdType"
/**
 * 外部IDの値を格納する変数。サービスメタデータのマッチングに使用されます。Null
 * または無効なデータを使用すると、サービスメタデータのマッチングに失敗します
 */
private const val EXTERNAL_ID_VALUE = "externalIdValue"
/**
 * 外部プレーヤーへの再生のディープリンクURI。
 */
private const val PLAYBACK_DEEP_LINK_URI = "playbackDeepLinkUri"
// Gracenote入力タイプのID
private const val GRACENOTE_ID = "gracenote_ontv" // onTV用Gracenote ID
private const val GRACENOTE_GVD = "gracenote_gvd" // GVD用Gracenote ID



class SetupActivity : Activity() {
    private fun insertChannel(): Long? {
        // 再生ディープリンクURIのコントラクト​
        // Intent.URI_INTENT_SCHEMEを使用して、インテントからURIを作成したり、URIを元のインテントに戻したりします
        val playbackDeepLinkIntent = Intent() // アプリで作成されます
        val playbackDeepLinkUri = playbackDeepLinkIntent.toUri(Intent.URI_INTENT_SCHEME)

        val jsonString: String? = try {
             JSONObject()
                .put(EXTERNAL_ID_TYPE, "<実際のIDタイプ>") // GRACENOTE_XXXに置き換えます
                .put(
                    EXTERNAL_ID_VALUE,
                    "<実際のID値>"
                ) // チャンネルに関連付けられたGracenote ID値に置き換えます
                .put(PLAYBACK_DEEP_LINK_URI, playbackDeepLinkUri)
                .toString()
        } catch (e: JSONException) {
            Log.e(TAG, "BLOBにデータを追加するときにエラーが発生しました", e)
            null
        }

        // BLOBを作成します
        val values = ContentValues().apply { // すべてのチャンネルデータを格納します
            put(TvContract.Channels.COLUMN_DISPLAY_NAME, "<実際の表示名>")
            put(TvContract.Channels.COLUMN_INPUT_ID, "<実際の入力ID>")
            if (jsonString != null) {
                put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, jsonString.toByteArray())
            }
        }

        val uri = contentResolver.insert(TvContract.Channels.CONTENT_URI, values)

        Log.i("SetupActivity", "チャンネルを挿入しました。 URI:$uri")
        return uri?.lastPathSegment?.toLongOrNull()
    }
}

private val TAG = "MyTAG"
アクティビティ 必須・任意 備考
externalIdTypeexternalIdValue 必須 これらのフィールド名は、Fire TVにGracenote情報を提供するための、開発者とAmazonとのコントラクトの一部です。これらの文字列は変更しないでください。
TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA 必須 Fire TVにディープリンクとGracenote情報を提供するための、開発者とAmazonとのコントラクトの一部です。
  • 別のタイプのGracenote IDを保有している場合は、その種類を確認してください。これについて不明な点がある場合は、Amazonの担当者にお問い合わせください。
  • Gracenoteを使用する予定はあるものの、まだGracenote IDがない場合は、開発目的で一時的に使用できるIDが用意されています。米国、英国、ドイツでは、 10171(Disney Channel)、10240(HBO)、12131(Cartoon Network)のサンプルIDを、gracenote_ontvのexternalIdTypeで使用できます。それ以外のマーケットプレイスでは、 GN9BBXQSECYVNGW(HBO)のサンプルIDを、gracenote_gvdのexternalIdTypeで使用できます。

Amazonのコントラクトキー文字列playbackDeepLinkUriを使用して、ディープリンクをJSONオブジェクトに挿入します。

/**
 * 外部プレーヤーへの再生のディープリンクURI。
 */
private final static String AMZ_KEY_PLAYBACK_DEEP_LINK_URI = "playbackDeepLinkUri";

...

Intent playbackDeepLinkIntent = new Intent();
...
// チャンネルのcontentValuesを作成します
ContentValues values = new contentValues();
values.put(Channels.COLUMN_INPUT_ID, inputId);
values.put(Channels.COLUMN_DISPLAY_NAME, channel.name);
...
// ディープリンクインテントを作成します
playbackDeepLinkIntent = // プロバイダーのチャンネルのディープリンクインテント
    ...
    try {
        String jsonString = new JSONObject()
            .put(AMZ_KEY_PLAYBACK_DEEP_LINK_URI, playbackDeepLinkIntent.toUri(Intent.URI_INTENT_SCHEME))
            .toString();

        // チャンネルのcontentValuesにjsonStringを追加します
        values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, jsonString.getBytes());
    } catch (JSONException e) {
        Log.i(TAG, "BLOBにデータを追加するときにエラーが発生しました " + e);
    }

Uri uri = context.getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);

import android.app.Activity
import android.content.ContentValues
import android.content.Intent
import android.media.tv.TvContract
import android.util.Log
import org.json.JSONException
import org.json.JSONObject

/**
 * 外部プレーヤーへの再生のディープリンクURI。
 */
private const val AMZ_KEY_PLAYBACK_DEEP_LINK_URI = "playbackDeepLinkUri"

class SetupActivity : Activity() {
    private fun insertChannel(): Long? {
        val playbackDeepLinkIntent = createPlaybackDeepLinkIntent() // プロバイダーのチャンネルのディープリンクインテント

        // チャンネルのcontentValuesを作成します
        val values = ContentValues().apply {
            put(
                TvContract.Channels.COLUMN_INPUT_ID,
                "com.example.android.sampletvinput/.RichTvInputService"
            )
            put(TvContract.Channels.COLUMN_DISPLAY_NAME, "My Test Channel")
        }

        try {
            val jsonString = JSONObject()
                .put(
                    AMZ_KEY_PLAYBACK_DEEP_LINK_URI,
                    playbackDeepLinkIntent.toUri(Intent.URI_INTENT_SCHEME)
                )
                .toString()

            // チャンネルのcontentValuesにjsonStringを追加します
            values.put(
                TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA,
                jsonString.toByteArray()
            )
        } catch (e: JSONException) {
            Log.i("SetupActivity", "BLOBにデータを追加するときにエラーが発生しました $e")
        }

        val uri = contentResolver.insert(TvContract.Channels.CONTENT_URI, values)

        Log.i("SetupActivity", "チャンネルを挿入しました。 URI:$uri")
        return uri?.lastPathSegment?.toLongOrNull()
    }

    private fun createPlaybackDeepLinkIntent(): Intent = TODO()
}
アクティビティ 必須・任意 備考
playbackDeepLinkUri 必須 Fire TVにチャンネルのディープリンクを提供するための、開発者とAmazonとのコントラクトの一部です。この文字列は変更しないでください。
TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA 必須 Fire TVにディープリンクとGracenote情報を提供するための、開発者とAmazonとのコントラクトの一部です。

チェックポイント: Fire TVのUIでのチャンネルの単独表示

次の手順を使用して、設定を検証します。

  1. APKをビルドしてFire TVにインストールします。
  2. [設定] > [ライブTV] > [チャンネル提供元を同期] の順に選択し、提供元を選択します。
  3. [ホーム] > [放映中のチャンネル] 行に移動します。挿入したチャンネルが、カード(コンテンツを含むボックスで、タイルとも呼ばれます)の1つとして表示されます。GracenoteまたはCDFが統合されていない場合は、チャンネル名を含む灰色のタイルが表示されます。デバイスにほかの提供元からのチャンネルが多数存在する場合は、上限の適用により、チャンネルが表示されないことがあります。
  4. [ライブTV] > [番組表] に移動してオプションメニュー(3本線)を開き、[チャンネルを絞り込み] を選択して、入力名を選択します。挿入したチャンネルが画面に1行で表示されます。
  5. [設定] > [ライブTV] > [チャンネルを管理] に移動します。(ジョブサービスのXMLファイルにある)入力名がリストに表示されます。ここに、挿入したチャンネルが割り当てられています。
  6. ディープリンクを使用している場合は、[放映中のチャンネル] 行のチャンネルカードをクリックします。アプリが起動し、想定されるチャンネルが表示されます。

GracenoteまたはCDFと統合している場合は、[放映中のチャンネル] 行と番組表に、チャンネルの完全な番組メタデータが表示されます。

トラブルシューティング

このセクションでは、発生する可能性のある問題のトラブルシューティング手順を説明します。

[放映中のチャンネル] 行または番組表にチャンネルが表示されない
  • チェックポイントを参照して、チャンネルが許可リストに追加されていることを確認します。
  • チャンネルの入力IDが、TvInputServiceの完全なクラスパスと同じであることを確認します。
  • デバッグ用APKと本番用APKのパッケージ名が同じであることを確認します。
  • チャンネルがTIFに正しく挿入されていることを確認します。
    • チャンネル情報を照会するハードコードクエリを挿入直後に作成して、チャンネルがデータベース内に存在することを確認します。
  • Amazonでチャンネルが正しく取得されていることを確認します。
    • チャンネルを挿入する前に、ADBのログを確認します。

      Mac/Linux:adb logcat | grep StationSync

      Windows:adb logcat | findstr StationSync

    • チャンネルを挿入すると、以下のようなログが表示されます。「added」は、AmazonがAndroidのTVデータベース内の新しいチャンネルを認識していることを意味します。

08-07 15:24:57.101 11882 11941 I StationSync: Started full channel sync
08-07 15:24:57.188 11882 11941 I StationSync: Finished full channel sync, found: 15, added: 1, removed: 0, updated: 0
[放映中のチャンネル] でチャンネルが空のタイルとして表示され、画像が表示されない(チャンネル名のみが表示される)
  • これは、チャンネルがGracenoteまたはCDFに統合されていない場合に想定される動作です。GracenoteまたはCDFが統合されている場合は、以下を参照してください。
チャンネルにGracenote IDまたはCDF IDが割り当てられているにもかかわらず、[放映中のチャンネル] または番組表にメタデータが表示されない
  • フィードがonTV、GVD、CDFのどれに対応しているかを確認し、TvContractUtilsで正しく定義してください。特定のマーケットプレイスのAmazonカタログでは、onTVがサポートされています。保有しているGracenote IDまたはCDF IDのタイプがAmazonでサポートされるタイプと一致しない場合は、Amazonの担当者にお問い合わせください。Gracenoteと連携して問題を修正するか、TIFに切り替えることになります。
  • Gracenote IDまたはCDF IDの値を再度確認してください。onTVでは数値のみ、GVDとCDFでは英数字が使用されます。


Last updated: 2025年5月5日

DevAssistant
Amazon Developer Assistant