開発者コンソール

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

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

次は最初のチャンネルを挿入します。この図に加えて、「Android開発の基本」のTIFアーキテクチャの図も参照してください。

チャンネルの挿入フロー

チャンネルと番組のメタデータは、TV入力によってTV入力フレームワーク(TIF)データベースに挿入されます。このデータは、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が別のパッケージにある場合、inputIdは、<アプリパッケージ>/<別のパッケージのフルパス + TvInputServiceへのパス>になります。
TvContract.Channels.CONTENT_URI 必須 AndroidのTVデータベース内のチャンネルテーブルを指すURIです。
ContentResolver.bulkInsert()またはContentResolver.applyBatch() 必須(本番用コードの場合) いずれかを使用すると、1回のデータベース操作ですべてのチャンネルの挿入が行われます。

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以外の場合は、チャンネル名の付いた灰色のタイルが表示されます。デバイスでほかの提供元のチャンネルが多数存在する場合は、挿入したチャンネルが表示されないこともあります(上限があります)。
  4. [ライブTV] > [番組表] に移動してオプションメニュー(3本線)を開き、[チャンネルを絞り込み] を選択して、入力名を選択します。挿入したチャンネルが画面に1行で表示されます。
  5. [設定] > [ライブTV] > [チャンネルを管理] の順に選択します。(ジョブサービスのXMLファイルにある)入力名がリストに表示されます。ここに、挿入したチャンネルが割り当てられています。
  6. (ディープリンクを使用している場合)[放映中のチャンネル] 行のチャンネルカードをクリックします。アプリが起動し、想定されるチャンネルが表示されます。
  7. (Gracenoteが統合されている場合)[放映中のチャンネル] 行と番組表に、チャンネルの完全な番組メタデータが表示されます。

トラブルシューティング

[放映中のチャンネル] 行または番組表にチャンネルが表示されない

  • チェックポイントを参照して、チャンネルが許可リストに追加されていることを確認します。
  • チャンネルのinputIdが、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に統合されていない場合に想定される動作です。Gracenoteが統合されている場合は、以下を参照してください。

Gracenote IDが割り当てられているチャンネルのメタデータが [放映中のチャンネル] または番組表に表示されない

  • フィードがonTVとGVDのどちらに対応しているかを確認し、TvContractUtilsで正しく定義してください。特定のマーケットプレイスのAmazonカタログでは、onTVがサポートされています。保有しているGracenote IDのタイプがAmazonでサポートされるタイプと一致しない場合は、Amazonの担当者にお問い合わせください。Gracenoteと連携して問題を修正するか、TIFに切り替えることになります。
  • Gracenote ID値を再度確認してください。onTVでは数値のみ、GVDでは英数字が使用されています。

次のステップ

次の手順の 手順4: Fire TVのUIで再生するに進みます。


Last updated: 2024年7月16日