TV 入力サービスを開発する

テレビ入力サービスはメディア ストリーム ソースを表し、メディア コンテンツをチャンネルおよび番組として地上波 TV の方法で提供できるようにします。テレビ入力サービスを使用すると、保護者による使用制限、番組ガイド情報、コンテンツのレーティングを提供できます。TV 入力サービスは Android システムの TV アプリと連携します。このアプリは、最終的にチャンネル コンテンツをテレビに制御して表示します。システム TV アプリは、デバイス専用に開発されたものであり、サードパーティ アプリでは変更できません。TV 入力フレームワーク(TIF)アーキテクチャとそのコンポーネントについて詳しくは、 TV 入力フレームワークをご覧ください。

TIF Companion Library を使用してテレビ入力サービスを作成する

TIF コンパニオン ライブラリは、一般的なテレビ入力サービス機能の拡張可能な実装を提供するフレームワークです。OEM が Android 5.0(API レベル 21)から Android 7.1(API レベル 25)向けのチャネルを作成するのに使用することを想定しています。

プロジェクトを更新する

OEM は、androidtv-sample-inputs リポジトリで TIF コンパニオン ライブラリをレガシーに利用できます。アプリにライブラリを組み込む方法の例については、こちらのリポジトリをご覧ください。

マニフェストでテレビ入力サービスを宣言する

アプリは、システムがアプリへのアクセスに使用する TvInputService 互換サービスを提供する必要があります。TIF コンパニオン ライブラリには、カスタマイズ可能な TvInputService のデフォルト実装を提供する BaseTvInputService クラスが用意されています。BaseTvInputService のサブクラスを作成し、マニフェストでサブクラスをサービスとして宣言します。

マニフェストの宣言内で BIND_TV_INPUT 権限を指定して、サービスが TV 入力をシステムに接続できるようにします。システム サービスはバインディングを実行し、BIND_TV_INPUT 権限を付与します。システム TV アプリは、TvInputManager インターフェースを介して TV 入力サービスにリクエストを送信します。

サービス宣言に、インテントで実行するアクションとして TvInputService を指定するインテント フィルタを含めます。また、サービス メタデータを個別の XML リソースとして宣言します。次の例は、サービス宣言、インテント フィルタ、サービス メタデータの宣言を示しています。

<service android:name=".rich.RichTvInputService"
    android:label="@string/rich_input_label"
    android:permission="android.permission.BIND_TV_INPUT">
    <!-- Required filter used by the system to launch our account service. -->
    <intent-filter>
        <action android:name="android.media.tv.TvInputService" />
    </intent-filter>
    <!-- An XML file which describes this input. This provides pointers to
    the RichTvInputSetupActivity to the system/TV app. -->
    <meta-data
        android:name="android.media.tv.input"
        android:resource="@xml/richtvinputservice" />
</service>

サービス メタデータを個別の XML ファイルで定義します。サービス メタデータ XML ファイルには、テレビ入力の初期設定とチャンネル スキャンを記述する設定インターフェースが含まれている必要があります。また、メタデータ ファイルには、ユーザーがコンテンツを録画できるかどうかを示すフラグも含める必要があります。アプリでコンテンツの録画をサポートする方法の詳細については、コンテンツの録画をサポートするをご覧ください。

サービス メタデータ ファイルはアプリの XML リソース ディレクトリにあり、マニフェストで宣言したリソースの名前と一致する必要があります。前の例のマニフェスト エントリを使用して、次の内容の XML ファイルを res/xml/richtvinputservice.xml に作成します。

<?xml version="1.0" encoding="utf-8"?>
<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
  android:canRecord="true"
  android:setupActivity="com.example.android.sampletvinput.rich.RichTvInputSetupActivity" />

チャンネルを定義してセットアップ アクティビティを作成する

TV 入力サービスは、ユーザーがシステム TV アプリを介してアクセスするチャンネルを少なくとも 1 つ定義する必要があります。そのチャンネルをシステム データベースに登録し、アプリのチャンネルが見つからない場合にシステムが呼び出すセットアップ アクティビティを指定する必要があります。

まず、アプリがシステムの電子番組ガイド(EPG)に対して読み書きできるようにします。EPG のデータには、ユーザーが視聴できるチャンネルと番組が含まれます。アプリでこれらのアクションを実行し、デバイスの再起動後に EPG と同期できるようにするには、次の要素をアプリ マニフェストに追加します。

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

次の要素を追加して、Android TV でコンテンツ チャンネルを提供するアプリとして Google Play ストアにアプリが表示されるようにします。

<uses-feature
    android:name="android.software.live_tv"
    android:required="true" />

次に、EpgSyncJobService クラスを拡張するクラスを作成します。この抽象クラスを使用すると、システム データベース内にチャネルを作成および更新するジョブサービスを簡単に作成できます。

サブクラスで、getChannels() にチャンネルの全リストを作成して返します。チャンネルが XMLTV ファイルに由来する場合は、XmlTvParser クラスを使用します。それ以外の場合は、Channel.Builder クラスを使用してプログラムでチャネルを生成します。

チャンネルごとに、チャンネルで特定の時間枠内に視聴できる番組のリストが必要になると、システムは getProgramsForChannel() を呼び出します。チャンネルの Program オブジェクトのリストを返します。XmlTvParser クラスを使用して XMLTV ファイルから番組を取得するか、Program.Builder クラスを使用してプログラムで番組を生成します。

Program オブジェクトごとに、InternalProviderData オブジェクトを使用して、番組の動画タイプなどの番組情報を設定します。番組の数が限られていて、チャンネルでループを繰り返す場合は、番組に関する情報を設定するときに、値が trueInternalProviderData.setRepeatable() メソッドを使用します。

ジョブサービスを実装したら、アプリ マニフェストに追加します。

<service
    android:name=".sync.SampleJobService"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:exported="true" />

最後に、セットアップ アクティビティを作成します。セットアップ アクティビティでは、チャンネルと番組のデータを同期する方法を指定する必要があります。これを行う方法の 1 つは、ユーザーがアクティビティの UI を介して行うことです。また、アクティビティの開始時にアプリで自動的に行うようにすることもできます。設定アクティビティがチャンネルと番組の情報を同期する必要がある場合、アプリはジョブサービスを開始する必要があります。

Kotlin

val inputId = getActivity().intent.getStringExtra(TvInputInfo.EXTRA_INPUT_ID)
EpgSyncJobService.cancelAllSyncRequests(getActivity())
EpgSyncJobService.requestImmediateSync(
        getActivity(),
        inputId,
        ComponentName(getActivity(), SampleJobService::class.java)
)

Java

String inputId = getActivity().getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
EpgSyncJobService.cancelAllSyncRequests(getActivity());
EpgSyncJobService.requestImmediateSync(getActivity(), inputId,
        new ComponentName(getActivity(), SampleJobService.class));

requestImmediateSync() メソッドを使用して、ジョブサービスを同期します。ユーザーは同期が完了するまで待つ必要があるため、リクエスト期間は比較的短くしてください。

setUpPeriodicSync() メソッドを使用して、ジョブサービスがバックグラウンドでチャンネルと番組のデータを定期的に同期するようにします。

Kotlin

EpgSyncJobService.setUpPeriodicSync(
        context,
        inputId,
        ComponentName(context, SampleJobService::class.java)
)

Java

EpgSyncJobService.setUpPeriodicSync(context, inputId,
        new ComponentName(context, SampleJobService.class));

TIF コンパニオン ライブラリには、requestImmediateSync() のオーバーロード メソッドが追加で用意されています。このメソッドを使用すると、同期するチャンネル データの長さをミリ秒単位で指定できます。デフォルトのメソッドでは、1 時間分のチャンネル データが同期されます。

TIF コンパニオン ライブラリには、setUpPeriodicSync() のオーバーロード メソッドも追加で用意されています。このメソッドでは、同期するチャンネル データの長さと、定期的な同期の頻度を指定できます。デフォルトの方法では、12 時間ごとに 48 時間分のチャンネル データが同期されます。

チャンネル データと EPG の詳細については、 チャンネル データを操作するをご覧ください。

チューニング リクエストとメディア再生を処理する

ユーザーが特定のチャンネルを選択すると、システム TV アプリはアプリが作成した Session を使用して、リクエストされたチャンネルにチューニングしてコンテンツを再生します。TIF コンパニオン ライブラリには、システムからのチャネル呼び出しやセッション呼び出しを処理するために拡張できるクラスがいくつか用意されています。

BaseTvInputService サブクラスにより、選局リクエストを処理するセッションが作成されます。onCreateSession() メソッドをオーバーライドし、BaseTvInputService.Session クラスから拡張されたセッションを作成して、新しいセッションで super.sessionCreated() を呼び出します。次の例では、onCreateSession()BaseTvInputService.Session を拡張する RichTvInputSessionImpl オブジェクトを返します。

Kotlin

override fun onCreateSession(inputId: String): Session =
        RichTvInputSessionImpl(this, inputId).apply {
            setOverlayViewEnabled(true)
        }

Java

@Override
public final Session onCreateSession(String inputId) {
    RichTvInputSessionImpl session = new RichTvInputSessionImpl(this, inputId);
    session.setOverlayViewEnabled(true);
    return session;
}

ユーザーがシステム TV アプリを使用していずれかのチャンネルの視聴を開始すると、システムはセッションの onPlayChannel() メソッドを呼び出します。番組の再生を開始する前に特別なチャンネルの初期化が必要な場合は、このメソッドをオーバーライドします。

次に、システムは現在スケジュールされている番組を取得し、セッションの onPlayProgram() メソッドを呼び出して、番組情報と開始時間をミリ秒単位で指定します。TvPlayer インターフェースを使用してプログラムの再生を開始します。

メディア プレーヤーのコードでは、特定の再生イベントを処理するために TvPlayer を実装する必要があります。TvPlayer クラスは、BaseTvInputService の実装を複雑にすることなく、タイムシフト制御などの機能を処理します。

セッションの getTvPlayer() メソッドで、TvPlayer を実装するメディア プレーヤーを返します。 TV 入力サービスのサンプルアプリには、ExoPlayer を使用するメディア プレーヤーが実装されています。

TV 入力フレームワークを使用して TV 入力サービスを作成する

テレビ入力サービスが TIF コンパニオン ライブラリを使用できない場合は、次のコンポーネントを実装する必要があります。

  • TvInputService: TV 入力に長時間の可用性とバックグラウンド機能を提供します。
  • TvInputService.Session はテレビの入力状態を維持し、ホスティング アプリと通信します。
  • TvContract は、TV 入力で利用可能なチャンネルと番組を記述します。
  • TvContract.Channels: テレビ チャンネルに関する情報を表します。
  • TvContract.Programs は、番組のタイトルや開始時間などのデータを持つテレビ番組を表します。
  • TvTrackInfo: 音声、動画、または字幕トラックを表します。
  • TvContentRating: コンテンツのレーティングを記述します。これにより、カスタムのコンテンツのレーティング スキームを作成できます。
  • TvInputManager はシステム TV アプリに API を提供し、TV 入力とアプリとのやり取りを管理します。

また、次の処理も行う必要があります。

  1. マニフェストで TV 入力サービスを宣言するの説明に沿って、マニフェストで TV 入力サービスを宣言します。
  2. サービス メタデータ ファイルを作成する。
  3. チャンネルと番組の情報を作成して登録する。
  4. セットアップ アクティビティを作成する。

テレビ入力サービスを定義する

サービス用に、TvInputService クラスを拡張します。TvInputService 実装はバインドされたサービスであり、システム サービスがそれにバインドするクライアントです。図 1 に、実装する必要があるサービス ライフサイクル メソッドを示します。

onCreate() メソッドは HandlerThread を初期化して開始します。これにより、システム主導のアクションを処理するために UI スレッドとは別のプロセス スレッドが提供されます。次の例では、onCreate() メソッドは CaptioningManager を初期化し、ACTION_BLOCKED_RATINGS_CHANGED アクションと ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED アクションを処理する準備をします。これらのアクションは、ユーザーが保護者による使用制限の設定を変更したり、ブロックした評価のリストが変更されたりしたときに実行されるシステム インテントを記述します。

Kotlin

override fun onCreate() {
    super.onCreate()
    handlerThread = HandlerThread(javaClass.simpleName).apply {
        start()
    }
    dbHandler = Handler(handlerThread.looper)
    handler = Handler()
    captioningManager = getSystemService(Context.CAPTIONING_SERVICE) as CaptioningManager

    setTheme(android.R.style.Theme_Holo_Light_NoActionBar)

    sessions = mutableListOf<BaseTvInputSessionImpl>()
    val intentFilter = IntentFilter().apply {
        addAction(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED)
        addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED)
    }
    registerReceiver(broadcastReceiver, intentFilter)
}

Java

@Override
public void onCreate() {
    super.onCreate();
    handlerThread = new HandlerThread(getClass()
      .getSimpleName());
    handlerThread.start();
    dbHandler = new Handler(handlerThread.getLooper());
    handler = new Handler();
    captioningManager = (CaptioningManager)
      getSystemService(Context.CAPTIONING_SERVICE);

    setTheme(android.R.style.Theme_Holo_Light_NoActionBar);

    sessions = new ArrayList<BaseTvInputSessionImpl>();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(TvInputManager
      .ACTION_BLOCKED_RATINGS_CHANGED);
    intentFilter.addAction(TvInputManager
      .ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
    registerReceiver(broadcastReceiver, intentFilter);
}

図 1.TvInputService のライフサイクル

ブロックされたコンテンツの操作と保護者による使用制限の設定について詳しくは、 コンテンツを管理するをご覧ください。TV 入力サービスで処理できる、その他のシステム主導のアクションについては、TvInputManager をご覧ください。

TvInputService は、プレーヤーの状態の変化を処理するために Handler.Callback を実装する TvInputService.Session を作成します。onSetSurface() を使用して、TvInputService.SessionSurface を動画コンテンツに設定します。Surface を使用して動画をレンダリングする方法については、プレーヤーをサーフェスと統合するをご覧ください。

TvInputService.Session は、ユーザーがチャンネルを選択すると onTune() イベントを処理し、コンテンツとコンテンツ メタデータの変更をシステム TV アプリに通知します。これらの notify() メソッドについては、このトレーニングの コントロール コンテンツトラック選択を処理するで詳しく説明します。

セットアップ アクティビティを定義する

システム TV アプリは、デベロッパーが TV 入力用に定義したセットアップ アクティビティで動作します。セットアップ アクティビティは必須で、システム データベースに少なくとも 1 つのチャンネル レコードを提供する必要があります。システム TV アプリは、テレビ入力のチャンネルが見つからない場合、セットアップ アクティビティを呼び出します。

セットアップ アクティビティでは、次のレッスンのチャンネル データの作成と更新で説明するように、TV 入力を通じて利用可能になったチャンネルがシステム TV アプリに記述されます。

その他の関連情報