テレビ入力は、セットアップ アクティビティで少なくとも 1 つのチャンネルの電子番組ガイド(EPG)データを提供する必要があります。また、更新のサイズとそれを処理する処理スレッドを考慮して、更新データを定期的に更新する必要があります。また、関連するコンテンツとアクティビティにユーザー��誘導するチャンネルのアプリリンクを指定することもできます。このレッスンでは、以下の点を念頭に置いて、システム データベースでチャンネル データと番組データを作成および更新する方法について説明します。
TV 入力サービスのサンプルアプリを試す。
権限を設定する
TV 入力を EPG データと連携させるには、Android マニフェスト ファイルで次のように書き込み権限を宣言する必要があります。
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
データベースにチャンネルを登録する
テレビ入力のためのチャンネル データのレコードは、Android TV システム データベースに保持されます。設定アクティビティでは、チャンネルごとに、TvContract.Channels
クラスの次のフィールドにチャンネル データをマッピングする必要があります。
COLUMN_DISPLAY_NAME
- チャンネルの表示名COLUMN_DISPLAY_NUMBER
- 表示されるチャンネル番号COLUMN_INPUT_ID
- テレビ入力サービスの IDCOLUMN_SERVICE_TYPE
- チャンネルのサービスタイプCOLUMN_TYPE
- チャンネルのブロードキャスト標準タイプCOLUMN_VIDEO_FORMAT
- チャンネルのデフォルトの動画フォーマット
TV 入力フレームワークは汎用性が高く、従来のブロードキャスト コンテンツとオーバー ザ トップ(OTT)コンテンツを区別せずに処理できますが、従来のブロードキャスト チャネルを識別しやすくするために、上記の列に加えて以下の列を定義することをおすすめします。
COLUMN_ORIGINAL_NETWORK_ID
- テレビ ネットワーク IDCOLUMN_SERVICE_ID
- サービス IDCOLUMN_TRANSPORT_STREAM_ID
- トランスポート ストリーム ID
チャンネルのアプリリンクの詳細を提供する場合は、追加のフィールドを更新する必要があります。アプリリンク フィールドの詳細については、アプリリンク情報を追加するをご覧くだ��い。
インターネット ストリーミング ベースのテレビ入力の場合は、各チャンネルを一意に識別できるように、上記に独自の値を割り当てます。
バックエンド サーバーからチャンネル メタデータ(XML、JSON などで)を取得し、設定アクティビティで、次のように値をシステム データベースにマッピングします。
Kotlin
val values = ContentValues().apply { put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, channel.number) put(TvContract.Channels.COLUMN_DISPLAY_NAME, channel.name) put(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId) put(TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID, channel.transportStreamId) put(TvContract.Channels.COLUMN_SERVICE_ID, channel.serviceId) put(TvContract.Channels.COLUMN_VIDEO_FORMAT, channel.videoFormat) } val uri = context.contentResolver.insert(TvContract.Channels.CONTENT_URI, values)
Java
ContentValues values = new ContentValues(); values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number); values.put(Channels.COLUMN_DISPLAY_NAME, channel.name); values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId); values.put(Channels.COLUMN_TRANSPORT_STREAM_ID, channel.transportStreamId); values.put(Channels.COLUMN_SERVICE_ID, channel.serviceId); values.put(Channels.COLUMN_VIDEO_FORMAT, channel.videoFormat); Uri uri = context.getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);
上記の例では、channel
はバックエンド サーバーのチャンネル メタデータを保持するオブジェクトです。
チャンネルと番組の情報を表示する
図 1 に示すように、システム TV アプリは、ユーザーがチャンネル間を移動する際に、チャンネルと番組の情報を表示します。チャンネルと番組の情報がシステム テレビアプリのチャンネルと番組情報のプレゼンターと連携するようにするには、以下のガイドラインに従ってください。
- チャンネル番号(
COLUMN_DISPLAY_NUMBER
) - アイコン(テレビ入力のマニフェスト内の
android:icon
) - プログラムの説明(
COLUMN_SHORT_DESCRIPTION
) - 番組タイトル(
COLUMN_TITLE
) - チャンネルのロゴ(
TvContract.Channels.Logo
)- 周囲のテキストに合わせるには #EEEEEE の色を使用します。
- パディングを入れない
- ポスターアート(
COLUMN_POSTER_ART_URI
)- 16:9~4:3 のアスペクト比
システム TV アプリは、図 2 に示すように、番組ガイドを通じて同じ情報(ポスターアートを含む)を提供します。
チャンネル データを更新する
既存のチャンネル データを更新する場合は、データを削除して再度追加するのでは���く、update()
メソッドを使用します。データの現在のバージョンを確認するには、更新するレコードを選択する際に Channels.COLUMN_VERSION_NUMBER
と Programs.COLUMN_VERSION_NUMBER
を使用します。
注: チャンネル データを ContentProvider
に追加する作業には時間がかかることがあります。現在の番組(現在の時刻から 2 時間以内の番組)は、残りのチャンネル データをバックグラウンドで更新するように EpgSyncJobService
を構成する場合にのみ追加します。例については、
Android TV Live TV サンプルアプリをご覧ください。
チャンネル データを一括で読み込む
大量のチャンネル データでシステム データベースを更新する場合は、ContentResolver
applyBatch()
または bulkInsert()
メソッドを使用します。以下に applyBatch()
を使用した例を示します。
Kotlin
val ops = ArrayList<ContentProviderOperation>() val programsCount = channelInfo.mPrograms.size channelInfo.mPrograms.forEachIndexed { index, program -> ops += ContentProviderOperation.newInsert( TvContract.Programs.CONTENT_URI).run { withValues(programs[index]) withValue(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, programStartSec * 1000) withValue( TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, (programStartSec + program.durationSec) * 1000 ) build() } programStartSec += program.durationSec if (index % 100 == 99 || index == programsCount - 1) { try { contentResolver.applyBatch(TvContract.AUTHORITY, ops) } catch (e: RemoteException) { Log.e(TAG, "Failed to insert programs.", e) return } catch (e: OperationApplicationException) { Log.e(TAG, "Failed to insert programs.", e) return } ops.clear() } }
Java
ArrayList<ContentProviderOperation> ops = new ArrayList<>(); int programsCount = channelInfo.mPrograms.size(); for (int j = 0; j < programsCount; ++j) { ProgramInfo program = channelInfo.mPrograms.get(j); ops.add(ContentProviderOperation.newInsert( TvContract.Programs.CONTENT_URI) .withValues(programs.get(j)) .withValue(Programs.COLUMN_START_TIME_UTC_MILLIS, programStartSec * 1000) .withValue(Programs.COLUMN_END_TIME_UTC_MILLIS, (programStartSec + program.durationSec) * 1000) .build()); programStartSec = programStartSec + program.durationSec; if (j % 100 == 99 || j == programsCount - 1) { try { getContentResolver().applyBatch(TvContract.AUTHORITY, ops); } catch (RemoteException | OperationApplicationException e) { Log.e(TAG, "Failed to insert programs.", e); return; } ops.clear(); } }
チャンネル データを非同期で処理する
サーバーからのストリームの取得や���ータ���ース�����ア��セスなどのデータ操作が、UI スレッドをブロックしないようにする必要があります。AsyncTask
を使用することは、更新を非同期で行う方法の 1 つです。たとえば、バックエンド サーバーからチャンネル情報を読み込む場合は、次のように AsyncTask
を使用します。
Kotlin
private class LoadTvInputTask(val context: Context) : AsyncTask<Uri, Unit, Unit>() { override fun doInBackground(vararg uris: Uri) { try { fetchUri(uris[0]) } catch (e: IOException) { Log.d("LoadTvInputTask", "fetchUri error") } } @Throws(IOException::class) private fun fetchUri(videoUri: Uri) { context.contentResolver.openInputStream(videoUri).use { inputStream -> Xml.newPullParser().also { parser -> try { parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false) parser.setInput(inputStream, null) sTvInput = ChannelXMLParser.parseTvInput(parser) sSampleChannels = ChannelXMLParser.parseChannelXML(parser) } catch (e: XmlPullParserException) { e.printStackTrace() } } } } }
Java
private static class LoadTvInputTask extends AsyncTask<Uri, Void, Void> { private Context mContext; public LoadTvInputTask(Context context) { mContext = context; } @Override protected Void doInBackground(Uri... uris) { try { fetchUri(uris[0]); } catch (IOException e) { Log.d("LoadTvInputTask", "fetchUri error"); } return null; } private void fetchUri(Uri videoUri) throws IOException { InputStream inputStream = null; try { inputStream = mContext.getContentResolver().openInputStream(videoUri); XmlPullParser parser = Xml.newPullParser(); try { parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); parser.setInput(inputStream, null); sTvInput = ChannelXMLParser.parseTvInput(parser); sSampleChannels = ChannelXMLParser.parseChannelXML(parser); } catch (XmlPullParserException e) { e.printStackTrace(); } } finally { if (inputStream != null) { inputStream.close(); } } } }
EPG データを定期的に更新する必要がある場合は、WorkManager
を使用して、アイドル時間(毎日午前 3 時など)に更新プロセスを実行することを検討してください。
データ更新タスクを UI スレッドから分離するその他の手法として、HandlerThread
クラスを使用することも、Looper
クラスと Handler
クラスを使用して独自の実装を行うこともできます。詳細については、
プロセスとスレッドをご覧ください。
アプリリンク情報を追加する
チャンネルでは、アプリリンクを使用して、ユーザーが���ャンネルのコンテンツの視聴中に関連アクティビティを簡単に開始できます。チャンネル アプリはアプリリンクを使用して、関連情報や追加コンテンツを表示するアクティビティを起動し、ユーザー エンゲージメントを拡張します。たとえば、アプリリンクを使用すると次のことができます。
- ユーザーが関連コンテンツを見つけて購入できる場所を案内する
- 現在再生中のコンテンツに関する追加情報を提供する
- エピソード形式のコンテンツを視聴中に、シリーズの次のエピソードを表示します。
- コンテンツの再生を中断することなく、ユーザーがコンテンツを操作できるようにします(コンテンツの評価やレビューなど)。
ユーザーがチャンネルのコンテンツの視聴中に [選択] を押してテレビメニューを表示すると、アプリリンクが表示されます。
ユーザーがアプリリンクを選択すると、システムはチャンネル アプリで指定されたインテント URI を使用してアクティビティを開始します。アプリリンク アクティビティがアクティブの間、チャンネルのコンテンツは再生され続けます。ユーザーは [戻る] を押すことで、チャンネルのコンテンツに戻ることができます。
アプリリンクにチャンネル データを提供する
Android TV は、チャンネル データからの情報を使用して、各チャンネルのアプリリンクを自動的に作成します。アプリリンクの情報を提供するには、TvContract.Channels
フィールドに以下の詳細を指定します。
COLUMN_APP_LINK_COLOR
- このチャンネルのアプリリンクのアクセント カラー。アクセント カラーの例については、図 2 のコールアウト 3 をご覧ください。COLUMN_APP_LINK_ICON_URI
- このチャンネルのアプリリンクのアプリバッジ アイコンの URI。アプリバッジ アイコンの例については、図 2 のコールアウト 2 をご覧ください。COLUMN_APP_LINK_INTENT_URI
- このチャンネルのアプリリンクのインテント URI。URI_INTENT_SCHEME
でtoUri(int)
を使用して URI を作成し、parseUri()
で URI を元のインテントに変換できます。COLUMN_APP_LINK_POSTER_ART_URI
- このチャンネルのアプリリンクの背景として使用されるポスターアートの URI。ポスター画像の例は、図 2 の吹き出し 1 をご覧ください。COLUMN_APP_LINK_TEXT
- このチャネルのアプリリンクを説明するリンクテキスト。アプリリンクの説明の例については、図 2 のコールアウト 3 をご覧ください。
チャンネル データにアプリリンク情報が指定されていない場合は、デフォルトのアプリリンクが作成されます。詳細項目はデフォルトでは以下のように選択されます。
- インテント URI(
COLUMN_APP_LINK_INTENT_URI
)の場合、システムはCATEGORY_LEANBACK_LAUNCHER
カ��ゴリのACTION_MAIN
アクティビティ(通常はアプリ マニフェストで定義されている)を使用します。このアクティビティが定義されていない場合は、機能していないアプリリンクが表示されます。ユーザーがクリックしても、何も起こりません。 - 説明文(
COLUMN_APP_LINK_TEXT
)には「Open app-name」が使用されます。有効なアプリリンクのインテント URI が定義されていない場合は、「No link available」が使用されます。 - アクセント カラー(
COLUMN_APP_LINK_COLOR
)には、デフォルトのアプリカラーが使用されます。 - ポスター画像(
COLUMN_APP_LINK_POSTER_ART_URI
)には、アプリのホーム画面のバナーが使用されます。アプリでバナーを提供しない場合、システムはデフォルトのテレビアプリの画像を使用します。 - バッジアイコン(
COLUMN_APP_LINK_ICON_URI
)には、アプリ名を示すバッジが使用されます。システムがアプリバナーまたはデフォルトのアプリ画像をポスター画像にも使用している場合、アプリバッジは表示されません。
チャネルのアプリリンクの詳細は、アプリの設定アクティビティで指定します。アプリリンクの詳細はいつでも更新できます。そのため、アプリリンクをチャンネルの変更と一致させる必要がある場合は、アプリリンクの詳細を更新して、必要に応じて ContentResolver.update()
を呼び出します。チャンネル データの更新について詳しくは、チャンネル データを更新するをご覧ください。