В этом руководстве для разработчиков описано, как добавить поддержку 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) } } }
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)
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))
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 }
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() ... }
- (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 } } }
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 }
- (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) } } ...
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() }
- (void)miniMediaControlsViewController: (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController shouldAppear:(BOOL)shouldAppear { [self updateControlBarsVisibility]; }
Добавить расширенный контроллер
В соответствии с требованиями Google Cast, приложение-отправитель должно предоставлять расширенный контроллер для транслируемого медиаконтента. Расширенный контроллер представляет собой полноэкранную версию мини-контроллера.
Расширенный контроллер представляет собой полноэкранный режим, обеспечивающий полный контроль над воспроизведением мультимедиа с удаленного устройства. Этот режим должен позволять приложению для трансляции управлять всеми аспектами сеанса трансляции, за исключением регулировки громкости Web Receiver и жизненного цикла сеанса (подключение/остановка трансляции). Он также предоставляет всю информацию о состоянии сеанса мультимедиа (обложка, название, субтитры и т. д.).
Функциональность этого представления реализована классом GCKUIExpandedMediaControlsViewController .
Первое, что вам нужно сделать, это включить расширенный контроллер по умолчанию в контексте трансляции. Измените делегат приложения, чтобы включить расширенный контроллер по умолчанию:
func applicationDidFinishLaunching(_ application: UIApplication) { .. GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true ... }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES; .. }
Добавьте следующий код в контроллер представления, чтобы при начале трансляции видео пользователем загружался развернутый контроллер:
func playSelectedItemRemotely() { GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls() ... // Load your media sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInformation) }
- (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)
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) } } }
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
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
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init]; [filter setLoggingLevel:GCKLoggerLevelVerbose forClasses:@[@"GCKUICastButton", @"GCKUIImageCache", @"NSMutableDictionary" ]]; [GCKLogger sharedInstance].filter = filter;
Имена классов могут быть либо буквальными именами, либо шаблонами glob, например, GCKUI\* и GCK\*Session .