Utilizza i dati del canale

L'ingresso TV deve fornire i dati della Guida elettronica ai programmi (EPG) per almeno un canale nella sua attività di configurazione. Dovresti inoltre aggiornare periodicamente questi dati, tenendo conto delle dimensioni dell'aggiornamento e del thread di elaborazione che li gestisce. Inoltre, puoi fornire link alle app per i canali che indirizzano l'utente verso attività e contenuti correlati. Questa lezione illustra la creazione e l'aggiornamento dei dati di canali e programmi nel database di sistema tenendo presenti queste considerazioni.

Prova l'app di esempio TV Input Service.

Autorizza

Affinché l'input TV possa funzionare con i dati EPG, deve dichiarare l'autorizzazione di scrittura nel file manifest Android come segue:

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

Registra i canali nel database

Il database del sistema Android TV conserva record dei dati sui canali per gli ingressi TV. Nell'attività di configurazione, per ciascuno dei tuoi canali, devi mappare i dati del canale ai seguenti campi della classe TvContract.Channels:

Sebbene il framework dell'input TV sia abbastanza generico da gestire sia i contenuti tradizionali di trasmissione sia i contenuti over-the-top (OTT) senza alcuna distinzione, ti consigliamo di definire le seguenti colonne in aggiunta a quelle riportate sopra per identificare meglio i canali di trasmissione tradizionali:

Se vuoi fornire dettagli sul link dell'app per i tuoi canali, devi aggiornare alcuni campi aggiuntivi. Per saperne di più sui campi del link dell'app, consulta Aggiungere informazioni sui link dell'app.

Per gli ingressi TV basati su streaming su internet, assegna i tuoi valori a quelli riportati sopra di conseguenza, in modo che ogni canale possa essere identificato in modo univoco.

Estrai i metadati del canale (in XML, JSON o altro) dal server di backend e, nell'attività di configurazione, mappa i valori al database di sistema nel seguente modo:

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

Nell'esempio precedente, channel è un oggetto che contiene i metadati del canale dal server di backend.

Presenta le informazioni sul canale e sul programma

L'app TV di sistema presenta informazioni sui canali e sui programmi agli utenti mentre sfogliano i canali, come mostrato nella Figura 1. Per assicurarti che le informazioni su canali e programmi funzionino con il presentatore delle informazioni sui canali e sui programmi dell'app TV di sistema, segui le linee guida riportate di seguito.

  1. Numero di canale (COLUMN_DISPLAY_NUMBER)
  2. Icona (android:icon nel manifest dell'input TV)
  3. Descrizione del programma (COLUMN_SHORT_DESCRIPTION)
  4. Titolo del programma (COLUMN_TITLE)
  5. Logo del canale (TvContract.Channels.Logo)
    • Utilizza il colore #EEEEEE per adattarlo al testo circostante
    • Non includere la spaziatura interna
  6. Poster art (COLUMN_POSTER_ART_URI)
    • Proporzioni tra 16:9 e 4:3

Figura 1. Il presentatore delle informazioni sul programma e sul canale dell'app TV di sistema.

L'app di sistema per la TV fornisce le stesse informazioni tramite la guida ai programmi, inclusa la locandina, come mostrato nella Figura 2.

Figura 2. La guida ai programmi dell'app per TV di sistema.

Aggiorna i dati del canale

Quando aggiorni i dati dei canali esistenti, utilizza il metodo update() anziché eliminarli e riaggiungerli. Puoi identificare la versione corrente dei dati utilizzando Channels.COLUMN_VERSION_NUMBER e Programs.COLUMN_VERSION_NUMBER quando scegli i record da aggiornare.

Nota: l'aggiunta dei dati del canale a ContentProvider può richiedere del tempo. Aggiungi i programmi attuali (entro due ore dall'ora attuale) solo quando configuri EpgSyncJobService per aggiornare gli altri dati del canale in background. Per un esempio, vedi l' app di esempio Android TV Live TV.

Caricamento in gruppo dei dati del canale

Quando aggiorni il database di sistema con una grande quantità di dati di canale, utilizza il metodo ContentResolver applyBatch() o bulkInsert(). Ecco un esempio utilizzando 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();
    }
}

Elabora i dati del canale in modo asincrono

La manipolazione dei dati, come il recupero di un flusso dal server o l'accesso al database, non deve bloccare il thread dell'interfaccia utente. L'utilizzo di un AsyncTask è un modo per eseguire gli aggiornamenti in modo asincrono. Ad esempio, durante il caricamento delle informazioni sul canale da un server di backend, puoi utilizzare AsyncTask come segue:

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 devi aggiornare regolarmente i dati EPG, valuta l'utilizzo di WorkManager per eseguire il processo di aggiornamento durante il tempo di inattività, ad esempio ogni giorno alle 03:00.

Altre tecniche per separare le attività di aggiornamento dei dati dal thread dell'interfaccia utente includono l'uso della classe HandlerThread o puoi implementarne una personalizzata utilizzando le classi Looper e Handler. Per ulteriori informazioni, consulta Processi e thread.

I canali possono utilizzare i link alle app per consentire agli utenti di avviare facilmente un'attività correlata mentre guardano i contenuti del canale. Le app del canale utilizzano i link alle app per estendere il coinvolgimento degli utenti lanciando attività che mostrano informazioni correlate o contenuti aggiuntivi. Ad esempio, puoi usare i link dell'app per:

  • Aiuta l'utente a scoprire e acquistare contenuti correlati.
  • Fornisci ulteriori informazioni sui contenuti attualmente in riproduzione.
  • Durante la visualizzazione dei contenuti a puntate, inizia a guardare l'episodio successivo di una serie.
  • Consenti all'utente di interagire con i contenuti, ad esempio valutare o recensire i contenuti, senza interromperne la riproduzione.

I link alle app vengono visualizzati quando l'utente preme Seleziona per mostrare il menu TV mentre guarda i contenuti del canale.

Figura 1. Un esempio di link all'app visualizzato nella riga Canali mentre sono mostrati i contenuti del canale.

Quando l'utente seleziona il link dell'app, il sistema avvia un'attività utilizzando un URI di intent specificato dall'app del canale. La riproduzione dei contenuti del canale continua mentre l'attività del link all'app è attiva. L'utente può tornare ai contenuti del canale premendo Indietro.

Fornisci i dati del canale per il collegamento dell'app

Android TV crea automaticamente un link all'app per ogni canale, utilizzando le informazioni ricavate dai dati del canale. Per fornire informazioni sul link dell'app, specifica i seguenti dettagli nei campi TvContract.Channels:

  • COLUMN_APP_LINK_COLOR: il colore acceso del link dell'app per questo canale. Per un esempio di colore di contrasto, vedi figura 2, callout 3.
  • COLUMN_APP_LINK_ICON_URI - L'URI dell'icona del badge dell'app del link dell'app per questo canale. Per un esempio di icona del badge dell'app, vedi la figura 2, callout 2.
  • COLUMN_APP_LINK_INTENT_URI - L'URI intent del link dell'app per questo canale. Puoi creare l'URI utilizzando toUri(int) con URI_INTENT_SCHEME e convertirlo nuovamente nell'intent originale con parseUri().
  • COLUMN_APP_LINK_POSTER_ART_URI - L'URI della locandina utilizzata come sfondo del link dell'app per questo canale. Per un esempio di immagine poster, vedi la figura 2, callout 1.
  • COLUMN_APP_LINK_TEXT - Il testo descrittivo del link dell'app del canale. Per una descrizione di esempio di un link dell'app, vedi il testo nella Figura 2, callout 3.

Figura 2. Dettagli del link dell'app.

Se i dati del canale non specificano informazioni sul link dell'app, il sistema crea un link dell'app predefinito. Il sistema sceglie i dettagli predefiniti come segue:

  • Per l'URI intent (COLUMN_APP_LINK_INTENT_URI), il sistema utilizza l'attività ACTION_MAIN per la categoria CATEGORY_LEANBACK_LAUNCHER, generalmente definita nel file manifest dell'app. Se questa attività non è definita, viene visualizzato un link dell'app non funzionante: se l'utente fa clic su di esso, non accade nulla.
  • Per il testo descrittivo (COLUMN_APP_LINK_TEXT), il sistema utilizza "Apri app-name". Se non viene definito un URI dell'intent del link dell'app utilizzabile, il sistema utilizza "Nessun link disponibile".
  • Per il colore di contrasto (COLUMN_APP_LINK_COLOR), il sistema utilizza il colore predefinito dell'app.
  • Per l'immagine poster (COLUMN_APP_LINK_POSTER_ART_URI), il sistema utilizza il banner della schermata Home dell'app. Se l'app non fornisce un banner, il sistema utilizza un'immagine predefinita dell'app per la TV.
  • Per l'icona del badge (COLUMN_APP_LINK_ICON_URI), il sistema utilizza un badge che mostra il nome dell'app. Se il sistema utilizza anche il banner o l'immagine predefinita dell'app per l'immagine poster, non viene mostrato alcun badge dell'app.

Puoi specificare i dettagli dei link dell'app per i tuoi canali nell'attività di configurazione dell'app. Puoi aggiornare questi dettagli del link dell'app in qualsiasi momento; pertanto, se un link all'app deve corrispondere alle modifiche del canale, aggiorna i dettagli del link dell'app e chiama ContentResolver.update() in base alle esigenze. Per maggiori dettagli sull'aggiornamento dei dati dei canali, consulta Aggiornare i dati dei canali.