Zintegruj przesyłanie z aplikacją na iOS

Z tego przewodnika dla programistów dowiesz się, jak dodać obsługę Google Cast do aplikacji nadawczej na iOS za pomocą pakietu SDK nadawcy na iOS.

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.

Platforma nadawcy odnosi się do pliku binarnego biblioteki klas Cast i powiązanych zasobów obecnych w czasie działania nadawcy. Aplikacja nadawcy lub aplikacja Cast to aplikacja, która również działa na urządzeniu nadawcy. Aplikacja odbiornika internetowego to aplikacja HTML działająca na odbiorniku internetowym.

Platforma 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 w aplikacji

Poniżej opisujemy typowy ogólny proces wykonywania aplikacji nadawcy na iOS:

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

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

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

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

Inicjowanie kontekstu Cast

Platforma Cast ma globalny obiekt singleton GCKCastContext, który koordynuje wszystkie działania platformy. Ten obiekt musi zostać zainicjowany na wczesnym etapie cyklu życia aplikacji, zwykle w metodzie -[application:didFinishLaunchingWithOptions:] delegata aplikacji, aby automatyczne wznawianie sesji po ponownym uruchomieniu aplikacji nadawcy mogło działać prawidłowo.

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

Metoda -[application:didFinishLaunchingWithOptions:] to też dobre miejsce na skonfigurowanie delegata logowania, który będzie odbierać komunikaty logu z platformy. Mogą 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ą projektowania Cast:

  • Nakładka wprowadzająca: klasa GCKCastContext ma metodę presentCastInstructionsViewControllerOnceWithCastButton, której można użyć do wyróżnienia przycisku Cast, gdy odbiornik internetowy jest dostępny po raz pierwszy. Aplikacja nadawcy może dostosować tekst, pozycję tekstu tytułu i przycisk Zamknij.

  • Przycisk Cast: od wersji 4.6.0 pakietu SDK Cast na iOS przycisk Cast jest zawsze widoczny, gdy urządzenie wysyłające jest połączone z Wi-Fi. Gdy użytkownik po raz pierwszy kliknie przycisk Cast po uruchomieniu aplikacji, pojawi się okno uprawnień, w którym będzie mógł przyznać aplikacji dostęp do urządzeń w sieci lokalnej. Następnie, gdy użytkownik kliknie przycisk Cast, wyświetli się okno przesyłania z listą wykrytych urządzeń. Gdy użytkownik kliknie przycisk Cast, gdy urządzenie jest połączone, wyświetlą się aktualne metadane multimediów (takie jak tytuł, nazwa studia nagraniowego i miniatura) lub użytkownik będzie mógł odłączyć urządzenie przesyłające. Gdy użytkownik kliknie przycisk Cast, a nie będzie dostępnych żadnych urządzeń, wyświetli się ekran z informacjami o tym, dlaczego nie można znaleźć urządzeń, i sposobami rozwiązania problemu.

  • Minikontroler: gdy użytkownik przesyła treści i opuścił bieżącą stronę 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.

  • Rozwinięty kontroler: gdy użytkownik przesyła treści, kliknięcie powiadomienia o multimediach lub minikontrolera powoduje uruchomienie rozwiniętego kontrolera, który wyświetla metadane aktualnie odtwarzanych multimediów i zawiera kilka przycisków do sterowania odtwarzaniem.

Dodawanie przycisku Cast

Platforma udostępnia komponent przycisku Cast jako podklasę UIButton. Można go dodać do paska tytułu aplikacji, umieszczając go w elemencie 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 powoduje otwarcie okna Cast udostępnianego przez platformę.

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

Konfigurowanie wykrywania urządzeń

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

Wykrywaniem w ramach platformy zarządza klasa GCKDiscoveryManager, która jest właściwością GCKCastContext. Platforma udostępnia domyślny komponent okna Cast do wyboru urządzenia i sterowania nim. Lista urządzeń jest uporządkowana leksykograficznie według przyjaznej nazwy urządzenia.

Jak działa zarządzanie sesjami

Pakiet Cast SDK wprowadza koncepcję sesji przesyłania, której ustanowienie łączy kroki łączenia się z urządzeniem, uruchamiania aplikacji Web Receiver (lub dołączania do niej), łączenia się z tą aplikacją i inicjowania kanału sterowania multimediami. Więcej informacji o sesjach Cast i cyklu życia odbiornika internetowego znajdziesz w przewodniku po cyklu życia aplikacji na odbiorniku internetowym.

Sesjami zarządza klasa GCKSessionManager, która jest właściwością GCKCastContext. Poszczególne sesje są reprezentowane przez podklasy klasy GCKSession, np. GCKCastSession reprezentuje sesje z urządzeniami przesyłającymi. Dostęp do aktualnie aktywnej sesji przesyłania (jeśli taka istnieje) możesz uzyskać za pomocą właściwości currentCastSession obiektu GCKSessionManager.

Interfejsu GCKSessionManagerListener można używać do monitorowania zdarzeń sesji, takich jak utworzenie, zawieszenie, wznowienie i zakończenie sesji. Platforma 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 lub nagłym zakończeniu działania, gdy sesja była aktywna).

Jeśli używane jest okno Cast, sesje są tworzone i zamykane automatycznie w odpowiedzi na gesty użytkownika. W przeciwnym razie aplikacja może jawnie rozpoczynać i kończyć sesje za pomocą metod w GCKSessionManager.

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

Przeniesienie odtwarzania

Zachowanie stanu sesji jest podstawą przenoszenia odtwarzania, dzięki któremu 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 zostanie zatrzymane na jednym urządzeniu (źródłowym) i będzie kontynuowane na innym (docelowym). Każde urządzenie Cast z najnowszym oprogramowaniem układowym może służyć jako źródło lub miejsce docelowe przeniesienia odtwarzania.

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

Więcej informacji znajdziesz w artykule Przenoszenie odtwarzania w odbiorniku internetowym.

Automatyczne ponowne łączenie

Platforma 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
  • Wybudzanie urządzenia z trybu uśpienia
  • Przywracanie po przełączeniu aplikacji w tło
  • Przywracanie po awarii aplikacji

Jak działają opcje sterowania multimediami

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

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

Instancja GCKRemoteMediaClient może być udostępniana przez wiele części aplikacji, a niektóre wewnętrzne komponenty platformy, takie jak okno Cast i miniodtwarzacz, faktycznie udostępniają instancję. W tym celu GCKRemoteMediaClient obsługuje rejestrację wielu GCKRemoteMediaClientListener.

Ustawianie metadanych multimediów

Klasa GCKMediaMetadata reprezentuje informacje o elemencie multimedialnym, który chcesz przesyłać. W poniższym przykładzie tworzona jest nowa instancja GCKMediaMetadata filmu i ustawiany jest tytuł, podtytuł, nazwa studia nagraniowego oraz 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]];

Informacje o używaniu obrazów z metadanymi multimediów znajdziesz w sekcji Wybór i buforowanie obrazów.

Wczytaj multimedia

Aby wczytać element multimedialny, utwórz instancję GCKMediaInformation, używając metadanych multimediów. Następnie pobierz bieżący obiekt GCKCastSession i użyj jego metody GCKRemoteMediaClient, aby wczytać multimedia w aplikacji odbiornika. Następnie możesz użyć metody GCKRemoteMediaClient do sterowania aplikacją odtwarzacza multimediów działającą na odbiorniku, 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;
}

Zobacz też sekcję dotyczącą używania ścieżek multimedialnych.

Format filmu 4K

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

Dodawanie minikontrolerów

Zgodnie z listą kontrolną projektowania Cast aplikacja nadawcy powinna udostępniać stały element sterujący, znany jako minikontroler, który powinien pojawiać się, gdy użytkownik opuści bieżącą stronę treści. Minikontroler zapewnia natychmiastowy dostęp do bieżącej sesji przesyłania i jest jej widocznym przypomnieniem.

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

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

Informacje o tym, jak aplikacja nadawcy może konfigurować wygląd widżetów Cast, znajdziesz w artykule Dostosowywanie interfejsu nadawcy na iOS.

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

  • Pozwól platformie Cast zarządzać układem miniodtwarzacza, opakowując istniejący kontroler widoku własnym kontrolerem widoku.
  • Zarządzaj układem widżetu miniodtwarzacza, dodając go do istniejącego kontrolera widoku przez udostępnienie podwidoku w scenorysie.

Zawijanie za pomocą GCKUICastContainerViewController

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

Pierwszy sposób jest zwykle realizowany 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 miniodtwarzacza bezpośrednio do istniejącego kontrolera widoku za pomocą metody 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 podwidok:

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

GCKUIMiniMediaControlsViewControllerDelegate informuje kontroler widoku hosta, kiedy minikontroler powinien być widoczny:

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ą projektowania Google Cast aplikacja nadawcy musi udostępniać rozszerzony kontroler do przesyłanych multimediów. Rozwinięty kontroler to pełnoekranowa wersja minikontrolera.

Rozwinięty kontroler to widok pełnoekranowy, który zapewnia pełną kontrolę nad zdalnym odtwarzaniem multimediów. Ten widok powinien umożliwiać aplikacji przesyłającej zarządzanie wszystkimi aspektami sesji przesyłania, którymi można zarządzać, z wyjątkiem sterowania głośnością odbiornika internetowego i cyklu życia sesji (łączenie/zatrzymywanie przesyłania). Zawiera też wszystkie informacje o stanie sesji multimedialnej (grafika, tytuł, podtytuł itp.).

Funkcje tego widoku są implementowane przez klasę GCKUIExpandedMediaControlsViewController.

Najpierw musisz włączyć domyślny rozszerzony kontroler w kontekście przesyłania. Zmodyfikuj obiekt delegowany 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;

  ..
}

Dodaj do kontrolera widoku ten kod, aby wczytać rozszerzony kontroler, gdy użytkownik zacznie przesyłać film:

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

Rozwinięty kontroler uruchomi się też automatycznie, gdy użytkownik kliknie minikontroler.

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

Informacje o tym, jak aplikacja nadawcy może konfigurować wygląd widżetów Cast, znajdziesz w artykule Stosowanie stylów niestandardowych w aplikacji na iOS.

Sterowanie głośnością

Platforma Cast automatycznie zarządza głośnością w aplikacji nadawcy. Platforma automatycznie synchronizuje głośność z odbiornikiem internetowym 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 przesyłającym mogą służyć do zmiany głośności sesji przesyłania na odbiorniku internetowym 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ługuj błędy

Bardzo ważne jest, aby aplikacje nadawcze obsługiwały wszystkie wywołania zwrotne błędów i podejmowały najlepsze decyzje na każdym etapie cyklu życia Cast. Aplikacja może wyświetlać użytkownikowi okna z błędami lub zakończyć sesję przesyłania.

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

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

Logowanie

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

Za pomocą GCKLogger pakiet SDK generuje dane wyjściowe logowania w postaci komunikatów debugowania, błędów i ostrzeżeń. Te komunikaty logu ułatwiają debugowanie i są przydatne podczas rozwiązywania problemów oraz ich identyfikowania. Domyślnie dane wyjściowe logu są ukryte, ale po przypisaniu GCKLoggerDelegate aplikacja nadawcy może odbierać te wiadomości z pakietu SDK i rejestrować 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 obiektu delegowanego (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 logu generowane przez GCKLogger. Ustaw minimalny poziom logowania dla każdej klasy, na przykład:

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, na przykład GCKUI\* i GCK\*Session.