Utiliser les données de chaîne

Votre entrée TV doit fournir des données de guide électronique des programmes (EPG) pour au moins une chaîne dans son activité de configuration. Vous devez également mettre à jour ces données régulièrement, en tenant compte de la taille de la mise à jour et du thread de traitement qui la gère. De plus, vous pouvez fournir des liens d'application pour les chaînes qui guident l'utilisateur vers des contenus et des activités associés. Cette leçon explique comment créer et mettre à jour des données de chaîne et de programme dans la base de données système en tenant compte de ces considérations.

Essayez l'exemple d'application TV Input Service.

Obtenir l'autorisation

Pour que votre entrée TV fonctionne avec les données EPG, elle doit déclarer l' autorisation d'écriture dans son fichier manifeste Android comme suit :

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

Enregistrer des chaînes dans la base de données

La base de données système Android TV conserve les enregistrements des données de chaîne pour les entrées TV. Dans votre activité de configuration, pour chacune de vos chaînes, vous devez mapper vos données de chaîne avec les champs suivants de la classe TvContract.Channels :

Bien que le framework d'entrée TV soit suffisamment générique pour gérer les contenus de diffusion traditionnels et les contenus OTT (Over-The-Top) sans distinction, vous pouvez définir les colonnes suivantes en plus pour mieux identifier les chaînes de diffusion traditionnelles :

Si vous souhaitez fournir des informations sur les liens d'application pour vos chaînes, vous devez mettre à jour des champs supplémentaires. Pour en savoir plus sur les champs de lien d'application, consultez Ajouter des informations sur les liens d'application.

Pour les entrées TV basées sur la diffusion en streaming sur Internet, attribuez vos propres valeurs en conséquence afin que chaque chaîne puisse être identifiée de manière unique.

Extrayez les métadonnées de votre chaîne (au format XML, JSON ou autre) à partir de votre serveur backend, puis, dans votre activité de configuration, mappez les valeurs à la base de données système comme suit :

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

Dans cet exemple, channel est un objet qui contient les métadonnées de la chaîne provenant du serveur backend.

Présenter des informations sur la chaîne et le programme

L'application TV du système présente des informations sur la chaîne et le programme aux utilisateurs lorsqu'ils zappent, comme illustré dans la figure 1. Pour vous assurer que les informations sur la chaîne et le programme fonctionnent avec le présentateur d'informations sur la chaîne et le programme de l'application TV du système, suivez ces consignes :

  1. Numéro de chaîne (COLUMN_DISPLAY_NUMBER)
  2. Icône (android:icon dans le fichier manifeste de l'entrée TV)
  3. Description du programme (COLUMN_SHORT_DESCRIPTION)
  4. Titre du programme (COLUMN_TITLE)
  5. Logo de la chaîne (TvContract.Channels.Logo)
    • Utilisez la couleur #EEEEEE pour correspondre au texte environnant.
    • N'incluez pas de marge intérieure.
  6. Affiche (COLUMN_POSTER_ART_URI)
    • Format entre 16:9 et 4:3
Figure 1. Présentateur d'informations sur la chaîne et le programme de l'application TV du système.

L'application TV du système fournit les mêmes informations via le guide des programmes, y compris l'affiche, comme illustré dans la figure 2.

Figure 2. Guide des programmes de l'application TV du système.

Mettre à jour les données de la chaîne

Lorsque vous mettez à jour des données de chaîne existantes, utilisez la méthode update au lieu de supprimer et de rajouter les données. Vous pouvez identifier la version actuelle des données à l'aide de Channels.COLUMN_VERSION_NUMBER et Programs.COLUMN_VERSION_NUMBER lorsque vous choisissez les enregistrements à mettre à jour.

Remarque : L'ajout de données de chaîne au ContentProvider peut prendre du temps. N'ajoutez les programmes en cours (ceux qui sont diffusés dans les deux heures suivant l'heure actuelle) que lorsque vous configurez votre EpgSyncJobService pour mettre à jour le reste des données de la chaîne en arrière-plan. Pour obtenir un exemple, consultez l' exemple d'application Android TV Live TV.

Charger des données de chaîne par lot

Lorsque vous mettez à jour la base de données système avec une grande quantité de données de chaîne, utilisez la méthode ContentResolver applyBatch ou bulkInsert. Voici un exemple d'utilisation de 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();
    }
}

Traiter les données de chaîne de manière asynchrone

La manipulation des données, comme l'extraction d'un flux à partir du serveur ou l'accès à la base de données, ne doit pas bloquer le thread UI. L'utilisation d'un AsyncTask est un moyen d'effectuer des mises à jour de manière asynchrone. Par exemple, lorsque vous chargez des informations sur la chaîne à partir d'un serveur backend, vous pouvez utiliser AsyncTask comme suit :

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

Si vous devez mettre à jour régulièrement les données EPG, envisagez d'utiliser WorkManager pour exécuter le processus de mise à jour pendant les périodes d'inactivité, par exemple tous les jours à 3h00.

D'autres techniques permettent de séparer les tâches de mise à jour des données du thread UI, par exemple en utilisant la HandlerThread classe. Vous pouvez également implémenter la vôtre à l'aide des Looper et Handler classes. Pour en savoir plus, consultez Processus et threads.

Les chaînes peuvent utiliser des liens d'application pour permettre aux utilisateurs de lancer une activité associée pendant qu'ils regardent le contenu de la chaîne. Les applications de chaîne utilisent des liens d'application pour étendre l'engagement des utilisateurs en lançant des activités qui affichent des informations associées ou du contenu supplémentaire. Par exemple, vous pouvez utiliser des liens d'application pour effectuer les opérations suivantes :

  • Guider l'utilisateur pour qu'il découvre et achète du contenu associé.
  • Fournir des informations supplémentaires sur le contenu en cours de lecture.
  • Lorsque vous regardez un contenu épisodique, commencez à regarder l'épisode suivant d'une série.
  • Permettre à l'utilisateur d'interagir avec le contenu (par exemple, le noter ou le commenter) sans interrompre la lecture.

Les liens d'application s'affichent lorsque l'utilisateur appuie sur Sélectionner pour afficher le menu TV pendant qu'il regarde le contenu de la chaîne.

Figure 1 : Exemple de lien d'application affiché sur la ligne Chaînes pendant la diffusion du contenu de la chaîne.

Lorsque l'utilisateur sélectionne le lien d'application, le système démarre une activité à l'aide d'un URI d'intent spécifié par l'application de la chaîne. Le contenu de la chaîne continue d'être diffusé pendant que l'activité du lien d'application est active. L'utilisateur peut revenir au contenu de la chaîne en appuyant sur Retour.

Fournir des données de chaîne de lien d'application

Android TV crée automatiquement un lien d'application pour chaque chaîne à l'aide des informations provenant des données de la chaîne. Pour fournir des informations sur les liens d'application, spécifiez les détails suivants dans les champs TvContract.Channels :

  • COLUMN_APP_LINK_COLOR : couleur d'accentuation du lien d'application pour cette chaîne. Pour obtenir un exemple de couleur d'accentuation, consultez la figure 2, légende 3.
  • COLUMN_APP_LINK_ICON_URI - URI de l'icône de badge d'application du lien d'application pour cette chaîne. Pour obtenir un exemple d'icône de badge d'application, consultez la figure 2, légende 2.
  • COLUMN_APP_LINK_INTENT_URI - URI d'intent du lien d'application pour cette chaîne. Vous pouvez créer l'URI à l'aide de toUri(int) avec URI_INTENT_SCHEME et reconvertir l'URI en intent d'origine avec parseUri.
  • COLUMN_APP_LINK_POSTER_ART_URI : URI de l'affiche utilisée comme arrière-plan du lien d'application pour cette chaîne. Pour obtenir un exemple d'affiche, consultez la figure 2, légende 1.
  • COLUMN_APP_LINK_TEXT - texte descriptif du lien d'application pour cette chaîne. Pour obtenir un exemple de description de lien d'application, consultez le texte de la figure 2, légende 3.
Figure 2 Détails du lien d'application.

Si les données de la chaîne ne spécifient pas d'informations sur les liens d'application, le système crée un lien d'application par défaut. Le système choisit les détails par défaut comme suit :

  • Pour l'URI d'intent (COLUMN_APP_LINK_INTENT_URI), le système utilise l'activité ACTION_MAIN pour la catégorie CATEGORY_LEANBACK_LAUNCHER, généralement définie dans le fichier manifeste d'application. Si cette activité n'est pas définie, un lien d'application non fonctionnel s'affiche. Si l'utilisateur clique dessus, rien ne se passe.
  • Pour le texte descriptif (COLUMN_APP_LINK_TEXT), le système utilise "Ouvrir app-name". Si aucun URI d'intent de lien d'application viable n'est défini, le système utilise "Aucun lien disponible".
  • Pour la couleur d'accentuation (COLUMN_APP_LINK_COLOR), le système utilise la couleur par défaut de l'application.
  • Pour l'image poster (COLUMN_APP_LINK_POSTER_ART_URI), le système utilise la bannière de l'écran d'accueil de l'application. Si l'application ne fournit pas de bannière, le système utilise une image d'application TV par défaut.
  • Pour l'icône de badge (COLUMN_APP_LINK_ICON_URI), le système utilise un badge qui affiche le nom de l'application. Si le système utilise également la bannière de l'application ou l'image d'application par défaut pour l'image poster, aucun badge d'application n'est affiché.

Vous spécifiez les détails des liens d'application pour vos chaînes dans l'activité de configuration de votre application. Vous pouvez mettre à jour ces détails à tout moment. Par conséquent, si un lien d'application doit correspondre aux modifications de la chaîne, mettez à jour les détails du lien d'application et appelez ContentResolver.update si nécessaire. Pour en savoir plus sur la mise à jour des données de la chaîne, consultez Mettre à jour les données de la chaîne.