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

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

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

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

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

Процесс приложения

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

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

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

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

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

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

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

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

Метод -[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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Если сеанс трансляции установлен с приложением веб-приемника, которое поддерживает пространство имен мультимедиа, экземпляр 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 предоставляет панель управления GCKUIMiniMediaControlsViewController , которую можно добавить в сцены, в которых вы хотите отобразить мини-контроллер.

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

См. раздел «Настройка пользовательского интерфейса iOS Sender» , чтобы узнать, как ваше приложение-отправитель может настроить внешний вид виджетов 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

Руотконтейнервиевконтроллер.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

Руотконтейнервиевконтроллер.м

@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 .

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

Кнопки физической громкости на устройстве-отправителе можно использовать для изменения громкости сеанса Cast на веб-приемнике с помощью флага 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. Приложение может отображать пользователю диалоговые окна об ошибках или может принять решение о завершении сеанса трансляции.

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

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;

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