Sua entrada de TV precisa fornecer dados ao Guia da programação eletrônico (EPG, na sigla em inglês) para pelo menos um canal na atividade de configuração. Também é necessário atualizar esses dados periodicamente, considerando o tamanho da atualização e o thread de processamento relacionado. Além disso, você pode oferecer links de apps para canais que direcionam o usuário para conteúdos e atividades relacionados. Esta lição aborda a criação e a atualização de dados de canais e programas na base de dados do sistema, pensando nessas considerações.
Confira o app de exemplo do serviço de entrada de TV (link em inglês).
Conseguir permissão
Para que sua entrada de TV funcione com dados do EPG, é necessário declarar a permissão de gravação no arquivo de manifesto do Android da seguinte maneira:
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
Registrar canais no banco de dados
O banco de dados do sistema da Android TV mantém registros de dados de canais para entradas de TV. Na sua atividade de configuração, é necessário mapear os dados de cada canal para os seguintes campos da classe TvContract.Channels:
COLUMN_DISPLAY_NAME: nome exibido do canalCOLUMN_DISPLAY_NUMBER: número do canal exibidoCOLUMN_INPUT_ID: ID do serviço de entrada de TVCOLUMN_SERVICE_TYPE: tipo de serviço do canalCOLUMN_TYPE- tipo de padrão de transmissão do canalCOLUMN_VIDEO_FORMAT: formato de vídeo padrão do canal
Embora o framework da entrada de TV seja genérico o suficiente para lidar tanto com a transmissão tradicional quanto com conteúdo over-the-top (OTT) sem distinções, é recomendável definir as seguintes colunas, além daquelas acima, para identificar melhor os canais de transmissão tradicionais:
COLUMN_ORIGINAL_NETWORK_ID: ID da rede de televisãoCOLUMN_SERVICE_ID: ID do serviçoCOLUMN_TRANSPORT_STREAM_ID: ID do stream de transporte
Se você quiser fornecer detalhes de links de apps para seus canais, é necessário atualizar outros campos. Para mais informações sobre os campos de links de apps, consulte Adicionar informações de links de apps.
Para entradas de TV baseadas em streaming da Internet, atribua os próprios valores de modo que cada canal possa ser identificado de forma única.
Extraia os metadados do seu canal (em XML, JSON etc.) do servidor de back-end e, na atividade de configuração, mapeie os valores para o banco de dados do sistema, da seguinte maneira:
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);
Neste exemplo, channel é um objeto que detém metadados de canal do servidor de back-end.
Apresentar informações de canais e programas
O app de TV do sistema apresenta informações de canais e programas para os usuários conforme eles passam pelos canais, como mostrado na figura 1. Para garantir que as informações de canais e programas funcionem no apresentador do app de TV do sistema, siga estas instruções:
- Número do canal (
COLUMN_DISPLAY_NUMBER) - Ícone
(
android:iconno manifesto da entrada de TV) - Descrição do programa (
COLUMN_SHORT_DESCRIPTION) - Título do programa (
COLUMN_TITLE) - Logotipo do canal (
TvContract.Channels.Logo)- Use a cor #EEEEEE para corresponder ao texto ao redor.
- Não inclua padding.
- Arte do pôster (
COLUMN_POSTER_ART_URI) - Use uma proporção entre 16:9 e 4:3.
O app de TV do sistema oferece as mesmas informações no guia da programação, inclusive a arte do pôster, como mostrado na figura 2.
Atualizar dados de canais
Ao atualizar dados de canais existentes, use o método update em vez de excluir e adicionar novamente os dados. Você pode identificar a versão atual dos dados usando Channels.COLUMN_VERSION_NUMBER e Programs.COLUMN_VERSION_NUMBER ao escolher os registros a serem atualizados.
Observação:adicionar dados de canais ao ContentProvider pode levar algum tempo. Adicione os programas atuais (aqueles dentro do intervalo de duas horas do horário atual) somente quando configurar seu EpgSyncJobService para atualizar o restante dos dados de canais em segundo plano. Consulte
o
app de exemplo de TV ao vivo do Android TV para ver um exemplo.
Carregar dados de canais em lote
Ao atualizar o banco de dados do sistema com uma grande quantidade de dados de canais, use o método ContentResolver
applyBatch ou bulkInsert. Veja um exemplo com 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(); } }
Processar dados de canais de forma assíncrona
A manipulação de dados, como a busca de um stream do servidor ou o acesso ao banco de dados, não deve bloquear a linha de execução da interface. Usar um AsyncTask é uma maneira de realizar atualizações de forma assíncrona. Por exemplo, ao carregar informações de canal de um servidor de back-end, você pode usar AsyncTask, da seguinte maneira:
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(); } } } }
Se você precisar atualizar os dados do EPG com frequência, considere usar
WorkManager
para executar o processo de atualização durante o tempo de inatividade, como todos os dias às 3h.
Outras técnicas para separar as tarefas de atualização de dados da linha de execução de interface incluem o uso da
HandlerThread classe. Você também pode implementar a própria técnica usando Looper
e Handler classes. Consulte
Processos e linhas de execução para saber mais.
Adicionar informações de links de apps
Os canais podem usar links de apps para permitir que os usuários iniciem uma atividade relacionada enquanto estiverem assistindo o conteúdo do canal. Apps de canais usam links de apps para estender o envolvimento do usuário iniciando atividades que mostram informações relacionadas ou outros conteúdos. Por exemplo, você pode usar links de apps para fazer o seguinte:
- Orientar o usuário para encontrar e adquirir conteúdo relacionado.
- Oferecer mais informações sobre o conteúdo em exibição no momento.
- Durante a visualização de conteúdo em episódios, iniciar a visualização do próximo episódio da série.
- Permitir que o usuário interaja com o conteúdo, por exemplo, classificando ou avaliando o conteúdo, sem interromper a exibição.
Links de apps são exibidos quando o usuário pressiona Select para mostrar o menu da TV enquanto assiste o conteúdo do canal.
Figura 1. Exemplo de link de app exibido na linha Channels enquanto o conteúdo do canal é apresentado.
Quando o usuário seleciona o link do app, o sistema inicia uma atividade usando um URI de intent especificado pelo app do canal. O conteúdo do canal continua sendo exibido enquanto a atividade do link do app está ativa. O usuário pode retornar ao conteúdo do canal pressionando Back.
Fornecer dados de canal do link do app
A Android TV cria automaticamente um link de app para cada canal, usando informações dos dados do canal. Para fornecer informações de links de apps, especifique os seguintes detalhes nos campos TvContract.Channels:
COLUMN_APP_LINK_COLOR- A cor de destaque do link de app desse canal. Para ver um exemplo de cor de destaque, consulte a figura 2, destaque 3.COLUMN_APP_LINK_ICON_URI- URI para o ícone do selo do app correspondente ao link de app desse canal. Para ver um exemplo de ícone de selo de app, consulte a figura 2, destaque 2.COLUMN_APP_LINK_INTENT_URI- URI de intent do link de app desse canal. Você pode criar o URI usandotoUri(int)comURI_INTENT_SCHEMEe converter o URI de volta para a intent original comparseUri.COLUMN_APP_LINK_POSTER_ART_URI: URI para a arte do pôster usada como segundo plano do link de app desse canal. Para ver um exemplo de imagem do pôster, consulte a figura 2, destaque 1.COLUMN_APP_LINK_TEXT- texto descritivo do link de app desse canal. Para ver um exemplo de descrição de link de app, consulte o texto na figura 2, destaque 3.
Se os dados do canal não especificarem informações de links de apps, o sistema vai criar um link de app padrão. O sistema escolhe os detalhes padrão da seguinte maneira:
- Para o URI da intent (
COLUMN_APP_LINK_INTENT_URI), o sistema usa a atividadeACTION_MAINpara a categoriaCATEGORY_LEANBACK_LAUNCHER, normalmente definida no manifesto do app. Se essa atividade não for definida, um link de app inativo será exibido. Se o usuário clicar nele, nada acontecerá. - Para o texto descritivo
(
COLUMN_APP_LINK_TEXT), o sistema usa "Open app-name". Se nenhum URI de intent viável para o link de app for definido, o sistema usará "No link available". - Para a cor de destaque (
COLUMN_APP_LINK_COLOR), o sistema usa a cor de app padrão. - Para a imagem do pôster (
COLUMN_APP_LINK_POSTER_ART_URI), o sistema usa o banner da tela inicial do app. Se o app não oferecer um banner, o sistema usará a imagem padrão do app de TV. - Para o ícone de selo (
COLUMN_APP_LINK_ICON_URI), o sistema usa um selo que mostra o nome do app. Se o sistema também estiver usando o banner ou imagem padrão do app como imagem do pôster, nenhum selo será exibido.
Especifique detalhes do link do app para seus canais na atividade de configuração do app. Você pode atualizar esses detalhes do link do app a qualquer momento. Portanto, se um link de app precisar corresponder às mudanças de canal, atualize os detalhes do link do app e chame ContentResolver.update conforme necessário. Para mais detalhes sobre como atualizar
dados de canais, consulte Atualizar dados de canais.