Работа с данными канала

В процессе настройки вход вашего телевизора должен предоставлять данные электронной программы передач (EPG) хотя бы для одного канала. Вам также следует периодически обновлять эти данные, учитывая размер обновления и поток обработки, который его обрабатывает. Кроме того, вы можете предоставить ссылки на приложения для каналов, которые направляют пользователя к соответствующему контенту и действиям. В этом уроке обсуждается создание и обновление данных о каналах и программах в системной базе данных с учетом этих соображений.

Попробуйте образец приложения TV Input Service .

Получить разрешение

Чтобы вход вашего телевизора мог работать с данными EPG, он должен объявить разрешение на запись в своем файле манифеста Android следующим образом:

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

Зарегистрировать каналы в базе данных

В базе данных системы Android TV хранятся записи данных о каналах для телевизионных входов. В процессе настройки для каждого из ваших каналов вы должны сопоставить данные вашего канала со следующими полями класса TvContract.Channels :

Хотя структура телевизионного ввода достаточно универсальна, чтобы без каких-либо различий обрабатывать как традиционное вещание, так и OTT-контент, вы можете определить следующие столбцы в дополнение к приведенным выше, чтобы лучше идентифицировать традиционные вещательные каналы:

Если вы хотите предоставить информацию о ссылках на приложения для своих каналов, вам необходимо обновить некоторые дополнительные поля. Дополнительные сведения о полях ссылки на приложение см. в разделе Добавление информации о ссылке на приложение .

Для телевизионных входов, основанных на потоковой передаче через Интернет, соответствующим образом назначьте свои собственные значения указанным выше, чтобы каждый канал можно было идентифицировать уникально.

Получите метаданные вашего канала (в формате XML, JSON или что-то еще) с вашего внутреннего сервера и в процессе настройки сопоставьте значения с системной базой данных следующим образом:

Котлин

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)

Ява

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

В приведенном выше примере channel — это объект, который содержит метаданные канала с внутреннего сервера.

Представление информации о канале и программе

Приложение системного ТВ предоставляет пользователям информацию о каналах и программах при их перелистывании, как показано на рис. 1. Чтобы убедиться, что информация о каналах и программах работает с презентатором информации о каналах и программах системного ТВ-приложения, следуйте приведенным ниже инструкциям.

  1. Номер канала ( COLUMN_DISPLAY_NUMBER )
  2. Значок ( android:icon в манифесте ТВ-входа)
  3. Описание программы ( COLUMN_SHORT_DESCRIPTION )
  4. Название программы ( COLUMN_TITLE )
  5. Логотип канала ( TvContract.Channels.Logo )
    • Используйте цвет #EEEEEE, чтобы он соответствовал окружающему тексту.
    • Не включать отступы
  6. Плакат ( COLUMN_POSTER_ART_URI )
    • Соотношение сторон от 16:9 до 4:3

Рис. 1. Канал системного приложения TV и презентатор информации о программе.

Приложение системного ТВ предоставляет ту же информацию через программу передач, включая плакаты, как показано на рис. 2.

Рис. 2. Программа передач приложения System TV.

Обновить данные канала

При обновлении существующих данных канала используйте метод update() вместо удаления и повторного добавления данных. Вы можете определить текущую версию данных, используя Channels.COLUMN_VERSION_NUMBER и Programs.COLUMN_VERSION_NUMBER при выборе записей для обновления.

Примечание. Добавление данных канала в ContentProvider может занять время. Добавляйте текущие программы (те, которые находятся в пределах двух часов от текущего времени) только тогда, когда вы настроите EpgSyncJobService для обновления остальных данных канала в фоновом режиме. См. пример приложения Android TV Live TV .

Пакетная загрузка данных канала

При обновлении базы данных системы большим объемом данных канала используйте метод ContentResolver applyBatch() или bulkInsert() . Вот пример использования applyBatch() :

Котлин

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

Ява

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

Обработка данных канала асинхронно

Манипулирование данными, например получение потока с сервера или доступ к базе данных, не должно блокировать поток пользовательского интерфейса. Использование AsyncTask — это один из способов асинхронного выполнения обновлений. Например, при загрузке информации о канале с внутреннего сервера вы можете использовать AsyncTask следующим образом:

Котлин

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

Ява

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

Если вам необходимо регулярно обновлять данные EPG, рассмотрите возможность использования WorkManager для запуска процесса обновления во время простоя, например каждый день в 3:00 ночи.

Другие методы отделения задач обновления данных от потока пользовательского интерфейса включают использование класса HandlerThread или вы можете реализовать свои собственные с помощью классов Looper и Handler . Дополнительные сведения см. в разделе Процессы и потоки .

Каналы могут использовать ссылки на приложения , чтобы пользователи могли легко запускать соответствующее действие во время просмотра контента канала. Приложения канала используют ссылки на приложения, чтобы расширить взаимодействие с пользователем, запуская действия, которые показывают соответствующую информацию или дополнительный контент. Например, вы можете использовать ссылки на приложения, чтобы сделать следующее:

  • Помогите пользователю найти и приобрести соответствующий контент.
  • Предоставьте дополнительную информацию о воспроизводимом в данный момент контенте.
  • Во время просмотра эпизодического контента начните просмотр следующего эпизода сериала.
  • Позвольте пользователю взаимодействовать с контентом, например оценивать или просматривать контент, не прерывая воспроизведение контента.

Ссылки на приложения отображаются, когда пользователь нажимает «Выбрать» , чтобы отобразить меню телевизора во время просмотра контента канала.

Рис. 1. Пример ссылки на приложение, отображаемой в строке «Каналы» при отображении содержимого канала.

Когда пользователь выбирает ссылку на приложение, система запускает действие, используя URI намерения, указанный приложением канала. Содержимое канала продолжает воспроизводиться, пока активность ссылки на приложение активна. Пользователь может вернуться к содержимому канала, нажав Назад .

Предоставьте данные канала ссылки на приложение.

Android TV автоматически создает ссылку на приложение для каждого канала, используя информацию из данных канала. Чтобы предоставить информацию о ссылке на приложение, укажите следующие данные в полях TvContract.Channels :

  • COLUMN_APP_LINK_COLOR – цвет акцента ссылки приложения для этого канала. Пример акцентного цвета см. на рис. 2, выноска 3.
  • COLUMN_APP_LINK_ICON_URI – URI значка значка приложения в ссылке на приложение для этого канала. Пример значка приложения см. на рис. 2, выноска 2.
  • COLUMN_APP_LINK_INTENT_URI — URI намерения ссылки приложения для этого канала. Вы можете создать URI, используя toUri(int) с URI_INTENT_SCHEME , и преобразовать URI обратно в исходное намерение с помощью parseUri() .
  • COLUMN_APP_LINK_POSTER_ART_URI – URI постера, используемого в качестве фона ссылки на приложение для этого канала. Пример изображения плаката см. на рис. 2, выноска 1.
  • COLUMN_APP_LINK_TEXT – описательный текст ссылки на приложение для этого канала. Пример описания ссылки на приложение см. в тексте на рис. 2, выноска 3.

Рисунок 2. Подробности ссылки на приложение.

Если в данных канала не указана информация о ссылке на приложение, система создает ссылку на приложение по умолчанию. Система выбирает данные по умолчанию следующим образом:

  • В качестве URI намерения ( COLUMN_APP_LINK_INTENT_URI ) система использует действие ACTION_MAIN для категории CATEGORY_LEANBACK_LAUNCHER , обычно определяемой в манифесте приложения. Если это действие не определено, появляется неработающая ссылка на приложение — если пользователь нажимает на нее, ничего не происходит.
  • В качестве описательного текста ( COLUMN_APP_LINK_TEXT ) система использует «Открыть app-name ». Если не определен жизнеспособный URI намерения ссылки на приложение, система использует сообщение «Нет доступной ссылки».
  • В качестве акцентного цвета ( COLUMN_APP_LINK_COLOR ) система использует цвет приложения по умолчанию.
  • В качестве изображения постера ( COLUMN_APP_LINK_POSTER_ART_URI ) система использует баннер главного экрана приложения. Если приложение не предоставляет баннер, система использует изображение телевизионного приложения по умолчанию.
  • В качестве значка значка ( COLUMN_APP_LINK_ICON_URI ) система использует значок, на котором отображается название приложения. Если система также использует баннер приложения или изображение приложения по умолчанию в качестве изображения плаката, значок приложения не отображается.

Вы указываете сведения о ссылках на приложение для своих каналов в процессе настройки приложения. Вы можете обновить эти сведения о ссылке на приложение в любой момент, поэтому, если ссылка на приложение должна соответствовать изменениям канала, обновите сведения о ссылке на приложение и при необходимости вызовите ContentResolver.update() . Дополнительные сведения об обновлении данных канала см. в разделе Обновление данных канала .