Praca z danymi kanału

Twoje wejście TV musi dostarczać dane Elektronicznego przewodnika po programach (EPG) dla co najmniej jednego kanału w ramach jego konfiguracji. Musisz też okresowo aktualizować te dane, biorąc pod uwagę rozmiar aktualizacji i wątek przetwarzania, który je obsługuje. Możesz też podać linki do aplikacji kanałów, które prowadzą użytkownika do powiązanych treści i aktywności. Z tej lekcji dowiesz się, jak tworzyć i aktualizować dane kanału i programu w bazie danych systemu.

Wypróbuj przykładową aplikację Usługa wejścia TV.

Przyznaj dostęp

Aby wejście TV mogło obsługiwać dane EPG, w pliku manifestu Androida muszą zadeklarować uprawnienia do zapisu w następujący sposób:

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

Zarejestruj kanały w bazie danych

Baza danych systemu Android TV zawiera rejestry danych kanałów dotyczących wejść TV. W konfiguracji każdego kanału musisz zmapować dane kanału na te pola klasy TvContract.Channels:

Chociaż struktura wejściowej sygnału telewizyjnego jest na tyle ogólna, aby poradzić sobie z tradycyjnymi transmisjami i treściami Over-The-Top (OTT) bez żadnego rozróżnienia, oprócz wymienionych powyżej możesz zdefiniować te kolumny, aby lepiej identyfikować tradycyjne kanały telewizyjne:

Jeśli chcesz podać szczegóły linku aplikacji do swoich kanałów, musisz zaktualizować niektóre dodatkowe pola. Więcej informacji o polach linków aplikacji znajdziesz w artykule Dodawanie informacji o linku aplikacji.

W przypadku urządzeń wejściowych telewizyjnych opartych na streamingu internetowym przypisz do powyższych wartości własne wartości, aby każdy kanał był identyfikowany jednoznacznie.

Pobierz metadane kanału (w formacie XML, JSON lub innym) z serwera backendu i podczas konfiguracji zmapuj wartości na bazę danych systemu w następujący sposób:

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

W powyższym przykładzie channel to obiekt, który przechowuje metadane kanału z serwera backendu.

Prezentowanie informacji o kanale i programie

Aplikacja systemowa TV wyświetla użytkownikom informacje o kanałach i programach, jak pokazano na rysunku 1. Aby mieć pewność, że informacje o kanale i programie są zgodne z wyświetlaczem informacji o kanale i programie w aplikacji systemowej TV, postępuj zgodnie z poniższymi wskazówkami.

  1. Numer kanału (COLUMN_DISPLAY_NUMBER)
  2. Ikona (android:icon w pliku manifestu danych wejściowych TV)
  3. Opis programu (COLUMN_SHORT_DESCRIPTION)
  4. Tytuł programu (COLUMN_TITLE)
  5. Logo kanału (TvContract.Channels.Logo)
    • Użyj koloru #EEEEEE, aby dopasować do tekstu otaczającego
    • Nie dodawaj dopełnienia
  6. Poster art (COLUMN_POSTER_ART_URI)
    • Współczynnik proporcji: od 16:9 do 4:3

Rysunek 1. Prezentujący kanał aplikacji i informacje o programie w systemie TV.

Aplikacja systemowa TV podaje te same informacje z przewodnika po programach, w tym plakat, jak widać na ilustracji 2.

Rysunek 2. Przewodnik po programach po aplikacjach systemowych.

Aktualizowanie danych kanału

Do aktualizowania istniejących danych kanału użyj metody update(), zamiast usuwać i ponownie dodawać dane. Bieżącą wersję danych możesz rozpoznać, wybierając rekordy do zaktualizowania za pomocą Channels.COLUMN_VERSION_NUMBER i Programs.COLUMN_VERSION_NUMBER.

Uwaga: dodanie danych kanału do ContentProvider może trochę potrwać. Dodaj bieżące programy (te w ciągu 2 godzin od bieżącego czasu) tylko wtedy, gdy skonfigurujesz urządzenie EpgSyncJobService tak, aby aktualizowało pozostałe dane kanału w tle. Przykład znajdziesz w artykule Przykładowa aplikacja na Androida TV Live TV.

Zbiorcze wczytywanie danych kanału

W przypadku aktualizowania systemowej bazy danych o dużej ilości danych kanału użyj metody ContentResolver applyBatch() lub bulkInsert(). Oto przykład użycia właściwości 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();
    }
}

Przetwarzanie danych kanału asynchronicznie

Manipulacja danymi, np. pobranie strumienia z serwera lub uzyskanie dostępu do bazy danych, nie powinna blokować wątku UI. Użycie elementu AsyncTask to jeden ze sposobów asynchronicznego przeprowadzania aktualizacji. Na przykład podczas wczytywania informacji o kanale z serwera backendu możesz użyć polecenia AsyncTask w ten sposób:

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

Jeśli musisz regularnie aktualizować dane EPG, rozważ używanie WorkManager do uruchamiania procesu aktualizacji w czasie bezczynności, np.codziennie o 3:00.

Inne metody oddzielania zadań aktualizacji danych od wątku interfejsu obejmują użycie klasy HandlerThread lub wdrożenie własnych metod za pomocą klas Looper i Handler. Więcej informacji znajdziesz w artykule Procesy i wątki.

Kanały mogą używać linków do aplikacji, aby umożliwić użytkownikom łatwe uruchamianie powiązanych działań podczas oglądania treści kanału. Aplikacje kanału korzystają z linków do aplikacji, aby zwiększać zaangażowanie użytkowników przez uruchamianie działań pokazujących powiązane informacje lub dodatkowe treści. Za pomocą linków do aplikacji możesz np.:

  • Pomagaj użytkownikom odkrywać i kupować powiązane treści.
  • Podaj dodatkowe informacje o aktualnie odtwarzanych treściach.
  • Oglądając treści w odcinkach, zacznij oglądać następny odcinek.
  • Pozwól użytkownikom na interakcję z treściami – na przykład ocenianie lub recenzowanie treści – bez przerywania odtwarzania.

Linki do aplikacji wyświetlają się, gdy użytkownik naciśnie Wybierz, aby wyświetlić menu telewizora podczas oglądania treści na kanale.

Rysunek 1. Przykładowy link do aplikacji wyświetlany w wierszu Kanały podczas pokazywania treści kanału.

Gdy użytkownik kliknie link do aplikacji, system rozpocznie działanie przy użyciu identyfikatora URI intencji określonego przez aplikację kanału. Materiały na kanale będą nadal odtwarzane, gdy aktywne jest linki do aplikacji. Użytkownik może wrócić do treści kanału, naciskając Wstecz.

Podaj dane kanału powiązanego z aplikacją

Android TV automatycznie tworzy link do aplikacji dla każdego kanału, korzystając z informacji z danych kanału. Aby podać informacje o linku aplikacji, w polach TvContract.Channels podaj te informacje:

  • COLUMN_APP_LINK_COLOR – kolor uzupełniający linku do aplikacji dla tego kanału. Przykładowy kolor uzupełniający zobacz ilustrację 2, objaśnienie 3.
  • COLUMN_APP_LINK_ICON_URI – Identyfikator URI ikony plakietki aplikacji linku do tego kanału. Przykład ikony plakietki przykładowej aplikacji znajdziesz na rys. 2, objaśnienie 2.
  • COLUMN_APP_LINK_INTENT_URI – identyfikator URI intencji linku do aplikacji dla tego kanału. Możesz utworzyć identyfikator URI za pomocą polecenia toUri(int) za pomocą URI_INTENT_SCHEME, a potem przekonwertować identyfikator URI z powrotem na pierwotną intencję za pomocą polecenia parseUri().
  • COLUMN_APP_LINK_POSTER_ART_URI – Identyfikator URI plakatu używany jako tło linku do aplikacji dla tego kanału. Przykładowy obraz plakatu znajdziesz na rys. 2, objaśnienie 1.
  • COLUMN_APP_LINK_TEXT – opisowy tekst linku do aplikacji dla tego kanału. Przykładowy opis linku do aplikacji znajdziesz na rys. 2, objaśnienie 3.

Rysunek 2. Szczegóły linku aplikacji.

Jeśli w danych kanału nie ma informacji o linku do aplikacji, system utworzy domyślny link do aplikacji. System wybiera te informacje domyślne:

  • W przypadku identyfikatora URI intencji (COLUMN_APP_LINK_INTENT_URI) system używa aktywności ACTION_MAIN dla kategorii CATEGORY_LEANBACK_LAUNCHER, zwykle zdefiniowanej w pliku manifestu aplikacji. Jeśli aktywność nie zostanie zdefiniowana, pojawi się link niedziałający aplikacji – jeśli użytkownik go kliknie, nic się nie stanie.
  • W przypadku tekstu opisu (COLUMN_APP_LINK_TEXT) system używa opcji „Otwórz app-name”. Jeśli nie zdefiniowany jest prawidłowy identyfikator URI intencji linku aplikacji, system użyje „Brak dostępnego linku”.
  • W przypadku koloru uzupełniającego (COLUMN_APP_LINK_COLOR) system używa domyślnego koloru aplikacji.
  • W przypadku obrazu plakatu (COLUMN_APP_LINK_POSTER_ART_URI) system używa banera na ekranie głównym aplikacji. Jeśli aplikacja nie ma banera, system użyje domyślnego obrazu aplikacji telewizyjnej.
  • W przypadku ikony plakietki (COLUMN_APP_LINK_ICON_URI) system używa plakietki z nazwą aplikacji. Jeśli jako obraz plakatu system używa też banera aplikacji lub domyślnego obrazu aplikacji, plakietka aplikacji nie jest widoczna.

Szczegóły linku aplikacji do kanałów określasz w ustawieniach konfiguracji aplikacji. Szczegóły linku aplikacji możesz zaktualizować w dowolnym momencie. Jeśli więc link aplikacji musi pasować do zmian w kanale, zaktualizuj szczegóły linku aplikacji i w razie potrzeby wywołaj ContentResolver.update(). Więcej informacji o aktualizowaniu danych kanału znajdziesz w artykule Aktualizowanie danych kanału.