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 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 compatibili con Google Cast, ad esempio la TV o il sistema audio. L'SDK Cast ti consente di aggiungere i componenti UI necessari in base all'elenco di controllo per la progettazione di Google Cast.

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

Cosa realizzeremo

Una volta completato questo codelab, avrai a disposizione 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 connetterti a un dispositivo di trasmissione e avviare un ricevitore multimediale.
  • Come trasmettere un video.
  • Come aggiungere un controller Cast Mini alla tua 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 un Chromecast con Google TV, ma è facoltativo per la parte restante del codelab. Se non ne hai uno, salta il passaggio Aggiungere assistenza per Cast Connect verso la fine di questo tutorial.

Esperienza

  • È richiesta una conoscenza pregressa dello sviluppo iOS.
  • È necessaria anche una conoscenza pregressa di come guardare la TV :)

Come userai questo tutorial?

Leggi solo tutto Leggi e completa gli esercizi

Come giudicheresti la tua esperienza con lo sviluppo di app per iOS?

Principiante Intermedio Esperto

Come valuteresti 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 decomprimere il file ZIP scaricato.

3. Esegui l'app di esempio

Logo Apple iOS

Innanzitutto, vediamo l'aspetto dell'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 oppure trasmetterlo a un dispositivo Google Cast.

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

Domande frequenti

Configurazione di CocoaPods

Per configurare CocoaPods, vai alla console ed esegui l'installazione utilizzando il metodo Ruby predefinito 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.

Eseguire l'app

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

Barra degli strumenti del simulatore app XCode

L'app video dovrebbe essere visualizzata dopo qualche secondo.

Assicurati di fare clic su "Consenti" quando viene visualizzata la notifica relativa all'accettazione di connessioni di rete in arrivo. 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.

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.

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

Illustrazione di un iPhone su cui è installata l'app CastVideo con il mini controller che appare in basso

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

4. Prepara il progetto di avvio

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 del mittente viene eseguita su un dispositivo mobile o un laptop,
  • Sul dispositivo Google Cast è in esecuzione un'app per ricevitore.

Configurazione del progetto

Ora puoi iniziare a creare sulla base del progetto di partenza 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 all'utente un elenco per la navigazione. 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.

MediaTableViewController

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

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 e poi si registra come MediaListModelDelegate per ricevere una notifica quando i metadati multimediali sono stati scaricati in modo da poter caricare la visualizzazione tabella.

L'utente visualizza un elenco di miniature di ogni video con una breve descrizione. Quando viene selezionato un elemento, il corrispondente MediaItem viene passato a MediaViewController.

MediaViewController

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

Il controller di visualizzazione ospita un elemento 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 alla descrizione dettagliata del video sotto. L'utente può riprodurre/mettere in pausa la riproduzione o cercare la riproduzione del video locale.

Domande frequenti

5. Aggiunta del pulsante Trasmetti

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

Un'applicazione compatibile con Google Cast mostra il pulsante Trasmetti in ciascuno dei propri 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 mittente, la selezione di un dispositivo di trasmissione avvia o riprende la riproduzione su tale dispositivo. 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 la configurazione Xcode che hai usato 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 possa essere avviata l'analisi dei dispositivi.

Durante l'inizializzazione di GCKCastContext è necessario specificare un oggetto GCKCastOptions. Questa classe contiene opzioni che influiscono sul comportamento del framework. Il più importante è l'ID applicazione del destinatario, 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:) è utile anche per configurare un delegato di logging in modo che riceva i messaggi di logging dal framework di trasmissione. Questi strumenti 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 Google Cast e ottenere un ID applicazione per l'app. Per questo codelab, utilizzeremo un ID app di esempio.

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

    ...
  }
  ...
}

Quindi, aggiungi il seguente codice a 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 fai clic su di esso, verranno elencati i dispositivi di trasmissione presenti sulla tua rete locale. Il rilevamento dei dispositivi è gestito automaticamente da GCKCastContext. Seleziona il tuo dispositivo di trasmissione: 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 viene mantenuto sincronizzato.

Non abbiamo ancora supportato 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 l'app CastVideo, che mostra i dettagli di un determinato video ("Lacrime d'acciaio"). In basso si trova il mini player

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

Trasmissione di contenuti multimediali

A livello generale, se vuoi riprodurre un contenuto multimediale su un dispositivo di trasmissione, è necessario che:

  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. Tieni traccia dello stato dei contenuti multimediali.
  5. Invia i comandi di riproduzione al destinatario in base alle interazioni dell'utente.

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

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

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

private var playbackMode = PlaybackMode.none

In questo codelab non è importante capire esattamente come funziona l'intera logica del player di esempio. È importante capire che il media player dell'app dovrà essere modificato per tenere conto 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 di trasmissione. Ad esempio, se iniziamo a trasmettere, dobbiamo interrompere la riproduzione locale e disattivare alcuni controlli. Analogamente, se interrompiamo la trasmissione quando ci troviamo in questo controller di visualizzazione, dobbiamo passare alla riproduzione locale. Per farlo, dobbiamo ascoltare i vari eventi generati dal framework di 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'unione), la connessione a un'applicazione ricevitore e l'inizializzazione di un canale di controllo multimediale, se opportuno. Il canale di controllo dei contenuti multimediali è il modo in cui il framework di trasmissione invia e riceve messaggi dal media player del ricevitore.

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 di ricezione a causa di problemi di rete viene gestita automaticamente dal framework di 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 nostro 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()
  }

  ...
}

Nel mese di MediaViewController, ci interessa essere informati quando ci connettiamo o ci disconnettono dal dispositivo di trasmissione in modo da poter passare da o verso il player locale. 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 applicazione (o di un'altra) in esecuzione su un dispositivo mobile diverso.

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

Caricamento contenuti multimediali in corso...

Nell'SDK Cast, GCKRemoteMediaClient fornisce una serie di utili API per la gestione della riproduzione remota di contenuti multimediali sul ricevitore. Per un GCKCastSession che supporta la riproduzione di contenuti multimediali, l'SDK crea automaticamente un'istanza di GCKRemoteMediaClient. È possibile 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 tuo dispositivo di trasmissione e inizia a riprodurre un video. Dovresti vedere il video in riproduzione sul ricevitore.

7. Mini controller

L'elenco di controllo della progettazione di Google Cast richiede che tutte le app di trasmissione dotate di un mini controller siano visualizzate quando l'utente esce dalla pagina di 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 con l'app CastVideo, focalizzata sul 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 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 imposta il setter/getter per controllare la visibilità del mini controller (lo 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 inizia la riproduzione 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 navigazione all'attività locale del player, lo stato del mini controller dovrebbe rimanere sincronizzato con lo stato di riproduzione dei contenuti multimediali del ricevitore.

8. Overlay introduttivo

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

Illustrazione di un iPhone con l'app CastVideo a cui è sovrapposto il pulsante Trasmetti, che evidenzia il pulsante Trasmetti e mostra 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 agli utenti per la prima volta. 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: 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 con l'app CastVideo che riproduce un video con il controller espanso che appare in basso

Il controller espanso ha una visualizzazione a schermo intero che offre il controllo completo della riproduzione di 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 (connettersi/interrompere la 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 è abilitare 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 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 toccherà 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 libreria di Cast Connect consente alle applicazioni dei mittenti esistenti di comunicare con le applicazioni Android TV tramite il protocollo Cast. Cast Connect si basa sull'infrastruttura di Google Cast e la tua app Android TV funge da ricevitore.

Dipendenze

In Podfile, assicurati che google-cast-sdk punti a 4.4.8 o a un livello superiore come elencato di seguito. Se hai apportato una modifica al file, esegui pod update dalla console per sincronizzare la modifica con il 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 destinatario viene avviato e viene trasmesso agli elementi GCKCastOptions impostati 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 del mittente, puoi specificare GCKCredentialsData per rappresentare chi partecipa alla sessione. credentials è una stringa che può essere definita dall'utente, a condizione che l'app ATV sia in grado di comprenderla. Il GCKCredentialsData viene trasmesso all'app Android TV soltanto durante l'ora di avvio o di iscrizione. Se lo imposti di nuovo mentre il dispositivo è connesso, non verrà trasmesso all'app Android TV.

Per impostare le credenziali di lancio, GCKCredentialsData deve essere definito in qualsiasi momento dopo l'impostazione di GCKLaunchOptions. Per dimostrare ciò, 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 carico

Per gestire credentials nelle 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 destinatario a cui il mittente trasmette, l'SDK applica automaticamente le credenziali indicate sopra alla sessione in corso.

Test di Cast Connect

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

  1. Trova l'indirizzo IP del tuo dispositivo Android TV. Di solito questa opzione è disponibile in Impostazioni > Rete e internet > (Nome della rete a cui è connesso il dispositivo). Sulla destra sono visualizzati i dettagli e l'IP del dispositivo sulla rete.
  2. Usa l'indirizzo IP del dispositivo per collegarti tramite ADB utilizzando il terminale:
$ adb connect <device_ip_address>:5555
  1. Nella finestra del terminale, vai alla cartella di primo livello per trovare 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:
$ 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 un dispositivo mobile. Una volta stabilita una sessione di trasmissione con il dispositivo Android TV, il dispositivo dovrebbe avviare l'applicazione Android Ricevir sulla tua Android TV. La riproduzione di un video inviato da un mittente di dispositivo mobile iOS dovrebbe avviarlo nel ricevitore Android e consentirti di controllare la riproduzione utilizzando il telecomando del dispositivo Android TV.

11. Personalizza i widget di trasmissione

Inizializzazione

Inizia con la cartella App-Fine. 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 menzionato nel resto di questo codelab, esegui il commit degli stili chiamando il codice seguente

styler.apply()

Personalizzazione delle visualizzazioni di Google Cast

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

styler.castViews.iconTintColor = .lightGray

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

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

Colori che cambiano

Puoi personalizzare il colore di sfondo per tutte le viste (o singolarmente per ogni vista). Il codice seguente 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

Modifica dei caratteri

Puoi personalizzare i caratteri per etichette diverse presenti nelle visualizzazioni Cast. 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)

Modifica delle immagini dei pulsanti predefinite

Aggiungi le tue immagini personalizzate al progetto e assegnale ai pulsanti per applicare 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 creare un tema per i widget di trasmissione usando il protocollo UIAspetto. I seguenti temi di codice per GCKUICastButton in tutte le visualizzazioni che vengono mostrati:

GCKUICastButton.appearance().tintColor = UIColor.gray

12. Congratulazioni

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

Per maggiori dettagli, consulta la guida per gli sviluppatori iOS Sender (Mittente iOS).