Sviluppare un servizio di ingresso TV

Un servizio di input TV rappresenta una sorgente di stream multimediali e ti consente di presentare i tuoi contenuti multimediali in modo lineare e televisivo come canali e programmi. Con un servizio di input TV, puoi fornire Controllo genitori, informazioni sulla guida ai programmi e classificazioni dei contenuti. Il servizio di input TV funziona con l'app Android TV, che controlla e presenta i contenuti dei canali sulla TV. L'app di sistema per la TV è sviluppata appositamente per il dispositivo ed è immutabile dalle app di terze parti. Per ulteriori informazioni sull'architettura TIF (TV Input Framework) e sui suoi componenti, consulta la pagina TV Input Framework.

Creare un servizio di input TV utilizzando la libreria companion TIF

La libreria Companion TIF è un framework che fornisce implementazioni estensibili delle funzionalità comuni dei servizi di input TV. È destinata esclusivamente agli OEM per creare canali da Android 5.0 (livello API 21) ad Android 7.1 (livello API 25).

Aggiorna il progetto

La libreria companion TIF è disponibile per l'utilizzo in versione legacy da parte degli OEM nel repository androidtv-sample-inputs. Consulta il repository per un esempio di come includere la libreria in un'app.

Dichiara il servizio di input TV nel file manifest

La tua app deve fornire un servizio compatibile con TvInputService che il sistema utilizza per accedere all'app. La libreria companion TIF fornisce la classe BaseTvInputService, che offre un'implementazione predefinita di TvInputService che puoi personalizzare. Crea una sottoclasse di BaseTvInputService e dichiarala nel tuo manifest come servizio.

Nella dichiarazione del file manifest, specifica l'autorizzazione BIND_TV_INPUT per consentire al servizio di connettere l'ingresso TV al sistema. Un servizio di sistema esegue l'associazione e ha l'autorizzazione BIND_TV_INPUT. L'app TV di sistema invia richieste ai servizi di input TV tramite l'interfaccia TvInputManager.

Nella dichiarazione del servizio, includi un filtro per intent che specifichi TvInputService come azione da eseguire con l'intent. Inoltre, dichiara i metadati del servizio come risorsa XML separata. La dichiarazione del servizio, il filtro di intent e la dichiarazione dei metadati di servizio sono mostrati nell'esempio seguente:

<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>

Definisci i metadati del servizio in un file XML separato. Il file XML dei metadati del servizio deve includere un'interfaccia di configurazione che descriva la configurazione iniziale e la scansione dei canali dell'ingresso TV. Il file di metadati deve anche contenere un flag che indica se gli utenti possono o meno registrare contenuti. Per ulteriori informazioni su come supportare la registrazione di contenuti nella tua app, consulta Supportare la registrazione di contenuti.

Il file di metadati del servizio si trova nella directory delle risorse XML dell'app e deve corrispondere al nome della risorsa dichiarata nel file manifest. Utilizzando le voci manifest dell'esempio precedente, creerai il file XML in res/xml/richtvinputservice.xml, con il seguente contenuto:

<?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" />

Definisci i canali e crea la tua attività di configurazione

Il servizio di input TV deve definire almeno un canale a cui gli utenti accedono tramite l'app di sistema per TV. Dovresti registrare i canali nel database di sistema e fornire un'attività di configurazione che il sistema richiama quando non riesce a trovare un canale per la tua app.

Prima di tutto, consenti alla tua app di leggere e scrivere nella Guida elettronica di programmazione (EPG), i cui dati includono canali e programmi disponibili per l'utente. Per consentire alla tua app di eseguire queste azioni ed eseguire la sincronizzazione con l'EPG dopo il riavvio del dispositivo, aggiungi i seguenti elementi al file manifest dell'app:

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

Aggiungi il seguente elemento per assicurarti che la tua app venga visualizzata nel Google Play Store come app che fornisce canali di contenuti su Android TV:

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

A questo punto, crea una classe che estenda la classe EpgSyncJobService. Questa classe astratta semplifica la creazione di un servizio job che crea e aggiorna canali nel database di sistema.

Nella sottoclasse, crea e restituisci l'elenco completo dei tuoi canali in getChannels(). Se i tuoi canali provengono da un file XMLTV, utilizza il corso XmlTvParser. In caso contrario, genera i canali in modo programmatico utilizzando la classe Channel.Builder.

Per ogni canale, il sistema chiama getProgramsForChannel() quando ha bisogno di un elenco di programmi che possono essere visualizzati in un determinato periodo di tempo sul canale. Restituisci un elenco di oggetti Program per il canale. Utilizza la classe XmlTvParser per ottenere i programmi da un file XMLTV oppure generali in modo programmatico utilizzando la classe Program.Builder.

Per ogni oggetto Program, utilizza un oggetto InternalProviderData per impostare informazioni del programma come il tipo di video del programma. Se hai solo un numero limitato di programmi che vuoi che il canale si ripeta in loop, utilizza il metodo InternalProviderData.setRepeatable() con valore true quando imposti le informazioni sul programma.

Dopo aver implementato il servizio job, aggiungilo al manifest dell'app:

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

Infine, crea un'attività di configurazione. L'attività di configurazione dovrebbe fornire un modo per sincronizzare i dati del canale e del programma. Un modo è che l'utente lo faccia tramite l'interfaccia utente nell'attività. Potresti anche impostare l'app in modo automatico quando inizia l'attività. Quando l'attività di configurazione deve sincronizzare le informazioni sul canale e sul programma, l'app deve avviare il servizio:

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));

Utilizza il metodo requestImmediateSync() per sincronizzare il servizio di job. L'utente deve attendere il completamento della sincronizzazione, quindi il periodo della richiesta deve essere relativamente breve.

Utilizza il metodo setUpPeriodicSync() per fare in modo che il servizio job sincronizzi periodicamente il canale e programmi i dati in background:

Kotlin

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

Java

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

La libreria companion TIF fornisce un ulteriore metodo di sovraccarico requestImmediateSync() che ti consente di specificare la durata dei dati del canale da sincronizzare in millisecondi. Il metodo predefinito sincronizza i dati del canale relativi al valore di un'ora.

La libreria companion TIF fornisce anche un ulteriore metodo di sovraccarico setUpPeriodicSync() che consente di specificare la durata dei dati dei canali da sincronizzare e la frequenza della sincronizzazione periodica. Il metodo predefinito sincronizza 48 ore di dati del canale ogni 12 ore.

Per maggiori dettagli sui dati dei canali e sull'EPG, consulta Utilizzare i dati dei canali.

Gestire le richieste di ottimizzazione e la riproduzione di contenuti multimediali

Quando un utente seleziona un canale specifico, l'app TV di sistema utilizza un Session, creato dalla tua app, per sintonizzarsi sul canale richiesto e riprodurre i contenuti. La libreria companion TIF offre diverse classi che puoi estendere per gestire le chiamate a canali e sessioni dal sistema.

La sottoclasse BaseTvInputService crea sessioni che gestiscono le richieste di ottimizzazione. Esegui l'override del metodo onCreateSession(), crea una sessione estesa dalla classe BaseTvInputService.Session e chiama super.sessionCreated() con la nuova sessione. Nel seguente esempio, onCreateSession() restituisce un oggetto RichTvInputSessionImpl che 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 l'utente utilizza l'app TV di sistema per iniziare a visualizzare uno dei tuoi canali, il sistema chiama il metodo onPlayChannel() della sessione. Esegui l'override di questo metodo se devi eseguire un'inizializzazione speciale di canale prima che il programma inizi a essere riprodotto.

Il sistema recupera quindi il programma attualmente pianificato e chiama il metodo onPlayProgram() della sessione, specificando le informazioni sul programma e l'ora di inizio in millisecondi. Utilizza l'interfaccia di TvPlayer per iniziare a riprodurre il programma.

Il codice del media player deve implementare TvPlayer per gestire eventi di riproduzione specifici. La classe TvPlayer gestisce funzionalità come i controlli del time-shifting senza aggiungere complessità alla tua implementazione di BaseTvInputService.

Nel metodo getTvPlayer() della sessione, restituisci il media player che implementa TvPlayer. L'app di esempio TV Input Service implementa un media player che utilizza ExoPlayer.

Creare un servizio di input TV utilizzando il framework di input TV

Se il tuo servizio di input TV non può utilizzare la libreria companion TIF, devi implementare i seguenti componenti:

  • TvInputService fornisce disponibilità a lunga esecuzione e in background per l'input TV
  • TvInputService.Session mantiene lo stato dell'ingresso TV e comunica con l'app di hosting
  • TvContract descrive i canali e i programmi disponibili per l'ingresso TV
  • TvContract.Channels rappresenta le informazioni su un canale TV
  • TvContract.Programs descrive un programma TV con dati quali titolo e ora di inizio del programma
  • TvTrackInfo rappresenta una traccia audio, video o di sottotitoli
  • TvContentRating descrive una classificazione dei contenuti e consente schemi di classificazione dei contenuti personalizzati
  • TvInputManager fornisce un'API all'app di sistema per TV e gestisce l'interazione con app e ingressi TV

Devi inoltre procedere nel seguente modo:

  1. Dichiara il tuo servizio di input TV nel file manifest, come descritto nella sezione Dichiarare il servizio di input TV nel file manifest.
  2. Crea il file dei metadati del servizio.
  3. Crea e registra le informazioni sul tuo canale e sul programma.
  4. Crea l'attività di configurazione.

Definisci il servizio di input TV

Per il tuo servizio, estendi il corso TvInputService. Un'implementazione TvInputService è un servizio associato in cui il servizio di sistema è il client associato. I metodi del ciclo di vita dei servizi da implementare sono illustrati nella Figura 1.

Il metodo onCreate() inizializza e avvia HandlerThread, che fornisce un thread di processo separato dal thread dell'interfaccia utente per gestire le azioni basate sul sistema. Nell'esempio seguente, il metodo onCreate() inizializza CaptioningManager e si prepara a gestire le azioni ACTION_BLOCKED_RATINGS_CHANGED e ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED. Queste azioni descrivono gli intent di sistema attivati quando l'utente modifica le impostazioni del Controllo genitori e quando viene apportata una modifica all'elenco delle classificazioni bloccate.

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 di vita di TVInputService.

Consulta la sezione Controllare i contenuti per ulteriori informazioni su come gestire i contenuti bloccati e usufruire del controllo genitori. Visita la pagina TvInputManager per scoprire altre azioni basate sul sistema che potresti voler gestire nel servizio di input TV.

L'elemento TvInputService crea una TvInputService.Session che implementa Handler.Callback per gestire le modifiche dello stato del player. Con onSetSurface(), TvInputService.Session imposta Surface con i contenuti video. Consulta la sezione Integrare il player con la piattaforma per ulteriori informazioni sull'utilizzo di Surface per il rendering del video.

L'TvInputService.Session gestisce l'evento onTune() quando l'utente seleziona un canale e invia una notifica all'app di sistema per la TV in caso di modifiche ai contenuti e ai metadati dei contenuti. Questi metodi notify() sono descritti in Controllare contenuti e Gestire la selezione delle tracce più avanti in questo corso di formazione.

Definisci l'attività di configurazione

L'app TV di sistema funziona con l'attività di configurazione definita per l'ingresso TV. L'attività di configurazione è obbligatoria e deve fornire almeno un record di canale per il database di sistema. L'app TV di sistema richiama l'attività di configurazione quando non riesce a trovare un canale per l'ingresso TV.

L'attività di configurazione descrive all'app TV di sistema i canali resi disponibili tramite l'ingresso TV, come mostrato nella lezione successiva, Creare e aggiornare i dati dei canali.

Riferimenti aggiuntivi