TV-Eingabedienst entwickeln

Ein TV-Eingabedienst steht für eine Medienstreamquelle, mit dem Sie Ihre Medieninhalte linear als TV-Sender als Kanäle und Programme präsentieren können. Mit einem TV-Eingabedienst können Sie Jugendschutzeinstellungen, Informationen zur Programmübersicht und Altersfreigaben festlegen. Der TV-Eingabedienst funktioniert mit der TV-App des Android-Systems. Diese App steuert und präsentiert Kanalinhalte 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 Frameworks (TIF) und ihren Komponenten finden Sie unter TV Input Framework.

TV-Eingabedienst mithilfe der TIF-Companion-Bibliothek erstellen

Die TIF-Companion-Bibliothek ist ein Framework, das erweiterbare Implementierungen von gängigen TV-Eingabedienstfunktionen bietet. Es ist ausschließlich für die Erstellung von Kanälen für Android 5.0 (API-Level 21) bis Android 7.1 (API-Level 25) gedacht.

Projekt aktualisieren

Die TIF-Companion-Bibliothek ist zur Legacy-Nutzung durch OEMs im Repository androidtv-sample-inputs verfügbar. In diesem Repository finden Sie ein Beispiel dafür, wie Sie die Bibliothek in eine Anwendung einbinden können.

TV-Eingabedienst im Manifest deklarieren

Ihre Anwendung muss einen mit TvInputService kompatiblen Dienst bereitstellen, mit dem das System auf Ihre Anwendung zugreift. Die TIF-Companion-Bibliothek enthält die Klasse BaseTvInputService, die eine Standardimplementierung von TvInputService bereitstellt, die Sie anpassen können. Erstellen Sie eine abgeleitete Klasse von BaseTvInputService und deklarieren Sie diese in Ihrem Manifest als Dienst.

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

Fügen Sie in die Dienstdeklaration einen Intent-Filter ein, der TvInputService als die mit dem Intent auszuführende Aktion angibt. Deklarieren Sie außerdem die Dienstmetadaten als separate XML-Ressource. Im folgenden Beispiel werden die Dienstdeklaration, der Intent-Filter und die Deklaration der Dienstmetadaten 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>

Definieren Sie die Dienstmetadaten in einer separaten XML-Datei. Die XML-Datei mit den Dienstmetadaten muss eine Einrichtungsschnittstelle enthalten, die die Erstkonfiguration der TV-Eingabe und den Kanalsuchlauf beschreibt. Die Metadatendatei sollte außerdem ein Flag enthalten, das angibt, ob Nutzer Inhalte aufzeichnen können. Weitere Informationen dazu, wie Sie die Aufzeichnung von Inhalten in Ihrer Anwendung unterstützen, finden Sie unter Inhaltsaufzeichnung unterstützen.

Die Dienstmetadatendatei befindet sich im XML-Ressourcenverzeichnis für Ihre Anwendung und muss mit dem Namen der Ressource übereinstimmen, die Sie im Manifest deklariert haben. Mit den Manifesteinträgen aus dem vorherigen Beispiel würden Sie die XML-Datei unter res/xml/richtvinputservice.xml mit folgendem Inhalt erstellen:

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

Ihr TV-Eingabedienst muss mindestens einen Kanal definieren, auf den Nutzer über die System-TV-App zugreifen. Sie sollten Ihre Kanäle in der Systemdatenbank registrieren und eine Einrichtungsaktivität angeben, die das System auslöst, wenn kein Kanal für Ihre App gefunden wird.

Ermögliche deiner App zuerst, im System Electronic Programming Guide (EPG) zu lesen und darin zu schreiben, dessen Daten Kanäle und Programme enthalten, die dem Nutzer zur Verfügung stehen. Damit deine App diese Aktionen ausführen und nach dem Geräteneustart mit dem elektronischen Programmführer synchronisieren kann, musst du deinem App-Manifest die folgenden Elemente hinzufügen:

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

Fügen Sie das folgende Element hinzu, damit Ihre App im Google Play Store als App angezeigt wird, die Inhaltskanäle in Android TV bereitstellt:

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

Erstellen Sie als Nächstes eine Klasse, die die Klasse EpgSyncJobService erweitert. Diese abstrakte Klasse erleichtert das Erstellen eines Jobdienstes, der Kanäle in der Systemdatenbank erstellt und aktualisiert.

Erstellen Sie in Ihrer Unterklasse Ihre vollständige Liste der Kanäle und geben Sie sie in getChannels() zurück. Wenn deine Kanäle aus einer XMLTV-Datei stammen, verwende die Klasse XmlTvParser. Andernfalls können Sie Kanäle mithilfe der Klasse Channel.Builder programmatisch generieren.

Für jeden Kanal ruft das System getProgramsForChannel() auf, wenn eine Liste der Programme benötigt wird, die in einem bestimmten Zeitraum auf dem Kanal angesehen werden können. Gibt eine Liste von Program-Objekten für den Kanal zurück. Verwenden Sie die Klasse XmlTvParser, um Programme aus einer XMLTV-Datei abzurufen, oder generieren Sie sie programmatisch mithilfe 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 Kanal in einer Schleife wiederholen soll, verwende die Methode InternalProviderData.setRepeatable() mit dem Wert true.

Nachdem Sie den Jobdienst implementiert haben, fügen Sie ihn zu Ihrem App-Manifest hinzu:

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

Erstellen Sie abschließend eine Einrichtungsaktivität. Die Einrichtung sollte eine Möglichkeit bieten, Kanal- und Programmdaten zu synchronisieren. Eine Möglichkeit dafür ist, dass der Nutzer dies über die UI in der Aktivität tut. Sie könnten auch einstellen, dass die App dies automatisch zu Beginn der Aktivität macht. Wenn die Einrichtungsaktivität die Kanal- und Programminformationen synchronisieren muss, sollte die Anwendung 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));

Verwenden Sie die Methode requestImmediateSync(), um den Jobdienst zu synchronisieren. Da der Nutzer warten muss, bis die Synchronisierung abgeschlossen ist, sollte der Anfragezeitraum relativ kurz sein.

Verwenden Sie die Methode setUpPeriodicSync(), damit der Jobdienst regelmäßig Kanal- und Programmdaten 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-Bibliothek bietet eine zusätzliche überlastete Methode von requestImmediateSync(), mit der Sie die Dauer der zu synchronisierenden Kanaldaten in Millisekunden angeben können. Mit der Standardmethode werden Kanaldaten aus einer Stunde synchronisiert.

Die TIF-Companion-Bibliothek bietet auch eine zusätzliche überlastete Methode von setUpPeriodicSync(), mit der Sie die Dauer der zu synchronisierenden Kanaldaten und die Häufigkeit der regelmäßigen Synchronisierung angeben können. Bei der Standardmethode werden alle 12 Stunden Kanaldaten aus einem Zeitraum von 48 Stunden synchronisiert.

Weitere Informationen zu Kanaldaten und zum elektronischen Programmführer finden Sie unter Mit Kanaldaten arbeiten.

Abstimmungsanfragen und Medienwiedergabe verarbeiten

Wenn ein Nutzer einen bestimmten Kanal auswählt, verwendet die System-TV-App eine von Ihrer App erstellte Session, um den angeforderten Kanal einzustellen und Inhalte wiederzugeben. Die TIF-Begleitbibliothek bietet mehrere Klassen, die Sie erweitern können, um Kanal- und Sitzungsaufrufe aus dem System zu verarbeiten.

Die abgeleitete Klasse BaseTvInputService erstellt Sitzungen, die Abstimmungsanfragen verarbeiten. Überschreiben Sie die Methode onCreateSession(), erstellen Sie eine Sitzung, die über die Klasse BaseTvInputService.Session erweitert wurde, und rufen Sie super.sessionCreated() mit der 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 Ihrer Kanäle anzusehen, ruft das System die Methode onPlayChannel() Ihrer Sitzung auf. Überschreiben Sie diese Methode, wenn Sie eine spezielle Kanalinitialisierung vornehmen müssen, bevor das Programm wiedergegeben wird.

Das System ruft dann das aktuell geplante Programm ab und ruft die Methode onPlayProgram() Ihrer Sitzung auf. Dabei werden die Programminformationen und die Startzeit in Millisekunden angegeben. Verwenden Sie die Schnittstelle TvPlayer, um das Programm zu starten.

Im Mediaplayer-Code sollte TvPlayer für die Verarbeitung bestimmter Wiedergabeereignisse implementiert sein. Die Klasse TvPlayer verarbeitet Funktionen wie zeitversetzte Nutzung, ohne die Komplexität Ihrer BaseTvInputService-Implementierung zu erhöhen.

Geben Sie in der Methode getTvPlayer() Ihrer Sitzung den Mediaplayer zurück, in dem TvPlayer implementiert ist. In der Beispiel-App TV Input Service wird ein Mediaplayer implementiert, der ExoPlayer verwendet.

TV-Eingabedienst mit dem TV-Eingabe-Framework erstellen

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

  • TvInputService bietet langfristige und Hintergrundverfügbarkeit der TV-Eingabe
  • TvInputService.Session behält den TV-Eingabestatus bei und kommuniziert mit der Hosting-App
  • TvContract beschreibt die Kanäle und Programme, die für den TV-Eingang verfügbar sind
  • TvContract.Channels steht für Informationen zu einem TV-Kanal.
  • TvContract.Programs beschreibt ein TV-Programm mit Daten wie dem Programmtitel und der Startzeit.
  • TvTrackInfo steht für einen Audio-, Video- oder Untertiteltrack
  • TvContentRating beschreibt eine Altersfreigabe und ermöglicht benutzerdefinierte Schemas für die Altersfreigabe.
  • TvInputManager stellt eine API für die System-TV-App bereit und verwaltet die Interaktion mit TV-Eingängen und Apps

Außerdem müssen Sie Folgendes tun:

  1. Deklarieren Sie Ihren TV-Eingabedienst im Manifest, wie unter TV-Eingabedienst im Manifest deklarieren beschrieben.
  2. Erstellen Sie die Datei mit den Dienstmetadaten.
  3. Erstelle und registriere deine Kanal- und Programminformationen.
  4. Erstellen Sie die Aktivität zur Einrichtung.

TV-Eingabedienst festlegen

Für Ihren Dienst erweitern Sie die Klasse TvInputService. Eine TvInputService-Implementierung ist ein gebundener Dienst, bei dem der Systemdienst der Client ist, der die Bindung an ihn herstellt. Die Methoden des Dienstlebenszyklus, die Sie implementieren müssen, sind in Abbildung 1 dargestellt.

Die Methode onCreate() initialisiert und startet den HandlerThread. Dieser stellt einen vom UI-Thread getrennten Prozessthread zur Verarbeitung systemgesteuerter Aktionen bereit. Im folgenden Beispiel initialisiert die Methode onCreate() das CaptioningManager und bereitet sich auf 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 Jugendschutzeinstellungen ändert und wenn sich die Liste der blockierten Bewertungen ä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 zur Handhabung gesperrter Inhalte und Jugendschutzeinstellungen finden Sie unter Inhalte verwalten. Weitere systemgesteuerte Aktionen, die Sie in Ihrem TV-Eingabedienst ausführen können, finden Sie unter TvInputManager.

Der TvInputService erstellt eine TvInputService.Session, die Handler.Callback implementiert, um Änderungen des Player-Status zu verarbeiten. Mit onSetSurface() legt TvInputService.Session den Surface auf den Videoinhalt fest. Weitere Informationen zum Rendern von Videos mit Surface findest du unter Player in Oberfläche integrieren.

Das TvInputService.Session verarbeitet das Ereignis onTune(), wenn der Nutzer einen Kanal auswählt, und benachrichtigt die System-TV-App über Änderungen an den Inhalts- und Inhaltsmetadaten. Diese notify()-Methoden werden weiter unten in diesem Training unter Inhalt steuern und Trackauswahl verarbeiten beschrieben.

Einrichtungsaktivität definieren

Die System-TV-App arbeitet mit der Einrichtungsaktivität, die Sie für Ihre TV-Eingabe festlegen. Die Einrichtungsaktivität ist erforderlich und muss mindestens einen Kanaleintrag für die Systemdatenbank enthalten. Die System-TV-App ruft die Einrichtungsaktivität auf, wenn kein Kanal für die TV-Eingabe gefunden wird.

In der Einrichtungsaktivität werden für die System-TV-App die Kanäle beschrieben, die über die TV-Eingabe zur Verfügung gestellt werden, wie in der nächsten Lektion Kanaldaten erstellen und aktualisieren gezeigt.

Weitere Referenzen