Questa guida per gli sviluppatori descrive come aggiungere il supporto di Google Cast all'app di invio per iOS utilizzando l'SDK di invio per iOS.
Il dispositivo mobile o il laptop è il mittente che controlla la riproduzione e il dispositivo Google Cast è il ricevente 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 esecuzione sul mittente. L'app mittente o l'app di trasmissione si riferisce a un'app in esecuzione anche sul mittente. L'app Web Receiver si riferisce all'applicazione HTML in esecuzione sul ricevitore web.
Il framework di invio utilizza un design di callback asincrono per informare l'app di invio degli eventi e per passare da uno stato all'altro del ciclo di vita dell'app di trasmissione.
Flusso di app
I passaggi che seguono descrivono il tipico flusso di esecuzione di alto livello per un'app iOS del mittente:
- Il framework di trasmissione inizia
GCKDiscoveryManager
in base alle proprietà fornite inGCKCastOptions
per iniziare la ricerca di dispositivi. - Quando l'utente fa clic sul pulsante Trasmetti, il framework mostra 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.
- 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 di invio e di ricezione 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 Web Receiver: quando l'utente esegue azioni nell'interfaccia utente del mittente, il framework passa queste richieste di controllo dei contenuti multimediali al Web Receiver e quando il Web Receiver 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 scollega l'app mittente dal ricevitore web.
Per risolvere i problemi relativi al 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 la documentazione di riferimento dell'API Google Cast per iOS. Le sezioni seguenti descrivono i passaggi per integrare la funzionalità di trasmissione 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
la ripresa automatica della sessione al riavvio dell'app mittente possa essere attivata correttamente.
Un oggetto GCKCastOptions
deve essere fornito 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 di ricerca e per avviare l'app del ricevitore web quando viene avviata una sessione di trasmissione.
Il metodo -[application:didFinishLaunchingWithOptions:]
è anche un buon punto di partenza per configurare un delegato di logging per ricevere i messaggi di logging dal framework.
Questi dati possono essere utili per il debug e la risoluzione dei problemi.
@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) } } }
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
Widget dell'esperienza utente di Trasmissione
L'SDK Cast per iOS fornisce i seguenti widget conformi al controllo elenco di progettazione 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 ricevitore web. L'app mittente può personalizzare il testo, la posizione del testo del titolo e il pulsante Escludi.Pulsante di trasmissione: a partire dall'SDK di trasmissione di Cast per iOS 4.6.0, il pulsante di trasmissione è sempre visibile quando il dispositivo di invio è connesso al Wi-Fi. La prima volta che l'utente tocca il pulsante Trasmetti dopo aver avviato l'app per la prima volta, viene visualizzata una finestra di dialogo delle autorizzazioni per consentire all'app di accedere alla rete locale dei dispositivi sulla rete. Successivamente, quando l'utente tocca il pulsante di trasmissione, viene visualizzata una finestra di dialogo di trasmissione che elenca i dispositivi rilevati. Quando l'utente tocca il pulsante di trasmissione 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) o l'utente può disconnettersi dal dispositivo di trasmissione. Quando l'utente preme il pulsante di trasmissione e non sono disponibili dispositivi, viene visualizzata una schermata che fornisce informazioni sul motivo per cui i dispositivi non sono stati trovati e su come risolvere il problema.
Mini controller: quando l'utente trasmette contenuti ed esce dalla pagina di contenuti corrente o dal controller espanso per passare a un'altra schermata nell'app di invio, il mini controller viene visualizzato nella parte inferiore dello schermo per consentire all'utente di vedere i metadati dei contenuti in riproduzione e controllare la riproduzione.
Controllo espanso: quando l'utente trasmette contenuti, se fa clic sulla notifica multimediale o sul mini controllo, viene avviato il controllo espanso, che mostra i metadati dei contenuti multimediali in riproduzione e offre diversi pulsanti per controllarne la riproduzione.
Aggiungere un pulsante Trasmetti
Il framework fornisce un componente Pulsante di trasmissione come sottoclasse di UIButton
. Può essere aggiunto alla barra del titolo dell'app inserendo un UIBarButtonItem
. Una tipica sottoclasseUIViewController
può installare un pulsante di trasmissione come segue:
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24)) castButton.tintColor = UIColor.gray navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
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 Trasmetti fornita dal framework.
GCKUICastButton
possono essere aggiunti anche direttamente allo storyboard.
Configura il rilevamento dei dispositivi
Nel framework, il rilevamento dei dispositivi avviene automaticamente. Non è necessario avviare o interrompere esplicitamente la procedura 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 della finestra di dialogo di trasmissione predefinito per la selezione e il controllo dei dispositivi. L'elenco dei dispositivi è ordinato in ordine alfabetico in base al nome visualizzato del dispositivo.
Come funziona la gestione delle sessioni
L'SDK Cast introduce il concetto di sessione di trasmissione, la cui impostazione combina i passaggi di connessione a un dispositivo, di lancio (o adesione) a un'app Web Receiver, di connessione a quell'app e di inizializzazione di un canale di controllo dei contenuti multimediali. Per ulteriori informazioni sulle sessioni di trasmissione e sul ciclo di vita del ricevitore web, consulta la guida al ciclo di vita dell'applicazione 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 di trasmissione. 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 di 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 l'interruzione anomala/abrupta dell'app mentre una sessione era attiva).
Se viene utilizzata la finestra di dialogo Trasmetti, le sessioni vengono create e chiuse automaticamente in risposta ai gesti dell'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 callback per eventi quali inizio sessione, fine sessione e così via.
Trasferimento dello streaming
La conservazione dello stato della sessione è alla base del trasferimento dello streaming, in cui gli utenti possono spostare gli 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 si interrompe su un dispositivo (la sorgente) e continua su un altro (la destinazione). Qualsiasi dispositivo di trasmissione con il firmware più recente può essere utilizzato come sorgente o destinazione in un trasferimento in streaming.
Per ottenere il nuovo dispositivo di destinazione durante il trasferimento dello stream, utilizza la proprietà
GCKCastSession#device
durante il callback
[sessionManager:didResumeCastSession:]
.
Per ulteriori informazioni, consulta Trasferimento di stream su Web Receiver.
Riconnessione automatica
Il framework Cast aggiunge la logica di ricollegamento per gestire automaticamente il ricollegamento in molti casi limite sottili, ad esempio:
- Ripristinare la connessione dopo una perdita temporanea del Wi-Fi
- Risolvere i problemi relativi alla modalità di sospensione del dispositivo
- Ripristinare l'app dopo averla messa in background
- Ripristino se l'app si è arrestata in modo anomalo
Come funziona il controllo dei contenuti multimediali
Se viene stabilita una sessione di trasmissione con un'app di ricezione web che supporta lo spazio dei nomi media, il framework creerà automaticamente un'istanza di GCKRemoteMediaClient
, a cui è possibile accedere come proprietà remoteMediaClient
dell'istanza GCKCastSession
.
Tutti i metodi di 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.
È previsto che l'istanza di GCKRemoteMediaClient
possa essere condivisa da più parti dell'app e, infatti, alcuni componenti interni
del framework, come la finestra di dialogo Trasmetti e i mini controlli multimediali, condividono l'istanza. A tal fine, GCKRemoteMediaClient
supporta la registrazione di più GCKRemoteMediaClientListener
.
Impostare 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, i sottotitoli, il nome dello studio di registrazione e due immagini.
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))
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 metadati multimediali.
Caricare contenuti multimediali
Per caricare un elemento multimediale, crea un'istanza
GCKMediaInformation
utilizzando i metadati del contenuto multimediale. Quindi, ottieni il valore corrente di GCKCastSession
e utilizza il relativo valore GCKRemoteMediaClient
per caricare i contenuti multimediali nell'app del 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.
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 }
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 della TV HDR e l'altezza e la larghezza in pixel. Le varianti del formato 4K sono indicate nella proprietà hdrType
tramite i valori enumerati GCKVideoInfoHDRType
.
Aggiungere mini controller
In base al checklist per la progettazione di Cast, un'app mittente deve fornire un controllo persistente noto come minicontroller che deve essere visualizzato quando l'utente esce dalla pagina dei contenuti corrente. Il mini controller offre accesso istantaneo e un promemoria visibile per la sessione di trasmissione in corso.
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/arresto al posto del pulsante di riproduzione/pausa nel mini controller.
Consulta Personalizzare l'interfaccia utente del mittente per iOS per scoprire in che modo la tua app di invio 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 inserendo il tuo view controller esistente nel suo view controller.
- Gestisci autonomamente il layout del widget del mini controller aggiungendolo al visualizzatore esistente fornendo una visualizzazione secondaria nello storyboard.
Esegui il wrapping utilizzando GCKUICastContainerViewController
Il primo metodo consiste nell'utilizzare il metodo
GCKUICastContainerViewController
che racchiude un altro controller della vista e aggiunge un pulsante
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 solitamente eseguito nel metodo-[application:didFinishLaunchingWithOptions:]
del delegante dell'app:
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() ... }
- (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]; ... }
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 } } }
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
Incorporare nel controller della visualizzazione esistente
Il secondo modo consiste nell'aggiungere il mini controller direttamente al view controller esistente utilizzando createMiniMediaControlsViewController
per creare un'istanza GCKUIMiniMediaControlsViewController
e poi aggiungerlo al view controller contenitore come visualizzazione secondaria.
Configura il tuo controller della visualizzazione nel delegato dell'app:
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 }
- (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 tuo UIViewController principale, crea un'istanza di GCKUIMiniMediaControlsViewController
e aggiungila al UIViewController contenitore come vista secondaria:
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) } } ...
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
Il
GCKUIMiniMediaControlsViewControllerDelegate
dice al view controller dell'host quando il mini controller deve essere visibile:
func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController, shouldAppear _: Bool) { updateControlBarsVisibility() }
- (void)miniMediaControlsViewController: (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController shouldAppear:(BOOL)shouldAppear { [self updateControlBarsVisibility]; }
Aggiungere il controller espanso
L'elenco di controllo per la progettazione di Google Cast richiede che un'app mittente fornisca un controllo aumentato 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 remoti. 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 (connetti/interrompi trasmissione). Fornisce inoltre tutte le informazioni sullo stato della sessione multimediale (artwork, titolo, sottotitoli 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:
func applicationDidFinishLaunching(_ application: UIApplication) { .. GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true ... }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES; .. }
Aggiungi il seguente codice al tuo ViewController per caricare il controller espanso quando l'utente avvia la trasmissione di un video:
func playSelectedItemRemotely() { GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls() ... // Load your media sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInformation) }
- (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/arresto al posto del pulsante di riproduzione/pausa nel controller espanso.
Consulta Applicare stili personalizzati all'app per iOS per scoprire in che modo l'app mittente può configurare l'aspetto dei widget di trasmissione.
Controllo del volume
Il framework di trasmissione gestisce automaticamente il volume dell'app mittente. Il framework si sincronizza automaticamente con il volume del ricevitore web per i widget dell'interfaccia utente forniti. Per sincronizzare un cursore fornito dall'app, utilizza
GCKUIDeviceVolumeController
.
Controllo del volume tramite pulsante fisico
I pulsanti del volume fisici sul dispositivo di invio possono essere utilizzati per regolare il volume della sessione di trasmissione sul ricevitore web utilizzando il flag physicalVolumeButtonsWillControlDeviceVolume
su GCKCastOptions
, impostato su GCKCastContext
.
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID) let options = GCKCastOptions(discoveryCriteria: criteria) options.physicalVolumeButtonsWillControlDeviceVolume = true GCKCastContext.setSharedInstanceWith(options)
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 di invio gestiscano tutti i callback di errore e decidano la risposta migliore per ogni fase del ciclo di vita di Cast. L'app può mostrare all'utente finestre di dialogo di errore o decidere di terminare la sessione di trasmissione.
Logging
GCKLogger
è un singleton utilizzato per il logging dal framework. Utilizza
GCKLoggerDelegate
per personalizzare la gestione dei messaggi di log.
Utilizzando GCKLogger
, l'SDK produce output di log 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 la loro identificazione. Per impostazione predefinita, l'output del log viene suppressed, ma assegnando un GCKLoggerDelegate
, l'app mittente può ricevere questi messaggi dall'SDK e registrarli nella console di sistema.
@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) } } }
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):
let filter = GCKLoggerFilter.init() filter.minimumLevel = GCKLoggerLevel.verbose GCKLogger.sharedInstance().filter = filter
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 di logging minimo per classe, ad esempio:
let filter = GCKLoggerFilter.init() filter.setLoggingLevel(GCKLoggerLevel.verbose, forClasses: ["GCKUICastButton", "GCKUIImageCache", "NSMutableDictionary"]) GCKLogger.sharedInstance().filter = filter
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 di glob, ad esempio
GCKUI\*
e GCK\*Session
.