Integrar o Google Cast ao seu app iOS

Este guia do desenvolvedor descreve como adicionar compatibilidade com o Google Cast ao seu iOS app remetente usando o SDK do remetente do iOS.

O dispositivo móvel ou laptop é o remetente que controla a reprodução, e o O dispositivo com Google Cast é o receptor que exibe o conteúdo na TV.

O framework do remetente se refere ao binário da biblioteca de classes do Cast e associado recursos presentes no tempo de execução no remetente. O app remetente ou o app Google Cast refere-se a um app que também é executado no remetente. O app receptor da Web refere-se ao aplicativo HTML executado no receptor da Web.

A estrutura do remetente usa um design de callback assíncrono para informar o remetente de eventos e fazer a transição entre os vários estados da vida útil do app Cast ciclo.

Fluxo de aplicativos

As etapas a seguir descrevem o fluxo de execução de alto nível típico de um remetente Aplicativo para iOS:

  • O framework do Cast é iniciado GCKDiscoveryManager com base nas propriedades GCKCastOptions para começar a procurar dispositivos.
  • Quando o usuário clica no botão Transmitir, o framework apresenta o Caixa de diálogo com a lista de dispositivos de transmissão descobertos.
  • Quando o usuário seleciona um dispositivo de transmissão, o framework tenta iniciar a App Receptor da Web no dispositivo de transmissão.
  • O framework invoca callbacks no app remetente para confirmar que o O app receptor da Web foi iniciado.
  • O framework cria um canal de comunicação entre o remetente Apps receptores da Web.
  • O framework usa o canal de comunicação para carregar e controlar mídias a reprodução no receptor da Web.
  • O framework sincroniza o estado de reprodução de mídia entre o remetente e o Receptor da Web: quando o usuário realiza ações na interface do remetente, o framework transmite essas solicitações de controle de mídia para o receptor da Web e quando ele envia atualizações de status de mídia, o framework atualiza o estado da interface do remetente.
  • Quando o usuário clicar no botão Transmitir para se desconectar do dispositivo de transmissão, O framework desconectará o app remetente do receptor da Web.

Para resolver problemas do remetente, é necessário ativar o registro.

Para acessar uma lista abrangente de todas as classes, métodos e eventos no Google Cast Framework do iOS, consulte a API Google Cast para iOS Referência. As seções a seguir abrangem as etapas para integrar o Google Cast ao seu app iOS.

Chamar métodos da linha de execução principal

Inicializar o contexto de transmissão

O framework do Google Cast tem um objeto Singleton global, o GCKCastContext, que coordena todas as atividades da estrutura. Esse objeto precisa ser inicializado no início do ciclo de vida do aplicativo, normalmente -[application:didFinishLaunchingWithOptions:] do delegado do app. que a retomada automática da sessão na reinicialização do app do remetente seja acionada corretamente.

Uma GCKCastOptions O objeto precisa ser fornecido ao inicializar o GCKCastContext. Essa classe contém opções que afetam o comportamento do framework. A maior mais importante é o ID do aplicativo do receptor da Web, usado para filtrar os resultados da descoberta e iniciar o app Receptor da Web quando uma sessão de transmissão for começar.

O método -[application:didFinishLaunchingWithOptions:] também é um bom lugar para configurar um delegado de geração de registros para receber as mensagens de registro do framework. Elas podem ser úteis para depuração e solução de 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

Os widgets de UX do Google Cast

O SDK do Cast para iOS fornece esses widgets que obedecem ao design do Cast Lista de verificação:

  • Sobreposição introdutória: A classe GCKCastContext tem um método, presentCastInstructionsViewControllerOnceWithCastButton, que pode ser usado para destacar o botão Transmitir na primeira vez que um receptor da Web está disponível. O app remetente pode personalizar o texto e a posição do título e o botão "Dispensar".

  • Botão Transmitir: A partir do SDK remetente do Cast para iOS 4.6.0, o botão Transmitir está sempre visível quando o dispositivo remetente estiver conectado ao Wi-Fi. Na primeira vez que o usuário toca no botão Transmitir depois de iniciar o app, uma caixa de diálogo de permissões aparece para que o usuário possa conceder ao app acesso à rede local aos dispositivos na na rede. Em seguida, quando o usuário toca no botão Transmitir, uma uma caixa de diálogo que lista os dispositivos descobertos é exibida. Quando o usuário toca no botão Transmitir enquanto o dispositivo estiver conectado, será mostrada metadados de mídia (como título, nome do estúdio de gravação e uma miniatura) imagem) ou permite que o usuário se desconecte do dispositivo de transmissão. Quando o usuário tocar no botão transmitir enquanto não houver dispositivos disponíveis, uma tela será exibido fornecendo informações ao usuário sobre por que os dispositivos não foram encontrados e como resolver problemas.

  • Minicontrole: Quando o usuário está transmitindo conteúdo e saiu da configuração página de conteúdo ou controlador expandido para outra tela no aplicativo remetente, o é exibido na parte inferior da tela para permitir que o usuário ver os metadados de mídia transmitidos no momento e controlar a reprodução.

  • Controlador expandido: Quando o usuário estiver transmitindo conteúdo, se clicar na notificação de mídia ou minicontrole, o controle expandido é iniciado, mostrando a metadados de mídia em reprodução no momento e fornece vários botões para controlar o a reprodução de mídia.

Adicionar um botão Transmitir

O framework fornece um componente do botão Transmitir como uma subclasse UIButton. Ela pode ser adicionado à barra de título do app colocando-o em uma UIBarButtonItem. Uma configuração típica A subclasse UIViewController pode instalar um botão Transmitir da seguinte maneira:

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

Por padrão, tocar no botão abre a caixa de diálogo "Transmitir" fornecida pelo de análise de dados em nuvem.

GCKUICastButton também pode ser adicionado diretamente ao storyboard.

Configurar a descoberta de dispositivos

No framework, a descoberta de dispositivos acontece automaticamente. Não é preciso iniciar ou interromper explicitamente o processo de descoberta, a menos que você implemente uma interface personalizada.

A descoberta no framework é gerenciada pela classe GCKDiscoveryManager, que é uma propriedade do GCKCastContext A oferece um componente padrão da caixa de diálogo do Google Cast para seleção de dispositivos e controle A lista de dispositivos é ordenada lexicograficamente pelo nome compatível com o dispositivo.

Como funciona o gerenciamento de sessões

O SDK do Cast apresenta o conceito de uma sessão do Google Cast, a que combina as etapas de conexão a um dispositivo, inicialização (ou entrada) de um App receptor, conectando-se a ele e inicializando um canal de controle de mídia. Consulte o receptor da Web Guia do ciclo de vida do aplicativo para mais informações sobre as sessões de transmissão e o ciclo de vida do receptor da Web.

As sessões são gerenciadas pela turma GCKSessionManager, que é uma propriedade do GCKCastContext As sessões individuais são representadas por subclasses da classe GCKSession: por exemplo, GCKCastSession representa sessões com dispositivos de transmissão. É possível acessar o conteúdo do Google Cast ativo no momento sessão (se houver), como a propriedade currentCastSession de GCKSessionManager.

A GCKSessionManagerListener pode ser usada para monitorar eventos de sessão, como criação de sessão, suspensão, retomada e rescisão. O framework suspende automaticamente sessões em que o app remetente entra em segundo plano e tenta retomar quando o aplicativo retorna para o primeiro plano (ou é reiniciado após um encerramento anormal/abrupto do app enquanto uma sessão estava ativa).

Se a caixa de diálogo "Transmitir" estiver sendo usada, as sessões serão criadas e removidas automaticamente em resposta aos gestos do usuário. Caso contrário, o app pode iniciar e encerrar sessões explicitamente usando métodos GCKSessionManager

Se o app precisar fazer processamento especial em resposta ao ciclo de vida da sessão ele pode registrar uma ou mais instâncias GCKSessionManagerListener com o GCKSessionManager. O GCKSessionManagerListener é um protocolo que define callbacks para eventos como início e fim de sessão, entre outros.

Transferência de stream

A preservação do estado da sessão é a base da transferência de stream, em que Os usuários podem mover streams de áudio e vídeo entre dispositivos usando comandos de voz, o Google Home no app ou em smart displays. A mídia é interrompida em um dispositivo (a fonte) e continua em outro (a destino). Qualquer dispositivo de transmissão com o firmware mais recente pode servir como origem ou destino em um transferência de stream.

Para receber o novo dispositivo de destino durante a transferência da transmissão, use o GCKCastSession#device durante a [sessionManager:didResumeCastSession:] o retorno de chamada.

Consulte Transferência de stream no receptor da Web para mais informações.

Reconexão automática

O framework do Cast adiciona a lógica de reconexão para gerenciar automaticamente a reconexão. em muitos casos isolados sutis, como:

  • Recuperar-se de uma perda temporária de Wi-Fi
  • Recuperar-se do modo de inatividade do dispositivo
  • Recuperar-se do app em segundo plano
  • Fazer a recuperação em caso de falha do app

Como funcionam os controles de mídia

Se uma sessão de transmissão for estabelecida com um app receptor da Web compatível com a namespace, uma instância de GCKRemoteMediaClient serão criadas automaticamente pela estrutura; ele pode ser acessado remoteMediaClient do GCKCastSession instância.

Todos os métodos em GCKRemoteMediaClient que emitem solicitações para o receptor da Web vai retornar objeto GCKRequest que pode ser usada para acompanhar essa solicitação. Um GCKRequestDelegate pode ser atribuído a esse objeto para receber notificações sobre o evento resultado da operação.

Espera-se que a instância de GCKRemoteMediaClient pode ser compartilhado por várias partes do aplicativo e, de fato, por alguns componentes internos do framework, como a caixa de diálogo "Transmitir" e os controles de minimídia, compartilham a instância. Para isso, GCKRemoteMediaClient permite o registro de vários GCKRemoteMediaClientListeners.

Definir metadados de mídia

A GCKMediaMetadata representa informações sobre um item de mídia que você deseja transmitir. O seguinte exemplo cria uma nova instância GCKMediaMetadata de um filme e define o título, subtítulo, nome do estúdio de gravação e duas imagens.

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

Consulte as seções Seleção de imagem e Armazenamento em cache sobre o uso de imagens com metadados de mídia.

Carregar mídia

Para carregar um item de mídia, crie um GCKMediaInformation usando os metadados da mídia. Em seguida, obtenha a imagem GCKCastSession e usar seu GCKRemoteMediaClient para carregar a mídia no app receptor. É possível usar GCKRemoteMediaClient para controlar um app de player de mídia em execução no receptor, como para abrir, pausar e parar.

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

Consulte também a seção sobre usando faixas de mídia.

Formato de vídeo 4K

Para determinar qual é o formato de vídeo da sua mídia, use a propriedade videoInfo de GCKMediaStatus para conseguir a instância atual GCKVideoInfo Esta instância contém o tipo de formato de TV HDR e a altura e largura em pixels. As variantes do formato 4K são indicadas na propriedade hdrType pelo tipo enumerado de valores GCKVideoInfoHDRType.

Adicionar minicontroles

De acordo com o Design do elenco lista de verificação, um app remetente deve fornecer um controle persistente conhecido como mini controlador que deve aparecer quando o usuário sair da página de conteúdo atual. O minicontrole oferece acesso instantâneo e um lembrete visível para o sessão de transmissão atual.

O framework do Google Cast oferece uma barra de controle, GCKUIMiniMediaControlsViewController, que pode ser adicionado às cenas em que você quer mostrar o minicontrole.

Quando o app remetente estiver reproduzindo uma transmissão ao vivo de vídeo ou áudio, o SDK exibe automaticamente um botão reproduzir/parar no lugar do botão reproduzir/pausar no minicontrole.

Consulte Personalizar a interface do remetente do iOS para saber como app remetente pode configurar a aparência dos widgets do Google Cast.

Há duas maneiras de adicionar o minicontrole a um app remetente:

  • Permita que o framework do Google Cast gerencie o layout do minicontrole encapsulando seu controlador de visualização existente com seu próprio controlador de visualização.
  • Gerencie o layout do widget do minicontrole adicionando-o ao seu controlador de visualização existente fornecendo uma subvisualização no storyboard.

Unir usando o GCKUICastContainerViewController

A primeira é usar GCKUICastContainerViewController que envolve outro controlador de visualizações e adiciona um GCKUIMiniMediaControlsViewController na parte de baixo. Essa abordagem é limitada, porque não é possível personalizar animação e não pode configurar o comportamento do controlador de visualização do contêiner.

A primeira forma normalmente é feita Método -[application:didFinishLaunchingWithOptions:] do delegado do 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

Incorporar em um controlador de visualização existente

A segunda maneira é adicionar o minicontrole diretamente à sua visualização existente controlador usando createMiniMediaControlsViewController para criar GCKUIMiniMediaControlsViewController e adicioná-la ao controlador de visualizações do contêiner como uma subvisualização.

Configure seu controlador de visualização no delegado do 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;
}

No controlador de visualização raiz, crie um GCKUIMiniMediaControlsViewController. e adicioná-la ao controlador de visualizações do contêiner como uma subvisualização:

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

A GCKUIMiniMediaControlsViewControllerDelegate informa ao controlador de visualização do host quando o minicontrolador estará visível:

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

Adicionar controle expandido

A Lista de verificação de design do Google Cast exige que um app remetente forneça uma lista de verificação controlador para a mídia transmitida. O controle expandido é uma versão em tela cheia do o minicontrole.

O controle expandido é uma exibição em tela cheia que oferece controle total do a reprodução de mídia remota. Essa visualização permite que um app de transmissão gerencie todos aspecto gerenciável de uma sessão de transmissão, exceto o volume do receptor da Web controle e ciclo de vida da sessão (conectar/parar transmissão). Ele também fornece todos os informações de status sobre a sessão de mídia (arte, título, subtítulo etc. adiante).

A funcionalidade dessa visualização é implementada pela função GCKUIExpandedMediaControlsViewController .

A primeira coisa que você precisa fazer é ativar o controle expandido padrão na contexto de transmissão. Modifique o delegado do app para ativar o controle expandido padrão:

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

Adicione o código a seguir ao seu controlador de visualização para carregar o controle expandido quando o usuário começa a transmitir um vídeo:

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

O controle expandido também será iniciado automaticamente quando o usuário toca no minicontrole.

Quando o app remetente estiver reproduzindo uma transmissão ao vivo de vídeo ou áudio, o SDK exibe automaticamente um botão reproduzir/parar no lugar do botão reproduzir/pausar no controle expandido.

Consulte Aplicar estilos personalizados ao seu iOS App para saber como o app remetente pode configurar a aparência dos widgets do Google Cast.

Controle do volume

O framework do Cast gerencia automaticamente o volume do app remetente. A sincroniza automaticamente com o volume do receptor da Web para o widgets de IU fornecidos. Para sincronizar um controle deslizante fornecido pelo app, use GCKUIDeviceVolumeController

Controle de volume do botão físico

Os botões de volume físico no dispositivo remetente podem ser usados para mudar a da sessão de transmissão no receptor da Web usando o a sinalização physicalVolumeButtonsWillControlDeviceVolume na GCKCastOptions, que é definido no 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];

Solucionar erros

É muito importante que os apps remetentes lidem com todos os callbacks de erro e decidam a melhor resposta para cada estágio do ciclo de vida do Google Cast. O app pode mostrar caixas de diálogo de erro para o usuário ou encerrar a sessão de transmissão.

Logging

GCKLogger é um Singleton usado para geração de registros pelo framework. Use o GCKLoggerDelegate para personalizar o gerenciamento das mensagens de registro.

Ao usar o GCKLogger, o SDK produz a saída de geração de registros na forma de depuração. mensagens, erros e avisos. Essas mensagens de registro ajudam na depuração e são úteis para solucionar e identificar problemas. Por padrão, a saída do registro é suprimidos, mas ao atribuir um GCKLoggerDelegate, o app remetente pode receber essas mensagens do SDK e registrá-las no console do 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 ativar também as mensagens de depuração e detalhadas, adicione esta linha ao código após definindo o delegado (mostrado 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;

Também é possível filtrar as mensagens de registro geradas GCKLogger Defina o nível mínimo de geração de registros por classe, por exemplo:

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;

Os nomes das classes podem ser nomes literais ou padrões glob, por exemplo, GCKUI\* e GCK\*Session.