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 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 les 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

Prérequis

  • La dernière version de Xcode
  • Un appareil mobile fonctionnant sous 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 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 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 en matière 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'exemple de code complet 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é, suivez les instructions ci-dessous pour ouvrir et exécuter l'exemple d'application terminée dans Xcode :

Questions fréquentes

Configuration de CocoaPods

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

sudo gem install cocoapods

Si vous rencontrez des problèmes, reportez-vous à la documentation officielle pour télécharger et installer le gestionnaire de dépendances.

Configuration du projet

  1. Accédez à votre terminal et accédez 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 apparaître au bout de quelques secondes.

N'oubliez pas de cliquer sur "Autoriser" lorsque la notification s'affiche concernant l'acceptation des connexions réseau entrantes. L'icône Cast ne s'affiche pas si cette option n'est pas acceptée.

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, puis 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 désormais visible en bas de l'écran.

Illustration d'un iPhone exécutant l'application CastVideos avec la mini-télécommande affichée 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 de caster 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 accédez 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

Ce UITableViewController affiche la liste des vidéos à partir d'une instance MediaListModel. 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. 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 les métadonnées associées, telles que son titre, sa 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é du téléchargement des métadonnées multimédias et du chargement de la vue de la table.

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 transmis à MediaViewController.

MediaViewController

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

Le contrôleur de vue héberge un élément LocalPlayerView, des commandes multimédias et une zone de texte permettant d'afficher la description de la vidéo sélectionnée. Le lecteur recouvre la partie supérieure de l'écran, laissant de la place en dessous pour la description détaillée de la vidéo. L'utilisateur peut lancer la lecture, mettre la vidéo en pause ou rechercher la lecture de la vidéo 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 dans l'angle supérieur droit

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 décrit dans la checklist de conception de Google Cast.

Configuration

Le projet de démarrage 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 comporte un objet singleton global, GCKCastContext, qui coordonne toutes les activités du framework. Cet objet doit être initialisé au début du 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. Le plus important est l'ID d'application du récepteur, qui sert à filtrer les résultats de détection d'appareils Cast et à lancer l'application réceptrice lorsqu'une session Cast est lancée.

La méthode application(_:didFinishLaunchingWithOptions:) est également utile pour 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 d'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)

    ...
  }
  ...
}

Ajoutez ensuite le code suivant à 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. L'icône Cast devrait s'afficher dans la barre de navigation de l'application. Lorsque vous cliquez dessus, les appareils Cast de votre réseau local s'affichent. 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 montre les détails d'une vidéo spécifique ("Tears of Steel"). En bas se trouve le mini-lecteur

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, la procédure est la suivante :

  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 quelque chose 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 dans la section précédente. L'étape 3 est facile à réaliser 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 sommes dans ce contrôleur de vue, nous devons passer à la 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 réceptrice en raison de problèmes de réseau est également gérée automatiquement par le framework Cast.

Les sessions Cast sont gérées par 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 l'é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 supprimées automatiquement en réponse aux gestes des utilisateurs à partir des 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. Il 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 les méthodes existantes ci-dessous pour que la logique de la session Cast permette 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 à présent 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 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, axée sur la mini-télécommande

Le SDK Cast fournit une barre de contrôle, 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, vous devriez voir la mini-télécommande 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 que celle-ci est désormais compatible avec la diffusion et qu'elle aide également les nouveaux utilisateurs de Google Cast.

Illustration d'un iPhone exécutant l'application CastVideos avec l'icône Cast en superposition, l'icône Cast mise en surbrillance et le message "Appuyez pour caster du 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 s'affiche pour la première fois auprès des utilisateurs. 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 présentation en superposition doit s'afficher.

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

La télécommande agrandie est un affichage en plein écran qui permet de contrôler entièrement la lecture des contenus multimédias à distance. Cette vue doit permettre à une application de diffusion de gérer tous les aspects gérables d'une session Cast, à 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 sur l'état de la session multimédia (illustrations, titre, sous-titres, etc.).

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

La première chose à faire est d'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 également 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, et 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 modifié le 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 indique comment le récepteur est lancé 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

Côté expéditeur, vous pouvez spécifier GCKCredentialsData 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 GCKCredentialsData 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, GCKCredentialsData doit être défini à tout moment après la définition des GCKLaunchOptions. Pour illustrer cela, ajoutons une logique pour le bouton Creds (Identifiants) afin de définir les identifiants à transmettre lorsque la session est établie. Ajoutez le code suivant à votre 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 les identifiants lors de la requête de chargement

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

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

En fonction de l'application réceptrice sur laquelle l'émetteur diffuse du contenu, le SDK applique automatiquement les identifiants ci-dessus à 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/ios_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. Ensuite, compilez et exécutez l'application sur un émulateur ou sur un appareil mobile. Lorsque vous établissez une session de diffusion avec votre appareil Android TV, l'application Android Receiver doit se lancer sur votre Android TV. Si vous lisez une vidéo à partir de votre émetteur mobile iOS, la vidéo doit être lancée 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. Par exemple, modifions la couleur de l'icône.

styler.castViews.iconTintColor = .lightGray

Si nécessaire, vous pouvez remplacer les valeurs par défaut pour chaque écran. Par exemple, pour remplacer la couleur lightGrayColor de l'icône uniquement pour la télécommande multimédia agrandie.

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

Modifier les 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 pour toutes les vues fournies par votre framework d'application Cast.

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

Modification des polices

Vous pouvez personnaliser les polices pour différents libellés affichés dans les vues de diffusion. Définissons toutes les polices sur "Courier-Oblique" à des fins d'illustration.

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 leur 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 thème 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 sous iOS.

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