Как работать с данными канала

Источник сигнала должен предоставлять данные электронного телегида хотя бы для одного канала. Кроме того, вам следует периодически обновлять эти данные, учитывая размер обновления и поток обработки, который его выполняет. Кроме того, вы можете добавить ссылки на приложения для каналов, которые ведут пользователя к связанному контенту и действиям. В этом уроке рассказывается о том, как создавать и обновлять данные о каналах и программах в системной базе данных с учетом этих факторов.

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

Как запросить разрешение

Чтобы источник ТВ-сигнала работал с данными телепрограммы, в его файле манифеста Android должно быть указано разрешение на запись:

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

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

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

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

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

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

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

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

В этом примере 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. Системное приложение для показа информации о телеканалах и программах.

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

Рисунок 2. Телегид системного ТВ-приложения.

Как изменить данные канала

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

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

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

При обновлении системной базы данных большим количеством данных о каналах используйте метод ContentResolver applyBatch или bulkInsert. Пример с разметкой типа 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();
    }
}

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

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

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

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

Другие способы отделить задачи обновления данных от потока UI включают использование класса 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. Подробнее о том, как обновить данные канала…