Este guia do desenvolvedor descreve como adicionar compatibilidade com o Google Cast ao seu app remetente do iOS usando o SDK do remetente do iOS.
O dispositivo móvel ou laptop é o remetente que controla a reprodução, e 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 aos recursos associados presentes no momento da execução no remetente. O app remetente ou app Cast se refere a um app que também está sendo executado no remetente. O app receptor da Web refere-se ao aplicativo HTML em execução no receptor da Web.
O framework do remetente usa um design de callback assíncrono para informar eventos ao app remetente e fazer a transição entre vários estados do ciclo de vida do app Google Cast.
Fluxo de aplicativos
As etapas a seguir descrevem o fluxo de execução de alto nível típico de um app iOS remetente:
- O framework do Cast inicia
GCKDiscoveryManager
com base nas propriedades fornecidas emGCKCastOptions
para começar a procurar dispositivos. - Quando o usuário clica no botão Transmitir, o framework apresenta a caixa de diálogo "Transmitir" com a lista de dispositivos de transmissão descobertos.
- Quando o usuário seleciona um dispositivo de transmissão, o framework tenta iniciar o app Receptor da Web no dispositivo de transmissão.
- O framework invoca callbacks no app remetente para confirmar que o app receptor da Web foi iniciado.
- O framework cria um canal de comunicação entre o remetente e os apps receptores da Web.
- O framework usa o canal de comunicação para carregar e controlar a reprodução de mídia 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 clica no botão Transmitir para se desconectar do dispositivo de transmissão, o framework desconecta o app remetente do receptor da Web.
Para resolver problemas do remetente, é necessário ativar o registro.
Para uma lista abrangente de todas as classes, métodos e eventos no framework do Google Cast para iOS, consulte a Referência da API Google Cast para iOS. As seções a seguir abordam 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 dele. Esse objeto precisa ser inicializado
no início do ciclo de vida do aplicativo, normalmente no método
-[application:didFinishLaunchingWithOptions:]
do delegado do app, para
que a retomada automática da sessão na reinicialização do app do remetente possa ser acionada corretamente.
Um objeto GCKCastOptions
precisa ser fornecido ao inicializar o GCKCastContext
.
Essa classe contém opções que afetam o comportamento do framework. O mais
importante é o ID do aplicativo do receptor da Web, que é usado para filtrar
os resultados da descoberta e iniciar o app quando uma sessão do Cast é
iniciada.
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.
@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
Os widgets de UX do Google Cast
O SDK do Cast para iOS fornece estes widgets que obedecem à lista de verificação de design do Cast:
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 estiver disponível. O app remetente pode personalizar o texto, a posição do texto do título e o botão "Dispensar".Botão "Transmitir": a partir do SDK remetente do iOS do Cast 4.6.0, o botão "Transmitir" estará 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 é exibida para que o usuário possa conceder ao app acesso à rede local aos dispositivos na rede. Em seguida, quando o usuário toca no botão Transmitir, uma caixa de diálogo de transmissão é exibida, listando os dispositivos descobertos. Quando o usuário toca no botão Transmitir enquanto o dispositivo está conectado, ele mostra os metadados de mídia atuais, como título, nome do estúdio de gravação e uma imagem em miniatura, ou permite que o usuário se desconecte do dispositivo de transmissão. Quando o usuário toca no botão Transmitir enquanto não há dispositivos disponíveis, uma tela é exibida com informações 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 página de conteúdo atual ou do controle expandido para outra tela no app remetente, o minicontrole será exibido na parte de baixo da tela para permitir que o usuário confira os metadados de mídia em transmissão no momento e controle a reprodução.
Controle expandido: quando o usuário estiver transmitindo conteúdo, se ele clicar na notificação de mídia ou no minicontrole, o controle expandido será iniciado, exibindo os metadados de mídia em reprodução e fornecendo vários botões para controlar a reprodução.
Adicionar um botão Transmitir
O framework fornece um componente do botão Transmitir como uma subclasse UIButton
. Ele pode
ser adicionado à barra de título do app colocando-o em uma UIBarButtonItem
. Uma subclasse
UIViewController
típica pode instalar um botão Transmitir da seguinte maneira:
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];
Por padrão, tocar no botão abre a caixa de diálogo "Transmitir" fornecida pelo framework.
GCKUICastButton
também pode ser adicionado diretamente ao storyboard.
Configurar a descoberta de dispositivos
No framework, a descoberta de dispositivos acontece automaticamente. Não é necessário 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
. O
framework oferece um componente padrão da caixa de diálogo "Transmitir" para seleção e
controle de dispositivos. 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. Esse estabelecimento combina as etapas de conexão a um dispositivo, inicialização (ou associação) de um app receptor da Web, conexão a esse app e inicialização de um canal de controle de mídia. Consulte o Guia do ciclo de vida do aplicativo para mais informações sobre as sessões do Google Cast e o ciclo de vida do receptor da Web.
As sessões são gerenciadas pela classe
GCKSessionManager
,
que é uma propriedade de
GCKCastContext
.
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 a sessão do Google Cast
ativa no momento (se houver) como a propriedade currentCastSession
de GCKSessionManager
.
A interface
GCKSessionManagerListener
pode ser usada para monitorar eventos de sessão, como criação,
suspensão, retomada e encerramento de sessão. O framework suspende
automaticamente as sessões quando o app remetente está entrando em segundo plano e tenta retomá-las
quando o app retorna ao 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 poderá iniciar e encerrar
sessões explicitamente usando métodos no
GCKSessionManager
.
Se o app precisar fazer um processamento especial em resposta a eventos de ciclo de vida
da sessão, ele poderá registrar uma ou mais instâncias GCKSessionManagerListener
com
o GCKSessionManager
. 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 os streams de áudio e vídeo existentes entre dispositivos usando comandos de voz, o app Google Home ou smart displays. A mídia é interrompida em um dispositivo (a origem) e continua em outro (o destino). Qualquer dispositivo de transmissão com o firmware mais recente pode servir como origens ou destinos em uma transferência por stream.
Para ter o novo dispositivo de destino durante a transferência de stream, use a propriedade
GCKCastSession#device
durante o callback
[sessionManager:didResumeCastSession:]
.
Consulte Transferência de stream no receptor da Web para mais informações.
Reconexão automática
O framework do Cast adiciona lógica de reconexão para lidar automaticamente com a reconexão em muitos casos 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 que oferece suporte ao namespace
de mídia, uma instância de
GCKRemoteMediaClient
será criada automaticamente pelo framework. Ela poderá ser acessada como a
propriedade remoteMediaClient
da
instância
GCKCastSession
.
Todos os métodos em GCKRemoteMediaClient
que emitem solicitações para o receptor da Web
retornarão um objeto
GCKRequest
que
pode ser usado para rastrear essa solicitação. Um
GCKRequestDelegate
pode ser atribuído a esse objeto para receber notificações sobre o resultado
da operação.
Espera-se que a instância de GCKRemoteMediaClient
seja compartilhada por várias partes do app, e alguns componentes internos
do framework, como a caixa de diálogo "Transmitir" e os controles de minimídia, compartilham a
instância. Por isso, o GCKRemoteMediaClient
oferece suporte ao registro de vários
GCKRemoteMediaClientListener
s.
Definir metadados de mídia
A classe
GCKMediaMetadata
representa informações sobre um item de mídia que você quer transmitir. O exemplo
a seguir cria uma nova instância GCKMediaMetadata
de um filme e define o título,
o subtítulo, o nome do estúdio de gravação e duas imagens.
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]];
Consulte a seção Seleção de imagens 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 uma instância
GCKMediaInformation
usando os metadados da mídia. Em seguida, acesse o
GCKCastSession
atual e
use o
GCKRemoteMediaClient
para carregar a mídia no app receptor. Em seguida, use GCKRemoteMediaClient
para controlar um app de player de mídia em execução no receptor, por exemplo, para abrir,
pausar e parar.
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; }
Consulte também a seção sobre como usar 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 acessar a instância atual de
GCKVideoInfo
.
Essa instância contém o tipo de formato de TV HDR e a altura e a largura em
pixels. As variantes do formato 4K são indicadas na propriedade hdrType
por valores
de tipo enumerado GCKVideoInfoHDRType
.
Adicionar minicontroles
De acordo com a Lista de verificação de design de transmissão, um app remetente precisa fornecer um controle persistente, conhecido como mini controlador, que aparece quando o usuário sai da página de conteúdo atual. O minicontrole oferece acesso instantâneo e um lembrete visível para a sessão atual do Google Cast.
O framework do Google Cast oferece uma barra de controle,
GCKUIMiniMediaControlsViewController
,
que pode ser adicionada à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 "Play/Stop" no lugar do botão "Play/pause" no minicontrole.
Consulte Personalizar a interface do remetente do iOS para saber como seu 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 unindo o controlador de visualização existente com o próprio controlador de visualização.
- Gerencie o layout do widget do minicontrole adicionando-o ao seu controlador de visualização atual, fornecendo uma subvisualização no storyboard.
Unir usando o GCKUICastContainerViewController
A primeira é usar o
GCKUICastContainerViewController
,
que envolve outro controlador de visualização e adiciona um
GCKUIMiniMediaControlsViewController
na parte de baixo. Essa abordagem é limitada, porque não é possível personalizar a animação e não é possível configurar o comportamento do controlador de visualização de contêiner.
Essa primeira maneira normalmente é feita no
método -[application:didFinishLaunchingWithOptions:]
do delegado do 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
Incorporar em um controlador de visualização existente
A segunda maneira é adicionar o minicontrolador diretamente ao controlador de visualização
existente usando
createMiniMediaControlsViewController
para criar uma instância
GCKUIMiniMediaControlsViewController
e adicioná-la ao controlador de visualizações de contêiner como uma subvisualização.
Configure seu controlador de visualização no delegado do 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; }
No controlador de visualizações raiz, crie uma instância de GCKUIMiniMediaControlsViewController
e adicione-a ao controlador de visualizações de contêiner como uma subvisualização:
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
O
GCKUIMiniMediaControlsViewControllerDelegate
informa ao controlador de visualização do host quando o minicontrolador ficará visível:
func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController, shouldAppear _: Bool) { updateControlBarsVisibility() }
- (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 um controlador expandido para a mídia que está sendo transmitida. O controle expandido é uma versão em tela cheia do minicontrole.
O controle expandido é uma exibição em tela cheia que oferece controle total da reprodução de mídia remota. Essa visualização permite que um app de transmissão gerencie todos os aspectos gerenciáveis de uma sessão de transmissão, exceto o controle de volume do receptor da Web e o ciclo de vida da sessão (conectar/parar transmissão). Ele também fornece todas as informações de status sobre a sessão de mídia (arte, título, subtítulo etc.).
A funcionalidade dessa visualização é implementada pela
classe
GCKUIExpandedMediaControlsViewController
.
A primeira coisa que você precisa fazer é ativar o controlador expandido padrão no contexto de transmissão. Modifique o delegado do app para ativar o controle expandido padrão:
func applicationDidFinishLaunching(_ application: UIApplication) { .. GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true ... }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES; .. }
Adicione o seguinte código ao seu controlador de visualização para carregar o controlador expandido quando o usuário começar a transmitir um vídeo:
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]; }
O controle expandido também será iniciado automaticamente quando o usuário tocar nele.
Quando o app remetente estiver reproduzindo uma transmissão ao vivo de vídeo ou áudio, o SDK exibe automaticamente um botão "Play/Stop" no lugar daquele no controle expandido.
Consulte Aplicar estilos personalizados ao seu app iOS 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. Ele
é sincronizado automaticamente com o volume do receptor da Web para os
widgets de interface 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 o
volume da sessão do Cast no receptor da Web usando a
flag physicalVolumeButtonsWillControlDeviceVolume
no
GCKCastOptions
,
que está definido no
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];
Solucionar erros
É muito importante que os apps remetentes processem todos os callbacks de erro e decidam a melhor resposta para cada estágio do ciclo de vida do Cast. O app pode mostrar caixas de diálogo de erro ao usuário ou encerrar a sessão de transmissão.
Geração de registros
GCKLogger
é um Singleton usado para geração de registros pelo framework. Use o
GCKLoggerDelegate
para personalizar o processamento das mensagens de registro.
Ao usar o GCKLogger
, o SDK produz uma saída de geração de registros na forma de mensagens
de depuração, erros e avisos. Essas mensagens de registro ajudam na depuração e são
úteis para solucionar problemas e identificar problemas. Por padrão, a saída do registro é
suprimida, mas ao atribuir um GCKLoggerDelegate
, o app remetente pode receber
essas mensagens do SDK e registrá-las no console do 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 ativar também mensagens de depuração e detalhadas, adicione esta linha ao código depois de definir o delegado (mostrado 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;
Também é possível filtrar as mensagens de registro produzidas por
GCKLogger
.
Defina o nível mínimo de geração de registros por classe, por exemplo:
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;
Os nomes de classes podem ser nomes literais ou padrões glob, por exemplo, GCKUI\*
e GCK\*Session
.