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 votre application pour contrôler un téléviseur ou un système audio. Le SDK Cast vous permet d'ajouter les composants d'interface utilisateur nécessaires, 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 compatible 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

  • Le SDK Android le plus récent
  • 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 un Chromecast ou un 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 c'est facultatif pour le reste de l'atelier de programmation. Si vous n'en avez pas, vous pouvez ignorer l'étape Ajouter la compatibilité 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é, les instructions suivantes décrivent comment ouvrir et exécuter l'application exemple terminée dans Android Studio:

Sélectionnez Import Project (Importer un projet) sur l'écran d'accueil ou cliquez sur File > Nouveau > Options du menu "Import Project" (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 Fichier > Android Studio "Sync Project with Gradle" (Synchroniser le projet avec Gradle) bouton 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, puis cliquez sur le bouton Bouton "Run" (Exécuter) d'Android Studio (triangle vert pointant vers la droite)Run (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'option "Caster des vidéos" Application avec la mini-télécommande qui s'affiche 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'option "Caster des vidéos" application

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 Android Studio "Sync Project with Gradle" (Synchroniser le projet avec Gradle) bouton 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 intégrer la fonctionnalité Google Cast, les éléments Activity doivent hériter de AppCompatActivity ou de son parent, FragmentActivity. Cette limite existe, car nous devons ajouter MediaRouteButton (fourni dans la bibliothèque Support MediaRouter) en tant que MediaRouteActionProvider. Cela ne fonctionnera que si l'activité hérite des classes mentionnées ci-dessus. 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 repose sur un élément ArrayAdapter (VideoListAdapter). La liste des vidéos et les métadonnées 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 et 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. L'objet MediaItem est transmis entre les activités. MediaItem dispose donc de méthodes utilitaires pour le convertir en Bundle, et inversement.

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.

L'activité héberge un 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 L'icône Cast qui 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 comporte un objet singleton global, 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 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é (à l'aide d'un ActionBar ou d'un Toolbar), vous devez d'abord ajouter l'élément de menu correspondant à votre menu.

Modifiez le fichier res/menu/browse.xml et ajoutez l'élément MediaRouteActionProvider dans le menu avant celui 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"/>

Remplacez la méthode onCreateOptionsMenu() de VideoBrowserActivity en utilisant CastButtonFactory pour intégrer MediaRouteButton au 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 (triangle vert pointant vers la droite)Run (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;option &quot;Caster des vidéos&quot; application

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 dans la section précédente. L'étape 3 est facile à 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 (triangle vert pointant vers la droite)Run (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 Cast exige que toutes les applications Cast fournissent une mini-télécommande qui s'affiche lorsque l'utilisateur quitte la page de contenu actuelle. La mini-télécommande offre un accès immédiat et un rappel visible concernant la session Cast en cours.

Illustration d&#39;une partie inférieure d&#39;un téléphone Android montrant le lecteur réduit dans l&#39;application Cast Vidéos

Le SDK Cast fournit une vue personnalisée, MiniControllerFragment, qui peut être ajoutée au fichier de mise en page de l'application pour les activités dans 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 (triangle vert pointant vers la droite)Run (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 l'application émettrice implémente les commandes multimédias à partir d'une notification et de l'écran de verrouillage.

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

Le SDK Cast fournit un MediaNotificationService pour aider l'application émettrice à créer des commandes multimédias pour les notifications et l'écran de verrouillage. 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 (triangle vert pointant vers la droite)Run (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 des 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 informer que celle-ci est désormais compatible avec la diffusion et qu'elle aide également les nouveaux utilisateurs de Google Cast.

Illustration montrant la superposition Cast de présentation autour de l&#39;icône Cast de l&#39;application Vidéos Cast pour Android

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

À présent, ajoutez un CastStateListener et appelez la méthode showIntroductoryOverlay lorsqu'un appareil Cast est disponible en modifiant la méthode onCreate et en remplaçant 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 fournisse une télécommande agrandie pour le 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 une télécommande agrandie 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 maintenant ExpandedControlsActivity dans AndroidManifest.xml, dans 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, la play-services-cast-framework doit être 19.0.0 ou supé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 indique comment le récepteur est lancé et est transmis au CastOptions renvoyé par la classe CastOptionsProvider. Si vous définissez l'indicateur mentionné ci-dessus sur false, le récepteur Web est lancé pour l'ID d'application défini dans la console développeur Cast.

Dans le fichier CastOptionsProvider.kt, ajoutez le code suivant à 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

Côté expéditeur, vous pouvez spécifier CredentialsData pour représenter qui rejoint la session. Le credentials est une chaîne qui peut être définie par l'utilisateur, à condition que votre application Android TV puisse la comprendre. Le CredentialsData n'est transmis à votre application Android TV que pendant le lancement ou l'heure de connexion. Si vous le configurez à nouveau alors que 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 distinctes pour chacune d'elles. Pour résoudre ce problème, 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 du fichier 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é). Les détails et l'adresse IP de l'appareil connecté au réseau s'affichent sur la droite.
  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 émettrice sur votre appareil mobile physique. En haut à droite, cliquez sur l'icône Cast, puis sélectionnez votre appareil Android TV parmi les options disponibles. L'application Android TV devrait à présent être lancée sur votre appareil Android TV. La lecture d'une vidéo devrait vous permettre de contrôler la lecture de la vidéo à 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, en définissant le style des boutons, le texte et l'apparence des vignettes, et en choisissant les types de boutons à afficher.

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.