Questa guida per gli sviluppatori descrive come aggiungere il supporto di Google Cast alla tua app per mittenti 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 visualizza 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. Con app mittente o app Cast si intende un'app in esecuzione anche sul mittente. L'app WebRicevitore 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 consentire la transizione tra i vari stati del ciclo di vita dell'app di trasmissione.
Flusso di app
I passaggi seguenti descrivono il flusso di esecuzione tipico di alto livello per un'app per iOS del mittente:
- Il framework di trasmissione viene avviato
GCKDiscoveryManager
in base alle proprietà fornite inGCKCastOptions
per avviare la scansione dei dispositivi. - Quando l'utente fa clic sul pulsante Trasmetti, nella struttura viene visualizzata 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 ricevitore sul dispositivo di trasmissione.
- Il framework richiama i callback nell'app del mittente per confermare che l'app Web ricevitore è stata avviata.
- Il framework crea un canale di comunicazione tra il mittente e le app Web Ricevitore.
- 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 passa queste richieste di controllo multimediale al ricevitore web e, quando quest'ultimo invia aggiornamenti sullo 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 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 del quadro di riferimento di Google Cast per iOS, consulta la documentazione di riferimento dell'API Google Cast per iOS. Le seguenti sezioni descrivono i passaggi per integrare Google Cast nella tua app per iOS.
Metodi di chiamata dal thread principale
Inizializzare il contesto Cast
Il framework Cast ha un oggetto singleton globale, GCKCastContext
, che coordina tutte le attività del framework. Questo oggetto deve essere inizializzato in anticipo durante il ciclo di vita dell'applicazione, in genere nel metodo -[application:didFinishLaunchingWithOptions:]
del delegato dell'app, in modo che la ripresa automatica delle sessioni al riavvio dell'app del mittente possa essere attivata correttamente.
È necessario specificare un oggetto GCKCastOptions
durante l'inizializzazione di GCKCastContext
.
Questa classe contiene opzioni che influiscono sul comportamento del framework. Il più importante è l'ID applicazione WebRicevitore, che viene utilizzato per filtrare i risultati del rilevamento e per avviare l'app WebRicevitore all'avvio di una sessione di Cast.
Il metodo -[application:didFinishLaunchingWithOptions:]
è anche un buon punto per configurare un delegato di logging che riceva i messaggi di log dal framework.
Queste 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
I widget UX di Cast
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 diventa 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 Cast 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 l'avvio iniziale dell'app, viene visualizzata una finestra di dialogo delle autorizzazioni in modo che l'utente possa concedere all'app l'accesso alla rete locale ai dispositivi nella rete. Successivamente, quando l'utente tocca il pulsante Trasmetti, viene visualizzata una finestra di dialogo Trasmetti in cui sono elencati i dispositivi rilevati. Quando l'utente tocca il pulsante Trasmetti mentre il dispositivo è connesso, vengono visualizzati i metadati multimediali correnti (come 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 sul motivo per cui i dispositivi non vengono trovati e su come risolvere il problema.
Mini controller: quando l'utente sta trasmettendo 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 per controllare la riproduzione.
Controller espanso: quando l'utente trasmette contenuti, se fa clic sulla notifica dei contenuti multimediali 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.
Aggiungi un pulsante Trasmetti
Il framework fornisce un componente Pulsante Trasmetti come sottoclasse UIButton
. Può
essere aggiunto alla barra del titolo dell'app racchiudendolo in un UIBarButtonItem
. Una sottoclasse
UIViewController
tipica può installare un pulsante Trasmetti nel seguente modo:
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, se tocchi il pulsante si apre la finestra di dialogo Trasmetti fornita dal framework.
GCKUICastButton
può anche essere aggiunto 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 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 Trasmetti per la selezione e il controllo dei dispositivi. L'elenco dei dispositivi è ordinato lessicograficamente in base al nome semplice per i dispositivi.
Come funziona la gestione delle sessioni
L'SDK Cast introduce il concetto di una sessione di trasmissione, la cui stabilità combina i passaggi per connettersi a un dispositivo, avviare (o partecipare) a un'app ricevitore web, connettersi all'app e inizializzare un canale di controllo multimediale. Per ulteriori informazioni sulle sessioni di trasmissione e sul ciclo di vita del ricevitore web, consulta la Guida al ciclo di vita dell'applicazione di Web receiver.
Le sessioni sono gestite dalla classe GCKSessionManager
, che è una proprietà di GCKCastContext
.
Le singole sessioni sono rappresentate dalle sottoclassi del corso GCKSession
: ad esempio, GCKCastSession
rappresenta le sessioni con dispositivi di trasmissione. Puoi accedere alla sessione Cast
attualmente attiva (se presente) come proprietà currentCastSession
di GCKSessionManager
.
L'interfaccia GCKSessionManagerListener
può essere utilizzata per monitorare gli eventi della sessione, ad esempio la creazione, la sospensione, la ripresa e la fine di una sessione. Il framework sospende automaticamente le sessioni quando l'app del mittente viene eseguita in background e tenta di riprenderle quando l'app torna in primo piano (o viene riavviata dopo una chiusura anomala o brusca dell'app mentre era attiva una sessione).
Se viene utilizzata la finestra di dialogo Trasmetti, le sessioni vengono create e disattivate automaticamente in risposta ai gesti dell'utente. In caso contrario, l'app può avviare e terminare
sessioni in modo esplicito tramite i metodi su
GCKSessionManager
.
Se l'app deve eseguire un'elaborazione speciale in risposta a 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 l'inizio, la fine della sessione e così via.
Trasferimento dello streaming
Conservare lo stato della sessione è la base del trasferimento dello streaming, in cui gli utenti possono 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 viene interrotta su un dispositivo (l'origine) e continua su un altro (la destinazione). Qualsiasi dispositivo di trasmissione con il firmware più recente può fungere da sorgente o destinazione in un trasferimento dello streaming.
Per recuperare il nuovo dispositivo di destinazione durante il trasferimento dello streaming, utilizza la proprietà GCKCastSession#device
durante il callback di [sessionManager:didResumeCastSession:]
.
Per ulteriori informazioni, vedi Trasferimento dello streaming su WebRicevitore.
Riconnessione automatica
Il framework Cast aggiunge una logica di riconnessione per gestire automaticamente la riconnessione in molti casi d'angolo sottili, ad esempio:
- Recuperare dopo una perdita temporanea del Wi-Fi
- Ripristina dalla sospensione del dispositivo
- Recuperare l'app in background
- Ripristinare se l'app ha avuto un arresto anomalo
Come funziona il controllo multimediale
Se viene stabilita una sessione di trasmissione con un'app Web Ricevitore 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 elemento GCKRequestDelegate
per ricevere notifiche sul risultato finale dell'operazione.
È previsto che l'istanza di GCKRemoteMediaClient
possa essere condivisa da più parti dell'app e alcuni componenti interni
del framework, come la finestra di dialogo Trasmetti e i controlli multimediali mini, condividono
l'istanza. A questo scopo, GCKRemoteMediaClient
supporta la registrazione di più GCKRemoteMediaClientListener
.
Imposta metadati multimediali
La classe GCKMediaMetadata
rappresenta informazioni su un elemento multimediale che vuoi trasmettere. L'esempio
seguente crea una nuova istanza GCKMediaMetadata
di un filmato e imposta titolo,
sottotitolo, 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 delle immagini e memorizzazione nella cache per informazioni sull'utilizzo delle immagini con metadati multimediali.
Carica contenuti multimediali
Per caricare un elemento multimediale, crea un'istanza GCKMediaInformation
utilizzando i metadati del contenuto multimediale. Dopodiché recupera l'GCKCastSession
corrente e utilizza la relativa GCKRemoteMediaClient
per caricare i contenuti multimediali sull'app del ricevitore. Potrai quindi utilizzare GCKRemoteMediaClient
per controllare un'app del 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 attuale di
GCKVideoInfo
.
Questa istanza contiene il tipo di formato HDR per la TV 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
Secondo l'elenco di controllo per la progettazione della trasmissione, un'app mittente deve fornire un controllo permanente noto come mini controller che dovrebbe essere visualizzato quando l'utente esce dalla pagina dei contenuti corrente. Il mini controller fornisce accesso immediato e un promemoria visibile per la sessione di trasmissione corrente.
Il framework Trasmetti include una barra di controllo,
GCKUIMiniMediaControlsViewController
,
che può essere aggiunta alle scene in cui vuoi mostrare il mini controller.
Quando l'app del mittente riproduce un live streaming video o audio, l'SDK mostra automaticamente un pulsante di riproduzione/interruzione al posto del pulsante di riproduzione/pausa nel mini controller.
Vedi Personalizzazione dell'interfaccia utente del mittente per iOS per sapere come l'app del 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 raggruppando il controller di visualizzazione esistente con il proprio controller di visualizzazione.
- Gestisci autonomamente il layout del widget del mini controller aggiungendolo al controller di visualizzazione esistente fornendo una visualizzazione secondaria nello storyboard.
Esegui il wrapping con GCKUICastContainerViewController
Il primo modo consiste nell'utilizzare GCKUICastContainerViewController
che aggrega un altro controller di visualizzazione e aggiunge un GCKUIMiniMediaControlsViewController
in basso. Questo approccio è limitato in quanto non puoi personalizzare l'animazione e configurare il comportamento del controller della vista container.
Questo primo metodo viene in genere eseguito nel
metodo -[application:didFinishLaunchingWithOptions:]
del delegato 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
Incorpora in un controller di visualizzazione esistente
Il secondo modo è aggiungere il mini controller direttamente al controller vista esistente utilizzando createMiniMediaControlsViewController
per creare un'istanza GCKUIMiniMediaControlsViewController
e aggiungendola al controller della vista container come vista secondaria.
Configura il controller di 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 controller della vista root, crea un'istanza GCKUIMiniMediaControlsViewController
e aggiungila al controller della vista container come visualizzazione 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
GCKUIMiniMediaControlsViewControllerDelegate
indica al controller della vista host quando il mini controller dovrebbe essere visibile:
func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController, shouldAppear _: Bool) { updateControlBarsVisibility() }
- (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 che vengono trasmessi. Il controller espanso è una versione a schermo intero del mini controller.
Il controller espanso è in una visualizzazione a schermo intero che offre il controllo completo della riproduzione dei contenuti multimediali da remoto. Questa vista 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 vista è implementata dalla classe GCKUIExpandedMediaControlsViewController
.
Per prima cosa devi abilitare il controller espanso predefinito nel contesto di trasmissione. Modifica il delegato dell'app per abilitare 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 controller di visualizzazione per caricare il controller espanso quando l'utente inizia a trasmettere 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 del mittente riproduce un live streaming video o audio, l'SDK mostra automaticamente un pulsante di riproduzione/interruzione al posto del pulsante di riproduzione/pausa nel controller espanso.
Consulta la pagina Applicare stili personalizzati all'app iOS per avere informazioni su come l'app del mittente può configurare l'aspetto dei widget di trasmissione.
Controllo del volume
Il framework Cast gestisce automaticamente il volume per l'app del mittente. Il framework si sincronizza automaticamente con il volume Web receiver per i widget UI forniti. Per sincronizzare un cursore fornito dall'app, usa GCKUIDeviceVolumeController
.
Controllo del volume tramite tasto fisico
I pulsanti fisici del volume sul dispositivo del mittente possono essere utilizzati per modificare il volume della sessione di trasmissione sul ricevitore web utilizzando il flag physicalVolumeButtonsWillControlDeviceVolume
su GCKCastOptions
, che è 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];
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 della trasmissione. L'app può mostrare finestre di errore all'utente o può decidere di terminare la sessione di trasmissione.
Logging
GCKLogger
è un singleton utilizzato per il logging dal framework. Utilizza GCKLoggerDelegate
per personalizzare la modalità di gestione dei messaggi di log.
Utilizzando l'GCKLogger
, l'SDK produce l'output di log sotto forma di messaggi di debug, errori e avvisi. Questi messaggi di log aiutano il debug e sono utili
per la risoluzione e l'identificazione dei problemi. Per impostazione predefinita, l'output di log è soppresso, ma assegnando un GCKLoggerDelegate
, l'app del 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 abilitare anche i messaggi di debug e con informazioni dettagliate, aggiungi questa riga al codice dopo aver impostato il delegato (come 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 minimo di logging 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 glob, ad esempio GCKUI\*
e GCK\*Session
.