Ten przewodnik dla programistów opisuje, jak dodać obsługę Google Cast do aplikacji nadawcy 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 odbiorca , który wyświetla treści na telewizorze.
Framework nadawcy to biblioteka klas Cast w postaci binarnej oraz powiązane z nią zasoby, które są dostępne w czasie działania aplikacji na nadawcy. Aplikacja nadawcy lub aplikacja Cast to aplikacja działająca również na nadawcy. Aplikacja Web Receiver to aplikacja HTML działająca na Web Receiver.
Framework 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 aplikacji
Poniższe kroki opisują typowy przepływ wykonywania aplikacji nadawcy na iOS:
- Framework Cast uruchamia
GCKDiscoveryManagerna podstawie właściwości podanych wGCKCastOptionsaby rozpocząć skanowanie urządzeń. - Gdy użytkownik kliknie przycisk Cast, framework wyświetli okno Cast z listą wykrytych urządzeń Cast.
- Gdy użytkownik wybierze urządzenie przesyłające, framework spróbuje uruchomić na nim aplikację Web Receiver.
- Framework wywołuje wywołania zwrotne w aplikacji nadawcy, aby potwierdzić, że aplikacja Web Receiver została uruchomiona.
- Framework tworzy kanał komunikacji między aplikacjami nadawcy i Web Receiver.
- Framework używa kanału komunikacji do wczytywania i sterowania odtwarzaniem multimediów na Web Receiver.
- Framework synchronizuje stan odtwarzania multimediów między nadawcą a Web Receiver: gdy użytkownik wykonuje działania w interfejsie nadawcy, framework przekazuje te żądania sterowania multimediami do Web Receiver, a gdy Web Receiver wysyła aktualizacje stanu multimediów, framework aktualizuje stan interfejsu nadawcy.
- Gdy użytkownik kliknie przycisk Cast, aby odłączyć się od urządzenia przesyłającego, framework odłączy aplikację nadawcy od Web Receiver.
Aby rozwiązać problemy z nadawcą, musisz włączyć logowanie.
Pełną listę wszystkich klas, metod i zdarzeń w frameworku Google Cast na iOS znajdziesz w dokumentacji interfejsu Google Cast iOS API Reference. W sekcjach poniżej opisujemy, jak zintegrować Cast z aplikacją na iOS.
Wywoływanie metod z wątku głównego
Inicjowanie kontekstu Cast
Framework Cast ma globalny obiekt singletonowy
GCKCastContext, który
koordynuje wszystkie działania frameworku. Ten obiekt musi zostać zainicjowany na wczesnym etapie cyklu życia aplikacji, zwykle w metodzie -[application:didFinishLaunchingWithOptions:] delegata aplikacji, aby można było prawidłowo wznowić sesję automatycznie po ponownym uruchomieniu aplikacji nadawcy.
Podczas inicjowania GCKCastContext należy podać obiekt GCKCastOptions.
Ta klasa zawiera opcje, które wpływają na działanie frameworku. Najważniejszy z nich to identyfikator aplikacji Web Receiver, który służy do filtrowania wyników wykrywania i uruchamiania aplikacji Web Receiver po rozpoczęciu sesji Cast.
Metoda -[application:didFinishLaunchingWithOptions:] to również dobre miejsce na skonfigurowanie delegata logowania, który będzie odbierać komunikaty logowania z frameworku.
Mogą one 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ą projektu Cast:
Nakładka wprowadzająca: Klasa
GCKCastContextma metodępresentCastInstructionsViewControllerOnceWithCastButton, która może służyć do wyróżnienia przycisku Cast przy pierwszym udostępnieniu Web Receiver. Aplikacja nadawcy może dostosować tekst, położenie tekstu tytułu i przycisku Zamknij.Przycisk przesyłania: Od wersji 4.6.0 pakietu Cast iOS Sender SDK przycisk przesyłania jest zawsze widoczny gdy urządzenie nadawcy jest połączone z Wi-Fi. Gdy użytkownik po raz pierwszy kliknie przycisk Cast po początkowym uruchomieniu aplikacji, pojawi się okno uprawnień, w którym użytkownik może przyznać aplikacji dostęp do urządzeń w sieci lokalnej. Następnie, gdy użytkownik kliknie przycisk Cast, wyświetli się okno Cast z listą wykrytych urządzeń. Gdy użytkownik kliknie przycisk Cast, gdy urządzenie jest połączone, wyświetlą się bieżące metadane multimediów (np. tytuł, nazwa studia nagrań i miniatura) lub użytkownik będzie mógł odłączyć się od urządzenia przesyłającego. Gdy użytkownik kliknie przycisk Cast, gdy nie ma dostępnych urządzeń, wyświetli się ekran z informacjami o tym, dlaczego nie można znaleźć urządzeń, i o tym, jak rozwiązać ten problem.
Minikontroler: gdy użytkownik przesyła treści i opuści stronę z 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.
Rozszerzony kontroler: gdy użytkownik przesyła treści, a następnie kliknie powiadomienie o multimediach lub minikontroler, uruchomi się rozszerzony kontroler, który wyświetla metadane aktualnie odtwarzanych multimediów i udostępnia kilka przycisków do sterowania odtwarzaniem multimediów.
Dodawanie przycisku Cast
Framework udostępnia komponent przycisku Cast jako podklasę UIButton. Można go dodać do paska tytułu aplikacji, umieszczając go w 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 spowoduje otwarcie okna przesyłania udostępnianego przez framework.
GCKUICastButton
można też dodać bezpośrednio do storyboardu.
Konfigurowanie wykrywania urządzeń
W frameworku wykrywanie urządzeń odbywa się automatycznie. Nie musisz wyraźnie rozpoczynać ani zatrzymywać procesu wykrywania, chyba że implementujesz niestandardowy interfejs.
Wykrywanie w frameworku jest zarządzane przez klasę
GCKDiscoveryManager,
która jest właściwością
GCKCastContext. Framework udostępnia domyślny komponent okna przesyłania do wybierania urządzeń i sterowania nimi. 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 obejmuje kroki połączenia z urządzeniem, uruchomienia (lub dołączenia) aplikacji Web Receiver, połączenia z tą aplikacją i zainicjowania kanału sterowania multimediami. Więcej informacji o sesjach Cast i cyklu życia Web Receiver znajdziesz w przewodniku Cykl życia aplikacji Web Receiver .
Sesje są zarządzane przez klasę
GCKSessionManager,
która jest właściwością
GCKCastContext.
Poszczególne sesje są reprezentowane przez podklasy klasy
GCKSession. Na przykład
GCKCastSession
reprezentuje sesje z urządzeniami Cast. Możesz uzyskać dostęp do aktualnie aktywnej sesji Cast (jeśli taka istnieje) jako właściwość currentCastSession klasy GCKSessionManager.
Interfejs
GCKSessionManagerListener
może służyć do monitorowania zdarzeń sesji, takich jak tworzenie sesji,
zawieszanie, wznawianie i kończenie. Framework 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/nagłym zakończeniu działania aplikacji, gdy sesja była aktywna).
Jeśli używasz okna przesyłania, sesje są tworzone i zamykane automatycznie w odpowiedzi na gesty użytkownika. W przeciwnym razie aplikacja może wyraźnie rozpoczynać i kończyć
sesje za pomocą metod w
GCKSessionManager.
Jeśli aplikacja musi wykonać specjalne przetwarzanie w odpowiedzi na zdarzenia cyklu życia, może zarejestrować co najmniej 1 instancję GCKSessionManagerListener w
GCKSessionManager. GCKSessionManagerListener to protokół, który definiuje wywołania zwrotne dla takich zdarzeń jak rozpoczęcie i zakończenie sesji itp.
Przeniesienie odtwarzania
Zachowanie stanu sesji jest podstawą przeniesienia odtwarzania, w którym 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 przesyłające z najnowszym oprogramowaniem układowym może służyć jako źródło lub miejsce docelowe w przypadku przeniesienia odtwarzania.
Aby uzyskać nowe urządzenie docelowe podczas przeniesienia odtwarzania, użyj właściwości
GCKCastSession#device
podczas wywołania zwrotnego
[sessionManager:didResumeCastSession:].
Więcej informacji znajdziesz w artykule Przeniesienie odtwarzania na Web Receiver.
Automatyczne ponowne łączenie
Framework 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,
- przywracanie połączenia po uśpieniu urządzenia,
- przywracanie połączenia po przejściu aplikacji do działania w tle,
- przywracanie połączenia po awarii aplikacji.
Jak działa sterowanie multimediami
Jeśli sesja Cast zostanie nawiązana z aplikacją Web Receiver, która obsługuje przestrzeń nazw multimediów
, framework automatycznie utworzy instancję
GCKRemoteMediaClient
. Można uzyskać do niej dostęp jako właściwość
remoteMediaClient instancji
GCKCastSession
.
Wszystkie metody w GCKRemoteMediaClient, które wysyłają żądania do Web Receiver
zwracają
GCKRequest obiekt, który
może sł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ć współdzielona przez wiele części aplikacji, a niektóre wewnętrzne komponenty platformy, takie jak okno przesyłania i miniodtwarzacz, rzeczywiście współdzielą tę instancję. W tym celu GCKRemoteMediaClient
obsługuje 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 buforowanie.
Wczytywanie multimediów
Aby wczytać element multimedialny, utwórz instancję
GCKMediaInformation
za pomocą metadanych multimediów. Następnie pobierz bieżący
GCKCastSession i
użyj jego
GCKRemoteMediaClient, aby wczytać multimedia w aplikacji odbiorcy. Następnie możesz użyć GCKRemoteMediaClient
do sterowania aplikacją odtwarzacza multimediów działającą na odbiorcy, 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 multimediów.
Format wideo 4K
Aby określić format wideo multimediów, użyj właściwości videoInfo klasy
GCKMediaStatus
aby uzyskać bieżącą instancję
GCKVideoInfo.
Ta instancja 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 wyliczenia GCKVideoInfoHDRType.
Dodaj minikontrolery
Zgodnie z listą kontrolną projektu Cast, aplikacja nadawcy powinna udostępniać trwały element sterujący, czyli miniodtwarzacz, który powinien pojawiać się, gdy użytkownik opuści stronę z treścią. Minikontroler zapewnia natychmiastowy dostęp i widoczne przypomnienie o bieżącej sesji Cast.
Framework Cast udostępnia pasek sterowania,
GCKUIMiniMediaControlsViewController,
który można dodać do scen, w których chcesz wyświetlać miniodtwarzacz.
Gdy aplikacja nadawcy odtwarza transmisję audio lub wideo na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/pauzy w miniodtwarzaczu.
Więcej informacji o tym, jak aplikacja nadawcy może skonfigurować wygląd widżetów Cast, znajdziesz w artykule Dostosowywanie interfejsu aplikacji nadawcy na iOS.
Miniodtwarzacz można dodać do aplikacji nadawcy na 2 sposoby:
- Pozwól frameworkowi Cast zarządzać układem miniodtwarzacza, umieszczając istniejący kontroler widoku w jego własnym kontrolerze widoku.
- Samodzielnie zarządzaj układem widżetu miniodtwarzacza, dodając go do istniejącego kontrolera widoku, podając podwidok w storyboardzie.
Umieszczanie za pomocą GCKUICastContainerViewController
Pierwszy sposób polega na użyciu
GCKUICastContainerViewController
, który umieszcza inny kontroler widoku i dodaje u dołu
GCKUIMiniMediaControlsViewController. To podejście jest ograniczone, ponieważ nie można dostosować animacji ani skonfigurować działania kontrolera widoku kontenera.
Ten pierwszy sposób jest zwykle stosowany 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 minikontrolera bezpośrednio do istniejącego kontrolera widoku za pomocą 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 podwidoku:
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
The
GCKUIMiniMediaControlsViewControllerDelegate
tells the host view controller when the mini controller should be visible:
func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController, shouldAppear _: Bool) { updateControlBarsVisibility() }
- (void)miniMediaControlsViewController: (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController shouldAppear:(BOOL)shouldAppear { [self updateControlBarsVisibility]; }
Dodaj rozszerzony kontroler
Zgodnie z listą kontrolną projektu Google Cast aplikacja nadawcy musi udostępniać rozszerzony odtwarzacz przesyłanych multimediów. Rozszerzony kontroler to wersja minikontrolera na pełnym ekranie.
Rozszerzony kontroler to widok na pełnym ekranie, który umożliwia pełne sterowanie odtwarzaniem multimediów na urządzeniu zdalnym. Ten widok powinien umożliwiać aplikacji przesyłającej zarządzanie każdym aspektem sesji przesyłania, z wyjątkiem sterowania głośnością Web Receiver i cyklem życia sesji (łączenie/zatrzymywanie przesyłania). Zawiera też wszystkie informacje o stanie sesji multimediów (okładka, tytuł, podtytuł itp.).
Funkcjonalność tego widoku jest implementowana przez klasę
GCKUIExpandedMediaControlsViewController.
Najpierw musisz włączyć domyślny rozszerzony odtwarzacz 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; .. }
Aby wczytać rozszerzony odtwarzacz, gdy użytkownik zacznie przesyłać film, dodaj do kontrolera widoku ten kod:
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]; }
Rozszerzony kontroler zostanie też uruchomiony automatycznie, gdy użytkownik kliknie minikontroler.
Gdy aplikacja nadawcy odtwarza transmisję audio lub wideo na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/pauzy w rozszerzonym kontrolerze.
Więcej informacji o tym, jak aplikacja nadawcy może skonfigurować wygląd widżetów Cast, znajdziesz w artykule Stosowanie stylów niestandardowych w aplikacji na iOS.
Sterowanie głośnością
Framework Cast automatycznie zarządza głośnością aplikacji nadawcy. Framework automatycznie synchronizuje się z głośnością Web Receiver 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 nadawcy mogą służyć do zmiany
głośności sesji Cast na Web Receiver 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ługa błędów
Bardzo ważne jest, aby aplikacje nadawcy obsługiwały wszystkie wywołania zwrotne błędów i decydowały o najlepszej reakcji na każdym etapie cyklu życia Cast. Aplikacja może wyświetlać użytkownikowi okna błędów lub zakończyć sesję Cast.
Pamiętaj, że niektóre błędy, w tym
GCKErrorCode
GCKErrorCodeCancelled, są zamierzonym działaniem.
Nie próbuj ponownie nawiązywać połączenia, które nie powiodło się z powodu GCKErrorCodeCancelled.
Może to spowodować nieoczekiwane zachowanie.
Logowanie
GCKLogger
to singleton używany przez framework do logowania. Użyj
GCKLoggerDelegate
, aby dostosować sposób obsługi komunikatów logowania.
Za pomocą GCKLogger pakiet SDK generuje dane logowania w postaci komunikatów debugowania, błędów i ostrzeżeń. Te komunikaty logowania ułatwiają debugowanie i są przydatne podczas rozwiązywania problemów i identyfikowania ich przyczyn. Domyślnie dane logowania są pomijane, ale po przypisaniu GCKLoggerDelegate aplikacja nadawcy może otrzymywać te komunikaty z pakietu SDK i zapisywać 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 (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 logowania 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.