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 Absender-Framework bezieht sich auf das Binärprogramm der Cast-Klassenbibliothek und die zugehörigen Ressourcen, die zur Laufzeit auf dem Absender vorhanden sind. Die Absender-App oder Cast-App ist eine App, die auch auf dem Absender 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 Zuständen 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
GCKDiscoveryManager
basierend auf den inGCKCastOptions
angegebenen Eigenschaften, um mit der Suche nach Geräten zu beginnen. - Wenn der Nutzer auf die Schaltfläche „Streamen“ klickt, wird im Framework der Cast-Dialog mit der Liste der erkannten Cast-Gerä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, übergibt das Framework diese Mediensteuerungsanfragen an den Web Receiver. Wenn der Web Receiver Medienstatus-Updates sendet, aktualisiert das Framework den Status der Sender-Benutzeroberfläche.
- Wenn der Nutzer auf die Schaltfläche „Streamen“ klickt, um die Verbindung zum Cast-Gerä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 Google 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 beim Starten einer Cast-Sitzung verwendet wird.
Die Methode -[application:didFinishLaunchingWithOptions:]
ist auch ein guter Ort, um ein Logging-Delegate einzurichten, das die Logging-Nachrichten vom Framework empfängt.
Sie können bei der Fehlerbehebung hilfreich 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
GCKCastContext
hat eine Methode,presentCastInstructionsViewControllerOnceWithCastButton
, mit der die Cast-Schaltfläche beim ersten Mal, wenn ein Web Receiver verfügbar ist, hervorgehoben werden kann. Die Absender-App kann den Text, die Position des Titeltexts und die Schaltfläche „Schließen“ anpassen.Schaltfläche „Streamen“: Ab Version 4.6.0 des Cast iOS Sender SDK ist die Schaltfläche „Streamen“ immer sichtbar, wenn das Sendergerät mit einem 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 die Schaltfläche zum Streamen tippt, wird ein 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 oder der Nutzer kann die Verbindung zum Übertragungsgerät trennen. Wenn der Nutzer auf die Schaltfläche „Streamen“ tippt, aber keine Geräte verfügbar sind, wird ein Bildschirm mit Informationen dazu angezeigt, warum keine Geräte gefunden werden und wie das Problem behoben werden 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.
Erweiterte Steuerung: Wenn der Nutzer Inhalte streamt und auf die Medienbenachrichtigung oder die Mini-Steuerung klickt, wird die erweiterte Steuerung gestartet. Dort werden die Metadaten der aktuell wiedergegebenen Medien angezeigt und es gibt mehrere Schaltflächen zum Steuern der Medienwiedergabe.
Cast-Symbol hinzufügen
Das Framework bietet eine Cast-Schaltflächenkomponente als UIButton
-Unterklasse. Sie kann der Titelleiste der App hinzugefügt werden, indem sie in UIBarButtonItem
eingeschlossen wird. In einer typischen UIViewController
-Unterklasse kann eine Cast-Schaltfläche 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];
Wenn Sie auf die Schaltfläche tippen, wird standardmäßig 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 Standardkomponente für das Cast-Dialogfeld zur 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 Media Control-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 ein Attribut 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 unterbricht Sitzungen automatisch, 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 der Cast-Dialog 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.
Verwende das Attribut GCKCastSession#device
während des [sessionManager:didResumeCastSession:]
-Callbacks, um das neue Zielgerät während der Streamübertragung abzurufen.
Weitere Informationen finden Sie unter Streamübertragung auf Web Receiver.
Automatische Wiederverbindung
Das Cast-Framework fügt eine Logik für die erneute Verbindung hinzu, um die erneute Verbindung in vielen subtilen Grenzfall-Szenarien automatisch zu verarbeiten, z. B.:
- Nach vorübergehendem WLAN-Verlust wiederherstellen
- Aus dem Ruhezustand reaktivieren
- Wiederherstellen nach dem Schließen der App im Hintergrund
- Wiederherstellen nach einem App-Absturz
Funktionsweise der Mediensteuerung
Wenn eine Cast-Sitzung mit einer Web Receiver-App hergestellt 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
, die Anfragen an den Web Receiver senden, 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 genutzt wird. Tatsächlich nutzen 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
s.
Medienmetadaten 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 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 ‑zwischenspeicherung.
Medien laden
Um ein Media-Element zu laden, erstellen Sie eine GCKMediaInformation
-Instanz mit den Metadaten des Media-Elements. Rufen Sie dann die aktuelle GCKCastSession
ab und verwenden Sie deren GCKRemoteMediaClient
, um die Medien in die Receiver-App zu laden. Anschließend können Sie GCKRemoteMediaClient
verwenden, um eine auf dem Receiver ausgeführte Mediaplayer-App zu steuern, 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 Property videoInfo
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 deiner 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 iOS-Absender-UI anpassen erfahren Sie, wie Ihre Absender-App das Aussehen der Cast-Widgets konfigurieren kann.
Es gibt zwei Möglichkeiten, den Mini-Controller einer Sender-App hinzuzufügen:
- Lassen Sie das Layout des Mini-Controllers vom Cast-Framework verwalten, indem Sie Ihren vorhandenen Ansichts-Controller mit einem eigenen Ansichts-Controller 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 ist die Verwendung von GCKUICastContainerViewController
, das einen anderen View-Controller 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. Dazu erstellen Sie mit createMiniMediaControlsViewController
eine GCKUIMiniMediaControlsViewController
-Instanz und fügen sie dann dem Container-Ansichts-Controller als untergeordnete Ansicht hinzu.
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 Stamm-View-Controller eine GCKUIMiniMediaControlsViewController
-Instanz und fügen Sie sie dem Container-View-Controller 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]; }
Maximierten Controller hinzufügen
Gemäß der Google Cast-Design-Checkliste muss eine Sender-App eine erweiterte Steuerung für die übertragenen Medien bereitstellen. 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.
Zuerst müssen Sie den standardmäßigen erweiterten Controller im Cast-Kontext aktivieren. Ändern Sie das App-Delegate, um den standardmäßigen 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 maximierten 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 deiner Sender-App ein Video- oder Audio-Livestream wiedergegeben wird, zeigt das SDK im erweiterten Controller automatisch eine Schaltfläche zum Starten/Stoppen anstelle der Schaltfläche zum Starten/Pausieren an.
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 SDK verwaltet die Lautstärke für die Sender-App automatisch. Das Framework wird für die bereitgestellten UI-Widgets automatisch mit der Lautstärke des Web Receivers 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 geändert werden. Dazu muss das Flag physicalVolumeButtonsWillControlDeviceVolume
für GCKCastOptions
festgelegt werden, was auf dem GCKCastContext
erfolgt.
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.
Logging
GCKLogger
ist ein Singleton, das vom Framework für das Logging verwendet wird. Mit dem Befehl GCKLoggerDelegate
können Sie anpassen, wie Logmeldungen verarbeitet werden.
Mit GCKLogger
erzeugt das SDK Log-Ausgaben in Form von Debug-Meldungen, Fehlern und Warnungen. Diese Logmeldungen helfen bei der Fehlerbehebung und sind nützlich, um Probleme zu identifizieren. Standardmäßig wird die Protokollausgabe 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 dem Code hinzu, nachdem Sie den Delegaten festgelegt haben (siehe oben):
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
.