Phát triển dịch vụ đầu vào TV

Dịch vụ đầu vào TV đại diện cho nguồn luồng nội dung nghe nhìn và cho phép bạn trình bày nội dung nghe nhìn theo kiểu truyền hình truyền hình tuyến tính ở dạng kênh và chương trình. Với dịch vụ đầu vào TV, bạn có thể cung cấp chế độ kiểm soát của cha mẹ, thông tin hướng dẫn về chương trình và mức phân loại nội dung. Dịch vụ đầu vào TV hoạt động với ứng dụng TV trên hệ thống Android. Cuối cùng, ứng dụng này sẽ điều khiển và hiển thị nội dung kênh trên TV. Ứng dụng TV hệ thống được phát triển riêng cho thiết bị này và các ứng dụng bên thứ ba không thể thay đổi. Để biết thêm thông tin về cấu trúc Khung đầu vào TV (TIF) và các thành phần của cấu trúc đó, hãy xem Khung đầu vào của TV.

Tạo dịch vụ đầu vào TV bằng Thư viện đồng hành TIF

Thư viện đồng hành TIF là một khung cung cấp các cách triển khai có thể mở rộng cho các tính năng phổ biến của dịch vụ đầu vào TV. API này chỉ dành cho OEM (Nhà sản xuất thiết bị gốc) sử dụng để xây dựng kênh cho Android 5.0 (API cấp 21) đến Android 7.1 (API cấp 25).

Cập nhật dự án

Các OEM (Nhà sản xuất thiết bị gốc) hiện đã có thể sử dụng Thư viện đồng hành TIF TIF trong kho lưu trữ androidtv-sample-inputs. Hãy xem kho lưu trữ đó để biết ví dụ về cách đưa thư viện vào một ứng dụng.

Khai báo dịch vụ đầu vào TV trong tệp kê khai

Ứng dụng của bạn phải cung cấp một dịch vụ tương thích với TvInputService mà hệ thống dùng để truy cập vào ứng dụng. Thư viện đồng hành TIF cung cấp lớp BaseTvInputService, cung cấp phương thức triển khai mặc định của TvInputService mà bạn có thể tuỳ chỉnh. Tạo lớp con của BaseTvInputService và khai báo lớp con trong tệp kê khai là dịch vụ.

Trong phần khai báo tệp kê khai, hãy chỉ định quyền BIND_TV_INPUT để cho phép dịch vụ kết nối đầu vào TV với hệ thống. Dịch vụ hệ thống sẽ thực hiện liên kết và có quyền BIND_TV_INPUT. Ứng dụng TV hệ thống gửi yêu cầu đến dịch vụ đầu vào TV thông qua giao diện TvInputManager.

Trong phần khai báo dịch vụ, hãy thêm một bộ lọc ý định chỉ định TvInputService làm hành động cần thực hiện với ý định. Đồng thời, hãy khai báo siêu dữ liệu dịch vụ dưới dạng một tài nguyên XML riêng. Phần khai báo dịch vụ, bộ lọc ý định và nội dung khai báo siêu dữ liệu dịch vụ được thể hiện trong ví dụ sau:

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

Hãy xác định siêu dữ liệu dịch vụ trong một tệp XML riêng. Tệp XML siêu dữ liệu dịch vụ phải bao gồm giao diện thiết lập mô tả cấu hình ban đầu và quét kênh của đầu vào TV. Tệp siêu dữ liệu cũng phải chứa cờ cho biết liệu người dùng có thể ghi nội dung hay không. Để biết thêm thông tin về cách hỗ trợ ghi nội dung trong ứng dụng của bạn, hãy xem bài viết Hỗ trợ ghi nội dung.

Tệp siêu dữ liệu dịch vụ nằm trong thư mục tài nguyên XML của ứng dụng và phải khớp với tên của tài nguyên bạn đã khai báo trong tệp kê khai. Sử dụng các mục nhập tệp kê khai từ ví dụ trước, bạn sẽ tạo tệp XML tại res/xml/richtvinputservice.xml với nội dung sau:

<?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" />

Xác định kênh và tạo hoạt động thiết lập

Dịch vụ đầu vào TV của bạn phải xác định ít nhất một kênh mà người dùng truy cập thông qua ứng dụng TV hệ thống. Bạn nên đăng ký các kênh trong cơ sở dữ liệu hệ thống và cung cấp hoạt động thiết lập mà hệ thống gọi khi không thể tìm thấy kênh cho ứng dụng của bạn.

Trước tiên, hãy cho phép ứng dụng đọc và ghi vào Hướng dẫn lập trình điện tử (EPG) của hệ thống. Hướng dẫn này có dữ liệu bao gồm các kênh và chương trình có sẵn cho người dùng. Để cho phép ứng dụng thực hiện những thao tác này và đồng bộ hoá với EPG sau khi khởi động lại thiết bị, hãy thêm các phần tử sau vào tệp kê khai ứng dụng:

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

Hãy thêm phần tử sau đây để đảm bảo rằng ứng dụng của bạn xuất hiện trong Cửa hàng Google Play dưới dạng một ứng dụng cung cấp kênh nội dung trong Android TV:

<uses-feature
    android:name="android.software.live_tv"
    android:required="true" />

Tiếp theo, hãy tạo một lớp mở rộng lớp EpgSyncJobService. Lớp trừu tượng này giúp bạn dễ dàng tạo dịch vụ công việc để tạo và cập nhật các kênh trong cơ sở dữ liệu hệ thống.

Trong lớp con, hãy tạo và trả về danh sách đầy đủ các kênh trong getChannels(). Nếu kênh của bạn đến từ một tệp XMLTV, hãy dùng lớp XmlTvParser. Nếu không, hãy tạo kênh theo phương thức lập trình bằng cách sử dụng lớp Channel.Builder.

Đối với mỗi kênh, hệ thống sẽ gọi getProgramsForChannel() khi cần một danh sách các chương trình có thể xem trong một khoảng thời gian nhất định trên kênh. Trả về danh sách các đối tượng Program cho kênh. Sử dụng lớp XmlTvParser để lấy các chương trình từ tệp XMLTV hoặc tạo các chương trình đó theo phương thức lập trình bằng cách sử dụng lớp Program.Builder.

Đối với mỗi đối tượng Program, hãy sử dụng đối tượng InternalProviderData để đặt thông tin chương trình, chẳng hạn như loại video của chương trình. Nếu bạn chỉ có một số chương trình mà bạn muốn kênh lặp lại trong một vòng lặp, hãy sử dụng phương thức InternalProviderData.setRepeatable() có giá trị true khi đặt thông tin về chương trình của bạn.

Sau khi bạn triển khai dịch vụ công việc, hãy thêm dịch vụ đó vào tệp kê khai ứng dụng:

<service
    android:name=".sync.SampleJobService"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:exported="true" />

Cuối cùng, hãy tạo một hoạt động thiết lập. Hoạt động thiết lập của bạn sẽ cung cấp cách thức để đồng bộ hoá dữ liệu kênh và chương trình. Một cách để thực hiện việc này là người dùng thực hiện việc đó thông qua giao diện người dùng trong hoạt động. Bạn cũng có thể yêu cầu ứng dụng tự động thực hiện việc này khi hoạt động bắt đầu. Khi hoạt động thiết lập cần đồng bộ hoá thông tin kênh và chương trình, ứng dụng sẽ bắt đầu dịch vụ công việc:

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

Sử dụng phương thức requestImmediateSync() để đồng bộ hóa dịch vụ công việc. Người dùng phải đợi quá trình đồng bộ hoá hoàn tất, vì vậy, bạn nên duy trì thời gian yêu cầu tương đối ngắn.

Sử dụng phương thức setUpPeriodicSync() để dịch vụ công việc đồng bộ hoá định kỳ kênh và dữ liệu chương trình trong nền:

Kotlin

EpgSyncJobService.setUpPeriodicSync(
        context,
        inputId,
        ComponentName(context, SampleJobService::class.java)
)

Java

EpgSyncJobService.setUpPeriodicSync(context, inputId,
        new ComponentName(context, SampleJobService.class));

Thư viện đồng hành TIF cung cấp thêm một phương thức nạp chồng của requestImmediateSync() cho phép bạn chỉ định thời lượng dữ liệu kênh cần đồng bộ hoá tính bằng mili giây. Phương thức mặc định sẽ đồng bộ hoá dữ liệu kênh trong 1 giờ.

Thư viện đồng hành TIF cũng cung cấp một phương thức nạp chồng bổ sung của setUpPeriodicSync() cho phép bạn chỉ định thời lượng dữ liệu kênh cần đồng bộ hoá và tần suất diễn ra quá trình đồng bộ hoá định kỳ. Phương thức mặc định sẽ đồng bộ hoá dữ liệu kênh trong 48 giờ mỗi 12 giờ.

Để biết thêm thông tin chi tiết về dữ liệu kênh và EPG, hãy xem phần Làm việc với dữ liệu kênh.

Xử lý các yêu cầu điều chỉnh và phát nội dung nghe nhìn

Khi người dùng chọn một kênh cụ thể, ứng dụng TV hệ thống sẽ dùng một Session do ứng dụng của bạn tạo để chuyển sang kênh được yêu cầu và phát nội dung. Thư viện đồng hành TIF cung cấp một số lớp mà bạn có thể mở rộng để xử lý các lệnh gọi kênh và phiên từ hệ thống.

Lớp con BaseTvInputService của bạn tạo các phiên xử lý các yêu cầu điều chỉnh. Ghi đè phương thức onCreateSession(), tạo một phiên mở rộng từ lớp BaseTvInputService.Session và gọi super.sessionCreated() bằng phiên mới của bạn. Trong ví dụ sau, onCreateSession() trả về đối tượng RichTvInputSessionImpl mở rộng BaseTvInputService.Session:

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

Khi người dùng dùng ứng dụng TV hệ thống để bắt đầu xem một trong các kênh của bạn, hệ thống sẽ gọi phương thức onPlayChannel() trong phiên của bạn. Hãy ghi đè phương thức này nếu bạn cần thực hiện bất kỳ thao tác khởi tạo kênh đặc biệt nào trước khi chương trình bắt đầu phát.

Sau đó, hệ thống sẽ lấy chương trình đang được lên lịch và gọi phương thức onPlayProgram() của phiên hoạt động, chỉ định thông tin chương trình và thời gian bắt đầu tính bằng mili giây. Sử dụng giao diện TvPlayer để bắt đầu chơi chương trình.

Mã trình phát nội dung đa phương tiện của bạn phải triển khai TvPlayer để xử lý các sự kiện phát cụ thể. Lớp TvPlayer xử lý các tính năng như các chế độ điều khiển dịch chuyển thời gian mà không làm phức tạp hoá quá trình triển khai BaseTvInputService.

Trong phương thức getTvPlayer() của phiên, hãy trả về trình phát nội dung đa phương tiện giúp triển khai TvPlayer. Ứng dụng mẫu Dịch vụ đầu vào TV triển khai một trình phát nội dung đa phương tiện sử dụng ExoPlayer.

Tạo dịch vụ đầu vào TV bằng khung đầu vào TV

Nếu dịch vụ đầu vào TV của bạn không thể sử dụng Thư viện đồng hành TIF, bạn cần triển khai các thành phần sau:

  • TvInputService cung cấp tính năng hoạt động trong thời gian dài và khả năng sử dụng ở chế độ nền cho đầu vào TV
  • TvInputService.Session duy trì trạng thái đầu vào TV và giao tiếp với ứng dụng lưu trữ
  • TvContract mô tả các kênh và chương trình có sẵn cho đầu vào TV
  • TvContract.Channels biểu thị thông tin về một kênh TV
  • TvContract.Programs mô tả một chương trình truyền hình có dữ liệu như tên chương trình và thời gian bắt đầu
  • TvTrackInfo biểu thị một âm thanh, video hoặc bản phụ đề
  • TvContentRating mô tả mức phân loại nội dung, cho phép lược đồ phân loại nội dung tùy chỉnh
  • TvInputManager cung cấp một API cho ứng dụng TV hệ thống và quản lý hoạt động tương tác với đầu vào và ứng dụng của TV

Bạn cũng cần thực hiện các việc sau:

  1. Khai báo dịch vụ đầu vào TV trong tệp kê khai, như mô tả trong phần Khai báo dịch vụ đầu vào TV của bạn trong tệp kê khai.
  2. Tạo tệp siêu dữ liệu của dịch vụ.
  3. Tạo và đăng ký thông tin kênh cũng như chương trình của bạn.
  4. Tạo hoạt động thiết lập.

Xác định dịch vụ đầu vào TV của bạn

Đối với dịch vụ của mình, bạn mở rộng lớp TvInputService. Quá trình triển khai TvInputService là một dịch vụ ràng buộc, trong đó dịch vụ hệ thống là ứng dụng liên kết với dịch vụ đó. Các phương thức vòng đời của dịch vụ mà bạn cần triển khai được minh hoạ trong hình 1.

Phương thức onCreate() khởi chạy và khởi động HandlerThread, cung cấp một luồng quy trình tách biệt với luồng giao diện người dùng để xử lý các thao tác do hệ thống điều khiển. Trong ví dụ sau, phương thức onCreate() sẽ khởi chạy CaptioningManager và chuẩn bị xử lý các thao tác ACTION_BLOCKED_RATINGS_CHANGEDACTION_PARENTAL_CONTROLS_ENABLED_CHANGED. Những thao tác này mô tả ý định của hệ thống được kích hoạt khi người dùng thay đổi chế độ cài đặt quyền kiểm soát của cha mẹ và khi có thay đổi trong danh sách điểm xếp hạng bị chặn.

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

Hình 1. Vòng đời của TvInputService.

Xem phần Kiểm soát nội dung để biết thêm thông tin về cách xử lý nội dung bị chặn và cung cấp quyền kiểm soát của cha mẹ. Hãy xem TvInputManager để biết thêm các thao tác do hệ thống điều khiển mà bạn có thể muốn xử lý trong dịch vụ đầu vào TV của mình.

TvInputService tạo một TvInputService.Session triển khai Handler.Callback để xử lý các thay đổi về trạng thái người chơi. Với onSetSurface(), TvInputService.Session sẽ đặt Surface với nội dung video. Vui lòng xem phần Tích hợp trình phát với nền tảng để biết thêm thông tin về cách làm việc với Surface để kết xuất video.

TvInputService.Session xử lý sự kiện onTune() khi người dùng chọn một kênh và thông báo cho ứng dụng TV của hệ thống về những thay đổi về nội dung và siêu dữ liệu. Các phương thức notify() này được mô tả trong phần Kiểm soát nội dungXử lý việc lựa chọn kênh trong chương trình đào tạo này.

Xác định hoạt động thiết lập

Ứng dụng TV hệ thống hoạt động với hoạt động thiết lập mà bạn xác định cho đầu vào TV của mình. Hoạt động thiết lập là bắt buộc và phải cung cấp ít nhất một bản ghi kênh cho cơ sở dữ liệu hệ thống. Ứng dụng TV hệ thống sẽ gọi hoạt động thiết lập khi không tìm thấy kênh cho đầu vào TV.

Hoạt động thiết lập sẽ mô tả cho ứng dụng TV hệ thống các kênh được cung cấp thông qua đầu vào TV, như minh hoạ trong bài học tiếp theo, Tạo và cập nhật dữ liệu kênh.

Tài liệu tham khảo bổ sung