Ten przewodnik dla programistów opisuje, jak dodać obsługę Google Cast do aplikacji wysyłającej na iOS za pomocą pakietu iOS Sender SDK.
Urządzenie mobilne lub laptop to nadawca, który steruje odtwarzaniem, a urządzenie Google Cast to odbiornik, który wyświetla treści na telewizorze.
Platforma nadawcy to binarna biblioteka klas Cast i powiązane z nią zasoby dostępne w czasie działania na urządzeniu wysyłającym. Aplikacja nadawcy lub aplikacja Cast to aplikacja działająca również na urządzeniu nadawcy. Aplikacja odbiornika internetowego to aplikacja HTML działająca na odbiorniku internetowym.
Platforma nadawcy korzysta z 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ższe kroki opisują typowy ogólny przepływ wykonywania w aplikacji nadawcy na iOS:
- Platforma Cast rozpoczyna
GCKDiscoveryManager
na podstawie właściwości podanych wGCKCastOptions
, aby rozpocząć skanowanie w poszukiwaniu urządzeń. - Gdy użytkownik kliknie przycisk przesyłania, platforma wyświetli okno przesyłania z listą wykrytych urządzeń obsługujących przesyłanie.
- Gdy użytkownik wybierze urządzenie przesyłające, platforma spróbuje uruchomić na nim aplikację Web Receiver.
- Platforma wywołuje wywołania zwrotne w aplikacji nadawcy, aby potwierdzić uruchomienie aplikacji Web Receiver.
- Framework tworzy kanał komunikacji między nadawcą a aplikacjami Web Receiver.
- Platforma korzysta z kanału komunikacji do wczytywania multimediów i sterowania ich odtwarzaniem na odbiorniku internetowym.
- 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 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 przesyłania, aby odłączyć się od urządzenia Cast, platforma odłączy aplikację wysyłającą od odbiornika internetowego.
Aby rozwiązać problem z nadawcą, musisz włączyć logowanie.
Pełną listę wszystkich klas, metod i zdarzeń w platformie Google Cast na iOS znajdziesz w dokumentacji interfejsu Google Cast API na iOS. W kolejnych sekcjach znajdziesz instrukcje integracji Cast z aplikacją na iOS.
Wywoływanie metod z wątku głównego
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 wysyłającej mogło się prawidłowo uruchamiać.
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 Cast.
Metoda -[application:didFinishLaunchingWithOptions:]
to również dobre miejsce na skonfigurowanie delegata logowania, który będzie odbierać wiadomości logowania z frameworka.
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 SDK na iOS udostępnia te widżety zgodne z listą kontrolną dotyczącą projektowania Cast:
Nakładka wprowadzająca: klasa
GCKCastContext
ma metodępresentCastInstructionsViewControllerOnceWithCastButton
, której można użyć, aby wyróżnić przycisk przesyłania przy pierwszym udostępnieniu odbiornika internetowego. Aplikacja nadawcy może dostosować tekst, położenie tekstu tytułu i przycisku Zamknij.Przycisk przesyłania: od wersji 4.6.0 pakietu SDK nadawcy Cast na iOS przycisk przesyłania jest zawsze widoczny, gdy urządzenie nadawcy jest połączone z Wi-Fi. Gdy użytkownik po raz pierwszy kliknie przycisk przesyłania po początkowym uruchomieniu aplikacji, pojawi się okno dialogowe z prośbą o przyznanie uprawnień. Umożliwi to użytkownikowi przyznanie aplikacji dostępu do urządzeń w sieci lokalnej. Gdy użytkownik naciśnie przycisk przesyłania, wyświetli się okno przesyłania, w którym będą widoczne wykryte urządzenia. Gdy użytkownik kliknie przycisk przesyłania, gdy urządzenie jest połączone, wyświetlą się bieżące metadane multimediów (np. tytuł, nazwa studia nagrań i miniatura) lub pojawi się możliwość odłączenia się od urządzenia przesyłającego. Gdy użytkownik kliknie przycisk przesyłania, a nie będzie dostępnych żadnych urządzeń, wyświetli się ekran z informacjami o tym, dlaczego nie można znaleźć urządzeń, oraz o tym, jak rozwiązać ten problem.
Mini Controller: gdy użytkownik przesyła treści i opuści bieżącą stronę z treściami lub rozszerzony kontroler, aby przejść do innego ekranu w aplikacji nadawcy, u dołu ekranu wyświetla się Mini Controller, który umożliwia użytkownikowi wyświetlanie metadanych przesyłanych obecnie multimediów i sterowanie odtwarzaniem.
Rozwinięty kontroler: gdy użytkownik przesyła treści, po kliknięciu powiadomienia o multimediach lub minikontrolera uruchamia się rozwinięty kontroler, 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 tagu UIBarButtonItem
. Typowa podklasaUIViewController
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 dotknięcie przycisku spowoduje otwarcie okna przesyłania dostarczonego przez platformę.
GCKUICastButton
można też dodawać bezpośrednio do scenorysu.
Konfigurowanie wykrywania urządzeń
W ramach platformy wykrywanie urządzeń odbywa się automatycznie. Nie musisz wyraźnie rozpoczynać ani zatrzymywać procesu wykrywania, chyba że wdrożysz niestandardowy interfejs.
Wykrywanie w ramach platformy jest zarządzane przez klasę GCKDiscoveryManager
, która jest właściwością klasy GCKCastContext
. Platforma udostępnia domyślny komponent okna Cast do wyboru i sterowania urządzeniami. Lista urządzeń jest uporządkowana leksykograficznie według przyjaznej nazwy urządzenia.
Jak działa zarządzanie sesjami
Pakiet Cast SDK wprowadza pojęcie sesji Cast, której utworzenie łączy kroki połączenia z urządzeniem, uruchomienia (lub dołączenia do) aplikacji Web Receiver, połączenia 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.
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 na urządzeniach przesyłających. Możesz uzyskać dostęp do aktualnie aktywnej sesji Cast (jeśli taka istnieje) jako właściwości currentCastSession
obiektu GCKSessionManager
.
Interfejs GCKSessionManagerListener
umożliwia monitorowanie zdarzeń sesji, takich jak tworzenie, zawieszanie, wznawianie i zakończenie sesji. Platforma automatycznie wstrzymuje sesje, gdy aplikacja wysyłająca przechodzi do działania w tle, i próbuje je wznowić, gdy aplikacja wraca na pierwszy plan (lub jest ponownie uruchamiana po nieprawidłowym/nagłym zakończeniu działania, gdy sesja była aktywna).
Jeśli używane jest okno przesyłania, sesje są tworzone i zamykane automatycznie w odpowiedzi na gesty użytkownika. W przeciwnym razie aplikacja może rozpoczynać i kończyć sesje w sposób jawny za pomocą metod w GCKSessionManager
.
Jeśli aplikacja musi wykonać specjalne przetwarzanie w odpowiedzi na zdarzenia związane z cyklem ż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 i zakończenie sesji itp.
Przeniesienie odtwarzania
Zachowanie stanu sesji jest podstawą przenoszenia strumieni, dzięki czemu 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 zatrzymuje się na jednym urządzeniu (źródłowym) i jest kontynuowane na innym (docelowym). Każde urządzenie Cast z najnowszym oprogramowaniem może być źródłem lub miejscem docelowym podczas przesyłania strumieniowego.
Aby uzyskać nowe urządzenie docelowe podczas przenoszenia strumienia, użyj właściwości
GCKCastSession#device
w wywołaniu zwrotnym
[sessionManager:didResumeCastSession:]
.
Więcej informacji znajdziesz w artykule Przesyłanie strumieniowe na odbiorniku internetowym.
Automatyczne ponowne łączenie
Platforma Cast dodaje logikę ponownego łączenia, aby automatycznie obsługiwać ponowne łączenie w wielu subtelnych przypadkach, takich jak:
- Przywracanie działania po tymczasowej utracie połączenia Wi-Fi
- Przywracanie działania po przejściu urządzenia w tryb uśpienia
- Przywracanie aplikacji po przejściu do tła
- Przywracanie po awarii aplikacji
Jak działa sterowanie multimediami
Jeśli sesja Cast 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 tego żądania. Do tego obiektu można przypisać GCKRequestDelegate
, aby otrzymywać powiadomienia o ostatecznym wyniku operacji.
Oczekuje się, że instancja GCKRemoteMediaClient
może być udostępniana przez wiele części aplikacji, a niektóre wewnętrzne komponenty
platformy, takie jak okno Cast i mini odtwarzacz multimediów, rzeczywiście udostępniają tę instancję. W tym celu usługa GCKRemoteMediaClient
umożliwia rejestrację wielu
GCKRemoteMediaClientListener
.
Ustawianie metadanych multimediów
Klasa
GCKMediaMetadata
reprezentuje informacje o elemencie multimedialnym, który chcesz przesłać. Poniższy przykład tworzy nową instancję GCKMediaMetadata
filmu i ustawia tytuł, podtytuł, nazwę studia nagrań i 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]];
Więcej informacji o używaniu obrazów z metadanymi multimediów znajdziesz w sekcji Wybieranie obrazów i ich buforowanie.
Wczytaj multimedia
Aby wczytać element multimedialny, utwórz instancję GCKMediaInformation
na podstawie metadanych multimediów. Następnie pobierz bieżący GCKCastSession
i użyj jego GCKRemoteMediaClient
, aby załadować multimedia w aplikacji odbiornika. Możesz wtedy użyć 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; }
Zapoznaj się też z sekcją dotyczącą używania ścieżek multimedialnych.
Format wideo 4K
Aby określić format wideo, użyj właściwości videoInfo
elementu GCKMediaStatus
, aby uzyskać bieżącą instancję elementu GCKVideoInfo
.
Ten obiekt zawiera typ formatu HDR TV oraz wysokość i szerokość w pikselach. Warianty formatu 4K są wskazywane we właściwości hdrType
przez wartości wyliczeniowe GCKVideoInfoHDRType
.
Dodawanie mini kontrolerów
Zgodnie z listą kontrolną dotyczącą projektowania Cast aplikacja wysyłająca powinna udostępniać stały element sterujący, czyli minikontroler, który powinien pojawiać się, gdy użytkownik opuści stronę z bieżącymi treściami. Mini kontroler zapewnia natychmiastowy dostęp i widoczne przypomnienie o bieżącej sesji Cast.
Platforma Cast udostępnia pasek sterowaniaGCKUIMiniMediaControlsViewController
, który można dodać do scen, w których chcesz wyświetlać minikontroler.
Gdy aplikacja wysyłająca odtwarza transmisję na żywo wideo lub audio, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymywania zamiast przycisku odtwarzania/pauzy w minikontrolerze.
Więcej informacji o tym, jak aplikacja wysyłająca może konfigurować wygląd widżetów Cast, znajdziesz w sekcji Dostosowywanie interfejsu aplikacji wysyłającej na iOS.
Miniodtwarzacz możesz dodać do aplikacji wysyłającej na 2 sposoby:
- Pozwól platformie Cast zarządzać układem minikontrolera, owijając istniejący kontroler widoku własnym kontrolerem widoku.
- Samodzielnie zarządzaj układem widżetu minikontrolera, dodając go do istniejącego kontrolera widoku, podając widok podrzędny w pliku storyboard.
Zawijanie za pomocą GCKUICastContainerViewController
Pierwszy sposób polega na użyciu
GCKUICastContainerViewController
który otacza inny kontroler widoku i dodaje
GCKUIMiniMediaControlsViewController
u dołu. To podejście ma jednak ograniczenia, ponieważ nie można dostosować animacji ani skonfigurować działania 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
Osadzanie w istniejącym kontrolerze widoku
Drugi sposób polega na dodaniu minikontrolera bezpośrednio do istniejącego kontrolera widoku za pomocą funkcji createMiniMediaControlsViewController
, aby utworzyć instancję GCKUIMiniMediaControlsViewController
, a następnie dodać ją do kontrolera widoku kontenera jako podwidok.
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 podrzędny widok:
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
Ten parametr
GCKUIMiniMediaControlsViewControllerDelegate
informuje kontroler widoku hosta, kiedy powinien być widoczny minikontroler:
func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController, shouldAppear _: Bool) { updateControlBarsVisibility() }
- (void)miniMediaControlsViewController: (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController shouldAppear:(BOOL)shouldAppear { [self updateControlBarsVisibility]; }
Dodawanie rozszerzonego kontrolera
Lista kontrolna projektu Google Cast wymaga, aby aplikacja wysyłająca udostępniała rozszerzony kontroler do przesyłanych multimediów. Rozwinięty kontroler to wersja mini kontrolera na pełnym ekranie.
Rozwinięty kontroler to widok pełnoekranowy, który zapewnia pełną kontrolę nad odtwarzaniem multimediów na urządzeniu zdalnym. Ten widok powinien umożliwiać aplikacji do przesyłania zarządzanie każdym aspektem sesji przesyłania, z wyjątkiem sterowania głośnością odbiornika internetowego i cyklem życia sesji (łączenie i zatrzymywanie przesyłania). Zawiera też wszystkie informacje o stanie sesji multimedialnej (okładka, tytuł, podtytuł itp.).
Funkcjonalność tego widoku jest realizowana przez klasę
GCKUIExpandedMediaControlsViewController
.
Najpierw musisz włączyć domyślny rozszerzony kontroler w kontekście przesyłania. Zmodyfikuj delegata 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ć rozwinięty kontroler, gdy użytkownik rozpocznie przesyłanie filmu:
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 zostanie też automatycznie uruchomiony, gdy użytkownik kliknie minikontroler.
Gdy aplikacja wysyłająca odtwarza transmisję na żywo wideo lub audio, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymywania zamiast przycisku odtwarzania/wstrzymywania na rozwiniętym kontrolerze.
Więcej informacji o tym, jak aplikacja wysyłająca może skonfigurować wygląd widżetów Cast, znajdziesz w artykule Stosowanie niestandardowych stylów w aplikacji na iOS.
Sterowanie głośnością
Platforma Cast automatycznie zarządza głośnością aplikacji wysyłającej. Platforma automatycznie synchronizuje się z głośnością odbiornika internetowego w przypadku dostarczonych widżetów interfejsu. Aby zsynchronizować suwak udostępniony przez aplikację, użyj GCKUIDeviceVolumeController
.
Sterowanie głośnością za pomocą przycisków fizycznych
Fizyczne przyciski głośności na urządzeniu wysyłającym mogą służyć do zmiany głośności sesji Cast 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
Aplikacje wysyłające muszą obsługiwać wszystkie wywołania zwrotne błędów i określać najlepszą odpowiedź na każdym etapie cyklu życia Cast. Aplikacja może wyświetlać użytkownikowi okna dialogowe z błędami lub zakończyć sesję Cast.
Logowanie
GCKLogger
to pojedynczy obiekt używany przez platformę do logowania. Użyj
GCKLoggerDelegate
, aby dostosować sposób obsługi wiadomości dziennika.
Za pomocą GCKLogger
pakiet SDK generuje dane logowania w postaci komunikatów debugowania, błędów i ostrzeżeń. Te komunikaty dziennika ułatwiają debugowanie i są przydatne podczas rozwiązywania problemów i ich identyfikowania. Domyślnie dane wyjściowe dziennika są pomijane, ale przypisując wartość GCKLoggerDelegate
, aplikacja wysyłająca może otrzymywać te komunikaty 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 delegata (pokazanego 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, np.:
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, np. GCKUI\*
i GCK\*Session
.