Cast in Ihre iOS-App einbinden

In diesem Entwicklerleitfaden wird erläutert, wie Sie der iOS-Absender-App mithilfe des iOS Sender SDK Unterstützung für Google Cast 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 bezieht sich auf eine App, die auch auf dem Absender ausgeführt wird. Die Web Receiver App bezieht sich auf 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 Lebenszyklus der Cast-App zu wechseln.

Anwendungsfluss

In den folgenden Schritten wird der typische Ablauf für eine iOS-App des Absenders beschrieben:

  • Das Cast-Framework startet GCKDiscoveryManager anhand der Attribute in GCKCastOptions, um mit dem Scannen nach Geräten zu beginnen.
  • Wenn der Nutzer auf das Cast-Symbol klickt, wird im Cast-Dialogfeld die 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 Absender-App auf, um zu bestätigen, dass die Web Receiver App gestartet wurde.
  • Das Framework erstellt einen Kommunikationskanal zwischen den Absender- und Web Receiver-Apps.
  • Das Framework verwendet den Kommunikationskanal, um die Medienwiedergabe auf dem Webempfänger zu laden und zu steuern.
  • Das Framework synchronisiert den Medienwiedergabestatus zwischen dem Absender und dem Webempfänger: Wenn der Nutzer die Aktionen der Benutzeroberfläche des Absenders durchführt, leitet das Framework diese Mediensteuerungsanfragen an den Webempfänger weiter. Wenn der Webempfänger den Medienstatus aktualisiert, aktualisiert das Framework den Status der Benutzeroberfläche des Absenders.
  • Wenn der Nutzer auf das Cast-Symbol klickt, um die Verbindung zum Übertragungsgerät zu trennen, wird die Verbindung der Absender-App vom Webempfänger vom Framework getrennt.

Wenn Sie Probleme mit dem Absender beheben möchten, müssen Sie Logging aktivieren.

Eine umfassende Liste aller Klassen, Methoden und Ereignisse im Google Cast-iOS-Framework finden Sie in der API-Referenz für Google Cast iOS. In den folgenden Abschnitten wird erläutert, wie Sie Cast in Ihre iOS-App einbinden.

Anrufmethoden aus Hauptthread

Cast-Kontext initialisieren

Das Cast-Framework hat das globale Singleton-Objekt GCKCastContext, das alle Aktivitäten des Frameworks koordiniert. Dieses Objekt muss zu Beginn des Lebenszyklus der Anwendung initialisiert werden, in der Regel in der Methode -[application:didFinishLaunchingWithOptions:] des Anwendungs-Bevollmächtigten, damit die automatische Sitzungswiederholung beim Neustart der Absender-App korrekt ausgelöst werden kann.

Beim Initialisieren des GCKCastContext muss ein GCKCastOptions-Objekt angegeben werden. Diese Klasse enthält Optionen, die sich auf das Verhalten des Frameworks auswirken. Am wichtigsten ist die ID der Web Receiver-Anwendung, die zum Filtern von Discovery-Ergebnissen und zum Starten der Web Receiver App verwendet wird, wenn eine Streamingsitzung gestartet wird.

Die Methode -[application:didFinishLaunchingWithOptions:] ist auch eine gute Möglichkeit, einen Logging-Bevollmächtigten einzurichten, um die Logging-Nachrichten vom Framework zu empfangen. Dies kann bei der Fehlerbehebung hilfreich 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)
    }
  }
}
Ziel 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

Die UX-Widgets der Cast-Funktion

Das Cast iOS SDK umfasst die folgenden Widgets, die der Checkliste für das Cast-Design entsprechen:

  • Einführungs-Overlay: Die Klasse GCKCastContext hat die Methode presentCastInstructionsViewControllerOnceWithCastButton, mit der Sie das Cast-Symbol hervorheben können, wenn ein Webempfänger das erste Mal verfügbar ist. Die Absender-App kann den Text, die Position des Titeltexts 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 Absendergerät mit einem WLAN verbunden ist. Wenn der Nutzer nach dem ersten Start der App auf das Cast-Symbol tippt, erscheint ein Berechtigungsdialogfeld, mit dem der Nutzer lokalen Geräten Zugriff auf Geräte im Netzwerk gewähren kann. Wenn der Nutzer dann auf das Cast-Symbol tippt, wird ein Streaming-Dialogfeld mit den erkannten Geräten angezeigt. Wenn der Nutzer auf das Cast-Symbol tippt, während das Gerät verbunden ist, werden die aktuellen Media-Metadaten (z. B. der Titel, der Name des Aufnahmestudios und ein Thumbnail) angezeigt. Alternativ kann der Nutzer die Verbindung zum Übertragungsgerät trennen. Wenn der Nutzer auf das Cast-Symbol tippt, aber keine Geräte verfügbar sind, wird auf dem Bildschirm angezeigt, warum die Geräte nicht gefunden wurden und wie das Problem behoben werden kann.

  • Mini-Controller: Wenn der Nutzer Inhalte streamt und die aktuelle Inhaltsseite oder den erweiterten Controller von einem anderen Bildschirm in der Absender-App weg verlassen hat, wird der Mini-Controller unten auf dem Bildschirm angezeigt. So kann er die aktuell gestreamten Medienmetadaten sehen und die Wiedergabe steuern.

  • Erweiterter Controller: Wenn der Nutzer Inhalte streamt und auf die Medienbenachrichtigung oder den Mini-Controller klickt, wird der maximierte Controller gestartet. Dieser zeigt die aktuell wiedergebenen Metadaten an und bietet mehrere Schaltflächen zur Steuerung der Medienwiedergabe.

Cast-Symbol hinzufügen

Das Framework bietet eine Cast-Schaltfläche als Komponente UIButton. Sie kann der Titelleiste der App hinzugefügt werden, indem Sie die Anweisung in einen UIBarButtonItem umschließen. Eine typische UIViewController-Unterklasse kann das 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)
Ziel C
GCKUICastButton *castButton = [[GCKUICastButton alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];
castButton.tintColor = [UIColor grayColor];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:castButton];

Wenn das Tippen auf die Schaltfläche standardmäßig aktiviert ist, wird das vom Framework bereitgestellte Cast-Dialogfeld geöffnet.

GCKUICastButton kann auch direkt dem Storyboard hinzugefügt werden.

Geräteerkennung konfigurieren

Im Framework findet die Geräteerkennung automatisch statt. Sie müssen den Erkennungsprozess nicht explizit starten oder beenden, es sei denn, Sie implementieren eine benutzerdefinierte UI.

Die Erkennung im Framework wird von der Klasse GCKDiscoveryManager verwaltet, einer Eigenschaft von GCKCastContext. Das Framework bietet eine standardmäßige Cast-Dialogkomponente für die Geräteauswahl und -steuerung. Die Geräteliste wird lexikografisch nach dem Gerätenamen sortiert.

Funktionsweise der Sitzungsverwaltung

Das Cast SDK stellt das Konzept einer Cast-Sitzung vor, bei der 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 Mediensteuerungskanals definiert werden. Weitere Informationen zu Cast-Sitzungen und dem Lebenszyklus des Webempfängers finden Sie im Leitfaden zum Lebenszyklus von Anwendungen.

Sitzungen werden von der Klasse GCKSessionManager verwaltet, einer Eigenschaft von GCKCastContext. Einzelne Sitzungen werden durch abgeleitete Klassen der Klasse GCKSession dargestellt. Beispielsweise stellt GCKCastSession Sitzungen mit Cast-Geräten dar. Sie können auf die derzeit aktive Cast-Sitzung (sofern vorhanden) als currentCastSession-Property von GCKSessionManager zugreifen.

Mit der Schnittstelle GCKSessionManagerListener können Sie Sitzungsereignisse überwachen, z. B. Sitzungserstellung, Sperrung, Wiederaufnahme und Beendigung. Das Framework sperrt Sitzungen automatisch, wenn die App des Absenders im Hintergrund ausgeführt wird, und versucht, sie fortzusetzen, wenn die App wieder in den Vordergrund wechselt oder wenn sie bei einer aktiven Sitzung nach einer ungewöhnlichen oder abrupten Beendigung neu gestartet wird.

Wenn das Cast-Dialogfeld verwendet wird, werden Sitzungen automatisch erstellt und als Reaktion auf Nutzergesten gelöscht. Andernfalls kann die Anwendung Sitzungen explizit über Methoden in GCKSessionManager starten und beenden.

Wenn die Anwendung als Reaktion auf Lebenszyklusereignisse der Sitzung eine spezielle Verarbeitung ausführen muss, kann sie eine oder mehrere GCKSessionManagerListener-Instanzen bei GCKSessionManager registrieren. GCKSessionManagerListener ist ein Protokoll, das Callbacks für Ereignisse wie Sitzungsstart und -ende definiert.

Stream-Übertragung

Die Beibehaltung des Sitzungsstatus ist die Grundlage der Streamübertragung, bei der Nutzer vorhandene Audio- und Videostreams mithilfe von Sprachbefehlen, der Google Home App oder Smart Displays zwischen Geräten verschieben können. Die Medienwiedergabe wird auf einem Gerät (Quelle) beendet und auf einem anderen (Ziel) fortgesetzt. Jedes Übertragungsgerät mit der neuesten Firmware kann als Quellen oder Ziele in einer Streamübertragung verwendet werden.

Um das neue Zielgerät während der Stream-Übertragung abzurufen, verwenden Sie das Attribut GCKCastSession#device während des [sessionManager:didResumeCastSession:]-Callbacks.

Weitere Informationen finden Sie unter Streamübertragung auf Webempfänger.

Automatische Neuverbindung

Das Cast-Framework fügt die Verbindungslogik hinzu, um die Verbindung in vielen subtilen Eckfällen automatisch zu verarbeiten, z. B.:

  • Wiederherstellung nach vorübergehendem WLAN-Verlust
  • Erholung nach Schlafenszeit
  • Hintergrund aus der App wiederherstellen
  • Wiederherstellung nach Absturz der App

So funktioniert die Mediensteuerung

Wenn eine Cast-Sitzung mit einer Web Receiver App erstellt wird, die den Medien-Namespace unterstützt, wird automatisch eine Instanz von GCKRemoteMediaClient vom Framework erstellt, auf die als Property remoteMediaClient der GCKCastSession-Instanz zugegriffen werden kann.

Alle Methoden von GCKRemoteMediaClient, die Anfragen an den Webempfänger senden, geben ein GCKRequest-Objekt zurück, mit dem diese Anfrage erfasst werden kann. Diesem Objekt kann eine GCKRequestDelegate zugewiesen werden, um Benachrichtigungen über das Ergebnis des Vorgangs zu erhalten.

Es wird erwartet, dass die Instanz von GCKRemoteMediaClient von mehreren Teilen der App gemeinsam genutzt werden kann. Einige interne Komponenten des Frameworks, z. B. das Cast-Dialogfeld und die Mini-Mediensteuerung, teilen die Instanz ebenfalls. Aus diesem Grund unterstützt GCKRemoteMediaClient die Registrierung mehrerer GCKRemoteMediaClientListeners.

Medienmetadaten festlegen

Die Klasse GCKMediaMetadata repräsentiert Informationen zu einem Medienelement, das Sie streamen möchten. 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))
Ziel 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]];

Weitere Informationen zur Verwendung von Bildern mit Medienmetadaten finden Sie im Abschnitt Bildauswahl und Caching.

Medien laden

Erstellen Sie zum Laden eines Medienelements eine GCKMediaInformation-Instanz mit den Metadaten des Mediums. Rufen Sie dann die aktuelle GCKCastSession ab und verwenden Sie die GCKRemoteMediaClient, um die Medien in der Empfänger-App zu laden. Anschließend können Sie GCKRemoteMediaClient verwenden, um die Nutzung einer Media-Player-App auf dem Empfänger zu steuern, z. B. für Wiedergabe, Pause und Stopp.

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
}
Ziel 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 Medien-Tracks verwenden.

4K-Videoformat

Verwenden Sie zum Ermitteln des Videoformats Ihrer Medien das Attribut videoInfo von GCKMediaStatus, um die aktuelle Instanz von GCKVideoInfo abzurufen. Diese Instanz enthält die Art des HDR-TV-Formats sowie die Höhe und Breite in Pixeln. Varianten des 4K-Formats werden im Attribut hdrType durch die Aufzählungswerte GCKVideoInfoHDRType angegeben.

Mini-Controller hinzufügen

Gemäß der Cast-Design-Checkliste sollte eine Absender-App ein dauerhaftes Steuerelement liefern, das sogenannte Mini-Controllers, das angezeigt werden soll, wenn der Nutzer die aktuelle Inhaltsseite verlässt. Der Mini-Controller bietet sofortigen Zugriff und eine sichtbare Erinnerung für die aktuelle Streamingsitzung.

Das Cast-Framework bietet eine Steuerleiste, GCKUIMiniMediaControlsViewController, die den Szenen hinzugefügt werden kann, in denen der Mini-Controller angezeigt werden soll.

Wenn die Absender-App ein Video oder einen Audio-Livestream abspielt, zeigt das SDK im Mini-Controller automatisch die Schaltfläche für Wiedergabe/Stopp an.

Unter iOS-Sender-UI anpassen erfahren Sie, wie die Absender-App das Erscheinungsbild der Cast-Widgets konfigurieren kann.

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

  • Sie können das Layout des Mini-Controllers über das Cast-Framework verwalten lassen. Dazu binden Sie einfach den vorhandenen Ansichts-Controller in einen eigenen Controller ein.
  • Sie können das Layout des Mini-Controller-Widgets selbst verwalten, indem Sie es Ihrem vorhandenen Ansicht-Controller hinzufügen, indem Sie eine untergeordnete Ansicht im Storyboard bereitstellen.

Wrapping mit dem GCKUICastContainerViewController

Die erste Möglichkeit besteht darin, den GCKUICastContainerViewController zu verwenden, der einen anderen Ansicht-Controller umschließt und unten einen GCKUIMiniMediaControlsViewController hinzufügt. Dieser Ansatz ist darin beschränkt, dass Sie die Animation nicht anpassen und das Verhalten des Containeransicht-Controllers nicht konfigurieren können.

Dies wird normalerweise in der Methode -[application:didFinishLaunchingWithOptions:] des Bevollmächtigten der App 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()

  ...
}
Ziel 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
    }
  }
}
Ziel 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 Controller einbinden

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

Richten Sie den Ansichts-Controller in der App-Delegierung 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
}
Ziel 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 in Ihrem Root-Controller eine Instanz GCKUIMiniMediaControlsViewController und fügen Sie sie dem Container View Controller als untergeordnete Ansicht 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)
    }
  }

...
Ziel 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()
  }
Ziel C
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

Erweiterten Controller hinzufügen

In der Checkliste für das Google Cast-Design muss eine Absender-App einen erweiterten Controller für die gestreamten Medien bereitstellen. Der erweiterte Controller ist eine Vollbildversion des Mini-Controllers.

Der erweiterte 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 Streamingsitzung verwalten können, mit Ausnahme der Lautstärkeregelung des Web Receivers und des Sitzungslebenszyklus (Verbindung streamen/beenden). Außerdem enthält er alle Statusinformationen zur Mediensitzung (Kunstwerk, Titel, Untertitel usw.).

Die Funktionalität dieser Ansicht wird von der Klasse GCKUIExpandedMediaControlsViewController implementiert.

Zuerst müssen Sie den standardmäßigen erweiterten Controller im Umwandlungskontext aktivieren. Ändern Sie den App-Bevollmächtigten, um den standardmäßigen erweiterten Controller zu aktivieren:

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

Fügen Sie dem 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)
}
Ziel C
- (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 die Absender-App ein Video oder einen Audio-Livestream abspielt, zeigt das SDK im erweiterten Controller automatisch die Schaltfläche für Wiedergabe/Stopp an.

Unter Benutzerdefinierte Stile auf Ihre iOS-App anwenden erfahren Sie, wie Ihre Absender-App das Erscheinungsbild der Cast-Widgets konfigurieren kann.

Lautstärkeregelung

Das Cast-Framework verwaltet die Lautstärke für die Absender-App automatisch. Das Framework wird automatisch mit dem Web Receiver-Volume für die bereitgestellten UI-Widgets synchronisiert. Wenn Sie einen von der App bereitgestellten Schieberegler synchronisieren möchten, verwenden Sie GCKUIDeviceVolumeController.

Lautstärkeregelung der Tasten

Über die Schaltflächen für die physische Lautstärke auf dem Absendergerät können Sie die Lautstärke der Streamingsitzung auf dem Webempfänger ändern. Dazu verwenden Sie das Flag physicalVolumeButtonsWillControlDeviceVolume für GCKCastOptions, das auf GCKCastContext festgelegt ist.

Swift
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
Ziel 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 Absender-Apps alle Fehler-Callbacks verarbeiten und die beste Antwort für jede Phase des Cast-Lebenszyklus festlegen. Die App kann dem Nutzer Fehlerdialoge anzeigen oder entscheiden, ob sie die Streamingsitzung beenden soll.

Logging

GCKLogger ist ein Singleton, das für das Logging durch das Framework verwendet wird. Mit GCKLoggerDelegate können Sie den Umgang mit Lognachrichten anpassen.

Mit GCKLogger generiert das SDK eine Logging-Ausgabe in Form von Debug-Nachrichten, Fehlern und Warnungen. Diese Logeinträge helfen bei der Fehlerbehebung und sind bei der Fehlerbehebung und Identifizierung von Problemen nützlich. Standardmäßig wird die Logausgabe unterdrückt. Wenn Sie jedoch ein GCKLoggerDelegate zuweisen, kann die Absenderanwendung diese Nachrichten 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)
    }
  }
}
Ziel 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

Fügen Sie dem Code nach dem Festlegen des Bevollmächtigten (siehe oben) diese Zeile hinzu, um auch Benachrichtigungs- und ausführliche Mitteilungen zu aktivieren:

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

Sie können auch die von GCKLogger generierten Lognachrichten filtern. Legen Sie die minimale Logging-Ebene pro Klasse fest. Beispiel:

Swift
let filter = GCKLoggerFilter.init()
filter.setLoggingLevel(GCKLoggerLevel.verbose, forClasses: ["GCKUICastButton",
                                                            "GCKUIImageCache",
                                                            "NSMutableDictionary"])
GCKLogger.sharedInstance().filter = filter
Ziel 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.