Sua entrada de TV precisa fornecer dados do Guia de programação eletrônica (EPG, na sigla em inglês) para pelo menos um canal na atividade de configuração. Também é necessário atualizar periodicamente esses dados, considerando o tamanho da atualização e a linha de execução de processamento que a processa. Além disso, você pode fornecer links de apps para canais que direcionam o usuário para conteúdo e atividades relacionados. Esta lição aborda a criação e a atualização de dados de canais e programas no banco de dados do sistema com essas considerações em mente.
Teste o app de exemplo TV Input Service (link em inglês).
Conseguir permissão
Para que a entrada de TV funcione com dados de EPG, ela precisa 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 atividade
de configuração, é preciso mapear os dados dos canais para os seguintes campos da
classe TvContract.Channels
:
COLUMN_DISPLAY_NAME
: o nome exibido do canalCOLUMN_DISPLAY_NUMBER
: o número exibido do canalCOLUMN_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 de entrada de TV seja genérico o suficiente para processar a transmissão tradicional e o 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ão.COLUMN_SERVICE_ID
: ID do serviçoCOLUMN_TRANSPORT_STREAM_ID
: ID do fluxo de transporte.
Se você quiser fornecer detalhes de links de apps para seus canais, será necessário atualizar alguns 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 de Internet, atribua seus próprios valores aos valores acima para que cada canal possa ser identificado de maneira exclusiva.
Extraia os metadados do seu canal (em XML, JSON ou qualquer outro) 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);
No exemplo acima, channel
é um objeto que conté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 aos usuários à medida que eles alternam entre os canais, conforme mostrado na Figura 1. Para garantir que as informações de canais e programas funcionem com o apresentador de informações de canais e programas do app de TV do sistema, siga as diretrizes abaixo.
- Número do canal (
COLUMN_DISPLAY_NUMBER
) - Ícone
(
android:icon
no 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 de pôster (
COLUMN_POSTER_ART_URI
)- Use uma proporção entre 16:9 e 4:3.
O app de TV do sistema fornece as mesmas informações no guia da programação, incluindo pôsteres, conforme 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. Identifique 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:a adição de dados de canais ao ContentProvider
pode levar algum tempo. Adicione os programas atuais (aqueles dentro de duas horas do horário atual)
somente quando configurar o EpgSyncJobService
para atualizar o restante
dos dados do canal em segundo plano. Consulte o
app de exemplo de TV ao vivo do Android TV (link em inglês) para conferir um exemplo.
Carregar dados de canais em lote
Ao atualizar o banco de dados do sistema com uma grande quantidade de dados do canal, 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 buscar um stream do servidor ou acessar o banco de dados, não pode
bloquear a linha de execução de IU. O uso de um AsyncTask
é unidirecional de fazer 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 regularmente, use
WorkManager
para executar o processo de atualização durante o período inativo, por exemplo, 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
classe HandlerThread
. Você também pode implementar uma técnica própria usando as classes Looper
e Handler
. 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 facilmente uma atividade relacionada enquanto estiverem assistindo o conteúdo do canal. Os apps de canais usam links de apps para estender o engajamento 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, comece a assistir o próximo episódio de uma série.
- Permita que o usuário interaja com o conteúdo, por exemplo, classificando ou analisando o conteúdo, sem interromper a reprodução.
Os links do app são mostrados quando o usuário pressiona Select para mostrar o menu da TV enquanto assiste o conteúdo do canal.
Quando o usuário seleciona o link do app, o sistema inicia uma atividade usando um URI da intent especificado pelo app do canal. O conteúdo do canal continua a ser reproduzido enquanto a atividade do link do app está ativa. O usuário pode retornar ao conteúdo do canal pressionando Voltar.
Fornecer dados de canal do link do app
O 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 do app para esse canal. Para conferir um exemplo de cor de destaque, consulte a Figura 2, destaque 3.COLUMN_APP_LINK_ICON_URI
: o URI do ícone de selo do app referente ao link de app desse canal. Para ver um exemplo de ícone de selo do app, consulte a Figura 2, frase de destaque 2.COLUMN_APP_LINK_INTENT_URI
: URI da intent do link de app desse canal. É possível criar o URI usandotoUri(int)
comURI_INTENT_SCHEME
e converter o URI de volta para a intent original comparseUri()
.COLUMN_APP_LINK_POSTER_ART_URI
: o URI da arte do pôster usado como plano de fundo do link de app deste canal. Para ver um exemplo de imagem de pôster, consulte a figura 2, destaque 1.COLUMN_APP_LINK_TEXT
: texto descritivo do link do app para esse canal. Para ver um exemplo de descrição de link de app, consulte o texto na Figura 2, frase de destaque 3.
Se os dados do canal não especificarem as informações do link de app, 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_MAIN
para 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 do link do app for definido, o sistema vai usar "No link disponível". - Para a cor de destaque
(
COLUMN_APP_LINK_COLOR
), o sistema usa a cor padrão do app. - 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 vai usar uma 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 a imagem padrão do app como imagem do pôster, nenhum selo de app será exibido.
Especifique os 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. Assim, se um link de app precisar corresponder a 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 do canal, consulte Atualizar dados do canal.