Este guia para desenvolvedores descreve como adicionar suporte ao Google Cast ao 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 de transmissão Google Cast é o receptor que mostra 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 tempo de execução no remetente. O app remetente ou app do Cast se refere a um app que também está em execução no remetente. O app Web Receiver se refere ao aplicativo HTML em execução no Web Receiver.
O framework do remetente usa um design de callback assíncrono para informar o app remetente sobre eventos e fazer a transição entre vários estados do ciclo de vida do app do Cast.
Fluxo de aplicativos
As etapas a seguir descrevem o fluxo de execução típico de alto nível para um app remetente do iOS:
- O framework do Cast inicia
GCKDiscoveryManagercom base nas propriedades fornecidas emGCKCastOptionspara começar a procurar dispositivos. - Quando o usuário clica no botão "Transmitir", o framework apresenta a caixa de diálogo do Cast 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 Web Receiver no dispositivo de transmissão.
- O framework invoca callbacks no app remetente para confirmar que o app Web Receiver foi iniciado.
- O framework cria um canal de comunicação entre os apps remetente e Web Receiver.
- O framework usa o canal de comunicação para carregar e controlar a reprodução de mídia no Web Receiver.
- O framework sincroniza o estado de reprodução de mídia entre o remetente e o Web Receiver: quando o usuário faz ações de interface do remetente, o framework transmite essas solicitações de controle de mídia para o Web Receiver. Quando o Web Receiver 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 Web Receiver.
Para resolver problemas no remetente, é necessário ativar a geração de registros logging.
Para uma lista completa de todas as classes, métodos e eventos no framework do Google Cast para iOS, consulte a Referência da API do Google Cast para iOS. As seções a seguir abordam as etapas para integrar o Cast ao seu app iOS.
Chamar métodos da linha de execução principal
Inicializar o contexto do Cast
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 remetente possa ser acionada corretamente.
Um GCKCastOptions
objeto precisa ser fornecido ao inicializar o GCKCastContext.
Essa classe contém opções que afetam o comportamento do framework. A mais importante delas é o ID do aplicativo Web Receiver, que é usado para filtrar os resultados da descoberta e iniciar o app Web Receiver quando uma sessão de transmissão é 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 geração de registros do framework.
Isso pode ser útil 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
Widgets de UX do Cast
O SDK do Cast para iOS fornece estes widgets que estão em conformidade com a lista de verificação de design do Cast:
Sobreposição introdutória: A classe
GCKCastContexttem um método,presentCastInstructionsViewControllerOnceWithCastButton, que pode ser usado para destacar o botão "Transmitir" na primeira vez que um Web Receiver 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 do remetente do Cast para iOS 4.6.0, o botão "Transmitir" fica sempre visível quando o dispositivo remetente está conectado ao Wi-Fi. Na primeira vez que o usuário tocar no botão Transmitir após iniciar o app, uma caixa de diálogo de permissões vai aparecer para que o usuário possa conceder ao app acesso à rede local para dispositivos na rede. Posteriormente, quando o usuário tocar no botão "Transmitir", uma caixa de diálogo de transmissão será mostrada com a lista de dispositivos descobertos. Quando o usuário tocar no botão "Transmitir" enquanto o dispositivo estiver conectado, ele vai mostrar os metadados de mídia atuais (como título, nome do estúdio de gravação e uma miniatura) ou permitir que o usuário se desconecte do dispositivo de transmissão. Quando o usuário tocar no botão "Transmitir" e não houver dispositivos disponíveis, uma tela será mostrada com informações sobre por que os dispositivos não foram encontrados e como resolver o problema.
Minicontrole: quando o usuário estiver transmitindo conteúdo e tiver navegado da página de conteúdo atual ou do controle expandido para outra tela no app remetente, o minicontrole será mostrado na parte de baixo da tela para permitir que o usuário veja os metadados de mídia que estão sendo transmitidos 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, mostrando os metadados de mídia que estão sendo reproduzidos e fornecendo vários botões para controlar a reprodução de mídia.
Adicionar um botão "Transmitir"
O framework fornece um componente de botão "Transmitir" como uma subclasse UIButton. Ele pode ser adicionado à barra de título do aplicativo colocando-o em um 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 vai abrir 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 fornece um componente de caixa de diálogo "Transmitir" padrão para seleção e controle de dispositivos. A lista de dispositivos é ordenada lexicograficamente pelo nome amigável do dispositivo.
Como o gerenciamento de sessões funciona
O SDK do Cast apresenta o conceito de uma sessão de transmissão, cuja criação combina as etapas de conexão a um dispositivo, inicialização (ou participação) de um app Web Receiver, 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 Web Receiver para mais informações sobre sessões de transmissão e o ciclo de vida do Web Receiver.
As sessões são gerenciadas pela classe
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 a sessão de transmissão ativa no momento (se houver) como a propriedade currentCastSession de GCKSessionManager.
A
GCKSessionManagerListener
interface 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á 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 eliminadas automaticamente em resposta aos gestos do usuário. Caso contrário, o app poderá iniciar e encerrar
sessões explicitamente usando métodos em
GCKSessionManager.
Se o app precisar fazer um processamento especial em resposta a eventos do 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 da sessão e assim por diante.
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 app Google Home ou Smart Displays. A mídia para de ser reproduzida em um dispositivo (a origem) e continua em outro (o destino). Qualquer dispositivo de transmissão com o firmware mais recente pode servir como origem ou destino em uma transferência de stream.
Para acessar o novo dispositivo de destino durante a transferência de stream, use a
GCKCastSession#device
propriedade durante o
[sessionManager:didResumeCastSession:]
callback.
Consulte Transferência de stream no Web Receiver para mais informações.
Reconexão automática
O framework do Cast adiciona lógica de reconexão para processar automaticamente a reconexão em muitos casos sutis, como:
- Recuperação de uma perda temporária de Wi-Fi
- Recuperar da suspensão do dispositivo
- Recuperação do app em segundo plano
- Recuperação se o app falhou
Como o controle de mídia funciona
Se uma sessão de transmissão for estabelecida com um app Web Receiver que oferece suporte ao namespace de mídia, uma instância de
GCKRemoteMediaClient
será criada automaticamente pelo framework. Ela pode ser acessada como a propriedade
remoteMediaClient da instância
GCKCastSession
.
Todos os métodos em GCKRemoteMediaClient que emitem solicitações para o Web Receiver
vão retornar um
GCKRequest objeto 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 final da operação.
Espera-se que a instância de GCKRemoteMediaClient possa ser compartilhada por várias partes do app, e alguns componentes internos do framework, como a caixa de diálogo do Cast e os minicontroles de mídia, compartilham a instância. Para isso, GCKRemoteMediaClient
oferece suporte ao registro de vários
GCKRemoteMediaClientListeners.
Definir metadados de mídia
A
GCKMediaMetadata
classe 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 e armazenamento em cache de imagens sobre o uso de imagens com metadados de mídia.
Carregar mídia
Para carregar um item de mídia, crie uma
GCKMediaInformation
instância usando os metadados da mídia. Em seguida, acesse o
GCKCastSession e
use o
GCKRemoteMediaClient
para carregar a mídia no app receptor. Em seguida, você pode usar GCKRemoteMediaClient
para controlar um app de player de mídia em execução no receptor, como reproduzir,
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 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 largura em pixels. As variantes do formato 4K são indicadas na propriedade hdrType pelos valores de enumeração GCKVideoInfoHDRType.
Adicionar minicontroles
De acordo com a lista de verificação de design do Cast, um app remetente precisa fornecer um controle persistente conhecido como minicontrole 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 Cast fornece uma barra de controle,
GCKUIMiniMediaControlsViewController,
que pode ser adicionada às cenas em que você quer mostrar o minicontrole.
Quando o app remetente estiver reproduzindo um stream de vídeo ou áudio ao vivo, o SDK vai mostrar automaticamente um botão de reprodução/parada no lugar do botão de reprodução/pausa no minicontrole.
Consulte Personalizar a interface do remetente do iOS para saber como o app remetente pode configurar a aparência dos widgets do Cast.
Há duas maneiras de adicionar o minicontrole a um app remetente:
- Permita que o framework do Cast gerencie o layout do minicontrole envolvendo o controle de visualização atual com o próprio controle de visualização.
- Gerencie o layout do widget do minicontrole adicionando-o ao controle de visualização atual, fornecendo uma subvisualização no storyboard.
Envolver usando o GCKUICastContainerViewController
A primeira maneira é usar o
GCKUICastContainerViewController
que envolve outro controle de visualização e adiciona um
GCKUIMiniMediaControlsViewController
na parte de baixo. Essa abordagem é limitada porque não é possível personalizar a animação nem configurar o comportamento do controle de visualização do contêiner.
Essa primeira maneira geralmente é 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 no controle de visualização atual
A segunda maneira é adicionar o minicontrole diretamente ao controle de visualização atual usando
createMiniMediaControlsViewController
para criar uma instância
GCKUIMiniMediaControlsViewController
e adicioná-la ao controle de visualização do contêiner como uma subvisualização.
Configure o controle 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 controle de visualização raiz, crie uma instância GCKUIMiniMediaControlsViewController e adicione-a ao controle de visualização do 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 controle de visualização do host quando o minicontrole precisa estar 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 controle expandido para a mídia transmitida. O controle expandido é uma versão em tela cheia do minicontrole.
O controle expandido é uma exibição de 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, com exceção do controle de volume do Web Receiver e do ciclo de vida da sessão (conectar/parar transmissão). Ela também oferece todas as informações de status sobre a sessão de mídia (artes, título, subtítulo etc.).
A funcionalidade dessa visualização é implementada pela
GCKUIExpandedMediaControlsViewController
classe.
A primeira coisa que você precisa fazer é ativar o controle 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 controle de visualização para carregar o controle 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 no minicontrole.
Quando o app remetente estiver reproduzindo um stream de vídeo ou áudio ao vivo, o SDK vai mostrar automaticamente um botão de reprodução/parada no lugar do botão de reprodução/pausa no controle expandido.
Controle do volume
O framework do Cast gerencia automaticamente o volume do app remetente. O framework é sincronizado automaticamente com o volume do Web Receiver 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ísicos no dispositivo remetente podem ser usados para mudar o
volume da sessão de transmissão no Web Receiver usando a
physicalVolumeButtonsWillControlDeviceVolume flag no
GCKCastOptions,
que é 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 decidir encerrar a sessão de transmissão.
Observe que alguns erros, incluindo o
GCKErrorCode
GCKErrorCodeCancelled, são comportamentos intencionais.
Não tente reconectar uma conexão que falhou com GCKErrorCodeCancelled.
Isso pode levar a um comportamento inesperado.
Logging
GCKLogger
é um singleton usado para geração de registros pelo framework. Use o
GCKLoggerDelegate
para personalizar como você processa mensagens de registro.
Usando o GCKLogger, o SDK produz a 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 de 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 mensagens detalhadas e de depuração, 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 registro 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 classe podem ser nomes literais ou padrões glob, por exemplo, GCKUI\* e GCK\*Session.