Cast in iOS-App einbinden

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 Sender, 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 die Binärdatei 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 ebenfalls auf dem Sender 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 Lebenszyklus der Cast-App zu wechseln.

Anwendungsfluss

In den folgenden Schritten wird der typische allgemeine Ausführungsablauf für eine iOS-Sender-App beschrieben:

  • Das Cast-Framework startet GCKDiscoveryManager basierend auf den in GCKCastOptions angegebenen Attributen, um nach Geräten zu suchen.
  • Wenn der Nutzer auf das Cast-Symbol klickt, wird im Framework das Cast-Dialogfeld mit der Liste der gefundenen Cast-Geräte angezeigt.
  • Wenn der Nutzer ein Übertragungsgerät auswählt, versucht das Framework, die Webempfänger-App auf dem Übertragungsgerät zu starten.
  • Das Framework ruft Callbacks in der Sender-App auf, um zu bestätigen, dass die Web-Empfänger-App gestartet wurde.
  • Das Framework erstellt einen Kommunikationskanal zwischen dem Sender und den Web-Empfänger-Apps.
  • Das Framework verwendet den Kommunikationskanal, um die Medienwiedergabe auf dem Webempfänger zu laden und zu steuern.
  • Das Framework synchronisiert den Status der Medienwiedergabe zwischen Sender und Web-Empfänger: Wenn der Nutzer Aktionen auf der Sender-UI ausführt, übergibt das Framework diese Mediensteuerungsanfragen an den Web-Empfänger. Wenn der Web-Empfänger Medienstatusaktualisierungen sendet, aktualisiert das Framework den Status der Sender-UI.
  • Wenn der Nutzer auf das Cast-Symbol klickt, um die Verbindung zum Cast-Gerät zu trennen, trennt das Framework die Verbindung der Sender-App zum Web-Receiver.

Wenn du Probleme mit deinem Sender beheben möchtest, musst du das Logging aktivieren.

Eine umfassende Liste aller Klassen, Methoden und Ereignisse im Google Cast iOS-Framework findest du in der Google Cast iOS API-Referenz. In den folgenden Abschnitten werden die Schritte zum Einbinden von Cast in Ihre iOS-App beschrieben.

Methoden aus dem Hauptthread aufrufen

Cast-Kontext initialisieren

Das Cast-Framework hat ein globales Singleton-Objekt, den GCKCastContext, der alle Aktivitäten des Frameworks koordiniert. Dieses Objekt muss früh im Lebenszyklus der Anwendung initialisiert werden, normalerweise in der Methode -[application:didFinishLaunchingWithOptions:] des App-Delegaten, damit die automatische Fortsetzung der Sitzung beim Neustart der Sender-App ordnungsgemäß ausgelöst werden kann.

Bei der Initialisierung 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 der Suchergebnisse und zum Starten der Web-Receiver-App verwendet wird, wenn eine Übertragungssitzung gestartet wird.

Die Methode -[application:didFinishLaunchingWithOptions:] ist auch ein guter Ort, um einen Logging-Delegaten einzurichten, der die Logeinträge vom Framework empfängt. Diese können für das Debugging und die Fehlerbehebung nützlich sein.

Swift
@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)
    }
  }
}
Objective-C

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

Cast-UX-Widgets

Das Cast iOS SDK bietet die folgenden Widgets, die der Cast-Design-Checkliste entsprechen:

  • Einführungsoverlay: Die Klasse GCKCastContext hat eine Methode, presentCastInstructionsViewControllerOnceWithCastButton, mit der das Cast-Symbol hervorgehoben werden kann, wenn ein Webempfänger zum ersten Mal verfügbar ist. Die Sender-App kann den Text, die Position des Titeltextes und die Schaltfläche „Schließen“ anpassen.

  • Cast-Symbol: Ab dem Cast iOS Sender SDK 4.6.0 ist das Cast-Symbol immer sichtbar, wenn das Sendergerät mit dem WLAN verbunden ist. Wenn der Nutzer nach dem ersten Start der App auf das Cast-Symbol tippt, wird ein Dialogfeld für Berechtigungen angezeigt, in dem er der App Zugriff auf Geräte im lokalen 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 das Cast-Symbol tippt, während das Gerät verbunden ist, werden die aktuellen Medienmetadaten (z. B. Titel, Name des Aufnahmestudios und ein Thumbnail) angezeigt oder der Nutzer kann die Verbindung zum Übertragungsgerät trennen. Wenn der Nutzer auf das Cast-Symbol tippt, während 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 aktuelle Inhaltsseite oder den maximierten Controller verlassen hat, um einen anderen Bildschirm in der Sender-App aufzurufen, 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. Dieser zeigt die Metadaten der aktuell wiedergegebenen Medien an und bietet mehrere Schaltflächen zur Steuerung der Medienwiedergabe.

Cast-Symbol hinzufügen

Das Framework stellt eine Cast-Symbol-Komponente als UIButton-Unterklasse bereit. Sie kann der Titelleiste der App hinzugefügt werden, indem sie in ein UIBarButtonItem-Element eingeschlossen wird. Eine typische UIViewController-Unterklasse kann ein Cast-Symbol so installieren:

Swift
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
castButton.tintColor = UIColor.gray
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
Objective-C
GCKUICastButton *castButton = [[GCKUICastButton alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];
castButton.tintColor = [UIColor grayColor];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:castButton];

Wenn du auf die Schaltfläche tippst, 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. Der Erkennungsprozess muss nicht explizit gestartet oder beendet werden, es sei denn, du implementierst eine benutzerdefinierte UI.

Die Erkennung im Framework wird von der Klasse GCKDiscoveryManager verwaltet, die eine Eigenschaft 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 Namen des Geräts sortiert.

Funktionsweise der 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 ein Attribut von GCKCastContext ist. Einzelne Sitzungen werden durch Unterklassen der Klasse GCKSession dargestellt. Beispielsweise stellt GCKCastSession Sitzungen mit Übertragungsgeräten dar. Du kannst auf die derzeit aktive Cast-Sitzung (falls vorhanden) als currentCastSession-Attribut von GCKSessionManager zugreifen.

Über die Schnittstelle GCKSessionManagerListener können Sie Sitzungsereignisse wie das Erstellen, Sperren, 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 wieder in den Vordergrund wechselt oder nach einem abnormalen/abrupten Beenden der App während einer aktiven Sitzung neu gestartet wird.

Wenn das Cast-Dialogfeld verwendet wird, werden Sitzungen automatisch als Reaktion auf Nutzergesten erstellt und beendet. Andernfalls kann die App Sitzungen explizit über Methoden für 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 beim GCKSessionManager registrieren. GCKSessionManagerListener ist ein Protokoll, das Callbacks für Ereignisse wie Sitzungsstart, 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 du das neue Zielgerät während der Stream-Übertragung erhalten möchtest, verwende das Attribut GCKCastSession#device während des Callbacks [sessionManager:didResumeCastSession:].

Weitere Informationen finden Sie unter Stream-Übertragung auf Web Receiver.

Automatische Wiederverbindung

Das Cast-Framework fügt eine Wiederverbindungslogik hinzu, um die Wiederverbindung in vielen speziellen Fällen automatisch zu verarbeiten, z. B.:

  • Wiederherstellung nach vorübergehendem WLAN-Ausfall
  • Aus dem Ruhemodus reaktivieren
  • Wiederherstellung nach Ausführung der App im Hintergrund
  • Wiederherstellung 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 vom Framework automatisch eine Instanz von GCKRemoteMediaClient erstellt. Auf diese kann als remoteMediaClient-Attribut der GCKCastSession-Instanz zugegriffen werden.

Alle Methoden für GCKRemoteMediaClient, die Anfragen an den Web-Empfänger 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 werden kann. Tatsächlich wird die Instanz von einigen internen Komponenten des Frameworks wie dem Cast-Dialogfeld und der Mini-Mediensteuerung gemeinsam genutzt. Zu diesem Zweck unterstützt GCKRemoteMediaClient die Registrierung mehrerer GCKRemoteMediaClientListeners.

Medienmetadaten festlegen

Die Klasse GCKMediaMetadata enthält Informationen zu einer Mediendatei, die du streamen möchtest. Im folgenden Beispiel wird eine neue GCKMediaMetadata-Instanz eines Films erstellt und der Titel, der Untertitel, der Name des Aufnahmestudios und zwei Bilder festgelegt.

Swift
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))
Objective-C
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

Wenn du eine Mediendatei laden möchtest, erstelle mit den Metadaten des Mediums eine GCKMediaInformation-Instanz. Rufe dann die aktuelle GCKCastSession ab und verwende die zugehörige GCKRemoteMediaClient, um die Medien in der Empfänger-App zu laden. Du kannst dann GCKRemoteMediaClient verwenden, um eine auf dem Empfänger ausgeführte Mediaplayer-App zu steuern, z. B. zum Abspielen, Pausieren und Beenden.

Swift
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
}
Objective-C
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 Medientracks verwenden.

4K-Videoformat

Wenn du das Videoformat deiner Medien ermitteln möchtest, verwende das Attribut videoInfo von GCKMediaStatus, um 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 Cast-Design-Checkliste sollte eine Sender-App ein dauerhaftes Steuerelement, den sogenannten Mini-Controller, bereitstellen, der angezeigt wird, wenn der Nutzer die aktuelle Inhaltsseite verlässt. Der Mini-Controller bietet sofortigen Zugriff und eine sichtbare Erinnerung für 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 deine Sender-App einen Video- oder Audio-Livestream abspielt, zeigt das SDK im Mini-Controller automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche an.

Unter iOS-Sender-UI anpassen erfährst du, wie du das Erscheinungsbild der Cast-Widgets in deiner Sender-App konfigurieren kannst.

Es gibt zwei Möglichkeiten, den Mini-Controller einer Sender-App hinzuzufügen:

  • Lass das Cast-Framework das Layout des Mini-Controllers verwalten, indem du deinen vorhandenen Ansichts-Controller mit seinem eigenen Ansichts-Controller umschließt.
  • Du kannst das Layout des Mini-Controller-Widgets selbst verwalten, indem du es deinem vorhandenen Ansichts-Controller hinzufügst und eine Unteransicht im Storyboard bereitstellst.

Mit dem GCKUICastContainerViewController verpacken

Die erste Möglichkeit besteht darin, den GCKUICastContainerViewController zu verwenden, der einen anderen View Controller umschließt und unten einen 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.

Diese erste Methode wird normalerweise in der Methode -[application:didFinishLaunchingWithOptions:] des App-Delegaten ausgeführt:

Swift
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()

  ...
}
Objective-C
- (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];
  ...

}
Swift
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
    }
  }
}
Objective-C

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 Ansichts-Controller einbetten

Die zweite Möglichkeit besteht darin, den Mini-Controller direkt dem vorhandenen Ansichts-Controller hinzuzufügen. Verwenden Sie dazu createMiniMediaControlsViewController, um eine GCKUIMiniMediaControlsViewController-Instanz zu erstellen, und fügen Sie sie dann dem Container-Ansichts-Controller als Unteransicht hinzu.

Richten Sie den Ansichts-Controller im App-Delegat ein:

Swift
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
}
Objective-C
- (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 im Root-View-Controller eine GCKUIMiniMediaControlsViewController-Instanz und fügen Sie sie dem Container-View-Controller als Subview hinzu:

Swift
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)
    }
  }

...
Objective-C

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

Der GCKUIMiniMediaControlsViewControllerDelegate teilt dem Host-View-Controller mit, wann der Mini-Controller sichtbar sein soll:

Swift
  func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController,
                                       shouldAppear _: Bool) {
    updateControlBarsVisibility()
  }
Objective-C
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

Maximierten Controller hinzufügen

Gemäß der Google Cast-Design-Checkliste muss eine Sender-App einen erweiterten Controller für die gestreamten Medien bereitstellen. Der maximierte Controller ist eine Vollbildversion des Mini-Controllers.

Der maximierte Controller ist eine Vollbildansicht, die die vollständige Kontrolle über die Remote-Medienwiedergabe bietet. In dieser Ansicht sollte eine Streaming-App alle verwaltbaren Aspekte einer Streaming-Sitzung verwalten können, mit Ausnahme der Lautstärkeregelung des Webempfängers und des Lebenszyklus der Sitzung (Verbindung herstellen/Streaming beenden). Außerdem werden alle Statusinformationen zur Mediensitzung (Artwork, Titel, Untertitel usw.) bereitgestellt.

Die Funktionalität dieser Ansicht wird durch die Klasse GCKUIExpandedMediaControlsViewController implementiert.

Zuerst musst du den standardmäßigen maximierten Controller im Übertragungskontext aktivieren. Ändere den App-Delegaten, um den standardmäßigen erweiterten Controller zu aktivieren:

Swift
func applicationDidFinishLaunching(_ application: UIApplication) {
  ..

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

  ...
}
Objective-C
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

Fügen Sie Ihrem Ansichts-Controller den folgenden Code hinzu, um den erweiterten Controller zu laden, wenn der Nutzer mit dem Streamen eines Videos beginnt:

Swift
func playSelectedItemRemotely() {
  GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()

  ...

  // Load your media
  sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInformation)
}
Objective-C
- (void)playSelectedItemRemotely {
  [[GCKCastContext sharedInstance] presentDefaultExpandedMediaControls];

  ...

  // Load your media
  [self.sessionManager.currentSession.remoteMediaClient loadMedia:mediaInformation];
}

Der maximierte Controller wird auch automatisch gestartet, wenn der Nutzer auf den Mini-Controller tippt.

Wenn deine Sender-App einen Video- oder Audio-Livestream abspielt, zeigt das SDK im maximierten Controller automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche an.

Unter Benutzerdefinierte Stile auf Ihre iOS-App anwenden erfahren Sie, wie Sie das Erscheinungsbild der Cast-Widgets in Ihrer Sender-App konfigurieren können.

Lautstärkeregelung

Das Cast-Framework verwaltet die Lautstärke für die Sender-App automatisch. Es synchronisiert sich automatisch mit der Web Receiver-Lautstärke für die bereitgestellten UI-Widgets. Verwende GCKUIDeviceVolumeController, um einen von der App bereitgestellten Schieberegler zu synchronisieren.

Lautstärkeregelung über physische Tasten

Mit den physischen Lautstärketasten am Sendergerät kann die Lautstärke der Übertragungssitzung auf dem Webempfänger geändert werden. Dazu wird das Flag physicalVolumeButtonsWillControlDeviceVolume für GCKCastOptions verwendet, das für GCKCastContext festgelegt ist.

Swift
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
Objective-C
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 Antwort für jede Phase des Cast-Lebenszyklus bestimmen. Die App kann dem Nutzer Fehlerdialoge anzeigen oder die Cast-Sitzung beenden.

Beachten Sie, dass einige Fehler, einschließlich GCKErrorCode GCKErrorCodeCancelled, beabsichtigtes Verhalten sind.

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 GCKLoggerDelegate können Sie die Verarbeitung von Logeinträgen anpassen.

Mit dem GCKLogger erzeugt das SDK Logging-Ausgaben in Form von Debug-Nachrichten, Fehlern und Warnungen. Diese Logeinträge unterstützen das 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.

Swift
@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)
    }
  }
}
Objective-C

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 Nachrichten aktivieren möchten, fügen Sie dem Code nach dem Festlegen des Bevollmächtigten (siehe oben) diese Zeile hinzu:

Swift
let filter = GCKLoggerFilter.init()
filter.minimumLevel = GCKLoggerLevel.verbose
GCKLogger.sharedInstance().filter = filter
Objective-C
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setMinimumLevel:GCKLoggerLevelVerbose];
[GCKLogger sharedInstance].filter = filter;

Sie können die von GCKLogger erzeugten Logeinträge auch filtern. Legen Sie die Mindestprotokollierungsebene pro Klasse fest, z. B.:

Swift
let filter = GCKLoggerFilter.init()
filter.setLoggingLevel(GCKLoggerLevel.verbose, forClasses: ["GCKUICastButton",
                                                            "GCKUIImageCache",
                                                            "NSMutableDictionary"])
GCKLogger.sharedInstance().filter = filter
Objective-C
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setLoggingLevel:GCKLoggerLevelVerbose
             forClasses:@[@"GCKUICastButton",
                          @"GCKUIImageCache",
                          @"NSMutableDictionary"
                          ]];
[GCKLogger sharedInstance].filter = filter;

Die Klassennamen können entweder Literalnamen oder Glob-Muster sein, z. B. GCKUI\* und GCK\*Session.