In diesem Entwicklerleitfaden wird beschrieben, wie Sie Ihrer iOS Sender-App mit dem iOS Sender SDK Google Cast-Unterstützung hinzufügen.
Das Mobilgerät oder der Laptop ist der Absender, der die Wiedergabe steuert, und das Google Cast-Gerät ist der Empfänger, der die Inhalte auf dem Fernseher anzeigt.
Das Sender-Framework bezieht sich auf das Binärprogramm der Cast-Klassenbibliothek und die zugehörigen Ressourcen, die zur Laufzeit auf dem Sender vorhanden sind. Die Sender-App oder Cast-App bezieht sich auf eine App, die auch auf dem Absendergerät ausgeführt wird. Die Web Receiver App ist die HTML-Anwendung, die auf dem Web Receiver ausgeführt wird.
Das Sender-Framework verwendet ein asynchrones Callback-Design, um die Sender-App über Ereignisse zu informieren und zwischen verschiedenen Status des Cast-App-Lebenszyklus zu wechseln.
Anwendungsfluss
Die folgenden Schritte beschreiben den typischen allgemeinen Ausführungsablauf für eine Sender-iOS-App:
- Das Cast-Framework startet
GCKDiscoveryManagerbasierend auf den inGCKCastOptionsangegebenen Eigenschaften, um mit der Suche nach Geräten zu beginnen. - Wenn der Nutzer auf das Cast-Symbol klickt, wird im Framework das Cast-Dialogfeld mit der Liste der erkannten Übertragungsgeräte angezeigt.
- Wenn der Nutzer ein Übertragungsgerät auswählt, versucht das Framework, die Web Receiver-App auf dem Übertragungsgerät zu starten.
- Das Framework ruft Callbacks in der Sender-App auf, um zu bestätigen, dass die Web Receiver-App gestartet wurde.
- Das Framework stellt einen Kommunikationskanal zwischen dem Absender und Web Receiver-Apps her.
- Das Framework verwendet den Kommunikationskanal, um die Medienwiedergabe auf dem Web Receiver zu laden und zu steuern.
- Das Framework synchronisiert den Status der Medienwiedergabe zwischen Sender und Web Receiver: Wenn der Nutzer Aktionen auf der Sender-Benutzeroberfläche ausführt, leitet das Framework diese Mediensteuerungsanfragen an den Web Receiver weiter. Wenn der Web Receiver Medienstatusaktualisierungen sendet, aktualisiert das Framework den Status der Sender-Benutzeroberfläche.
- Wenn der Nutzer auf das Cast-Symbol klickt, um die Verbindung zum Übertragungsgerät zu trennen, trennt das Framework die Sender-App vom Web Receiver.
Um Fehler bei Ihrem Sender zu beheben, müssen Sie Logging aktivieren.
Eine vollständige Liste aller Klassen, Methoden und Ereignisse im Google Cast iOS-Framework finden Sie in der Google Cast iOS API-Referenz. In den folgenden Abschnitten werden die Schritte zur Integration von Cast in Ihre iOS-App beschrieben.
Methoden aus dem Hauptthread aufrufen
Cast-Kontext initialisieren
Das Cast-Framework hat ein globales Singleton-Objekt, das GCKCastContext, das alle Aktivitäten des Frameworks koordiniert. Dieses Objekt muss früh im Lebenszyklus der Anwendung initialisiert werden, in der Regel in der Methode -[application:didFinishLaunchingWithOptions:] des App-Delegaten, damit die automatische Wiederaufnahme der Sitzung beim Neustart der Sender-App ordnungsgemäß ausgelöst werden kann.
Beim Initialisieren von GCKCastContext muss ein GCKCastOptions-Objekt angegeben werden.
Diese Klasse enthält Optionen, die sich auf das Verhalten des Frameworks auswirken. Die wichtigste davon ist die Web Receiver-Anwendungs-ID, die zum Filtern von Erkennungsergebnissen und zum Starten der Web Receiver-App verwendet wird, wenn eine Cast-Sitzung gestartet wird.
Die Methode -[application:didFinishLaunchingWithOptions:] ist auch ein guter Ort, um einen Logging-Delegate einzurichten, der die Logging-Nachrichten vom Framework empfängt.
Sie können für das Debugging und die Fehlerbehebung nützlich sein.
@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
Die Cast-UX-Widgets
Das Cast iOS SDK bietet die folgenden Widgets, die der Cast Design Checklist entsprechen:
Einführungs-Overlay: Die Klasse
GCKCastContexthat eine Methode,presentCastInstructionsViewControllerOnceWithCastButton, mit der das Cast-Symbol beim ersten Mal, wenn ein Web Receiver verfügbar ist, hervorgehoben werden kann. In der Sender-App können der Text und die Position des Titeltexts sowie die Schaltfläche „Schließen“ angepasst werden.Cast-Symbol: Ab Version 4.6.0 des Cast iOS Sender SDK ist das Cast-Symbol immer sichtbar, wenn das Sendergerät mit dem WLAN verbunden ist. Wenn der Nutzer nach dem ersten Starten der App zum ersten Mal auf die Cast-Schaltfläche tippt, wird ein Berechtigungsdialogfeld angezeigt, in dem er der App den Zugriff auf das lokale Netzwerk für Geräte im Netzwerk gewähren kann. Wenn der Nutzer anschließend auf das Cast-Symbol tippt, wird ein Cast-Dialogfeld mit den gefundenen Geräten angezeigt. Wenn der Nutzer auf die Schaltfläche „Streamen“ tippt, während das Gerät verbunden ist, werden die aktuellen Media-Metadaten (z. B. Titel, Name des Aufnahmestudios und ein Thumbnailbild) angezeigt. Der Nutzer kann auch die Verbindung zum Übertragungsgerät trennen. Wenn der Nutzer auf den Übertragungssymbol tippt, während keine Geräte verfügbar sind, wird ein Bildschirm angezeigt, auf dem er Informationen dazu erhält, warum keine Geräte gefunden werden können und wie er das Problem beheben kann.
Mini-Controller: Wenn der Nutzer Inhalte streamt und die Seite mit den aktuellen Inhalten oder den erweiterten Controller in der Sender-App verlassen hat, wird der Mini-Controller unten auf dem Bildschirm angezeigt. So kann der Nutzer die Metadaten der aktuell gestreamten Medien sehen und die Wiedergabe steuern.
Erweiterter Controller: Wenn der Nutzer Inhalte streamt und auf die Medienbenachrichtigung oder den Mini-Controller klickt, wird der erweiterte Controller gestartet. Dort werden die Metadaten der aktuell wiedergegebenen Medien angezeigt und es gibt mehrere Schaltflächen zur Steuerung der Medienwiedergabe.
Cast-Symbol hinzufügen
Das Framework bietet eine Cast-Symbol-Komponente als UIButton-Unterklasse. Sie kann der Titelleiste der App hinzugefügt werden, indem sie in UIBarButtonItem eingeschlossen wird. In einer typischen UIViewController-Unterklasse kann ein Cast-Button so installiert werden:
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];
Standardmäßig wird durch Tippen auf den Button das vom Framework bereitgestellte Cast-Dialogfeld geöffnet.
GCKUICastButton können auch direkt dem Storyboard hinzugefügt werden.
Geräteerkennung konfigurieren
Im Framework erfolgt die Geräteerkennung automatisch. Sie müssen den Erkennungsprozess nicht explizit starten oder beenden, es sei denn, Sie implementieren eine benutzerdefinierte Benutzeroberfläche.
Die Erkennung im Framework wird von der Klasse GCKDiscoveryManager verwaltet, die eine Property von GCKCastContext ist. Das Framework bietet eine standardmäßige Cast-Dialogfeldkomponente für die Geräteauswahl und ‑steuerung. Die Geräteliste ist lexikografisch nach dem Anzeigenamen des Geräts sortiert.
So funktioniert die Sitzungsverwaltung
Im Cast SDK wird das Konzept einer Cast-Sitzung eingeführt. Die Einrichtung einer solchen Sitzung umfasst die Schritte zum Herstellen einer Verbindung zu einem Gerät, zum Starten (oder Beitreten) einer Web Receiver-App, zum Herstellen einer Verbindung zu dieser App und zum Initialisieren eines Mediensteuerung-Channels. Weitere Informationen zu Cast-Sitzungen und zum Web Receiver-Lebenszyklus finden Sie im Leitfaden zum Anwendungslebenszyklus für Web Receiver.
Sitzungen werden von der Klasse GCKSessionManager verwaltet, die eine Eigenschaft von GCKCastContext ist.
Einzelne Sitzungen werden durch Unterklassen der Klasse GCKSession dargestellt, z. B. GCKCastSession für Sitzungen mit Cast-Geräten. Sie können auf die aktuell aktive Cast-Sitzung (falls vorhanden) als currentCastSession-Property von GCKSessionManager zugreifen.
Über die GCKSessionManagerListener-Schnittstelle können Sie Sitzungsereignisse wie das Erstellen, Anhalten, Fortsetzen und Beenden von Sitzungen überwachen. Das Framework setzt Sitzungen automatisch aus, wenn die Sender-App in den Hintergrund wechselt, und versucht, sie fortzusetzen, wenn die App in den Vordergrund zurückkehrt oder nach einem abnormalen/abrupten Beenden der App neu gestartet wird, während eine Sitzung aktiv war.
Wenn das Cast-Dialogfeld verwendet wird, werden Sitzungen automatisch als Reaktion auf Nutzeraktionen erstellt und beendet. Andernfalls kann die App Sitzungen explizit über Methoden in GCKSessionManager starten und beenden.
Wenn die App als Reaktion auf Ereignisse im Sitzungslebenszyklus eine spezielle Verarbeitung durchführen muss, kann sie eine oder mehrere GCKSessionManagerListener-Instanzen bei der GCKSessionManager registrieren. GCKSessionManagerListener ist ein Protokoll, das Callbacks für Ereignisse wie Sitzungsbeginn, Sitzungsende usw. definiert.
Stream-Übertragung
Das Beibehalten des Sitzungsstatus ist die Grundlage für die Stream-Übertragung, bei der Nutzer vorhandene Audio- und Videostreams per Sprachbefehl, über die Google Home App oder über Smart Displays auf andere Geräte übertragen können. Die Medienwiedergabe wird auf einem Gerät (der Quelle) beendet und auf einem anderen Gerät (dem Ziel) fortgesetzt. Jedes Cast-Gerät mit der neuesten Firmware kann als Quelle oder Ziel bei einer Stream-Übertragung dienen.
Wenn Sie das neue Zielgerät während der Stream-Übertragung erhalten möchten, verwenden Sie die Eigenschaft GCKCastSession#device während des [sessionManager:didResumeCastSession:]-Callbacks.
Weitere Informationen finden Sie unter Stream-Übertragung auf Web Receiver.
Automatische Wiederverbindung
Das Cast-Framework fügt eine Logik zum erneuten Verbinden hinzu, um die erneute Verbindung in vielen subtilen Grenzfall-Szenarien automatisch zu verarbeiten, z. B.:
- Nach einem vorübergehenden WLAN-Verlust wiederherstellen
- Aus dem Geräteruhemodus reaktivieren
- Wiederherstellen nach dem Schließen der App im Hintergrund
- Wiederherstellen, wenn die App abgestürzt ist
Funktionsweise der Mediensteuerung
Wenn eine Cast-Sitzung mit einer Web Receiver-App eingerichtet wird, die den Media-Namespace unterstützt, wird automatisch eine Instanz von GCKRemoteMediaClient vom Framework erstellt. Sie kann als remoteMediaClient-Property der GCKCastSession-Instanz aufgerufen werden.
Alle Methoden für GCKRemoteMediaClient, mit denen Anfragen an den Web Receiver gesendet werden, geben ein GCKRequest-Objekt zurück, mit dem die Anfrage verfolgt werden kann. Diesem Objekt kann ein GCKRequestDelegate zugewiesen werden, um Benachrichtigungen über das endgültige Ergebnis des Vorgangs zu erhalten.
Es wird erwartet, dass die Instanz von GCKRemoteMediaClient von mehreren Teilen der App gemeinsam verwendet wird. Tatsächlich verwenden einige interne Komponenten des Frameworks wie der Cast-Dialog und die Mini-Media-Steuerelemente die Instanz gemeinsam. Dazu unterstützt GCKRemoteMediaClient die Registrierung mehrerer GCKRemoteMediaClientListener.
Media-Metadaten festlegen
Die Klasse GCKMediaMetadata enthält Informationen zu einem Media-Element, das du streamen möchtest. Im folgenden Beispiel wird eine neue GCKMediaMetadata-Instanz eines Films erstellt und der Titel, Untertitel, Name des Aufnahmestudios und zwei Bilder werden festgelegt.
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]];
Informationen zur Verwendung von Bildern mit Medienmetadaten finden Sie im Abschnitt Bildauswahl und ‑caching.
Medien laden
Um ein Media-Element zu laden, erstellen Sie eine GCKMediaInformation-Instanz mit den Metadaten des Media-Elements. Rufen Sie dann das aktuelle GCKCastSession ab und verwenden Sie dessen GCKRemoteMediaClient, um die Medien in die Receiver-App zu laden. Sie können dann GCKRemoteMediaClient verwenden, um eine Mediaplayer-App zu steuern, die auf dem Receiver ausgeführt wird, z. B. zum Abspielen, Pausieren und Beenden.
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; }
Weitere Informationen finden Sie im Abschnitt Mediatracks verwenden.
4K-Videoformat
Verwenden Sie die videoInfo-Property von GCKMediaStatus, um das Videoformat Ihrer Media zu ermitteln und die aktuelle Instanz von GCKVideoInfo abzurufen.
Diese Instanz enthält den Typ des HDR-TV-Formats sowie die Höhe und Breite in Pixeln. Varianten des 4K-Formats werden in der Property hdrType durch die Enum-Werte GCKVideoInfoHDRType angegeben.
Mini-Controller hinzufügen
Gemäß der Checkliste für das Cast-Design sollte eine Sender-App ein dauerhaftes Steuerelement, den Mini-Controller, bereitstellen, der angezeigt wird, wenn der Nutzer die aktuelle Inhaltsseite verlässt. Der Mini-Controller bietet sofortigen Zugriff und eine sichtbare Erinnerung an die aktuelle Cast-Sitzung.
Das Cast-Framework bietet eine Steuerleiste, GCKUIMiniMediaControlsViewController, die den Szenen hinzugefügt werden kann, in denen der Mini-Controller angezeigt werden soll.
Wenn in Ihrer Sender-App ein Video- oder Audio-Livestream wiedergegeben wird, zeigt das SDK im Mini-Controller automatisch eine Schaltfläche zum Starten/Beenden anstelle der Schaltfläche zum Starten/Pausieren an.
Unter Benutzeroberfläche des iOS-Absenders anpassen erfahren Sie, wie Ihre Sender-App das Aussehen der Cast-Widgets konfigurieren kann.
Es gibt zwei Möglichkeiten, den Mini-Controller einer Sender-App hinzuzufügen:
- Lassen Sie das Cast-Framework das Layout des Mini-Controllers verwalten, indem Sie Ihren vorhandenen Ansichtscontroller mit einem eigenen Ansichtscontroller umschließen.
- Sie können das Layout des Mini-Controller-Widgets selbst verwalten, indem Sie es Ihrem vorhandenen Ansichts-Controller hinzufügen und eine untergeordnete Ansicht im Storyboard bereitstellen.
Mit GCKUICastContainerViewController umschließen
Die erste Möglichkeit besteht darin, GCKUICastContainerViewController zu verwenden, das einen anderen Ansichtscontroller umschließt und unten ein GCKUIMiniMediaControlsViewController hinzufügt. Dieser Ansatz ist insofern eingeschränkt, als Sie die Animation nicht anpassen und das Verhalten des Container-View-Controllers nicht konfigurieren können.
Die erste Methode wird in der Regel in der Methode -[application:didFinishLaunchingWithOptions:] des App-Delegaten ausgeführt:
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
In vorhandenen Ansichtscontroller einbetten
Die zweite Möglichkeit besteht darin, den Mini-Controller direkt zu Ihrem vorhandenen Ansichts-Controller hinzuzufügen. Verwenden Sie dazu createMiniMediaControlsViewController, um eine GCKUIMiniMediaControlsViewController-Instanz zu erstellen und sie dann dem Container-Ansichts-Controller als untergeordnete Ansicht hinzuzufügen.
Richten Sie den Ansichtscontroller im App-Delegate ein:
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; }
Erstellen Sie in Ihrem Root-Ansichtscontroller eine GCKUIMiniMediaControlsViewController-Instanz und fügen Sie sie dem Container-Ansichtscontroller als untergeordnete Ansicht hinzu:
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
Die
GCKUIMiniMediaControlsViewControllerDelegate
teilt dem Host-Ansichtscontroller mit, wann der Mini-Controller sichtbar sein soll:
func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController, shouldAppear _: Bool) { updateControlBarsVisibility() }
- (void)miniMediaControlsViewController: (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController shouldAppear:(BOOL)shouldAppear { [self updateControlBarsVisibility]; }
Erweiterten Controller hinzufügen
Die Google Cast-Design-Checkliste erfordert, dass eine Sender-App einen erweiterten Controller für die übertragenen Medien bereitstellt. Der maximierte Controller ist eine Vollbildversion des Mini-Controllers.
Der erweiterte Controller ist eine Vollbildansicht, die die vollständige Steuerung der Medienwiedergabe auf dem Remote-Gerät ermöglicht. In dieser Ansicht sollte eine Casting-App jeden verwaltbaren Aspekt einer Cast-Sitzung verwalten können, mit Ausnahme der Lautstärkeregelung des Web Receivers und des Sitzungslebenszyklus (Verbinden/Beenden des Castings). Außerdem werden alle Statusinformationen zur Mediensitzung (Artwork, Titel, Untertitel usw.) bereitgestellt.
Die Funktionalität dieser Ansicht wird von der Klasse GCKUIExpandedMediaControlsViewController implementiert.
Als Erstes müssen Sie den standardmäßig maximierten Controller im Cast-Kontext aktivieren. Ändern Sie den App-Delegaten, um den standardmäßig maximierten Controller zu aktivieren:
func applicationDidFinishLaunching(_ application: UIApplication) { .. GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true ... }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES; .. }
Fügen Sie Ihrem Ansichtscontroller den folgenden Code hinzu, um den erweiterten Controller zu laden, wenn der Nutzer mit dem Streamen eines Videos beginnt:
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]; }
Der erweiterte Controller wird auch automatisch gestartet, wenn der Nutzer auf den Mini-Controller tippt.
Wenn in Ihrer Sender-App ein Video- oder Audio-Livestream wiedergegeben wird, wird im erweiterten Controller automatisch ein Start-/Stopp-Button anstelle des Start-/Pause-Buttons angezeigt.
Unter Benutzerdefinierte Stile auf Ihre iOS-App anwenden erfahren Sie, wie Ihre Sender-App das Aussehen der Cast-Widgets konfigurieren kann.
Lautstärkeregelung
Das Cast-Framework verwaltet die Lautstärke für die Sender-App automatisch. Das Framework wird automatisch mit der Lautstärke des Web Receivers für die bereitgestellten UI-Widgets synchronisiert. Verwenden Sie GCKUIDeviceVolumeController, um einen von der App bereitgestellten Schieberegler zu synchronisieren.
Lautstärkeregelung über physische Tasten
Mit den physischen Lautstärketasten auf dem Sendergerät kann die Lautstärke der Cast-Sitzung auf dem Web Receiver über das Flag physicalVolumeButtonsWillControlDeviceVolume in GCKCastOptions geändert werden, das in GCKCastContext festgelegt ist.
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];
Fehler verarbeiten
Es ist sehr wichtig, dass Sender-Apps alle Fehler-Callbacks verarbeiten und die beste Reaktion für jede Phase des Cast-Lebenszyklus festlegen. Die App kann dem Nutzer Fehlerdialogfelder anzeigen oder die Cast-Sitzung beenden.
Einige Fehler, einschließlich des
GCKErrorCode
GCKErrorCodeCancelled, sind beabsichtigt.
Versuchen Sie nicht, eine Verbindung, die mit GCKErrorCodeCancelled fehlgeschlagen ist, noch einmal herzustellen.
Andernfalls kann es zu unerwartetem Verhalten kommen.
Logging
GCKLogger
ist ein Singleton, das vom Framework für das Logging verwendet wird. Mit dem GCKLoggerDelegate können Sie anpassen, wie Protokollnachrichten verarbeitet werden.
Mit GCKLogger erzeugt das SDK Log-Ausgaben in Form von Debug-Meldungen, Fehlern und Warnungen. Diese Logmeldungen helfen beim Debugging und sind nützlich für die Fehlerbehebung und die Identifizierung von Problemen. Standardmäßig wird die Logausgabe unterdrückt. Durch Zuweisen eines GCKLoggerDelegate kann die Sender-App diese Nachrichten jedoch vom SDK empfangen und in der Systemkonsole protokollieren.
@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
Wenn Sie auch Debug- und ausführliche Meldungen aktivieren möchten, fügen Sie diese Zeile nach dem Festlegen des Delegates (siehe oben) in den Code ein:
let filter = GCKLoggerFilter.init() filter.minimumLevel = GCKLoggerLevel.verbose GCKLogger.sharedInstance().filter = filter
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init]; [filter setMinimumLevel:GCKLoggerLevelVerbose]; [GCKLogger sharedInstance].filter = filter;
Sie können auch die Logmeldungen filtern, die von GCKLogger generiert werden.
Legen Sie die Mindestprotokollierungsebene pro Klasse fest, z. B.:
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;
Die Klassennamen können entweder Literale oder Glob-Muster sein, z. B. GCKUI\* und GCK\*Session.