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

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

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

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

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

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

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

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

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

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

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

Инициализировать контекст Cast

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

Объект GCKCastOptions должен быть предоставлен при инициализации GCKCastContext . Этот класс содержит параметры, влияющие на поведение фреймворка. Наиболее важным из них является идентификатор приложения 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)
    }
  }
}
Цель-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 Design:

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

  • Кнопка трансляции : начиная с версии SDK 4.6.0 для отправителя iOS, кнопка трансляции всегда видна, когда устройство-отправитель подключено к сети Wi-Fi. Когда пользователь впервые нажимает кнопку 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)
Цель-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 и жизненном цикле веб-приемника см. в руководстве по жизненному циклу приложения веб-приемника.

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

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

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

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

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

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

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

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

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

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

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

Как работает медиа-контроль

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

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

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

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

Класс 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))
Цель-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
}
Цель-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;
}

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

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

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

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

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

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

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

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

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

  • Позвольте платформе 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()

  ...
}
Цель-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
    }
  }
}
Цель-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
}
Цель-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)
    }
  }

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

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

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

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

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

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

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

  ...
}
Цель-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)
}
Цель-C
- (void)playSelectedItemRemotely {
  [[GCKCastContext sharedInstance] presentDefaultExpandedMediaControls];

  ...

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

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

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

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

Контроль громкости

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

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

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

Быстрый
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
Цель-C
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                          initWithApplicationID:kReceiverAppID];
GCKCastOptions *options = [[GCKCastOptions alloc]
                                          initWithDiscoveryCriteria :criteria];
options.physicalVolumeButtonsWillControlDeviceVolume = YES;
[GCKCastContext setSharedInstanceWithOptions:options];

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

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

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

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)
    }
  }
}
Цель-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
Цель-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
Цель-C
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setLoggingLevel:GCKLoggerLevelVerbose
             forClasses:@[@"GCKUICastButton",
                          @"GCKUIImageCache",
                          @"NSMutableDictionary"
                          ]];
[GCKLogger sharedInstance].filter = filter;

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