Trabalhar com dados do canal

Sua entrada de TV precisa fornecer dados ao Guia de programação eletrônico (EPG, na sigla em inglês) por pelo menos um canal na atividade de configuração. Você também deve atualizar periodicamente os dados, considerando o tamanho da atualização e a linha de execução que cuida disso. Além disso, você pode fornecer links do app 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 no banco de dados do sistema com essas considerações em mente.

Experimente 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 de EPG, ela precisa declarar o gravação no arquivo de manifesto do Android da seguinte forma:

<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 configuração atividade, para cada um de seus canais, você deve mapear seus dados de canal para os seguintes campos da Classe TvContract.Channels:

Embora o framework de entrada de TV seja genérico o suficiente para lidar com a transmissão tradicional e conteúdo over-the-top (OTT) sem distinção, convém definir as seguintes colunas em além dos acima para identificar melhor os canais de transmissão tradicionais:

Se você quiser fornecer detalhes do link do app para seus canais, vai precisar atualizar alguns campos adicionais. Para mais informações sobre os campos de links de apps, consulte Adicionar informações sobre o link do app

Para entradas de TV baseadas em streaming de Internet, atribua seus próprios valores ao código acima para que e cada canal pode ser identificado de forma exclusiva.

Extraia os metadados do seu canal (em XML, JSON ou qualquer outro) do servidor de back-end e na configuração A atividade mapeia os valores para o banco de dados do sistema da seguinte forma:

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 conforme eles navegam pelos canais. como mostrado na figura 1. Para garantir que as informações do canal e do programa funcionem com o app de TV do sistema apresentador de informações do canal e do programa, siga as orientações abaixo.

  1. Número do canal (COLUMN_DISPLAY_NUMBER)
  2. Ícone (android:icon no manifesto da entrada de TV)
  3. Descrição do programa (COLUMN_SHORT_DESCRIPTION)
  4. Título do programa (COLUMN_TITLE)
  5. Logotipo do canal (TvContract.Channels.Logo)
    • Use a cor #EEEEEE para corresponder ao texto ao redor.
    • Não inclua padding.
  6. Arte do pôster (COLUMN_POSTER_ART_URI)
    • Use uma proporção entre 16:9 e 4:3.

Figura 1. Apresentador de informações de canais e programas do app de TV do sistema.

O app de TV do sistema fornece as mesmas informações no guia da programação, incluindo a arte do pôster, como mostrado na figura 2.

Figura 2. Guia da programação do app de TV do sistema.

Atualizar dados de canais

Ao atualizar dados de canais existentes, use o 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. Adicionar programas atuais (aqueles dentro do intervalo de duas horas do horário atual) somente quando você configura seu 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).

Carregar dados de canais em lote

Ao atualizar o banco de dados do sistema com uma grande quantidade de dados de canais, use o 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, deve não bloquear a linha de execução de IU. Usar um AsyncTask é um de realizar atualizações de forma assíncrona. Por exemplo, ao carregar informações de canal de um servidor de back-end, use AsyncTask desta 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, considere usar WorkManager para executar o processo de atualização durante o tempo 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 IU incluem o uso do Classe HandlerThread ou você pode implementar a sua própria usando Looper e Handler. Consulte Processos e linhas de execução para mais informações.

Os canais podem usar links de apps para permitir que os usuários iniciem facilmente um anúncio relacionado atividade enquanto assistem ao conteúdo do canal. Os apps do canal usam links do app para aumentar o engajamento do usuário iniciando atividades que mostram informações relacionadas ou conteúdo adicional. Por exemplo, você pode usar links de apps faça 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.
  • Enquanto assiste episódios, comece a assistir o próximo episódio de uma Google Workspace.
  • Permitir que o usuário interaja com o conteúdo. Por exemplo, avaliar ou comentar conteúdo sem interromper a reprodução.

Links de apps aparecem quando o usuário pressiona Select para mostrar o Menu da TV enquanto assiste o conteúdo do canal.

Figura 1. Um exemplo de link de app exibido na linha Canais enquanto o conteúdo do canal é mostrado.

Quando o usuário seleciona o link do aplicativo, o sistema inicia uma atividade usando um URI de intent especificado pelo app do canal. O conteúdo do canal continua tocando enquanto a atividade do link de app estiver ativa. O usuário pode retornar ao canal conteúdo pressionando Voltar.

Fornecer dados de canal do link do app

O Android TV cria automaticamente um link do app para cada canal, usando informações dos dados do canal. Para fornecer informações sobre links de apps, especifique os seguintes detalhes em seu Campos TvContract.Channels:

  • COLUMN_APP_LINK_COLOR: o cor de destaque do link de app desse canal. Por exemplo, a cor de destaque, veja a figura 2, destaque 3.
  • COLUMN_APP_LINK_ICON_URI – O URI do ícone do selo do app do link do app desse canal. Para um exemplo de ícone de selo de app, consulte a figura 2, destaque 2.
  • COLUMN_APP_LINK_INTENT_URI – O URI de intent do link de app para esse canal. É possível criar o URI usando toUri(int) com URI_INTENT_SCHEME e converta o URI de volta à intent original com parseUri()
  • COLUMN_APP_LINK_POSTER_ART_URI - O URI da arte do pôster usada como plano de fundo do link do app para este canal. Para ver um exemplo de imagem de pôster, consulte a figura 2, destaque 1.
  • COLUMN_APP_LINK_TEXT – O texto descritivo do link do app para este canal. Para obter um exemplo descrição do link do app, consulte o texto na figura 2, frase de destaque 3.

Figura 2. Detalhes do link de app.

Se os dados do canal não especificarem informações do link do app, o sistema cria 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 o ACTION_MAIN Atividade para a categoria CATEGORY_LEANBACK_LAUNCHER, normalmente definida no manifesto do app. Se essa atividade não estiver definida, um link de aplicativo que não funciona será exibido. o usuário clicar nele, nada acontece.
  • Para o texto descritivo (COLUMN_APP_LINK_TEXT), o sistema usa "Abrir app-name". Se nenhum URI de intent viável para o link de app for definido, o sistema usa a mensagem "Nenhum 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 fornecer um o sistema usa uma imagem padrão de app de TV.
  • Para o ícone do 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 do app ou imagem padrão do app para a imagem do pôster, nenhum selo do app vai ser exibido.

Você especifica detalhes do link do app para seus canais na atividade de configuração. Você pode atualizar esses detalhes do link do app a qualquer momento. Por isso, se um link de app precisar corresponder às mudanças de canal, atualize o app detalhes do link e ligar ContentResolver.update() conforme necessário. Para mais detalhes sobre como atualizar os dados de canal, consulte Atualizar dados de canal.