Mit Kanaldaten arbeiten

Dein TV-Eingang muss Daten zum elektronischen Programmführer (Electronic Program Guide, EPG) für mindestens einen Kanal bei der Einrichtung bereitstellen. Außerdem sollten Sie diese Daten regelmäßig aktualisieren, wobei Sie die Größe der Aktualisierung und den Verarbeitungsthread berücksichtigen, der sie verarbeitet. Außerdem kannst du App-Links für Kanäle bereitstellen, über die Nutzer zu ähnlichen Inhalten und Aktivitäten gelangen. In dieser Lektion wird beschrieben, wie Sie Kanal- und Programmdaten in der Systemdatenbank unter Berücksichtigung dieser Überlegungen erstellen und aktualisieren.

Probieren Sie die Beispiel-App TV Input Service aus.

Berechtigung einholen

Damit Ihre TV-Eingabe mit EPG-Daten funktioniert, muss die Schreibberechtigung in der zugehörigen Android-Manifestdatei wie folgt deklariert werden:

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

Kanäle in der Datenbank registrieren

Die Android TV-Systemdatenbank verwaltet Datensätze von Kanaldaten für TV-Eingaben. In den Einrichtungsaktivitäten müssen Sie die Kanaldaten für jeden Kanal den folgenden Feldern der Klasse TvContract.Channels zuordnen:

Obwohl das TV-Eingabe-Framework generisch genug ist, um sowohl herkömmliche Rundfunk- als auch OTT-Inhalte ohne Unterscheidung zu verarbeiten, können Sie die folgenden Spalten zusätzlich zu den obigen Spalten definieren, um herkömmliche Übertragungskanäle besser zu identifizieren:

Wenn du Details zu App-Links für deine Kanäle angeben möchtest, musst du einige zusätzliche Felder aktualisieren. Weitere Informationen zu Feldern für App-Links finden Sie unter Informationen zu App-Links hinzufügen.

Weisen Sie für TV-Eingaben, die auf Internetstreaming basieren, Ihre eigenen Werte zu, damit jeder Kanal eindeutig identifiziert werden kann.

Rufen Sie die Kanalmetadaten (in XML, JSON usw.) von Ihrem Back-End-Server ab und ordnen Sie die Werte in Ihrer Einrichtungsaktivität der Systemdatenbank zu:

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

Im obigen Beispiel ist channel ein Objekt, das Kanalmetadaten vom Back-End-Server enthält.

Kanal- und Programminformationen präsentieren

Die System-TV-App zeigt Nutzern beim Blättern durch Kanäle Kanal- und Programminformationen an (siehe Abbildung 1). Damit die Kanal- und Programminformationen mit dem Kanal- und Programminformationsfeed der System-TV-App funktionieren, beachte die folgenden Richtlinien.

  1. Kanalnummer (COLUMN_DISPLAY_NUMBER)
  2. Symbol (android:icon im Manifest der TV-Eingabe)
  3. Programmbeschreibung (COLUMN_SHORT_DESCRIPTION)
  4. Programmtitel (COLUMN_TITLE)
  5. Kanallogo (TvContract.Channels.Logo)
    • Verwenden Sie die Farbe #EEEEEE, um sie an den umgebenden Text anzupassen.
    • Keinen Abstand einfügen
  6. Poster (COLUMN_POSTER_ART_URI)
    • Seitenverhältnis zwischen 16:9 und 4:3

Abbildung 1: Kanal- und Programminformationen des Systems für die TV-App

Die System-TV-App stellt über die Programmübersicht dieselben Informationen bereit, einschließlich Postergrafiken, wie in Abbildung 2 dargestellt.

Abbildung 2: Programmübersicht der System TV App.

Kanaldaten aktualisieren

Wenn Sie vorhandene Kanaldaten aktualisieren, sollten Sie die Methode update() verwenden, anstatt die Daten zu löschen und neu hinzuzufügen. Sie können die aktuelle Version der Daten mithilfe von Channels.COLUMN_VERSION_NUMBER und Programs.COLUMN_VERSION_NUMBER ermitteln, wenn Sie die zu aktualisierenden Datensätze auswählen.

Hinweis:Das Hinzufügen von Kanaldaten zu ContentProvider kann einige Zeit dauern. Füge aktuelle Programme (die innerhalb von zwei Stunden ab der aktuellen Uhrzeit) nur hinzu, wenn du EpgSyncJobService so konfigurierst, dass die restlichen Kanaldaten im Hintergrund aktualisiert werden. Ein Beispiel hierfür ist die Beispiel-App „Android TV Live TV“.

Kanaldaten im Batch laden

Verwenden Sie zum Aktualisieren der Systemdatenbank mit großen Mengen an Kanaldaten die Methode ContentResolver applyBatch() oder bulkInsert(). Hier ein Beispiel mit 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();
    }
}

Kanaldaten asynchron verarbeiten

Datenmanipulationen wie das Abrufen eines Streams vom Server oder der Zugriff auf die Datenbank sollten den UI-Thread nicht blockieren. Die Verwendung von AsyncTask ist eine Möglichkeit, Aktualisierungen asynchron durchzuführen. Wenn Sie beispielsweise Kanalinformationen von einem Back-End-Server laden, können Sie AsyncTask so verwenden:

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

Wenn Sie die EPG-Daten regelmäßig aktualisieren müssen, sollten Sie WorkManager verwenden, um den Aktualisierungsprozess bei Inaktivität auszuführen, z. B. täglich um 3:00 Uhr.

Andere Techniken zum Trennen der Aufgaben zur Datenaktualisierung vom UI-Thread sind die Verwendung der Klasse HandlerThread. Sie können auch Ihre eigenen mit den Klassen Looper und Handler implementieren. Weitere Informationen finden Sie unter Prozesse und Threads.

Kanäle können App-Links verwenden, mit denen Nutzer auf einfache Weise eine ähnliche Aktivität starten können, während sie sich Kanalinhalte ansehen. Kanal-Apps verwenden App-Links, um die Nutzerinteraktion durch Aktivitäten zu steigern, die zugehörige Informationen oder zusätzliche Inhalte enthalten. Sie können App-Links beispielsweise für Folgendes verwenden:

  • Leiten Sie den Nutzer dazu, ähnliche Inhalte zu entdecken und zu kaufen.
  • Geben Sie zusätzliche Informationen zu aktuell wiedergegebenen Inhalten an.
  • Beginne während der Wiedergabe von Inhalten im Serienformat die nächste Folge einer Serie.
  • Ermöglichen Sie den Nutzern, mit Inhalten zu interagieren (z. B. Inhalte zu bewerten oder zu überprüfen), ohne die Wiedergabe zu unterbrechen.

App-Links werden angezeigt, wenn der Nutzer während der Wiedergabe von Kanalinhalten auf Auswählen drückt, um das TV-Menü aufzurufen.

Abbildung 1: Ein Beispiel für einen App-Link in der Zeile Channels, während Kanalinhalte zu sehen sind.

Wenn der Nutzer den App-Link auswählt, startet das System eine Aktivität mithilfe eines von der Kanal-App angegebenen Intent-URI. Kanalinhalte werden weiter abgespielt, während die App-Link-Aktivität aktiv ist. Der Nutzer kann durch Drücken der Zurück-Taste zum Kanalinhalt zurückkehren.

Daten zum App-Link-Kanal angeben

Android TV erstellt mithilfe von Informationen aus den Kanaldaten automatisch einen App-Link für jeden Kanal. Geben Sie in den TvContract.Channels-Feldern die folgenden Details an, um Informationen zu App-Links bereitzustellen:

  • COLUMN_APP_LINK_COLOR: Die Akzentfarbe des App-Links für diesen Kanal. Ein Beispiel für eine Akzentfarbe siehe Abbildung 2, Legende 3.
  • COLUMN_APP_LINK_ICON_URI: Der URI für das App-Badge-Symbol des App-Links für diesen Kanal. Ein Beispiel für ein App-Badge-Symbol findest du in Abbildung 2, Zusatzinformationen 2.
  • COLUMN_APP_LINK_INTENT_URI: Der Intent-URI des App-Links für diesen Kanal. Sie können den URI mit toUri(int) und URI_INTENT_SCHEME erstellen und den URI mit parseUri() wieder in den ursprünglichen Intent konvertieren.
  • COLUMN_APP_LINK_POSTER_ART_URI – Der URI für die Postergrafiken, die als Hintergrund des App-Links für diesen Kanal verwendet werden. Ein Beispiel für ein Posterbild findest du in Abbildung 2, Zusatzinformationen 1.
  • COLUMN_APP_LINK_TEXT: der beschreibende Linktext des App-Links für diesen Kanal. Ein Beispiel für die Beschreibung eines App-Links findest du in Abbildung 2, Zusatzinformation 3.

Abbildung 2: Details zur App-Verknüpfung.

Wenn in den Kanaldaten keine Informationen zur App-Verknüpfung angegeben sind, erstellt das System eine standardmäßige App-Verknüpfung. Das System wählt die Standarddetails so aus:

  • Für den Intent-URI (COLUMN_APP_LINK_INTENT_URI) verwendet das System die Aktivität ACTION_MAIN für die Kategorie CATEGORY_LEANBACK_LAUNCHER, die in der Regel im App-Manifest definiert ist. Wenn diese Aktivität nicht definiert ist, wird ein nicht funktionierender App-Link angezeigt. Klickt der Nutzer darauf, passiert nichts.
  • Für den beschreibenden Text (COLUMN_APP_LINK_TEXT) verwendet das System „Open app-name“. Wenn kein gültiger URI für den App-Link definiert ist, verwendet das System „Kein Link verfügbar“.
  • Für die Akzentfarbe (COLUMN_APP_LINK_COLOR) verwendet das System die Standard-App-Farbe.
  • Für das Posterbild (COLUMN_APP_LINK_POSTER_ART_URI) verwendet das System das Startbildschirmbanner der App. Wenn die App kein Banner bereitstellt, verwendet das System ein Standardbild für die TV-App.
  • Für das Logosymbol (COLUMN_APP_LINK_ICON_URI) verwendet das System ein Logo, das den App-Namen anzeigt. Wenn das System zusätzlich das App-Banner oder das Standard-App-Bild für das Posterbild verwendet, wird kein App-Logo angezeigt.

Details zum App-Link für Ihre Kanäle geben Sie bei der Einrichtungsaktivität Ihrer App an. Du kannst die Details zur App-Verknüpfung jederzeit aktualisieren. Wenn ein App-Link also mit Kanaländerungen übereinstimmen muss, aktualisiere die Details zur App-Verknüpfung und rufe ContentResolver.update() nach Bedarf auf. Weitere Informationen zum Aktualisieren von Kanaldaten finden Sie unter Kanaldaten aktualisieren.