La entrada de TV debe proporcionar los datos de la Guía electrónica de programas (EPG) para al menos un canal en su actividad de configuración. Además, debes actualizar periódicamente esos datos teniendo en cuenta el tamaño de la actualización y el subproceso de procesamiento que la controla. Además, puedes proporcionar vínculos de app para los canales que guían al usuario al contenido y las actividades relacionados. En esta lección, se analiza la creación y actualización de datos de canales y programas en la base de datos del sistema con estas consideraciones en mente.
Prueba la app de ejemplo de TV Input Service.
Obtén el permiso
Para que la entrada de TV funcione con los datos de la EPG, debe declarar el permiso de escritura en el archivo de manifiesto de Android de la siguiente manera:
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
Registra los canales en la base de datos
La base de datos del sistema de Android TV guarda registros de los datos de los canales para las entradas de TV. En la actividad de configuración, para cada uno de los canales, debes asignar los datos de tu canal a los siguientes campos de la clase TvContract.Channels
:
COLUMN_DISPLAY_NAME
: Es el nombre del canal que se muestra.COLUMN_DISPLAY_NUMBER
: El número de canal que se muestraCOLUMN_INPUT_ID
: El ID del servicio de entrada de TVCOLUMN_SERVICE_TYPE
: El tipo de servicio del canalCOLUMN_TYPE
: El tipo estándar de transmisión del canalCOLUMN_VIDEO_FORMAT
: El formato de video predeterminado para el canal
Si bien el framework de entrada de TV es lo suficientemente genérico como para controlar el contenido de transmisión tradicional y el contenido de transmisión libre (OTT) sin ninguna distinción, te recomendamos que definas las siguientes columnas, además de las anteriores, para identificar mejor los canales de emisión tradicionales:
COLUMN_ORIGINAL_NETWORK_ID
: ID de red televisivaCOLUMN_SERVICE_ID
: ID del servicioCOLUMN_TRANSPORT_STREAM_ID
: ID de la corriente de transporte
Si quieres proporcionar detalles del vínculo de la app para tus canales, debes actualizar algunos campos adicionales. Para obtener más información sobre los campos del vínculo de la app, consulta Cómo agregar información del vínculo de la app.
Para las entradas de TV basadas en transmisión de Internet, asigna tus propios valores según corresponda para que cada canal se pueda identificar de manera única.
Obtén los metadatos de tu canal (en XML, JSON o cualquier otro) del servidor de backend y, en tu actividad de configuración, asigna los valores a la base de datos del sistema de la siguiente manera:
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);
En el ejemplo anterior, channel
es un objeto que conserva los metadatos del canal desde el servidor de backend.
Presenta la información de los canales y los programas
La app de TV del sistema presenta información sobre los canales y los programas a los usuarios a medida que pasan de un canal a otro, como se muestra en la Figura 1. Para asegurarte de que la información del canal y el programa funcione con el presentador de la información de canales y programas de la app de TV del sistema, sigue los lineamientos que se indican a continuación.
- Número de canal (
COLUMN_DISPLAY_NUMBER
) - Ícono (
android:icon
en el manifiesto de la entrada de TV) - Descripción del programa (
COLUMN_SHORT_DESCRIPTION
) - Título del programa (
COLUMN_TITLE
) - Logotipo del canal (
TvContract.Channels.Logo
)- Usa el color #EEEEEE para que coincida con el texto adyacente.
- No incluyas relleno.
- Afiche de arte (
COLUMN_POSTER_ART_URI
)- La relación de aspecto debe ser entre 16:9 y 4.3.
![](https://developer.android.com/static/images/tv/channel-info.png?authuser=7&hl=es)
Figura 1: El presentador de la información de programas y canales de la app para TV del sistema
La app para TV del sistema proporciona la misma información a través de la guía de programas, incluido el afiche, como se muestra en la Figura 2.
![](https://developer.android.com/static/images/tv/prog-guide.png?authuser=7&hl=es)
Figura 2: La guía de programas de la app para TV del sistema
Actualiza los datos de los canales
Cuando actualices los datos de los canales existentes, usa el método update()
en lugar de borrar y volver a agregar los datos. Para identificar la versión actual de los datos, puedes usar Channels.COLUMN_VERSION_NUMBER
y Programs.COLUMN_VERSION_NUMBER
cuando elijas los registros que se actualizarán.
Nota: Agregar datos de canales a ContentProvider
puede llevar tiempo. Agrega los programas actuales (los que se encuentren dentro de las dos horas posteriores a la hora actual) solo cuando configures tu EpgSyncJobService
para actualizar el resto de los datos de canales en segundo plano. Consulta la
app de ejemplo de TV en vivo de Android TV.
Carga los datos de los canales en lote
Cuando actualices la base de datos del sistema con una gran cantidad de datos de canales, usa el método ContentResolver
applyBatch()
o bulkInsert()
. A continuación, se muestra un ejemplo con el uso de 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(); } }
Procesa los datos de los canales de forma simultánea
La manipulación de datos, como la recuperación de una transmisión desde el servidor o el acceso a la base de datos, no debe bloquear el subproceso de IU. Usar AsyncTask
es una manera de realizar actualizaciones de forma asíncrona. Por ejemplo, cuando cargas información de canales desde un servidor de backend, puedes usar AsyncTask
de la siguiente manera:
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(); } } } }
Si necesitas actualizar los datos de EPG con regularidad, considera usar WorkManager
para ejecutar el proceso de actualización durante el tiempo de inactividad, por ejemplo, todos los días a las 3:00 a.m.
Otras técnicas para separar las tareas de actualización de datos del subproceso de IU incluyen el uso de la clase HandlerThread
, o bien puedes implementar la tuya con las clases Looper
y Handler
. Consulta
Procesos y subprocesos para obtener más información.
Agrega información de vínculos de app
Los canales pueden usar vínculos de app para que los usuarios puedan iniciar fácilmente una actividad relacionada mientras miran el contenido de un canal. Las apps de canal usan vínculos de app para extender la participación de los usuarios mediante el lanzamiento de actividades que muestran información relacionada o contenido adicional. Por ejemplo, puedes usar vínculos de apps para lo siguiente:
- Guiar al usuario para que descubra y compre contenido relacionado
- Proporcionar información adicional acerca del contenido de reproducción actual
- Mientras miras contenido de episodios, comienza a ver el siguiente episodio de la serie.
- Permitir que el usuario interactúe con el contenido (por ejemplo, calificar o dejar una opinión) sin interrumpir la reproducción
Los vínculos de apps se muestran cuando el usuario presiona Seleccionar para mostrar el menú de la TV mientras mira el contenido del canal.
![](https://developer.android.com/static/images/training/tv/tif/app-link.png?authuser=7&hl=es)
Figura 1: Ejemplo de vínculo de app que se muestra en la fila Canales mientras se muestra el contenido del canal
Cuando el usuario selecciona el vínculo de la app, el sistema inicia una actividad con un URI de intent especificado por la app del canal. El contenido del canal se sigue reproduciendo mientras la actividad del vínculo de la app está activa. Para regresar al contenido del canal, el usuario puede presionar Atrás.
Proporciona datos de canales del vínculo de app
Android TV crea automáticamente un vínculo de app para cada canal con la información de los datos de canales. Para proporcionar información de vínculo de app, especifica los siguientes detalles en los campos TvContract.Channels
:
COLUMN_APP_LINK_COLOR
: Es el color de acento del vínculo de la app de este canal. Para ver un ejemplo de color de elementos destacados, consulta la Figura 2, leyenda 3.COLUMN_APP_LINK_ICON_URI
: Es el URI del ícono de insignia del vínculo de app de este canal. Si deseas ver un ejemplo de ícono de insignia de la app, consulta la Figura 2, leyenda 2.COLUMN_APP_LINK_INTENT_URI
: Es el URI de intent del vínculo de app de este canal. Puedes crear el URI usandotoUri(int)
conURI_INTENT_SCHEME
y volver a convertirlo en el intent original conparseUri()
.COLUMN_APP_LINK_POSTER_ART_URI
: Es el URI del material gráfico de póster usado como fondo del vínculo de app de este canal. Para ver un ejemplo de imagen de póster, consulta la figura 2, leyenda 1.COLUMN_APP_LINK_TEXT
: Es el texto descriptivo del vínculo de la app de este canal. Para ver un ejemplo de una descripción de vínculo de app, consulta el texto de la Figura 2, leyenda 3.
![](https://developer.android.com/static/images/training/tv/tif/app-link-diagram.png?authuser=7&hl=es)
Figura 2: Detalles de vínculo de app
Si los datos del canal no especifican información de vínculo de app, el sistema crea un vínculo de app predeterminado. El sistema elige los detalles predeterminados de la siguiente manera:
- En el caso del URI del intent (
COLUMN_APP_LINK_INTENT_URI
), el sistema usa la actividadACTION_MAIN
para la categoríaCATEGORY_LEANBACK_LAUNCHER
, que por lo general se define en el manifiesto de la app. Si no se define esta actividad, se muestra un vínculo de app que no funciona; si el usuario hace clic en él, no sucede nada. - Para el texto descriptivo (
COLUMN_APP_LINK_TEXT
), el sistema usa "Abrir app-name". Si no se define un URI de intent de vínculo de app viable, el sistema usa "No hay ningún vínculo disponible". - Para el color de los elementos destacados (
COLUMN_APP_LINK_COLOR
), el sistema usa el color predeterminado de la app. - Para la imagen de póster (
COLUMN_APP_LINK_POSTER_ART_URI
), el sistema usa el banner de la pantalla principal de la app. Si la app no proporciona un banner, el sistema usa una imagen de app para TV predeterminada. - Para el ícono de insignia (
COLUMN_APP_LINK_ICON_URI
), el sistema usa una insignia que muestra el nombre de la app. Si el sistema también usa el banner de app o la imagen de app predeterminada para la imagen de póster, no se muestra ninguna insignia de app.
Debes especificar los detalles del vínculo de la app para tus canales en la actividad de configuración de la app. Puedes actualizar estos detalles de vínculo de la app en cualquier momento. Por lo tanto, si un vínculo de app debe coincidir con los cambios de canal, actualiza los detalles de vínculo de la app y llama a ContentResolver.update()
según sea necesario. Para obtener más información sobre los datos del canal, consulta Cómo actualizar los datos de los canales.