Zintegruj przesyłanie z aplikacją na iOS

Ten przewodnik dla programistów opisuje, jak dodać obsługę Google Cast do aplikacji nadawcy na iOS za pomocą pakietu iOS Sender SDK.

Urządzenie mobilne lub laptop to nadawca , który steruje odtwarzaniem, a urządzenie Google Cast to odbiorca , który wyświetla treści na telewizorze.

Framework nadawcy to biblioteka klas Cast w postaci binarnej oraz powiązane z nią zasoby, które są dostępne w czasie działania aplikacji na nadawcy. Aplikacja nadawcy lub aplikacja Cast to aplikacja działająca również na nadawcy. Aplikacja Web Receiver to aplikacja HTML działająca na Web Receiver.

Framework nadawcy używa asynchronicznego wywołania zwrotnego, aby informować aplikację nadawcy o zdarzeniach i przechodzić między różnymi stanami cyklu życia aplikacji Cast.

Przepływ aplikacji

Poniższe kroki opisują typowy przepływ wykonywania aplikacji nadawcy na iOS:

  • Framework Cast uruchamia GCKDiscoveryManager na podstawie właściwości podanych w GCKCastOptions aby rozpocząć skanowanie urządzeń.
  • Gdy użytkownik kliknie przycisk Cast, framework wyświetli okno Cast z listą wykrytych urządzeń Cast.
  • Gdy użytkownik wybierze urządzenie przesyłające, framework spróbuje uruchomić na nim aplikację Web Receiver.
  • Framework wywołuje wywołania zwrotne w aplikacji nadawcy, aby potwierdzić, że aplikacja Web Receiver została uruchomiona.
  • Framework tworzy kanał komunikacji między aplikacjami nadawcy i Web Receiver.
  • Framework używa kanału komunikacji do wczytywania i sterowania odtwarzaniem multimediów na Web Receiver.
  • Framework synchronizuje stan odtwarzania multimediów między nadawcą a Web Receiver: gdy użytkownik wykonuje działania w interfejsie nadawcy, framework przekazuje te żądania sterowania multimediami do Web Receiver, a gdy Web Receiver wysyła aktualizacje stanu multimediów, framework aktualizuje stan interfejsu nadawcy.
  • Gdy użytkownik kliknie przycisk Cast, aby odłączyć się od urządzenia przesyłającego, framework odłączy aplikację nadawcy od Web Receiver.

Aby rozwiązać problemy z nadawcą, musisz włączyć logowanie.

Pełną listę wszystkich klas, metod i zdarzeń w frameworku Google Cast na iOS znajdziesz w dokumentacji interfejsu Google Cast iOS API Reference. W sekcjach poniżej opisujemy, jak zintegrować Cast z aplikacją na iOS.

Wywoływanie metod z wątku głównego

Inicjowanie kontekstu Cast

Framework Cast ma globalny obiekt singletonowy GCKCastContext, który koordynuje wszystkie działania frameworku. Ten obiekt musi zostać zainicjowany na wczesnym etapie cyklu życia aplikacji, zwykle w metodzie -[application:didFinishLaunchingWithOptions:] delegata aplikacji, aby można było prawidłowo wznowić sesję automatycznie po ponownym uruchomieniu aplikacji nadawcy.

Podczas inicjowania GCKCastContext należy podać obiekt GCKCastOptions. Ta klasa zawiera opcje, które wpływają na działanie frameworku. Najważniejszy z nich to identyfikator aplikacji Web Receiver, który służy do filtrowania wyników wykrywania i uruchamiania aplikacji Web Receiver po rozpoczęciu sesji Cast.

Metoda -[application:didFinishLaunchingWithOptions:] to również dobre miejsce na skonfigurowanie delegata logowania, który będzie odbierać komunikaty logowania z frameworku. Mogą one być przydatne podczas debugowania i rozwiązywania problemów.

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

Widżety UX Cast

Pakiet Cast iOS SDK udostępnia te widżety, które są zgodne z listą kontrolną projektu Cast:

  • Nakładka wprowadzająca: Klasa GCKCastContext ma metodę presentCastInstructionsViewControllerOnceWithCastButton, która może służyć do wyróżnienia przycisku Cast przy pierwszym udostępnieniu Web Receiver. Aplikacja nadawcy może dostosować tekst, położenie tekstu tytułu i przycisku Zamknij.

  • Przycisk przesyłania: Od wersji 4.6.0 pakietu Cast iOS Sender SDK przycisk przesyłania jest zawsze widoczny gdy urządzenie nadawcy jest połączone z Wi-Fi. Gdy użytkownik po raz pierwszy kliknie przycisk Cast po początkowym uruchomieniu aplikacji, pojawi się okno uprawnień, w którym użytkownik może przyznać aplikacji dostęp do urządzeń w sieci lokalnej. Następnie, gdy użytkownik kliknie przycisk Cast, wyświetli się okno Cast z listą wykrytych urządzeń. Gdy użytkownik kliknie przycisk Cast, gdy urządzenie jest połączone, wyświetlą się bieżące metadane multimediów (np. tytuł, nazwa studia nagrań i miniatura) lub użytkownik będzie mógł odłączyć się od urządzenia przesyłającego. Gdy użytkownik kliknie przycisk Cast, gdy nie ma dostępnych urządzeń, wyświetli się ekran z informacjami o tym, dlaczego nie można znaleźć urządzeń, i o tym, jak rozwiązać ten problem.

  • Minikontroler: gdy użytkownik przesyła treści i opuści stronę z treścią lub rozszerzony kontroler, aby przejść do innego ekranu w aplikacji nadawcy, u dołu ekranu wyświetla się minikontroler, który umożliwia użytkownikowi wyświetlanie metadanych aktualnie przesyłanych multimediów i sterowanie odtwarzaniem.

  • Rozszerzony kontroler: gdy użytkownik przesyła treści, a następnie kliknie powiadomienie o multimediach lub minikontroler, uruchomi się rozszerzony kontroler, który wyświetla metadane aktualnie odtwarzanych multimediów i udostępnia kilka przycisków do sterowania odtwarzaniem multimediów.

Dodawanie przycisku Cast

Framework udostępnia komponent przycisku Cast jako podklasę UIButton. Można go dodać do paska tytułu aplikacji, umieszczając go w UIBarButtonItem. Typowa podklasa UIViewController może zainstalować przycisk Cast w ten sposób:

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];

Domyślnie kliknięcie przycisku spowoduje otwarcie okna przesyłania udostępnianego przez framework.

GCKUICastButton można też dodać bezpośrednio do storyboardu.

Konfigurowanie wykrywania urządzeń

W frameworku wykrywanie urządzeń odbywa się automatycznie. Nie musisz wyraźnie rozpoczynać ani zatrzymywać procesu wykrywania, chyba że implementujesz niestandardowy interfejs.

Wykrywanie w frameworku jest zarządzane przez klasę GCKDiscoveryManager, która jest właściwością GCKCastContext. Framework udostępnia domyślny komponent okna przesyłania do wybierania urządzeń i sterowania nimi. Lista urządzeń jest uporządkowana leksykograficznie według przyjaznej nazwy urządzenia.

Jak działa zarządzanie sesjami

Pakiet Cast SDK wprowadza pojęcie sesji Cast, której utworzenie obejmuje kroki połączenia z urządzeniem, uruchomienia (lub dołączenia) aplikacji Web Receiver, połączenia z tą aplikacją i zainicjowania kanału sterowania multimediami. Więcej informacji o sesjach Cast i cyklu życia Web Receiver znajdziesz w przewodniku Cykl życia aplikacji Web Receiver .

Sesje są zarządzane przez klasę GCKSessionManager, która jest właściwością GCKCastContext. Poszczególne sesje są reprezentowane przez podklasy klasy GCKSession. Na przykład GCKCastSession reprezentuje sesje z urządzeniami Cast. Możesz uzyskać dostęp do aktualnie aktywnej sesji Cast (jeśli taka istnieje) jako właściwość currentCastSession klasy GCKSessionManager.

Interfejs GCKSessionManagerListener może służyć do monitorowania zdarzeń sesji, takich jak tworzenie sesji, zawieszanie, wznawianie i kończenie. Framework automatycznie zawiesza sesje, gdy aplikacja nadawcy przechodzi do działania w tle, i próbuje je wznowić, gdy aplikacja wraca na pierwszy plan (lub jest ponownie uruchamiana po nieprawidłowym/nagłym zakończeniu działania aplikacji, gdy sesja była aktywna).

Jeśli używasz okna przesyłania, sesje są tworzone i zamykane automatycznie w odpowiedzi na gesty użytkownika. W przeciwnym razie aplikacja może wyraźnie rozpoczynać i kończyć sesje za pomocą metod w GCKSessionManager.

Jeśli aplikacja musi wykonać specjalne przetwarzanie w odpowiedzi na zdarzenia cyklu życia, może zarejestrować co najmniej 1 instancję GCKSessionManagerListener w GCKSessionManager. GCKSessionManagerListener to protokół, który definiuje wywołania zwrotne dla takich zdarzeń jak rozpoczęcie i zakończenie sesji itp.

Przeniesienie odtwarzania

Zachowanie stanu sesji jest podstawą przeniesienia odtwarzania, w którym użytkownicy mogą przenosić istniejące strumienie audio i wideo między urządzeniami za pomocą poleceń głosowych, aplikacji Google Home lub inteligentnych ekranów. Odtwarzanie multimediów zatrzymuje się na jednym urządzeniu (źródłowym) i jest kontynuowane na innym (docelowym). Każde urządzenie przesyłające z najnowszym oprogramowaniem układowym może służyć jako źródło lub miejsce docelowe w przypadku przeniesienia odtwarzania.

Aby uzyskać nowe urządzenie docelowe podczas przeniesienia odtwarzania, użyj właściwości GCKCastSession#device podczas wywołania zwrotnego [sessionManager:didResumeCastSession:].

Więcej informacji znajdziesz w artykule Przeniesienie odtwarzania na Web Receiver.

Automatyczne ponowne łączenie

Framework Cast dodaje logikę ponownego łączenia, aby automatycznie obsługiwać ponowne łączenie w wielu subtelnych przypadkach, takich jak:

  • przywracanie połączenia po tymczasowej utracie Wi-Fi,
  • przywracanie połączenia po uśpieniu urządzenia,
  • przywracanie połączenia po przejściu aplikacji do działania w tle,
  • przywracanie połączenia po awarii aplikacji.

Jak działa sterowanie multimediami

Jeśli sesja Cast zostanie nawiązana z aplikacją Web Receiver, która obsługuje przestrzeń nazw multimediów , framework automatycznie utworzy instancję GCKRemoteMediaClient . Można uzyskać do niej dostęp jako właściwość remoteMediaClient instancji GCKCastSession .

Wszystkie metody w GCKRemoteMediaClient, które wysyłają żądania do Web Receiver zwracają GCKRequest obiekt, który może służyć do śledzenia tego żądania. Do tego obiektu można przypisać GCKRequestDelegate , aby otrzymywać powiadomienia o ostatecznym wyniku operacji.

Oczekuje się, że instancja GCKRemoteMediaClient może być współdzielona przez wiele części aplikacji, a niektóre wewnętrzne komponenty platformy, takie jak okno przesyłania i miniodtwarzacz, rzeczywiście współdzielą tę instancję. W tym celu GCKRemoteMediaClient obsługuje rejestrację wielu GCKRemoteMediaClientListener.

Ustawianie metadanych multimediów

Klasa GCKMediaMetadata reprezentuje informacje o elemencie multimedialnym, który chcesz przesłać. Poniższy przykład tworzy nową instancję GCKMediaMetadata filmu i ustawia tytuł, podtytuł, nazwę studia nagrań i 2 obrazy.

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]];

Więcej informacji o używaniu obrazów z metadanymi multimediów znajdziesz w sekcji Wybieranie obrazów i buforowanie.

Wczytywanie multimediów

Aby wczytać element multimedialny, utwórz instancję GCKMediaInformation za pomocą metadanych multimediów. Następnie pobierz bieżący GCKCastSession i użyj jego GCKRemoteMediaClient, aby wczytać multimedia w aplikacji odbiorcy. Następnie możesz użyć GCKRemoteMediaClient do sterowania aplikacją odtwarzacza multimediów działającą na odbiorcy, np. do odtwarzania, wstrzymywania i zatrzymywania.

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;
}

Zapoznaj się też z sekcją dotyczącą używania ścieżek multimediów.

Format wideo 4K

Aby określić format wideo multimediów, użyj właściwości videoInfo klasy GCKMediaStatus aby uzyskać bieżącą instancję GCKVideoInfo. Ta instancja zawiera typ formatu HDR TV oraz wysokość i szerokość w pikselach. Warianty formatu 4K są wskazywane we właściwości hdrType przez wartości wyliczenia GCKVideoInfoHDRType.

Dodaj minikontrolery

Zgodnie z listą kontrolną projektu Cast, aplikacja nadawcy powinna udostępniać trwały element sterujący, czyli miniodtwarzacz, który powinien pojawiać się, gdy użytkownik opuści stronę z treścią. Minikontroler zapewnia natychmiastowy dostęp i widoczne przypomnienie o bieżącej sesji Cast.

Framework Cast udostępnia pasek sterowania, GCKUIMiniMediaControlsViewController, który można dodać do scen, w których chcesz wyświetlać miniodtwarzacz.

Gdy aplikacja nadawcy odtwarza transmisję audio lub wideo na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/pauzy w miniodtwarzaczu.

Więcej informacji o tym, jak aplikacja nadawcy może skonfigurować wygląd widżetów Cast, znajdziesz w artykule Dostosowywanie interfejsu aplikacji nadawcy na iOS.

Miniodtwarzacz można dodać do aplikacji nadawcy na 2 sposoby:

  • Pozwól frameworkowi Cast zarządzać układem miniodtwarzacza, umieszczając istniejący kontroler widoku w jego własnym kontrolerze widoku.
  • Samodzielnie zarządzaj układem widżetu miniodtwarzacza, dodając go do istniejącego kontrolera widoku, podając podwidok w storyboardzie.

Umieszczanie za pomocą GCKUICastContainerViewController

Pierwszy sposób polega na użyciu GCKUICastContainerViewController , który umieszcza inny kontroler widoku i dodaje u dołu GCKUIMiniMediaControlsViewController. To podejście jest ograniczone, ponieważ nie można dostosować animacji ani skonfigurować działania kontrolera widoku kontenera.

Ten pierwszy sposób jest zwykle stosowany w metodzie -[application:didFinishLaunchingWithOptions:] delegata aplikacji:

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

Umieszczanie w istniejącym kontrolerze widoku

Drugi sposób polega na dodaniu minikontrolera bezpośrednio do istniejącego kontrolera widoku za pomocą createMiniMediaControlsViewController, aby utworzyć instancję GCKUIMiniMediaControlsViewController, a następnie dodaniu jej do kontrolera widoku kontenera jako podwidoku.

Skonfiguruj kontroler widoku w delegacie aplikacji:

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;
}

W głównym kontrolerze widoku utwórz instancję GCKUIMiniMediaControlsViewController i dodaj ją do kontrolera widoku kontenera jako podwidoku:

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

The GCKUIMiniMediaControlsViewControllerDelegate tells the host view controller when the mini controller should be visible:

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

Dodaj rozszerzony kontroler

Zgodnie z listą kontrolną projektu Google Cast aplikacja nadawcy musi udostępniać rozszerzony odtwarzacz przesyłanych multimediów. Rozszerzony kontroler to wersja minikontrolera na pełnym ekranie.

Rozszerzony kontroler to widok na pełnym ekranie, który umożliwia pełne sterowanie odtwarzaniem multimediów na urządzeniu zdalnym. Ten widok powinien umożliwiać aplikacji przesyłającej zarządzanie każdym aspektem sesji przesyłania, z wyjątkiem sterowania głośnością Web Receiver i cyklem życia sesji (łączenie/zatrzymywanie przesyłania). Zawiera też wszystkie informacje o stanie sesji multimediów (okładka, tytuł, podtytuł itp.).

Funkcjonalność tego widoku jest implementowana przez klasę GCKUIExpandedMediaControlsViewController.

Najpierw musisz włączyć domyślny rozszerzony odtwarzacz w kontekście przesyłania. Zmodyfikuj delegata aplikacji, aby włączyć domyślny rozszerzony kontroler:

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

Aby wczytać rozszerzony odtwarzacz, gdy użytkownik zacznie przesyłać film, dodaj do kontrolera widoku ten kod:

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];
}

Rozszerzony kontroler zostanie też uruchomiony automatycznie, gdy użytkownik kliknie minikontroler.

Gdy aplikacja nadawcy odtwarza transmisję audio lub wideo na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/pauzy w rozszerzonym kontrolerze.

Więcej informacji o tym, jak aplikacja nadawcy może skonfigurować wygląd widżetów Cast, znajdziesz w artykule Stosowanie stylów niestandardowych w aplikacji na iOS.

Sterowanie głośnością

Framework Cast automatycznie zarządza głośnością aplikacji nadawcy. Framework automatycznie synchronizuje się z głośnością Web Receiver w przypadku dostarczonych widżetów interfejsu. Aby zsynchronizować suwak udostępniany przez aplikację, użyj GCKUIDeviceVolumeController.

Sterowanie głośnością za pomocą przycisków fizycznych

Fizyczne przyciski głośności na urządzeniu nadawcy mogą służyć do zmiany głośności sesji Cast na Web Receiver za pomocą flagi physicalVolumeButtonsWillControlDeviceVolume w GCKCastOptions, która jest ustawiona w GCKCastContext.

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];

Obsługa błędów

Bardzo ważne jest, aby aplikacje nadawcy obsługiwały wszystkie wywołania zwrotne błędów i decydowały o najlepszej reakcji na każdym etapie cyklu życia Cast. Aplikacja może wyświetlać użytkownikowi okna błędów lub zakończyć sesję Cast.

Pamiętaj, że niektóre błędy, w tym GCKErrorCode GCKErrorCodeCancelled, są zamierzonym działaniem.

Nie próbuj ponownie nawiązywać połączenia, które nie powiodło się z powodu GCKErrorCodeCancelled. Może to spowodować nieoczekiwane zachowanie.

Logowanie

GCKLogger to singleton używany przez framework do logowania. Użyj GCKLoggerDelegate , aby dostosować sposób obsługi komunikatów logowania.

Za pomocą GCKLogger pakiet SDK generuje dane logowania w postaci komunikatów debugowania, błędów i ostrzeżeń. Te komunikaty logowania ułatwiają debugowanie i są przydatne podczas rozwiązywania problemów i identyfikowania ich przyczyn. Domyślnie dane logowania są pomijane, ale po przypisaniu GCKLoggerDelegate aplikacja nadawcy może otrzymywać te komunikaty z pakietu SDK i zapisywać je w konsoli systemowej.

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

Aby włączyć też komunikaty debugowania i szczegółowe, dodaj ten wiersz do kodu po ustawieniu delegata (jak pokazano wcześniej):

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;

Możesz też filtrować komunikaty logowania generowane przez GCKLogger. Ustaw minimalny poziom logowania dla każdej klasy, np.:

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;

Nazwy klas mogą być nazwami dosłownymi lub wzorcami glob, np. GCKUI\* i GCK\*Session.