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

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

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

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

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

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

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

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

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

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

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

Инициализируйте контекст приведения типов.

Фреймворк 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

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

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

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

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

SDK Cast представляет концепцию сессии Cast, установление которой объединяет шаги подключения к устройству, запуска (или присоединения) приложения Web Receiver, подключения к этому приложению и инициализации канала управления мультимедиа. Дополнительную информацию о сессиях Cast и жизненном цикле Web Receiver см. в руководстве по жизненному циклу приложения Web Receiver.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Предполагается, что экземпляр GCKRemoteMediaClient может использоваться несколькими частями приложения, и действительно, некоторые внутренние компоненты фреймворка, такие как диалоговое окно Cast и мини-элементы управления мультимедиа, используют один и тот же экземпляр. В связи с этим 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 автоматически отображает кнопку воспроизведения/остановки вместо кнопки воспроизведения/паузы на мини-контроллере.

См. раздел «Настройка пользовательского интерфейса отправителя 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()

  ...
}
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, приложение-отправитель должно предоставлять расширенный контроллер для транслируемого медиаконтента. Расширенный контроллер представляет собой полноэкранную версию мини-контроллера.

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

Функциональность этого представления реализована классом 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 автоматически отображает кнопку воспроизведения/остановки вместо кнопки воспроизведения/паузы в расширенном контроллере.

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

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

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

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

Физические кнопки регулировки громкости на устройстве-отправителе можно использовать для изменения громкости сеанса Cast на веб-приемнике с помощью флага 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];

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

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

Обратите внимание, что некоторые ошибки, включая GCKErrorCode GCKErrorCodeCancelled , являются запланированным поведением.

Не пытайтесь повторно установить соединение, которое завершилось с ошибкой GCKErrorCodeCancelled . Это может привести к непредвиденным последствиям.

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

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;

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