開發電視輸入服務

電視輸入服務代表媒體串流來源,可讓您以傳統電視方式呈現媒體內容,做為頻道和節目。您可以使用電視輸入服務提供家長監護功能、節目指南資訊和內容分級。電視輸入服務可與 Android 系統 TV 應用程式搭配使用。這個應用程式最終會控制並在電視上顯示頻道內容。系統電視應用程式是專為裝置開發,由第三方應用程式無法變更。如要進一步瞭解電視輸入架構 (TIF) 架構及其元件,請參閱 電視輸入架構

使用 TIF 隨附程式庫建立電視輸入服務

TIF 隨附程式庫是一種架構,可針對常見的電視輸入服務功能提供可擴充的實作。原始設備製造商 (OEM) 必須使用 Android 5.0 (API 級別 21) 至 Android 7.1 (API 級別 25) 來建立管道。

更新專案

androidtv-sample-inputs 存放區中可供原始設備製造商 (OEM) 使用 TIF 隨附程式庫。如要瞭解如何在應用程式中加入程式庫,請參閱該存放區。

在資訊清單中宣告電視輸入服務

您的應用程式必須提供與 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),其資料包含使用者可用的頻道和程式。如要讓應用程式執行這些動作,並在裝置重新啟動後與 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 內容管道的應用程式顯示在 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 物件設定節目資訊,例如節目的影片類型。如果只有部分節目希望頻道以迴圈方式重複播放,請在設定方案資訊時,使用 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 小時的管道資料。

如要進一步瞭解頻道資料和 EPG,請參閱「 使用頻道資料」。

處理調整要求和媒體播放

當使用者選取特定頻道時,系統電視應用程式會使用應用程式建立的 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 的媒體播放器。「 TV Input Service」範例應用程式會實作使用 ExoPlayer 的媒體播放器。

使用電視輸入架構建立電視輸入服務

如果電視輸入服務無法使用 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

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

當使用者選取頻道時,TvInputService.Session 會處理 onTune() 事件,並通知系統電視應用程式的內容和內容中繼資料有異動。在本訓練課程的後續章節中,請參閱「 控制內容」和「處理音軌選取」一文。notify()

定義設定活動

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

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

其他參考資料