Разработка сервиса ТВ-входа

Сервис ввода ТВ представляет собой источник медиапотока и позволяет представлять медиаконтент в виде каналов и программ, как на обычном телевидении. С помощью сервиса ввода ТВ вы можете предоставлять родительский контроль, информацию о телепрограмме и рейтинги контента. Сервис ввода ТВ работает с системным ТВ-приложением Android. Это приложение управляет контентом каналов и показывает его на телевизоре. Системное ТВ-приложение разработано специально для устройства и не может быть изменено сторонними приложениями. Подробную информацию об архитектуре TV Input Framework (TIF) и ее компонентах можно найти в статье TV Input Framework.

Как создать сервис ввода ТВ с помощью библиотеки TIF Companion Library

TIF Companion Library – это фреймворк, который предоставляет расширяемые реализации распространенных функций сервиса ввода ТВ. Он предназначен для OEM-производителей, которые создают каналы для Android 5.0 (уровень API 21) – Android 7.1 (уровень API 25).

Обновите проект

Сопутствующая библиотека TIF доступна для использования OEM-производителями в репозитории androidtv-sample-inputs. В этом репозитории вы найдете пример того, как включить библиотеку в приложение.

Объявите сервис ввода ТВ в манифесте

Приложение должно предоставлять совместимый с TvInputService сервис, который система использует для доступа к приложению. В библиотеке TIF Companion Library есть класс BaseTvInputService, который предоставляет реализацию TvInputService по умолчанию. Ее можно настроить. Создайте подкласс BaseTvInputService и объявите его в манифесте как сервис.

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

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

<service android:name=".rich.RichTvInputService"
    android:label="@string/rich_input_label"
    android:permission="android.permission.BIND_TV_INPUT">
    <!-- Required filter used by the system to launch our account service. -->
    <intent-filter>
        <action android:name="android.media.tv.TvInputService" />
    </intent-filter>
    <!-- An XML file which describes this input. This provides pointers to
    the RichTvInputSetupActivity to the system/TV app. -->
    <meta-data
        android:name="android.media.tv.input"
        android:resource="@xml/richtvinputservice" />
</service>

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

Файл метаданных сервиса находится в каталоге ресурсов XML вашего приложения и должен соответствовать названию ресурса, объявленного в манифесте. Используя записи манифеста из предыдущего примера, вы создадите XML-файл в res/xml/richtvinputservice.xml со следующим содержимым:

<?xml version="1.0" encoding="utf-8"?>
<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
  android:canRecord="true"
  android:setupActivity="com.example.android.sampletvinput.rich.RichTvInputSetupActivity" />

Определите каналы и создайте действие по настройке

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

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

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

Добавьте следующий элемент, чтобы ваше приложение показывалось в Google Play как приложение, предоставляющее каналы контента в Android TV:

<uses-feature
    android:name="android.software.live_tv"
    android:required="true" />

Затем создайте класс, расширяющий класс EpgSyncJobService. Этот абстрактный класс позволяет создать сервис заданий, который создает и обновляет каналы в системной базе данных.

В подклассе создайте и верните полный список каналов в getChannels. Если каналы поступают из файла XMLTV, используйте класс XmlTvParser. В противном случае создайте каналы программно с помощью класса Channel.Builder.

Для каждого канала система вызывает getProgramsForChannel, когда ей нужен список программ, которые можно посмотреть на канале в течение заданного периода времени. Возвращает список объектов Program для канала. Используйте класс XmlTvParser, чтобы получить программы из файла XMLTV, или создайте их программно с помощью класса Program.Builder.

Для каждого объекта Program используйте объект InternalProviderData, чтобы задать информацию о программе, например тип видео. Если вы хотите, чтобы на канале повторялось ограниченное количество программ, используйте метод InternalProviderData.setRepeatable со значением true при настройке информации о программе.

После реализации сервиса заданий добавьте его в манифест приложения:

<service
    android:name=".sync.SampleJobService"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:exported="true" />

Наконец, создайте действие по настройке. В действии настройки должна быть предусмотрена возможность синхронизации данных о каналах и программах. Один из способов – сделать это через интерфейс в действии. Вы также можете настроить приложение так, чтобы оно делало это автоматически в начале тренировки. Когда приложению нужно синхронизировать информацию о каналах и программах, оно должно запустить сервис заданий:

Kotlin

val inputId = getActivity().intent.getStringExtra(TvInputInfo.EXTRA_INPUT_ID)
EpgSyncJobService.cancelAllSyncRequests(getActivity())
EpgSyncJobService.requestImmediateSync(
        getActivity(),
        inputId,
        ComponentName(getActivity(), SampleJobService::class.java)
)

Java

String inputId = getActivity().getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
EpgSyncJobService.cancelAllSyncRequests(getActivity());
EpgSyncJobService.requestImmediateSync(getActivity(), inputId,
        new ComponentName(getActivity(), SampleJobService.class));

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

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

Kotlin

EpgSyncJobService.setUpPeriodicSync(
        context,
        inputId,
        ComponentName(context, SampleJobService::class.java)
)

Java

EpgSyncJobService.setUpPeriodicSync(context, inputId,
        new ComponentName(context, SampleJobService.class));

В библиотеке TIF Companion Library есть дополнительный перегруженный метод requestImmediateSync, который позволяет указать продолжительность синхронизации данных канала в миллисекундах. По умолчанию синхронизируются данные за один час.

В библиотеке TIF Companion Library также есть дополнительный перегруженный метод setUpPeriodicSync, который позволяет указать продолжительность синхронизации данных канала и частоту периодической синхронизации. По умолчанию синхронизируются данные за 48 часов каждые 12 часов.

Подробнее о данных каналов и программе передач см. Работа с данными каналов.

Обработка запросов на настройку и воспроизведение медиаконтента

Когда пользователь выбирает определенный канал, системное ТВ-приложение использует Session, созданный вашим приложением, чтобы настроиться на нужный канал и воспроизвести контент. В библиотеке TIF Companion Library есть несколько классов, которые можно расширить для обработки вызовов каналов и сеансов из системы.

Подкласс BaseTvInputService создает сеансы, которые обрабатывают запросы на настройку. Переопределите метод onCreateSession, создайте сеанс, расширенный из класса BaseTvInputService.Session, и вызовите super.sessionCreated с новым сеансом. В примере ниже onCreateSession возвращает объект RichTvInputSessionImpl, который расширяет BaseTvInputService.Session:

Kotlin

override fun onCreateSession(inputId: String): Session =
        RichTvInputSessionImpl(this, inputId).apply {
            setOverlayViewEnabled(true)
        }

Java

@Override
public final Session onCreateSession(String inputId) {
    RichTvInputSessionImpl session = new RichTvInputSessionImpl(this, inputId);
    session.setOverlayViewEnabled(true);
    return session;
}

Когда пользователь запускает просмотр одного из ваших каналов в системном ТВ-приложении, система вызывает метод onPlayChannel сеанса. Переопределите этот метод, если вам нужно выполнить специальную инициализацию канала перед началом воспроизведения программы.

Затем система получает текущую запланированную программу и вызывает метод onPlayProgram вашего сеанса, указывая информацию о программе и время начала в миллисекундах. Запустите программу в интерфейсе TvPlayer.

В коде проигрывателя должен быть реализован класс TvPlayer, который обрабатывает определенные события воспроизведения. Класс TvPlayer обрабатывает такие функции, как управление сдвигом по времени, не усложняя реализацию BaseTvInputService.

В методе getTvPlayer сеанса верните медиапроигрыватель, который реализует TvPlayer. В примере приложения TV Input Service реализован медиапроигрыватель, использующий ExoPlayer.

Создайте сервис ТВ-входа с помощью фреймворка TV Input Framework.

Если ваш сервис ТВ-входа не может использовать сопутствующую библиотеку TIF, вам необходимо реализовать следующие компоненты:

  • TvInputService обеспечивает длительную работу и доступность в фоновом режиме для ТВ-входа.
  • TvInputService.Session поддерживает состояние ТВ-входа и взаимодействует с приложением, в котором он размещен.
  • TvContract описывает каналы и программы, доступные для ТВ-входа.
  • TvContract.Channels – информация о телеканале.
  • TvContract.Programs описывает телепрограмму с такими данными, как название программы и время начала.
  • TvTrackInfo – аудио- или видеодорожка либо дорожка субтитров.
  • TvContentRating – описывает возрастное ограничение, позволяет использовать собственные схемы возрастных ограничений.
  • TvInputManager предоставляет API для системного ТВ-приложения и управляет взаимодействием с источниками ТВ-сигнала и приложениями.

Также необходимо сделать следующее:

  1. Объявите сервис в манифесте, как описано в разделе Объявление сервиса в манифесте.
  2. Создайте файл метаданных сервиса.
  3. Создайте и зарегистрируйте информацию о канале и программе.
  4. Создайте действие по настройке.

Определите сервис источника ТВ-сигнала

Для своего сервиса вы расширяете класс TvInputService. Реализация TvInputService – это привязанная служба, в которой системная служба является клиентом, привязанным к ней. Методы жизненного цикла сервиса, которые необходимо реализовать, показаны на рисунке 1.

Метод onCreate инициализирует и запускает HandlerThread, который предоставляет поток процесса, отдельный от потока UI, для обработки действий, управляемых системой. В примере ниже метод onCreate инициализирует CaptioningManager и подготавливает обработку действий ACTION_BLOCKED_RATINGS_CHANGED и ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED. Эти действия описывают системные намерения, которые запускаются, когда пользователь меняет настройки родительского контроля и когда меняется список заблокированных возрастных ограничений.

Kotlin

override fun onCreate() {
    super.onCreate()
    handlerThread = HandlerThread(javaClass.simpleName).apply {
        start()
    }
    dbHandler = Handler(handlerThread.looper)
    handler = Handler()
    captioningManager = getSystemService(Context.CAPTIONING_SERVICE) as CaptioningManager

    setTheme(android.R.style.Theme_Holo_Light_NoActionBar)

    sessions = mutableListOf<BaseTvInputSessionImpl>()
    val intentFilter = IntentFilter().apply {
        addAction(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED)
        addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED)
    }
    registerReceiver(broadcastReceiver, intentFilter)
}

Java

@Override
public void onCreate() {
    super.onCreate();
    handlerThread = new HandlerThread(getClass()
      .getSimpleName());
    handlerThread.start();
    dbHandler = new Handler(handlerThread.getLooper());
    handler = new Handler();
    captioningManager = (CaptioningManager)
      getSystemService(Context.CAPTIONING_SERVICE);

    setTheme(android.R.style.Theme_Holo_Light_NoActionBar);

    sessions = new ArrayList<BaseTvInputSessionImpl>();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(TvInputManager
      .ACTION_BLOCKED_RATINGS_CHANGED);
    intentFilter.addAction(TvInputManager
      .ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
    registerReceiver(broadcastReceiver, intentFilter);
}

Рис. 1. Жизненный цикл TvInputService.

Подробнее о том, как управлять заблокированным контентом и использовать родительский контроль. Дополнительные действия, управляемые системой, которые вы можете обрабатывать в сервисе ТВ-входа, описаны в TvInputManager.

TvInputService создает TvInputService.Session, который реализует Handler.Callback для обработки изменений состояния проигрывателя. При использовании onSetSurface TvInputService.Session задает Surface с видеоконтентом. Подробнее о том, как использовать Surface для воспроизведения видео, читайте в разделе Интеграция проигрывателя с поверхностью.

TvInputService.Session обрабатывает событие onTune, когда пользователь выбирает канал, и уведомляет системное ТВ-приложение об изменениях в контенте и его метаданных. Эти методы notify описаны в разделах Управление контентом и Выбор дорожки далее в этом курсе.

Определите действие по настройке

Системное ТВ-приложение работает с действием настройки, которое вы определяете для своего ТВ-входа. Действие по настройке является обязательным и должно предоставлять по крайней мере одну запись канала для системной базы данных. Системное ТВ-приложение вызывает действие настройки, когда не может найти канал для ТВ-входа.

В действии настройки системному ТВ-приложению передаются сведения о каналах, доступных через ТВ-вход. Подробнее об этом рассказывается в следующем уроке – Создание и обновление данных о каналах.

Дополнительные ссылки