使用頻道資料

您的電視輸入資料至少必須提供電子節目指南 (EPG) 資料 只與一個管道建立關聯您也應該定期更新 考慮更新的大小和處理執行緒 負責處理不同類型的工作此外,你也可以提供頻道的應用程式連結 引導使用者取得相關內容和活動。 本課程會說明如何建立及更新 系統資料庫需要考量這些因素

請嘗試 電視輸入服務範例應用程式。

授予權限

您的電視輸入裝置必須宣告 寫入權限,如下所示:

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

在資料庫中註冊頻道

Android TV 系統資料庫會維護電視輸入來源的頻道資料記錄。設定 您都必須將頻道資料對應到以下欄位的 TvContract.Channels 類別:

雖然電視輸入架構相當通用,可同時處理傳統廣播和電視 over-the-top (OTT) 內容沒有任何差異,建議您在 以及上述的傳統廣播頻道:

如要提供頻道的應用程式連結詳細資料,您必須 更新一些額外欄位如要進一步瞭解應用程式連結欄位,請參閱 新增應用程式連結資訊

針對以網際網路串流為基礎的電視輸入來源,請在上方自行指派值,以便 每個管道都可以用於識別。

從後端伺服器擷取頻道中繼資料,包括 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 應用程式的 頻道和計劃資訊的畫面,請遵循下列規範。

  1. 頻道編號 (COLUMN_DISPLAY_NUMBER)
  2. 圖示 (android:icon 的 電視輸入裝置的資訊清單)
  3. 方案說明 (COLUMN_SHORT_DESCRIPTION)
  4. 方案名稱 (COLUMN_TITLE)
  5. 頻道標誌 (TvContract.Channels.Logo)
    • 使用 #EEEEEE 顏色代表與周圍文字相符
    • 不加入邊框間距
  6. 海報圖片 (COLUMN_POSTER_ART_URI)
    • 顯示比例介於 16:9 和 4:3

圖 1. 系統電視應用程式頻道和節目資訊顯示器。

系統 TV 應用程式透過節目指南提供相同的資訊,包括海報圖片 如圖 2 所示。

圖 2. 系統 TV 應用程式節目指南。

更新頻道資料

更新現有頻道資料時,請使用 update() 方法,而不是刪除並重新新增資料。您可以辨識資料的目前版本 使用「Channels.COLUMN_VERSION_NUMBER」 和Programs.COLUMN_VERSION_NUMBER

注意:ContentProvider 中新增頻道資料 可能需要一些時間新增目前的節目 (在目前時間的兩小時內) 只有在設定 EpgSyncJobService 來更新其餘資料時 模型資料的背景中詳情請見 Android 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 等同於 以非同步方式執行更新例如,從後端伺服器載入頻道資訊時 您可以按照以下方式使用 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();
            }
        }
    }
}

如果您需要定期更新電子節目表資料,建議您使用 WorkManager敬上 在閒置時間執行更新程序,例如每天凌晨 3 點。

其他將資料更新工作與 UI 執行緒分離的技術包括: HandlerThread 類別,也可以使用 Looper 自行實作 和 Handler 類別。詳情請參閱 程序和執行緒

頻道可使用應用程式連結,讓使用者輕鬆啟動相關的 觀看頻道內容時的活動。管道應用程式會使用 應用程式連結,然後啟動會顯示 相關資訊或其他內容舉例來說,您可以使用應用程式連結 :

  • 引導使用者探索及購買相關內容。
  • 針對目前播放的內容提供額外資訊。
  • 觀看系列內容時, 播放下一集 這是 Gemini 版 Google Workspace 系列課程之一
  • 讓使用者與內容互動,例如評分或評論 完全不必中斷內容播放。

使用者按下 Select 後 觀看頻道內容時顯示的電視選單。

圖 1. 應用程式連結範例 頻道內容顯示時,則會顯示在「頻道」列中。

當使用者選取應用程式連結時,系統會透過 頻道應用程式指定的意圖 URI。頻道內容繼續播放 在應用程式連結活動啟用的狀態下進行。使用者可以返回頻道 按下「返回」圖示

提供應用程式連結管道資料

Android TV 會自動為每個頻道建立應用程式連結 頻道資料如要提供應用程式連結資訊, 指定下列詳細資料 TvContract.Channels 欄位:

圖 2. 應用程式連結詳細資料。

如果管道資料未指定應用程式連結資訊,系統會 建立預設的應用程式連結系統會選擇下列預設詳細資料:

  • 適用於意圖 URI (COLUMN_APP_LINK_INTENT_URI), 系統會使用 ACTION_MAIN CATEGORY_LEANBACK_LAUNCHER 類別的活動,通常在應用程式資訊清單中定義。 如未定義這個活動,系統會顯示無法運作的應用程式連結 (如果未定義), 使用者點擊訊息時,不會有任何反應
  • 用於說明文字 (COLUMN_APP_LINK_TEXT),系統 使用「開啟 app-name」。如果沒有定義可用的應用程式連結意圖 URI 系統便會使用「沒有可用的連結」選項。
  • 適用於強調色 (COLUMN_APP_LINK_COLOR), 系統會使用預設的應用程式顏色。
  • 適用於代表圖片 (COLUMN_APP_LINK_POSTER_ART_URI), 系統會使用應用程式的主畫面橫幅。如果應用程式未提供 橫幅,系統會使用預設的 TV 應用程式圖片。
  • 徽章圖示 (COLUMN_APP_LINK_ICON_URI), 系統會使用顯示應用程式名稱的標記如果系統也使用 代表海報圖片的應用程式橫幅或預設的應用程式圖片,不會顯示應用程式徽章。

您可以在應用程式的 設定活動。您隨時都可更新這些應用程式連結詳細資料,例如 如果應用程式連結需要與頻道變更一致,請更新應用程式 連結詳細資料和通話 ContentResolver.update()。進一步瞭解如何更新 頻道資料,請參閱「更新頻道資料」一文。