Z tego przewodnika dla programistów dowiesz się, jak dodać obsługę Google Cast do aplikacji nadawczej na iOS za pomocą pakietu SDK nadawcy na iOS.
Urządzenie mobilne lub laptop to nadawca, który steruje odtwarzaniem, a urządzenie Google Cast to odbiorca, który wyświetla treści na telewizorze.
Platforma nadawcy odnosi się do pliku binarnego biblioteki klas Cast i powiązanych zasobów obecnych w czasie działania nadawcy. Aplikacja nadawcy lub aplikacja Cast to aplikacja, która również działa na urządzeniu nadawcy. Aplikacja odbiornika internetowego to aplikacja HTML działająca na odbiorniku internetowym.
Platforma nadawcy używa asynchronicznego wywołania zwrotnego, aby informować aplikację nadawcy o zdarzeniach i przechodzić między różnymi stanami cyklu życia aplikacji Cast.
Przepływ w aplikacji
Poniżej opisujemy typowy ogólny proces wykonywania aplikacji nadawcy na iOS:
- Platforma Cast uruchamia
GCKDiscoveryManagerna podstawie właściwości podanych wGCKCastOptions, aby rozpocząć skanowanie w poszukiwaniu urządzeń. - Gdy użytkownik kliknie przycisk Cast, platforma wyświetli okno Cast z listą wykrytych urządzeń Cast.
- Gdy użytkownik wybierze urządzenie Cast, platforma spróbuje uruchomić na nim aplikację odbiornika internetowego.
- Platforma wywołuje wywołania zwrotne w aplikacji nadawcy, aby potwierdzić, że aplikacja odbiornika internetowego została uruchomiona.
- Platforma tworzy kanał komunikacji między aplikacjami nadawcy i odbiornika internetowego.
- Platforma używa kanału komunikacyjnego do wczytywania i kontrolowania odtwarzania multimediów w aplikacji Web Receiver.
- Platforma synchronizuje stan odtwarzania multimediów między nadawcą a odbiornikiem internetowym: gdy użytkownik wykonuje działania w interfejsie nadawcy, platforma przekazuje te żądania opcji sterowania multimediami do odbiornika internetowego, a gdy odbiornik internetowy wysyła aktualizacje stanu multimediów, platforma aktualizuje stan interfejsu nadawcy.
- Gdy użytkownik kliknie przycisk Cast, aby odłączyć się od urządzenia Cast, platforma odłączy aplikację nadawczą od odbiornika internetowego.
Aby rozwiązać problemy z nadawcą, musisz włączyć logowanie.
Pełną listę wszystkich klas, metod i zdarzeń w środowisku Google Cast na iOS znajdziesz w dokumentacji interfejsu Google Cast iOS API. W sekcjach poniżej opisujemy, jak zintegrować Cast z aplikacją na iOS.
Wywoływanie metod z głównego wątku
Inicjowanie kontekstu Cast
Platforma Cast ma globalny obiekt singleton GCKCastContext, który koordynuje wszystkie działania platformy. Ten obiekt musi zostać zainicjowany na wczesnym etapie cyklu życia aplikacji, zwykle w metodzie -[application:didFinishLaunchingWithOptions:] delegata aplikacji, aby automatyczne wznawianie sesji po ponownym uruchomieniu aplikacji nadawcy mogło działać prawidłowo.
Podczas inicjowania GCKCastContext należy podać obiekt GCKCastOptions.
Ta klasa zawiera opcje, które wpływają na działanie platformy. Najważniejszy z nich to identyfikator aplikacji odbiornika internetowego, który służy do filtrowania wyników wykrywania i uruchamiania aplikacji odbiornika internetowego po rozpoczęciu sesji przesyłania.
Metoda -[application:didFinishLaunchingWithOptions:] to też dobre miejsce na skonfigurowanie delegata logowania, który będzie odbierać komunikaty logu z platformy.
Mogą być przydatne podczas debugowania i rozwiązywania problemów.
@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
Widżety UX Cast
Pakiet Cast iOS SDK udostępnia te widżety, które są zgodne z listą kontrolną projektowania Cast:
Nakładka wprowadzająca: klasa
GCKCastContextma metodępresentCastInstructionsViewControllerOnceWithCastButton, której można użyć do wyróżnienia przycisku Cast, gdy odbiornik internetowy jest dostępny po raz pierwszy. Aplikacja nadawcy może dostosować tekst, pozycję tekstu tytułu i przycisk Zamknij.Przycisk Cast: od wersji 4.6.0 pakietu SDK Cast na iOS przycisk Cast jest zawsze widoczny, gdy urządzenie wysyłające jest połączone z Wi-Fi. Gdy użytkownik po raz pierwszy kliknie przycisk Cast po uruchomieniu aplikacji, pojawi się okno uprawnień, w którym będzie mógł przyznać aplikacji dostęp do urządzeń w sieci lokalnej. Następnie, gdy użytkownik kliknie przycisk Cast, wyświetli się okno przesyłania z listą wykrytych urządzeń. Gdy użytkownik kliknie przycisk Cast, gdy urządzenie jest połączone, wyświetlą się aktualne metadane multimediów (takie jak tytuł, nazwa studia nagraniowego i miniatura) lub użytkownik będzie mógł odłączyć urządzenie przesyłające. Gdy użytkownik kliknie przycisk Cast, a nie będzie dostępnych żadnych urządzeń, wyświetli się ekran z informacjami o tym, dlaczego nie można znaleźć urządzeń, i sposobami rozwiązania problemu.
Minikontroler: gdy użytkownik przesyła treści i opuścił bieżącą stronę treści lub rozszerzony kontroler, aby przejść do innego ekranu w aplikacji nadawcy, u dołu ekranu wyświetla się minikontroler, który umożliwia użytkownikowi wyświetlanie metadanych aktualnie przesyłanych multimediów i sterowanie odtwarzaniem.
Rozwinięty kontroler: gdy użytkownik przesyła treści, kliknięcie powiadomienia o multimediach lub minikontrolera powoduje uruchomienie rozwiniętego kontrolera, który wyświetla metadane aktualnie odtwarzanych multimediów i zawiera kilka przycisków do sterowania odtwarzaniem.
Dodawanie przycisku Cast
Platforma udostępnia komponent przycisku Cast jako podklasę UIButton. Można go dodać do paska tytułu aplikacji, umieszczając go w elemencie UIBarButtonItem. Typowa podklasa UIViewController może zainstalować przycisk Cast w ten sposób:
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];
Domyślnie kliknięcie przycisku powoduje otwarcie okna Cast udostępnianego przez platformę.
GCKUICastButton można też dodać bezpośrednio do scenorysu.
Konfigurowanie wykrywania urządzeń
W tym frameworku wykrywanie urządzeń odbywa się automatycznie. Nie musisz jawnie rozpoczynać ani zatrzymywać procesu wykrywania, chyba że implementujesz niestandardowy interfejs.
Wykrywaniem w ramach platformy zarządza klasa GCKDiscoveryManager, która jest właściwością GCKCastContext. Platforma udostępnia domyślny komponent okna Cast do wyboru urządzenia i sterowania nim. Lista urządzeń jest uporządkowana leksykograficznie według przyjaznej nazwy urządzenia.
Jak działa zarządzanie sesjami
Pakiet Cast SDK wprowadza koncepcję sesji przesyłania, której ustanowienie łączy kroki łączenia się z urządzeniem, uruchamiania aplikacji Web Receiver (lub dołączania do niej), łączenia się z tą aplikacją i inicjowania kanału sterowania multimediami. Więcej informacji o sesjach Cast i cyklu życia odbiornika internetowego znajdziesz w przewodniku po cyklu życia aplikacji na odbiorniku internetowym.
Sesjami zarządza klasa GCKSessionManager, która jest właściwością GCKCastContext.
Poszczególne sesje są reprezentowane przez podklasy klasy GCKSession, np. GCKCastSession reprezentuje sesje z urządzeniami przesyłającymi. Dostęp do aktualnie aktywnej sesji przesyłania (jeśli taka istnieje) możesz uzyskać za pomocą właściwości currentCastSession obiektu GCKSessionManager.
Interfejsu GCKSessionManagerListener można używać do monitorowania zdarzeń sesji, takich jak utworzenie, zawieszenie, wznowienie i zakończenie sesji. Platforma automatycznie zawiesza sesje, gdy aplikacja nadawcy przechodzi do działania w tle, i próbuje je wznowić, gdy aplikacja wraca na pierwszy plan (lub jest ponownie uruchamiana po nieprawidłowym lub nagłym zakończeniu działania, gdy sesja była aktywna).
Jeśli używane jest okno Cast, sesje są tworzone i zamykane automatycznie w odpowiedzi na gesty użytkownika. W przeciwnym razie aplikacja może jawnie rozpoczynać i kończyć sesje za pomocą metod w GCKSessionManager.
Jeśli aplikacja musi wykonać specjalne przetwarzanie w odpowiedzi na zdarzenia cyklu życia sesji, może zarejestrować co najmniej 1 instancję GCKSessionManagerListener w GCKSessionManager. GCKSessionManagerListener to protokół, który definiuje wywołania zwrotne dla zdarzeń takich jak rozpoczęcie sesji, zakończenie sesji itp.
Przeniesienie odtwarzania
Zachowanie stanu sesji jest podstawą przenoszenia odtwarzania, dzięki któremu użytkownicy mogą przenosić istniejące strumienie audio i wideo między urządzeniami za pomocą poleceń głosowych, aplikacji Google Home lub inteligentnych ekranów. Odtwarzanie multimediów zostanie zatrzymane na jednym urządzeniu (źródłowym) i będzie kontynuowane na innym (docelowym). Każde urządzenie Cast z najnowszym oprogramowaniem układowym może służyć jako źródło lub miejsce docelowe przeniesienia odtwarzania.
Aby uzyskać nowe urządzenie docelowe podczas przenoszenia odtwarzania, użyj właściwości GCKCastSession#device podczas wywołania zwrotnego [sessionManager:didResumeCastSession:].
Więcej informacji znajdziesz w artykule Przenoszenie odtwarzania w odbiorniku internetowym.
Automatyczne ponowne łączenie
Platforma Cast dodaje logikę ponownego łączenia, aby automatycznie obsługiwać ponowne łączenie w wielu subtelnych przypadkach, takich jak:
- Przywracanie połączenia po tymczasowej utracie Wi-Fi
- Wybudzanie urządzenia z trybu uśpienia
- Przywracanie po przełączeniu aplikacji w tło
- Przywracanie po awarii aplikacji
Jak działają opcje sterowania multimediami
Jeśli sesja przesyłania zostanie nawiązana z aplikacją odbiornika internetowego, która obsługuje przestrzeń nazw multimediów, platforma automatycznie utworzy instancję GCKRemoteMediaClient. Można uzyskać do niej dostęp jako do właściwości remoteMediaClient instancji GCKCastSession.
Wszystkie metody w GCKRemoteMediaClient, które wysyłają żądania do odbiornika internetowego, zwracają obiekt GCKRequest, którego można użyć do śledzenia żądania. Do tego obiektu można przypisać GCKRequestDelegate, aby otrzymywać powiadomienia o ostatecznym wyniku operacji.
Instancja GCKRemoteMediaClient może być udostępniana przez wiele części aplikacji, a niektóre wewnętrzne komponenty platformy, takie jak okno Cast i miniodtwarzacz, faktycznie udostępniają instancję. W tym celu GCKRemoteMediaClient obsługuje rejestrację wielu GCKRemoteMediaClientListener.
Ustawianie metadanych multimediów
Klasa GCKMediaMetadata reprezentuje informacje o elemencie multimedialnym, który chcesz przesyłać. W poniższym przykładzie tworzona jest nowa instancja GCKMediaMetadata filmu i ustawiany jest tytuł, podtytuł, nazwa studia nagraniowego oraz 2 obrazy.
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]];
Informacje o używaniu obrazów z metadanymi multimediów znajdziesz w sekcji Wybór i buforowanie obrazów.
Wczytaj multimedia
Aby wczytać element multimedialny, utwórz instancję GCKMediaInformation, używając metadanych multimediów. Następnie pobierz bieżący obiekt GCKCastSession i użyj jego metody GCKRemoteMediaClient, aby wczytać multimedia w aplikacji odbiornika. Następnie możesz użyć metody GCKRemoteMediaClient do sterowania aplikacją odtwarzacza multimediów działającą na odbiorniku, np. do odtwarzania, wstrzymywania i zatrzymywania.
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; }
Zobacz też sekcję dotyczącą używania ścieżek multimedialnych.
Format filmu 4K
Aby określić format wideo multimediów, użyj właściwości videoInfo elementu GCKMediaStatus, aby uzyskać bieżącą instancję elementu GCKVideoInfo.
Ta instancja zawiera typ formatu telewizora HDR oraz wysokość i szerokość w pikselach. Warianty formatu 4K są wskazywane we właściwości hdrType przez wartości typu wyliczeniowego GCKVideoInfoHDRType.
Dodawanie minikontrolerów
Zgodnie z listą kontrolną projektowania Cast aplikacja nadawcy powinna udostępniać stały element sterujący, znany jako minikontroler, który powinien pojawiać się, gdy użytkownik opuści bieżącą stronę treści. Minikontroler zapewnia natychmiastowy dostęp do bieżącej sesji przesyłania i jest jej widocznym przypomnieniem.
Platforma Cast udostępnia pasek sterowania GCKUIMiniMediaControlsViewController, który można dodawać do scen, w których chcesz wyświetlać miniodtwarzacz.
Gdy aplikacja nadawcy odtwarza transmisję na żywo wideo lub audio, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/wstrzymania w minikontrolerze.
Informacje o tym, jak aplikacja nadawcy może konfigurować wygląd widżetów Cast, znajdziesz w artykule Dostosowywanie interfejsu nadawcy na iOS.
Minikontroler można dodać do aplikacji nadawcy na 2 sposoby:
- Pozwól platformie Cast zarządzać układem miniodtwarzacza, opakowując istniejący kontroler widoku własnym kontrolerem widoku.
- Zarządzaj układem widżetu miniodtwarzacza, dodając go do istniejącego kontrolera widoku przez udostępnienie podwidoku w scenorysie.
Zawijanie za pomocą GCKUICastContainerViewController
Pierwszy sposób to użycie GCKUICastContainerViewController, który opakowuje inny kontroler widoku i dodaje GCKUIMiniMediaControlsViewController u dołu. To podejście jest ograniczone, ponieważ nie można dostosować animacji ani skonfigurować zachowania kontrolera widoku kontenera.
Pierwszy sposób jest zwykle realizowany w metodzie -[application:didFinishLaunchingWithOptions:] delegata aplikacji:
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
Umieszczanie w istniejącym kontrolerze widoku
Drugi sposób polega na dodaniu miniodtwarzacza bezpośrednio do istniejącego kontrolera widoku za pomocą metody createMiniMediaControlsViewController, aby utworzyć instancję GCKUIMiniMediaControlsViewController, a następnie dodaniu jej do kontrolera widoku kontenera jako podwidoku.
Skonfiguruj kontroler widoku w delegacie aplikacji:
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; }
W głównym kontrolerze widoku utwórz instancję GCKUIMiniMediaControlsViewController i dodaj ją do kontrolera widoku kontenera jako podwidok:
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 informuje kontroler widoku hosta, kiedy minikontroler powinien być widoczny:
func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController, shouldAppear _: Bool) { updateControlBarsVisibility() }
- (void)miniMediaControlsViewController: (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController shouldAppear:(BOOL)shouldAppear { [self updateControlBarsVisibility]; }
Dodaj rozszerzony kontroler
Zgodnie z listą kontrolną projektowania Google Cast aplikacja nadawcy musi udostępniać rozszerzony kontroler do przesyłanych multimediów. Rozwinięty kontroler to pełnoekranowa wersja minikontrolera.
Rozwinięty kontroler to widok pełnoekranowy, który zapewnia pełną kontrolę nad zdalnym odtwarzaniem multimediów. Ten widok powinien umożliwiać aplikacji przesyłającej zarządzanie wszystkimi aspektami sesji przesyłania, którymi można zarządzać, z wyjątkiem sterowania głośnością odbiornika internetowego i cyklu życia sesji (łączenie/zatrzymywanie przesyłania). Zawiera też wszystkie informacje o stanie sesji multimedialnej (grafika, tytuł, podtytuł itp.).
Funkcje tego widoku są implementowane przez klasę GCKUIExpandedMediaControlsViewController.
Najpierw musisz włączyć domyślny rozszerzony kontroler w kontekście przesyłania. Zmodyfikuj obiekt delegowany aplikacji, aby włączyć domyślny rozszerzony kontroler:
func applicationDidFinishLaunching(_ application: UIApplication) { .. GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true ... }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES; .. }
Dodaj do kontrolera widoku ten kod, aby wczytać rozszerzony kontroler, gdy użytkownik zacznie przesyłać film:
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]; }
Rozwinięty kontroler uruchomi się też automatycznie, gdy użytkownik kliknie minikontroler.
Gdy aplikacja nadawcy odtwarza transmisję na żywo wideo lub audio, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/wstrzymania w rozwiniętym kontrolerze.
Informacje o tym, jak aplikacja nadawcy może konfigurować wygląd widżetów Cast, znajdziesz w artykule Stosowanie stylów niestandardowych w aplikacji na iOS.
Sterowanie głośnością
Platforma Cast automatycznie zarządza głośnością w aplikacji nadawcy. Platforma automatycznie synchronizuje głośność z odbiornikiem internetowym w przypadku dostarczonych widżetów interfejsu. Aby zsynchronizować suwak udostępniany przez aplikację, użyj GCKUIDeviceVolumeController.
Sterowanie głośnością za pomocą przycisków fizycznych
Fizyczne przyciski głośności na urządzeniu przesyłającym mogą służyć do zmiany głośności sesji przesyłania na odbiorniku internetowym za pomocą flagi physicalVolumeButtonsWillControlDeviceVolume w GCKCastOptions, która jest ustawiona w 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];
Obsługuj błędy
Bardzo ważne jest, aby aplikacje nadawcze obsługiwały wszystkie wywołania zwrotne błędów i podejmowały najlepsze decyzje na każdym etapie cyklu życia Cast. Aplikacja może wyświetlać użytkownikowi okna z błędami lub zakończyć sesję przesyłania.
Pamiętaj, że niektóre błędy, w tym GCKErrorCode
GCKErrorCodeCancelled, są zamierzonym zachowaniem.
Nie próbuj ponownie nawiązać połączenia, które nie powiodło się z powodu błędu GCKErrorCodeCancelled.
Może to spowodować nieoczekiwane zachowanie.
Logowanie
GCKLogger to singleton używany przez platformę do logowania. Użyj GCKLoggerDelegate, aby dostosować sposób obsługi komunikatów logu.
Za pomocą GCKLogger pakiet SDK generuje dane wyjściowe logowania w postaci komunikatów debugowania, błędów i ostrzeżeń. Te komunikaty logu ułatwiają debugowanie i są przydatne podczas rozwiązywania problemów oraz ich identyfikowania. Domyślnie dane wyjściowe logu są ukryte, ale po przypisaniu GCKLoggerDelegate aplikacja nadawcy może odbierać te wiadomości z pakietu SDK i rejestrować je w konsoli systemowej.
@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
Aby włączyć też komunikaty debugowania i szczegółowe, dodaj ten wiersz do kodu po ustawieniu obiektu delegowanego (jak pokazano wcześniej):
let filter = GCKLoggerFilter.init() filter.minimumLevel = GCKLoggerLevel.verbose GCKLogger.sharedInstance().filter = filter
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init]; [filter setMinimumLevel:GCKLoggerLevelVerbose]; [GCKLogger sharedInstance].filter = filter;
Możesz też filtrować komunikaty logu generowane przez GCKLogger.
Ustaw minimalny poziom logowania dla każdej klasy, na przykład:
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;
Nazwy klas mogą być nazwami dosłownymi lub wzorcami glob, na przykład GCKUI\* i GCK\*Session.