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) í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, có cân nhắc quy mô của bản cập nhật và luồng xử lý xử lý vấn đề đó. Ngoài ra, bạn có thể cung cấp đường liên kết ứng dụng cho các kênh giúp hướng dẫn người dùng đến nội dung và hoạt động có liên quan. Bài học 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ủa mình để cân nhắc đến những điều này.

Dùng thử Ứng dụng mẫu TV Input Service.

Xin cấp quyền

Để đầu vào TV hoạt động với dữ liệu EPG, đầu vào đó phải khai báo quyền ghi vào 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 Android TV duy trì các bản ghi dữ liệu kênh cho đầu vào TV. Trong phần thiết lập đối với mỗi kênh, bạn phải liên kết dữ liệu kênh với các trường sau đây Lớp TvContract.Channels:

Mặc dù khung đầu vào TV đủ tổng quát để xử lý cả phát sóng truyền thống và nội dung trực tiếp qua Internet (OTT) mà không có bất kỳ sự phân biệt nào, bạn có thể muốn xác định các cột sau trong ngoài những tiêu chí nêu 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 hãy cập nhật một số trường bổ sung. Để biết thêm thông tin về trường liên kết ứng dụng, hãy xem Thêm thông tin về đường liên kết ứng dụng.

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

Lấy siêu dữ liệu kênh (ở định dạng XML, JSON hoặc bất kỳ định dạng nào khác) từ máy chủ phụ trợ và trong phần thiết lập hoạt động ánh xạ các giá trị vào 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 về kênh và chương trình phù hợp với người trình bày thông tin về kênh và chương trình, 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 TV 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 cùng một thông tin thông qua hướng dẫn chương trình, bao gồm ảnh áp phích, như minh hoạ trong hình 2.

Hình 2. Hướng dẫn chương trình ứng dụng TV 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 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 ý: Thêm dữ liệu kênh vào ContentProvider có thể tốn thời gian. Thêm các chương trình hiện tại (các chương trình trong vòng hai 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 các thông tin còn lại của dữ liệu kênh trong nền. Xem Ví dụ: ứng dụng mẫu Android TV Live TV.

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

Khi cập nhật cơ sở dữ liệu hệ thống với một lượng lớn dữ liệu kênh, hãy sử dụng ContentResolver applyBatch() hoặc bulkInsert() . Dưới đây là 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 đối 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, không chặn luồng giao diện người dùng. Sử dụng AsyncTask là một để thực hiệ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 thường xuyên cập nhật dữ liệu EPG, 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ó một số kỹ thuật khác để tách riêng tác vụ cập nhật dữ liệu khỏi luồng giao diện người dùng: sử dụng tham số HandlerThread hoặc bạn có thể triển khai lớp của riêng mình bằng cách sử dụng LooperHandler lớp. Xem Các quy trình và luồng để biết thêm thông tin.

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 khởi chạy một ứng dụng hoạt động trong lúc xem nội dung kênh. Hoạt động sử dụng ứng dụng của kênh đường liên kết ứng dụng để tă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 liên quan hoặc nội dung bổ sung. Ví dụ: bạn có thể sử dụng đường liên kết đến ứng dụng để làm như 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 đang xem nội dung nhiều tập, hãy bắt đầu xem tập tiếp theo trong loạt phim.
  • Cho phép người dùng tương tác với nội dung, chẳng hạn như xếp hạng hoặc đánh giá nội dung mà không làm gián đoạn quá trình phát nội dung.

Đường liên kết đến ứng dụng sẽ hiển thị khi người dùng nhấn vào Select (Chọn) để hiển thị Trình đơn TV trong khi xem nội dung kênh.

Hình 1. Ví dụ về đường liên kết đến ứng dụng được hiển thị trên hàng Channels (Kênh) khi nội dung kênh được 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 trên kênh sẽ 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 kênh bằng cách nhấn 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 kênh. Cách cung cấp thông tin về đường liên kết đến ứng dụng: hãy chỉ định các thông tin chi tiết sau trong Trường TvContract.Channels:

  • COLUMN_APP_LINK_COLOR – màu nhấn của liên kết ứng dụng cho kênh này. Ví dụ về màu nhấn, 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 của kênh này. Đối với ví dụ về biểu tượng huy hiệu ứng dụng, xem hình 2, chú thích 2.
  • COLUMN_APP_LINK_INTENT_URI – URI ý định của đường liên kết đến ứng dụng cho kênh này. Bạn có thể tạo URI sử dụng toUri(int) với URI_INTENT_SCHEME và chuyển đổi URI trở lại ý định ban đầu bằng parseUri().
  • COLUMN_APP_LINK_POSTER_ART_URI – URI cho ảnh áp phích được dùng làm nền của đường liên kết đến ứng dụng cho kênh này. Để xem ví dụ về hình ảnh áp phích, vui lòng xem hình 2, chú thích 1.
  • COLUMN_APP_LINK_TEXT – Văn bản mô tả của đường liên kết đến ứng dụng cho kênh này. Ví dụ nội dung mô tả về đường liên kết đến ứng dụng, 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 mặc định đến ứng dụng. Hệ thống sẽ 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 ACTION_MAIN hoạt động 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, một đường liên kết đến ứ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ử dụng "Mở app-name". Nếu không xác định URI ý định của đường liên kết ứng dụng khả thi, hệ thống sẽ sử dụng "Không có liên kết nào".
  • Dành cho 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ẽ 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 ứng dụng TV mặc định.
  • Đối với biểu tượng huy hiệu (COLUMN_APP_LINK_ICON_URI), hệ thống sử dụng huy hiệu hiển thị 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, sẽ không có huy hiệu ứng dụng nào hiển thị.

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