Un'app per iOS compatibile con Google Cast

1. Panoramica

Logo di Google Cast

Questo codelab ti insegnerà come modificare un'app video 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 possono quindi utilizzare il proprio dispositivo mobile come telecomando per la riproduzione di contenuti multimediali sulla TV.

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

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

Cosa realizzeremo?

Dopo aver completato questo codelab, avrai un'app video per iOS in grado di trasmettere video a un dispositivo Google Cast.

Obiettivi didattici

  • Come 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 di trasmissione 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

  • Il nuovo 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 è richiesto Chromecast con Google TV, ma è facoltativo per il resto del codelab. Se non ne hai uno, puoi saltare il passaggio Aggiungere il supporto per Cast Connect, verso la fine di questo tutorial.

Esperienza

  • Devi avere una conoscenza approfondita dello sviluppo iOS.
  • Avrai anche bisogno di sapere come guardare la TV in precedenza. :)

Come utilizzerai questo tutorial?

Leggilo soltanto Leggilo e completa gli esercizi

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

Principiante Intermedio Esperto

Come giudichi la tua esperienza con la visione della TV?

Principiante Intermedio Esperto

2. Recupera il codice campione

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

e apri il file ZIP scaricato.

3. Esegui l'app di esempio

Logo Apple iOS

Innanzitutto, vediamo come si presenta l'app di esempio completata. L'app è un video player di base. L'utente può selezionare un video da un elenco e quindi riprodurlo localmente sul dispositivo o trasmetterlo a un dispositivo Google Cast.

Una volta scaricato il codice, segui queste istruzioni per aprire ed eseguire l'app di esempio completata in Xcode:

Domande frequenti

Configurazione CocoaPods

Per configurare CocoaPods, vai alla console e installalo utilizzando il Ruby predefinito disponibile su macOS:

sudo gem install cocoapods

In caso di problemi, consulta la documentazione ufficiale per scaricare e installare Dipendente.

Configurazione del progetto

  1. Vai al terminale e vai alla directory del codelab.
  2. Installa 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 della 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 di app XCode

Dovresti vedere l'app video dopo qualche secondo.

Assicurati di fare clic su "Consenti' quando viene visualizzata la notifica relativa all'accettazione delle connessioni di rete in arrivo. L'icona di trasmissione non viene visualizzata se questa opzione non viene accettata.

Finestra di dialogo per la conferma in cui si chiede l'autorizzazione ad accettare 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.

La riproduzione del video inizierà sul tuo dispositivo Google Cast.

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

Torna all'elenco dei video.

Nella parte inferiore dello schermo è ora visibile un mini controller.

Illustrazione di un iPhone che esegue l'app CastVideo con il mini controller in basso

Fai clic sul pulsante di pausa nel 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 sul dispositivo Google Cast.

4. Prepara il progetto di avvio

Illustrazione di un iPhone con app CastVideo

Dobbiamo aggiungere il supporto per Google Cast all'app di avvio che hai scaricato. Ecco una terminologia di Google Cast che utilizzeremo in questo codelab:

  • L'app mittente viene eseguita su un dispositivo mobile o laptop
  • Viene eseguita l'app destinatario del dispositivo Google Cast.

Configurazione del progetto

Ora puoi basarti sul progetto di base utilizzando Xcode:

  1. Vai al terminale e vai alla directory del codelab.
  2. Installa 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 della 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 all'utente da sfogliare. Gli utenti possono selezionare un video per visualizzarne i dettagli o riprodurlo localmente sul dispositivo mobile.

L'app è composta da due controller di visualizzazione principali: MediaTableViewController e MediaViewController.

MediaViewViewController

Questa UITableViewController mostra un elenco di video da un'istanza di MediaListModel. L'elenco dei video e i relativi metadati associati vengono ospitati su un server remoto come file JSON. MediaListModel recupera questo JSON e lo elabora per creare un elenco di oggetti MediaItem.

Un oggetto MediaItem modella un video e i relativi metadati associati, come titolo, descrizione, URL di un'immagine e URL per lo stream.

MediaTableViewController crea un'istanza MediaListModel e si registra come MediaListModelDelegate per essere informato quando i metadati multimediali sono stati scaricati, in modo che possa caricare la visualizzazione tabella.

All'utente viene presentato un elenco di miniature dei video con una breve descrizione per ogni video. Quando viene selezionato un elemento, l'elemento MediaItem corrispondente viene passato all'elemento MediaViewController.

Controller MediaView

Questo controller di visualizzazione mostra i metadati di un video specifico e consente all'utente di riprodurre il video 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 sotto L'utente può riprodurre/mettere in pausa o riprodurre la riproduzione del video locale.

Domande frequenti

5. Aggiungere il pulsante Trasmetti

Illustrazione del terzo superiore di un iPhone con l'app CastVideo che mostra il pulsante Trasmetti nell'angolo in alto a destra

In un'applicazione compatibile con Google Cast, viene visualizzato il pulsante Trasmetti in ogni controller di visualizzazione. Fai clic sul pulsante Trasmetti per visualizzare un elenco di dispositivi di trasmissione che un utente può selezionare. Se l'utente stava riproducendo contenuti localmente sul dispositivo di trasmissione, selezionando un dispositivo di trasmissione viene avviata o riprende la riproduzione sul dispositivo di trasmissione. Durante una sessione di trasmissione l'utente può fare clic sul pulsante Trasmetti in qualsiasi momento e interrompere la trasmissione dell'applicazione al dispositivo di trasmissione. L'utente deve essere in grado di connettersi o disconnettersi dal dispositivo di trasmissione in qualsiasi schermata dell'applicazione, come descritto nell'elenco di controllo per la progettazione di Google Cast.

Configurazione

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

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 attivarsi correttamente e avviare la scansione dei dispositivi.

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

Il metodo application(_:didFinishLaunchingWithOptions:) è inoltre un'ottima soluzione per configurare un delegato al logging in modo da ricevere i messaggi di logging dal framework Cast. Questi possono essere utili per il debug e la risoluzione dei problemi.

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

Aggiungi il seguente codice a AppDelegate.swift per inizializzare GCKCastContext con l'ID applicazione indicato dalle impostazioni predefinite dell'utente e 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 GCKCastContext è inizializzato, dobbiamo aggiungere il pulsante Trasmetti per consentire all'utente di selezionare un dispositivo di trasmissione. L'SDK Cast fornisce un componente pulsante Trasmetti chiamato GCKUICastButton come sottoclasse UIButton. Può essere aggiunto alla barra del titolo dell'applicazione inserendolo in un UIBarButtonItem. Dobbiamo aggiungere il pulsante Trasmetti sia a MediaTableViewController sia a MediaViewController.

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

    ...
  }
  ...
}

Aggiungi quindi 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 e facendo clic su di esso verranno elencati i dispositivi di trasmissione sulla tua rete locale. La funzionalità di rilevamento dei dispositivi viene gestita automaticamente da GCKCastContext. Seleziona il tuo dispositivo di trasmissione per caricare l'app del ricevitore di esempio. 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 video sul dispositivo di trasmissione. Fai clic sul pulsante Trasmetti per interrompere la trasmissione.

6. Trasmettere contenuti video

Illustrazione di un iPhone con app CastVideo che mostra dettagli su un determinato video ('Lacrime d'acciaio'). In basso c'è il mini player

L'app di esempio verrà estesa anche per la riproduzione dei video da remoto su un dispositivo di trasmissione. Per fare ciò, dobbiamo ascoltare i vari eventi generati dal framework Cast.

Trasmissione di contenuti multimediali

A livello generale, se vuoi riprodurre contenuti multimediali su un dispositivo di trasmissione, si verifica 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 del 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 ricevitore in base alle interazioni dell'utente.

Il passaggio 1 equivale alla mappatura di un oggetto a un altro; GCKMediaInformation è ben compreso da Google SDK e MediaItem è l'incapsulamento della nostra app per un elemento multimediale; possiamo facilmente mappare un MediaItem a un GCKMediaInformation. Abbiamo già completato il Passaggio 2 nella sezione precedente. Il passaggio 3 è facile da eseguire con l'SDK Cast.

L'app di esempio MediaViewController fa già distinzione tra la riproduzione locale e la riproduzione remota usando questa enumerazione:

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

private var playbackMode = PlaybackMode.none

Non è importante in questo codelab capire esattamente come funziona tutta la logica del player di esempio. È importante capire che il media player dell'app deve essere modificato per essere a conoscenza delle 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'interfaccia utente in base alle transizioni di stato che avvengono nel framework Cast. Ad esempio, se iniziamo a trasmettere, dobbiamo interrompere la riproduzione locale e disattivare alcuni controlli. Analogamente, se interrompiamo la trasmissione con questo controller, dobbiamo passare alla riproduzione locale. Per gestire questa situazione, dobbiamo ascoltare i vari eventi generati dal framework Cast.

Gestione delle sessioni di trasmissione

Per il framework Cast, una sessione di trasmissione combina i passaggi per connettersi a un dispositivo, lanciare (o partecipare), connettersi a un'applicazione ricevente e inizializzare un canale di controllo multimediale, se opportuno. Il canale di controllo dei contenuti multimediali è il modo in cui il framework Cast invia e riceve messaggi dal lettore multimediale 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 di trasmissione.

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 delle sessioni, come creazione, sospensione, ripresa e terminazione.

Innanzitutto, dobbiamo registrare il listener di sessioni 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()
  }

  ...
}

MediaViewController ci interessa essere informati quando ci connettiamo o disconnettiamo dal dispositivo di trasmissione, così potremo passare dal player locale al player o viceversa. Tieni presente che la connettività può essere interrotta non solo dall'istanza in esecuzione sul dispositivo mobile, ma anche da un'altra istanza della tua (o di un'altra) applicazione in esecuzione su un altro dispositivo mobile.

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

Caricamento dell'elemento multimediale in corso...

Nell'SDK Cast, GCKRemoteMediaClient fornisce una serie di pratiche API per la gestione della riproduzione di contenuti multimediali remoti sul ricevitore. Per un elemento GCKCastSession che supporta la riproduzione di contenuti multimediali, l'istanza di GCKRemoteMediaClient verrà creata automaticamente dall'SDK. È accessibile 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 vari metodi esistenti per utilizzare la logica della sessione di trasmissione per 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 tuo dispositivo di trasmissione e avvia la riproduzione di un video. Il video verrà riprodotto sul ricevitore.

7. Mini controller

Nell'elenco di controllo della progettazione di Google Cast, tutte le app di trasmissione devono fornire un mini controller quando l'utente esce dalla pagina dei contenuti correnti. Il mini controller fornisce l'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 in evidenza il mini controller

L'SDK Cast fornisce 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 seguente codice per la condizione if useCastContainerViewController nel 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 (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, dovresti vedere il mini controller nella parte inferiore di ogni scena. Puoi controllare la riproduzione remota utilizzando il mini controller. Se passi dall'attività di esplorazione all'attività del player locale, lo stato del mini controller deve rimanere sincronizzato con lo stato di riproduzione dell'elemento multimediale del ricevitore.

8. Overlay introduttivo

L'elenco di controllo per la progettazione di Google Cast richiede a un'app del mittente di presentare il pulsante Trasmetti agli utenti esistenti per informarli che l'app del mittente ora supporta la trasmissione e aiuta anche gli utenti nuovi di Google Cast.

Illustrazione di un iPhone che esegue l'app CastVideos con l'overlay del pulsante Trasmetti, che evidenzia il pulsante Trasmetti e mostra il messaggio 'Tocca per trasmettere contenuti multimediali alla TV e agli altoparlanti'

Il corso GCKCastContext utilizza 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 a 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, dovresti vedere l'overlay introduttivo.

9. Controller espanso

L'elenco di controllo per la progettazione di Google Cast richiede che un'app del 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 che utilizza l'app CastVideo che riproduce un video con il controller espanso in basso

Il controller espanso è una visualizzazione a schermo intero che offre il controllo completo 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 trasmissione). Fornisce inoltre tutte le informazioni sullo stato della sessione multimediale (artwork, titolo, sottotitolo e così via).

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

La prima cosa da fare è attivare il controller espanso predefinito nel contesto di trasmissione. Modifica AppDelegate.swift per attivare 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 codice seguente 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()
}

Inoltre, il controller espanso verrà avviato automaticamente quando l'utente tocca il mini controller.

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

10. Aggiungi il supporto di Cast Connect

La raccolta di Cast Connect consente alle applicazioni del mittente esistenti di comunicare con le applicazioni Android TV tramite il protocollo di trasmissione. Cast Connect si basa sull'infrastruttura di trasmissione, in cui la tua app per Android TV funge da ricevitore.

Dipendenze

In Podfile, assicurati che google-cast-sdk punti 4.4.8 o un valore superiore, come elencato 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'

Opzioni di lancio di GCK

Per lanciare l'applicazione Android TV, nota anche come Ricevitore Android, dobbiamo impostare il flag androidReceiverCompatible su true nell'oggetto GCKLaunchOptions. L'oggetto GCKLaunchOptions determina la modalità di avvio del ricevitore e viene trasmesso a GCKCastOptions, che è impostato nell'istanza condivisa tramite 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 mittente, puoi specificare GCKCredentialsData per specificare chi partecipa alla sessione. Il credentials è una stringa che può essere definita dall'utente purché la tua app ATV possa capirla. Il codice GCKCredentialsData viene trasmesso alla tua app Android TV soltanto al momento del lancio o durante la partecipazione. 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 delle GCKLaunchOptions. Per dimostrarlo, aggiungiamo una logica per il pulsante Creds per impostare le credenziali da trasmettere quando la sessione viene stabilita. Aggiungi il seguente codice al tuo 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 nell'app Ricevitore web e Android TV, aggiungi il seguente codice nella classe MediaTableViewController.swift nella funzione loadSelectedItem:

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

A seconda dell'app del ricevitore a cui viene trasmesso il mittente, l'SDK applica automaticamente le credenziali riportate 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 & Internet > (nome di rete a cui è collegato il dispositivo). A destra vengono visualizzati i dettagli e l'IP del tuo dispositivo sulla rete.
  2. Usa l'indirizzo IP del tuo dispositivo per connetterti tramite ADB tramite il terminale:
$ adb connect <device_ip_address>:5555
  1. Dalla finestra del terminale, vai alla cartella di primo livello per gli esempi di 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 questo comando:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. Ora dovresti riuscire a vedere un'app dal nome di Trasmetti video nel menu Le tue app sul tuo dispositivo Android TV.
  2. Dopodiché, crea ed esegui l'app su un emulatore o un dispositivo mobile. Dopo avere stabilito una sessione di trasmissione con il dispositivo Android TV, dovrebbe essere avviata l'applicazione ricevitore Android su Android TV. Se riproduci un video dal tuo dispositivo mobile iOS, dovresti avviare il video nel ricevitore Android e consentirti di controllare la riproduzione utilizzando il telecomando del tuo dispositivo Android TV.

11. Personalizzare i widget di trasmissione

Inizializzazione

Inizia con la cartella Fine app. Aggiungi quanto segue al metodo applicationDidFinishLaunchingWithOptions nel tuo file AppDelegate.swift.

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

Una volta completata l'applicazione di una o più personalizzazioni descritte nel resto di questo codelab, esegui il commit degli stili chiamando il codice seguente

styler.apply()

Personalizzazione delle visualizzazioni Cast

Puoi personalizzare tutte le viste gestite da Framework delle applicazioni di Cast seguendo le linee guida predefinite per lo stile. Ad esempio, cambiamo il colore di tinta dell'icona.

styler.castViews.iconTintColor = .lightGray

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

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

Modifica dei colori

Puoi personalizzare il colore dello sfondo per tutte le visualizzazioni (o per ogni singola visualizzazione). Il codice che segue imposta il colore di sfondo su blu per tutte le viste fornite da Cast Application Framework.

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

Modificare i caratteri

Puoi personalizzare i caratteri delle diverse etichette visualizzate nelle viste di trasmissione. Impostiamo tutti i caratteri su "Corriere-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 le tue immagini personalizzate al progetto e assegna le immagini ai pulsanti per assegnare loro uno stile.

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

Modificare il tema del pulsante Trasmetti

Puoi anche applicare un tema ai widget Cast utilizzando il protocollo Aspetto UI. Il codice che segue genera il tema GCKUICastButton in tutte le viste visualizzate:

GCKUICastButton.appearance().tintColor = UIColor.gray

12 Congratulazioni

Ora sai come trasmettere un'app video compatibile con Google Cast utilizzando i widget dell'SDK Google Cast su iOS.

Per maggiori dettagli, consulta la guida per gli sviluppatori di Mittente iOS.