Praca z danymi kanału

Dane wejściowe telewizora muszą w ramach konfiguracji udostępniać dane elektronicznego przewodnika po programach (EPG) co najmniej 1 kanału. Musisz też okresowo aktualizować te dane, biorąc pod uwagę rozmiar aktualizacji i wątek przetwarzania, który ją obsługuje. Dodatkowo możesz podać linki do aplikacji dla kanałów, które kierują użytkownika do powiązanych treści i działań. Z tego artykułu dowiesz się, jak tworzyć i aktualizować dane kanałów i programów w systemowej bazie danych, biorąc pod uwagę te kwestie.

Wypróbuj przykładową aplikację TV Input Service.

Uzyskiwanie uprawnień

Aby dane wejściowe telewizora mogły korzystać z danych EPG, muszą zadeklarować uprawnienie do zapisu w pliku manifestu Androida w ten sposób:

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

Rejestrowanie kanałów w bazie danych

Systemowa baza danych Androida TV przechowuje rekordy danych kanałów dla danych wejściowych telewizora. W ramach konfiguracji dla każdego kanału musisz przypisać dane kanału do tych pól klasy TvContract.Channels:

Chociaż framework danych wejściowych telewizora jest wystarczająco ogólny, aby obsługiwać zarówno tradycyjne transmisje, jak i treści OTT bez rozróżniania ich, możesz dodatkowo zdefiniować te kolumny, aby lepiej identyfikować tradycyjne kanały transmisji:

Jeśli chcesz podać szczegóły linku do aplikacji dla swoich kanałów, musisz zaktualizować kilka dodatkowych pól. Więcej informacji o polach linków do aplikacji znajdziesz w sekcji Dodawanie informacji o linkach do aplikacji.

W przypadku danych wejściowych telewizora opartych na strumieniowaniu internetowym przypisz odpowiednio własne wartości, aby każdy kanał można było jednoznacznie zidentyfikować.

Pobierz metadane kanału (w formacie XML, JSON lub innym) z serwera backendu, a następnie w ramach konfiguracji przypisz wartości do systemowej bazy danych w ten 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 tym przykładzie channel to obiekt, który zawiera metadane kanału z serwera backendu.

Wyświetlanie informacji o kanałach i programach

Systemowa aplikacja TV wyświetla użytkownikom informacje o kanałach i programach, gdy przełączają się między kanałami, jak pokazano na ilustracji 1. Aby informacje o kanałach i programach działały z modułem wyświetlania informacji o kanałach i programach w systemowej aplikacji TV, postępuj zgodnie z tymi wskazówkami:

  1. Numer kanału (COLUMN_DISPLAY_NUMBER)
  2. Ikona (android:icon w manifeście danych wejściowych telewizora)
  3. Opis programu (COLUMN_SHORT_DESCRIPTION)
  4. Tytuł programu (COLUMN_TITLE)
  5. Logo kanału (TvContract.Channels.Logo)
    • Użyj koloru #EEEEEE, aby dopasować go do otaczającego tekstu.
    • Nie dodawaj dopełnienia.
  6. Grafika plakatu (COLUMN_POSTER_ART_URI)
    • Format obrazu od 16:9 do 4:3.
Ilustracja 1. Moduł wyświetlania informacji o kanałach i programach w systemowej aplikacji TV.

Systemowa aplikacja TV udostępnia te same informacje w przewodniku po programach, w tym grafikę plakatu, jak pokazano na ilustracji 2.

Ilustracja 2. Przewodnik po programach w systemowej aplikacji TV.

Aktualizowanie danych kanału

Podczas aktualizowania istniejących danych kanału użyj metody update zamiast usuwać i ponownie dodawać dane. Aktualną wersję danych możesz zidentyfikować, używając Channels.COLUMN_VERSION_NUMBER i Programs.COLUMN_VERSION_NUMBER podczas wybierania rekordów do zaktualizowania.

Uwaga: dodawanie danych kanału do ContentProvider może zająć trochę czasu. Aktualne programy (te, które będą emitowane w ciągu 2 godzin od bieżącego czasu) dodawaj tylko wtedy, gdy skonfigurujesz EpgSyncJobService tak, aby aktualizował pozostałe dane kanału w tle. Przykład znajdziesz w przykładowej aplikacji Android TV Live TV.

Wczytywanie danych kanału w trybie wsadowym

Podczas aktualizowania systemowej bazy danych dużą ilością danych kanału użyj metody ContentResolver applyBatch lub bulkInsert. Oto przykład użycia 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();
    }
}

Asynchroniczne przetwarzanie danych kanału

Manipulowanie danymi, np. pobieranie strumienia z serwera lub uzyskiwanie dostępu do bazy danych, nie powinno blokować wątku UI. Jednym ze sposobów na asynchroniczne wykonywanie aktualizacji jest użycie AsyncTask. Na przykład podczas wczytywania informacji o kanale z serwera backendu możesz użyć 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życie WorkManager aby uruchamiać proces aktualizacji w czasie bezczynności, np. codziennie o 3:00.

Inne techniki oddzielania zadań aktualizacji danych od wątku UI obejmują użycie klasy HandlerThread lub zaimplementowanie własnej 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 uruchamianie powiązanej aktywności podczas oglądania treści kanału. Aplikacje kanałów używają linków do aplikacji, aby zwiększyć zaangażowanie użytkowników, uruchamiając aktywności, które wyświetlają powiązane informacje lub dodatkowe treści. Na przykład możesz użyć linków do aplikacji, aby:

  • zachęcić użytkownika do odkrywania i kupowania powiązanych treści;
  • udostępniać dodatkowe informacje o aktualnie odtwarzanych treściach;
  • podczas oglądania treści odcinkowych rozpocząć oglądanie następnego odcinka serii;
  • umożliwić użytkownikowi interakcję z treściami – np. ocenianie lub recenzowanie treści – bez przerywania odtwarzania.

Linki do aplikacji są wyświetlane, gdy użytkownik naciśnie Wybierz , aby wyświetlić menu telewizora podczas oglądania treści kanału.

Ilustracja 1. Przykład linku do aplikacji wyświetlanego w wierszu Kanały podczas wyświetlania treści kanału.

Gdy użytkownik wybierze link do aplikacji, system uruchomi aktywność za pomocą identyfikatora URI intencji określonego przez aplikację kanału. Treści kanału są odtwarzane, gdy aktywność linku do aplikacji jest aktywna. Użytkownik może wrócić do treści kanału, naciskając Wstecz.

Podawanie danych kanału linku do aplikacji

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 do aplikacji, określ te szczegóły w polach TvContract.Channels:

  • COLUMN_APP_LINK_COLOR – kolor uzupełniający linku aplikacji dla tego kanału. Przykład koloru uzupełniającego znajdziesz na ilustracji 2 w punkcie 3.
  • COLUMN_APP_LINK_ICON_URI – identyfikator URI ikony plakietki aplikacji linku do aplikacji dla tego kanału. Przykład ikony plakietki aplikacji znajdziesz na ilustracji 2 w punkcie 2.
  • COLUMN_APP_LINK_INTENT_URI – identyfikator URI intencji linku do aplikacji dla tego kanału. Identyfikator URI możesz utworzyć za pomocą toUri(int) z URI_INTENT_SCHEME, a następnie przekonwertować go z powrotem na pierwotną intencję za pomocą parseUri.
  • COLUMN_APP_LINK_POSTER_ART_URI – identyfikator URI grafiki plakatu używanej jako tło linku do aplikacji dla tego kanału. Przykład obrazu plakatu znajdziesz na ilustracji 2 w punkcie 1.
  • COLUMN_APP_LINK_TEXT – opisowy tekst linku do aplikacji dla tego kanału. Przykład opisu linku do aplikacji znajdziesz na ilustracji 2 w punkcie 3.
Ilustracja 2. Szczegóły linku do aplikacji.

Jeśli dane kanału nie określają informacji o linku do aplikacji, system tworzy domyślny link do aplikacji. System wybiera domyślne szczegóły w ten sposób:

  • W przypadku identyfikatora URI intencji (COLUMN_APP_LINK_INTENT_URI) system używa aktywności ACTION_MAIN dla kategorii CATEGORY_LEANBACK_LAUNCHER, która jest zwykle zdefiniowana w manifeście aplikacji. Jeśli ta aktywność nie jest zdefiniowana, wyświetla się niedziałający link do aplikacji – jeśli użytkownik go kliknie, nic się nie stanie.
  • W przypadku tekstu opisowego (COLUMN_APP_LINK_TEXT) system używa tekstu „Otwórz app-name”. Jeśli nie zdefiniowano odpowiedniego identyfikatora URI intencji linku do aplikacji, system używa tekstu „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 ekranu głównego aplikacji. Jeśli aplikacja nie udostępnia banera, system używa domyślnego obrazu aplikacji TV.
  • W przypadku ikony plakietki (COLUMN_APP_LINK_ICON_URI) system używa plakietki, która wyświetla nazwę aplikacji. Jeśli system używa też banera aplikacji lub domyślnego obrazu aplikacji jako obrazu plakatu, nie jest wyświetlana żadna plakietka aplikacji.

Szczegóły linku do aplikacji dla swoich kanałów określasz w ramach konfiguracji aplikacji. Te szczegóły linku do aplikacji możesz aktualizować w dowolnym momencie, więc jeśli link do aplikacji musi pasować do zmian kanału, zaktualizuj szczegóły linku do aplikacji i w razie potrzeby wywołaj ContentResolver.update. Więcej informacji o aktualizowaniu danych kanału znajdziesz w sekcji Aktualizowanie danych kanału.