La entrada de TV debe proporcionar los datos de la Guía electrónica de programas (EPG) durante al menos un canal en su actividad de configuración. Además, debes actualizar periódicamente que datos, teniendo en cuenta el tamaño de la actualización y el subproceso de procesamiento que lo maneja. Además, puedes proporcionar vínculos de apps para los canales que guían al usuario hacia el contenido y las actividades relacionadas. Esta lección trata sobre la creación y actualización de datos de canales y programas en la de 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 datos EPG, debe declarar el el permiso de escritura en su 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 tu configuración
actividad, para cada uno de tus canales, debes asignar los datos de tu canal a los siguientes campos del
Clase TvContract.Channels
:
COLUMN_DISPLAY_NAME
: el nombre que se muestra del elemento canalCOLUMN_DISPLAY_NUMBER
: el canal que se muestra númeroCOLUMN_INPUT_ID
: El ID del servicio de entrada de TVCOLUMN_SERVICE_TYPE
: El tipo de servicio del canalCOLUMN_TYPE
: El estándar de transmisión del canal tipoCOLUMN_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 gestionar transmisiones tradicionales y de transmisión libre (OTT) sin ninguna distinción, podrías definir las siguientes columnas en además de los mencionados anteriormente para identificar mejor los canales de transmisión tradicionales:
COLUMN_ORIGINAL_NETWORK_ID
: la televisión ID de redCOLUMN_SERVICE_ID
: ID del servicioCOLUMN_TRANSPORT_STREAM_ID
: el flujo de transporte ID
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 aplicación, consulta Agrega información de vínculo de app.
Para las entradas de TV basadas en transmisión por Internet, asigna tus propios valores según corresponda para lo siguiente: cada canal puede identificarse de forma única.
Extrae los metadatos de tu canal (en XML, JSON o cualquier otro) desde tu servidor de backend y en tu configuración. La actividad 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 contiene metadatos del canal de la
servidor de backend.
Presenta la información de los canales y los programas
La app de TV del sistema presenta información de canales y programas a los usuarios mientras pasan de un canal a otro. como se muestra en la figura 1. Para asegurarte de que la información del canal y del programa funciona con la aplicación de TV del sistema, presentador de información de canales y programas, sigue los lineamientos que aparecen a continuación.
- Número de canal (
COLUMN_DISPLAY_NUMBER
) - Ícono
(
android:icon
en 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?hl=es-419)
Figura 1: El presentador de la información de programas y canales de la app para TV del sistema
La app de TV del sistema proporciona la misma información a través de la guía de programas, incluidos los afiches, como se muestra en la figura 2.
![](https://developer.android.com/static/images/tv/prog-guide.png?hl=es-419)
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
update()
en lugar de borrar y volver a agregar los datos. Puedes identificar la versión actual de los datos
mediante Channels.COLUMN_VERSION_NUMBER
y Programs.COLUMN_VERSION_NUMBER
.
a la hora de elegir
los registros para actualizar.
Nota: Agrega datos del canal a ContentProvider
puede llevar tiempo. Agregar programas actuales (los que tengan dos horas a partir de la hora actual)
solo cuando configures tu EpgSyncJobService
para actualizar el resto
de los datos del canal en segundo plano. Consulta
el
App de ejemplo de Android TV Live 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 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, debe
no bloquees el subproceso de IU. Usar AsyncTask
es uno
de realizar actualizaciones de forma asíncrona. Por ejemplo, cuando cargas información
de canales desde un servidor 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
HandlerThread
. También puedes implementar una propia con 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 lanzar fácilmente una app mientras miran el contenido de su canal. Uso de apps de canal vínculos a apps para extender la participación de los usuarios a través del lanzamiento de actividades que muestren 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 basado en episodios, comenzar a ver el siguiente episodio en una .
- Permitir que el usuario interactúe con el contenido; por ejemplo, califica o opina contenido sin interrumpir su reproducción.
Los vínculos de la app se muestran cuando el usuario presiona Seleccionar para mostrar el menú de TV mientras miras el contenido de un canal.
![](https://developer.android.com/static/images/training/tv/tif/app-link.png?hl=es-419)
Figura 1: Ejemplo de vínculo de aplicación se muestra en la fila Canales y se muestra el contenido del canal.
Cuando el usuario selecciona el vínculo de la aplicación, el sistema inicia una actividad utilizando un URI de intent especificado por la app del canal. Se sigue reproduciendo el contenido del canal mientras la actividad del vínculo de la aplicación está activa. El usuario puede volver al canal contenido presionando Atrás.
Proporciona datos de canales del vínculo de app
Android TV crea automáticamente un vínculo de app para cada canal.
usando información de los datos del canal. Para proporcionar información sobre vínculos de apps,
especifica los siguientes detalles en las
Campos TvContract.Channels
:
COLUMN_APP_LINK_COLOR
: el color de los elementos destacados del vínculo de la aplicación para este canal. Para un ejemplo de color de acento, consulta la figura 2, leyenda 3.COLUMN_APP_LINK_ICON_URI
: Es la URI del ícono de insignia de la app del vínculo de app de este canal. Para un ejemplo de ícono de insignia de la app, consulta la Figura 2, texto destacado 2.COLUMN_APP_LINK_INTENT_URI
: Es el URI de intent del vínculo de la app de este canal. Puedes crear el URI usandotoUri(int)
conURI_INTENT_SCHEME
y volver a convertir el URI al intent original conparseUri()
COLUMN_APP_LINK_POSTER_ART_URI
- El URI del afiche de arte usado como fondo del vínculo de la app para 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. Por ejemplo descripción del vínculo de la app, consulta el texto de la figura 2, texto destacado 3.
![](https://developer.android.com/static/images/training/tv/tif/app-link-diagram.png?hl=es-419)
Figura 2: Detalles de vínculo de app
Si los datos del canal no especifican información de vínculos de apps, el sistema crea un vínculo de app predeterminado. El sistema elige los detalles predeterminados de la siguiente manera:
- Para el URI del intent
(
COLUMN_APP_LINK_INTENT_URI
), el sistema usa el métodoACTION_MAIN
para la categoríaCATEGORY_LEANBACK_LAUNCHER
, que generalmente se define en el manifiesto de la app. Si no se define esta actividad, aparece un vínculo de la aplicación 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 ningún URI de intent viable de vínculo de aplicación, 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 de app predeterminado. - Para la imagen de póster
(
COLUMN_APP_LINK_POSTER_ART_URI
), el sistema usa el banner de la pantalla de inicio de la app. Si la app no proporciona una , el sistema utiliza una imagen de app de TV predeterminada. - Para el ícono de insignia
(
COLUMN_APP_LINK_ICON_URI
), el 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
de configuración. Puedes actualizar estos detalles del vínculo de la app en cualquier momento, por lo que
Si el vínculo de una app debe coincidir con los cambios de canal, actualiza la app.
los detalles del vínculo y la llamada
ContentResolver.update()
según sea necesario. Para obtener más información sobre la actualización
datos de los canales, consulta Actualiza los datos de los canales.