Desenvolver um serviço de entrada de TV

Um serviço de entrada de TV representa uma fonte de transmissão de mídia e permite a apresentação de conteúdo de mídia no formato de TV com transmissão linear, com canais e programas. Com um serviço de entrada de TV, você pode oferecer controle dos pais, informações sobre o guia de programação e classificações de conteúdo. O serviço de entrada de TV funciona com o app de TV do sistema Android. Esse app controla e apresenta o conteúdo de canais na TV. O app de TV do sistema foi desenvolvido especificamente para dispositivos e não pode ser alterado por apps de terceiros. Para saber mais sobre o TV Input Framework (TIF) e seus componentes, consulte TV Input Framework (link em inglês).

Criar um serviço de entrada de TV usando a TIF Companion Library

A TIF Companion Library é um framework que fornece implementações extensíveis de recursos comuns de serviços de entrada de TV. Use a TIF Companion Library para criar seu próprio serviço de entrada de TV com rapidez e facilidade, seguindo as práticas recomendadas para o Android TV.

Atualizar seu projeto

Para começar a usar a TIF Companion Library, adicione o seguinte ao arquivo build.gradle do seu app:

    compile 'com.google.android.libraries.tv:companionlibrary:0.2'
    

No momento, a TIF Companion Library não faz parte do framework do Android. Ela é distribuída como uma dependência do Gradle por meio do jcenter, não com o SDK do Android. Você pode encontrar no jcenter a versão mais recente da tif-companion library (link em inglês).

Declarar seu serviço de entrada de TV no manifesto

Seu app precisa fornecer um serviço compatível com a classe TvInputService que o sistema usa para acessá-lo. A TIF Companion Library fornece a classe BaseTvInputService, que oferece uma implementação padrão de TvInputService, que pode ser personalizada. Crie uma subclasse de BaseTvInputService e declare-a no seu manifesto como um serviço.

Na declaração do manifesto, especifique a permissão BIND_TV_INPUT para que o serviço possa conectar a entrada de TV ao sistema. Um serviço do sistema executa a vinculação e tem a permissão BIND_TV_INPUT. O app de TV do sistema envia solicitações aos serviços de entrada de TV por meio da interface TvInputManager.

Na declaração de serviço, inclua um filtro de intent que especifique TvInputService como a ação a ser executada com o intent. Além disso, declare os metadados de serviço com um recurso XML separado. A declaração de serviço, o filtro de intent e a declaração de metadados de serviço são mostrados no exemplo a seguir:

    <service android:name=".rich.RichTvInputService"
        android:label="@string/rich_input_label"
        android:permission="android.permission.BIND_TV_INPUT">
        <!-- Required filter used by the system to launch our account service. -->
        <intent-filter>
            <action android:name="android.media.tv.TvInputService" />
        </intent-filter>
        <!-- An XML file which describes this input. This provides pointers to
        the RichTvInputSetupActivity to the system/TV app. -->
        <meta-data
            android:name="android.media.tv.input"
            android:resource="@xml/richtvinputservice" />
    </service>
    

Defina os metadados de serviço em um arquivo XML separado. O arquivo XML de metadados de serviço precisa incluir uma interface de configuração que descreva a configuração inicial e a procura de canais da entrada de TV. O arquivo de metadados também precisa ter uma sinalização que indique se os usuários podem ou não gravar conteúdo. Para saber mais sobre como oferecer compatibilidade com gravação de conteúdo no seu app, consulte Gravação de TV.

O arquivo de metadados de serviço fica no diretório de recursos XML do seu app e precisa ter o mesmo nome do recurso que você declarou no manifesto. Usando as entradas do manifesto do exemplo anterior, você criaria o arquivo XML em res/xml/richtvinputservice.xml, com o seguinte conteúdo:

    <?xml version="1.0" encoding="utf-8"?>
    <tv-input xmlns:android="http://schemas.android.com/apk/res/android"
      android:canRecord="true"
      android:setupActivity="com.example.android.sampletvinput.rich.RichTvInputSetupActivity" />
    

Definir canais e criar atividades de configuração

Seu serviço de entrada de TV precisa definir pelo menos um canal que os usuários acessem pelo app de TV do sistema. Você precisa registrar seus canais no banco de dados do sistema e fornecer uma atividade de configuração para o sistema invocar quando não encontrar um canal para seu app.

Primeiro, permita que seu app leia e grave no Guia eletrônico de programação (EPG, na sigla em inglês) do sistema, cujos dados incluem os canais e programas disponíveis para o usuário. Para permitir que seu app realize essas ações e seja sincronizado com o EPG após a reinicialização do dispositivo, adicione os seguintes elementos ao manifesto do app:

    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED "/>
    

Adicione o seguinte elemento para garantir que seu app apareça na Google Play Store como um app que oferece canais de conteúdo no Android TV:

    <uses-feature
        android:name="android.software.live_tv"
        android:required="true" />
    

Em seguida, crie uma classe que estenda a EpgSyncJobService. Essa classe abstrata facilita a criação de um serviço de tarefas que cria e atualiza canais no banco de dados do sistema.

Na sua subclasse, crie e retorne sua lista completa de canais em getChannels(). Se seus canais vêm de um arquivo XMLTV, use a classe XmlTvParser. Caso contrário, gere canais de forma programática usando a classe Channel.Builder.

Para cada canal, o sistema chama getProgramsForChannel() quando precisa de uma lista de programas que podem ser visualizados em uma janela de tempo específica no canal. Retorne uma lista de objetos Program para o canal. Use a classe XmlTvParser para receber programas de um arquivo XMLTV ou gere-os de forma programática usando a classe Program.Builder.

Para cada objeto Program, use um objeto InternalProviderData para definir informações do programa, como o tipo de vídeo dele. Se você tiver um número limitado de programas que você quer que o canal repita em loop, use o método InternalProviderData.setRepeatable() com valor true ao configurar as informações sobre seu programa.

Depois de implementar o serviço de tarefas, adicione-o ao manifesto do seu app:

    <service
        android:name=".sync.SampleJobService"
        android:permission="android.permission.BIND_JOB_SERVICE"
        android:exported="true" />
    

Para terminar, crie uma atividade de configuração. Sua atividade de configuração precisa fornecer uma forma de sincronizar os dados dos canais e programas. O usuário pode fazer isso por meio da IU na atividade. Você também pode programar para que o app faça isso automaticamente quando a atividade começar. Quando a atividade de configuração precisar sincronizar as informações dos canais e programas, será necessário que o app inicie o serviço de tarefas:

Kotlin

    val inputId = getActivity().intent.getStringExtra(TvInputInfo.EXTRA_INPUT_ID)
    EpgSyncJobService.cancelAllSyncRequests(getActivity())
    EpgSyncJobService.requestImmediateSync(
            getActivity(),
            inputId,
            ComponentName(getActivity(), SampleJobService::class.java)
    )
    

Java

    String inputId = getActivity().getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
    EpgSyncJobService.cancelAllSyncRequests(getActivity());
    EpgSyncJobService.requestImmediateSync(getActivity(), inputId,
            new ComponentName(getActivity(), SampleJobService.class));
    

Use o método requestImmediateSync() para sincronizar o serviço de tarefas. Como o usuário precisa esperar a sincronização terminar, mantenha o período de solicitação relativamente curto.

Use o método setUpPeriodicSync() para fazer com que o serviço de tarefas sincronize os dados dos canais e programas periodicamente em segundo plano:

Kotlin

    EpgSyncJobService.setUpPeriodicSync(
            context,
            inputId,
            ComponentName(context, SampleJobService::class.java)
    )
    

Java

    EpgSyncJobService.setUpPeriodicSync(context, inputId,
            new ComponentName(context, SampleJobService.class));
    

A TIF Companion Library oferece um método sobrecarregado adicional de requestImmediateSync(), que permite especificar a duração da sincronização dos dados de canais em milissegundos. O método padrão sincroniza o equivalente a uma hora de dados de canais.

A TIF Companion Library também fornece outro método sobrecarregado de setUpPeriodicSync(), que permite especificar a duração da sincronização dos dados de canais e a frequência em que a sincronização periódica ocorrerá. O método padrão sincroniza 48 horas de dados de canais a cada 12 horas.

Para ver mais detalhes sobre dados de canais e o EPG, consulte Trabalhar com dados de canais.

Processar solicitações de sintonização e reprodução de mídia

Quando um usuário seleciona um canal específico, o app de TV do sistema usa uma Session, criada pelo app, para sintonizar o canal solicitado e abrir conteúdo. A TIF Companion Library oferece várias classes que você pode estender para processar chamadas de canais e sessões no sistema.

A subclasse BaseTvInputService cria sessões que processam solicitações de sintonização. Modifique o método onCreateSession(), crie uma sessão estendida da classe BaseTvInputService.Session e chame super.sessionCreated() com sua nova sessão. No exemplo a seguir, onCreateSession() retorna um objeto RichTvInputSessionImpl que estende BaseTvInputService.Session:

Kotlin

    override fun onCreateSession(inputId: String): Session =
            RichTvInputSessionImpl(this, inputId).apply {
                setOverlayViewEnabled(true)
            }
    

Java

    @Override
    public final Session onCreateSession(String inputId) {
        RichTvInputSessionImpl session = new RichTvInputSessionImpl(this, inputId);
        session.setOverlayViewEnabled(true);
        return session;
    }
    

Quando o usuário usa o app de TV do sistema para começar a visualizar um dos seus canais, o sistema chama o método onPlayChannel() da sua sessão. Modifique esse método se precisar executar qualquer inicialização de canal especial antes de o programa começar a ser reproduzido.

O sistema, então, recebe o programa que está agendado e chama o método onPlayProgram() da sua sessão, especificando as informações do programa e o horário de início em milissegundos. Use a interface TvPlayer para começar a mostrar o programa.

O código do seu player de mídia precisa implementar TvPlayer para processar eventos específicos de reprodução. A classe TvPlayer processa recursos como controles de time-shifting sem aumentar a complexidade da sua implementação de BaseTvInputService.

No método getTvPlayer() da sua sessão, retorne o player de mídia que implementa TvPlayer. O app de amostra TV Input Service (link em inglês) implementa um player de mídia que usa o ExoPlayer.

Criar um serviço de entrada de TV usando o framework de entrada de TV

Se seu serviço de entrada de TV não puder usar a TIF Companion Library, você precisará implementar os seguintes componentes:

  • TvInputService oferece disponibilidade de longa duração e em segundo plano para a entrada de TV.
  • TvInputService.Session mantém o estado da entrada de TV e se comunica com o app host.
  • TvContract descreve os canais e os programas disponíveis para a entrada de TV.
  • TvContract.Channels representa informações sobre um canal de TV.
  • TvContract.Programs descreve um programa de TV com dados como título do programa e horário de início.
  • TvTrackInfo representa uma faixa de áudio, vídeo ou legendas.
  • TvContentRating faz uma classificação do conteúdo e permite esquemas personalizados de classificações.
  • TvInputManager fornece uma API ao app de TV do sistema e gerencia a interação com entradas e apps de TV.

Você também precisa fazer o seguinte:

  1. Declare seu serviço de entrada de TV no manifesto, conforme descrito em Declarar o serviço de entrada de TV no manifesto.
  2. Crie o arquivo de metadados do serviço.
  3. Crie e registre suas informações de canais e programas.
  4. Crie suas atividades de configuração.

Definir o serviço de entrada de TV

Estenda a classe TvInputService para seu serviço. Uma implementação de TvInputService é um serviço vinculado em que o serviço de sistema é o cliente que se vincula a ele. A figura 1 mostra os métodos de ciclo de vida do serviço que você precisa implementar.

O método onCreate() é inicializado e inicia a HandlerThread, que fornece uma linha de execução do processo separada da linha de execução de IU para processar ações do sistema. No exemplo a seguir, o método onCreate() inicializa o CaptioningManager e se prepara para processar as ações ACTION_BLOCKED_RATINGS_CHANGED e ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED. Essas ações descrevem intents do sistema disparados quando o usuário altera as configurações de controle dos pais e quando há uma alteração na lista de classificações bloqueadas.

Kotlin

    override fun onCreate() {
        super.onCreate()
        handlerThread = HandlerThread(javaClass.simpleName).apply {
            start()
        }
        dbHandler = Handler(handlerThread.looper)
        handler = Handler()
        captioningManager = getSystemService(Context.CAPTIONING_SERVICE) as CaptioningManager

        setTheme(android.R.style.Theme_Holo_Light_NoActionBar)

        sessions = mutableListOf<BaseTvInputSessionImpl>()
        val intentFilter = IntentFilter().apply {
            addAction(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED)
            addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED)
        }
        registerReceiver(broadcastReceiver, intentFilter)
    }
    

Java

    @Override
    public void onCreate() {
        super.onCreate();
        handlerThread = new HandlerThread(getClass()
          .getSimpleName());
        handlerThread.start();
        dbHandler = new Handler(handlerThread.getLooper());
        handler = new Handler();
        captioningManager = (CaptioningManager)
          getSystemService(Context.CAPTIONING_SERVICE);

        setTheme(android.R.style.Theme_Holo_Light_NoActionBar);

        sessions = new ArrayList<BaseTvInputSessionImpl>();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(TvInputManager
          .ACTION_BLOCKED_RATINGS_CHANGED);
        intentFilter.addAction(TvInputManager
          .ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
        registerReceiver(broadcastReceiver, intentFilter);
    }
    

Figura 1.Ciclo de vida da classe TvInputService.

Consulte Controlar conteúdo para ver mais informações sobre como trabalhar com conteúdo bloqueado e oferecer controle dos pais. Consulte TvInputManager para conhecer mais ações do sistema que você pode processar no seu serviço de entrada de TV.

O TvInputService cria uma TvInputService.Session que implementa Handler.Callback para processar alterações no estado do player. Com onSetSurface(), a TvInputService.Session define a Surface com o conteúdo do vídeo. Consulte Integrar o player com a superfície para ver mais informações sobre como trabalhar com Surface para renderizar vídeos.

A TvInputService.Session processa o evento onTune() quando o usuário seleciona um canal e notifica o app de TV do sistema sobre alterações no conteúdo e nos metadados do conteúdo. Esses métodos notify() estão descritos nas seções Controlar conteúdo e Processar seleção de faixas deste treinamento.

Definir atividades de configuração

O app de TV do sistema trabalha com as atividades de configuração definidas para sua entrada de TV. As atividades de configuração são obrigatórias e precisam fornecer pelo menos um registro de canal para o banco de dados do sistema. O app de TV do sistema invoca as atividades de configuração quando não encontra um canal para a entrada de TV.

As atividades de configuração descrevem para o app de TV do sistema os canais disponibilizados por meio da entrada de TV, conforme demonstrado na próxima lição, Criar e atualizar dados de canais.

Outras referências