Rendre une application iOS compatible avec Cast

1. Présentation

Logo Google Cast

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

Qu'est-ce que Google Cast ?

Google Cast permet aux utilisateurs de caster des contenus depuis un appareil mobile sur un téléviseur. Les utilisateurs peuvent ainsi utiliser leur appareil mobile comme télécommande pour la lecture de contenus multimédias sur leur téléviseur.

Le SDK Google Cast vous permet d'étendre votre application aux appareils compatibles Google Cast (comme 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 iOS 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 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

Ce dont vous avez besoin

  • Le dernier Xcode
  • Un appareil mobile équipé d'iOS 9 ou version ultérieure (ou le simulateur Xcode)
  • Un câble de données USB pour connecter votre appareil mobile à votre ordinateur de développement (si vous utilisez un appareil)
  • 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 cette option est facultative pour le reste de l'atelier de programmation. Si vous n'en avez pas, n'hésitez pas à passer 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 sur iOS.
  • 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 expérience de création d'applications iOS ?

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 l'ensemble de l'exemple de code sur votre ordinateur...

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

3. Exécuter l'application exemple

Logo Apple iOS

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 Xcode:

Questions fréquentes

Configuration de CocoaPods

Pour configurer CocoaPods, accédez à votre console et installez-le à l'aide de l'environnement Ruby par défaut disponible sur macOS:

sudo gem install cocoapods

En cas de problème, consultez la documentation officielle pour télécharger et installer le gestionnaire de dépendances.

Configuration du projet

  1. Accédez à votre terminal et au répertoire de l'atelier de programmation.
  2. Installez les dépendances à partir du Podfile.
cd app-done
pod update
pod install
  1. Ouvrez Xcode, puis sélectionnez Open another project (Ouvrir un autre projet).
  2. Sélectionnez le fichier CastVideos-ios.xcworkspace dans le répertoire Icône Dossierapp-done du dossier de l'exemple de code.

Exécuter l'application

Sélectionnez la cible et le simulateur, puis exécutez l'application:

Barre d'outils du simulateur d'application XCode

L'application vidéo devrait s'afficher au bout de quelques secondes.

N'oubliez pas de cliquer sur "Autoriser" lorsque la notification vous invitant à accepter les connexions réseau entrantes s'affiche. Si cette option n'est pas acceptée, l'icône Cast ne s'affiche pas.

Boîte de dialogue de confirmation demandant l'autorisation d'accepter les connexions réseau entrantes

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

Sélectionnez une vidéo et 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 iPhone exécutant l'application CastVideos, avec la mini-télécommande en bas

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 l'icône Cast pour arrêter la diffusion sur l'appareil Google Cast.

4. Préparer le projet de départ

Illustration d'un iPhone exécutant l'application CastVideos

Nous objectif est de rendre l'application de départ que vous avez téléchargée compatible avec Google Cast. Voici quelques termes liés à Google Cast que nous utiliserons dans cet atelier de programmation:

  • 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.

Configuration du projet

Vous êtes maintenant prêt à développer le projet de démarrage à l'aide de Xcode:

  1. Accédez à votre terminal et au répertoire de l'atelier de programmation.
  2. Installez les dépendances à partir du Podfile.
cd app-start
pod update
pod install
  1. Ouvrez Xcode, puis sélectionnez Open another project (Ouvrir un autre projet).
  2. Sélectionnez le fichier CastVideos-ios.xcworkspace dans le répertoire Icône Dossierapp-start du dossier de l'exemple de code.

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 se compose de deux contrôleurs de vue principaux: MediaTableViewController et MediaViewController..

MediaTableViewController

Cet UITableViewController affiche une liste de vidéos provenant d'une instance MediaListModel. 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. MediaListModel récupère ce fichier JSON et le traite pour créer une liste d'objets MediaItem.

Un objet MediaItem modélise une vidéo et ses métadonnées associées, telles que le titre, la description, l'URL d'une image et l'URL du flux.

MediaTableViewController crée une instance MediaListModel, puis s'enregistre en tant que MediaListModelDelegate pour être informé lorsque les métadonnées multimédias ont été téléchargées afin de pouvoir charger la vue Tableau.

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é, l'élément MediaItem correspondant est transmis à MediaViewController.

MediaViewController

Ce contrôleur de vue affiche les métadonnées d'une vidéo spécifique et permet à l'utilisateur de lire la vidéo localement sur l'appareil mobile.

Le contrôleur de vue héberge un LocalPlayerView, des commandes multimédias et une zone de texte permettant d'afficher la description de la vidéo sélectionnée. Le lecteur occupe la partie supérieure de l'écran, ce qui laisse de la place à la description détaillée de la vidéo sous la vidéo. L'utilisateur peut lire/mettre en pause la vidéo ou rechercher sa lecture en local.

Questions fréquentes

5. Ajouter l'icône Cast

Illustration du tiers supérieur d'un iPhone exécutant l'application CastVideos, avec l'icône Cast en haut à droite

Une application compatible Cast affiche l'icône Cast dans chacun de ses contrôleurs de vue. 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 quel écran de votre application, comme indiqué dans la checklist de conception Google Cast.

Configuration

Le projet de départ nécessite les mêmes dépendances et la même configuration Xcode que pour l'application exemple terminée. Revenez à cette section et suivez la même procédure pour ajouter GoogleCast.framework au projet de démarrage de l'application.

Initialisation

Le framework Cast dispose d'un objet singleton global, GCKCastContext, qui coordonne toutes les activités du framework. Cet objet doit être initialisé tôt dans le cycle de vie de l'application, généralement dans la méthode application(_:didFinishLaunchingWithOptions:) du délégué de l'application, afin que la reprise de session automatique au redémarrage de l'application émettrice puisse se déclencher correctement et que la recherche d'appareils puisse démarrer.

Un objet GCKCastOptions doit être fourni lors de l'initialisation de GCKCastContext. Cette classe contient des options qui affectent le comportement du framework. La plus importante d'entre elles est l'ID de l'application du récepteur, qui permet de filtrer les résultats de détection des appareils Cast et de lancer l'application réceptrice lorsqu'une session Cast est lancée.

La méthode application(_:didFinishLaunchingWithOptions:) permet également de configurer un délégué de journalisation afin de recevoir les messages de journalisation du framework Cast. Ils peuvent être utiles pour le débogage et le dépannage.

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 code suivant à AppDelegate.swift pour initialiser GCKCastContext avec l'ID application par défaut de l'utilisateur, puis ajoutez un enregistreur pour le framework Google Cast:

import GoogleCast

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  fileprivate var enableSDKLogging = true

  ...

  func application(_: UIApplication,
                   didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    ...
    let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID))
    options.physicalVolumeButtonsWillControlDeviceVolume = true
    GCKCastContext.setSharedInstanceWith(options)

    window?.clipsToBounds = true
    setupCastLogging()
    ...
  }
  ...
  func setupCastLogging() {
    let logFilter = GCKLoggerFilter()
    let classesToLog = ["GCKDeviceScanner", "GCKDeviceProvider", "GCKDiscoveryManager", "GCKCastChannel",
                        "GCKMediaControlChannel", "GCKUICastButton", "GCKUIMediaController", "NSMutableDictionary"]
    logFilter.setLoggingLevel(.verbose, forClasses: classesToLog)
    GCKLogger.sharedInstance().filter = logFilter
    GCKLogger.sharedInstance().delegate = self
  }
}

...

// MARK: - GCKLoggerDelegate

extension AppDelegate: GCKLoggerDelegate {
  func logMessage(_ message: String,
                  at _: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if enableSDKLogging {
      // Send SDK's log messages directly to the console.
      print("\(location): \(function) - \(message)")
    }
  }
}

Icône Cast

Après avoir initialisé GCKCastContext, nous devons ajouter l'icône Cast pour permettre à l'utilisateur de choisir son appareil Cast. Le SDK Cast fournit un composant pour l'icône Cast appelé GCKUICastButton en tant que sous-classe UIButton. Vous pouvez l'ajouter à la barre de titre de l'application en l'encapsulant dans une UIBarButtonItem. Nous devons ajouter l'icône Cast à MediaTableViewController et à MediaViewController.

Ajoutez le code suivant à MediaTableViewController.swift et MediaViewController.swift:

import GoogleCast

@objc(MediaTableViewController)
class MediaTableViewController: UITableViewController, GCKSessionManagerListener,
  MediaListModelDelegate, GCKRequestDelegate {
  private var castButton: GCKUICastButton!
  ...
  override func viewDidLoad() {
    print("MediaTableViewController - viewDidLoad")
    super.viewDidLoad()

    ...
    castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
                                               width: CGFloat(24), height: CGFloat(24)))
    // Overwrite the UIAppearance theme in the AppDelegate.
    castButton.tintColor = UIColor.white
    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)

    ...
  }
  ...
}

Ensuite, ajoutez le code suivant à votre fichier MediaViewController.swift:

import GoogleCast

@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener, GCKRemoteMediaClientListener,
  LocalPlayerViewDelegate, GCKRequestDelegate {
  private var castButton: GCKUICastButton!
  ...
  override func viewDidLoad() {
    super.viewDidLoad()
    print("in MediaViewController viewDidLoad")
    ...
    castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
                                               width: CGFloat(24), height: CGFloat(24)))
    // Overwrite the UIAppearance theme in the AppDelegate.
    castButton.tintColor = UIColor.white
    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)

    ...
  }
  ...
}

Exécutez maintenant l'application. Vous devriez voir une icône Cast dans la barre de navigation de l'application. Lorsque vous cliquez dessus, la liste des appareils Cast de votre réseau local s'affiche. La détection d'appareils est gérée automatiquement par GCKCastContext. 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 arrêter la diffusion.

6. Diffuser du contenu vidéo

Illustration d'un iPhone exécutant l'application CastVideos, qui affiche des détails sur une vidéo en particulier ("Tears of Steel"). Le lecteur réduit en bas de l'écran

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

De manière générale, si vous souhaitez lire un contenu multimédia sur un appareil Cast, procédez comme suit:

  1. Créez un objet GCKMediaInformation à partir du SDK Cast qui modélise un élément multimédia.
  2. L'utilisateur se connecte à l'appareil Cast pour lancer votre application réceptrice.
  3. Chargez l'objet GCKMediaInformation 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.

L'étape 1 consiste à mapper un objet à un autre. GCKMediaInformation est un élément que le SDK Cast comprend, et MediaItem est l'encapsulation de notre application pour un élément multimédia. Nous pouvons facilement mapper un MediaItem à un GCKMediaInformation. Nous avons déjà effectué l'étape 2 de la section précédente. L'étape 3 est simple à effectuer avec le SDK Cast.

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

enum PlaybackMode: Int {
  case none = 0
  case local
  case remote
}

private var playbackMode = PlaybackMode.none

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 la diffusion lorsque nous nous trouvons dans ce contrôleur de vue, nous devons passer à la lecture en local. 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 réceptrice en raison de problèmes de mise en réseau est également gérée automatiquement par le framework Cast.

Les sessions de diffusion sont gérées par le GCKSessionManager, accessible via GCKCastContext.sharedInstance().sessionManager. Les rappels GCKSessionManagerListener peuvent être utilisés pour surveiller les événements de session, tels que la création, la suspension, la reprise et l'arrêt.

Nous devons d'abord enregistrer notre écouteur de session et initialiser certaines variables:

class MediaViewController: UIViewController, GCKSessionManagerListener,
  GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {

  ...
  private var sessionManager: GCKSessionManager!
  ...

  required init?(coder: NSCoder) {
    super.init(coder: coder)

    sessionManager = GCKCastContext.sharedInstance().sessionManager

    ...
  }

  override func viewWillAppear(_ animated: Bool) {
    ...

    let hasConnectedSession: Bool = (sessionManager.hasConnectedSession())
    if hasConnectedSession, (playbackMode != .remote) {
      populateMediaInfo(false, playPosition: 0)
      switchToRemotePlayback()
    } else if sessionManager.currentSession == nil, (playbackMode != .local) {
      switchToLocalPlayback()
    }

    sessionManager.add(self)

    ...
  }

  override func viewWillDisappear(_ animated: Bool) {
    ...

    sessionManager.remove(self)
    sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
    ...
    super.viewWillDisappear(animated)
  }

  func switchToLocalPlayback() {
    ...

    sessionManager.currentCastSession?.remoteMediaClient?.remove(self)

    ...
  }

  func switchToRemotePlayback() {
    ...

    sessionManager.currentCastSession?.remoteMediaClient?.add(self)

    ...
  }


  // MARK: - GCKSessionManagerListener

  func sessionManager(_: GCKSessionManager, didStart session: GCKSession) {
    print("MediaViewController: sessionManager didStartSession \(session)")
    setQueueButtonVisible(true)
    switchToRemotePlayback()
  }

  func sessionManager(_: GCKSessionManager, didResumeSession session: GCKSession) {
    print("MediaViewController: sessionManager didResumeSession \(session)")
    setQueueButtonVisible(true)
    switchToRemotePlayback()
  }

  func sessionManager(_: GCKSessionManager, didEnd _: GCKSession, withError error: Error?) {
    print("session ended with error: \(String(describing: error))")
    let message = "The Casting session has ended.\n\(String(describing: error))"
    if let window = appDelegate?.window {
      Toast.displayMessage(message, for: 3, in: window)
    }
    setQueueButtonVisible(false)
    switchToLocalPlayback()
  }

  func sessionManager(_: GCKSessionManager, didFailToStartSessionWithError error: Error?) {
    if let error = error {
      showAlert(withTitle: "Failed to start a session", message: error.localizedDescription)
    }
    setQueueButtonVisible(false)
  }

  func sessionManager(_: GCKSessionManager,
                      didFailToResumeSession _: GCKSession, withError _: Error?) {
    if let window = UIApplication.shared.delegate?.window {
      Toast.displayMessage("The Casting session could not be resumed.",
                           for: 3, in: window)
    }
    setQueueButtonVisible(false)
    switchToLocalPlayback()
  }

  ...
}

Dans MediaViewController, nous souhaitons être informés lorsque nous sommes connectés ou déconnectés de l'appareil Cast afin de pouvoir basculer vers ou depuis le lecteur local. 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 GCKCastContext.sharedInstance().sessionManager.currentCastSession. Les sessions sont créées et détruites automatiquement en réponse aux gestes de l'utilisateur dans les boîtes de dialogue Cast.

Chargement de contenu multimédia

Dans le SDK Cast, GCKRemoteMediaClient fournit un ensemble d'API pratiques pour gérer la lecture de contenus multimédias à distance sur le récepteur. Pour toute GCKCastSession compatible avec la lecture de contenus multimédias, une instance de GCKRemoteMediaClient sera créée automatiquement par le SDK. Elle est accessible en tant que propriété remoteMediaClient de l'instance GCKCastSession.

Ajoutez le code suivant à MediaViewController.swift pour charger la vidéo actuellement sélectionnée sur le récepteur:

@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener,
  GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
  ...

  @objc func playSelectedItemRemotely() {
    loadSelectedItem(byAppending: false)
  }

  /**
   * Loads the currently selected item in the current cast media session.
   * @param appending If YES, the item is appended to the current queue if there
   * is one. If NO, or if
   * there is no queue, a new queue containing only the selected item is created.
   */
  func loadSelectedItem(byAppending appending: Bool) {
    print("enqueue item \(String(describing: mediaInfo))")
    if let remoteMediaClient = sessionManager.currentCastSession?.remoteMediaClient {
      let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
      mediaQueueItemBuilder.mediaInformation = mediaInfo
      mediaQueueItemBuilder.autoplay = true
      mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
      let mediaQueueItem = mediaQueueItemBuilder.build()
      if appending {
        let request = remoteMediaClient.queueInsert(mediaQueueItem, beforeItemWithID: kGCKMediaQueueInvalidItemID)
        request.delegate = self
      } else {
        let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
        queueDataBuilder.items = [mediaQueueItem]
        queueDataBuilder.repeatMode = remoteMediaClient.mediaStatus?.queueRepeatMode ?? .off

        let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
        mediaLoadRequestDataBuilder.mediaInformation = mediaInfo
        mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()

        let request = remoteMediaClient.loadMedia(with: mediaLoadRequestDataBuilder.build())
        request.delegate = self
      }
    }
  }
  ...
}

À présent, mettez à jour plusieurs méthodes existantes pour utiliser la logique de la session Cast afin de permettre la lecture à distance:

required init?(coder: NSCoder) {
  super.init(coder: coder)
  ...
  castMediaController = GCKUIMediaController()
  ...
}

func switchToLocalPlayback() {
  print("switchToLocalPlayback")
  if playbackMode == .local {
    return
  }
  setQueueButtonVisible(false)
  var playPosition: TimeInterval = 0
  var paused: Bool = false
  var ended: Bool = false
  if playbackMode == .remote {
    playPosition = castMediaController.lastKnownStreamPosition
    paused = (castMediaController.lastKnownPlayerState == .paused)
    ended = (castMediaController.lastKnownPlayerState == .idle)
    print("last player state: \(castMediaController.lastKnownPlayerState), ended: \(ended)")
  }
  populateMediaInfo((!paused && !ended), playPosition: playPosition)
  sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
  playbackMode = .local
}

func switchToRemotePlayback() {
  print("switchToRemotePlayback; mediaInfo is \(String(describing: mediaInfo))")
  if playbackMode == .remote {
    return
  }
  // If we were playing locally, load the local media on the remote player
  if playbackMode == .local, (_localPlayerView.playerState != .stopped), (mediaInfo != nil) {
    print("loading media: \(String(describing: mediaInfo))")
    let paused: Bool = (_localPlayerView.playerState == .paused)
    let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
    mediaQueueItemBuilder.mediaInformation = mediaInfo
    mediaQueueItemBuilder.autoplay = !paused
    mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
    mediaQueueItemBuilder.startTime = _localPlayerView.streamPosition ?? 0
    let mediaQueueItem = mediaQueueItemBuilder.build()

    let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
    queueDataBuilder.items = [mediaQueueItem]
    queueDataBuilder.repeatMode = .off

    let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
    mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()

    let request = sessionManager.currentCastSession?.remoteMediaClient?.loadMedia(with: mediaLoadRequestDataBuilder.build())
    request?.delegate = self
  }
  _localPlayerView.stop()
  _localPlayerView.showSplashScreen()
  setQueueButtonVisible(true)
  sessionManager.currentCastSession?.remoteMediaClient?.add(self)
  playbackMode = .remote
}

/* Play has been pressed in the LocalPlayerView. */
func continueAfterPlayButtonClicked() -> Bool {
  let hasConnectedCastSession = sessionManager.hasConnectedCastSession
  if mediaInfo != nil, hasConnectedCastSession() {
    // Display an alert box to allow the user to add to queue or play
    // immediately.
    if actionSheet == nil {
      actionSheet = ActionSheet(title: "Play Item", message: "Select an action", cancelButtonText: "Cancel")
      actionSheet?.addAction(withTitle: "Play Now", target: self,
                             selector: #selector(playSelectedItemRemotely))
    }
    actionSheet?.present(in: self, sourceView: _localPlayerView)
    return false
  }
  return true
}

Exécutez maintenant 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

Selon la checklist de conception Cast, toutes les applications Cast doivent afficher une mini-télécommande lorsque l'utilisateur quitte la page de contenu actuelle. Cette mini-télécommande permet à l'utilisateur d'avoir un accès instantané à sa session Cast en cours, et de disposer d'un rappel visible.

Illustration de la partie inférieure d'un iPhone exécutant l'application CastVideos, mettant l'accent sur la mini-télécommande

Le SDK Cast fournit une barre de commandes, GCKUIMiniMediaControlsViewController, qui peut être ajoutée aux scènes dans lesquelles vous souhaitez afficher les commandes persistantes.

Pour l'application exemple, nous allons utiliser GCKUICastContainerViewController, qui encapsule un autre contrôleur de vue et ajoute un GCKUIMiniMediaControlsViewController en bas.

Modifiez le fichier AppDelegate.swift et ajoutez le code suivant pour la condition if useCastContainerViewController dans la méthode suivante:

func application(_: UIApplication,
                 didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  ...
  let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
  guard let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
    as? UINavigationController else { return false }
  let castContainerVC = GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
    as GCKUICastContainerViewController
  castContainerVC.miniMediaControlsItemEnabled = true
  window = UIWindow(frame: UIScreen.main.bounds)
  window?.rootViewController = castContainerVC
  window?.makeKeyAndVisible()
  ...
}

Ajoutez cette propriété et un setter/getter pour contrôler la visibilité de la mini-télécommande (nous les utiliserons dans une section ultérieure):

var isCastControlBarsEnabled: Bool {
    get {
      if useCastContainerViewController {
        let castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
        return castContainerVC!.miniMediaControlsItemEnabled
      } else {
        let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
        return rootContainerVC!.miniMediaControlsViewEnabled
      }
    }
    set(notificationsEnabled) {
      if useCastContainerViewController {
        var castContainerVC: GCKUICastContainerViewController?
        castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
        castContainerVC?.miniMediaControlsItemEnabled = notificationsEnabled
      } else {
        var rootContainerVC: RootContainerViewController?
        rootContainerVC = (window?.rootViewController as? RootContainerViewController)
        rootContainerVC?.miniMediaControlsViewEnabled = notificationsEnabled
      }
    }
  }

Exécutez l'application et castez une vidéo. Lorsque la lecture démarre sur le récepteur, la mini-télécommande devrait s'afficher au bas de chaque scène. 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. 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 qu'elle est désormais compatible avec la fonctionnalité Caster et aide également les nouveaux utilisateurs de Google Cast.

Illustration d'un iPhone exécutant l'application CastVideos avec le bouton Cast en superposition, mettant en évidence l'icône Cast et affichant le message "Appuyez pour caster un contenu multimédia sur votre téléviseur et vos enceintes".

La classe GCKCastContext comporte une méthode, presentCastInstructionsViewControllerOnce, qui permet de mettre en surbrillance l'icône Cast lorsqu'elle est présentée aux utilisateurs pour la première fois. Ajoutez le code suivant à MediaViewController.swift et MediaTableViewController.swift:

override func viewDidLoad() {
  ...

  NotificationCenter.default.addObserver(self, selector: #selector(castDeviceDidChange),
                                         name: NSNotification.Name.gckCastStateDidChange,
                                         object: GCKCastContext.sharedInstance())
}

@objc func castDeviceDidChange(_: Notification) {
  if GCKCastContext.sharedInstance().castState != .noDevicesAvailable {
    // You can present the instructions on how to use Google Cast on
    // the first time the user uses you app
    GCKCastContext.sharedInstance().presentCastInstructionsViewControllerOnce(with: castButton)
  }
}

Exécutez l'application sur votre appareil mobile. La superposition de présentation devrait s'afficher.

9. 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 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'un iPhone exécutant l'application CastVideos lisant une vidéo dont la télécommande agrandie apparaît en bas

La télécommande agrandie est une vue plein écran qui permet de contrôler entièrement la lecture des contenus multimédias à distance. Cette vue devrait permettre à une application de diffusion de gérer tous les aspects gérables d'une session de diffusion, à l'exception du contrôle du volume du récepteur et du cycle de vie de la session (connexion/arrêt de la diffusion). Il fournit également toutes les informations d'état de la session multimédia (illustration, titre, sous-titre, etc.).

La fonctionnalité de cette vue est implémentée par la classe GCKUIExpandedMediaControlsViewController.

Commencez par activer la télécommande agrandie par défaut dans le contexte de diffusion. Modifiez AppDelegate.swift pour activer la télécommande agrandie par défaut:

import GoogleCast

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  ...

  func application(_: UIApplication,
                   didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    ...
    // Add after the setShareInstanceWith(options) is set.
    GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
    ...
  }
  ...
}

Ajoutez le code suivant à MediaViewController.swift pour charger la télécommande agrandie lorsque l'utilisateur commence à caster une vidéo:

@objc func playSelectedItemRemotely() {
  ...
  appDelegate?.isCastControlBarsEnabled = false
  GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()
}

La télécommande agrandie se lance automatiquement lorsque l'utilisateur appuie sur la mini-télécommande.

Exécutez l'application et castez 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.

10. 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 : votre application Android TV joue le rôle de récepteur.

Dépendances

Dans votre Podfile, assurez-vous que google-cast-sdk pointe vers 4.4.8 ou une version ultérieure, comme indiqué ci-dessous. Si vous avez apporté une modification au fichier, exécutez pod update à partir de la console pour synchroniser la modification avec votre projet.

pod 'google-cast-sdk', '>=4.4.8'

GCKLaunchOptions

Pour lancer l'application Android TV, également appelée Android Receiver, nous devons définir l'indicateur androidReceiverCompatible sur "true" dans l'objet GCKLaunchOptions. Cet objet GCKLaunchOptions détermine le mode de lancement du récepteur et est transmis aux GCKCastOptions, qui sont définis dans l'instance partagée à l'aide de GCKCastContext.setSharedInstanceWith.

Ajoutez les lignes suivantes à votre fichier AppDelegate.swift:

let options = GCKCastOptions(discoveryCriteria:
                          GCKDiscoveryCriteria(applicationID: kReceiverAppID))
...
/** Following code enables CastConnect */
let launchOptions = GCKLaunchOptions()
launchOptions.androidReceiverCompatible = true
options.launchOptions = launchOptions

GCKCastContext.setSharedInstanceWith(options)

Définir les identifiants de lancement

Du côté de l'expéditeur, vous pouvez spécifier GCKCredentialsData pour représenter qui rejoint la session. credentials est une chaîne qui peut être définie par l'utilisateur, à condition que votre application Android TV puisse la comprendre. Le GCKCredentialsData n'est transmis à votre application Android TV que lors du lancement ou de la connexion. Si vous la reconfigurez alors que vous êtes connecté, elle ne sera pas transmise à votre application Android TV.

Pour définir les identifiants de lancement, GCKCredentialsData doit être défini à tout moment après que les GCKLaunchOptions ont été définis. Pour illustrer cela, ajoutons une logique au bouton Creds afin de définir les identifiants à transmettre lorsque la session est établie. Ajoutez le code suivant à votre fichier MediaTableViewController.swift:

class MediaTableViewController: UITableViewController, GCKSessionManagerListener, MediaListModelDelegate, GCKRequestDelegate {
  ...
  private var credentials: String? = nil
  ...
  override func viewDidLoad() {
    ...
    navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Creds", style: .plain,
                                                       target: self, action: #selector(toggleLaunchCreds))
    ...
    setLaunchCreds()
  }
  ...
  @objc func toggleLaunchCreds(_: Any){
    if (credentials == nil) {
        credentials = "{\"userId\":\"id123\"}"
    } else {
        credentials = nil
    }
    Toast.displayMessage("Launch Credentials: "+(credentials ?? "Null"), for: 3, in: appDelegate?.window)
    print("Credentials set: "+(credentials ?? "Null"))
    setLaunchCreds()
  }
  ...
  func setLaunchCreds() {
    GCKCastContext.sharedInstance()
        .setLaunch(GCKCredentialsData(credentials: credentials))
  }
}

Définir des identifiants pour la requête de chargement

Pour gérer credentials sur les applications Web et Android TV du récepteur, ajoutez le code suivant dans votre classe MediaTableViewController.swift sous la fonction loadSelectedItem:

let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
...
mediaLoadRequestDataBuilder.credentials = credentials
...

En fonction de l'application réceptrice vers laquelle l'émetteur diffuse du contenu, le SDK applique automatiquement les identifiants ci-dessus à la session en cours.

Tester 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 se trouve généralement dans Paramètres > Réseau et Internet > (Nom du réseau auquel votre appareil est connecté). Les détails et l'adresse IP de votre appareil sur le réseau s'affichent à 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 celui-ci. Exemple :
$ cd Desktop/ios_codelab_src
  1. Installez le fichier .apk qui se trouve dans ce dossier sur votre Android TV en exécutant la commande suivante:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. Une application du nom de Caster des vidéos devrait maintenant s'afficher dans le menu Vos applications de votre appareil Android TV.
  2. Une fois que vous avez terminé, créez et exécutez l'application dans un émulateur ou sur un appareil mobile. Lorsque vous établissez une session de diffusion avec votre appareil Android TV, celui-ci devrait maintenant lancer l'application Android Receiver sur votre Android TV. La lecture d'une vidéo à partir de votre émetteur mobile iOS devrait lancer la vidéo dans Android Receiver et vous permettre de contrôler la lecture à l'aide de la télécommande de votre appareil Android TV.

11. Personnaliser les widgets Cast

Initialisation

Commencez par le dossier "App-Done". Ajoutez le code suivant à la méthode applicationDidFinishLaunchingWithOptions dans votre fichier AppDelegate.swift.

func application(_: UIApplication,
                 didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  ...
  let styler = GCKUIStyle.sharedInstance()
  ...
}

Une fois que vous avez terminé d'appliquer une ou plusieurs personnalisations, comme indiqué dans la suite de cet atelier de programmation, validez les styles en appelant le code ci-dessous.

styler.apply()

Personnaliser les vues Cast

Vous pouvez personnaliser toutes les vues gérées par le framework d'application Cast en appliquant des consignes de style par défaut à toutes les vues. À titre d’exemple, modifions la couleur de l’icône.

styler.castViews.iconTintColor = .lightGray

Vous pouvez remplacer les valeurs par défaut pour chaque écran si nécessaire. Par exemple, pour remplacer la couleur lightGrayColor de l'icône uniquement pour le contrôleur multimédia étendu.

styler.castViews.mediaControl.expandedController.iconTintColor = .green

Modification des couleurs

Vous pouvez personnaliser la couleur d'arrière-plan de toutes les vues (ou individuellement pour chaque vue). Le code suivant définit la couleur d'arrière-plan sur bleu de toutes les vues fournies par le framework d'application Cast.

styler.castViews.backgroundColor = .blue
styler.castViews.mediaControl.miniController.backgroundColor = .yellow

Modification des polices

Vous pouvez personnaliser les polices pour les différents libellés affichés dans les vues Cast. À titre d'exemple, définissons toutes les polices sur "Courier-Oblique".

styler.castViews.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 16) ?? UIFont.systemFont(ofSize: 16)
styler.castViews.mediaControl.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 6) ?? UIFont.systemFont(ofSize: 6)

Modifier les images de boutons par défaut

Ajoutez vos propres images personnalisées au projet et attribuez-les à vos boutons pour appliquer un style.

let muteOnImage = UIImage.init(named: "yourImage.png")
if let muteOnImage = muteOnImage {
  styler.castViews.muteOnImage = muteOnImage
}

Modifier le thème de l'icône Cast

Vous pouvez également personnaliser les widgets Cast à l'aide du protocole UIAppearance. Le code suivant crée un thème pour le GCKUICastButton sur toutes les vues où il apparaît:

GCKUICastButton.appearance().tintColor = UIColor.gray

12. Félicitations !

Vous savez maintenant comment rendre une application vidéo compatible Cast à l'aide des widgets du SDK Cast sur iOS.

Pour en savoir plus, consultez le guide du développeur sur les expéditeurs iOS.