Интегрируйте Cast в свое приложение для iOS

В этом руководстве разработчика описывается, как добавить поддержку Google Cast в приложение-передатчик iOS с помощью iOS Sender SDK.

Мобильное устройство или ноутбук является отправителем , который управляет воспроизведением, а устройство Google Cast — приемником , который отображает контент на телевизоре.

Фреймворк отправителя относится к двоичному файлу библиотеки классов Cast и связанным с ним ресурсам, присутствующим во время выполнения на отправителе. Приложение отправителя (или приложение Cast) относится к приложению, также работающему на отправителе. Приложение веб-приёмника относится к HTML-приложению, работающему на веб-приёмнике.

Платформа отправителя использует асинхронную конструкцию обратного вызова для информирования приложения-отправителя о событиях и для перехода между различными состояниями жизненного цикла приложения Cast.

Поток приложения

Следующие шаги описывают типичный высокоуровневый поток выполнения для приложения-отправителя iOS:

  • Платформа Cast запускает GCKDiscoveryManager на основе свойств, предоставленных в GCKCastOptions , чтобы начать сканирование устройств.
  • Когда пользователь нажимает кнопку Cast, фреймворк отображает диалоговое окно Cast со списком обнаруженных устройств Cast.
  • Когда пользователь выбирает устройство Cast, фреймворк пытается запустить приложение Web Receiver на устройстве Cast.
  • Фреймворк вызывает обратные вызовы в приложении-отправителе, чтобы подтвердить запуск приложения Web Receiver.
  • Фреймворк создает канал связи между отправителем и приложениями Web Receiver.
  • Фреймворк использует канал связи для загрузки и управления воспроизведением мультимедиа на веб-приемнике.
  • Фреймворк синхронизирует состояние воспроизведения мультимедиа между отправителем и веб-приемником: когда пользователь выполняет действия в пользовательском интерфейсе отправителя, фреймворк передает эти запросы на управление мультимедиа веб-приемнику, а когда веб-приемник отправляет обновления статуса мультимедиа, фреймворк обновляет состояние пользовательского интерфейса отправителя.
  • Когда пользователь нажимает кнопку Cast, чтобы отключиться от устройства Cast, фреймворк отключает приложение-отправитель от веб-приемника.

Для устранения неполадок отправителя вам необходимо включить ведение журнала .

Полный список всех классов, методов и событий фреймворка Google Cast для iOS см. в справочнике Google Cast iOS API . В следующих разделах описываются шаги по интеграции Cast в ваше приложение для iOS.

Вызов методов из основного потока

Инициализируйте контекст Cast

Фреймворк Cast имеет глобальный синглтон-объект GCKCastContext , который координирует все действия фреймворка. Этот объект должен быть инициализирован на ранних этапах жизненного цикла приложения, обычно в методе -[application:didFinishLaunchingWithOptions:] делегата приложения, чтобы автоматическое возобновление сеанса при перезапуске приложения-отправителя могло сработать корректно.

При инициализации GCKCastContext необходимо указать объект GCKCastOptions . Этот класс содержит параметры, влияющие на поведение фреймворка. Наиболее важным из них является идентификатор приложения Web Receiver, который используется для фильтрации результатов обнаружения и запуска приложения Web Receiver при начале сеанса Cast.

Метод -[application:didFinishLaunchingWithOptions:] также подходит для настройки делегата логирования, который будет получать сообщения логирования от фреймворка. Это может быть полезно для отладки и устранения неполадок.

Быстрый
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate {
  let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID
  let kDebugLoggingEnabled = true

  var window: UIWindow?

  func applicationDidFinishLaunching(_ application: UIApplication) {
    let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
    let options = GCKCastOptions(discoveryCriteria: criteria)
    GCKCastContext.setSharedInstanceWith(options)

    // Enable logger.
    GCKLogger.sharedInstance().delegate = self

    ...
  }

  // MARK: - GCKLoggerDelegate

  func logMessage(_ message: String,
                  at level: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if (kDebugLoggingEnabled) {
      print(function + " - " + message)
    }
  }
}
Objective-C

AppDelegate.h

@interface AppDelegate () <GCKLoggerDelegate>
@end

AppDelegate.m

@implementation AppDelegate

static NSString *const kReceiverAppID = @"AABBCCDD";
static const BOOL kDebugLoggingEnabled = YES;

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                    initWithApplicationID:kReceiverAppID];
  GCKCastOptions *options = [[GCKCastOptions alloc] initWithDiscoveryCriteria:criteria];
  [GCKCastContext setSharedInstanceWithOptions:options];

  // Enable logger.
  [GCKLogger sharedInstance].delegate = self;

  ...

  return YES;
}

...

#pragma mark - GCKLoggerDelegate

- (void)logMessage:(NSString *)message
           atLevel:(GCKLoggerLevel)level
      fromFunction:(NSString *)function
          location:(NSString *)location {
  if (kDebugLoggingEnabled) {
    NSLog(@"%@ - %@, %@", function, message, location);
  }
}

@end

Виджеты Cast UX

Cast iOS SDK предоставляет следующие виджеты, которые соответствуют контрольному списку дизайна Cast:

  • Вводный оверлей : класс GCKCastContext имеет метод presentCastInstructionsViewControllerOnceWithCastButton , который можно использовать для подсветки кнопки трансляции при первом доступе веб-приёмника. Приложение-отправитель может настроить текст, положение заголовка и кнопку «Отклонить».

  • Кнопка Cast : Начиная с версии Cast iOS Sender SDK 4.6.0, кнопка Cast всегда видна, когда устройство-отправитель подключено к Wi-Fi. При первом нажатии пользователем кнопки Cast после первоначального запуска приложения появляется диалоговое окно разрешений, позволяющее пользователю предоставить приложению доступ к устройствам в локальной сети. Впоследствии, когда пользователь нажимает кнопку Cast, отображается диалоговое окно Cast со списком обнаруженных устройств. Когда пользователь нажимает кнопку Cast, когда устройство подключено, оно отображает текущие метаданные мультимедиа (такие как название, название студии звукозаписи и миниатюрное изображение) или позволяет пользователю отключиться от устройства Cast. Когда пользователь нажимает кнопку Cast, когда нет доступных устройств, отображается экран с информацией о том, почему устройства не найдены, и как устранить неполадки.

  • Мини-контроллер : когда пользователь транслирует контент и переходит со страницы текущего контента или расширенного контроллера на другой экран в приложении-отправителе, в нижней части экрана отображается мини-контроллер, позволяющий пользователю видеть метаданные текущего транслируемого медиаконтента и управлять воспроизведением.

  • Расширенный контроллер : если пользователь транслирует контент, то при нажатии на уведомление мультимедиа или мини-контроллер запускается расширенный контроллер, который отображает метаданные воспроизводимого в данный момент мультимедиа и предоставляет несколько кнопок для управления воспроизведением мультимедиа.

Добавить кнопку трансляции

Фреймворк предоставляет компонент кнопки Cast в качестве подкласса UIButton . Его можно добавить в заголовок приложения, обернув в UIBarButtonItem . Типичный подкласс UIViewController может установить кнопку Cast следующим образом:

Быстрый
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];

По умолчанию нажатие кнопки открывает диалоговое окно Cast, предоставляемое фреймворком.

GCKUICastButton также можно добавить непосредственно в раскадровку.

Настроить обнаружение устройств

В рамках фреймворка обнаружение устройств происходит автоматически. Нет необходимости явно запускать или останавливать процесс обнаружения, если только вы не реализуете собственный пользовательский интерфейс.

Обнаружение устройств во фреймворке управляется классом GCKDiscoveryManager , который является свойством GCKCastContext . Фреймворк предоставляет стандартный компонент диалога Cast для выбора и управления устройствами. Список устройств упорядочен лексикографически по понятным именам устройств.

Как работает управление сеансами

В Cast SDK представлена ​​концепция сеанса Cast, установление которого включает в себя следующие этапы: подключение к устройству, запуск (или присоединение) приложения Web Receiver, подключение к этому приложению и инициализация канала управления медиаконтентом. Подробнее о сеансах Cast и жизненном цикле Web Receiver см. в руководстве по жизненному циклу приложения Web Receiver.

Сеансы управляются классом GCKSessionManager , который является свойством GCKCastContext . Отдельные сеансы представлены подклассами класса GCKSession : например, GCKCastSession представляет сеансы с устройствами Cast. Доступ к текущему активному сеансу Cast (если таковой имеется) можно получить через свойство currentCastSession класса GCKSessionManager .

Интерфейс GCKSessionManagerListener можно использовать для мониторинга событий сеанса, таких как создание, приостановка, возобновление и завершение сеанса. Фреймворк автоматически приостанавливает сеансы, когда приложение-отправитель переходит в фоновый режим, и пытается возобновить их, когда приложение возвращается на передний план (или перезапускается после аварийного/внезапного завершения работы приложения во время активного сеанса).

Если используется диалоговое окно «Трансляция», сеансы создаются и завершаются автоматически в ответ на жесты пользователя. В противном случае приложение может запускать и завершать сеансы явно с помощью методов GCKSessionManager .

Если приложению необходимо выполнить специальную обработку в ответ на события жизненного цикла сеанса, оно может зарегистрировать один или несколько экземпляров GCKSessionManagerListener в GCKSessionManager . GCKSessionManagerListener — это протокол, который определяет обратные вызовы для таких событий, как начало сеанса, конец сеанса и т. д.

Потоковая передача

Сохранение состояния сеанса — основа потоковой передачи, при которой пользователи могут перемещать существующие аудио- и видеопотоки между устройствами с помощью голосовых команд, приложения Google Home или умных дисплеев. Воспроизведение медиафайлов останавливается на одном устройстве (источнике) и продолжается на другом (приемнике). Любое устройство Cast с последней версией прошивки может служить источником или приемником потоковой передачи.

Чтобы получить новое целевое устройство во время передачи потока, используйте свойство GCKCastSession#device во время обратного вызова [sessionManager:didResumeCastSession:] .

Дополнительную информацию см. в разделе Передача потока на веб-приемнике .

Автоматическое переподключение

Фреймворк Cast добавляет логику повторного подключения для автоматической обработки повторного подключения во многих сложных случаях, таких как:

  • Восстановление после временной потери Wi-Fi
  • Выход устройства из спящего режима
  • Восстановление после перевода приложения в фоновый режим
  • Восстановление в случае сбоя приложения

Как работает контроль над СМИ

Если сеанс Cast установлен с помощью приложения Web Receiver, которое поддерживает пространство имен мультимедиа, экземпляр GCKRemoteMediaClient будет создан фреймворком автоматически; к нему можно получить доступ как к свойству remoteMediaClient экземпляра GCKCastSession .

Все методы GCKRemoteMediaClient , отправляющие запросы к веб-приёмнику, возвращают объект GCKRequest , который можно использовать для отслеживания запроса. Этому объекту можно назначить GCKRequestDelegate для получения уведомлений о конечном результате операции.

Предполагается, что экземпляр GCKRemoteMediaClient может совместно использоваться несколькими частями приложения, и некоторые внутренние компоненты фреймворка, такие как диалоговое окно трансляции и мини-элементы управления мультимедиа, действительно используют этот экземпляр. Для этого GCKRemoteMediaClient поддерживает регистрацию нескольких прослушивателей GCKRemoteMediaClientListener .

Установить метаданные медиа

Класс GCKMediaMetadata содержит информацию о медиафайле, который вы хотите транслировать. В следующем примере создаётся новый экземпляр GCKMediaMetadata фильма и задаётся название, подзаголовок, название студии звукозаписи и два изображения.

Быстрый
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]];

Информацию об использовании изображений с метаданными мультимедиа см. в разделе «Выбор и кэширование изображений» .

Загрузить медиа

Чтобы загрузить медиафайл, создайте экземпляр GCKMediaInformation , используя метаданные медиафайла. Затем получите текущий GCKCastSession и используйте его GCKRemoteMediaClient для загрузки медиафайла в приложение-приёмник. После этого вы сможете использовать GCKRemoteMediaClient для управления медиаплеером, запущенным на приёмнике, например, для воспроизведения, приостановки и остановки.

Быстрый
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;
}

См. также раздел об использовании медиа-треков .

Формат видео 4K

Чтобы определить видеоформат медиаконтента, используйте свойство videoInfo объекта GCKMediaStatus для получения текущего экземпляра GCKVideoInfo . Этот экземпляр содержит тип формата HDR TV, а также высоту и ширину в пикселях. Варианты формата 4K указаны в свойстве hdrType с помощью перечисления значений GCKVideoInfoHDRType .

Добавить мини-контроллеры

Согласно контрольному списку дизайна Cast , приложение-отправитель должно предоставлять постоянный элемент управления, известный как мини-контроллер , который должен появляться при уходе пользователя с текущей страницы контента. Мини-контроллер обеспечивает мгновенный доступ и визуальное напоминание о текущем сеансе Cast.

Платформа Cast предоставляет панель управления GCKUIMiniMediaControlsViewController , которую можно добавлять в сцены, в которых вы хотите отображать мини-контроллер.

Когда приложение-отправитель воспроизводит потоковое видео или аудио, SDK автоматически отображает кнопку воспроизведения/остановки вместо кнопки воспроизведения/паузы на мини-контроллере.

Информацию о том, как приложение-отправитель может настроить внешний вид виджетов Cast, см. в разделе Настройка пользовательского интерфейса iOS Sender.

Добавить мини-контроллер в приложение-отправитель можно двумя способами:

  • Позвольте платформе Cast управлять компоновкой мини-контроллера, обернув ваш существующий контроллер представления в его собственный контроллер представления.
  • Управляйте макетом виджета мини-контроллера самостоятельно, добавляя его к существующему контроллеру представлений и предоставляя подпредставление в раскадровке.

Обертка с использованием GCKUICastContainerViewController

Первый способ — использовать GCKUICastContainerViewController , который оборачивает другой контроллер представления и добавляет GCKUIMiniMediaControlsViewController внизу. Этот подход ограничен тем, что вы не можете настраивать анимацию и поведение контроллера представления контейнера.

Первый способ обычно реализуется в методе -[application:didFinishLaunchingWithOptions:] делегата приложения:

Быстрый
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];
  ...

}
Быстрый
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

Встроить в существующий контроллер представления

Второй способ — добавить мини-контроллер непосредственно к существующему контроллеру представления, используя createMiniMediaControlsViewController для создания экземпляра GCKUIMiniMediaControlsViewController , а затем добавить его к контроллеру представления контейнера в качестве подпредставления.

Настройте контроллер представления в делегате приложения:

Быстрый
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;
}

В корневом контроллере представления создайте экземпляр GCKUIMiniMediaControlsViewController и добавьте его в контроллер представления контейнера как подпредставление:

Быстрый
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 сообщает контроллеру представления хоста, когда мини-контроллер должен быть видимым:

Быстрый
  func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController,
                                       shouldAppear _: Bool) {
    updateControlBarsVisibility()
  }
Objective-C
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

Добавить расширенный контроллер

Контрольный список дизайна Google Cast требует, чтобы приложение-отправитель предоставляло расширенный контроллер для транслируемого медиаконтента. Расширенный контроллер представляет собой полноэкранную версию мини-контроллера.

Расширенный контроллер — это полноэкранное представление, которое обеспечивает полный контроль над воспроизведением удалённого медиаконтента. Это представление позволяет приложению для трансляции управлять всеми управляемыми аспектами сеанса трансляции, за исключением регулировки громкости веб-приёмника и управления жизненным циклом сеанса (подключение/остановка трансляции). Оно также предоставляет всю информацию о состоянии сеанса (обложка, название, субтитры и т. д.).

Функциональность этого представления реализуется классом GCKUIExpandedMediaControlsViewController .

Первое, что вам нужно сделать, — это включить расширенный контроллер по умолчанию в контексте приведения. Измените делегат приложения, чтобы включить расширенный контроллер по умолчанию:

Быстрый
func applicationDidFinishLaunching(_ application: UIApplication) {
  ..

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

Добавьте следующий код в контроллер представления, чтобы загрузить расширенный контроллер, когда пользователь начинает транслировать видео:

Быстрый
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];
}

Расширенный контроллер также будет запускаться автоматически, когда пользователь нажмет на мини-контроллер.

Когда приложение-отправитель воспроизводит потоковое видео или аудио, SDK автоматически отображает кнопку воспроизведения/остановки вместо кнопки воспроизведения/паузы в расширенном контроллере.

Сведения о том, как приложение-отправитель может настроить внешний вид виджетов Cast, см. в разделе Применение пользовательских стилей к приложению iOS.

Регулятор громкости

Фреймворк Cast автоматически управляет громкостью для приложения-отправителя. Фреймворк автоматически синхронизируется с громкостью веб-приёмника для предоставленных виджетов пользовательского интерфейса. Для синхронизации ползунка, предоставляемого приложением, используйте GCKUIDeviceVolumeController .

Физическая кнопка регулировки громкости

Физические кнопки регулировки громкости на устройстве-отправителе можно использовать для изменения громкости сеанса трансляции на веб-приемнике с помощью флага physicalVolumeButtonsWillControlDeviceVolume в GCKCastOptions , который устанавливается в GCKCastContext .

Быстрый
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];

Обработка ошибок

Для приложений-отправителей крайне важно обрабатывать все обратные вызовы ошибок и выбирать наилучший ответ для каждого этапа жизненного цикла трансляции. Приложение может отображать пользователю диалоговые окна с сообщениями об ошибках или принимать решение о завершении сеанса трансляции.

Ведение журнала

GCKLogger — это синглтон, используемый фреймворком для ведения журналов. Используйте GCKLoggerDelegate для настройки обработки сообщений журнала.

Используя GCKLogger , SDK создает журнал событий в виде отладочных сообщений, сообщений об ошибках и предупреждений. Эти сообщения журнала помогают в отладке и полезны для поиска и устранения неполадок. По умолчанию журнал событий отключен, но, назначив GCKLoggerDelegate , приложение-отправитель может получать эти сообщения от SDK и выводить их в системную консоль.

Быстрый
@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

Чтобы включить отладку и подробные сообщения, добавьте эту строку в код после настройки делегата (показано ранее):

Быстрый
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;

Вы также можете фильтровать сообщения журнала, создаваемые GCKLogger . Установите минимальный уровень ведения журнала для каждого класса, например:

Быстрый
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;

Имена классов могут быть как буквальными именами, так и шаблонами глобальных переменных, например, GCKUI\* и GCK\*Session .