Integra Cast en tu app para iOS

En esta guía para desarrolladores, se describe cómo agregar compatibilidad con Google Cast a tu dispositivo iOS app emisora con el SDK de Sender de iOS

El dispositivo móvil o laptop es el remitente que controla la reproducción. El dispositivo Google Cast es el receptor que muestra el contenido en la TV.

El framework de remitente hace referencia al objeto binario de la biblioteca de la clase Cast y al asociado recursos presentes en el tiempo de ejecución en el remitente. La app del remitente o la app de Cast hace referencia a una app que también se ejecuta en el remitente. La app del receptor web se refiere a la aplicación HTML que se ejecuta en el receptor web.

El framework del remitente usa un diseño de devolución de llamada asíncrono para informar al remitente. de eventos y para hacer la transición entre varios estados de la vida de la app de Cast ciclo.

Flujo de la app

En los siguientes pasos, se describe el flujo de ejecución de alto nivel típico de un remitente App para iOS:

  • Se iniciará el framework de Cast. GCKDiscoveryManager según las propiedades proporcionadas en GCKCastOptions a comenzar a buscar dispositivos.
  • Cuando el usuario hace clic en el botón para transmitir, el framework presenta la transmisión con la lista de dispositivos de transmisión detectados.
  • Cuando el usuario selecciona un dispositivo de transmisión, el framework intenta iniciar el App receptora web en el dispositivo de transmisión
  • El framework invoca las devoluciones de llamada en la app emisora para confirmar que el Se inició la app de Web Receiver.
  • El framework crea un canal de comunicación entre el remitente y apps del receptor web.
  • El framework usa el canal de comunicación para cargar y controlar el contenido multimedia la reproducción en el receptor web.
  • El framework sincroniza el estado de reproducción del contenido multimedia entre el emisor y Receptor web: cuando el usuario realiza acciones de IU de remitente, el framework pasa estas solicitudes de control de contenido multimedia al receptor web envía actualizaciones de estado del contenido multimedia, el framework actualiza el estado de la IU del remitente.
  • Cuando el usuario hace clic en el botón para transmitir a fin de desconectarse del dispositivo de transmisión, el framework desconectará la app emisora del receptor web.

Para solucionar problemas relacionados con el remitente, debes habilitar el registro.

Para obtener una lista completa de todas las clases, los métodos y los eventos de Google Cast framework de iOS, consulta la API de Google Cast para iOS Referencia. En las siguientes secciones, se describen los pasos para integrar Cast a tu aplicación de iOS.

Métodos de llamada desde el subproceso principal

Cómo inicializar el contexto de transmisión

El framework de Cast tiene un objeto singleton global, el GCKCastContext, que coordina todas las actividades del framework. Se debe inicializar este objeto al inicio del ciclo de vida de la aplicación, normalmente en el -[application:didFinishLaunchingWithOptions:] del delegado de la app, por lo que que la reanudación automática de sesión cuando se reinicia la app emisora puede activarse correctamente.

Un objeto GCKCastOptions se debe suministrar el objeto cuando se inicializa GCKCastContext. Esta clase contiene opciones que afectan el comportamiento del framework. El más importante de estos es el ID de aplicación del receptor web, que se usa para filtrar de descubrimiento y de iniciar la app del receptor web cuando se ejecute empezaste.

El método -[application:didFinishLaunchingWithOptions:] también es un buen lugar para configurar un delegado de registro que reciba los mensajes de registro del framework. Pueden ser útiles para depurar y solucionar problemas.

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

Los widgets de UX de Cast

El SDK de Cast para iOS proporciona estos widgets que cumplen con el diseño de Cast. Lista de tareas:

  • Superposición introductoria: La clase GCKCastContext tiene un método: presentCastInstructionsViewControllerOnceWithCastButton: que se puede usar para destacar el botón para transmitir la primera vez que un receptor web está disponible. La app emisora puede personalizar el texto y la posición del título el texto y el botón Descartar.

  • Botón para transmitir: A partir del SDK remitente de Cast iOS 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 en el botón Transmitir después de iniciar la app, aparecerá un diálogo de permisos para que el usuario pueda otorgar a la app acceso de red local a los dispositivos de la red. Posteriormente, cuando el usuario presione el botón de transmisión, se iniciará una transmisión se muestra un diálogo en el que se enumeran los dispositivos detectados. Cuando el usuario toca en el botón para transmitir mientras el dispositivo está conectado, se mostrará el estado metadatos de medios (como el título, el nombre del estudio de grabación y una miniatura) imagen) o permite que el usuario se desconecte del dispositivo de transmisión. Cuando el usuario toca el botón para transmitir cuando no hay dispositivos disponibles, aparece una pantalla Se mostrará información del usuario sobre el motivo por el que no se encuentran los dispositivos. y cómo solucionar problemas.

  • Minicontrol: Cuando el usuario está transmitiendo contenido y salió del entorno actual la página de contenido o el controlador expandido a otra pantalla en la app emisora, el el minicontrol se muestra en la parte inferior de la pantalla para permitir que el usuario ver los metadatos de los medios que se están transmitiendo y controlar la reproducción.

  • Control expandido: Cuando el usuario transmite contenido, si hace clic en la notificación multimedia o minicontrol, se inicia el control expandido, que muestra que actualmente reproduce metadatos multimedia y ofrece 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. Puede a la barra de título de la app uniéndola a una UIBarButtonItem. Un La subclase UIViewController puede instalar un botón para transmitir de la siguiente manera:

Swift
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];

De forma predeterminada, si presionas el botón, se abrirá el diálogo de transmisión que proporciona la en un framework de aplicaciones.

GCKUICastButton también se puede agregar directamente al guión gráfico.

Cómo configurar la detección de dispositivos

En el framework, la detección de dispositivos ocurre automáticamente. No es necesario iniciar o detener de manera explícita el proceso de descubrimiento, a menos que implementes una IU personalizada

La clase administra el descubrimiento en el marco. GCKDiscoveryManager: que es una propiedad del GCKCastContext El proporciona un componente de diálogo de Cast predeterminado para seleccionar dispositivos y control. La lista de dispositivos está ordenada de forma lexicográfica según el nombre descriptivo.

Cómo funciona la administración de sesiones

El SDK de Cast presenta el concepto de sesión de transmisión, el establecimiento principal que combina los pasos para conectarse a un dispositivo, iniciar (o unirse) a una Web App receptora, conectándose a esa app e inicializando un canal de control multimedia. Ver el receptor web Guía del ciclo de vida de la aplicación para obtener más información sobre las sesiones de transmisión y el ciclo de vida del receptor web.

La clase administra las sesiones GCKSessionManager: que es una propiedad del GCKCastContext Las sesiones individuales se representan mediante subclases de la clase GCKSession: Por ejemplo, GCKCastSession representa sesiones con dispositivos de transmisión. Puedes acceder a la transmisión activa actual. sesión (si existe), como la propiedad currentCastSession de GCKSessionManager.

El GCKSessionManagerListener se puede usar para supervisar los eventos de sesión, como la creación de sesiones, suspensión, reanudación y rescisión. El framework se suspende automáticamente. sesiones cuando la app emisora pasa a segundo plano e intenta reanudar cuando la app regresa al primer plano (o se reinicia tras una el cierre anormal o abrupto de la app mientras había una sesión activa).

Si se usa el diálogo de transmisión, las sesiones se crean y se eliminan automáticamente en respuesta a los gestos del usuario. De lo contrario, la app puede iniciarse y finalizar de forma explícita mediante métodos en GCKSessionManager

Si la app necesita realizar un procesamiento especial en respuesta al ciclo de vida de la sesión eventos, puede registrar una o más instancias de GCKSessionManagerListener con GCKSessionManager GCKSessionManagerListener es un protocolo que define devoluciones de llamadas para eventos como inicio y fin de sesión, entre otros.

Transferencia de transmisión

Preservar el estado de la sesión es la base de la transferencia de transmisión, en la que los usuarios pueden transferir transmisiones de audio y video existentes entre dispositivos mediante comandos por voz, Google Home una app o una pantalla inteligente. 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 funcionar como fuente o destino en un de transmisión continua.

Para obtener el nuevo dispositivo de destino durante la transferencia de transmisión, usa el GCKCastSession#device propiedad durante el [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 una lógica de reconexión para controlarla automáticamente. en muchos casos excepcionales sutiles, como los siguientes:

  • Cómo recuperarse de una pérdida temporal de Wi-Fi
  • Cómo recuperarse del dispositivo
  • Cómo recuperarse de la app en segundo plano
  • Cómo recuperar el estado si la app falló

Cómo funciona el control multimedia

Si se establece una sesión de transmisión con una app receptora web que admite el contenido multimedia de espacio de nombres, una instancia de GCKRemoteMediaClient el framework creará automáticamente; se puede acceder a ella como propiedad remoteMediaClient de GCKCastSession instancia.

Todos los métodos de GCKRemoteMediaClient que envían solicitudes al receptor web devolverá un objeto GCKRequest que se puede usar para rastrear esa solicitud. R GCKRequestDelegate pueden asignarse a este objeto para recibir notificaciones sobre la posible resultado de la operación.

Se espera que la instancia de GCKRemoteMediaClient pueden compartirse entre varias partes de la aplicación y, de hecho, algunos componentes internos del marco de trabajo, como el diálogo de transmisión y los minicontroles multimedia, instancia. Para ello, GCKRemoteMediaClient admite el registro de varias GCKRemoteMediaClientListener

Configura metadatos de contenido multimedia

El GCKMediaMetadata la clase representa información sobre un elemento multimedia que deseas transmitir. Lo siguiente En este ejemplo, se crea una nueva instancia de GCKMediaMetadata de una película y se establece el título. subtítulo, el nombre del estudio de grabación y dos imágenes.

Swift
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]];

Consulta la sección Selección de imágenes y Almacenamiento en caché sobre el uso de imágenes con metadatos de medios.

Cargar contenido multimedia

Para cargar un elemento multimedia, crea GCKMediaInformation con los metadatos del contenido multimedia. Luego, obtén el valor GCKCastSession y usa su GCKRemoteMediaClient para cargar el contenido multimedia en la app receptora. Luego, puedes usar GCKRemoteMediaClient para controlar una app de reproducción multimedia que se ejecuta en la app receptora, como para reproducir, hacer una pausa y detenerse.

Swift
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;
}

Consulta también la sección sobre usar pistas de medios.

Formato de video 4K

Para determinar qué formato de video es tu contenido multimedia, usa la propiedad videoInfo 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 por enumeración. valores GCKVideoInfoHDRType.

Agrega minicontroles

Según el estudio Cast Design Lista de tareas, una aplicación emisora debe proporcionar un control persistente conocido como el prefijo mini responsable del tratamiento de datos que debería aparecer cuando el usuario salga de la página de contenido actual. El minicontrol proporciona acceso instantáneo y un recordatorio visible del sesión de transmisión actual.

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 audio o video, el SDK muestra automáticamente un botón de reproducción/detener en lugar del botón de reproducción/pausa en el minicontrol.

Consulta Cómo personalizar la IU de remitente de iOS para saber cómo app emisora puede configurar la apariencia de los widgets de Cast.

Hay dos formas de agregar el minicontrolador a una app emisora:

  • Deja que el framework de Cast administre el diseño del minicontrol uniendo tu controlador de vistas existente con su propio controlador de vistas.
  • Administra por tu cuenta el diseño del widget del minicontrol agrégalo a tu controlador de vista existente proporcionando una subvista en el guion gráfico.

Une con GCKUICastContainerViewController

La primera es usar GCKUICastContainerViewController que une otro controlador de vista y agrega un GCKUIMiniMediaControlsViewController abajo. Este enfoque es limitado, ya que no puedes personalizar la animación y no puede configurar el comportamiento del controlador de vista del contenedor.

Normalmente, esta primera forma se hace Método -[application:didFinishLaunchingWithOptions:] del delegado de la app:

Swift
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];
  ...

}
.
Swift
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

Cómo incorporar en el controlador de vistas existente

La segunda forma es agregar el minicontrol directamente a tu vista existente responsable del tratamiento de datos con createMiniMediaControlsViewController para crear un GCKUIMiniMediaControlsViewController y, luego, agregarla al controlador de vista del contenedor como una subvista.

Configura el controlador de vistas en el delegado de la app:

Swift
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;
}

En tu controlador de vista raíz, crea un GCKUIMiniMediaControlsViewController. y agregarla al controlador de vista del contenedor como una subvista:

Swift
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

El GCKUIMiniMediaControlsViewControllerDelegate le indica al controlador de vista del host cuándo debe estar visible el minicontrolador:

Swift
  func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController,
                                       shouldAppear _: Bool) {
    updateControlBarsVisibility()
  }
Objective-C
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

Agregar control expandido

La lista de tareas de diseño de Google Cast requiere que la app emisora proporcione una responsable para el contenido multimedia que se transmite. El control expandido es una versión de pantalla completa de el minicontrol.

El control expandido es una vista de pantalla completa que ofrece control total de la reproducción remota de contenido multimedia Esta vista debería permitir que una app de transmisión administre cada más manejable de una sesión de transmisión, con la excepción del volumen de Web Receiver y el ciclo de vida de la sesión (conectar/detener transmisión). También proporciona todos los información de estado sobre la sesión multimedia (material gráfico, título, subtítulo, etc.) ).

La funcionalidad de esta vista se implementa GCKUIExpandedMediaControlsViewController clase.

Lo primero que debes hacer es habilitar el control expandido predeterminado en la contexto de transmisión. Modifica el delegado de la app para habilitar el control expandido predeterminado:

Swift
func applicationDidFinishLaunching(_ application: UIApplication) {
  ..

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

  ...
}
Objective-C
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

Agrega el siguiente código a tu controlador de vista para cargar el control expandido Cuando el usuario comienza a transmitir un video:

Swift
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];
}

El control expandido también se iniciará automáticamente cuando el usuario presiona el minicontrol.

Cuando tu app emisora reproduce una transmisión en vivo de audio o video, el SDK muestra automáticamente un botón de reproducción/detener en lugar del botón de reproducción/pausa en el control expandido.

Consulta Cómo aplicar estilos personalizados a tu iOS Aplicación 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 se sincroniza automáticamente con el volumen del receptor web widgets de IU proporcionados. Para sincronizar un control deslizante proporcionado por la app, usa GCKUIDeviceVolumeController

Control de volumen del botón físico

Puedes usar los botones de volumen físicos del dispositivo emisor para cambiar de la sesión de transmisión en el receptor web mediante el La marca physicalVolumeButtonsWillControlDeviceVolume en GCKCastOptions, que se establece en el GCKCastContext

Swift
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];

Cómo solucionar errores

Es muy importante que las apps emisoras manejen 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 los diálogos de error al usuario o puede decidir finalizar la sesión de transmisión.

Logging

GCKLogger es un singleton que el framework usa para el registro. Usa el GCKLoggerDelegate para personalizar la forma en que manejas los mensajes de registro.

Con el GCKLogger, el SDK produce resultados del registro en forma de depuración. mensajes, errores y advertencias. Estos mensajes de registro ayudan con la depuración y son útiles para identificar y solucionar problemas. De forma predeterminada, el resultado del registro pero si asignas un GCKLoggerDelegate, la app emisora puede recibir estos mensajes desde el SDK y registrarlos en la consola del sistema.

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

Para habilitar también los mensajes detallados y de depuración, agrega esta línea al código después de Configura el delegado (como se mostró anteriormente):

Swift
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;

También puedes filtrar los mensajes de registro producidos por GCKLogger Establece el nivel de registro mínimo por clase, por ejemplo:

Swift
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;

Los nombres de las clases pueden ser nombres literales o patrones glob, por ejemplo, GCKUI\* y GCK\*Session.