Cast Connect avec l'application Android TV

googlecastnew500.png

Cet atelier de programmation vous explique comment modifier une application Android TV afin que des applications émettrices Cast existantes puissent caster des contenus et communiquer avec l'application Android TV.

Présentation de Google Cast et de Cast Connect

Google Cast permet aux utilisateurs de caster des contenus depuis un appareil mobile sur un téléviseur. Une session Google Cast comprend généralement deux composants : une application émettrice et une application réceptrice. Les applications émettrices, telles qu'une application mobile ou un site Web comme youtube.com, lancent et contrôlent la lecture d'une application réceptrice. Les applications réceptrices Google Cast sont des applications HTML5 qui s'exécutent sur des appareils Chromecast et Android TV.

La quasi-intégralité de l'état d'une session Cast est stockée dans l'application réceptrice. Lors de la mise à jour de l'état, par exemple si un nouvel élément multimédia est chargé, un état du contenu multimédia est diffusé vers tous les émetteurs. Ces annonces contiennent l'état actuel de la session Cast. Les applications émettrices utilisent cet état de contenu multimédia pour afficher des informations de lecture dans leur interface utilisateur.

Cast Connect s'appuie sur cette infrastructure où votre application Android TV joue le rôle de récepteur. La bibliothèque Cast Connect permet à l'application Android TV de recevoir des messages et de diffuser l'état d'un contenu multimédia comme s'il s'agissait d'une application réceptrice Cast.

Qu'allons-nous créer ?

Lorsque vous aurez terminé cet atelier de programmation, vous pourrez utiliser des applications émettrices Cast pour caster des vidéos vers une application Android TV. L'application Android TV peut également communiquer avec les applications émettrices via le protocole Cast.

Points abordés

  • Ajouter la bibliothèque Cast Connect à un exemple d'application Android TV.
  • Connecter une application émettrice Cast et lancer l'application Android TV.
  • Lancer la lecture d'un contenu multimédia dans l'application Android TV via l'application émettrice Cast.
  • Comment envoyer l'état d'un contenu multimédia depuis l'application Android TV vers les applications émettrices Cast.

Prérequis

Vous pouvez télécharger l'exemple de code dans son intégralité sur votre ordinateur…

Télécharger le code source

puis décompresser le fichier ZIP téléchargé.

Voyons d'abord comment se présente l'exemple d'application terminée. L'application Android TV utilise l'interface utilisateur Leanback et un lecteur vidéo basique. L'utilisateur peut sélectionner une vidéo dans une liste pour en lancer la lecture sur le téléviseur. Grâce à l'application mobile émettrice associée, un utilisateur peut également caster une vidéo sur l'application Android TV.

f9f98aa234e84bae.png

Enregistrer des appareils de développeur

Pour activer les fonctionnalités de Cast Connect pour le développement d'applications, vous devez enregistrer le numéro de série du Chromecast intégré à l'appareil Android TV que vous utiliserez dans la console de développement Cast. Pour trouver ce numéro de série, accédez à Paramètres > Préférences relatives aux appareils > Chromecast Intégré > Numéro de série sur votre Android TV. Notez que ce numéro est différent du numéro de série de votre appareil physique et peut uniquement être obtenu à l'aide de la méthode décrite ci-dessus.

f74dfe19bc459b76.png

Si l'enregistrement n'est pas effectué, Cast Connect ne fonctionnera que pour les applications installées depuis le Google Play Store pour des raisons de sécurité. Redémarrez l'appareil 15 minutes après le lancement du processus d'enregistrement.

Installer l'application émettrice Android

Pour tester les demandes d'émission depuis un appareil mobile, nous mettons à votre disposition une application émettrice simple appelée Cast Videos. Nous allons utiliser ADB pour installer cet APK. Si vous avez déjà installé une autre version de Cast Videos, veuillez la désinstaller de tous les profils hébergés par l'appareil avant de continuer.

  1. Activez les options pour les développeurs et le débogage USB sur votre téléphone Android.
  2. Branchez un câble de données USB pour connecter votre téléphone Android à votre ordinateur de développement.
  3. Installez mobile-sender.apk sur votre téléphone Android.

93e35a0f0332f290.png

  1. L'application Cast Videos se trouve sur votre téléphone Android. e29d89df484d9661.png

d6a0435ec3bac0af.png

Installer l'application Android TV

Les instructions ci-dessous décrivent comment ouvrir et exécuter l'exemple d'application terminée dans Android Studio :

  1. Sélectionnez Import Project (Importer un projet) sur l'écran d'accueil ou passez par les options du menu File > New > Import Project… (Fichier > Nouveau > Importer un projet…).
  2. Sélectionnez le répertoire android_studio_folder.pngapp-done dans le dossier de l'exemple de code, puis cliquez sur "OK".
  3. Cliquez sur File (Fichier) > 1791b5212a8947d.png Sync Project with Gradle Files (Synchroniser le projet avec les fichiers Gradle).
  4. Activez les options pour les développeurs et le débogage USB sur votre appareil Android TV.
  5. Effectuez la connexion ADB à votre appareil Android TV. Il doit s'afficher dans Android Studio. 7bcf00bfb6877ad5.png
  6. Cliquez sur le bouton execute.pngRun (Exécuter). L'application Android TV Cast Connect Codelab devrait apparaître après quelques secondes.

Diffusons Cast Connect avec l'application Android TV

  1. Accédez à l'écran d'accueil Android TV.
  2. Ouvrez l'application émettrice Cast Videos sur votre téléphone Android. Cliquez sur l'icône Cast f77992b2cf0422a2.png, puis sélectionnez votre appareil Android TV.
  3. L'application Cast Connect Codelab Android TV est lancée sur votre téléviseur Android TV, et l'icône Cast de votre application émettrice indique qu'elle est connectée303287388679d79b.png.
  4. Sélectionnez une vidéo dans l'application Android TV pour en lancer la lecture sur votre téléviseur.
  5. Sur votre téléphone mobile, une mini télécommande s'affiche désormais à la base de l'application émettrice. Vous pouvez utiliser le bouton Lecture/Pause pour contrôler la lecture.
  6. Sélectionnez une vidéo sur le téléphone mobile, puis lancez sa lecture. La lecture de la vidéo commence sur votre téléviseur Android TV, et votre télécommande s'affiche sur votre émetteur mobile.
  7. Verrouillez votre téléphone, puis déverrouillez-le : une notification vous permettant de contrôler la lecture du contenu multimédia ou d'arrêter la diffusion devrait maintenant apparaître sur l'écran de verrouillage.

a20257e816c913a.png

Maintenant que nous avons vérifié que l'intégration de Cast Connect dans l'application était effectuée, il nous reste à ajouter la compatibilité avec Cast Connect sur l'application de démarrage que vous avez téléchargée. Il est temps d'apporter les modifications souhaitées au projet de démarrage à l'aide d'Android Studio :

  1. Sélectionnez Import Project (Importer un projet) sur l'écran d'accueil ou passez par les options du menu File > New > Import Project… (Fichier > Nouveau > Importer un projet…).
  2. Sélectionnez le répertoire android_studio_folder.pngapp-start dans le dossier de l'exemple de code, puis cliquez sur "OK".
  3. Cliquez sur File (Fichier) > 1791b5212a8947d.png Sync Project with Gradle Files (Synchroniser le projet avec les fichiers Gradle).
  4. Sélectionnez un appareil Android TV, puis cliquez sur le bouton execute.pngRun (Exécuter) pour exécuter l'application et découvrir l'interface utilisateur. 7bcf00bfb6877ad5.png

f9f98aa234e84bae.png

Conception de l'application

L'application fournit une liste de vidéos que l'utilisateur peut parcourir. Les utilisateurs peuvent sélectionner une vidéo à lire sur l'appareil Android TV. L'application peut effectuer deux activités principales : MainActivity et PlaybackActivity.

MainActivity

Cette activité contient un fragment (MainFragment). La liste des vidéos et leurs métadonnées associées sont configurées dans la classe MovieList, tandis que la méthode setupMovies() est appelée pour créer une liste d'objets Movie.

Un objet Movie représente une entité vidéo avec un titre, une description, des vignettes et une URL de vidéo. Chaque objet Movie est associé à un CardPresenter pour présenter la vignette vidéo avec le titre et le producteur, puis transmis à ArrayObjectAdapter.

Lorsqu'un élément est sélectionné, l'objet Movie correspondant est transmis à l'activité PlaybackActivity.

PlaybackActivity

Cette activité contient un fragment (PlaybackVideoFragment) qui héberge une classe VideoView avec le lecteur ExoPlayer, des contrôles multimédias et une zone de texte pour afficher la description de la vidéo sélectionnée et permettre à l'utilisateur de lire la vidéo sur l'appareil Android TV. La télécommande permet de lire/mettre en pause une vidéo ou de chercher un niveau d'avancement dans sa lecture.

Conditions préalables pour Cast Connect

Cast Connect utilise de nouvelles versions des services Google Play nécessitant la mise à jour de votre application Android TV pour utiliser l'espace de noms AndroidX.

Pour pouvoir utiliser Cast Connect dans votre application Android TV, vous devez créer et gérer des événements à partir d'une session multimédia. La bibliothèque Cast Connect génère des états de contenus multimédias basés sur l'état de la session multimédia. Votre session multimédia est également utilisée par la bibliothèque Cast Connect pour signaler l'arrivée de certains messages d'un émetteur, comme une mise en pause.

Dépendances

Mettez à jour le fichier build.gradle de l'application afin d'inclure les dépendances de bibliothèque requises :

dependencies {
    ....

    // Cast Connect libraries and dependencies
    implementation 'com.google.android.gms:play-services-cast-tv:17.0.0'
    implementation 'com.google.android.gms:play-services-cast:19.0.0'
}

Synchronisez le projet pour vérifier qu'il ne comporte aucune erreur.

Initialisation

CastReceiverContext est un objet singleton permettant de coordonner toutes les interactions Cast. Vous devez mettre en œuvre l'interface ReceiverOptionsProvider pour fournir les options CastReceiverOptions lors de l'initialisation de CastReceiverContext.

Créez le fichier CastReceiverOptionsProvider.java, puis ajoutez la classe suivante au projet :

package com.google.sample.cast.castconnect;

import android.content.Context;
import com.google.android.gms.cast.tv.CastReceiverOptions;
import com.google.android.gms.cast.tv.ReceiverOptionsProvider;

public class CastReceiverOptionsProvider implements ReceiverOptionsProvider {
    @Override
    public CastReceiverOptions getOptions(Context context) {
        return new CastReceiverOptions.Builder(context)
                .setStatusText("Cast Connect Codelab")
                .build();
    }
}

Indiquez ensuite la source des options du récepteur dans la balise <application> du fichier AndroidManifest.xml de l'application :

<application>
  ...

  <meta-data
    android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
    android:value="com.google.sample.cast.castconnect.CastReceiverOptionsProvider" />
</application>

Pour vous connecter à votre application Android TV via l'émetteur Cast, sélectionnez l'activité à lancer. Dans cet atelier de programmation, nous lancerons l'activité MainActivity de l'application lors du démarrage d'une session Cast. Dans le fichier AndroidManifest.xml, ajoutez le filtre d'intent de lancement de l'activité MainActivity.

<activity android:name=".MainActivity">
  ...
  <intent-filter>
    <action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
    <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</activity>

Cycle de vie d'un contexte du récepteur Cast

Vous devez démarrer le contexte CastReceiverContext lorsque votre application est lancée, puis arrêter ce contexte CastReceiverContext lorsque l'application passe à l'arrière-plan. Nous vous recommandons d'utiliser l'élément LifecycleObserver de la bibliothèque androidx.lifecycle pour gérer les appels de CastReceiverContext.start() et de CastReceiverContext.stop().

Ouvrez MyApplication.java, initialisez le contexte de diffusion en appelant initInstance() dans la méthode onCreate de l'application. Dans la classe AppLifeCycleObserver, utilisez start() sur CastReceiverContext lorsque l'application est réactivée et stop() lorsque l'application est mise en pause :

package com.google.sample.cast.castconnect;

...

import com.google.android.gms.cast.tv.CastReceiverContext;

public class MyApplication extends Application {

    private static final String LOG_TAG = "MyApplication";

    @Override
    public void onCreate() {
        super.onCreate();
        CastReceiverContext.initInstance(this);
        ProcessLifecycleOwner.get().getLifecycle().addObserver(new AppLifecycleObserver());
    }

    public static class AppLifecycleObserver implements DefaultLifecycleObserver {
        @Override
        public void onResume(@NonNull LifecycleOwner owner) {
            Log.d(LOG_TAG, "onResume");
            CastReceiverContext.getInstance().start();
        }

        @Override
        public void onPause(@NonNull LifecycleOwner owner) {
            Log.d(LOG_TAG, "onPause");
            CastReceiverContext.getInstance().stop();
        }
    }
}

Connecter MediaSession à MediaManager

MediaManager est une propriété du singleton CastReceiverContext, elle gère l'état du contenu multimédia et l'intent de chargement, convertit les messages de l'espace de noms multimédia des émetteurs en commandes multimédias et renvoie l'état du contenu multimédia aux émetteurs.

Lorsque vous créez une propriété MediaSession, vous devez également fournir le jeton MediaSession actuel à MediaManager afin de lui indiquer où envoyer les commandes et récupérer l'état de la lecture du contenu multimédia. Assurez-vous que le fichier MediaSession est initialisé avant de définir le jeton sur MediaManager.

import com.google.android.gms.cast.tv.CastReceiverContext;
...

public class PlaybackVideoFragment extends VideoSupportFragment {

    private CastReceiverContext castReceiverContext;
    ...

    private void initializePlayer() {
        if (mPlayer == null) {
            ...
            mMediaSession = new MediaSessionCompat(getContext(), LOG_TAG);
            ...

            castReceiverContext = CastReceiverContext.getInstance();
            if (castReceiverContext != null) {
                MediaManager mediaManager = castReceiverContext.getMediaManager();
                mediaManager.setSessionCompatToken(mMediaSession.getSessionToken());
            }
        }
    }
}

Lorsque vous n'utilisez plus la propriété MediaSession pour des raisons d'inactivité, nous vous conseillons de définir un jeton nul sur MediaManager :

private void releasePlayer() {
    if (mMediaSession != null) {
        mMediaSession.release();
    }
    if (castReceiverContext != null) {
        MediaManager mediaManager = castReceiverContext.getMediaManager();
        mediaManager.setSessionCompatToken(null);
    }
    ...
}

Exécutons l'exemple d'application

Cliquez sur le bouton execute.pngRun (Exécuter) pour déployer l'application sur votre appareil Android TV, fermez l'application, puis revenez sur l'écran d'accueil Android TV. Depuis l'émetteur, cliquez sur l'icône Cast f77992b2cf0422a2.png, puis sélectionnez votre appareil Android TV. L'application Android TV s'affiche sur l'appareil, et l'icône Cast affiche l'état connecté.

La commande de chargement est envoyée via un intent avec le nom de package que vous avez défini dans la console de développement. Pour spécifier l'activité cible qui recevra cet intent, vous devez ajouter le filtre d'intent prédéfini suivant dans votre application Android TV. Dans le fichier AndroidManifest.xml, ajoutez le filtre d'intent de chargement à PlayerActivity :

<activity android:name="com.google.sample.cast.castconnect.PlaybackActivity"
          android:launchMode="singleTask">
  <intent-filter>
     <action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
     <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</activity>

Gérer les requêtes de chargement sur Android TV

Maintenant que l'activité est configurée pour recevoir cet intent contenant une requête de chargement, nous devons gérer cette action.

L'application appelle une méthode privée appelée processIntent lorsque l'activité démarre. Cette méthode contient la structure de traitement des intents reçus. Pour gérer une requête de chargement, nous allons modifier cette méthode et envoyer l'intent pour un traitement supplémentaire en appelant la méthode onNewIntent de l'instance MediaManager. Si MediaManager détecte que l'intent est une requête de chargement, il extrait l'objet MediaLoadRequestData de l'intent et appelle MediaLoadCommandCallback.onLoad(). Modifiez la méthode processIntent dans PlaybackVideoFragment pour gérer l'intent contenant la requête de chargement :

public void processIntent(Intent intent) {
    MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager();
    // Pass intent to Cast SDK
    if (mediaManager.onNewIntent(intent)) {
        return;
    }

    // Clears all overrides in the modifier.
    mediaManager.getMediaStatusModifier().clear();

    // If the SDK doesn't recognize the intent, handle the intent with your own logic.
    ...
}

Nous allons ensuite étendre la classe abstraite MediaLoadCommandCallback, qui va remplacer la méthode onLoad() appelée par MediaManager. Cette méthode reçoit les données de la requête de chargement et les convertit en objet Movie. Une fois converti, le film est lu par le lecteur local. Le MediaManager est ensuite mis à jour avec MediaLoadRequest et diffuse l'état MediaStatus aux émetteurs connectés. Créez une classe privée imbriquée appelée MyMediaLoadCommandCallback dans PlaybackVideoFragment :

private class MyMediaLoadCommandCallback extends MediaLoadCommandCallback {
    @Override
    public Task<MediaLoadRequestData> onLoad(String senderId, MediaLoadRequestData mediaLoadRequestData) {
        Toast.makeText(getActivity(), "onLoad()", Toast.LENGTH_SHORT).show();

        if (mediaLoadRequestData == null) {
            // Throw MediaException to indicate load failure.
            return Tasks.forException(new MediaException(
                    new MediaError.Builder()
                            .setDetailedErrorCode(MediaError.DetailedErrorCode.LOAD_FAILED)
                            .setReason(MediaError.ERROR_REASON_INVALID_REQUEST)
                            .build()));
        }

        return Tasks.call(() -> {
            play(convertLoadRequestToMovie(mediaLoadRequestData));

            // Update media metadata and state
            MediaManager mediaManager = castReceiverContext.getMediaManager();
            mediaManager.setDataFromLoad(mediaLoadRequestData);
            mediaManager.broadcastMediaStatus();

            // Return the resolved MediaLoadRequestData to indicate load success.
            return mediaLoadRequestData;
        });
    }
}

private Movie convertLoadRequestToMovie(MediaLoadRequestData mediaLoadRequestData) {
    if (mediaLoadRequestData == null) {
        return null;
    }
    MediaInfo mediaInfo = mediaLoadRequestData.getMediaInfo();
    if (mediaInfo == null) {
        return null;
    }

    String videoUrl = mediaInfo.getContentId();
    if (mediaInfo.getContentUrl() != null) {
        videoUrl = mediaInfo.getContentUrl();
    }

    MediaMetadata metadata = mediaInfo.getMetadata();
    Movie movie = new Movie();
    movie.setVideoUrl(videoUrl);
    if (metadata != null) {
        movie.setTitle(metadata.getString(MediaMetadata.KEY_TITLE));
        movie.setDescription(metadata.getString(MediaMetadata.KEY_SUBTITLE));
        movie.setCardImageUrl(metadata.getImages().get(0).getUrl().toString());
    }
    return movie;
}

Maintenant que le rappel a été défini, nous devons l'enregistrer dans le fragment MediaManager. Le rappel doit être enregistré avant l'appel de MediaManager.onNewIntent(). Ajoutez setMediaLoadCommandCallback lorsque le lecteur est initialisé :

private void initializePlayer() {
    if (mPlayer == null) {
        ...
        mMediaSession = new MediaSessionCompat(getContext(), LOG_TAG);
        ...

        castReceiverContext = CastReceiverContext.getInstance();
        if (castReceiverContext != null) {
            MediaManager mediaManager = castReceiverContext.getMediaManager();
            mediaManager.setSessionCompatToken(mMediaSession.getSessionToken());
            mediaManager.setMediaLoadCommandCallback(new MyMediaLoadCommandCallback());
        }
    }
}

Exécutons l'exemple d'application

Cliquez sur le bouton execute.pngRun (Exécuter) pour déployer l'application sur votre appareil Android TV,. Depuis l'émetteur, cliquez sur l'icône Cast f77992b2cf0422a2.png, puis sélectionnez votre appareil Android TV. L'application Android TV est alors lancée sur l'appareil Android TV. Sélectionnez une vidéo sur l'appareil mobile pour en démarrer la lecture sur l'appareil Android TV. Vérifiez si vous recevez une notification sur votre téléphone vous donnant accès à des commandes de lecture. Essayez d'utiliser les commandes telles que la mise en pause, qui devrait suspendre la lecture de la vidéo sur l'appareil Android TV.

L'application actuelle offre les commandes de base compatibles avec une session multimédia, telles que lecture, pause et la recherche. Toutefois, certaines commandes Google Cast ne sont pas disponibles dans une session multimédia. Vous devez enregistrer un rappel MediaCommandCallback pour pouvoir utiliser ces commandes Cast.

Ajoutez MyMediaCommandCallback à l'instance MediaManager en utilisant setMediaCommandCallback une fois le lecteur initialisé :

private void initializePlayer() {
        ...

        castReceiverContext = CastReceiverContext.getInstance();
        if (castReceiverContext != null) {
            MediaManager mediaManager = castReceiverContext.getMediaManager();
            ...
            mediaManager.setMediaCommandCallback(new MyMediaCommandCallback());
        }
}

Créez la classe MyMediaCommandCallback pour remplacer certaines méthodes, par exemple onQueueUpdate(), afin d'activer ces commandes Cast :

private class MyMediaCommandCallback extends MediaCommandCallback {
        @Override
        public Task<Void> onQueueUpdate(String senderId, QueueUpdateRequestData queueUpdateRequestData) {
            Toast.makeText(getActivity(), "onQueueUpdate()", Toast.LENGTH_SHORT).show();

            // Queue Prev / Next
            if (queueUpdateRequestData.getJump() != null) {
                Toast.makeText(getActivity(),
                        "onQueueUpdate(): Jump = " + queueUpdateRequestData.getJump(),
                        Toast.LENGTH_SHORT).show();
            }

            return super.onQueueUpdate(senderId, queueUpdateRequestData);
        }
}

Modifier l'état du contenu multimédia

L'état de base du contenu multimédia est fourni à Cast Connect depuis la session multimédia. Pour utiliser les fonctionnalités avancées, votre application Android TV peut spécifier et remplacer des propriétés d'état supplémentaires via un élément MediaStatusModifier. MediaStatusModifier fonctionnera toujours sur la session MediaSession que vous avez définie dans CastReceiverContext.

Par exemple, voici comment spécifier setMediaCommandSupported lorsque le rappel onLoad est activé :

private class MyMediaLoadCommandCallback extends MediaLoadCommandCallback {
    @Override
    public Task<MediaLoadRequestData> onLoad(String senderId, MediaLoadRequestData mediaLoadRequestData) {
        Toast.makeText(getActivity(), "onLoad()", Toast.LENGTH_SHORT).show();

        ...

        return Tasks.call(() -> {
            play(convertLoadRequestToMovie(mediaLoadRequestData));

            // Update media metadata and state
            MediaManager mediaManager = castReceiverContext.getMediaManager();
            mediaManager.setDataFromLoad(mediaLoadRequestData);

            // Use MediaStatusModifier to provide additional information for Cast senders.
            mediaManager.getMediaStatusModifier()
                        .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT, true)
                        .setIsPlayingAd(false);

            mediaManager.broadcastMediaStatus();

            // Return the resolved MediaLoadRequestData to indicate load success.
            return mediaLoadRequestData;
        });
    }
}

Intercepter l'état MediaStatus avant sa diffusion

Comme pour l'élément MessageInterceptor du SDK du récepteur Web, vous pouvez spécifier un MediaStatusWriter dans votre MediaManager si vous souhaitez apporter des modifications supplémentaires à votre état MediaStatus avant qu'il ne soit diffusé auprès des émetteurs connectés.

Par exemple, vous pouvez définir des données personnalisées dans MediaStatus avant de les diffuser aux émetteurs mobiles :

MediaManager mediaManager = castReceiverContext.getMediaManager();
...

// Use MediaStatusInterceptor to process the MediaStatus before sending out.
mediaManager.setMediaStatusInterceptor(mediaStatusWriter -> {
    try {
        mediaStatusWriter.setCustomData(new JSONObject("{myData: 'CustomData'}"));
    } catch (JSONException e) {
        e.printStackTrace();
    }
});

Vous savez maintenant comment rendre une application Android TV compatible avec Google Cast à l'aide de la bibliothèque Cast Connect.

Pour en savoir plus, consultez le guide du développeur : https://developers.google.com/cast/docs/android_tv_receiver.