Integra Google Cast nella tua app iOS

Questa guida per gli sviluppatori descrive come aggiungere il supporto di Google Cast alla tua app mittente iOS utilizzando l'SDK Mittente iOS.

Il dispositivo mobile o il laptop è il mittente che controlla la riproduzione, mentre il dispositivo Google Cast è il destinatario che mostra i contenuti sulla TV.

Il framework mittente si riferisce al programma binario della libreria di classe Cast e alle risorse associate presenti in fase di esecuzione sul mittente. L'app mittente o l'app Cast si riferisce a un'app in esecuzione anche sul mittente. L'app Ricevitore web si riferisce all'applicazione HTML in esecuzione sul Ricevitore web.

Il framework del mittente utilizza un design di callback asincrono per informare l'app del mittente degli eventi e per passare da uno stato all'altro del ciclo di vita dell'app Cast.

Flusso dell'app

I seguenti passaggi descrivono il flusso di esecuzione tipico di alto livello per un'app iOS per iOS:

  • Il framework di Cast avvia GCKDiscoveryManager in base alle proprietà fornite in GCKCastOptions per iniziare la scansione dei dispositivi.
  • Quando l'utente fa clic sul pulsante Trasmetti, il framework presenta la finestra di dialogo Trasmetti con l'elenco dei dispositivi di trasmissione rilevati.
  • Quando l'utente seleziona un dispositivo di trasmissione, il framework tenta di avviare l'app Ricevitore web sul dispositivo di trasmissione.
  • Il framework richiama i callback nell'app del mittente per confermare che l'app Ricevitore web sia stata avviata.
  • Il framework crea un canale di comunicazione tra le app del mittente e del ricevitore web.
  • Il framework utilizza il canale di comunicazione per caricare e controllare la riproduzione dei contenuti multimediali sul ricevitore web.
  • Il framework sincronizza lo stato di riproduzione dei contenuti multimediali tra il mittente e il ricevitore web: quando l'utente esegue azioni dell'interfaccia utente del mittente, il framework trasmette le richieste di controllo dei contenuti multimediali al ricevitore web e, quando il ricevitore invia aggiornamenti sullo stato dei contenuti multimediali, il framework aggiorna lo stato della UI del mittente.
  • Quando l'utente fa clic sul pulsante Trasmetti per disconnettersi dal dispositivo di trasmissione, il framework disconnette l'app del mittente dal ricevitore web.

Per risolvere i problemi del mittente, devi abilitare il logging.

Per un elenco completo di tutti i corsi, i metodi e gli eventi nel framework Google Cast per iOS, consulta il riferimento API di Google Cast per iOS. Le seguenti sezioni descrivono i passaggi per l'integrazione di Google Cast nella tua app per iOS.

Metodi di chiamata dal thread principale

Inizializzare il contesto di trasmissione

Il framework Cast ha un oggetto singleton globale, GCKCastContext, che coordina tutte le attività del framework. Questo oggetto deve essere inizializzato in anticipo nel 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'app del mittente possa attivarsi correttamente.

Quando si inizializza GCKCastContext, è necessario specificare un oggetto GCKCastOptions. Questa classe contiene opzioni che influiscono sul comportamento del framework. Il più importante è l'ID applicazione Ricevitore web, che viene utilizzato per filtrare i risultati del rilevamento e per lanciare l'app Ricevitore web all'avvio di una sessione di trasmissione.

Il metodo -[application:didFinishLaunchingWithOptions:] è anche un'ottima soluzione per configurare un delegato per la ricezione dei messaggi di logging dal framework. Questi possono essere utili per il debug e la risoluzione dei problemi.

Swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate {
  let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID
  let kDebugLoggingEnabled = true

  var window: UIWindow?

  func applicationDidFinishLaunching(_ application: UIApplication) {
    let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
    let options = GCKCastOptions(discoveryCriteria: criteria)
    GCKCastContext.setSharedInstanceWith(options)

    // Enable logger.
    GCKLogger.sharedInstance().delegate = self

    ...
  }

  // MARK: - GCKLoggerDelegate

  func logMessage(_ message: String,
                  at level: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if (kDebugLoggingEnabled) {
      print(function + " - " + message)
    }
  }
}
Obiettivo-C

Delegato

@interface AppDelegate () <GCKLoggerDelegate>
@end

Delegato

@implementation AppDelegate

static NSString *const kReceiverAppID = @"AABBCCDD";
static const BOOL kDebugLoggingEnabled = YES;

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                    initWithApplicationID:kReceiverAppID];
  GCKCastOptions *options = [[GCKCastOptions alloc] initWithDiscoveryCriteria:criteria];
  [GCKCastContext setSharedInstanceWithOptions:options];

  // Enable logger.
  [GCKLogger sharedInstance].delegate = self;

  ...

  return YES;
}

...

#pragma mark - GCKLoggerDelegate

- (void)logMessage:(NSString *)message
           atLevel:(GCKLoggerLevel)level
      fromFunction:(NSString *)function
          location:(NSString *)location {
  if (kDebugLoggingEnabled) {
    NSLog(@"%@ - %@, %@", function, message, location);
  }
}

@end

I widget Cast UX

L'SDK Cast per iOS fornisce i seguenti widget conformi all'elenco di controllo per la progettazione di Google Cast:

  • Overlay introduttivo: La classe GCKCastContext ha un metodo, presentCastInstructionsViewControllerOnceWithCastButton, che può essere utilizzato per mettere in evidenza il pulsante Trasmetti la prima volta che è disponibile un ricevitore web. L'app del mittente può personalizzare il testo, la posizione del testo del titolo e il pulsante Ignora.

  • Pulsante Trasmetti: a partire dall'SDK di Trasmetti per iOS 4.6.0, il pulsante Trasmetti è sempre visibile quando il dispositivo del mittente è connesso alla rete Wi-Fi. La prima volta che l'utente tocca il pulsante Trasmetti dopo aver avviato l'app, viene visualizzata una finestra di dialogo delle autorizzazioni per consentire all'utente di concedere all'app l'accesso alla rete locale ai dispositivi nella rete. Di conseguenza, quando l'utente tocca il pulsante Trasmetti, viene visualizzata una finestra di dialogo Trasmetti che elenca i dispositivi rilevati. Quando l'utente tocca il pulsante Trasmetti mentre il dispositivo è connesso, vengono visualizzati i metadati multimediali correnti (ad esempio il titolo, il nome dello studio di registrazione e un'immagine in miniatura) oppure consente all'utente di disconnettersi dal dispositivo di trasmissione. Quando l'utente tocca il pulsante Trasmetti quando non sono disponibili dispositivi, viene visualizzata una schermata che fornisce informazioni sull'utente e sui motivi per cui i dispositivi non sono stati trovati.

  • Mini controller: quando l'utente trasmette contenuti ed è uscito dalla pagina dei contenuti corrente o dal controller espanso a un'altra schermata dell'app del mittente, il mini controller viene visualizzato nella parte inferiore dello schermo per consentire all'utente di vedere i metadati multimediali attualmente trasmessi e di controllare la riproduzione.

  • Controller espanso. Quando l'utente trasmette contenuti, se fa clic sulla notifica multimediale o sul mini controller, viene avviato il controller espanso, che mostra i metadati multimediali attualmente in riproduzione e fornisce diversi pulsanti per controllare la riproduzione dei contenuti multimediali.

Aggiungere un pulsante Trasmetti

Il framework fornisce un componente Pulsante Trasmetti come sottoclasse UIButton. Può essere aggiunto alla barra del titolo dell'app inserendolo in un UIBarButtonItem. Una tipica sottoclasse UIViewController può installare un pulsante Trasmetti nel seguente modo:

Swift
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
castButton.tintColor = UIColor.gray
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
Obiettivo-C
GCKUICastButton *castButton = [[GCKUICastButton alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];
castButton.tintColor = [UIColor grayColor];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:castButton];

Per impostazione predefinita, tocca il pulsante per aprire la finestra di dialogo Trasmetti fornita dal frame.

GCKUICastButton può anche essere aggiunto direttamente allo storyboard.

Configurare il rilevamento dei dispositivi

Nel framework, il rilevamento dei dispositivi avviene automaticamente. Non è necessario avviare o interrompere esplicitamente il processo di rilevamento a meno che non implementi un'interfaccia utente personalizzata.

La scoperta nel framework è gestita dalla classe GCKDiscoveryManager, che è una proprietà di GCKCastContext. Il framework fornisce un componente predefinito della finestra di dialogo Trasmetti per la selezione e il controllo dei dispositivi. L'elenco dei dispositivi è ordinato in base al nome del dispositivo.

Come funziona la gestione delle sessioni

L'SDK Cast introduce il concetto di sessione di trasmissione, la cui combinazione combina i passaggi per connettersi a un dispositivo, lanciare (o partecipare) a un'app web Ricevitore, connettersi a quell'app e inizializzare un canale di controllo multimediale. Consulta la Guida al ciclo di vita dell'applicazione per il ricevitore web per ulteriori informazioni sulle sessioni di trasmissione e sul ciclo di vita del ricevitore web.

Le sessioni sono gestite dalla classe GCKSessionManager, che è una proprietà di GCKCastContext. Le singole sessioni sono rappresentate dalle sottoclassi della classe GCKSession: ad esempio, GCKCastSession rappresenta le sessioni con dispositivi di trasmissione. Puoi accedere alla sessione di trasmissione attualmente attiva (se presente) come proprietà currentCastSession di GCKSessionManager.

L'interfaccia di GCKSessionManagerListener può essere utilizzata per monitorare gli eventi relativi alle sessioni, come la creazione, la sospensione, la ripresa e l'interruzione della sessione. Il framework sospende automaticamente le sessioni quando l'app del mittente è in background e tenta di riprenderle quando l'app torna in primo piano (o viene riavviata dopo un'interruzione anomala o improvvisa dell'app mentre era attiva).

Se viene utilizzata la finestra di dialogo Trasmetti, le sessioni vengono create e eliminate automaticamente in risposta ai gesti dell'utente. In caso contrario, l'app può avviare e terminare le sessioni in modo esplicito tramite metodi GCKSessionManager.

Se l'app deve eseguire un'elaborazione speciale in risposta agli eventi del ciclo di vita delle sessioni, può registrare una o più istanze GCKSessionManagerListener con GCKSessionManager. GCKSessionManagerListener è un protocollo che definisce i callback per eventi come inizio sessione, fine sessione e così via.

Trasferimento dello streaming

Conservare lo stato della sessione è la base del trasferimento dello stream, in cui gli utenti possono spostare stream audio e video esistenti su più dispositivi utilizzando i comandi vocali, l'app Google Home o gli smart display. La riproduzione dei contenuti multimediali viene interrotta su un dispositivo (l'origine) e continua su un altro (la destinazione). Tutti i dispositivi di trasmissione con il firmware più recente possono essere utilizzati come origini o destinazioni in un trasferimento di streaming.

Per acquistare il nuovo dispositivo di destinazione durante il trasferimento dello stream, utilizza la proprietà GCKCastSession#device durante il callback [sessionManager:didResumeCastSession:].

Per ulteriori informazioni, consulta la pagina Trasferimento dello streaming sul ricevitore web.

Riconnessione automatica

Il framework Cast aggiunge una logica di riconnessione per gestire automaticamente la riconnessione in molti casi d'angolo discreti, ad esempio:

  • Esegui il ripristino da una perdita temporanea della rete Wi-Fi
  • Ripristina dal sonno dispositivo
  • Esegui il ripristino dall'app in background
  • Ripristino in caso di arresto anomalo dell'app

Come funziona il controllo dei contenuti multimediali

Se viene stabilita una sessione di trasmissione con un'app ricevitore web che supporta lo spazio dei nomi dei contenuti multimediali, il framework creerà automaticamente un'istanza di GCKRemoteMediaClient, a cui è possibile accedere come proprietà remoteMediaClient dell'istanza di GCKCastSession.

Tutti i metodi su GCKRemoteMediaClient che inviano richieste al Ricevitore web restituiscono un oggetto GCKRequest che può essere utilizzato per monitorare tale richiesta. Puoi assegnare un oggetto GCKRequestDelegate a questo oggetto per ricevere notifiche sul risultato finale dell'operazione.

Si prevede che l'istanza di GCKRemoteMediaClient possa essere condivisa da più parti dell'app e infatti alcuni componenti interni del framework, come i dialoghi di trasmissione e i controlli mini-media, condividono l'istanza. A questo scopo, GCKRemoteMediaClient supporta la registrazione di più GCKRemoteMediaClientListener.

Imposta metadati multimediali

Il corso GCKMediaMetadata rappresenta le informazioni su un elemento multimediale che vuoi trasmettere. L'esempio seguente crea una nuova istanza GCKMediaMetadata di un film e imposta il titolo, il sottotitolo, il nome dello studio di registrazione e due immagini.

Swift
let metadata = GCKMediaMetadata()
metadata.setString("Big Buck Bunny (2008)", forKey: kGCKMetadataKeyTitle)
metadata.setString("Big Buck Bunny tells the story of a giant rabbit with a heart bigger than " +
  "himself. When one sunny day three rodents rudely harass him, something " +
  "snaps... and the rabbit ain't no bunny anymore! In the typical cartoon " +
  "tradition he prepares the nasty rodents a comical revenge.",
                   forKey: kGCKMetadataKeySubtitle)
metadata.addImage(GCKImage(url: URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg")!,
                           width: 480,
                           height: 360))
Obiettivo-C
GCKMediaMetadata *metadata = [[GCKMediaMetadata alloc]
                                initWithMetadataType:GCKMediaMetadataTypeMovie];
[metadata setString:@"Big Buck Bunny (2008)" forKey:kGCKMetadataKeyTitle];
[metadata setString:@"Big Buck Bunny tells the story of a giant rabbit with a heart bigger than "
 "himself. When one sunny day three rodents rudely harass him, something "
 "snaps... and the rabbit ain't no bunny anymore! In the typical cartoon "
 "tradition he prepares the nasty rodents a comical revenge."
             forKey:kGCKMetadataKeySubtitle];
[metadata addImage:[[GCKImage alloc]
                    initWithURL:[[NSURL alloc] initWithString:@"https://commondatastorage.googleapis.com/"
                                 "gtv-videos-bucket/sample/images/BigBuckBunny.jpg"]
                    width:480
                    height:360]];

Consulta la sezione Selezione e memorizzazione delle immagini nella cache sull'utilizzo delle immagini con metadati multimediali.

Carica contenuti multimediali

Per caricare un elemento multimediale, crea un'istanza GCKMediaInformation utilizzando i metadati dei media. Scarica la versione attuale di GCKCastSession e utilizzala GCKRemoteMediaClient per caricare i contenuti multimediali nell'app del ricevitore. Puoi quindi utilizzare GCKRemoteMediaClient per controllare l'app di un lettore multimediale in esecuzione sul ricevitore, ad esempio per riprodurre, mettere in pausa e interrompere.

Swift
let url = URL.init(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
guard let mediaURL = url else {
  print("invalid mediaURL")
  return
}

let mediaInfoBuilder = GCKMediaInformationBuilder.init(contentURL: mediaURL)
mediaInfoBuilder.streamType = GCKMediaStreamType.none;
mediaInfoBuilder.contentType = "video/mp4"
mediaInfoBuilder.metadata = metadata;
mediaInformation = mediaInfoBuilder.build()

guard let mediaInfo = mediaInformation else {
  print("invalid mediaInformation")
  return
}

if let request = sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInfo) {
  request.delegate = self
}
Obiettivo-C
GCKMediaInformationBuilder *mediaInfoBuilder =
  [[GCKMediaInformationBuilder alloc] initWithContentURL:
   [NSURL URLWithString:@"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"]];
mediaInfoBuilder.streamType = GCKMediaStreamTypeNone;
mediaInfoBuilder.contentType = @"video/mp4";
mediaInfoBuilder.metadata = metadata;
self.mediaInformation = [mediaInfoBuilder build];

GCKRequest *request = [self.sessionManager.currentSession.remoteMediaClient loadMedia:self.mediaInformation];
if (request != nil) {
  request.delegate = self;
}

Consulta anche la sezione sull'utilizzo di tracce multimediali.

Formato video 4K

Per determinare il formato video dei tuoi contenuti multimediali, usa la proprietà videoInfo di GCKMediaStatus per ottenere l'istanza attuale di GCKVideoInfo. Questa istanza contiene il tipo di formato TV HDR e l'altezza e la larghezza in pixel. Le varianti del formato 4K sono indicate nella proprietà hdrType con valori enumerati GCKVideoInfoHDRType.

Aggiungi mini controller

In base all'elenco di controllo per la progettazione Cast, un'app del mittente dovrebbe fornire un controllo permanente noto come mini controller che dovrebbe essere visualizzato quando l'utente esce dalla pagina dei contenuti correnti. Il mini controller fornisce l'accesso istantaneo e un promemoria visibile per la sessione di trasmissione corrente.

Il framework Google Cast fornisce una barra di controllo, GCKUIMiniMediaControlsViewController, che può essere aggiunta alle scene in cui vuoi mostrare il mini controller.

Durante la riproduzione di un live streaming di video o audio da parte dell'app del mittente, l'SDK mostra automaticamente un pulsante di riproduzione/interruzione al posto del pulsante di riproduzione/pausa nel mini controller.

Visita la pagina Personalizzare l'interfaccia utente di Mittente iOS per scoprire in che modo l'app mittente può configurare l'aspetto dei widget Cast.

Esistono due modi per aggiungere il mini controller a un'app mittente:

  • Consenti al framework di trasmissione di gestire il layout del mini controller associando il controller di vista esistente al proprio controller.
  • Gestisci il layout del widget del mini controller aggiungendolo al controller vista esistente fornendo una visualizzazione secondaria nello storyboard.

Aggrega con GCKUICastContainerViewController

Il primo modo è utilizzare GCKUICastContainerViewController che aggrega un altro controller di visualizzazione e aggiunge un GCKUIMiniMediaControlsViewController in fondo. Questo approccio è limitato poiché non puoi personalizzare l'animazione e configurare il comportamento del controller di visualizzazione container.

In primo luogo, questa operazione viene eseguita in genere nel metodo -[application:didFinishLaunchingWithOptions:] del delegato all'app:

Swift
func applicationDidFinishLaunching(_ application: UIApplication) {
  ...

  // Wrap main view in the GCKUICastContainerViewController and display the mini controller.
  let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
  let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
  let castContainerVC =
          GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
  castContainerVC.miniMediaControlsItemEnabled = true
  window = UIWindow(frame: UIScreen.main.bounds)
  window!.rootViewController = castContainerVC
  window!.makeKeyAndVisible()

  ...
}
Obiettivo-C
- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  // Wrap main view in the GCKUICastContainerViewController and display the mini controller.
  UIStoryboard *appStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
  UINavigationController *navigationController =
          [appStoryboard instantiateViewControllerWithIdentifier:@"MainNavigation"];
  GCKUICastContainerViewController *castContainerVC =
          [[GCKCastContext sharedInstance] createCastContainerControllerForViewController:navigationController];
  castContainerVC.miniMediaControlsItemEnabled = YES;
  self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
  self.window.rootViewController = castContainerVC;
  [self.window makeKeyAndVisible];
  ...

}
Swift
var castControlBarsEnabled: Bool {
  set(enabled) {
    if let castContainerVC = self.window?.rootViewController as? GCKUICastContainerViewController {
      castContainerVC.miniMediaControlsItemEnabled = enabled
    } else {
      print("GCKUICastContainerViewController is not correctly configured")
    }
  }
  get {
    if let castContainerVC = self.window?.rootViewController as? GCKUICastContainerViewController {
      return castContainerVC.miniMediaControlsItemEnabled
    } else {
      print("GCKUICastContainerViewController is not correctly configured")
      return false
    }
  }
}
Obiettivo-C

Delegato

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, assign) BOOL castControlBarsEnabled;

@end

Delegato

@implementation AppDelegate

...

- (void)setCastControlBarsEnabled:(BOOL)notificationsEnabled {
  GCKUICastContainerViewController *castContainerVC;
  castContainerVC =
      (GCKUICastContainerViewController *)self.window.rootViewController;
  castContainerVC.miniMediaControlsItemEnabled = notificationsEnabled;
}

- (BOOL)castControlBarsEnabled {
  GCKUICastContainerViewController *castContainerVC;
  castContainerVC =
      (GCKUICastContainerViewController *)self.window.rootViewController;
  return castContainerVC.miniMediaControlsItemEnabled;
}

...

@end

Incorpora nel controller della vista esistente

Il secondo modo è aggiungere il mini controller direttamente al controller vista esistente utilizzando createMiniMediaControlsViewController per creare un'istanza GCKUIMiniMediaControlsViewController e poi aggiungerla al controller container come vista secondaria.

Configura il controller della vista nel delegato dell'app:

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
  window?.clipsToBounds = true

  let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
  rootContainerVC?.miniMediaControlsViewEnabled = true

  ...

  return true
}
Obiettivo-C
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  self.window.clipsToBounds = YES;

  RootContainerViewController *rootContainerVC;
  rootContainerVC =
      (RootContainerViewController *)self.window.rootViewController;
  rootContainerVC.miniMediaControlsViewEnabled = YES;

  ...

  return YES;
}

Nel controller della visualizzazione radice, crea un'istanza GCKUIMiniMediaControlsViewController e aggiungila al controller della visualizzazione container come vista secondaria:

Swift
let kCastControlBarsAnimationDuration: TimeInterval = 0.20

@objc(RootContainerViewController)
class RootContainerViewController: UIViewController, GCKUIMiniMediaControlsViewControllerDelegate {
  @IBOutlet weak private var _miniMediaControlsContainerView: UIView!
  @IBOutlet weak private var _miniMediaControlsHeightConstraint: NSLayoutConstraint!
  private var miniMediaControlsViewController: GCKUIMiniMediaControlsViewController!
  var miniMediaControlsViewEnabled = false {
    didSet {
      if self.isViewLoaded {
        self.updateControlBarsVisibility()
      }
    }
  }

  var overriddenNavigationController: UINavigationController?

  override var navigationController: UINavigationController? {

    get {
      return overriddenNavigationController
    }

    set {
      overriddenNavigationController = newValue
    }
  }
  var miniMediaControlsItemEnabled = false

  override func viewDidLoad() {
    super.viewDidLoad()
    let castContext = GCKCastContext.sharedInstance()
    self.miniMediaControlsViewController = castContext.createMiniMediaControlsViewController()
    self.miniMediaControlsViewController.delegate = self
    self.updateControlBarsVisibility()
    self.installViewController(self.miniMediaControlsViewController,
                               inContainerView: self._miniMediaControlsContainerView)
  }

  func updateControlBarsVisibility() {
    if self.miniMediaControlsViewEnabled && self.miniMediaControlsViewController.active {
      self._miniMediaControlsHeightConstraint.constant = self.miniMediaControlsViewController.minHeight
      self.view.bringSubview(toFront: self._miniMediaControlsContainerView)
    } else {
      self._miniMediaControlsHeightConstraint.constant = 0
    }
    UIView.animate(withDuration: kCastControlBarsAnimationDuration, animations: {() -> Void in
      self.view.layoutIfNeeded()
    })
    self.view.setNeedsLayout()
  }

  func installViewController(_ viewController: UIViewController?, inContainerView containerView: UIView) {
    if let viewController = viewController {
      self.addChildViewController(viewController)
      viewController.view.frame = containerView.bounds
      containerView.addSubview(viewController.view)
      viewController.didMove(toParentViewController: self)
    }
  }

  func uninstallViewController(_ viewController: UIViewController) {
    viewController.willMove(toParentViewController: nil)
    viewController.view.removeFromSuperview()
    viewController.removeFromParentViewController()
  }

  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "NavigationVCEmbedSegue" {
      self.navigationController = (segue.destination as? UINavigationController)
    }
  }

...
Obiettivo-C

RootContainerViewController.h

static const NSTimeInterval kCastControlBarsAnimationDuration = 0.20;

@interface RootContainerViewController () <GCKUIMiniMediaControlsViewControllerDelegate> {
  __weak IBOutlet UIView *_miniMediaControlsContainerView;
  __weak IBOutlet NSLayoutConstraint *_miniMediaControlsHeightConstraint;
  GCKUIMiniMediaControlsViewController *_miniMediaControlsViewController;
}

@property(nonatomic, weak, readwrite) UINavigationController *navigationController;

@property(nonatomic, assign, readwrite) BOOL miniMediaControlsViewEnabled;
@property(nonatomic, assign, readwrite) BOOL miniMediaControlsItemEnabled;

@end

RootContainerViewController.m

@implementation RootContainerViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  GCKCastContext *castContext = [GCKCastContext sharedInstance];
  _miniMediaControlsViewController =
      [castContext createMiniMediaControlsViewController];
  _miniMediaControlsViewController.delegate = self;

  [self updateControlBarsVisibility];
  [self installViewController:_miniMediaControlsViewController
              inContainerView:_miniMediaControlsContainerView];
}

- (void)setMiniMediaControlsViewEnabled:(BOOL)miniMediaControlsViewEnabled {
  _miniMediaControlsViewEnabled = miniMediaControlsViewEnabled;
  if (self.isViewLoaded) {
    [self updateControlBarsVisibility];
  }
}

- (void)updateControlBarsVisibility {
  if (self.miniMediaControlsViewEnabled &&
      _miniMediaControlsViewController.active) {
    _miniMediaControlsHeightConstraint.constant =
        _miniMediaControlsViewController.minHeight;
    [self.view bringSubviewToFront:_miniMediaControlsContainerView];
  } else {
    _miniMediaControlsHeightConstraint.constant = 0;
  }
  [UIView animateWithDuration:kCastControlBarsAnimationDuration
                   animations:^{
                     [self.view layoutIfNeeded];
                   }];
  [self.view setNeedsLayout];
}

- (void)installViewController:(UIViewController *)viewController
              inContainerView:(UIView *)containerView {
  if (viewController) {
    [self addChildViewController:viewController];
    viewController.view.frame = containerView.bounds;
    [containerView addSubview:viewController.view];
    [viewController didMoveToParentViewController:self];
  }
}

- (void)uninstallViewController:(UIViewController *)viewController {
  [viewController willMoveToParentViewController:nil];
  [viewController.view removeFromSuperview];
  [viewController removeFromParentViewController];
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  if ([segue.identifier isEqualToString:@"NavigationVCEmbedSegue"]) {
    self.navigationController =
        (UINavigationController *)segue.destinationViewController;
  }
}

...

@end

GCKUIMiniMediaControlsViewControllerDelegate indica al controller visualizzazione host quando deve essere visibile:

Swift
  func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController,
                                       shouldAppear _: Bool) {
    updateControlBarsVisibility()
  }
Obiettivo-C
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

Aggiungi controller espanso

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

Il controller espanso è una visualizzazione a schermo intero che offre il controllo completo della riproduzione multimediale 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 e della durata della sessione del ricevitore web (connessione/interruzione della trasmissione). Fornisce inoltre tutte le informazioni sullo stato della sessione multimediale (opera d'arte, 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 il delegato dell'app per abilitare il controller espanso predefinito:

Swift
func applicationDidFinishLaunching(_ application: UIApplication) {
  ..

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

  ...
}
Obiettivo-C
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

Aggiungi il seguente codice al controller View per caricare il controller espanso quando l'utente inizia a trasmettere un video:

Swift
func playSelectedItemRemotely() {
  GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()

  ...

  // Load your media
  sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInformation)
}
Obiettivo-C
- (void)playSelectedItemRemotely {
  [[GCKCastContext sharedInstance] presentDefaultExpandedMediaControls];

  ...

  // Load your media
  [self.sessionManager.currentSession.remoteMediaClient loadMedia:mediaInformation];
}

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

Quando l'app del mittente riproduce un live streaming di un video o audio, l'SDK visualizza automaticamente un pulsante di riproduzione/interruzione al posto del pulsante di riproduzione/pausa nel controller espanso.

Consulta la sezione Applicare stili personalizzati all'app per iOS per scoprire come l'app del mittente può configurare l'aspetto dei widget Cast.

Controllo del volume

Il framework di trasmissione gestisce automaticamente il volume per l'app del mittente. Il framework si sincronizza automaticamente con il volume del ricevitore web per i widget dell'interfaccia utente forniti. Per sincronizzare un dispositivo di scorrimento fornito dall'app, utilizza GCKUIDeviceVolumeController.

Controllo fisico del pulsante dei volumi

Puoi utilizzare i pulsanti fisici del volume sul dispositivo del mittente per regolare il volume della sessione di trasmissione sul ricevitore web utilizzando il flag physicalVolumeButtonsWillControlDeviceVolume in GCKCastOptions, che è impostato sul GCKCastContext.

Swift
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
Obiettivo-C
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                          initWithApplicationID:kReceiverAppID];
GCKCastOptions *options = [[GCKCastOptions alloc]
                                          initWithDiscoveryCriteria :criteria];
options.physicalVolumeButtonsWillControlDeviceVolume = YES;
[GCKCastContext setSharedInstanceWithOptions:options];

Gestire gli errori

È molto importante che le app dei mittenti gestiscano tutti i callback di errore e decidano la risposta migliore per ogni fase del ciclo di vita di trasmissione. L'app può mostrare all'utente le finestre di dialogo di errore oppure può decidere di terminare la sessione di trasmissione.

Logging

GCKLogger è un singolo utilizzato per il logging dal framework. Utilizza GCKLoggerDelegate per personalizzare la gestione dei messaggi di log.

Con GCKLogger, l'SDK produce output di logging sotto forma di messaggi di debug, errori e avvisi. Questi messaggi di log sono utili per il debug e sono utili per la risoluzione dei problemi e l'identificazione dei problemi. Per impostazione predefinita, l'output del log viene soppresso, ma se assegni un GCKLoggerDelegate, l'app del mittente può ricevere questi messaggi dall'SDK e registrarli nella console di sistema.

Swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate {
  let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID
  let kDebugLoggingEnabled = true

  var window: UIWindow?

  func applicationDidFinishLaunching(_ application: UIApplication) {
    ...

    // Enable logger.
    GCKLogger.sharedInstance().delegate = self

    ...
  }

  // MARK: - GCKLoggerDelegate

  func logMessage(_ message: String,
                  at level: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if (kDebugLoggingEnabled) {
      print(function + " - " + message)
    }
  }
}
Obiettivo-C

Delegato

@interface AppDelegate () <GCKLoggerDelegate>
@end

Delegato

@implementation AppDelegate

static NSString *const kReceiverAppID = @"AABBCCDD";
static const BOOL kDebugLoggingEnabled = YES;

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  // Enable logger.
  [GCKLogger sharedInstance].delegate = self;

  ...

  return YES;
}

...

#pragma mark - GCKLoggerDelegate

- (void)logMessage:(NSString *)message
           atLevel:(GCKLoggerLevel)level
      fromFunction:(NSString *)function
          location:(NSString *)location {
  if (kDebugLoggingEnabled) {
    NSLog(@"%@ - %@, %@", function, message, location);
  }
}

@end

Per attivare anche i messaggi di debug e dettagliati, aggiungi questa riga al codice dopo aver impostato il delegato (mostrato in precedenza):

Swift
let filter = GCKLoggerFilter.init()
filter.minimumLevel = GCKLoggerLevel.verbose
GCKLogger.sharedInstance().filter = filter
Obiettivo-C
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setMinimumLevel:GCKLoggerLevelVerbose];
[GCKLogger sharedInstance].filter = filter;

Puoi anche filtrare i messaggi di log prodotti da GCKLogger. Imposta il livello di logging minimo per classe, ad esempio:

Swift
let filter = GCKLoggerFilter.init()
filter.setLoggingLevel(GCKLoggerLevel.verbose, forClasses: ["GCKUICastButton",
                                                            "GCKUIImageCache",
                                                            "NSMutableDictionary"])
GCKLogger.sharedInstance().filter = filter
Obiettivo-C
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setLoggingLevel:GCKLoggerLevelVerbose
             forClasses:@[@"GCKUICastButton",
                          @"GCKUIImageCache",
                          @"NSMutableDictionary"
                          ]];
[GCKLogger sharedInstance].filter = filter;

I nomi delle classi possono essere valori letterali o pattern glob, ad esempio GCKUI\* e GCK\*Session.