Integra Trasmetti nella tua app per iOS

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

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

Il framework del mittente si riferisce al file binario della libreria di classi Cast e alle risorse associate presenti in fase di runtime sul mittente. L'app mittente o l'app Cast fa riferimento a un'app in esecuzione anche sul mittente. L'app Web Receiver si riferisce all'applicazione HTML in esecuzione sul Web Receiver.

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

Flusso dell'app

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

  • Il framework Cast avvia GCKDiscoveryManager in base alle proprietà fornite in GCKCastOptions per iniziare la ricerca di 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 Web Receiver sul dispositivo di trasmissione.
  • Il framework richiama i callback nell'app mittente per confermare che l'app Web Receiver è stata avviata.
  • Il framework crea un canale di comunicazione tra le app mittente e 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 nell'interfaccia utente del mittente, il framework trasmette queste richieste di controllo multimediale al ricevitore web e, quando il ricevitore web invia aggiornamenti dello stato dei contenuti multimediali, il framework aggiorna lo stato dell'interfaccia utente del mittente.
  • Quando l'utente fa clic sul pulsante Trasmetti per disconnettersi dal dispositivo di trasmissione, il framework disconnette l'app mittente dal ricevitore web.

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

Per un elenco completo di tutte le classi, i metodi e gli eventi nel framework Google Cast per iOS, consulta il Riferimento API Google Cast per iOS. Le sezioni seguenti descrivono i passaggi per integrare Cast nella tua app per iOS.

Chiama i metodi dal thread principale

Inizializza 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 all'inizio del ciclo di vita dell'applicazione, in genere nel metodo -[application:didFinishLaunchingWithOptions:] del delegato dell'app, in modo che il ripristino automatico della sessione al riavvio dell'app mittente possa attivarsi correttamente.

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

Il metodo -[application:didFinishLaunchingWithOptions:] è anche un buon punto per configurare un delegato di logging per ricevere i messaggi di logging dal framework. Queste informazioni 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)
    }
  }
}
Objective-C

AppDelegate.h

@interface AppDelegate () <GCKLoggerDelegate>
@end

AppDelegate.m

@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 UX di Cast

L'SDK Cast per iOS fornisce questi widget conformi alla checklist per il design di 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 Web Receiver. L'app mittente può personalizzare il testo, la posizione del testo del titolo e il pulsante Chiudi.

  • Pulsante Trasmetti: a partire dall'SDK per mittente per iOS di Cast 4.6.0, il pulsante Trasmetti è sempre visibile quando il dispositivo mittente è connesso al 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 che consente all'utente di concedere all'app l'accesso alla rete locale ai dispositivi sulla rete. Successivamente, quando l'utente tocca il pulsante Trasmetti, viene visualizzata una finestra di dialogo di trasmissione che elenca i dispositivi rilevati. Quando l'utente tocca il pulsante Trasmetti mentre il dispositivo è connesso, vengono visualizzati i metadati correnti dei contenuti multimediali (ad esempio titolo, nome dello studio di registrazione e un'immagine in miniatura) o l'utente può disconnettersi dal dispositivo di trasmissione. Quando l'utente tocca il pulsante Trasmetti mentre non sono disponibili dispositivi, viene visualizzata una schermata che fornisce informazioni sul motivo per cui i dispositivi non vengono trovati e su come risolvere i problemi.

  • Mini controller: quando l'utente trasmette contenuti e si è allontanato dalla pagina dei contenuti corrente o dal controller espanso per passare a un'altra schermata nell'app mittente, il mini controller viene visualizzato nella parte inferiore dello schermo per consentire all'utente di visualizzare i metadati dei contenuti 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 dei contenuti multimediali in riproduzione e fornisce diversi pulsanti per controllare la riproduzione dei contenuti multimediali.

Aggiungi un pulsante Trasmetti

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

Swift
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
castButton.tintColor = UIColor.gray
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
Objective-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, toccando il pulsante si apre la finestra di dialogo di trasmissione fornita dal framework.

GCKUICastButton possono anche essere aggiunti direttamente allo storyboard.

Configura 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 tu non implementi una UI personalizzata.

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

Come funziona la gestione delle sessioni

L'SDK Cast introduce il concetto di sessione di trasmissione, la cui creazione combina i passaggi di connessione a un dispositivo, avvio (o partecipazione) di un'app Web Receiver, connessione a quell'app e inizializzazione di un canale di controllo multimediale. Per saperne di più sulle sessioni di trasmissione e sul ciclo di vita del ricevitore web, consulta la guida al ciclo di vita delle applicazioni del ricevitore web.

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

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

Se viene utilizzata la finestra di dialogo di trasmissione, le sessioni vengono create e interrotte automaticamente in risposta alle attivazioni dall'utente. In caso contrario, l'app può avviare e terminare le sessioni in modo esplicito tramite i metodi su GCKSessionManager.

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

Trasferimento dello streaming

La conservazione dello stato della sessione è alla base del trasferimento dello streaming, che consente agli utenti di spostare gli stream audio e video esistenti tra i dispositivi utilizzando i comandi vocali, l'app Google Home o gli smart display. La riproduzione dei contenuti multimediali si interrompe su un dispositivo (la sorgente) e continua su un altro (la destinazione). Qualsiasi dispositivo di trasmissione con il firmware più recente può fungere da origine o destinazione in un trasferimento dello streaming.

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

Per saperne di più, consulta Trasferimento dello streaming su Web Receiver.

Riconnessione automatica

Il framework Cast aggiunge la logica di riconnessione per gestire automaticamente la riconnessione in molti casi limite, ad esempio:

  • Eseguire il ripristino in caso di perdita temporanea del Wi-Fi
  • Ripristino dalla modalità di sospensione del dispositivo
  • Ripristino dall'esecuzione in background dell'app
  • Eseguire il ripristino in caso di arresto anomalo dell'app

Come funzionano i controlli multimediali

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

Tutti i metodi su GCKRemoteMediaClient che inviano richieste al ricevitore web restituiranno un oggetto GCKRequest che può essere utilizzato per monitorare la richiesta. A questo oggetto può essere assegnato un GCKRequestDelegate per ricevere notifiche sull'eventuale risultato dell'operazione.

Si prevede che l'istanza di GCKRemoteMediaClient possa essere condivisa da più parti dell'app e, di fatto, alcuni componenti interni del framework, come la finestra di dialogo di trasmissione e i controlli multimediali ridotti, condividono l'istanza. A questo scopo, GCKRemoteMediaClient supporta la registrazione di più GCKRemoteMediaClientListener.

Imposta i metadati dei contenuti multimediali

La classe 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))
Objective-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 nella cache delle immagini sull'utilizzo delle immagini con i metadati dei contenuti multimediali.

Carica contenuti multimediali

Per caricare un elemento multimediale, crea un'istanza GCKMediaInformation utilizzando i metadati dell'elemento multimediale. Poi, recupera l'oggetto GCKCastSession corrente e utilizza il relativo GCKRemoteMediaClient per caricare i contenuti multimediali nell'app ricevitore. Puoi quindi utilizzare GCKRemoteMediaClient per controllare un'app di media player 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
}
Objective-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 delle tracce multimediali.

Formato video 4K

Per determinare il formato video dei tuoi contenuti multimediali, utilizza la proprietà videoInfo di GCKMediaStatus per ottenere l'istanza corrente 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 dai valori enum GCKVideoInfoHDRType.

Aggiungi mini controller

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

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

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

Consulta Personalizza l'interfaccia utente del mittente iOS per scoprire come la tua app mittente può configurare l'aspetto dei widget di trasmissione.

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

  • Consenti al framework Cast di gestire il layout del mini controller eseguendo il wrapping del tuo view controller esistente con il proprio view controller.
  • Gestisci autonomamente il layout del widget del mini controller aggiungendolo al tuo view controller esistente fornendo una visualizzazione secondaria nello storyboard.

Aggrega utilizzando GCKUICastContainerViewController

Il primo modo è utilizzare GCKUICastContainerViewController, che racchiude un altro view controller e aggiunge un GCKUIMiniMediaControlsViewController in basso. Questo approccio è limitato in quanto non puoi personalizzare l'animazione e non puoi configurare il comportamento del view controller del contenitore.

Questo primo metodo viene in genere eseguito nel metodo -[application:didFinishLaunchingWithOptions:] del delegato dell'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()

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

AppDelegate.h

@interface AppDelegate : UIResponder <UIApplicationDelegate>

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

@end

AppDelegate.m

@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 view controller esistente

Il secondo modo consiste nell'aggiungere il mini controller direttamente al controller della visualizzazione esistente utilizzando createMiniMediaControlsViewController per creare un'istanza GCKUIMiniMediaControlsViewController e poi aggiungerla al controller della visualizzazione del container come visualizzazione secondaria.

Configura il view controller 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
}
Objective-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 view controller principale, crea un'istanza GCKUIMiniMediaControlsViewController e aggiungila al view controller del container come sottoview:

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

...
Objective-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 della visualizzazione host quando il mini controller deve essere visibile:

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

Aggiungi controller espanso

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

Il controller espanso è 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 web e del ciclo di vita della sessione (connessione/interruzione della trasmissione). Fornisce inoltre tutte le informazioni sullo stato della sessione multimediale (artwork, titolo, sottotitolo e così via).

La funzionalità di questa 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 attivare il controller espanso predefinito:

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

Aggiungi il seguente codice al tuo view controller 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)
}
Objective-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 mittente riproduce un live streaming video o audio, l'SDK visualizza automaticamente un pulsante di riproduzione/interruzione al posto del pulsante di riproduzione/pausa nel controller espanso.

Consulta Applica stili personalizzati alla tua app per iOS per scoprire come l'app mittente può configurare l'aspetto dei widget di Cast.

Controllo del volume

Il framework Cast gestisce automaticamente il volume per l'app mittente. Il framework si sincronizza automaticamente con il volume del Web Receiver per i widget UI forniti. Per sincronizzare un cursore fornito dall'app, utilizza GCKUIDeviceVolumeController.

Controllo del volume tramite pulsante fisico

I tasti del volume fisici sul dispositivo mittente possono essere utilizzati per modificare il volume della sessione di trasmissione sul ricevitore web utilizzando il flag physicalVolumeButtonsWillControlDeviceVolume su GCKCastOptions, impostato su GCKCastContext.

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

Gestisci gli errori

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

Tieni presente che alcuni errori, tra cui GCKErrorCode GCKErrorCodeCancelled, sono comportamenti previsti.

Non tentare di riprovare una connessione non riuscita con GCKErrorCodeCancelled. Ciò potrebbe portare al verificarsi di comportamenti imprevisti.

Logging

GCKLogger è un singleton utilizzato per il logging dal framework. Utilizza il GCKLoggerDelegate per personalizzare il modo in cui gestisci i messaggi di log.

Utilizzando GCKLogger, l'SDK produce un output di logging sotto forma di messaggi di debug, errori e avvisi. Questi messaggi di log facilitano il debug e sono utili per la risoluzione dei problemi e l'identificazione delle problematiche. Per impostazione predefinita, l'output del log viene soppresso, ma assegnando un GCKLoggerDelegate, l'app 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)
    }
  }
}
Objective-C

AppDelegate.h

@interface AppDelegate () <GCKLoggerDelegate>
@end

AppDelegate.m

@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
Objective-C
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setMinimumLevel:GCKLoggerLevelVerbose];
[GCKLogger sharedInstance].filter = filter;

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

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

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