En esta guía para desarrolladores, se describe cómo agregar compatibilidad con Google Cast a tu app emisora para iOS con el SDK de iOS Sender.
El dispositivo móvil o la laptop es el emisor que controla la reproducción, y el dispositivo Google Cast es el receptor que muestra el contenido en la TV.
El framework del emisor se refiere al objeto binario de la biblioteca de clases de Cast y a los recursos asociados presentes en el tiempo de ejecución en el emisor. La app emisora o app de Cast se refiere a una app que también se ejecuta en el emisor. La app Web Receiver se refiere a la aplicación HTML que se ejecuta en Web Receiver.
El framework del emisor usa un diseño de devolución de llamada asíncrono para informar a la app emisora sobre los eventos y realizar la transición entre varios estados del ciclo de vida de la app de Cast.
Flujo de la app
En los siguientes pasos, se describe el flujo de ejecución típico de alto nivel para una app emisora para iOS:
- El framework de Cast inicia
GCKDiscoveryManageren función de las propiedades proporcionadas enGCKCastOptionspara comenzar a buscar dispositivos. - Cuando el usuario hace clic en el botón para transmitir, el framework presenta el diálogo de Cast con la lista de dispositivos de transmisión descubiertos.
- Cuando el usuario selecciona un dispositivo de transmisión, el framework intenta iniciar la app Web Receiver en el dispositivo de transmisión.
- El framework invoca devoluciones de llamada en la app emisora para confirmar que se inició la app Web Receiver.
- El framework crea un canal de comunicación entre las apps emisora y Web Receiver.
- El framework usa el canal de comunicación para cargar y controlar la reproducción de contenido multimedia en Web Receiver.
- El framework sincroniza el estado de reproducción de contenido multimedia entre el emisor y Web Receiver: cuando el usuario realiza acciones de la IU del emisor, el framework pasa esas solicitudes de control de contenido multimedia a Web Receiver y, cuando Web Receiver envía actualizaciones de estado de contenido multimedia, el framework actualiza el estado de la IU del emisor.
- Cuando el usuario hace clic en el botón para transmitir para desconectarse del dispositivo de transmisión, el framework desconectará la app emisora de Web Receiver.
Para solucionar problemas del emisor, debes habilitar el registro.
Para obtener una lista completa de todas las clases, los métodos y los eventos en el framework de Google Cast para iOS, consulta la referencia de la API de Google Cast para iOS. En las siguientes secciones, se describen los pasos para integrar Cast en tu app para iOS.
Llama a métodos desde el subproceso principal
Inicializa el contexto de Cast
El framework de Cast tiene un objeto singleton global, el
GCKCastContext, que
coordina todas las actividades del framework. Este objeto debe inicializarse con anticipación en el ciclo de vida de la aplicación, por lo general, en el método -[application:didFinishLaunchingWithOptions:] del delegado de la app. De esta manera, cuando se reinicie la app emisora, la reanudación automática de una sesión se activará correctamente.
Se debe proporcionar un GCKCastOptions
objeto cuando se inicializa GCKCastContext.
Esta clase contiene opciones que afectan el comportamiento del framework. La más importante es el ID de la aplicación Web Receiver, que se usa para filtrar los resultados de la detección y para iniciar la app Web Receiver cuando comienza una sesión de transmisión.
El método -[application:didFinishLaunchingWithOptions:] también sirve para configurar un delegado de registro a fin de recibir mensajes de registro del framework.
Esto puede ser útil, por ejemplo, para depurar y solucionar problemas.
@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
Los widgets de UX de Cast
El SDK de Cast para iOS proporciona estos widgets que cumplen con la lista de tareas de diseño de Cast:
Superposición introductoria: La clase
GCKCastContexttiene un método,presentCastInstructionsViewControllerOnceWithCastButton, que se puede usar para destacar el botón para transmitir la primera vez que está disponible Web Receiver. La app emisora puede personalizar el texto, la posición del texto del título y el botón Descartar.Botón para transmitir: A partir del SDK de Cast iOS Sender 4.6.0, el botón para transmitir siempre está visible cuando el dispositivo emisor está conectado a Wi-Fi. La primera vez que el usuario presiona el botón para transmitir después de iniciar la app, aparece un diálogo de permisos para que el usuario pueda otorgar a la app acceso a la red local a los dispositivos de la red. Posteriormente, cuando el usuario presiona el botón para transmitir, se muestra un diálogo de transmisión que muestra los dispositivos descubiertos. Cuando el usuario presiona el botón para transmitir mientras el dispositivo está conectado, se muestran los metadatos de contenido multimedia actuales (como el título, el nombre del estudio de grabación y una imagen en miniatura) o se permite que el usuario se desconecte del dispositivo de transmisión. Cuando el usuario presiona el botón para transmitir mientras no hay dispositivos disponibles, se mostrará una pantalla que le brindará información sobre por qué no se encuentran los dispositivos y cómo solucionar el problema.
Minicontrol: Cuando el usuario transmite contenido y abandona la página de contenido actual o el control expandido a otra pantalla en la app emisora, el minicontrol se muestra en la parte inferior de la pantalla para permitir que el usuario vea los metadatos de contenido multimedia que se están transmitiendo y controle la reproducción.
Control expandido: Cuando el usuario transmite contenido, si hace clic en la notificación de contenido multimedia o en el minicontrol, se inicia el control expandido, que muestra los metadatos de contenido multimedia que se están reproduciendo y proporciona varios botones para controlar la reproducción de contenido multimedia.
Agrega un botón para transmitir
El framework proporciona un componente de botón para transmitir como una subclase UIButton. Puedes agregarlo a la barra de título de la app si la unes en un UIBarButtonItem. Una subclase UIViewController típica puede instalar un botón para transmitir de la siguiente manera:
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];
De forma predeterminada, si presionas el botón, se abrirá el diálogo de Cast que proporciona el framework.
GCKUICastButton
también se puede agregar directamente al storyboard.
Configura el descubrimiento de dispositivos
En el framework, el descubrimiento de dispositivos se realiza automáticamente. No es necesario iniciar ni detener explícitamente el proceso de descubrimiento, a menos que implementes una IU personalizada.
El descubrimiento en el framework se administra con la clase
GCKDiscoveryManager,
que es una propiedad de la
GCKCastContext. El framework proporciona un componente de diálogo de Cast predeterminado para la selección y el control de dispositivos. La lista de dispositivos se ordena lexicográficamente por nombre descriptivo del dispositivo.
Cómo funciona la administración de sesiones
El SDK de Cast presenta el concepto de una sesión de Cast, cuyo establecimiento combina los pasos para conectarse a un dispositivo, iniciar (o unirse a) una app Web Receiver, conectarse a esa app y, luego, inicializar un canal de control de contenido multimedia. Consulta la guía Ciclo de vida de la aplicación Web Receiver para obtener más información sobre las sesiones de Cast y el ciclo de vida de Web Receiver.
Las sesiones se administran con la clase
GCKSessionManager,
que es una propiedad de
GCKCastContext.
Las sesiones individuales se representan con subclases de la clase
GCKSession. Por ejemplo,
GCKCastSession
representa sesiones con dispositivos de transmisión. Puedes acceder a la sesión de Cast activa en ese momento (si la hay) como la propiedad currentCastSession de GCKSessionManager.
La
GCKSessionManagerListener
interfaz se puede usar para supervisar los eventos de sesión, como la creación,
la suspensión, la reanudación y la finalización de la sesión. El framework suspende automáticamente las sesiones cuando la app emisora pasa a segundo plano y trata de reanudarlas cuando la app vuelve a primer plano (o se reinicia después de una finalización anormal o abrupta de la app mientras una sesión estaba activa).
Si se usa el diálogo de Cast, las sesiones se crean y desconectan automáticamente en respuesta a los gestos del usuario. De lo contrario, la app puede iniciar y finalizar
sesiones de forma explícita a través de métodos en
GCKSessionManager.
Si la app necesita realizar un procesamiento especial en respuesta a los eventos del ciclo de vida de la sesión, puede registrar una o más instancias de GCKSessionManagerListener con GCKSessionManager. GCKSessionManagerListener es un protocolo que define devoluciones de llamada para eventos como el inicio y el final de la sesión, etcétera.
Transferencia de transmisión
La preservación del estado de la sesión es la base de la transferencia de transmisión, en la que los usuarios pueden mover transmisiones de audio y video existentes entre dispositivos con comandos de voz, la app de Google Home o pantallas inteligentes. El contenido multimedia deja de reproducirse en un dispositivo (la fuente) y continúa en otro (el destino). Cualquier dispositivo de transmisión con el firmware más reciente puede servir como fuente o destino en una transferencia de transmisión.
Para obtener el nuevo dispositivo de destino durante la transferencia de transmisión, usa la
GCKCastSession#device
propiedad durante la
[sessionManager:didResumeCastSession:]
devolución de llamada.
Consulta Transferencia de transmisión en Web Receiver para obtener más información.
Reconexión automática
El framework de Cast agrega lógica de reconexión para controlar automáticamente la reconexión en muchos casos extremos sutiles, como los siguientes:
- Recuperación de una pérdida temporal de Wi-Fi
- Recuperación del modo de suspensión del dispositivo
- Recuperación de la app en segundo plano
- Recuperación si la app falló
Cómo funciona el control de contenido multimedia
Si se establece una sesión de Cast con una app Web Receiver que admite el espacio de nombres de contenido multimedia, el framework creará automáticamente una instancia de
GCKRemoteMediaClient
. Se puede acceder a ella como la propiedad
remoteMediaClient de la instancia
GCKCastSession
.
Todos los métodos de GCKRemoteMediaClient que emiten solicitudes a Web Receiver
mostrarán un
GCKRequest objeto que
se puede usar para hacer un seguimiento de esa solicitud. A
GCKRequestDelegate
se puede asignar a este objeto para recibir notificaciones sobre el resultado final de la operación.
Se espera que la instancia de GCKRemoteMediaClient se pueda compartir entre varias partes de la app. De hecho, algunos componentes internos del framework, como el diálogo de Cast y los minicontroles de contenido multimedia, comparten la instancia. Para ello, GCKRemoteMediaClient
admite el registro de varios
GCKRemoteMediaClientListener.
Establece metadatos de contenido multimedia
La
GCKMediaMetadata
clase representa información sobre un elemento de contenido multimedia que deseas transmitir. En el siguiente ejemplo, se crea una instancia GCKMediaMetadata nueva de una película y se establecen el título, el subtítulo, el nombre del estudio de grabación y dos imágenes.
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]];
Consulta la sección Selección y almacenamiento en caché de imágenes sobre el uso de imágenes con metadatos de contenido multimedia.
Carga contenido multimedia
Para cargar un elemento de contenido multimedia, crea una
GCKMediaInformation
instancia con los metadatos del contenido multimedia. Luego, obtén el
GCKCastSession y
usa su
GCKRemoteMediaClient
para cargar el contenido multimedia en la app receptora. Luego, puedes usar GCKRemoteMediaClient
para controlar una app de reproductor de contenido multimedia que se ejecuta en el receptor, como reproducir,
pausar y detener.
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; }
Consulta también la sección sobre el uso de pistas de contenido multimedia.
Formato de video en 4K
Para determinar el formato de video de tu contenido multimedia, usa la videoInfo propiedad de
GCKMediaStatus
para obtener la instancia actual de
GCKVideoInfo.
Esta instancia contiene el tipo de formato de TV HDR y la altura y el ancho en píxeles. Las variantes del formato 4K se indican en la propiedad hdrType con valores de enum GCKVideoInfoHDRType.
Agrega minicontroles
Según la lista de tareas de diseño de Cast, una app emisora debe proporcionar un control persistente conocido como minicontrol que debe aparecer cuando el usuario abandona la página de contenido actual. El minicontrol proporciona acceso instantáneo y un recordatorio visible de la sesión de transmisión en curso.
El framework de Cast proporciona una barra de control,
GCKUIMiniMediaControlsViewController,
que se puede agregar a las escenas en las que quieres mostrar el minicontrol.
Cuando tu app emisora reproduce una transmisión en vivo de video o audio, el SDK muestra automáticamente un botón de reproducción/detención en lugar del botón de reproducción/pausa en el minicontrol.
Consulta Personaliza la IU del emisor para iOS para saber cómo tu app emisora puede configurar la apariencia de los widgets de Cast.
Hay dos formas de agregar el minicontrol a una app emisora:
- Permite que el framework de Cast administre el diseño del minicontrol uniendo tu controlador de vistas existente con su propio controlador de vistas.
- Administra el diseño del widget del minicontrol agregándolo a tu controlador de vistas existente proporcionando una vista secundaria en el storyboard.
Unir con GCKUICastContainerViewController
La primera forma es usar
GCKUICastContainerViewController
que une otro controlador de vistas y agrega un
GCKUIMiniMediaControlsViewController
en la parte inferior. Este enfoque es limitado, ya que no puedes personalizar la animación ni configurar el comportamiento del controlador de vistas del contenedor.
Por lo general, esta primera forma se realiza en el método -[application:didFinishLaunchingWithOptions:] del delegado de la app:
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
Incorpora en el controlador de vistas existente
La segunda forma es agregar el minicontrol directamente a tu controlador de vistas existente
usando
createMiniMediaControlsViewController
para crear una instancia
GCKUIMiniMediaControlsViewController
y, luego, agregarla al controlador de vistas del contenedor como una vista secundaria.
Configura tu controlador de vistas en el delegado de la app:
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; }
En el controlador de vistas raíz, crea una instancia GCKUIMiniMediaControlsViewController y agrégala al controlador de vistas del contenedor como una vista secundaria:
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
The
GCKUIMiniMediaControlsViewControllerDelegate
le indica al controlador de vistas del host cuándo debe estar visible el minicontrol:
func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController, shouldAppear _: Bool) { updateControlBarsVisibility() }
- (void)miniMediaControlsViewController: (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController shouldAppear:(BOOL)shouldAppear { [self updateControlBarsVisibility]; }
Agrega un control expandido
La lista de tareas de diseño de Google Cast requiere que la app emisora proporcione un control expandido para el contenido multimedia que se está transmitiendo. Este control expandido es una versión de pantalla completa del minicontrol.
El control expandido es una vista de pantalla completa que ofrece un control total de la reproducción multimedia remota. Esta vista debería permitir que una app de transmisión gestione todos los aspectos de la administración de una sesión de transmisión, excepto el control de volumen de Web Receiver y el ciclo de vida de la sesión (conectar/detener transmisión). También proporciona toda la información de estado sobre la sesión multimedia (arte gráfico, títulos, subtítulos, etcétera).
La funcionalidad de esta vista se implementa con la
GCKUIExpandedMediaControlsViewController
clase.
Lo primero que debes hacer es habilitar el control expandido predeterminado en el contexto de transmisión. Modifica el delegado de la app para habilitar el control expandido predeterminado:
func applicationDidFinishLaunching(_ application: UIApplication) { .. GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true ... }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES; .. }
Agrega el siguiente código a tu controlador de vistas para cargar el control expandido cuando el usuario comience a transmitir un video:
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]; }
El control expandido también se iniciará automáticamente cuando el usuario presione el minicontrol.
Cuando tu app emisora reproduce una transmisión en vivo de video o audio, el SDK muestra automáticamente un botón de reproducción/detención en lugar del botón de reproducción/pausa en el control expandido.
Consulta Aplica estilos personalizados a tu app para iOS para saber cómo tu app emisora puede configurar la apariencia de los widgets de Cast.
Control de volumen
El framework de Cast administra automáticamente el volumen de la app emisora. El framework se sincroniza automáticamente con el volumen de Web Receiver para los widgets de la IU proporcionados. Para sincronizar un control deslizante proporcionado por la app, usa
GCKUIDeviceVolumeController.
Control de volumen del botón físico
Los botones de volumen físicos del dispositivo emisor se pueden usar para cambiar el
volumen de la sesión de Cast en Web Receiver con la
physicalVolumeButtonsWillControlDeviceVolume marca en la
GCKCastOptions,
que se establece en el
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];
Soluciona errores
Es muy importante que las apps emisoras controlen todas las devoluciones de llamada de error y decidan la mejor respuesta para cada etapa del ciclo de vida de Cast. La app puede mostrar diálogos de error al usuario o puede decidir finalizar la sesión de Cast.
Ten en cuenta que algunos errores, incluido el
GCKErrorCode
GCKErrorCodeCancelled, son comportamientos previstos.
No intentes volver a intentar una conexión que falló con GCKErrorCodeCancelled.
Si lo haces, se puede producir un comportamiento inesperado.
Logging
GCKLogger
es un singleton que usa el framework para el registro. Usa el
GCKLoggerDelegate
para personalizar la forma en que controlas los mensajes de registro.
Con GCKLogger, el SDK produce resultados de registro en forma de mensajes de depuración, errores y advertencias. Estos mensajes de registro ayudan a la depuración y son útiles para solucionar problemas y para identificar problemas. De forma predeterminada, se suprime el resultado del registro, pero, si se asigna un GCKLoggerDelegate, la app emisora puede recibir estos mensajes del SDK y registrarlos en la consola del sistema.
@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
Para habilitar también los mensajes detallados y de depuración, agrega esta línea al código después de configurar el delegado (que se mostró anteriormente):
let filter = GCKLoggerFilter.init() filter.minimumLevel = GCKLoggerLevel.verbose GCKLogger.sharedInstance().filter = filter
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init]; [filter setMinimumLevel:GCKLoggerLevelVerbose]; [GCKLogger sharedInstance].filter = filter;
También puedes filtrar los mensajes de registro que produce
GCKLogger.
Establece el nivel de registro mínimo por clase, por ejemplo:
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;
Los nombres de clase pueden ser nombres literales o patrones glob, por ejemplo, GCKUI\* y GCK\*Session.