TV-Eingabedienst entwickeln

Ein TV-Eingabedienst stellt eine Media-Stream-Quelle dar und ermöglicht es dir, deine Media-Inhalte auf lineare, broadcastähnliche Weise als Channels und Programme zu präsentieren. Mit einem TV-Eingabedienst kannst du Jugendschutzeinstellungen, Programmübersichten und Altersfreigaben bereitstellen. Der TV-Eingabedienst funktioniert mit der TV App des Android-Systems. Diese App steuert und präsentiert letztendlich die Channel-Inhalte auf dem Fernseher. Die System-TV-App wurde speziell für das Gerät entwickelt und kann von Drittanbieter-Apps nicht geändert werden. Weitere Informationen zur Architektur des TV Input Framework (TIF) und seinen Komponenten findest du unter TV Input Framework.

TV-Eingabedienst mit der TIF Companion Library erstellen

Die TIF Companion Library ist ein Framework, das erweiterbare Implementierungen gängiger Funktionen von TV-Eingabediensten bietet. Sie ist nur für OEMs gedacht, um Channels für Android 5.0 (API-Level 21) bis Android 7.1 (API-Level 25) zu erstellen.

Projekt aktualisieren

Die TIF Companion Library ist für die Legacy-Verwendung durch OEMs im Repository androidtv-sample-inputs verfügbar. Dort findest du ein Beispiel dafür, wie du die Bibliothek in eine App einbindest.

TV-Eingabedienst im Manifest deklarieren

Deine App muss einen TvInputService-kompatiblen Dienst bereitstellen, über den das System auf deine App zugreift. Die TIF Companion Library bietet die Klasse BaseTvInputService, die eine Standardimplementierung von TvInputService enthält, die du anpassen kannst. Erstelle eine Unterklasse von BaseTvInputService und deklariere die Unterklasse in deinem Manifest als Dienst.

Gib in der Manifestdeklaration die Berechtigung BIND_TV_INPUT an, damit der Dienst den TV-Eingang mit dem System verbinden kann. Ein Systemdienst führt die Bindung aus und hat die Berechtigung BIND_TV_INPUT. Die System-TV-App sendet Anfragen über die TvInputManager-Schnittstelle an TV-Eingabedienste.

Füge in deiner Dienstdeklaration einen Intent-Filter ein, der TvInputService als Aktion angibt, die mit dem Intent ausgeführt werden soll. Deklariere außerdem die Dienstmetadaten als separate XML-Ressource. Die Dienstdeklaration, der Intent-Filter und die Dienstmetadatendeklaration sind im folgenden Beispiel dargestellt:

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

Definiere die Dienstmetadaten in einer separaten XML-Datei. Die XML-Datei mit den Dienstmetadaten muss eine Einrichtungsschnittstelle enthalten, die die Erstkonfiguration und die Channel-Suche des TV-Eingangs beschreibt. Die Metadatendatei sollte auch ein Flag enthalten, das angibt, ob Nutzer Inhalte aufzeichnen können. Weitere Informationen zum Unterstützen der Aufzeichnung von Inhalten in deiner App findest du unter Aufzeichnung von Inhalten unterstützen.

Die Dienstmetadatendatei befindet sich im XML-Ressourcenverzeichnis deiner App und muss mit dem Namen der Ressource übereinstimmen, die du im Manifest deklariert hast. Wenn du die Manifesteinträge aus dem vorherigen Beispiel verwendest, erstellst du die XML-Datei unter res/xml/richtvinputservice.xml mit folgendem Inhalt:

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

Channels definieren und Einrichtungsaktivität erstellen

Dein TV-Eingabedienst muss mindestens einen Channel definieren, auf den Nutzer über die System-TV-App zugreifen können. Du solltest deine Channels in der Systemdatenbank registrieren und eine Einrichtungsaktivität bereitstellen, die das System aufruft, wenn es keinen Channel für deine App findet.

Aktiviere zuerst, dass deine App Daten aus dem elektronischen Programmführer (EPG) des Systems lesen und in ihn schreiben kann. Die Daten umfassen Channels und Programme, die dem Nutzer zur Verfügung stehen. Damit deine App diese Aktionen ausführen und nach einem Neustart des Geräts mit dem EPG synchronisiert werden kann, füge dem App-Manifest die folgenden Elemente hinzu:

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

Füge das folgende Element hinzu, damit deine App im Google Play Store als App angezeigt wird, die Content-Channels in Android TV bereitstellt:

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

Erstelle als Nächstes eine Klasse, die die Klasse EpgSyncJobService erweitert. Mit dieser abstrakten Klasse kannst du einen Jobdienst erstellen, der Channels in der Systemdatenbank erstellt und aktualisiert.

Erstelle in deiner abgeleiteten Klasse mit getChannels die vollständige Liste der Channels und gib sie zurück. Wenn deine Channels aus einer XMLTV-Datei stammen, verwende die Klasse XmlTvParser. Andernfalls generiere Channels programmatisch mit der Klasse Channel.Builder.

Für jeden Channel ruft das System getProgramsForChannel auf, wenn eine Liste von Programmen benötigt wird, die in einem bestimmten Zeitraum auf dem Channel angesehen werden können. Gib eine Liste von Program-Objekten für den Channel zurück. Verwende die Klasse XmlTvParser, um Programme aus einer XMLTV-Datei abzurufen, oder generiere sie programmatisch mit der Klasse Program.Builder.

Verwende für jedes Program-Objekt ein InternalProviderData-Objekt, um Programminformationen wie den Videotyp des Programms festzulegen. Wenn du nur eine begrenzte Anzahl von Programmen hast, die der Channel in einer Schleife wiederholen soll, verwende die Methode InternalProviderData.setRepeatable mit dem Wert true, wenn du Informationen zu deinem Programm festlegst.

Nachdem du den Jobdienst implementiert hast, füge ihn dem App-Manifest hinzu:

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

Erstelle schließlich eine Einrichtungsaktivität. Deine Einrichtungsaktivität sollte eine Möglichkeit zum Synchronisieren von Channel- und Programmdaten bieten. Eine Möglichkeit besteht darin, dass der Nutzer dies über die Benutzeroberfläche in der Aktivität tut. Du kannst die App auch automatisch synchronisieren lassen, wenn die Aktivität gestartet wird. Wenn die Einrichtungsaktivität Channel- und Programminformationen synchronisieren muss, sollte die App den Jobdienst starten:

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

Verwende die Methode requestImmediateSync, um den Jobdienst zu synchronisieren. Der Nutzer muss warten, bis die Synchronisierung abgeschlossen ist. Daher solltest du den Anfragezeitraum relativ kurz halten.

Verwende die Methode setUpPeriodicSync, damit der Jobdienst Channel- und Programmdaten regelmäßig im Hintergrund synchronisiert:

Kotlin

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

Java

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

Die TIF Companion Library bietet eine zusätzliche überladene Methode von requestImmediateSync, mit der du die Dauer der zu synchronisierenden Channel-Daten in Millisekunden angeben kannst. Die Standardmethode synchronisiert Channel-Daten für eine Stunde.

Die TIF Companion Library bietet auch eine zusätzliche überladene Methode von setUpPeriodicSync, mit der du die Dauer der zu synchronisierenden Channel-Daten und die Häufigkeit der regelmäßigen Synchronisierung angeben kannst. Die Standardmethode synchronisiert alle 12 Stunden Channel-Daten für 48 Stunden.

Weitere Informationen zu Channel-Daten und dem EPG findest du unter Mit Channel-Daten arbeiten.

Anfragen zum Abstimmen und zur Medienwiedergabe verarbeiten

Wenn ein Nutzer einen bestimmten Channel auswählt, verwendet die System-TV-App eine Session, die von deiner App erstellt wurde, um den angeforderten Channel abzustimmen und Inhalte abzuspielen. Die TIF Companion Library bietet mehrere Klassen, die du erweitern kannst, um Channel- und Sitzungsaufrufe vom System zu verarbeiten.

Deine BaseTvInputService-Unterklasse erstellt Sitzungen, die Anfragen zum Abstimmen verarbeiten. Überschreibe die Methode onCreateSession, erstelle eine Sitzung, die von der Klasse BaseTvInputService.Session abgeleitet ist, und rufe super.sessionCreated mit deiner neuen Sitzung auf. Im folgenden Beispiel gibt onCreateSession ein RichTvInputSessionImpl-Objekt zurück, das BaseTvInputService.Session erweitert:

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

Wenn der Nutzer die System-TV-App verwendet, um einen deiner Channels anzusehen, ruft das System die Methode onPlayChannel deiner Sitzung auf. Überschreibe diese Methode, wenn du vor Beginn der Wiedergabe des Programms eine spezielle Channel-Initialisierung durchführen musst.

Das System ruft dann das aktuell geplante Programm ab und ruft die Methode onPlayProgram deiner Sitzung auf, wobei die Programminformationen und die Startzeit in Millisekunden angegeben werden. Verwende die Schnittstelle TvPlayer, um die Wiedergabe des Programms zu starten.

Dein Mediaplayer-Code sollte TvPlayer implementieren, um bestimmte Wiedergabeereignisse zu verarbeiten. Die Klasse TvPlayer verarbeitet Funktionen wie die zeitversetzte Wiedergabe, ohne die Implementierung von BaseTvInputService zu verkomplizieren.

Gib in der Methode getTvPlayer deiner Sitzung deinen Mediaplayer zurück, der TvPlayer implementiert. Die Beispiel-App für den TV-Eingabedienst implementiert einen Mediaplayer, der ExoPlayer verwendet.

TV-Eingabedienst mit dem TV Input Framework erstellen

Wenn dein TV-Eingabedienst die TIF Companion Library nicht verwenden kann, musst du die folgenden Komponenten implementieren:

  • TvInputService bietet eine langfristige und Hintergrundverfügbarkeit für den TV-Eingang.
  • TvInputService.Session verwaltet den Status des TV-Eingangs und kommuniziert mit der Host-App.
  • TvContract beschreibt die Channels und Programme, die für den TV Eingang verfügbar sind.
  • TvContract.Channels enthält Informationen zu einem TV-Channel.
  • TvContract.Programs beschreibt ein TV-Programm mit Daten wie Programmtitel und Startzeit.
  • TvTrackInfo stellt einen Audio-, Video- oder Untertiteltrack dar.
  • TvContentRating beschreibt eine Altersfreigabe und ermöglicht benutzerdefinierte Altersfreigabeschemas.
  • TvInputManager bietet eine API für die System-TV-App und verwaltet die Interaktion mit TV-Eingängen und ‑Apps.

Außerdem musst du Folgendes tun:

  1. Deklariere deinen TV-Eingabedienst im Manifest, wie unter TV-Eingabedienst im Manifest deklarieren beschrieben.
  2. Erstelle die Dienstmetadatendatei.
  3. Erstelle und registriere deine Channel- und Programminformationen.
  4. Erstelle deine Einrichtungsaktivität.

TV-Eingabedienst definieren

Für deinen Dienst erweiterst du die Klasse TvInputService. Eine TvInputService Implementierung ist ein gebundener Dienst, bei dem der Systemdienst der Client ist, der sich damit verbindet. Die Methoden des Dienstlebenszyklus, die du implementieren musst, sind in Abbildung 1 dargestellt.

Die Methode onCreate initialisiert und startet den HandlerThread, der einen Prozess-Thread bereitstellt, der vom UI-Thread getrennt ist, um systemgesteuerte Aktionen zu verarbeiten. Im folgenden Beispiel initialisiert die Methode onCreate den CaptioningManager und bereitet die Verarbeitung der Aktionen ACTION_BLOCKED_RATINGS_CHANGED und ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED vor. Diese Aktionen beschreiben System-Intents, die ausgelöst werden, wenn der Nutzer die Einstellungen für die Kindersicherung ändert und wenn sich die Liste der blockierten Altersfreigaben ändert.

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

Abbildung 1: Lebenszyklus von TvInputService

Weitere Informationen zum Arbeiten mit blockierten Inhalten und zum Bereitstellen der Kindersicherung findest du unter Inhalte steuern. Unter TvInputManager findest du weitere systemgesteuerte Aktionen, die du in deinem TV-Eingabedienst verarbeiten kannst.

Der TvInputService erstellt eine TvInputService.Session, die Handler.Callback implementiert, um Änderungen des Playerstatus zu verarbeiten. Mit onSetSurface, legt die TvInputService.Session die Surface mit den Videoinhalten fest. Weitere Informationen zum Arbeiten mit Surface zum Rendern von Videos findest du unter Player mit Surface integrieren.

Die TvInputService.Session verarbeitet das Ereignis onTune, wenn der Nutzer einen Channel auswählt, und benachrichtigt die System-TV-App über Änderungen an den Inhalten und den Inhaltsmetadaten. Diese notify Methoden werden in dieser Schulung unter Inhalte steuern und Auswahl von Tracks verarbeiten weiter beschrieben.

Einrichtungsaktivität definieren

Die System-TV-App funktioniert mit der Einrichtungsaktivität, die du für deinen TV-Eingang definierst. Die Einrichtungsaktivität ist erforderlich und muss mindestens einen Channel-Eintrag für die Systemdatenbank bereitstellen. Die System-TV-App ruft die Einrichtungsaktivität auf, wenn sie keinen Channel für den TV-Eingang findet.

Die Einrichtungsaktivität beschreibt der System-TV-App die Channels, die über den TV Eingang verfügbar gemacht werden, wie in der nächsten Lektion Channel Daten erstellen und aktualisieren gezeigt.

Zusätzliche Referenzen