Opracowanie usługi wejścia TV

Usługa wejścia TV stanowi źródło strumienia multimediów i umożliwia prezentowanie treści multimedialnych w liniowy sposób nadawania telewizji jako kanały i programy. Dzięki usłudze wejścia TV możesz zapewnić kontrolę rodzicielską, informacje z przewodnika po programach i oceny treści. Usługa wejścia TV współpracuje z aplikacją systemową Android TV. Ta aplikacja steruje i wyświetla treści z kanału na telewizorze. Aplikacja systemowa TV została opracowana specjalnie dla urządzenia i niezmienna przez aplikacje innych firm. Więcej informacji o architekturze platformy wejściowej TV (TIF) i jej składnikach znajdziesz w artykule na temat platformy wejścia TV.

Tworzenie usługi wejścia TV za pomocą biblioteki towarzyszącej TIF

Biblioteka towarzysząca TIF to platforma, która umożliwia rozszerzalne implementacje popularnych funkcji usług wprowadzania danych telewizyjnych. Powinna być używana przez producentów OEM przy tworzeniu kanałów wyłącznie dla Androida od 5.0 (poziom interfejsu API 21) do 7.1 (poziom API 25).

Aktualizowanie projektu

Biblioteka towarzysząca TIF jest dostępna do użytku starszego typu przez producentów OEM w repozytorium androidtv-sample-inputs. W tym repozytorium znajdziesz przykład dodania biblioteki do aplikacji.

Zadeklarowanie usługi wejścia TV w pliku manifestu

Twoja aplikacja musi udostępniać usługę zgodną z TvInputService, z której system uzyskuje dostęp do aplikacji. Biblioteka towarzysząca TIF udostępnia klasę BaseTvInputService stanowiącą domyślną implementację tagu TvInputService, którą możesz dostosować. Utwórz podklasę klasy BaseTvInputService i zadeklaruj ją w pliku manifestu jako usługę.

W deklaracji w pliku manifestu określ uprawnienie BIND_TV_INPUT, aby umożliwić usłudze połączenie wejścia TV z systemem. Usługa systemowa wykonuje powiązanie i ma uprawnienie BIND_TV_INPUT. Aplikacja systemowa TV wysyła żądania do usług wprowadzania danych TV przez interfejs TvInputManager.

W deklaracji usługi umieść filtr intencji, który wskazuje TvInputService jako działanie, które ma zostać wykonane z zamiarem. Zadeklaruj także metadane usługi jako oddzielny zasób XML. Deklaracja usługi, filtr intencji i deklaracja metadanych usługi znajdują się w tym przykładzie:

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

Zdefiniuj metadane usługi w osobnym pliku XML. Plik XML metadanych usługi musi zawierać interfejs konfiguracji, który opisuje początkową konfigurację wejścia TV i skanowanie kanałów. Plik metadanych powinien też zawierać flagę informującą, czy użytkownicy mogą nagrywać treści. Więcej informacji na temat nagrywania treści w aplikacji znajdziesz w artykule Obsługa nagrywania treści.

Plik metadanych usługi znajduje się w katalogu zasobów XML aplikacji i musi odpowiadać nazwie zasobu zadeklarowanego w pliku manifestu. Korzystając z wpisów w pliku manifestu z poprzedniego przykładu, utwórz plik XML pod adresem res/xml/richtvinputservice.xml z następującą treścią:

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

Zdefiniuj kanały i utwórz aktywność związaną z konfiguracją

Usługa wejścia TV musi zdefiniować co najmniej 1 kanał, do którego użytkownicy mają dostęp przez systemową aplikację TV. Rejestrowanie kanałów w bazie danych systemu i udostępnianie działania konfiguracji, które jest wywoływane przez system, gdy nie może znaleźć kanału dla aplikacji.

Najpierw włącz w aplikacji odczyt i zapis w systemie Electronic Programming Guide (EPG), którego dane obejmują kanały i programy dostępne dla użytkownika. Aby umożliwić aplikacji wykonywanie tych działań i synchronizowanie ich z EPG po ponownym uruchomieniu urządzenia, dodaj do manifestu aplikacji te elementy:

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

Dodaj ten element, aby mieć pewność, że Twoja aplikacja pojawi się w Sklepie Google Play jako aplikacja udostępniająca kanały treści na Androidzie TV:

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

Następnie utwórz klasę, która rozszerza klasę EpgSyncJobService. Ta klasa abstrakcyjna ułatwia tworzenie usługi zadań, która tworzy i aktualizuje kanały w bazie danych systemu.

W podklasie utwórz i zwróć pełną listę kanałów w zadaniu getChannels(). Jeśli Twoje kanały pochodzą z pliku XMLTV, użyj klasy XmlTvParser. W przeciwnym razie generuj kanały automatycznie za pomocą klasy Channel.Builder.

W przypadku każdego kanału system wywołuje funkcję getProgramsForChannel(), gdy potrzebuje listy programów, które można wyświetlić w danym przedziale czasu na kanale. Zwraca listę obiektów Program kanału. Użyj klasy XmlTvParser, aby uzyskać programy z pliku XMLTV lub wygeneruj je automatycznie za pomocą klasy Program.Builder.

W przypadku każdego obiektu Program użyj obiektu InternalProviderData, aby ustawić informacje o programie, takie jak typ filmu w programie. Jeśli masz ograniczoną liczbę programów i chcesz, aby kanał powtarzał w pętli, użyj metody InternalProviderData.setRepeatable() z wartością true podczas ustawiania informacji o programie.

Po zaimplementowaniu usługi zadań dodaj ją do pliku manifestu aplikacji:

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

Na koniec utwórz aktywność związaną z konfiguracją. Konfiguracja powinna umożliwiać synchronizację danych kanału i programu. Użytkownik może to zrobić na przykład za pomocą interfejsu użytkownika aktywności. Aplikacja może też robić to automatycznie po rozpoczęciu aktywności. Gdy aktywność związana z konfiguracją musi synchronizować informacje o kanale i programie, aplikacja powinna uruchomić usługę zadania:

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

Aby zsynchronizować usługę zadań, użyj metody requestImmediateSync(). Użytkownik musi poczekać na zakończenie synchronizacji, więc okres żądania powinien być stosunkowo krótki.

Użyj metody setUpPeriodicSync(), aby usługa zadań okresowo synchronizowała dane kanału i programu w tle:

Kotlin

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

Java

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

Biblioteka towarzysząca TIF udostępnia dodatkową, przeciążoną metodę requestImmediateSync(), która umożliwia określenie w milisekundach czasu trwania synchronizacji danych kanału. Domyślna metoda synchronizuje dane kanału z jednej godziny.

Biblioteka towarzysząca TIF udostępnia też dodatkową, przeciążoną metodę setUpPeriodicSync(), która pozwala określić czas trwania synchronizacji danych kanału oraz częstotliwość synchronizacji. Metoda domyślna synchronizuje dane kanału z 48 godzin co 12 godzin.

Więcej informacji na temat danych kanału i EPG znajdziesz w artykule Praca z danymi kanału.

Obsługa żądań dostrajania i odtwarzania multimediów

Gdy użytkownik wybiera konkretny kanał, aplikacja systemowa TV korzysta z utworzonego przez Twoją aplikację elementu Session, który dostraja się do tego kanału i odtwarza treści. Biblioteka towarzysząca TIF zawiera kilka zajęć, które możesz rozszerzyć o obsługę wywołań kanałów i sesji z systemu.

Klasa podrzędna BaseTvInputService tworzy sesje, które obsługują żądania dostrajania. Zastąp metodę onCreateSession(), utwórz sesję przedłużoną z klasy BaseTvInputService.Session i wywołaj super.sessionCreated() swoją nową sesją. W tym przykładzie onCreateSession() zwraca obiekt RichTvInputSessionImpl, który rozszerza zakres 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;
}

Gdy użytkownik rozpocznie oglądanie jednego z Twoich kanałów za pomocą systemowej aplikacji TV, system wywoła metodę onPlayChannel() Twojej sesji. Zignoruj tę metodę, jeśli musisz zainicjować specjalne zainicjowanie kanału przed rozpoczęciem odtwarzania programu.

System pobiera obecnie zaplanowany program i wywołuje metodę onPlayProgram() sesji, podając informacje o programie oraz czas rozpoczęcia w milisekundach. Aby rozpocząć odtwarzanie programu, użyj interfejsu TvPlayer.

W kodzie odtwarzacza multimediów powinien być implementowany obiekt TvPlayer do obsługi określonych zdarzeń odtwarzania. Klasa TvPlayer obsługuje takie funkcje jak elementy sterujące przesuwaniem w czasie, nie zwiększając złożoności implementacji BaseTvInputService.

W metodzie getTvPlayer() sesji zwróć odtwarzacz, który korzysta z metody TvPlayer. Przykładowa usługa wprowadzania danych TV zawiera odtwarzacz, który korzysta z ExoPlayer.

Utwórz usługę wejścia TV za pomocą schematu wejścia TV

Jeśli usługa wejścia TV nie obsługuje biblioteki towarzyszącej TIF, musisz zaimplementować te komponenty:

  • TvInputService zapewnia długotrwałe działanie i dostępność wejścia TV w tle
  • TvInputService.Session utrzymuje stan wejścia TV i komunikuje się z aplikacją hostującą
  • TvContract opisuje kanały i programy dostępne dla wejścia TV
  • TvContract.Channels reprezentuje informacje o kanale telewizyjnym
  • TvContract.Programs opisuje program telewizyjny za pomocą takich danych jak tytuł i godzina rozpoczęcia
  • TvTrackInfo reprezentuje ścieżkę audio, wideo lub napisy
  • TvContentRating opisuje ocenę treści i umożliwia stosowanie niestandardowych schematów oceny treści.
  • TvInputManager udostępnia interfejs API dla systemowej aplikacji TV i zarządza interakcją z wejściami i aplikacjami telewizora

Musisz też wykonać te czynności:

  1. Zadeklaruj w pliku manifestu usługę wejścia TV zgodnie z opisem w sekcji Deklarowanie usługi wejścia TV w pliku manifestu.
  2. Utwórz plik metadanych usługi.
  3. Utwórz i zarejestruj informacje o kanale i programie.
  4. Utwórz aktywność związaną z konfiguracją.

Określ usługę wejścia TV

W przypadku swojej usługi wydłużasz zajęcia TvInputService. Implementacja TvInputService to powiązana usługa, w której usługą systemową jest powiązany z nią klient. Metody cyklu życia usługi, które należy wdrożyć, zostały pokazane na ilustracji 1.

Metoda onCreate() inicjuje i uruchamia HandlerThread, który udostępnia wątek procesu niezależny od wątku UI do obsługi działań zależnych od systemu. W poniższym przykładzie metoda onCreate() inicjuje CaptioningManager i przygotowuje się do obsługi działań ACTION_BLOCKED_RATINGS_CHANGED oraz ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED. Te działania opisują intencje systemowe wywoływane, gdy użytkownik zmienia ustawienia kontroli rodzicielskiej oraz gdy następuje zmiana listy zablokowanych ocen.

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

Rysunek 1. Cykl życia usługi TvInputService.

Więcej informacji o pracy z zablokowanymi treściami i zapewnianiu kontroli rodzicielskiej znajdziesz w artykule Kontrola nad treściami. Więcej informacji o działaniach systemowych, które możesz wykorzystać w usłudze wejściowej telewizora, znajdziesz w sekcji TvInputManager.

TvInputService tworzy tag TvInputService.Session, który implementuje Handler.Callback do obsługi zmian stanu odtwarzacza. W przypadku onSetSurface() właściwość TvInputService.Session ustawia treść wideo na potrzeby elementu Surface. Więcej informacji o renderowaniu filmów za pomocą komponentu Surface znajdziesz w artykule Integrowanie odtwarzacza z platformą.

Gdy użytkownik wybierze kanał, TvInputService.Session obsługuje zdarzenie onTune(), a także powiadamia aplikację systemową na telewizory o zmianach w treściach i metadanych treści. Te metody notify() zostały opisane w sekcjach Kontrolowanie treści i Wybór ścieżki w dalszej części tego szkolenia.

Zdefiniuj aktywność związaną z konfiguracją

Aplikacja systemowa TV obsługuje zdefiniowane przez Ciebie działanie związane z konfiguracją wejścia TV. Działanie konfiguracyjne jest wymagane i musi zapewnić co najmniej jeden rekord kanału dla bazy danych systemu. Aplikacja systemowa TV wywołuje działanie konfiguracyjne, gdy nie może znaleźć kanału dla wejścia TV.

Ćwiczenie związane z konfiguracją opisuje systemową aplikację TV kanały udostępnione przez wejście na telewizor, co pokazano w następnej lekcji: Tworzenie i aktualizowanie danych kanału.

Dodatkowe materiały referencyjne