Un servicio de entrada de TV representa una fuente de transmisión multimedia y te permite presentar el contenido multimedia como se transmite en la TV, es decir, de forma lineal, con canales y programas. Con un servicio de entrada de TV, puedes proporcionar controles parentales, información de la guía de programas y clasificaciones de contenidos. El servicio de entrada de TV funciona con la app de TV del sistema Android. En última instancia, esta app controla y presenta contenido de canales en la TV. La app de TV del sistema se desarrolló específicamente para el dispositivo y no puede ser reemplazada por apps de terceros. Para obtener más información sobre la arquitectura del marco de trabajo de entrada de TV (TIF) y sus componentes, consulta Marco de trabajo de entrada de TV.
Cómo crear un servicio de entrada de TV usando la Biblioteca complementaria del TIF
La Biblioteca complementaria del TIF es un marco de trabajo que proporciona implementaciones extensibles de funciones comunes de servicio de entrada de TV. El propósito es que los OEM lo usen para crear canales para Android 5.0 (nivel 21 de API) hasta Android 7.1 (nivel 25 de API) únicamente.
Cómo actualizar tu proyecto
La Biblioteca complementaria del TIF está disponible para el uso heredado por parte de los OEM en el repositorio androidtv-sample-inputs. Consulta ese repositorio para ver un ejemplo de cómo incluir la biblioteca en una app.
Cómo declarar tu servicio de entrada de TV en el manifiesto
Tu app debe proporcionar un servicio compatible con TvInputService que el sistema utilice para acceder a ella. La Biblioteca complementaria del TIF proporciona la clase BaseTvInputService, que ofrece una implementación predeterminada y personalizable de TvInputService. Crea una subclase de BaseTvInputService y declárala en tu manifiesto como servicio.
Dentro de la declaración del manifiesto, especifica el permiso BIND_TV_INPUT para permitir que el servicio conecte la entrada de TV al sistema. Un servicio del sistema realiza la vinculación y cuenta con el permiso BIND_TV_INPUT.
La app de TV del sistema envía solicitudes a los servicios de entrada de TV mediante la interfaz de TvInputManager.
En tu declaración de servicio, incluye un filtro de intents que especifique TvInputService como la acción que se va a realizar con el intent. También declara los metadatos del servicio como recurso XML independiente. La declaración del servicio, el filtro de intents y la declaración de metadatos del servicio se muestran en el siguiente ejemplo:
<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>
Define los metadatos del servicio en un archivo XML independiente. El archivo XML de metadatos del servicio debe incluir una interfaz de configuración que describa la configuración inicial de la entrada de TV y la búsqueda de canales. El archivo de metadatos también debe contener una marca que indique si los usuarios pueden grabar contenido o no. Para obtener más información sobre cómo admitir la grabación de contenido en tu app, consulta Cómo admitir la grabación de contenido.
El archivo de metadatos del servicio se encuentra en el directorio de recursos de XML de tu app y debe coincidir con el nombre del recurso que declaraste en el manifiesto. Con las entradas del manifiesto del ejemplo anterior, puedes crear un archivo en formato XML en res/xml/richtvinputservice.xml, con los siguientes contenidos:
<?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" />
Cómo definir canales y crear tu actividad de configuración
El servicio de entrada de TV debe definir al menos un canal al que los usuarios puedan acceder mediante la app de TV del sistema. Deberías registrar los canales en la base de datos del sistema y proporcionar una actividad de configuración que el sistema pueda invocar cuando no encuentre un canal para tu app.
Primero, habilita tu app para lectura y escritura en la Guía de programación electrónica (EPG) del sistema, cuyos datos incluyen canales y programas disponibles para el usuario. Si quieres habilitar la app para que lleve a cabo estas acciones y sincronizarla con la EPG después del reinicio del dispositivo, agrega los siguientes elementos en el manifiesto de la app:
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED "/>
Agrega el siguiente elemento para asegurarte de que la app aparezca en Google Play Store como app que proporciona canales de contenido en Android TV:
<uses-feature android:name="android.software.live_tv" android:required="true" />
A continuación, crea una clase que extienda la clase EpgSyncJobService. Esta clase abstracta te permite crear fácilmente un servicio de trabajo que crea y actualiza canales en la base de datos del sistema.
En tu subclase, crea y muestra tu lista completa de canales en getChannels. Si tus canales provienen de un archivo XMLTV, usa la clase XmlTvParser. De lo contrario, genera canales de manera programática usando la clase Channel.Builder.
Para cada canal, el sistema llama a getProgramsForChannel cuando necesita una lista de programas que se puedan ver dentro de un período determinado en el canal. Muestra una lista de objetos Program para el canal. Usa la clase XmlTvParser para obtener programas desde un archivo XMLTV o genéralos de manera programática usando la clase Program.Builder.
Por cada objeto Program, usa un objeto InternalProviderData para establecer información del programa, como el tipo de video del programa. Si solo quieres que el canal repita indefinidamente una cantidad limitada de programas, usa el método InternalProviderData.setRepeatable con un valor de true cuando configures la información sobre el programa.
Después de implementar el servicio de trabajo, agrégalo al manifiesto de la app:
<service android:name=".sync.SampleJobService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true" />
Finalmente, crea una actividad de configuración. Esta debería proporcionar una manera de sincronizar los datos del canal y los del programa. Una forma de hacerlo es que el usuario lo haga con la IU en la actividad. También puedes hacer que la app lo haga automáticamente cuando se inicia la actividad. Cuando la actividad de configuración necesite sincronizar la información del canal y del programa, la app deberá iniciar el servicio de trabajo:
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));
Usa el método requestImmediateSync para sincronizar el servicio de trabajo. El usuario debe esperar que finalice la sincronización, de modo que debes mantener un período de solicitud relativamente corto.
Usa el método setUpPeriodicSync para hacer que el servicio de trabajo sincronice de manera regular los datos del canal y del programa en segundo plano:
Kotlin
EpgSyncJobService.setUpPeriodicSync( context, inputId, ComponentName(context, SampleJobService::class.java) )
Java
EpgSyncJobService.setUpPeriodicSync(context, inputId, new ComponentName(context, SampleJobService.class));
La Biblioteca complementaria del TIF proporciona un método adicional de requestImmediateSync sobrecargado que te permite especificar la duración en milisegundos de los datos del canal que se van a sincronizar. El método predeterminado sincroniza una hora de datos del canal.
La Biblioteca complementaria del TIF proporciona un método adicional de setUpPeriodicSync sobrecargado que te permite especificar la duración de los datos del canal que se van a sincronizar y con qué frecuencia se realizará la sincronización regular. El método predeterminado sincroniza 48 horas de datos del canal cada 12 horas.
Para obtener más detalles sobre los datos del canal y la EPG, consulta Cómo trabajar con datos del canal.
Cómo manejar solicitudes de sintonización y reproducción de contenido multimedia
Cuando un usuario selecciona un canal específico, la app de TV del sistema usa una Session, creada por tu app, para sintonizar el canal solicitado y reproducir contenido. La Biblioteca complementaria del TIF proporciona varias clases que puedes extender para manejar las llamadas de canal y sesión desde el sistema.
La subclase BaseTvInputService crea sesiones que manejan solicitudes de sintonización. Anula el método onCreateSession, crea una sesión extendida desde la clase BaseTvInputService.Session y llama a super.sessionCreated con tu nueva sesión. En el siguiente ejemplo, onCreateSession muestra un objeto RichTvInputSessionImpl que extiende 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; }
Cuando el usuario utiliza la app de TV del sistema para comenzar a ver uno de tus canales, el sistema llama al método onPlayChannel de tu sesión. Anula este método si necesitas realizar una inicialización de canal especial antes de que el programa comience a reproducir.
Luego, el sistema obtiene el programa agendado, llama al método onPlayProgram de tu sesión y especifica la información del programa y el tiempo de inicio en milisegundos. Usa la interfaz TvPlayer para comenzar a reproducir el programa.
El código de reproductor multimedia debería implementar TvPlayer para controlar eventos de reproducción específicos. La clase TvPlayer maneja funciones como controles con pausa en directo sin agregar complejidad a tu implementación de BaseTvInputService.
En el método getTvPlayer de tu sesión, muestra
el reproductor multimedia que implementa TvPlayer. La app de ejemplo del
servicio de entrada de TV implementa un reproductor multimedia que usa
ExoPlayer.
Cómo crear un servicio de entrada de TV con el marco de trabajo de entrada de TV
Si tu servicio de entrada de TV no puede usar la Biblioteca complementaria del TIF, debes implementar los siguientes complementos:
TvInputServiceproporciona disponibilidad de larga duración y en segundo plano para la entrada de TV.TvInputService.Sessionmantiene el estado de la entrada de TV y se comunica con la app host.TvContractdescribe los canales y programas disponibles para la entrada de TVTvContract.Channels, que representa información acerca de un canal de TV.TvContract.Programsdescribe un programa de TV con datos como el título del programa y la hora de inicio.TvTrackInfo, que representa una pista de audio, video o subtítulo.TvContentRatingdescribe una clasificación de contenido y permite usar esquemas personalizados de clasificación de contenido .TvInputManagerproporciona una API a la app de TV del sistema y administra la interacción con las entradas y apps de TV.
También debes realizar lo siguiente:
- Declara tu servicio de entrada de TV en el manifiesto, como se describe en Cómo declarar tu servicio de entrada de TV en el manifiesto.
- Crea el archivo de metadatos del servicio.
- Crea y registra la información del canal y el programa.
- Crea la actividad de configuración.
Cómo definir tu servicio de entrada de TV
Para tu servicio, extiende la clase TvInputService. Una
TvInputService implementación es un
servicio vinculado en el que el servicio del sistema
es el cliente que lo vincula. En la figura 1, se enumeran los métodos de ciclo de vida de servicio que debes implementar.
El método onCreate inicializa e inicia el HandlerThread, que proporciona un subproceso separado del subproceso de IU a fin de controlar acciones impulsadas por el sistema. En el siguiente ejemplo, el método onCreate inicializa el CaptioningManager y se prepara para controlar las acciones ACTION_BLOCKED_RATINGS_CHANGED y ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED. Estas acciones describen a los intents del sistema que se activan cuando el usuario cambia la configuración de control parental y cuando hay un cambio en la lista de clasificaciones bloqueadas.
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); }
Figura 1: Ciclo de vida de TvInputService
Consulta
Cómo controlar el contenido para obtener más información sobre cómo trabajar con contenido bloqueo y proporcionar
control parental. Consulta TvInputManager para conocer más acciones impulsadas por el sistema que puedes controlar en tu servicio de entrada de TV.
La clase TvInputService crea una
TvInputService.Session que implementa Handler.Callback
para manejar los cambios de estado del reproductor. Con
onSetSurface,
el TvInputService.Session establece la clase Surface con el
contenido del video. Consulta Cómo integrar el reproductor con Surface
para obtener más información sobre cómo trabajar con Surface en el procesamiento de videos.
La clase TvInputService.Session controla el evento onTune cuando el usuario selecciona un canal y notifica a la app de TV del sistema cuando hay cambios en el contenido y los metadatos del contenido. Más adelante en esta capacitación, se ofrece una descripción de estos métodos notify en
Cómo controlar el contenido y Cómo controlar la selección de pistas.
Cómo definir la actividad de configuración
La app de TV del sistema funciona con la actividad de configuración que defines para tu entrada de TV. La actividad de configuración es obligatoria y debe proporcionar al menos un registro de canal para la base de datos del sistema. La app de TV del sistema invoca la actividad de configuración cuando no puede encontrar un canal para la entrada de TV.
La actividad de seguridad describe a la app de TV los canales disponibles a través de la entrada de TV, como se demuestra en la siguiente lección, Cómo crear y actualizar datos del canal.