開發電視輸入服務

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

使用 TIF 隨附資料庫建立電視輸入服務

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

更新專案

TIF 隨附程式庫可在 androidtv-sample-inputs 存放區中供 OEM 使用。請參閱該存放區的範例,瞭解如何在應用程式中加入程式庫。

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

應用程式必須提供與 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 中提供內容管道的應用程式:

<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 小時的頻道資料。

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

處理調整要求和媒體播放

使用者選取特定頻道時,系統電視應用程式會使用由您的應用程式建立的 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 輸入服務範例應用程式會實作使用 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 會建立 TvInputService.Session,用於實作 Handler.Callback 來處理玩家狀態變更。而使用 onSetSurface() 時,TvInputService.Session 會以影片內容來設定 Surface。如要進一步瞭解如何使用 Surface 轉譯影片,請參閱「將播放器與介面整合」。

使用者選取頻道時,TvInputService.Session 會處理 onTune() 事件,並通知系統電視應用程式變更內容和內容中繼資料。在本訓練課程中,我們會進一步介紹這些 notify() 方法,請參閱「 控制內容」和「處理音軌選取」。

定義設定活動

系統電視應用程式會使用您為電視輸入裝置定義的設定活動。必須提供設定活動,且至少須為系統資料庫提供一筆管道記錄。系統電視應用程式找不到電視輸入來源的頻道時,會叫用設定活動。

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

其他參考資料