開發電視輸入服務

電視輸入服務代表媒體串流來源,並允許您以線性廣播電視的方式將媒體內容呈現為頻道和節目。透過電視輸入服務,您可以提供家長監護、節目指南資訊和內容分級。電視輸入服務會與 Android 系統電視應用程式搭配運作。這個應用程式最終會控制電視上的頻道內容並呈現。系統電視應用是專為該設備開發的,第三方應用無法對其進行修改。如要進一步瞭解 TV Input Framework (TIF) 架構和元件,請參閱「 TV Input Framework」。

使用 TIF 伴侶庫建立電視輸入服務

TIF Companion Library 是一個架構,可提供常見電視輸入服務功能的擴充實作項目。OEM 只能使用這個程式庫,為 Android 5.0 (API 級別 21) 到 Android 7.1 (API 級別 25) 建立管道。

更新專案

OEM 可在 androidtv-sample-inputs 存放區中,使用舊版 TIF Companion Library。請參閱該儲存庫,以瞭解如何在應用程式中包含該庫的範例。

請在清單中聲明您的電視輸入服務。

您的應用程式必須提供系統用於存取您的應用程式的 TvInputService 相容服務。 TIF 配套庫提供了 BaseTvInputService 類,該類提供了 TvInputService 的預設實現,您可以對其進行自訂。建立 BaseTvInputService 的子類別,並在資訊清單中將子類別宣告為服務。

在清單聲明中,指定 BIND_TV_INPUT 權限,以允許服務將電視輸入連接到系統。系統服務執行綁定操作,並擁有 BIND_TV_INPUT 權限。 系統電視應用程式透過TvInputManager介面向電視輸入服務發送請求。

在您的服務聲明中,包含一個意圖過濾器,該過濾器指定 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 資源目錄中,且必須與您在資訊清單中宣告的資源名稱相符。使用上一個範例中的資訊清單項目,您會在 res/xml/richtvinputservice.xml 建立 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" />

定義頻道並建立您的設定活動

電視輸入服務必須定義至少一個頻道,供使用者透過系統電視應用程式存取。您應在系統資料庫中註冊頻道,並提供設定活動,供系統在找不到應用程式的頻道時叫用。

首先,請允許應用程式讀取及寫入系統電子節目表 (EPG),這項資料包括使用者可觀看的頻道和節目。若要使您的應用程式能夠執行這些操作,並在裝置重新啟動後與電子節目表同步,請將下列元素新增至您的應用程式資訊清單:

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

加入以下元素,以確保您的應用程式在 Google Play 商店中顯示為提供 Android TV 內容頻道的應用程式:

<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 物件來設定節目訊息,例如節目的視訊類型。如果只有少數幾個節目想讓頻道循環播放,請在設定節目資訊時,使用 InternalProviderData.setRepeatable 方法並將值設為 true

實作作業服務後,請將其新增至應用程式資訊清單:

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

最後,建立設定活動。設定活動應提供同步處理頻道和節目資料的方法。其中一種做法是讓使用者透過活動中的 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 的額外過載方法,可讓您以毫秒為單位,指定要同步處理的頻道資料時間長度。預設方法會同步處理一小時的頻道資料。

TIF 隨附程式庫也提供 setUpPeriodicSync 的額外過載方法,可讓您指定要同步處理的頻道資料時間長度,以及定期同步處理的頻率。預設方法每 12 小時同步 48 小時的頻道資料。

如要進一步瞭解頻道資料和電子節目表,請參閱「 使用頻道資料」。

處理調頻請求和媒體播放

使用者選取特定頻道時,系統 TV 應用程式會使用應用程式建立的 Session,切換至所選頻道並播放內容。TIF 隨附程式庫提供多個可擴充的類別,用於處理系統的管道和工作階段呼叫。

您的 BaseTvInputService 子類別會建立工作階段,處理微調要求。覆寫 onCreateSession 方法,建立從 BaseTvInputService.Session 類別擴充的工作階段,然後使用新工作階段呼叫 super.sessionCreated。在以下範例中,onCreateSession 會傳回擴充 BaseTvInputService.SessionRichTvInputSessionImpl 物件:

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;
}

使用者透過系統電視應用程式開始觀看你的頻道時,系統會呼叫工作階段的 onPlayChannel 方法。如果需要在節目開始播放前進行任何特殊頻道初始化作業,請覆寫這個方法。

接著,系統會取得目前排定的節目,並呼叫工作階段的 onPlayProgram 方法,以毫秒為單位指定節目資訊和開始時間。使用 TvPlayer 介面開始播放程式。

您的媒體播放器程式碼應實作 TvPlayer 來處理特定的播放事件。TvPlayer 類別處理諸如時間平移控制之類的功能,而不會增加 BaseTvInputService 實現的複雜性。

在工作階段的 getTvPlayer 方法中,傳回實作 TvPlayer 的媒體播放器。 電視輸入服務範例應用程式會實作使用 ExoPlayer 的媒體播放器。

使用 TV 輸入架構建立 TV 輸入服務

如果電視輸入服務無法使用 TIF 隨附程式庫,您必須實作下列元件:

此外,您還需要完成下列事項:

  1. 在資訊清單中宣告電視輸入服務,如「在資訊清單中宣告電視輸入服務」一文所述。
  2. 建立服務元資料檔。
  3. 建立及註冊頻道和節目資訊。
  4. 建立您的設定活動。

定義電視輸入服務

您會擴充服務的 TvInputService 類別。TvInputService 實作是繫結服務,系統服務是繫結至該服務的用戶端。圖 1 說明您需要實作的服務生命週期方法。

onCreate 方法會初始化並啟動 HandlerThread,後者會提供與 UI 執行緒不同的程序執行緒,以處理系統驅動的動作。在以下範例中,onCreate 方法會初始化 CaptioningManager,並準備處理 ACTION_BLOCKED_RATINGS_CHANGEDACTION_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 生命週期。

如要進一步瞭解如何處理遭封鎖的內容及提供家長監護功能,請參閱「 控管內容」。如要瞭解更多系統驅動的動作,請參閱 TvInputManager,您可能需要在 TV 輸入服務中處理這些動作。

TvInputService 會建立實作 Handler.CallbackTvInputService.Session,以處理播放器狀態變更。使用 onSetSurface, TvInputService.Session 會使用影片內容設定 Surface。如要進一步瞭解如何使用 Surface 算繪影片,請參閱「將播放器與介面整合」一文。

TvInputService.Session 會在使用者選取頻道時處理 onTune 事件,並通知系統 TV 應用程式內容和內容中繼資料的變更。本訓練課程稍後會進一步說明這些 notify 方法,請參閱「 控管內容」和「處理軌道選取作業」 。

定義您的設定活動

系統電視應用程式會搭配您為電視輸入裝置定義的設定活動運作。您必須完成設定活動,並為系統資料庫提供至少一筆管道記錄。如果系統找不到電視輸入裝置的頻道,就會叫用設定活動。

如下一堂課「建立及更新頻道資料」所示,設定活動會向系統 TV 應用程式說明透過 TV 輸入提供的頻道。

其他參考資料