開発者コンソール

Fire TVにおけるイベントの統合

Fire TVにおけるイベントの統合

Fire TVのイベントは、視聴時にリアルタイムで進行している(ライブイベント)か、再放送されているスタンドアロンイベントです。ライブイベントの例としては、スポーツイベントやライブコンサートなどがあります。

このドキュメントでは、Fire TVイベントに関するユーザーのエンゲージメントの促進や、Fire TVのUIでのコンテンツの再生・リプレイ方法について説明します。

リニアTV、ビデオオンデマンド、ライブイベントの詳細については、重要な定義を参照してください。

Fire TVの機能

Fire TVのライブイベント統合が完了すると、アプリをインストールしてサインインした全ユーザーに、パートナー専用のビデオコンテンツの行が表示されます(コンテンツはリアルタイムで更新できます)。行の任意のイベントにユーザーがフォーカスを移動すると、イベントの詳細なメタデータが表示され、画面の右上隅で再生が開始されます。

現在放送中のライブイベントにフォーカスがある状態
現在放送中のライブイベントにフォーカスがある状態
短いビデオクリップにフォーカスがある状態
短いビデオクリップにフォーカスがある状態

ユーザーがイベントのカードを選択すると、アプリで全画面再生されます。

アプリでの全画面再生
アプリでの全画面再生

サポートするコンテンツ

Amazonがサポートするビデオコンテンツは次のとおりです。

  1. リアルタイムのライブイベント:Twitchフィードなどのライブフィード。
  2. 予約されたライブイベントや今後のイベント:有料視聴イベントやオリンピックの試合など。
  3. リニアチャンネル:従来のケーブルTVのように、スケジュールに従って放送される番組。
  4. キャッチアップと再放送(VOD):いつでも視聴可能。
  5. ハイライト、試合のダイジェスト、インタビュー(短い形式のVOD):4と似ているが、それよりも短めなコンテンツ(試合のハイライトなど)。

認定チェックリスト

Amazonでは、以下のチェックリストを使用して、アプリにおけるイベントの統合を認定しています。アプリの実装は、以下に示す想定される動作に直接影響します。認定の例外には、状況に応じて対応します。

権限状態の変更

  • 視聴権限がある場合、ワンクリックで全画面再生されます。ほとんどの場合、ユーザーがアプリをインストールし、開いてサインインすると、視聴権限のあるイベントがFire TVのUIに表示されます。
  • 視聴権限がなくなったときは、Fire TVのUIからコンテンツを削除する必要があります。たとえば、メンバーシップの期限切れ、アプリからのログアウト、アプリのアンインストールなどがあった場合です。

閲覧と再生

  • スムーズなユーザーエクスペリエンスを提供するために、イベントのタイトル、説明、開始時刻と終了時刻(該当する場合)、コンテンツ画像などのイベントメタデータが必要となります。このメタデータは、データベース情報の一部としてデバイスにプッシュする必要があります。
  • ユーザーがライブコンテンツにフォーカスを移動すると、背景画像に代わってビデオフィード(ライブプレビュー)が表示されます。
  • イベントのタイルにフォーカスを置いてコンテキストメニューボタンを押すと、ユーザーは [<アプリ名>で今すぐ観る] または [<アプリ名>を起動] を選択できます。[<アプリ名>で今すぐ観る] を選択した場合はアプリ内の全画面再生にディープリンクされ、[<アプリ名>を起動] を選択した場合はアプリのメインUIにディープリンクされます。ビデオ情報をデバイスにプッシュするには、アプリでディープリンクを提供する必要があります。
  • アプリの機能制限が有効になっている場合、再生前にPINの入力を求めるプロンプトが表示されます。機能制限が有効になっている場合は、ライブチャンネルのプレビューを無効にする必要があります。
  • 選択ボタン、またはコンテキストメニュー項目の [<アプリ名>で今すぐ観る] を使用してイベントにアクセスすると、アプリ内の全画面再生にディープリンクされ、イベントが放送中であればそのコンテンツを直接再生できます。選択したイベントが放送前の場合や、ライブイベントのリプレイである場合は、イベントの詳細ページか、アプリ内の全画面再生の画面が表示されます。[戻る] ボタンを1~2回押すとFire TVのUIに戻ります。それ以上押しても画面は変わりません。

開発、ステージング、デプロイ

  • 本番環境に進む前に、Amazonが特定のアカウントを許可リストに登録して、イベントのエクスペリエンスを確認および認定できる必要があります。
  • イベント機能は、Amazonの担当者と合意したマーケットプレイスでのみ有効にする必要があります。その後、新しいマーケットプレイスで公開するには、新しい認定プロセスが必要になります。

Fire TVのおすすめチャンネル

Fire TVでのイベントの統合は、Androidのホーム画面のおすすめチャンネルに基づいています。これはもともとAndroid 8.0(APIレベル26)で導入されていましたが、Fire OSがアップデートされ、すべてのFire TVデバイス(APIレベル26以前のデバイスを含む)でこのAPIがサポートされるようになりました。

許可リストへのパッケージの登録

開発用のライブイベント行にコンテンツが表示されるようにするには、まず許可リストへの追加が必要です。次の情報をAmazonの担当者と共有します。

  1. アプリのパッケージ名。
  2. 行に表示する表示名(さまざまなUIで正常に表示されるよう、適切な長さにしてください)。
  3. テスト対象のデバイスシリアル番号(DSN)のリスト。DSNを使用すると、デバイスは電子番組表(EPG)にコンテンツを表示できます。一方、カスタマーID(CID)を使用すると、[ライブ] タブにコンテンツを表示できます。これにより、公開前にデバイスで上記の機能を検証できるようになります。

プロバイダーのアトリビューション

アプリのブランドを表す単一のモノクロロゴを、Amazonの担当者に提供します。このロゴは、ユーザーがライブイベント行を閲覧したときに表示されるコンテンツのプレビュー画像上にオーバーレイされます。この画像ファイルは、高さ34px、幅は最大でオーバーレイ画像の幅の25%までとする必要があります。

機能の互換性

アプリの実装は、Fire TVデバイス間で統一されます。Amazonでは、Android O(APIレベル26)で初めて導入されたこの機能を、以前のバージョンのFire TVデバイス(Fire OS 5および6)にバックポートしました。アプリが正常に動作するように、データベースを操作する際に機能の互換性を確認してください(後述のコードサンプルセクションで、機能の互換性確認を参照してください)。

おすすめチャンネル

ローカルチャンネルデータベースにおすすめチャンネル(タイプがPREVIEWinputIdフィールドが空)を1つだけプッシュするように、アプリを更新する必要があります。アプリでおすすめチャンネルを1つだけ作成し、タイプをPREVIEWとして定義するとともに、APP_LINK_INTENT_URIがアプリのメインアクティビティを指すように設定する必要があります。アプリ名に適合したdisplayNameを指定します。inputIdは設定しないでください。複数のおすすめチャンネルを挿入した場合、データベース内に追加のおすすめチャンネルと関連するプレビュープログラムがあっても、この統合の有効なコンテンツとして認識されません。

イベントの権限状態と順序

チャンネルを作成したら、視聴権限のあるコンテンツをFire TVデバイスのローカルデータベースにプッシュするようにアプリを更新します。コンテンツは、アプリがバックグラウンドで実行されている間も追加、更新、削除できます。このデータベースにコンテンツが存在すれば、ユーザーにそのコンテンツを視聴する権限があるということになります。データベースは、ユーザーに表示されるすべてのコンテンツの情報源として参照されます。コンテンツの追加や削除はこのデータベースからいつでも行うことができ、変更は5分以内にUIに反映されます。

アプリ内のイベントのソート順はプログラムのweightに基づいており、プログラムの挿入順序にフォールバックします。Fire TVでは、視認性を最大限に高めるために、最も話題性のあるコンテンツを前面に配置し、現在放送中のプログラムを行の先頭に置いて強調することを推奨しています。

番組メタデータ属性

UIの更新はFire TVで管理しますが、コンテンツの管理はすべてアプリで行う必要があります。アプリでは、TYPE_EVENTTYPE_CHANNELTYPE_CLIPのいずれかのプログラムをAndroidデータベースに挿入します。TvContractCompat.PreviewProgramsは可能な限り詳しく入力することをお勧めしますが、Amazonプラットフォームで最適なコンテンツエクスペリエンスを実現するために推奨される最小限の属性を次の表に示します。以下のメタデータのほとんどは、「ミニ詳細」の一部として表示されます。ミニ詳細は、コンテンツにフォーカスがあるときに左上隅に表示されるセクションです。

番組メタデータの表示
番組メタデータの表示

使用可能なメタデータは次のとおりです。上の図のコールアウトは、下表の「リファレンス」列に対応しています。

使用可能なTIFフィールドとプレビュープログラム列 目的 リファレンス 必須・任意
channel_id
COLUMN_CHANNEL_ID
ライブイベントをプレビューチャンネルにマッピングします。 必須
type
COLUMN_TYPE
PreviewPrograms.TYPE_EVENTPreviewPrograms.TYPE_CHANNELPreviewPrograms.TYPE_CLIPのいずれかに設定する必要があります。ほかのタイプはサポートしていません。 必須
title
COLUMN_WEIGHT
行内のタイルの位置を決めます。COLUMN_WEIGHTが設定されていない場合、フォールバックはプログラムの挿入順序になります。 任意
title
COLUMN_TITLE
イベントのタイトル。コンテンツタイルとミニ詳細に表示されます。 A 必須
short_description
COLUMN_SHORT_DESCRIPTION
イベントの説明。ミニ詳細に表示されます。 C 必須
poster_art_uri
COLUMN_POSTER_ART_URI
ライブプレビューが利用できない場合に、コンテンツタイルと背景画像の両方に使用される16:9の画像。 F、H 必須
intent_uri
COLUMN_INTENT_URI
ユーザーがコンテンツを選択したときに実行されるアクション。これにより、イベントが全画面で再生されます。 必須
start_time_utc_millis
COLUMN_START_TIME_UTC_MILLIS
イベントの日付と開始時刻。ミニ詳細に表示されます。ライブバッジを表示するかどうかの決定にも使用されます。 B、G 必須
end_time_utc_millis
COLUMN_END_TIME_UTC_MILLIS
イベントの終了時刻。ミニ詳細に表示されます。ライブバッジを表示するかどうかの決定にも使用されます。 B、G 必須
live
COLUMN_LIVE
コンテンツが現在公開されている(true)か、以前のライブイベントを再放送する(false)かに関係なく、ライブバッジを表示するかどうかを決定します。 E 必須
preview_video_uri
COLUMN_PREVIEW_VIDEO_URI
コンテンツタイルにフォーカスがあるときに、画面の右上隅にビデオがレンダリングされます。「ライブプレビュー」と呼ばれます。 H 必須
logo_uri
COLUMN_LOGO_URI
アプリのロゴ。上記の「プロバイダーのアトリビューション」セクションではサポートされていますが、現在のところTIFではサポートされていません。 H 任意
content_rating
COLUMN_CONTENT_RATING
機能制限のレーティング(表示目的でのみ使用)。 B 任意
interaction_count
COLUMN_INTERACTION_COUNT
PreviewPrograms.TYPE_CLIPの場合のみ。視聴者がこのコンテンツを操作した回数。ミニ詳細に表示されます。 B 任意^
internal_provider_data
COLUMN_INTERNAL_PROVIDER_DATA
2つのオプションフィールド(up_next
original_air_time)を含むJSONテキスト。
PreviewPrograms.TYPE_CHANNELでのみ使用します。現在のプログラムの後に再生されるコンテンツのタイトルを含む文字列。ミニ詳細に表示されます。

original_air_time。コンテンツの再放送に使用します。現在再生中のコンテンツが最初にブロードキャストされた時刻を表す文字列。ミニ詳細に表示されます。
C 任意^

^ 開発に利用可能。プレゼンテーションのサポートは現在準備中です。

Fire OS 7デバイスでは、PreviewProgram.Builderを使用してPreviewProgramオブジェクトを作成できます。それ以前のバージョンのFire OSでは、ContentValuesを手動で作成する必要があります。後述のコードサンプルセクションを参照してください。

コンテンツのサポート: イベント

スポーツイベントなどのようにスケジュールがわかっている場合は、PreviewProgram.TYPE_EVENTタイプを使用します。ライブフラグをtrueに設定し、開始時刻と終了時刻を設定します。スケジュールがわかっているイベントプログラム(スポーツイベントなど)の場合、Fire TVがサポートする高度な機能を最大限に活用するために、開始時刻と終了時刻の両方をメタデータとしてプッシュすることをお勧めします。以下に注意してください。

  1. Fire TVでは、7日以内に放送が予定されているイベントが表示され、終了時刻が過ぎると自動的に削除されます。したがって、7日以上先に開催されるイベントでも事前にプッシュしておくことができます。イベントの表示が早すぎたり、時間が過ぎても削除されなかったりという心配はありません。
  2. 現在放送中のイベントには、ステータスが放送中であることをユーザーに知らせるライブバッジがタイル上に表示されます。ライブバッジは、放送されていないときは非表示になります。
  3. 有効な開始時刻と終了時刻を指定すると、イベントの進行状況をユーザーに知らせる赤い進行状況バーがイベントタイルの下部に表示されます。

リアルタイムのイベント(ライブビデオブログなど)には、PreviewProgram.TYPE_EVENTタイプを使用します。ライブフラグをtrueに設定し、開始時刻と終了時刻にそれぞれLong.MIN_VALUELong.MAX_VALUEを設定します。このタイプのコンテンツをデータベースから削除すると、UIからも削除されます。

コンテンツのサポート: 再放送

ライブコンテンツの再放送(以前のスポーツイベントの再放送など)には、PreviewProgram.TYPE_EVENTを使用します。ライブフラグをfalseに設定し、開始時刻と終了時刻にそれぞれLong.MIN_VALUELong.MAX_VALUEを設定します。このタイプのコンテンツをデータベースから削除すると、UIからも削除されます。ユーザーが一度終了したFire TVのUIに再度アクセスする場合のベストプラクティスは、ユーザーが最後に視聴した時点から再生を開始することです。

コンテンツのサポート: 短いビデオクリップ

コンテンツが短いビデオクリップ(スポーツイベントのハイライトなど)の場合は、PreviewProgram.TYPE_CLIPを使用します。ライブフラグをfalseに設定し、開始時刻と終了時刻にそれぞれLong.MIN_VALUELong.MAX_VALUEを設定します。interaction_countフィールドは、視聴者がこのコンテンツを操作した回数をミニ詳細に設定します。このタイプのコンテンツをデータベースから削除すると、UIからも削除されます。

コンテンツのサポート: リニアチャンネル

シンプルな機能セットに基づくこの統合の一部として、リニアチャンネル(従来のTVやケーブルTVのように特定の時間にスケジュールされている番組)を含めることができます。リニアチャンネルのみを統合することを目的としたアプリの場合は、ライブTVの統合: リニアの統合手順が適している可能性があります。

PreviewProgram.TYPE_CHANNELタイプを使用し、liveフラグをtrueに設定します。少なくとも、説明とポスターアートにはチャンネルの属性を反映する必要があります。ただし、これらのフィールドは現在放送中のコンテンツの情報で更新し、次に放送予定の番組の情報をup_nextフィールドに追加することを強くお勧めします。タイルを選択すると、(最終視聴時ではなく)最新のタイムスタンプで全画面再生されます。このタイプのコンテンツをデータベースから削除すると、UIからも削除されます。

ベストプラクティス

以下は、Fire TVで快適なライブイベントエクスペリエンスを提供するのに役立つ製品および実装のガイドラインです。

全般

  • 簡単に登録できるようにして、適宜トライアルを促す。たとえば、アプリの登録フォームの項目を減らすことや、モバイルを使用した登録などが挙げられます。
  • Fire TVで利用可能な新機能をユーザーが把握できるように、ダウンロードページとリリースノートにこの統合について記載する。
  • データベースを操作する前に、必ずデバイスの機能の互換性を確認する。これにより、イベントのコンテンツがすべてのFire TVデバイスで正しく表示されるようになります。
  • ディープリンクフローを最適化して、全画面再生を2.5秒以内に開始する。

コンテンツの挿入

  • ライブイベントをデータベースに挿入する際は、サポートライブラリのヘルパー関数(PreviewProgram.Builderなど)を使用するのではなく、手動でライブイベントのContentValuesを構築する(推奨)。
  • カルーセルを完全に埋めるために、少なくとも5枚のタイルを挿入する。利用可能なPreviewProgramsが4個に満たない場合、その行は非表示になります。再放送、短いビデオクリップ、リニアチャンネルの追加を検討して、最小限のプログラム数を維持してください。
  • ライブイベントプログラムに必要なすべてのメタデータを提供する。プログラムの種類、開始時刻、終了時刻、ライブバッジの詳細については、番組メタデータ属性を参照してください。
  • COLUMN_TITLE列に表示可能なイベント名を指定する。Fire TVでは、最大16文字の英数字または8~10文字の全角文字がプログラムタイルに表示されます。
  • 閲覧でのライブイベントのプレビューをサポートして、エンゲージメントを促進する。

コンテンツの更新

  • JobSchedulerまたはWorkManagerを使用して、イベントの情報が正確であることを定期的に確認し、向こう7日間にわたって表示されるイベントの量が十分であることを確認する。これにより、アプリがフォアグラウンドでアクティブでない場合でも、閲覧エクスペリエンスにおいて常に豊富なイベントコンテンツが表示され、実際の視聴権限のあるイベントコンテンツと同期されるようになります。
  • データベース操作の頻度を最小限に抑えるため、放送が予定されている将来のイベントを事前にプッシュする。Fire TVでは、パフォーマンスを最適化するため、イベント行ごとにタイルは最大15個に制限されています。また、終了したイベントは、終了時刻を過ぎてから5分以内にカルーセルから削除されます。アプリで必要な処理は、次回の定期同期で期限切れのライブイベントコンテンツを削除することだけです。

コードサンプル

ライブイベントの一般的な実装を以下に示します。

Androidのマニフェスト

<!-- 必須。このサービスはJobSchedulerを使用してライブイベント
     データを定期的に同期します。 -->
<service
    android:name=".SampleJobService"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:exported="true" />


<!-- 必須。このレシーバーは再起動後に同期ジョブのスケジュールをトリガーします。 -->
<receiver android:name=".rich.RichBootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

<!-- このサービスはプレビューを再生します。Fire TV UIでTvViewを使用したプレビューの再生を
     サポートする場合にのみ必要です。 -->
<service android:name=".PreviewInputService"
    android:permission="android.permission.BIND_TV_INPUT">
    <!-- アカウントサービスを起動するためにシステムで使用される必須フィルター。 -->
    <intent-filter>
        <action android:name="android.media.tv.TvInputService" />
    </intent-filter>
    <!-- この入力を説明するXMLファイル。 -->
    <meta-data
        android:name="android.media.tv.input"
        android:resource="@xml/previewinputservice" />
</service>

機能の互換性確認

// 特定のデバイスに機能がバックポートされていることを識別するAmazonのマーカー
private final static String AMAZON_SYS_FEATURE_KEY =
  "com.amazon.feature.fos7_tv_provider";

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  // この機能は、Android O(API 26)バージョン以降でサポートが保証されています
  .....
} else if (isPreviewProgramBackportedOnFOS(context))(
  // この機能は、初期のFOSデバイスではバックポートを介してAmazonでサポートされます
  .....
}

public static boolean isPreviewProgramBackportedOnFOS(Context context) {
  PackageManager pm = context.getPackageManager();
  return pm.hasSystemFeature(AMAZON_SYS_FEATURE_KEY) &&
    pm.hasSystemFeature(AMAZON_SYS_FEATURE_KEY, 1);
}
// 特定のデバイスに機能がバックポートされていることを識別するAmazonのマーカー
private const val AMAZON_SYS_FEATURE_KEY = "com.amazon.feature.fos7_tv_provider"

private fun checkFeatureCompatibility(context: Context) {
    when {
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
            // この機能は、Android O(API 26)バージョン以降でサポートが保証されています
        }
        isPreviewProgramBackportedOnFOS(context) -> {
            // この機能は、初期のFOSデバイスではバックポートを介してAmazonでサポートされます
        }
        else -> {
            // 機能は利用できません
        }
    }
}

public fun isPreviewProgramBackportedOnFOS(context: Context): Boolean {
    val pm = context.packageManager;
    return pm.hasSystemFeature(AMAZON_SYS_FEATURE_KEY) &&
            pm.hasSystemFeature(AMAZON_SYS_FEATURE_KEY, 1)
}

プレビューチャンネルの挿入

public void buildPreviewChannel() {
    Channel.Builder builder = new Channel.Builder();
    builder.setType(TvContractCompat.Channels.TYPE_PREVIEW)
            .setDisplayName("SampleProviderChannel")
            .setAppLinkIntentUri(Uri.REQUIRED);/* サードパーティのメイン画面を
                                               起動するインテントである必要があります */
    Channel newChannel = builder.build();
    ContentValues cv = newChannel.toContentValues();
    Uri channelUri = resolver.insert(TvContractCompat.Channels.CONTENT_URI, cv);
}
fun buildPreviewChannel(context: Context) {
    val newChannel = Channel.Builder()
        .setType(TvContractCompat.Channels.TYPE_PREVIEW)
        .setDisplayName("SampleProviderChannel")
        .setAppLinkIntentUri(Uri.REQUIRED) // サードパーティのメイン画面の.build()を呼び出すインテントである必要があります

    val cv: ContentValues = newChannel.toContentValues()
    val channelUri: Uri? = context.contentResolver.insert(TvContractCompat.Channels.CONTENT_URI, cv)
}

ライブイベントの作成

public static ContentValues createLiveEventContentValues(Context context,
  long channelId) {

 ComponentName componentName = new ComponentName(context, PreviewVideoInputService.class);
 Uri previewVideoUri = TvContractCompat.buildPreviewProgramUri(1)
       .buildUpon()
       .appendQueryParameter("input", TvContractCompat.buildInputId(componentName))
       .build();
       
  TvContentRating[] ratings = new TvContentRating[1];
  ratings[0] = TvContentRating.createRating("com.android.tv.dummy",
    "US_TV_DUMMY",
    "US_TV_PG_DUMMY",
    "US_TV_D_DUMMY");

  /* 注: 公開前に必ずテストしてください。例:
       adb shell
       am start -W -a android.intent.action.VIEW -d "example://test/blablabla?e=1"
  */
  Intent deeplinkIntent = new Intent("android.intent.action.VIEW",
    Uri.parse("example://test/blablabla?e=1"));

  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    Log.d(Utils.DEBUG_TAG, "Androidビルド26以上です。ライブイベントをサポートしています。");
    PreviewProgram.Builder liveEventBuilder = new PreviewProgram.Builder();
    liveEventBuilder.setChannelId(channelId)
      .setLive(true)
      .setType(TvContractCompat.PreviewPrograms.TYPE_EVENT)
      .setTitle("ダミーのイベントタイトル")
      .setDescription("ダミーのイベントの説明")
      .setStartTimeUtcMillis(10000000)
      .setEndTimeUtcMillis(20000000)
      .setPosterArtUri(Uri.parse("http://placekitten.com/200/300"))
      .setLogoUri(Uri.parse("http://provider.logo.uri"))
      .setIntent(deeplinkIntent)
      .setPreviewVideoUri(previewVideoUri)
      .setContentRatings(ratings);
    return liveEventBuilder.build().toContentValues();
  } else if (Utils.isPreviewProgramBackportedOnFOS(context)) {
    Log.d(Utils.DEBUG_TAG, "バックポートされた機能が検出されました。 ライブイベントをサポートしています。");

    // PreviewProgram.Builder()内のsetIntent()の内部ロジックに合わせます
    Uri deeplinkIntentUri = Uri.parse(deeplinkIntent.toUri(URI_INTENT_SCHEME));

    /* ビルド26未満の場合は、ContentValuesを手動で作成します。ビルド26未満では、
       PreviewProgramBuilderは使用できません。toContentValues()メソッドによって
       重要なキーが内部で削除され、クラッシュを
       引き起こすためです。 */
    ContentValues cv = new ContentValues();
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_CHANNEL_ID, channelId);
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_LIVE, true);
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_TYPE,
      TvContractCompat.PreviewPrograms.TYPE_EVENT);
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_TITLE, "ダミーのイベントタイトル");
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_SHORT_DESCRIPTION,
      "ダミーのイベントの説明");
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_START_TIME_UTC_MILLIS,
      10000000);
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_END_TIME_UTC_MILLIS,
      20000000);
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_POSTER_ART_URI,
      "http://placekitten.com/200/300");
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_LOGO_URI,
      "http://provider.logo.uri");
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_INTENT_URI,
      (deeplinkIntentUri.toString()));
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI,
      previewVideoUri.toString());
    cv.put(TvContractCompat.PreviewPrograms.COLUMN_CONTENT_RATING,
      contentRatingsToString(ratings));
    return cv;
  } else {
    Log.d(Utils.DEBUG_TAG, "システムのビルドがライブイベントをサポートしていません");
    return null;
  }
}
fun createLiveEventContentValues(context: Context, channelId: Long): ContentValues? {

    val componentName = new ComponentName(context, PreviewVideoInputService.class)  
    val previewVideoUri: Uri = TvContractCompat.buildPreviewProgramUri(1)
        .buildUpon()
        .appendQueryParameter("input", TvContractCompat.buildInputId(componentName))
        .build()
  
    val ratings = arrayOf<TvContentRating>(
        TvContentRating.createRating(
            "com.android.tv.dummy",
            "US_TV_DUMMY",
            "US_TV_PG_DUMMY",
            "US_TV_D_DUMMY"
        )
    )

    /* 注: 公開前に必ずテストしてください。例:
       adb shell
       am start -W -a android.intent.action.VIEW -d "example://test/blablabla?e=1"
  */
    val deeplinkIntent = Intent(
        "android.intent.action.VIEW",
        Uri.parse("example://test/blablabla?e=1")
    )
    return when {
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
            Log.d(TAG, "Androidビルド26以上です。ライブイベントをサポートしています。")
            PreviewProgram.Builder()
                .setChannelId(channelId)
                .setLive(true)
                .setType(TvContractCompat.PreviewPrograms.TYPE_EVENT)
                .setTitle("ダミーのイベントタイトル")
                .setDescription("ダミーのイベントの説明")
                .setStartTimeUtcMillis(10000000)
                .setEndTimeUtcMillis(20000000)
                .setPosterArtUri(Uri.parse("http://placekitten.com/200/300"))
                .setLogoUri(Uri.parse("http://provider.logo.uri"))
                .setIntent(deeplinkIntent)
                .setPreviewVideoUri(previewVideoUri)
                .setContentRatings(ratings)
                .build()
                .toContentValues()
        }
        isPreviewProgramBackportedOnFOS(context) -> {
            Log.d(TAG, "バックポートされた機能が検出されました。 ライブイベントをサポートしています。")

            // PreviewProgram.Builder()内のsetIntent()の内部ロジックに合わせます
            val deeplinkIntentUri: Uri = Uri.parse(deeplinkIntent.toUri(URI_INTENT_SCHEME))

            // ビルド26未満の場合は、ContentValuesを手動で作成します。ビルド26未満では、
            // PreviewProgramBuilderは使用できません。toContentValues()メソッドによって
            // 重要なキーが内部で削除され、クラッシュを
            // 引き起こすためです。
            ContentValues().apply {
                put(TvContractCompat.PreviewPrograms.COLUMN_CHANNEL_ID, channelId)
                put(TvContractCompat.PreviewPrograms.COLUMN_LIVE, true)
                put(
                    TvContractCompat.PreviewPrograms.COLUMN_TYPE,
                    TvContractCompat.PreviewPrograms.TYPE_EVENT
                )
                put(TvContractCompat.PreviewPrograms.COLUMN_TITLE, "ダミーのイベントタイトル")
                put(
                    TvContractCompat.PreviewPrograms.COLUMN_SHORT_DESCRIPTION,
                    "ダミーのイベントの説明"
                )
                put(
                    TvContractCompat.PreviewPrograms.COLUMN_START_TIME_UTC_MILLIS,
                    10000000
                )
                put(
                    TvContractCompat.PreviewPrograms.COLUMN_END_TIME_UTC_MILLIS,
                    20000000
                )
                put(
                    TvContractCompat.PreviewPrograms.COLUMN_POSTER_ART_URI,
                    "http://placekitten.com/200/300"
                )
                put(
                    TvContractCompat.PreviewPrograms.COLUMN_LOGO_URI,
                    "http://provider.logo.uri"
                )
                put(
                    TvContractCompat.PreviewPrograms.COLUMN_INTENT_URI,
                    deeplinkIntentUri.toString()
                )
                put(
                    TvContractCompat.PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI,
                    previewVideoUri.toString()
                )
                put(
                    TvContractCompat.PreviewPrograms.COLUMN_CONTENT_RATING,
                    contentRatingsToString(ratings)
                )
            }
        }
        else -> {
            Log.d(TAG, "システムのビルドがライブイベントをサポートしていません")
            null
        }
    }
}

private const val TAG = "MyTag"

// 特定のデバイスに機能がバックポートされていることを識別するAmazonのマーカー
private const val AMAZON_SYS_FEATURE_KEY = "com.amazon.feature.fos7_tv_provider"

public fun isPreviewProgramBackportedOnFOS(context: Context): Boolean {
    val pm = context.packageManager;
    return pm.hasSystemFeature(AMAZON_SYS_FEATURE_KEY) &&
            pm.hasSystemFeature(AMAZON_SYS_FEATURE_KEY, 1)
}

fun contentRatingsToString(contentRatings: Array<TvContentRating>?): String? {
    if (contentRatings == null || contentRatings.isEmpty()) {
        return null
    }
    val DELIMITER = ","
    val ratings = StringBuilder(contentRatings[0].flattenToString())
    for (i in 1 until contentRatings.size) {
        ratings.append(DELIMITER)
        ratings.append(contentRatings[i].flattenToString())
    }
    return ratings.toString()
}

コンテンツの挿入

ContentValues[] liveEventsArray = <PreviewProgramContentValues配列>
  resolver.bulkInsert(TvContractCompat.PreviewPrograms.CONTENT_URI, liveEventsArray);
fun insertLiveEventContent(context: Context) {
    val liveEventsArray = arrayOf<ContentValues>()// PreviewProgramのContentValuesの配列
    context.contentResolver.bulkInsert(
        TvContractCompat.PreviewPrograms.CONTENT_URI,
        liveEventsArray
    )
}

コンテンツの更新

ContentValues updateValues = <更新するPreviewProgramの値>
  resolver.update(TvContractCompat.buildPreviewProgramUri( <更新するプレビュープログラムID> ), updateValues, null, null);
fun updateLiveEventContent(context: Context, programIdToUpdate: Long, updateValues: ContentValues) {
    context.contentResolver.update(TvContractCompat.buildPreviewProgramUri(programIdToUpdate), updateValues, null, null);
}

Last updated: 2022年10月10日