Un'app per iOS compatibile con Google Cast

1. Panoramica

Logo di Google Cast

Questo codelab ti insegnerà a modificare un'app video per iOS esistente per trasmettere contenuti su un dispositivo compatibile con Google Cast.

Che cos'è Google Cast?

Google Cast consente agli utenti di trasmettere contenuti da un dispositivo mobile a una TV. Gli utenti potranno quindi utilizzare il proprio dispositivo mobile come telecomando per riprodurre contenuti multimediali sulla TV.

L'SDK Google Cast ti consente di estendere la tua app per controllare i dispositivi compatibili con Google Cast (ad esempio una TV o un impianto audio). L'SDK Cast ti consente di aggiungere i componenti dell'interfaccia utente necessari in base all'elenco di controllo per la progettazione di Google Cast.

Viene fornito l'elenco di controllo per la progettazione di Google Cast per rendere l'esperienza utente di Google Cast semplice e prevedibile su tutte le piattaforme supportate.

Cosa realizzeremo?

Al termine del codelab, avrai a disposizione un'app video per iOS in grado di trasmettere video a un dispositivo Google Cast.

Obiettivi didattici

  • Aggiungere l'SDK Google Cast a un'app video di esempio.
  • Come aggiungere il pulsante Trasmetti per selezionare un dispositivo Google Cast.
  • Come connettersi a un dispositivo di trasmissione e avviare un ricevitore multimediale.
  • Come trasmettere un video.
  • Come aggiungere un mini controller Cast all'app.
  • Come aggiungere un controller espanso.
  • Come fornire un overlay introduttivo.
  • Come personalizzare i widget di trasmissione.
  • Come integrare Cast Connect

Che cosa ti serve

  • L'ultima versione di Xcode.
  • Un dispositivo mobile con iOS 9 o versioni successive (o il simulatore Xcode).
  • Un cavo dati USB per collegare il dispositivo mobile al computer di sviluppo (se utilizzi un dispositivo).
  • Un dispositivo Google Cast, ad esempio Chromecast o Android TV, configurato con accesso a internet.
  • Una TV o un monitor con ingresso HDMI.
  • Per testare l'integrazione di Cast Connect è necessario Chromecast con Google TV, ma è facoltativo per il resto del codelab. Se non ne hai uno, salta il passaggio Aggiungi il supporto di Cast Connect alla fine di questo tutorial.

Esperienza

  • Devi avere conoscenze pregresse nello sviluppo iOS.
  • Ti servirà anche una conoscenza pregressa dell'esperienza TV. :)

Come utilizzerai questo tutorial?

Solo lettura Leggilo e completa gli esercizi

Come giudichi la tua esperienza di creazione di app per iOS?

Principiante Livello intermedio Eccellente

Come giudichi la tua esperienza con la visione della TV?

Principiante Intermedio Proficiente

2. recupera il codice campione

Puoi scaricare tutto il codice campione sul tuo computer...

e decomprimi il file ZIP scaricato.

3. Esegui l'app di esempio

Logo Apple iOS

Per prima cosa, vediamo che aspetto ha l'app di esempio completata. L'app è un video player di base. L'utente può selezionare un video da un elenco e riprodurlo localmente sul dispositivo o trasmetterlo a un dispositivo Google Cast.

Una volta scaricato il codice, le seguenti istruzioni descrivono come aprire ed eseguire l'app di esempio completata in Xcode:

Domande frequenti

Configurazione di CocoaPods

Per configurare CocoaPods, vai alla console e installalo utilizzando la versione Ruby predefinita disponibile su macOS:

sudo gem install cocoapods

In caso di problemi, consulta la documentazione ufficiale per scaricare e installare il gestore delle dipendenze.

Configurazione del progetto

  1. Vai al terminale e vai alla directory del codelab.
  2. Installare le dipendenze dal podfile.
cd app-done
pod update
pod install
  1. Apri Xcode e seleziona Apri un altro progetto...
  2. Seleziona il file CastVideos-ios.xcworkspace dalla directory icona cartellaapp-done nella cartella del codice di esempio.

Esegui l'app

Seleziona il target e il simulatore, quindi esegui l'app:

Barra degli strumenti del simulatore dell'app XCode

Dovresti vedere l'app video dopo qualche secondo.

Assicurati di fare clic su "Consenti". quando viene visualizzata la notifica relativa all'accettazione di connessioni di rete in entrata. Se questa opzione non viene accettata, l'icona Trasmetti non viene visualizzata.

Finestra di dialogo di conferma che chiede l'autorizzazione per accettare le connessioni di rete in entrata

Fai clic sul pulsante Trasmetti e seleziona il tuo dispositivo Google Cast.

Seleziona un video e fai clic sul pulsante di riproduzione.

Il video verrà riprodotto sul dispositivo Google Cast.

Viene visualizzato il controller espanso. Puoi utilizzare il pulsante di riproduzione/pausa per controllare la riproduzione.

Torna all'elenco dei video.

Ora nella parte inferiore dello schermo è visibile un mini controller.

Illustrazione di un iPhone su cui è in esecuzione l'app CastVideo con il mini controller visualizzato in basso

Fai clic sul pulsante Pausa del mini controller per mettere in pausa il video sul ricevitore. Fai clic sul pulsante di riproduzione nel mini controller per continuare a riprodurre il video.

Fai clic sul pulsante Trasmetti per interrompere la trasmissione al dispositivo Google Cast.

4. Prepara il progetto iniziale

Illustrazione di un iPhone con l'app CastVideo

Dobbiamo aggiungere il supporto di Google Cast all'app iniziale che hai scaricato. Ecco alcuni termini di Google Cast che utilizzeremo in questo codelab:

  • un'app di mittente viene eseguita su un dispositivo mobile o un laptop
  • sul dispositivo Google Cast viene eseguita un'app di ricevitore.

Configurazione del progetto

Ora puoi iniziare a creare a partire dal progetto iniziale utilizzando Xcode:

  1. Vai al terminale e vai alla directory del codelab.
  2. Installare le dipendenze dal podfile.
cd app-start
pod update
pod install
  1. Apri Xcode e seleziona Apri un altro progetto...
  2. Seleziona il file CastVideos-ios.xcworkspace dalla directory icona cartellaapp-start nella cartella del codice di esempio.

Progettazione di app

L'app recupera un elenco di video da un server web remoto e fornisce un elenco da consultare all'utente. Gli utenti possono selezionare un video per visualizzarne i dettagli o riprodurlo localmente sui dispositivi mobili.

L'app è composta da due controller della vista principale: MediaTableViewController e MediaViewController.

MediaTableViewController

Questo UITableViewController mostra un elenco di video di un'istanza MediaListModel. L'elenco dei video e i relativi metadati associati sono ospitati su un server remoto sotto forma di file JSON. MediaListModel recupera questo JSON e lo elabora per creare un elenco di oggetti MediaItem.

Un oggetto MediaItem modella un video e i metadati associati, come il titolo, la descrizione, l'URL di un'immagine e l'URL dello stream.

MediaTableViewController crea un'istanza MediaListModel, quindi si registra come MediaListModelDelegate per ricevere un avviso quando i metadati multimediali sono stati scaricati in modo da poter caricare la visualizzazione tabella.

All'utente viene presentato un elenco di miniature con una breve descrizione per ogni video. Quando un elemento viene selezionato, il valore MediaItem corrispondente viene trasmesso a MediaViewController.

MediaViewController

Questo controller di visualizzazione visualizza i metadati relativi a un determinato video e consente all'utente di riprodurlo localmente sul dispositivo mobile.

Il controller di visualizzazione ospita un LocalPlayerView, alcuni controlli multimediali e un'area di testo per mostrare la descrizione del video selezionato. Il player copre la parte superiore dello schermo, lasciando spazio per la descrizione dettagliata del video. L'utente può riprodurre/mettere in pausa o cercare la riproduzione del video locale.

Domande frequenti

5. Aggiunta del pulsante Trasmetti

Illustrazione del terzo superiore di un iPhone su cui è in esecuzione l'app CastVideo, con il pulsante Trasmetti nell'angolo in alto a destra

Un'applicazione compatibile con Google Cast mostra il pulsante Trasmetti in ciascuno dei suoi controller di visualizzazione. Se fai clic sul pulsante Trasmetti, viene visualizzato un elenco di dispositivi di trasmissione selezionabili dall'utente. Se l'utente stava riproducendo contenuti localmente sul dispositivo di trasmissione, la selezione di un dispositivo di trasmissione avvia o riprende la riproduzione su quel dispositivo di trasmissione. Durante una sessione di trasmissione, l'utente può fare clic sul pulsante Trasmetti e interrompere la trasmissione dell'applicazione al dispositivo di trasmissione in qualsiasi momento. L'utente deve essere in grado di connettersi o disconnettersi dal dispositivo di trasmissione mentre si trova in qualsiasi schermata dell'applicazione, come descritto nell'elenco di controllo per la progettazione di Google Cast.

Configurazione

Il progetto iniziale richiede le stesse dipendenze e configurazione Xcode che hai utilizzato per l'app di esempio completata. Torna a questa sezione e segui gli stessi passaggi per aggiungere GoogleCast.framework al progetto iniziale dell'app.

Inizializzazione

Il framework Cast ha un oggetto singleton globale, GCKCastContext, che coordina tutte le attività del framework. Questo oggetto deve essere inizializzato all'inizio del ciclo di vita dell'applicazione, in genere nel metodo application(_:didFinishLaunchingWithOptions:) del delegato dell'app, in modo che la ripresa automatica della sessione al riavvio dell'applicazione del mittente possa essere attivata correttamente e che venga avviata l'analisi dei dispositivi.

È necessario specificare un oggetto GCKCastOptions durante l'inizializzazione di GCKCastContext. Questa classe contiene opzioni che influiscono sul comportamento del framework. Il più importante è l'ID applicazione ricevitore, utilizzato per filtrare i risultati di rilevamento dei dispositivi di trasmissione e per avviare l'applicazione ricevitore quando viene avviata una sessione di trasmissione.

Il metodo application(_:didFinishLaunchingWithOptions:) è utile anche per configurare un delegato di logging che riceva i messaggi di log dal framework di trasmissione. Queste possono essere utili per il debug e la risoluzione dei problemi.

Quando sviluppi la tua app compatibile con Google Cast, devi registrarti come sviluppatore Google Cast e ottenere un ID applicazione per l'app. Per questo codelab, utilizzeremo un ID app di esempio.

Aggiungi il seguente codice a AppDelegate.swift per inizializzare GCKCastContext con l'ID applicazione dai valori predefiniti dell'utente, quindi aggiungi un logger per il 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)")
    }
  }
}

Pulsante Trasmetti

Ora che l'GCKCastContext è stato inizializzato, dobbiamo aggiungere il pulsante Trasmetti per consentire all'utente di selezionare un dispositivo di trasmissione. L'SDK Cast fornisce un componente del pulsante Trasmetti denominato GCKUICastButton come sottoclasse UIButton. Può essere aggiunto alla barra del titolo dell'applicazione racchiudendolo all'interno di un UIBarButtonItem. Dobbiamo aggiungere il pulsante Trasmetti sia a MediaTableViewController che a MediaViewController.

Aggiungi il seguente codice a MediaTableViewController.swift e 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)

    ...
  }
  ...
}

Poi, aggiungi il seguente codice al tuo 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)

    ...
  }
  ...
}

Ora esegui l'app. Dovresti vedere un pulsante Trasmetti nella barra di navigazione dell'app; quando ci fai clic sopra, verranno elencati i dispositivi di trasmissione della tua rete locale. Il rilevamento dei dispositivi è gestito automaticamente da GCKCastContext. Seleziona il tuo dispositivo di trasmissione e l'app ricevitore di esempio verrà caricata sul dispositivo di trasmissione. Puoi spostarti tra l'attività di navigazione e l'attività del player locale e lo stato del pulsante Trasmetti rimane sincronizzato.

Non abbiamo collegato alcun supporto per la riproduzione di contenuti multimediali, quindi non puoi ancora riprodurre i video sul dispositivo di trasmissione. Fai clic sul pulsante Trasmetti per interrompere la trasmissione.

6. Trasmissione di contenuti video

Illustrazione di un iPhone su cui è in esecuzione un'app CastVideo, che mostra i dettagli di un determinato video ("Tears of Steel"). In basso c'è il mini player

Estenderemo l'app di esempio per riprodurre i video anche da remoto su un dispositivo di trasmissione. Per farlo, dobbiamo ascoltare i vari eventi generati dal framework Cast.

Trasmissione di contenuti multimediali

A livello generale, se vuoi riprodurre un contenuto multimediale su un dispositivo di trasmissione, si deve verificare quanto segue:

  1. Crea un oggetto GCKMediaInformation dall'SDK Cast che modella un elemento multimediale.
  2. L'utente si connette al dispositivo di trasmissione per avviare l'applicazione ricevitore.
  3. Carica l'oggetto GCKMediaInformation nel ricevitore e riproduci i contenuti.
  4. Monitora lo stato dei contenuti multimediali.
  5. Invia comandi di riproduzione al destinatario in base alle interazioni dell'utente.

Il passaggio 1 equivale a mappare un oggetto all'altro; GCKMediaInformation è un elemento comprensibile dall'SDK Cast, mentre MediaItem è l'incapsulamento della nostra app per un elemento multimediale. possiamo facilmente mappare un MediaItem a un GCKMediaInformation. Abbiamo già eseguito il passaggio 2 nella sezione precedente. Il passaggio 3 è semplice da eseguire con l'SDK Google Cast.

L'app di esempio MediaViewController già distingue tra la riproduzione locale e quella remota utilizzando questa enum:

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

private var playbackMode = PlaybackMode.none

In questo codelab non è importante capire esattamente come funziona tutta la logica del player di esempio. È importante capire che il media player dell'app dovrà essere modificato in modo da rilevare le due posizioni di riproduzione in modo simile.

Al momento il player locale è sempre nello stato di riproduzione locale perché non sa ancora nulla sugli stati di trasmissione. Dobbiamo aggiornare l'UI in base alle transizioni di stato che si verificano nel framework Cast. Ad esempio, se iniziamo a trasmettere, dobbiamo interrompere la riproduzione locale e disattivare alcuni controlli. Analogamente, se interrompiamo la trasmissione quando siamo in questo view controller, dobbiamo passare alla riproduzione locale. Per farcela dobbiamo ascoltare i vari eventi generati dal framework Cast.

Gestione delle sessioni di trasmissione

Per il framework di trasmissione, una sessione di trasmissione combina i passaggi per la connessione a un dispositivo, l'avvio (o l'accesso), la connessione a un'applicazione ricevitore e l'inizializzazione di un canale di controllo multimediale, se appropriato. Il canale di controllo multimediale è il modo in cui il framework di trasmissione invia e riceve messaggi dal media player ricevente.

La sessione di trasmissione viene avviata automaticamente quando l'utente seleziona un dispositivo dal pulsante Trasmetti e viene interrotta automaticamente quando l'utente si disconnette. Anche la riconnessione a una sessione del ricevitore a causa di problemi di rete viene gestita automaticamente dal framework Cast.

Le sessioni di trasmissione sono gestite dalla GCKSessionManager, a cui è possibile accedere tramite GCKCastContext.sharedInstance().sessionManager. I callback GCKSessionManagerListener possono essere utilizzati per monitorare gli eventi della sessione, ad esempio creazione, sospensione, ripresa e chiusura.

Per prima cosa dobbiamo registrare il listener di sessione e inizializzare alcune variabili:

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

  ...
}

In MediaViewController, ci interessa essere informati quando saremo connessi o disconnessi dal dispositivo di trasmissione in modo da poter passare al player locale o viceversa. Tieni presente che la connettività può essere interrotta non solo dall'istanza dell'applicazione in esecuzione sul tuo dispositivo mobile, ma anche da un'altra istanza della tua (o di un'altra) applicazione in esecuzione su un dispositivo mobile diverso.

La sessione attualmente attiva è accessibile come GCKCastContext.sharedInstance().sessionManager.currentCastSession. Le sessioni vengono create e rimosse automaticamente in risposta ai gesti dell'utente dalle finestre di dialogo Trasmetti.

Caricamento dei contenuti multimediali in corso...

Nell'SDK Cast, GCKRemoteMediaClient fornisce una serie di pratiche API per gestire la riproduzione multimediale remota sul ricevitore. Per un GCKCastSession che supporta la riproduzione di contenuti multimediali, l'SDK creerà automaticamente un'istanza di GCKRemoteMediaClient. Puoi accedervi come proprietà remoteMediaClient dell'istanza GCKCastSession.

Aggiungi il seguente codice a MediaViewController.swift per caricare il video attualmente selezionato sul ricevitore:

@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
      }
    }
  }
  ...
}

Ora aggiorna i vari metodi esistenti per utilizzare la logica della sessione di trasmissione in modo da supportare la riproduzione remota:

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
}

Ora esegui l'app sul tuo dispositivo mobile. Collegati al dispositivo di trasmissione e inizia a riprodurre un video. Il video dovrebbe essere riprodotto sul ricevitore.

7. Mini controller

L'elenco di controllo per la progettazione della trasmissione richiede che tutte le app Google Cast forniscano un mini controller quando l'utente esce dalla pagina dei contenuti corrente. Il mini controller fornisce accesso immediato e un promemoria visibile per la sessione di trasmissione corrente.

Illustrazione della parte inferiore di un iPhone su cui è in esecuzione l'app CastVideo, con l'attenzione sul mini controller

L'SDK Cast mette a disposizione una barra di controllo, GCKUIMiniMediaControlsViewController, che può essere aggiunta alle scene in cui vuoi mostrare i controlli permanenti.

Per l'app di esempio, utilizzeremo GCKUICastContainerViewController che aggrega un altro controller di visualizzazione e aggiunge un GCKUIMiniMediaControlsViewController in basso.

Modifica il file AppDelegate.swift e aggiungi il codice seguente per la condizione if useCastContainerViewController con il seguente metodo:

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

Aggiungi questa proprietà e il setter/getter per controllare la visibilità del mini controller (li utilizzeremo in una sezione successiva):

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

Esegui l'app e trasmetti un video. Quando la riproduzione inizia sul ricevitore, il mini controller dovrebbe apparire in fondo a ogni scena. Puoi controllare la riproduzione remota utilizzando il mini controller. Se passi dall'attività di navigazione all'attività del player locale e viceversa, lo stato del mini controller dovrebbe rimanere sincronizzato con lo stato di riproduzione dei contenuti multimediali del destinatario.

8. Overlay introduttivo

L'elenco di controllo per la progettazione di Google Cast richiede che un'app mittente presenti il pulsante Trasmetti agli utenti esistenti per informarli che ora l'app del mittente supporta la trasmissione e che aiuta gli utenti che non hanno mai utilizzato Google Cast.

Illustrazione di un iPhone su cui è in esecuzione l'app CastVideo con l'overlay del pulsante Trasmetti, in cui è evidenziato il pulsante Trasmetti e viene visualizzato il messaggio "Tocca per trasmettere contenuti multimediali alla TV e agli altoparlanti"

La classe GCKCastContext ha un metodo, presentCastInstructionsViewControllerOnce, che può essere utilizzato per evidenziare il pulsante Trasmetti quando viene mostrato per la prima volta agli utenti. Aggiungi il seguente codice a MediaViewController.swift e 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)
  }
}

Esegui l'app sul tuo dispositivo mobile e dovresti vedere l'overlay introduttivo.

9. Controller espanso

L'elenco di controllo per la progettazione di Google Cast richiede che un'app mittente fornisca un controller espanso per i contenuti multimediali trasmessi. Il controller espanso è una versione a schermo intero del mini controller.

Illustrazione di un iPhone su cui è in esecuzione l'app CastVideo che riproduce un video con il controller espanso in basso

Il controller espanso è in una visualizzazione a schermo intero che offre il pieno controllo della riproduzione dei contenuti multimediali da remoto. Questa visualizzazione dovrebbe consentire a un'app di trasmissione di gestire ogni aspetto gestibile di una sessione di trasmissione, ad eccezione del controllo del volume del ricevitore e del ciclo di vita della sessione (connessione/interruzione della trasmissione). Fornisce inoltre tutte le informazioni sullo stato della sessione multimediale (artwork, titolo, sottotitolo e così via).

La funzionalità di questa vista è implementata dalla classe GCKUIExpandedMediaControlsViewController.

Per prima cosa devi attivare il controller espanso predefinito nel contesto di trasmissione. Modifica AppDelegate.swift per abilitare il controller espanso predefinito:

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

Aggiungi il seguente codice a MediaViewController.swift per caricare il controller espanso quando l'utente inizia a trasmettere un video:

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

Il controller espanso verrà avviato automaticamente anche quando l'utente tocca il mini controller.

Esegui l'app e trasmetti un video. Dovresti vedere il controller espanso. Torna all'elenco dei video: quando fai clic sul mini controller, il controller espanso verrà caricato di nuovo.

10. Aggiungere il supporto di Cast Connect

La raccolta Cast Connect consente alle applicazioni di mittenti esistenti di comunicare con le applicazioni Android TV tramite il protocollo Cast. Cast Connect si basa sull'infrastruttura Cast e l'app per Android TV funge da ricevitore.

Dipendenze

In Podfile, assicurati che google-cast-sdk punti a 4.4.8 o a una versione successiva, come indicato di seguito. Se hai apportato una modifica al file, esegui pod update dalla console per sincronizzare la modifica con il tuo progetto.

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

GCKLaunchOptions

Per poter avviare l'applicazione Android TV, nota anche come ricevitore Android, dobbiamo impostare il flag androidReceiverCompatible su true nell'oggetto GCKLaunchOptions. Questo oggetto GCKLaunchOptions determina il modo in cui il ricevitore viene avviato e viene passato ai GCKCastOptions, che sono impostati nell'istanza condivisa utilizzando GCKCastContext.setSharedInstanceWith.

Aggiungi le seguenti righe a 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)

Imposta credenziali di avvio

Sul lato del mittente, puoi specificare GCKCredentialsData per rappresentare chi parteciperà alla sessione. credentials è una stringa che può essere definita dall'utente, purché l'app ATV possa comprenderla. Il GCKCredentialsData viene trasmesso alla tua app Android TV soltanto al momento del lancio o dell'iscrizione. Se lo imposti di nuovo mentre sei connesso, non verrà trasmesso all'app Android TV.

Per impostare le credenziali di lancio, è necessario definire GCKCredentialsData in qualsiasi momento dopo l'impostazione dei GCKLaunchOptions. Per dimostrarlo, aggiungiamo la logica per il pulsante Creds per impostare le credenziali da trasmettere quando viene stabilita la sessione. Aggiungi il seguente codice a 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))
  }
}

Imposta credenziali su richiesta di caricamento

Per gestire credentials sia nell'app Web sia nell'app Ricevitore TV, aggiungi il seguente codice nella tua classe MediaTableViewController.swift nella funzione loadSelectedItem:

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

A seconda dell'app del destinatario a cui il mittente esegue la trasmissione, l'SDK applicherà automaticamente le credenziali di cui sopra alla sessione in corso.

Test di Cast Connect

Procedura per installare l'APK Android TV su Chromecast con Google TV

  1. Trova l'indirizzo IP del tuo dispositivo Android TV. In genere, è disponibile in Impostazioni > Rete e Internet > (Nome di rete a cui è connesso il dispositivo). A destra vengono visualizzati i dettagli e l'indirizzo IP del dispositivo in rete.
  2. Utilizza l'indirizzo IP del tuo dispositivo per collegarlo tramite ADB utilizzando il terminale:
$ adb connect <device_ip_address>:5555
  1. Dalla finestra del terminale, vai alla cartella di primo livello con gli esempi del codelab che hai scaricato all'inizio di questo codelab. Ad esempio:
$ cd Desktop/ios_codelab_src
  1. Installa il file .apk in questa cartella su Android TV eseguendo:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. Ora dovresti riuscire a vedere un'app chiamata Trasmetti video nel menu Le tue app sul dispositivo Android TV.
  2. Al termine, crea ed esegui l'app su un emulatore o su un dispositivo mobile. Una volta stabilita una sessione di trasmissione con il dispositivo Android TV, ora dovrebbe essere avviata l'applicazione Android Ricevitore su Android TV. La riproduzione di un video dal tuo mittente di dispositivo mobile iOS dovrebbe avviarlo nel ricevitore Android e consentirti di controllare la riproduzione utilizzando il telecomando del tuo dispositivo Android TV.

11. Personalizzare i widget di Google Cast

Inizializzazione

Inizia dalla cartella App-Done (App completata). Aggiungi quanto segue al metodo applicationDidFinishLaunchingWithOptions nel file AppDelegate.swift.

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

Dopo aver applicato una o più personalizzazioni, come indicato nel resto di questo codelab, esegui il commit degli stili chiamando il codice riportato di seguito

styler.apply()

Personalizzazione delle visualizzazioni di trasmissione

Puoi personalizzare tutte le visualizzazioni gestite dal framework dell'applicazione di Google Cast impostando linee guida per lo stile predefinite per tutte le viste. Ad esempio, cambiamo il colore della tinta dell'icona.

styler.castViews.iconTintColor = .lightGray

Se necessario, puoi eseguire l'override dei valori predefiniti per ogni schermata. Ad esempio, per eseguire l'override del colore lightGrayColor per il colore della tonalità dell'icona solo per il controller multimediale espanso.

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

Colori variabili

Puoi personalizzare il colore dello sfondo per tutte le visualizzazioni (o singolarmente per ciascuna di esse). Il seguente codice imposta il colore di sfondo su blu per tutte le visualizzazioni fornite da Cast Application Framework.

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

Modifica dei caratteri

Puoi personalizzare i caratteri per etichette diverse presenti nella visualizzazione Trasmissione. Impostiamo tutti i caratteri su "Courier-Oblique" a scopo illustrativo.

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)

Modificare le immagini dei pulsanti predefinite

Aggiungi immagini personalizzate al progetto e assegnale ai pulsanti per applicarne uno stile.

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

Modifica del tema del pulsante Trasmetti

Puoi anche usare il protocollo UIAspetto per i widget di trasmissione. Il seguente codice applica il tema a GCKUICastButton in tutte le viste che compare:

GCKUICastButton.appearance().tintColor = UIColor.gray

12. Complimenti

Ora sai come abilitare la trasmissione di un'app video utilizzando i widget dell'SDK Cast su iOS.

Per ulteriori dettagli, consulta la guida per gli sviluppatori relativa al mittente iOS.