Rendre une application Android compatible avec Cast

1. Présentation

Logo Google Cast

Dans cet atelier de programmation, vous allez apprendre à modifier une application vidéo Android existante afin de caster du contenu sur un appareil compatible Google Cast.

Qu'est-ce que Google Cast ?

Google Cast permet aux utilisateurs de caster du contenu multimédia depuis un appareil mobile sur un téléviseur, et d'utiliser leur appareil mobile comme télécommande lors de la diffusion.

Le SDK Google Cast vous permet d'étendre les fonctionnalités de votre application afin de contrôler un téléviseur ou un système audio. Il vous permet d'ajouter les composants d'UI (interface utilisateur) requis, en fonction de la checklist de conception Google Cast.

La checklist de conception de Google Cast a été conçue pour garantir une expérience utilisateur simple et prévisible sur toutes les plates-formes compatibles.

Qu'allons-nous créer ?

À la fin de cet atelier de programmation, vous disposerez d'une application vidéo Android capable de caster des vidéos sur un appareil Google Cast.

Points abordés

  • Comment ajouter le SDK Google Cast à un exemple d'application vidéo
  • Comment ajouter l'icône Cast permettant de sélectionner un appareil Google Cast
  • Comment se connecter à un appareil Cast et lancer un récepteur multimédia
  • Comment caster une vidéo
  • Comment ajouter une mini-télécommande Cast à votre appli
  • Comment gérer les notifications du contenu multimédia et les commandes de l'écran de verrouillage
  • Comment ajouter une télécommande agrandie
  • Comment afficher un message en superposition pour annoncer le lancement de la fonctionnalité Cast
  • Comment personnaliser les widgets Cast
  • Intégrer Cast Connect

Prérequis

  • La dernière version du SDK Android
  • Android Studio, version 3.2 ou ultérieure
  • Un appareil mobile équipé d'Android 4.1 Jelly Bean ou ultérieure (niveau d'API 16)
  • Un câble de données USB pour connecter votre appareil mobile à votre ordinateur de développement
  • Un appareil Google Cast, comme Chromecast ou Android TV, configuré pour accéder à Internet.
  • Un téléviseur ou un moniteur doté d'une entrée HDMI
  • Un Chromecast avec Google TV est nécessaire pour tester l'intégration de Cast Connect, mais il est facultatif pour le reste de l'atelier de programmation. Si vous n'en avez pas, n'hésitez pas à ignorer l'étape Ajouter la prise en charge de Cast Connect, vers la fin de ce tutoriel.

Expérience

  • Vous devez avoir une connaissance préalable du développement Kotlin et d'Android.
  • Vous devez également avoir une expérience préalable en tant que téléspectateur :)

Comment allez-vous utiliser ce tutoriel ?

Je vais le lire uniquement Je vais le lire et effectuer les exercices

Comment évalueriez-vous votre niveau d'expérience en matière de création d'applications Android ?

Débutant Intermédiaire Expert

Comment évalueriez-vous votre expérience en tant que téléspectateur ?

Débutant Intermédiaire Expert

2. Obtenir l'exemple de code

Vous pouvez télécharger tout l'exemple de code sur votre ordinateur…

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

3. Exécuter l'application exemple

icône d'une paire de boussoles

Voyons d'abord comment se présente notre exemple d'application une fois terminée. L'appli est un lecteur vidéo de base. L'utilisateur peut sélectionner une vidéo à partir d'une liste, puis la lire en local sur l'appareil ou la caster sur un appareil Google Cast.

Une fois le code téléchargé, suivez les instructions ci-dessous pour ouvrir et exécuter l'exemple d'application terminée dans Android Studio:

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…).

Sélectionnez le répertoire Icône Dossierapp-done dans le dossier de l'exemple de code, puis cliquez sur "OK".

Cliquez sur File (Fichier) > Bouton "Sync Project with Gradle" (Synchroniser le projet avec Gradle) d'Android Studio Sync Project with Gradle Files (Synchroniser le projet avec les fichiers Gradle).

Activez le débogage USB sur votre appareil Android. Notez que l'écran des options pour les développeurs est masqué par défaut dans la version Android 4.2 et les versions ultérieures. Pour le rendre visible, accédez à Paramètres > À propos du téléphone et appuyez sept fois sur Numéro de build. Revenez ensuite à l'écran précédent et sélectionnez Système > Paramètres avancés. En bas de l'écran, appuyez sur Options pour les développeurs, puis sur l'option Débogage USB pour l'activer.

Branchez votre appareil Android et cliquez sur le bouton Bouton "Run" (Exécuter) d'Android Studio, un triangle vert pointant vers la droiteRun (Exécuter) dans Android Studio. L'application vidéo intitulée Cast Videos devrait apparaître au bout de quelques secondes.

Cliquez sur l'icône Cast dans l'appli vidéo, puis sélectionnez votre appareil Google Cast.

Après avoir choisi une vidéo, cliquez sur le bouton de lecture.

La vidéo démarrera alors sur votre appareil Google Cast.

Et une télécommande agrandie s'affichera en plein écran sur votre appareil mobile. À l'aide du bouton de lecture/pause, vous pouvez contrôler la diffusion en cours.

Revenez à la liste des vidéos.

Une mini-télécommande est maintenant visible au bas de l'écran. Illustration d'un téléphone Android exécutant l'application "Caster des vidéos" avec la mini-télécommande affichée en bas de l'écran

Cliquez sur le bouton "Pause" de la mini-télécommande pour mettre la vidéo en pause sur le récepteur. Pour continuer de regarder la vidéo, cliquez de nouveau sur le bouton de lecture de la mini-télécommande.

Cliquez sur le bouton d'accueil de l'appareil mobile. Déroulez les notifications depuis le haut de l'écran : une notification concernant votre session Cast en cours devrait maintenant s'afficher.

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.

Revenez à l'appli vidéo, puis cliquez sur l'icône Cast pour arrêter la diffusion sur l'appareil Google Cast.

Questions fréquentes

4. Préparer le projet de départ

Illustration d 'un téléphone Android exécutant l'application Cast Videos

Nous objectif est de rendre l'application de départ que vous avez téléchargée compatible avec Google Cast. Au cours de cet atelier de programmation, nous utiliserons la terminologie Google Cast suivante :

  • Une appli de type émetteur s'exécute sur un appareil mobile ou un ordinateur portable.
  • Une appli de type récepteur s'exécute sur l'appareil Google Cast.

Vous voici prêt à poursuivre le développement du projet de départ en utilisant Android Studio :

  1. Sélectionnez le répertoire Icône Dossierapp-start à partir de votre exemple de code téléchargé (sélectionnez Import Project (Importer un projet) sur l'écran d'accueil ou l'option de menu File > New > Import Project (Fichier > Nouveau > Importer un projet).
  2. Cliquez sur le bouton Bouton "Sync Project with Gradle" (Synchroniser le projet avec Gradle) d'Android Studio Sync Project with Gradle Files (Synchroniser le projet avec les fichiers Gradle).
  3. Cliquez sur le bouton Bouton "Run" (Exécuter) d'Android Studio (triangle vert pointant vers la droite)Run (Exécuter) pour exécuter l'application et explorer l'interface utilisateur.

Conception d'applications

L'application récupère une liste de vidéos à partir d'un serveur Web distant pour fournir une liste à parcourir à l'utilisateur. Ce dernier peut alors sélectionner une vidéo pour en afficher les détails ou la lire localement sur son appareil mobile.

L'application peut effectuer deux activités principales : VideoBrowserActivity et LocalPlayerActivity. Pour pouvoir intégrer la fonctionnalité Google Cast, les activités doivent hériter de AppCompatActivity ou bien de son parent FragmentActivity. Cette restriction vient du fait que nous devrons ajouter l'élément MediaRouteButton (fourni dans la bibliothèque Support MediaRouter) en tant que MediaRouteActionProvider, ce qui ne fonctionnera que si l'activité hérite des classes mentionnées ci-dessus. En effet, la bibliothèque Support MediaRouter dépend de la bibliothèque Support AppCompat qui fournit les classes requises.

VideoBrowserActivity

Cette activité contient un Fragment (VideoBrowserFragment). Cette liste est soutenue par un ArrayAdapter (VideoListAdapter). La liste des vidéos et les métadonnées qui y sont associées sont hébergées sur un serveur distant, sous la forme d'un fichier JSON. Un AsyncTaskLoader (VideoItemLoader) récupère ce fichier JSON, puis le traite pour créer une liste d'objets MediaItem.

Un objet MediaItem sert à modéliser une vidéo, ainsi que les métadonnées qui lui sont associées telles que le titre, la description, l'URL du flux, l'URL des images associées et les pistes de texte (pour les sous-titres), le cas échéant. Comme l'objet MediaItem est transmis d'une activité à l'autre, il dispose de méthodes utilitaires qui permettent de le convertir en Bundle, et inversement.MediaItem

Une fois que le loader a créé la liste de MediaItems, il la transmet au VideoListAdapter, qui la présente ensuite dans le VideoBrowserFragment. Pour l'utilisateur, une liste de vignettes vidéos, où chaque vidéo est accompagnée d'une brève description, s'affiche à l'écran. Lorsqu'un élément est sélectionné, le MediaItem correspondant est converti en Bundle et transmis à LocalPlayerActivity.

LocalPlayerActivity

Cette activité affiche les métadonnées associées à une vidéo particulière, et permet à l'utilisateur de lire la vidéo localement sur son appareil mobile.

Elle héberge également une VideoView, des commandes multimédias et une zone de texte permettant d'afficher la description de la vidéo sélectionnée. Le lecteur vidéo occupe la partie supérieure de l'écran, laissant ainsi la place d'ajouter une description détaillée de la vidéo en dessous. L'utilisateur peut lire la vidéo choisie, la mettre en pause ou rechercher d'autres vidéos à visionner localement.

Dépendances

Comme nous utilisons AppCompatActivity, nous avons besoin de la bibliothèque Support AppCompat. Pour gérer la liste de vidéos et obtenir les images de la liste de manière asynchrone, nous utilisons la bibliothèque Volley.

Questions fréquentes

5. Ajouter l'icône Cast

Illustration de la partie supérieure d'un téléphone Android avec l'application Cast Video en cours d'exécution. Le bouton Cast s'affiche en haut à droite de l'écran.

Une application compatible Cast affiche l'icône Cast dans chacune de ses activités. Lorsque l'utilisateur clique sur cette icône, la liste des appareils Cast qu'il peut sélectionner s'affiche. Si un contenu était en cours de lecture localement sur l'appareil émetteur, le fait de sélectionner un appareil Cast démarre ou reprend cette même lecture directement sur l'appareil Cast sélectionné. À tout moment, l'utilisateur doit pouvoir cliquer sur l'icône Cast pour interrompre la diffusion émise à partir de votre application sur l'appareil Cast. L'utilisateur doit pouvoir se connecter à l'appareil Cast ou s'en déconnecter depuis n'importe quelle activité de votre application, comme décrit dans la checklist de conception Google Cast.

Dépendances

Mettez à jour le fichier app/build.gradle pour inclure les dépendances de la bibliothèque nécessaires :

dependencies {
    implementation 'androidx.appcompat:appcompat:1.5.0'
    implementation 'androidx.mediarouter:mediarouter:1.3.1'
    implementation 'androidx.recyclerview:recyclerview:1.2.1'
    implementation 'com.google.android.gms:play-services-cast-framework:21.1.0'
    implementation 'com.android.volley:volley:1.2.1'
    implementation "androidx.core:core-ktx:1.8.0"
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}

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

Initialisation

Le framework Cast dispose d'un objet singleton global, le CastContext, qui coordonne toutes les interactions Cast.

Vous devez implémenter l'interface OptionsProvider pour fournir les CastOptions nécessaires à l'initialisation du singleton CastContext. L'option la plus importante est l'ID de l'application "récepteur" : elle permet de filtrer les résultats lors de la détection des appareils Cast et de lancer l'application "récepteur" lorsqu'une session Cast est démarrée.

Lorsque vous développez votre propre application compatible Cast, vous devez vous inscrire en tant que développeur Cast afin d'obtenir votre ID d'application. Cela dit, dans cet atelier de programmation, nous utiliserons un exemple d'ID d'application.

Ajoutez le nouveau fichier CastOptionsProvider.kt suivant au package com.google.sample.cast.refplayer du projet:

package com.google.sample.cast.refplayer

import android.content.Context
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.SessionProvider

class CastOptionsProvider : OptionsProvider {
    override fun getCastOptions(context: Context): CastOptions {
        return CastOptions.Builder()
                .setReceiverApplicationId(context.getString(R.string.app_id))
                .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}

Déclarez ensuite l'OptionsProvider dans le tag application du fichier AndroidManifest.xml :

<meta-data
    android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
    android:value="com.google.sample.cast.refplayer.CastOptionsProvider" />

Dans VideoBrowserActivity, appelez la méthode onCreate pour lancer en douceur l'initialisation de CastContext :

import com.google.android.gms.cast.framework.CastContext

private var mCastContext: CastContext? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.video_browser)
    setupActionBar()

    mCastContext = CastContext.getSharedInstance(this)
}

Appliquez la même logique d'initialisation à LocalPlayerActivity.

Icône Cast

Après avoir initialisé CastContext, nous devons ajouter l'icône Cast pour permettre à l'utilisateur de choisir son appareil Cast. L'icône Cast est implémentée par le MediaRouteButton de la bibliothèque Support MediaRouter. Comme pour toute icône d'action que vous pouvez ajouter à votre activité via une ActionBar (barre d'action) ou une Toolbar (barre d'outils), vous devez d'abord ajouter l'élément de menu correspondant à votre menu.

Pour ce faire, modifiez le fichier res/menu/browse.xml et ajoutez l'élément MediaRouteActionProvider dans le menu avant l'élément des paramètres:

<item
    android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
    app:showAsAction="always"/>

Dans VideoBrowserActivity, remplacez la méthode onCreateOptionsMenu() par CastButtonFactory pour implémenter le MediaRouteButton dans le framework Cast:

import com.google.android.gms.cast.framework.CastButtonFactory

private var mediaRouteMenuItem: MenuItem? = null

override fun onCreateOptionsMenu(menu: Menu): Boolean {
     super.onCreateOptionsMenu(menu)
     menuInflater.inflate(R.menu.browse, menu)
     mediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu,
                R.id.media_route_menu_item)
     return true
}

Procédez de la même manière pour remplacer onCreateOptionsMenu dans LocalPlayerActivity.

Cliquez sur le bouton Bouton &quot;Run&quot; (Exécuter) d&#39;Android Studio, un triangle vert pointant vers la droiteRun (Exécuter) pour exécuter l'application sur votre appareil mobile. Une icône Cast devrait maintenant apparaître dans la barre d'action de l'application. Elle affichera la liste des appareils Cast de votre réseau local si vous cliquez dessus. La détection des appareils est automatiquement gérée par CastContext. Sélectionnez ensuite votre appareil Cast pour y charger l'exemple de l'application récepteur. Sachez que le fait de passer de l'activité de navigation à l'activité de lecture en local n'affecte pas l'état de votre icône Cast qui restera synchronisée.

Pour le moment, vous ne pouvez pas lire de vidéos sur l'appareil Cast, car nous n'avons pas encore connecté de support média. Cliquez sur l'icône Cast pour vous déconnecter.

6. Diffuser du contenu vidéo

Illustration d &#39;un téléphone Android exécutant l&#39;application Cast Videos

Nous allons étendre notre exemple d'application à la lecture de vidéos à distance sur un appareil Cast. Pour ce faire, nous devons écouter les différents événements générés par le framework Cast.

Caster un contenu multimédia

En règle générale, si vous souhaitez lire un contenu multimédia sur un appareil Cast, vous devez procéder comme suit :

  1. Créez un objet MediaInfo qui modélise un élément multimédia.
  2. Connectez-vous à l'appareil Cast, puis lancez votre application "récepteur".
  3. Chargez l'objet MediaInfo sur votre récepteur et lisez son contenu.
  4. Suivez l'état du contenu multimédia.
  5. Envoyez des commandes de lecture au récepteur en fonction des interactions de l'utilisateur.

Nous avons déjà effectué l'étape 2 à la section précédente. L'étape 3 est simple à réaliser avec le framework Cast. Quant à l'étape 1, elle équivaut au mappage d'un objet sur un autre : MediaInfo étant quelque chose que le framework Cast comprend, et MediaItem étant l'encapsulation de notre application pour un élément multimédia, nous devrions facilement pouvoir mapper un MediaItem sur un MediaInfo.

L'exemple d'application LocalPlayerActivity fait déjà la distinction entre la lecture en local et la lecture à distance à l'aide de l'énumération suivante :

private var mLocation: PlaybackLocation? = null

enum class PlaybackLocation {
    LOCAL, REMOTE
}

enum class PlaybackState {
    PLAYING, PAUSED, BUFFERING, IDLE
}

Dans cet atelier de programmation, vous n'avez pas besoin de comprendre exactement comment fonctionne la logique du lecteur de test. En revanche, il est important que vous compreniez que le lecteur multimédia de votre application doit être modifié de sorte à pouvoir également détecter ces deux contextes de lecture.

Pour le moment, notre lecteur local est toujours configuré à l'état de lecture locale puisque la possibilité de caster du contenu lui est encore inconnue. Nous devons donc mettre à jour l'interface utilisateur pour prendre en compte les transitions d'état qui se produisent dans le framework Cast. Par exemple, si nous commençons à caster un contenu, nous devons arrêter la lecture en local et désactiver certaines commandes. De même, si nous arrêtons de caster un contenu, nous devons repasser en lecture locale. Pour cela, il nous faut écouter les différents événements générés par le framework Cast.

Gestion d'une session Cast

Dans le framework Cast, une session Cast combine plusieurs étapes : la connexion à un appareil, le lancement (ou la reprise) d'une session, la connexion à une application "récepteur" et l'initialisation d'un canal de commande multimédia, le cas échéant. Le canal de commande multimédia détermine la façon dont le framework Cast envoie et reçoit les messages du lecteur multimédia récepteur.

La session Cast démarre automatiquement lorsque l'utilisateur sélectionne un appareil à partir de l'icône Cast, puis s'arrête automatiquement lorsque l'utilisateur se déconnecte. La reconnexion à une session sur un appareil récepteur suite à des problèmes de réseau est aussi gérée automatiquement par le SDK Cast.

Ajoutons un écouteur SessionManagerListener à l'activité LocalPlayerActivity :

import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.SessionManagerListener
...

private var mSessionManagerListener: SessionManagerListener<CastSession>? = null
private var mCastSession: CastSession? = null
...

private fun setupCastListener() {
    mSessionManagerListener = object : SessionManagerListener<CastSession> {
        override fun onSessionEnded(session: CastSession, error: Int) {
            onApplicationDisconnected()
        }

        override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) {
            onApplicationConnected(session)
        }

        override fun onSessionResumeFailed(session: CastSession, error: Int) {
            onApplicationDisconnected()
        }

        override fun onSessionStarted(session: CastSession, sessionId: String) {
            onApplicationConnected(session)
        }

        override fun onSessionStartFailed(session: CastSession, error: Int) {
            onApplicationDisconnected()
        }

        override fun onSessionStarting(session: CastSession) {}
        override fun onSessionEnding(session: CastSession) {}
        override fun onSessionResuming(session: CastSession, sessionId: String) {}
        override fun onSessionSuspended(session: CastSession, reason: Int) {}
        private fun onApplicationConnected(castSession: CastSession) {
            mCastSession = castSession
            if (null != mSelectedMedia) {
                if (mPlaybackState == PlaybackState.PLAYING) {
                    mVideoView!!.pause()
                    loadRemoteMedia(mSeekbar!!.progress, true)
                    return
                } else {
                    mPlaybackState = PlaybackState.IDLE
                    updatePlaybackLocation(PlaybackLocation.REMOTE)
                }
            }
            updatePlayButton(mPlaybackState)
            invalidateOptionsMenu()
        }

        private fun onApplicationDisconnected() {
            updatePlaybackLocation(PlaybackLocation.LOCAL)
            mPlaybackState = PlaybackState.IDLE
            mLocation = PlaybackLocation.LOCAL
            updatePlayButton(mPlaybackState)
            invalidateOptionsMenu()
       }
   }
}

Ce qui nous intéresse dans l'activité LocalPlayerActivity, c'est d'être informés en cas de connexion ou de déconnexion de l'appareil Cast afin de pouvoir basculer vers le lecteur local si nécessaire, et inversement. Notez que la connectivité peut être interrompue par l'instance de votre application s'exécutant sur votre appareil mobile, mais également par une autre instance de votre application (ou d'une autre application) exécutée sur un autre appareil mobile.

La session actuellement active est accessible en tant que SessionManager.getCurrentSession(). Les sessions sont créées et détruites automatiquement en réponse aux interactions de l'utilisateur avec les boîtes de dialogue Cast.

Nous allons maintenant enregistrer notre écouteur de session et initialiser certaines variables que nous utiliserons dans l'activité. Remplacez la méthode onCreate de LocalPlayerActivity par :

import com.google.android.gms.cast.framework.CastContext
...

private var mCastContext: CastContext? = null
...

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    mCastContext = CastContext.getSharedInstance(this)
    mCastSession = mCastContext!!.sessionManager.currentCastSession
    setupCastListener()
    ...
    loadViews()
    ...
    val bundle = intent.extras
    if (bundle != null) {
        ....
        if (shouldStartPlayback) {
              ....

        } else {
            if (mCastSession != null && mCastSession!!.isConnected()) {
                updatePlaybackLocation(PlaybackLocation.REMOTE)
            } else {
                updatePlaybackLocation(PlaybackLocation.LOCAL)
            }
            mPlaybackState = PlaybackState.IDLE
            updatePlayButton(mPlaybackState)
        }
    }
    ...
}

Chargement de contenu multimédia

Dans le SDK Cast, RemoteMediaClient fournit un ensemble d'API pratiques pour gérer la lecture de contenus multimédias à distance sur le récepteur. Pour toute CastSession compatible avec la lecture de contenus multimédias, une instance de RemoteMediaClient sera créée automatiquement par le SDK. Vous pouvez y accéder en appelant la méthode getRemoteMediaClient() dans l'instance CastSession. Ajoutez les méthodes suivantes à LocalPlayerActivity pour charger la vidéo actuellement sélectionnée sur le récepteur :

import com.google.android.gms.cast.framework.media.RemoteMediaClient
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaLoadOptions
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.common.images.WebImage
import com.google.android.gms.cast.MediaLoadRequestData

private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
    if (mCastSession == null) {
        return
    }
    val remoteMediaClient = mCastSession!!.remoteMediaClient ?: return
    remoteMediaClient.load( MediaLoadRequestData.Builder()
                .setMediaInfo(buildMediaInfo())
                .setAutoplay(autoPlay)
                .setCurrentTime(position.toLong()).build())
}

private fun buildMediaInfo(): MediaInfo? {
    val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
    mSelectedMedia?.studio?.let { movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, it) }
    mSelectedMedia?.title?.let { movieMetadata.putString(MediaMetadata.KEY_TITLE, it) }
    movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia!!.getImage(0))))
    movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia!!.getImage(1))))
    return mSelectedMedia!!.url?.let {
        MediaInfo.Builder(it)
            .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
            .setContentType("videos/mp4")
            .setMetadata(movieMetadata)
            .setStreamDuration((mSelectedMedia!!.duration * 1000).toLong())
            .build()
    }
}

À présent, mettez à jour les méthodes existantes ci-dessous pour que la logique de la session Cast permette la lecture à distance :

private fun play(position: Int) {
    startControllersTimer()
    when (mLocation) {
        PlaybackLocation.LOCAL -> {
            mVideoView!!.seekTo(position)
            mVideoView!!.start()
        }
        PlaybackLocation.REMOTE -> {
            mPlaybackState = PlaybackState.BUFFERING
            updatePlayButton(mPlaybackState)
            //seek to a new position within the current media item's new position 
            //which is in milliseconds from the beginning of the stream
            mCastSession!!.remoteMediaClient?.seek(position.toLong())
        }
        else -> {}
    }
    restartTrickplayTimer()
}
private fun togglePlayback() {
    ...
    PlaybackState.IDLE -> when (mLocation) {
        ...
        PlaybackLocation.REMOTE -> {
            if (mCastSession != null && mCastSession!!.isConnected) {
                loadRemoteMedia(mSeekbar!!.progress, true)
            }
        }
        else -> {}
    }
    ...
}
override fun onPause() {
    ...
    mCastContext!!.sessionManager.removeSessionManagerListener(
                mSessionManagerListener!!, CastSession::class.java)
}
override fun onResume() {
    Log.d(TAG, "onResume() was called")
    mCastContext!!.sessionManager.addSessionManagerListener(
            mSessionManagerListener!!, CastSession::class.java)
    if (mCastSession != null && mCastSession!!.isConnected) {
        updatePlaybackLocation(PlaybackLocation.REMOTE)
    } else {
        updatePlaybackLocation(PlaybackLocation.LOCAL)
    }
    super.onResume()
}

Dans le cas de la méthode updatePlayButton, modifiez la valeur de la variable isConnected :

private fun updatePlayButton(state: PlaybackState?) {
    ...
    val isConnected = (mCastSession != null
                && (mCastSession!!.isConnected || mCastSession!!.isConnecting))
    ...
}

Cliquez sur le bouton Bouton &quot;Run&quot; (Exécuter) d&#39;Android Studio, un triangle vert pointant vers la droiteRun (Exécuter) pour exécuter l'application sur votre appareil mobile. Ensuite, connectez-vous à votre appareil Cast et lancez la lecture d'une vidéo. Votre session Cast devrait maintenant commencer sur le récepteur.

7. Mini-télécommande

La checklist de conception de Google Cast exige que toutes les applications Cast soient dotées d'une mini-télécommande qui s'affiche dès que l'utilisateur quitte la page de contenu actuelle. Cette mini-télécommande permet à l'utilisateur d'accéder instantanément à sa session Cast en cours et de disposer d'un rappel visible.

Illustration de la partie inférieure d&#39;un téléphone Android affichant le minilecteur dans l&#39;application Cast Videos

Le SDK Cast fournit une vue personnalisée, MiniControllerFragment, qui peut être ajoutée au fichier de mise en page des activités pour lesquelles vous souhaitez afficher la mini-télécommande.

Ajoutez la définition de fragment suivante en bas de res/layout/player_activity.xml et res/layout/video_browser.xml :

<fragment
    android:id="@+id/castMiniController"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment"/>

Cliquez sur le bouton Bouton &quot;Run&quot; (Exécuter) d&#39;Android Studio, un triangle vert pointant vers la droiteRun (Exécuter) pour exécuter l'application et caster une vidéo. Au démarrage de la lecture sur le récepteur, vous devriez maintenant voir s'afficher la mini-télécommande en bas de chaque activité. Vous pouvez contrôler la lecture à distance à l'aide de la mini-télécommande. Si vous passez de l'activité de navigation à l'activité de lecture en local, l'état de votre mini-télécommande restera synchronisé avec le statut du lecteur multimédia récepteur.

8. Notifications et écran de verrouillage

La checklist de conception de Google Cast exige que les applications "émetteur" implémentent des commandes multimédias via la barre de notifications et l'écran de verrouillage.

Illustration d&#39;un téléphone Android affichant les commandes multimédias dans la zone de notifications

Afin d'aider l'application "émetteur" à créer des commandes multimédias via les notifications et l'écran de verrouillage, le SDK Cast fournit un service de notification multimédia : MediaNotificationService. Ce service est automatiquement fusionné avec le fichier manifeste de votre application par Gradle.

Ainsi, lorsque l'émetteur caste un contenu, MediaNotificationService s'exécute en arrière-plan et affiche une notification qui contient une vignette d'image et des métadonnées sur l'élément en cours de diffusion, ainsi qu'un bouton de lecture/pause et un bouton pour arrêter la lecture.

Les commandes accessibles au niveau de la notification et de l'écran de verrouillage peuvent être activées avec CastOptions lors de l'initialisation de CastContext. Elles sont activées par défaut. Tant que la fonctionnalité de notification restera activée, la fonctionnalité de l'écran de verrouillage le sera également.

Modifiez CastOptionsProvider et changez l'implémentation de getCastOptions de façon à obtenir le code suivant :

import com.google.android.gms.cast.framework.media.CastMediaOptions
import com.google.android.gms.cast.framework.media.NotificationOptions

override fun getCastOptions(context: Context): CastOptions {
   val notificationOptions = NotificationOptions.Builder()
            .setTargetActivityClassName(VideoBrowserActivity::class.java.name)
            .build()
    val mediaOptions = CastMediaOptions.Builder()
            .setNotificationOptions(notificationOptions)
            .build()
   return CastOptions.Builder()
                .setReceiverApplicationId(context.getString(R.string.app_id))
                .setCastMediaOptions(mediaOptions)
                .build()
}

Cliquez sur le bouton Bouton &quot;Run&quot; (Exécuter) d&#39;Android Studio, un triangle vert pointant vers la droiteRun (Exécuter) pour exécuter l'application sur votre appareil mobile. Castez une vidéo et quittez l'exemple d'application. Une notification concernant la vidéo en cours de lecture sur le récepteur devrait maintenant apparaître. Verrouillez votre appareil mobile. L'écran de verrouillage devrait à présent afficher les commandes de lecture multimédia sur l'appareil Cast.

Illustration d&#39;un téléphone Android affichant les commandes multimédias sur l&#39;écran de verrouillage

9. Message d'annonce en superposition

La checklist de conception de Google Cast exige que l'application émettrice présente l'icône Cast aux utilisateurs existants pour les prévenir que cette nouvelle fonctionnalité est disponible et aider ceux qui ne connaissent pas encore Google Cast.

Illustration montrant la superposition d&#39;introduction de Cast autour du bouton Cast dans l&#39;application Android Cast Videos

Le SDK Cast fournit une vue personnalisée, IntroductoryOverlay, qui permet de mettre en surbrillance l'icône Cast lorsqu'elle s'affiche pour la première fois auprès des utilisateurs. Ajoutez le code suivant à VideoBrowserActivity :

import com.google.android.gms.cast.framework.IntroductoryOverlay
import android.os.Looper

private var mIntroductoryOverlay: IntroductoryOverlay? = null

private fun showIntroductoryOverlay() {
    mIntroductoryOverlay?.remove()
    if (mediaRouteMenuItem?.isVisible == true) {
       Looper.myLooper().run {
           mIntroductoryOverlay = com.google.android.gms.cast.framework.IntroductoryOverlay.Builder(
                    this@VideoBrowserActivity, mediaRouteMenuItem!!)
                   .setTitleText("Introducing Cast")
                   .setSingleTime()
                   .setOnOverlayDismissedListener(
                           object : IntroductoryOverlay.OnOverlayDismissedListener {
                               override fun onOverlayDismissed() {
                                   mIntroductoryOverlay = null
                               }
                          })
                   .build()
          mIntroductoryOverlay!!.show()
        }
    }
}

Ajoutez maintenant un écouteur CastStateListener et appelez la méthode showIntroductoryOverlay lorsqu'un appareil Cast est disponible en modifiant la méthode onCreate. Remplacez ensuite les méthodes onResume et onPause pour qu'elles correspondent à ce qui suit:

import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.cast.framework.CastStateListener

private var mCastStateListener: CastStateListener? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.video_browser)
    setupActionBar()
    mCastStateListener = object : CastStateListener {
            override fun onCastStateChanged(newState: Int) {
                if (newState != CastState.NO_DEVICES_AVAILABLE) {
                    showIntroductoryOverlay()
                }
            }
        }
    mCastContext = CastContext.getSharedInstance(this)
}

override fun onResume() {
    super.onResume()
    mCastContext?.addCastStateListener(mCastStateListener!!)
}

override fun onPause() {
    super.onPause()
    mCastContext?.removeCastStateListener(mCastStateListener!!)
}

Effacez les données de l'application ou supprimez-la de votre appareil. Cliquez ensuite sur le bouton Bouton &quot;Run&quot; (Exécuter) d&#39;Android Studio (triangle vert pointant vers la droite)Run (Exécuter) pour exécuter l'application sur votre appareil mobile. La superposition d'introduction doit s'afficher. Si la superposition ne s'affiche pas, effacez les données de l'application.

10. Télécommande agrandie

La checklist de conception de Google Cast exige que l'application émettrice soit dotée d'une télécommande agrandie associée au contenu multimédia en cours de diffusion. La télécommande agrandie est une version en plein écran de la mini-télécommande.

Illustration d&#39;une vidéo en cours de lecture sur un téléphone Android avec la télécommande étendue en superposition

Le SDK Cast fournit un widget pour la télécommande agrandie appelé ExpandedControllerActivity. Il s'agit d'une classe abstraite que vous devez sous-classer pour ajouter une icône Cast.

Commencez par créer un fichier de ressources de menu appelé expanded_controller.xml afin d'intégrer l'icône Cast à la télécommande agrandie :

<?xml version="1.0" encoding="utf-8"?>

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
            android:id="@+id/media_route_menu_item"
            android:title="@string/media_route_menu_title"
            app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
            app:showAsAction="always"/>

</menu>

Créez un package expandedcontrols dans le package com.google.sample.cast.refplayer. Ensuite, créez un fichier nommé ExpandedControlsActivity.kt dans le package com.google.sample.cast.refplayer.expandedcontrols.

package com.google.sample.cast.refplayer.expandedcontrols

import android.view.Menu
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
import com.google.sample.cast.refplayer.R
import com.google.android.gms.cast.framework.CastButtonFactory

class ExpandedControlsActivity : ExpandedControllerActivity() {
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)
        menuInflater.inflate(R.menu.expanded_controller, menu)
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
        return true
    }
}

Déclarez ensuite l'ExpandedControlsActivity dans le AndroidManifest.xml au sein de la balise application au-dessus de OPTIONS_PROVIDER_CLASS_NAME:

<application>
    ...
    <activity
        android:name="com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:theme="@style/Theme.CastVideosDark"
        android:screenOrientation="portrait"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
        </intent-filter>
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.google.sample.cast.refplayer.VideoBrowserActivity"/>
    </activity>
    ...
</application>

Modifiez CastOptionsProvider, puis changez NotificationOptions et CastMediaOptions de façon à définir l'activité cible sur ExpandedControlsActivity :

import com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity

override fun getCastOptions(context: Context): CastOptions {
    val notificationOptions = NotificationOptions.Builder()
            .setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
            .build()
    val mediaOptions = CastMediaOptions.Builder()
            .setNotificationOptions(notificationOptions)
            .setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
            .build()
    return CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setCastMediaOptions(mediaOptions)
            .build()
}

Mettez à jour la méthode loadRemoteMedia de LocalPlayerActivity afin d'afficher ExpandedControlsActivity lorsque le chargement du contenu multimédia à distance est terminé :

import com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity

private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
    if (mCastSession == null) {
        return
    }
    val remoteMediaClient = mCastSession!!.remoteMediaClient ?: return
    remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() {
        override fun onStatusUpdated() {
            val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java)
            startActivity(intent)
            remoteMediaClient.unregisterCallback(this)
        }
    })
    remoteMediaClient.load(MediaLoadRequestData.Builder()
                .setMediaInfo(buildMediaInfo())
                .setAutoplay(autoPlay)
                .setCurrentTime(position.toLong()).build())
}

Cliquez sur le bouton Bouton &quot;Run&quot; (Exécuter) d&#39;Android Studio (triangle vert pointant vers la droite)Run (Exécuter) pour exécuter l'application sur votre appareil mobile et caster une vidéo. Vous devriez maintenant voir s'afficher la télécommande agrandie. Revenez à la liste des vidéos, puis cliquez sur la mini-télécommande pour l'agrandir et l'afficher à nouveau en plein écran. Quittez l'application pour voir la notification. Cliquez sur l'image de la notification pour charger la télécommande agrandie.

11. Ajouter la compatibilité avec Cast Connect

La bibliothèque Cast Connect permet aux applications émettrices existantes de communiquer avec les applications Android TV via le protocole Cast. Cast Connect s'appuie sur l'infrastructure Cast, et votre application Android TV joue le rôle de récepteur.

Dépendances

Remarque: Pour implémenter Cast Connect, play-services-cast-framework doit être 19.0.0 ou une version ultérieure.

LaunchOptions

Pour lancer l'application Android TV, également appelée Android Receiver, nous devons définir l'indicateur setAndroidReceiverCompatible sur "true" dans l'objet LaunchOptions. Cet objet LaunchOptions détermine comment le récepteur est lancé et est transmis à l'CastOptions renvoyé par la classe CastOptionsProvider. Si vous définissez l'indicateur mentionné ci-dessus sur false, le récepteur Web pour l'ID d'application défini dans la console développeur Cast sera lancé.

Dans le fichier CastOptionsProvider.kt, ajoutez ce qui suit à la méthode getCastOptions:

import com.google.android.gms.cast.LaunchOptions
...
val launchOptions = LaunchOptions.Builder()
            .setAndroidReceiverCompatible(true)
            .build()
return new CastOptions.Builder()
        .setLaunchOptions(launchOptions)
        ...
        .build()

Définir les identifiants de lancement

Du côté de l'expéditeur, vous pouvez spécifier CredentialsData pour représenter la personne qui rejoint la session. credentials est une chaîne pouvant être définie par l'utilisateur, à condition que votre application ATV puisse la comprendre. Le CredentialsData n'est transmis à votre application Android TV que pendant le lancement ou l'heure de connexion. Si vous le définissez à nouveau lorsque vous êtes connecté, il ne sera pas transmis à votre application Android TV.

Pour définir les identifiants de lancement, CredentialsData doit être défini et transmis à l'objet LaunchOptions. Ajoutez le code suivant à la méthode getCastOptions dans votre fichier CastOptionsProvider.kt:

import com.google.android.gms.cast.CredentialsData
...

val credentialsData = CredentialsData.Builder()
        .setCredentials("{\"userId\": \"abc\"}")
        .build()
val launchOptions = LaunchOptions.Builder()
       ...
       .setCredentialsData(credentialsData)
       .build()

Définir des identifiants sur LoadRequest

Si votre application Web Receiver et votre application Android TV gèrent credentials différemment, vous devrez peut-être définir des credentials distincts pour chacune. Pour ce faire, ajoutez le code suivant dans votre fichier LocalPlayerActivity.kt sous la fonction loadRemoteMedia:

remoteMediaClient.load(MediaLoadRequestData.Builder()
       ...
       .setCredentials("user-credentials")
       .setAtvCredentials("atv-user-credentials")
       .build())

En fonction de l'application réceptrice sur laquelle l'émetteur diffuse du contenu, le SDK gère désormais automatiquement les identifiants à utiliser pour la session en cours.

Test de Cast Connect

Procédure d'installation de l'APK Android TV sur Chromecast avec Google TV

  1. Recherchez l'adresse IP de votre appareil Android TV. Elle est généralement disponible sous Paramètres > Réseau et Internet > (Nom du réseau auquel votre appareil est connecté). À droite, vous trouverez les informations et l'adresse IP de votre appareil sur le réseau.
  2. Utilisez l'adresse IP de votre appareil pour vous y connecter via ADB à l'aide du terminal:
$ adb connect <device_ip_address>:5555
  1. Dans la fenêtre de votre terminal, accédez au dossier de premier niveau contenant les exemples de l'atelier de programmation que vous avez téléchargés au début de cet atelier de programmation. Exemple :
$ cd Desktop/android_codelab_src
  1. Installez le fichier .apk de ce dossier sur votre Android TV en exécutant la commande suivante:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. Vous devriez à présent voir une application nommée Caster des vidéos dans le menu Vos applications de votre appareil Android TV.
  2. Revenez à votre projet Android Studio, puis cliquez sur le bouton "Run" (Exécuter) pour installer et exécuter l'application d'envoi sur votre appareil mobile physique. En haut à droite, cliquez sur l'icône Cast et sélectionnez votre appareil Android TV parmi les options disponibles. L'application Android TV devrait maintenant s'ouvrir sur votre appareil Android TV. Si vous lancez une vidéo, vous devriez pouvoir la contrôler à l'aide de votre télécommande Android TV.

12. Personnaliser les widgets Cast

Vous pouvez personnaliser les widgets Cast en définissant les couleurs, le type et le style des boutons à afficher et du texte, ainsi que l'apparence des vignettes.

Mettre à jour res/values/styles_castvideo.xml

<style name="Theme.CastVideosTheme" parent="Theme.AppCompat.Light.NoActionBar">
    ...
    <item name="mediaRouteTheme">@style/CustomMediaRouterTheme</item>
    <item name="castIntroOverlayStyle">@style/CustomCastIntroOverlay</item>
    <item name="castMiniControllerStyle">@style/CustomCastMiniController</item>
    <item name="castExpandedControllerStyle">@style/CustomCastExpandedController</item>
    <item name="castExpandedControllerToolbarStyle">
        @style/ThemeOverlay.AppCompat.ActionBar
    </item>
    ...
</style>

Déclarez les thèmes personnalisés suivants :

<!-- Customize Cast Button -->
<style name="CustomMediaRouterTheme" parent="Theme.MediaRouter">
    <item name="mediaRouteButtonStyle">@style/CustomMediaRouteButtonStyle</item>
</style>
<style name="CustomMediaRouteButtonStyle" parent="Widget.MediaRouter.Light.MediaRouteButton">
    <item name="mediaRouteButtonTint">#EEFF41</item>
</style>

<!-- Customize Introductory Overlay -->
<style name="CustomCastIntroOverlay" parent="CastIntroOverlay">
    <item name="castButtonTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Button</item>
    <item name="castTitleTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Title</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Button" parent="android:style/TextAppearance">
    <item name="android:textColor">#FFFFFF</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Title" parent="android:style/TextAppearance.Large">
    <item name="android:textColor">#FFFFFF</item>
</style>

<!-- Customize Mini Controller -->
<style name="CustomCastMiniController" parent="CastMiniController">
    <item name="castShowImageThumbnail">true</item>
    <item name="castTitleTextAppearance">@style/TextAppearance.AppCompat.Subhead</item>
    <item name="castSubtitleTextAppearance">@style/TextAppearance.AppCompat.Caption</item>
    <item name="castBackground">@color/accent</item>
    <item name="castProgressBarColor">@color/orange</item>
</style>

<!-- Customize Expanded Controller -->
<style name="CustomCastExpandedController" parent="CastExpandedController">
    <item name="castButtonColor">#FFFFFF</item>
    <item name="castPlayButtonDrawable">@drawable/cast_ic_expanded_controller_play</item>
    <item name="castPauseButtonDrawable">@drawable/cast_ic_expanded_controller_pause</item>
    <item name="castStopButtonDrawable">@drawable/cast_ic_expanded_controller_stop</item>
</style>

13. Félicitations

Vous savez désormais comment rendre une application vidéo compatible avec Cast à l'aide des widgets du SDK Cast sur Android.

Pour en savoir plus, consultez le guide du développeur Android Sender.