Làm việc với dữ liệu kênh

Đầu vào TV của bạn phải cung cấp dữ liệu Hướng dẫn chương trình điện tử (EPG) cho ít nhất một kênh trong hoạt động thiết lập. Bạn cũng nên định kỳ cập nhật dữ liệu đó, đồng thời xem xét kích thước của bản cập nhật và luồng xử lý xử lý dữ liệu đó. Ngoài ra, bạn có thể cung cấp đường liên kết ứng dụng cho các kênh hướng dẫn người dùng đến nội dung và hoạt động liên quan. Bài này thảo luận về việc tạo và cập nhật dữ liệu kênh và chương trình trên cơ sở dữ liệu hệ thống có lưu ý những điểm cần cân nhắc sau đây.

Dùng thử ứng dụng mẫu TV Input Service (Dịch vụ đầu vào TV).

Xin cấp quyền

Để đầu vào TV của bạn hoạt động với dữ liệu EPG, thiết bị đó phải khai báo quyền ghi trong tệp kê khai Android như sau:

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

Đăng ký kênh trong cơ sở dữ liệu

Cơ sở dữ liệu hệ thống của Android TV duy trì các bản ghi dữ liệu kênh cho đầu vào TV. Trong hoạt động thiết lập, đối với từng kênh, bạn phải liên kết dữ liệu kênh với các trường sau đây của lớp TvContract.Channels:

Mặc dù khung đầu vào TV đủ chung chung để xử lý cả nội dung phát sóng truyền thống và nội dung trực tiếp qua Internet (OTT) mà không có sự khác biệt nào, nhưng bạn có thể muốn xác định các cột sau ngoài các cột ở trên để xác định tốt hơn các kênh phát sóng truyền thống:

Nếu muốn cung cấp thông tin chi tiết về đường liên kết ứng dụng cho các kênh của mình, bạn cần cập nhật một số trường bổ sung. Để biết thêm thông tin về các trường liên kết ứng dụng, hãy xem phần Thêm thông tin liên kết ứng dụng.

Đối với đầu vào của TV dựa trên việc phát trực tuyến qua Internet, hãy gán giá trị riêng của bạn cho các giá trị nêu trên sao cho mỗi kênh có thể được xác định riêng biệt.

Lấy siêu dữ liệu kênh (ở định dạng XML, JSON hoặc bất kỳ hình thức nào) từ máy chủ phụ trợ và trong hoạt động thiết lập của bạn sẽ ánh xạ các giá trị đến cơ sở dữ liệu hệ thống như sau:

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

Trong ví dụ trên, channel là một đối tượng lưu giữ siêu dữ liệu kênh từ máy chủ phụ trợ.

Trình bày thông tin về kênh và chương trình

Ứng dụng TV hệ thống hiển thị thông tin về kênh và chương trình cho người dùng khi họ lướt xem các kênh, như minh hoạ trong hình 1. Để đảm bảo thông tin kênh và chương trình hoạt động với trình trình bày thông tin kênh và chương trình của ứng dụng TV hệ thống, hãy làm theo các nguyên tắc dưới đây.

  1. Số kênh (COLUMN_DISPLAY_NUMBER)
  2. Biểu tượng (android:icon trong tệp kê khai của đầu vào TV)
  3. Mô tả chương trình (COLUMN_SHORT_DESCRIPTION)
  4. Tên chương trình (COLUMN_TITLE)
  5. Biểu trưng của kênh (TvContract.Channels.Logo)
    • Sử dụng màu #EEEEEE để khớp với văn bản xung quanh
    • Không thêm khoảng đệm
  6. Ảnh áp phích (COLUMN_POSTER_ART_URI)
    • Tỷ lệ khung hình từ 16:9 đến 4:3

Hình 1. Kênh ứng dụng truyền hình hệ thống và người trình bày thông tin chương trình.

Ứng dụng truyền hình hệ thống cung cấp thông tin tương tự thông qua hướng dẫn chương trình, bao gồm cả ảnh áp phích như trong hình 2.

Hình 2. Hướng dẫn về chương trình ứng dụng TV trên hệ thống.

Cập nhật dữ liệu kênh

Khi cập nhật dữ liệu kênh hiện có, hãy sử dụng phương thức update() thay vì xoá và thêm lại dữ liệu. Bạn có thể xác định phiên bản hiện tại của dữ liệu bằng cách sử dụng Channels.COLUMN_VERSION_NUMBERPrograms.COLUMN_VERSION_NUMBER khi chọn các bản ghi cần cập nhật.

Lưu ý: Việc thêm dữ liệu kênh vào ContentProvider có thể mất chút thời gian. Chỉ thêm các chương trình hiện tại (các chương trình trong vòng 2 giờ kể từ thời điểm hiện tại) chỉ khi bạn định cấu hình EpgSyncJobService để cập nhật phần còn lại của dữ liệu kênh trong nền. Hãy xem Ứng dụng mẫu Android TV Live TV để biết ví dụ.

Tải hàng loạt dữ liệu kênh

Khi cập nhật cơ sở dữ liệu hệ thống bằng một lượng lớn dữ liệu kênh, hãy sử dụng phương thức ContentResolver applyBatch() hoặc bulkInsert(). Dưới đây là một ví dụ về cách sử dụng 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();
    }
}

Xử lý dữ liệu kênh một cách không đồng bộ

Thao tác với dữ liệu (chẳng hạn như tìm nạp luồng từ máy chủ hoặc truy cập cơ sở dữ liệu) sẽ không chặn luồng giao diện người dùng. Sử dụng AsyncTask là một cách để thực hiện các bản cập nhật không đồng bộ. Ví dụ: khi tải thông tin kênh từ một máy chủ phụ trợ, bạn có thể sử dụng AsyncTask như sau:

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

Nếu bạn cần cập nhật dữ liệu EPG thường xuyên, hãy cân nhắc sử dụng WorkManager để chạy quá trình cập nhật trong thời gian không hoạt động, chẳng hạn như lúc 3 giờ sáng hằng ngày.

Các kỹ thuật khác để phân tách tác vụ cập nhật dữ liệu khỏi luồng giao diện người dùng bao gồm việc sử dụng lớp HandlerThread, hoặc bạn có thể triển khai của riêng mình bằng cách sử dụng các lớp LooperHandler. Vui lòng xem bài viết Quy trình và luồng để biết thêm thông tin.

Các kênh có thể sử dụng đường liên kết ứng dụng để cho phép người dùng dễ dàng chạy một hoạt động liên quan trong khi xem nội dung của kênh. Ứng dụng kênh sử dụng đường liên kết ứng dụng để mở rộng mức độ tương tác của người dùng bằng cách khởi chạy các hoạt động cho thấy thông tin liên quan hoặc nội dung bổ sung. Ví dụ: bạn có thể sử dụng đường liên kết ứng dụng để làm những việc sau:

  • Hướng dẫn người dùng khám phá và mua nội dung có liên quan.
  • Cung cấp thêm thông tin về nội dung đang phát.
  • Khi xem nội dung theo tập, hãy bắt đầu xem tập tiếp theo trong một loạt phim.
  • Cho phép người dùng tương tác với nội dung (ví dụ: xếp hạng hoặc đánh giá nội dung) mà không làm gián đoạn việc phát nội dung.

Các đường liên kết ứng dụng sẽ xuất hiện khi người dùng nhấn Select (Chọn) để hiện trình đơn TV trong khi xem nội dung kênh.

Hình 1. Ví dụ về một đường liên kết trong ứng dụng xuất hiện trên hàng Channels (Kênh) trong khi nội dung kênh đang hiển thị.

Khi người dùng chọn đường liên kết đến ứng dụng, hệ thống sẽ bắt đầu một hoạt động bằng cách sử dụng URI ý định do ứng dụng kênh chỉ định. Nội dung kênh tiếp tục phát trong khi hoạt động liên kết ứng dụng đang hoạt động. Người dùng có thể quay lại nội dung kênh bằng cách nhấn Back (Quay lại).

Cung cấp dữ liệu về kênh liên kết ứng dụng

Android TV tự động tạo một đường liên kết đến ứng dụng cho mỗi kênh bằng cách sử dụng thông tin từ dữ liệu của kênh. Để cung cấp thông tin về đường liên kết đến ứng dụng, hãy chỉ định thông tin chi tiết sau trong các trường TvContract.Channels:

  • COLUMN_APP_LINK_COLOR - Màu nhấn của đường liên kết đến ứng dụng cho kênh này. Để biết ví dụ về màu nhấn, hãy xem hình 2, chú thích 3.
  • COLUMN_APP_LINK_ICON_URI – URI cho biểu tượng huy hiệu ứng dụng của đường liên kết đến ứng dụng trên kênh này. Để biết biểu tượng huy hiệu ứng dụng ví dụ, hãy xem hình 2, chú thích 2.
  • COLUMN_APP_LINK_INTENT_URI – URI ý định của đường liên kết ứng dụng cho kênh này. Bạn có thể tạo URI bằng cách sử dụng toUri(int) với URI_INTENT_SCHEME và chuyển đổi URI trở về ý định ban đầu bằng parseUri().
  • COLUMN_APP_LINK_POSTER_ART_URI – URI của ảnh áp phích được dùng làm nền của đường liên kết ứng dụng cho kênh này. Để xem ví dụ về hình ảnh áp phích, hãy xem hình 2, chú thích 1.
  • COLUMN_APP_LINK_TEXT - Văn bản liên kết mô tả của liên kết ứng dụng cho kênh này. Đối với nội dung mô tả về đường liên kết ứng dụng mẫu, hãy xem văn bản trong hình 2, chú thích 3.

Hình 2. Thông tin chi tiết về đường liên kết đến ứng dụng.

Nếu dữ liệu kênh không chỉ định thông tin về đường liên kết ứng dụng, hệ thống sẽ tạo một đường liên kết ứng dụng mặc định. Hệ thống chọn các thông tin chi tiết mặc định như sau:

  • Đối với URI ý định (COLUMN_APP_LINK_INTENT_URI), hệ thống sẽ sử dụng hoạt động ACTION_MAIN cho danh mục CATEGORY_LEANBACK_LAUNCHER, thường được xác định trong tệp kê khai ứng dụng. Nếu hoạt động này không được xác định, thì một đường liên kết ứng dụng không hoạt động sẽ xuất hiện – nếu người dùng nhấp vào thì sẽ không có gì xảy ra.
  • Đối với văn bản mô tả (COLUMN_APP_LINK_TEXT), hệ thống sẽ sử dụng "Mở app-name". Nếu không xác định được URI ý định liên kết ứng dụng khả thi nào, hệ thống sẽ sử dụng giá trị "Không có đường liên kết nào".
  • Đối với màu nhấn (COLUMN_APP_LINK_COLOR), hệ thống sẽ sử dụng màu mặc định của ứng dụng.
  • Đối với hình ảnh áp phích (COLUMN_APP_LINK_POSTER_ART_URI), hệ thống sẽ sử dụng biểu ngữ trên màn hình chính của ứng dụng. Nếu ứng dụng không cung cấp biểu ngữ, hệ thống sẽ sử dụng hình ảnh của ứng dụng truyền hình mặc định.
  • Đối với biểu tượng huy hiệu (COLUMN_APP_LINK_ICON_URI), hệ thống sử dụng một huy hiệu cho biết tên ứng dụng. Nếu hệ thống cũng đang sử dụng biểu ngữ ứng dụng hoặc hình ảnh ứng dụng mặc định cho hình ảnh áp phích, thì sẽ không có huy hiệu ứng dụng nào xuất hiện.

Bạn chỉ định thông tin chi tiết về đường liên kết ứng dụng cho các kênh của mình trong hoạt động thiết lập của ứng dụng. Bạn có thể cập nhật thông tin chi tiết về đường liên kết ứng dụng này bất cứ lúc nào. Vì vậy, nếu một đường liên kết ứng dụng cần khớp với các thay đổi về kênh, hãy cập nhật thông tin chi tiết về đường liên kết ứng dụng và gọi ContentResolver.update() nếu cần. Để biết thêm thông tin chi tiết về cách cập nhật dữ liệu kênh, hãy xem bài viết Cập nhật dữ liệu kênh.