Tornar um app iOS compatível com Cast

1. Visão geral

Logotipo do Google Cast

Este codelab ensinará você a modificar um app de vídeo iOS existente para transmitir conteúdo em um dispositivo compatível com Google Cast.

O que é o Google Cast?

O Google Cast permite que os usuários transmitam conteúdo de um dispositivo móvel para uma TV. O dispositivo poderá ser usado como controle remoto para a reprodução de mídia na TV.

O SDK do Google Cast permite ampliar seu app para controlar dispositivos compatíveis com Google Cast (como uma TV ou um sistema de som). Com o SDK do Cast, é possível adicionar os componentes de interface necessários com base na Lista de verificação de design do Google Cast.

Essa lista é fornecida para facilitar a experiência do usuário do Google Cast e deixá-la mais previsível em todas as plataformas compatíveis.

O que vamos criar?

Ao concluir este codelab, você terá um app de vídeo iOS capaz de transmitir vídeos para um dispositivo com Google Cast.

O que você vai aprender

  • Como adicionar o SDK do Google Cast a um app de vídeo de amostra.
  • Como adicionar o botão "Transmitir" para selecionar um dispositivo com Google Cast.
  • Como se conectar a um dispositivo com Google Cast e iniciar um receptor de mídia.
  • Como transmitir um vídeo.
  • Como adicionar um minicontrole do Google Cast ao seu app.
  • Como adicionar um controle expandido.
  • Como fornecer uma sobreposição introdutória.
  • Como personalizar os widgets do Google Cast.
  • Como integrar o Cast Connect

O que é necessário

  • O Xcode mais recente.
  • Um dispositivo móvel com iOS 9 ou mais recente (ou o Xcode Simulator).
  • Um cabo de dados USB para conectar o dispositivo móvel ao computador de desenvolvimento (se estiver usando um dispositivo).
  • Um dispositivo com Google Cast, como um Chromecast ou Android TV, configurado com acesso à Internet.
  • Uma TV ou um monitor com entrada HDMI.
  • O Chromecast com Google TV é necessário para testar a integração do Cast Connect, mas é opcional no restante do codelab. Caso ainda não tenha, pule a etapa Adicionar suporte do Cast Connect ao final deste tutorial.

Experiência

  • É necessário ter conhecimento prévio em desenvolvimento para iOS.
  • Também é necessário ter conhecimento prévio de como assistir TV :)

Como você usará este tutorial?

Apenas leitura Leitura e exercícios

Como você classificaria sua experiência com a criação de apps iOS?

Iniciante Intermediário Proficiente

Como você classificaria sua experiência com o ato de assistir TV?

Iniciante Intermediário Proficiente

2. Fazer o download do exemplo de código

Você pode fazer o download do exemplo de código inteiro para seu computador…

e descompactar o arquivo ZIP salvo.

3. Executar o app de amostra

Logotipo da Apple iOS

Primeiro, vamos ver a aparência do app de amostra concluído. O app é um player de vídeo básico. O usuário pode selecionar um vídeo em uma lista e assisti-lo localmente no dispositivo ou transmiti-lo para um dispositivo com Google Cast.

Com o código transferido por download, as instruções a seguir descrevem como abrir e executar o app de exemplo concluído no Xcode:

Perguntas frequentes

Configuração do CocoaPods

Para configurar o CocoaPods, acesse seu console e instale usando o Ruby padrão disponível no macOS:

sudo gem install cocoapods

Se tiver problemas, consulte a documentação oficial para fazer o download e instalar o gerenciador de dependências.

Configurar o projeto

  1. Acesse o terminal e navegue até o diretório do codelab.
  2. Instale as dependências do Podfile.
cd app-done
pod update
pod install
  1. Abra o Xcode e selecione Abrir outro projeto...
  2. Selecione o arquivo CastVideos-ios.xcworkspace no diretório ícone da pastaapp-done na pasta do exemplo de código.

Executar o app

Selecione o destino e o simulador e execute o app:

Barra de ferramentas do simulador de app XCode

Você verá o app de vídeo aparecer após alguns segundos.

Clique em "Permitir" quando a notificação sobre a aceitação de conexões de rede for exibida. O ícone do Google Cast não será exibido se essa opção não for aceita.

Caixa de diálogo de confirmação solicitando permissão para aceitar conexões de rede de entrada

Clique no botão "Transmitir" e selecione seu dispositivo com Google Cast.

Selecione um vídeo e clique no botão "Assistir".

O vídeo começará a ser reproduzido no seu dispositivo com Google Cast.

O controle expandido será exibido. É possível usar o botão "Assistir/Pausar" para controlar a reprodução.

Volte à lista de vídeos.

Um minicontrole agora está visível na parte de baixo da tela.

Ilustração de um iPhone executando o app Cast grupo com o minicontrole aparecendo na parte de baixo

Clique no botão "Pausar" do minicontrole para pausar o vídeo no receptor. Clique no botão "Assistir" para continuar a reprodução do vídeo.

Clique no botão "Transmitir" para interromper a transmissão para o dispositivo com Google Cast.

4. Preparar o projeto inicial

Ilustração de um iPhone executando o app CastVídeos

Precisamos adicionar compatibilidade com o Google Cast ao app inicial que você transferiu por download. Aqui está parte da terminologia do Google Cast que será usada neste codelab:

  • Um app remetente é executado em um dispositivo móvel ou laptop.
  • Um aplicativo receptor é executado no dispositivo com Google Cast.

Configurar o projeto

Agora está tudo pronto para você criar com base no projeto inicial usando o Xcode:

  1. Acesse o terminal e navegue até o diretório do codelab.
  2. Instale as dependências do Podfile.
cd app-start
pod update
pod install
  1. Abra o Xcode e selecione Abrir outro projeto...
  2. Selecione o arquivo CastVideos-ios.xcworkspace no diretório ícone da pastaapp-start na pasta do exemplo de código.

Design do app

O app busca uma lista de vídeos de um servidor da Web remoto e fornece uma lista para o usuário navegar. Os usuários podem selecionar um vídeo para ver os detalhes ou assisti-lo localmente no dispositivo móvel.

O app consiste em dois controladores de visualização principais: MediaTableViewController e MediaViewController..

MediaTableViewController

Este UITableViewController exibe uma lista de vídeos de uma instância do MediaListModel. A lista de vídeos e os metadados associados a eles estão hospedados em um servidor remoto como um arquivo JSON. MediaListModel busca esse JSON e o processa para criar uma lista de objetos MediaItem.

Um objeto MediaItem modela um vídeo e os metadados associados a ele, como título, descrição, URL de uma imagem e URL do stream.

O MediaTableViewController cria uma instância de MediaListModel e se registra como um MediaListModelDelegate para ser informado quando os metadados de mídia forem transferidos por download para que possa carregar a visualização em tabela.

Uma lista de miniaturas de vídeos é apresentada ao usuário com uma breve descrição de cada vídeo. Quando um item é selecionado, o MediaItem correspondente é transmitido para o MediaViewController.

MediaViewController

Esse controlador de visualização exibe os metadados sobre um vídeo específico e permite que o usuário reproduza o vídeo localmente no dispositivo móvel.

O controlador de visualização hospeda uma LocalPlayerView, alguns controles de mídia e uma área de texto para mostrar a descrição do vídeo selecionado. O player cobre a parte de cima da tela, deixando espaço para a descrição detalhada do vídeo abaixo. O usuário pode reproduzir/pausar ou procurar a reprodução do vídeo local.

Perguntas frequentes

5. Como adicionar o botão "Transmitir"

Ilustração da parte superior de um iPhone executando o app CastVídeos mostrando o botão Transmitir no canto superior direito

Um aplicativo compatível com Cast exibe o botão Transmitir em cada um dos controles de visualização. Ao clicar nesse botão, é exibida uma lista de dispositivos de transmissão que o usuário pode selecionar. Se o usuário estava assistindo conteúdo localmente no dispositivo remetente, selecionar um dispositivo de transmissão iniciará ou retomará a reprodução no dispositivo com Google Cast. A qualquer momento durante uma sessão, o usuário pode clicar no botão "Transmitir" e parar a transmissão do seu aplicativo para o dispositivo com Google Cast. O usuário precisa conseguir se conectar ou desconectar do dispositivo de transmissão enquanto estiver usando qualquer tela do seu app, conforme descrito na Lista de verificação de design do Google Cast.

Configuração

O projeto inicial requer as mesmas dependências e a configuração do Xcode que você usou para o app de exemplo concluído. Volte a essa seção e siga as mesmas etapas para adicionar o GoogleCast.framework ao projeto inicial do app.

Inicializaçã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 aplicativo remetente seja acionada corretamente e a verificação de dispositivos possa ser iniciada.

Um objeto GCKCastOptions 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 receptor, que é usado para filtrar os resultados da descoberta de dispositivos com Google Cast e iniciar o aplicativo receptor 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 registro do framework do Cast. Elas podem ser úteis para depuração e solução de problemas.

Quando você desenvolve seu próprio app compatível com Cast, é preciso se registrar como desenvolvedor do Google Cast e receber um ID para seu app. Para este codelab, usaremos um ID de app de amostra.

Adicione o seguinte código ao AppDelegate.swift para inicializar o GCKCastContext com o ID do aplicativo dos padrões do usuário e adicione um logger ao framework do Google Cast:

import GoogleCast

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  fileprivate var enableSDKLogging = true

  ...

  func application(_: UIApplication,
                   didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    ...
    let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID))
    options.physicalVolumeButtonsWillControlDeviceVolume = true
    GCKCastContext.setSharedInstanceWith(options)

    window?.clipsToBounds = true
    setupCastLogging()
    ...
  }
  ...
  func setupCastLogging() {
    let logFilter = GCKLoggerFilter()
    let classesToLog = ["GCKDeviceScanner", "GCKDeviceProvider", "GCKDiscoveryManager", "GCKCastChannel",
                        "GCKMediaControlChannel", "GCKUICastButton", "GCKUIMediaController", "NSMutableDictionary"]
    logFilter.setLoggingLevel(.verbose, forClasses: classesToLog)
    GCKLogger.sharedInstance().filter = logFilter
    GCKLogger.sharedInstance().delegate = self
  }
}

...

// MARK: - GCKLoggerDelegate

extension AppDelegate: GCKLoggerDelegate {
  func logMessage(_ message: String,
                  at _: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if enableSDKLogging {
      // Send SDK's log messages directly to the console.
      print("\(location): \(function) - \(message)")
    }
  }
}

Botão "Transmitir"

Agora que o GCKCastContext foi inicializado, precisamos adicionar o botão "Transmitir" para permitir que o usuário selecione um dispositivo com Google Cast. O SDK do Google Cast oferece um componente do botão Transmitir chamado GCKUICastButton como uma subclasse UIButton. Ele pode ser adicionado à barra de título do aplicativo colocando-o em um UIBarButtonItem. É necessário adicionar o botão Transmitir ao MediaTableViewController e ao MediaViewController.

Adicione o seguinte código a MediaTableViewController.swift e MediaViewController.swift:

import GoogleCast

@objc(MediaTableViewController)
class MediaTableViewController: UITableViewController, GCKSessionManagerListener,
  MediaListModelDelegate, GCKRequestDelegate {
  private var castButton: GCKUICastButton!
  ...
  override func viewDidLoad() {
    print("MediaTableViewController - viewDidLoad")
    super.viewDidLoad()

    ...
    castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
                                               width: CGFloat(24), height: CGFloat(24)))
    // Overwrite the UIAppearance theme in the AppDelegate.
    castButton.tintColor = UIColor.white
    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)

    ...
  }
  ...
}

Em seguida, adicione o seguinte código ao MediaViewController.swift:

import GoogleCast

@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener, GCKRemoteMediaClientListener,
  LocalPlayerViewDelegate, GCKRequestDelegate {
  private var castButton: GCKUICastButton!
  ...
  override func viewDidLoad() {
    super.viewDidLoad()
    print("in MediaViewController viewDidLoad")
    ...
    castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
                                               width: CGFloat(24), height: CGFloat(24)))
    // Overwrite the UIAppearance theme in the AppDelegate.
    castButton.tintColor = UIColor.white
    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)

    ...
  }
  ...
}

Agora execute o app. Você vai encontrar um botão "Transmitir" na barra de navegação do app. Quando clicar nele, os dispositivos de transmissão vão ser listados na sua rede local. A descoberta de dispositivos é gerenciada automaticamente pelo GCKCastContext. Selecione o dispositivo de transmissão para que o app de amostra receptor seja carregado nele. Você pode alternar entre a atividade de navegação e a atividade do player local mantendo estado do botão "Transmitir" sincronizado.

Não adicionamos compatibilidade com a reprodução de mídia, então você ainda não pode assistir vídeos no dispositivo de transmissão. Clique no botão Transmitir para interromper a transmissão.

6. Como transmitir conteúdo de vídeo

Ilustração de um iPhone executando o app CastVídeos, que mostra detalhes de um vídeo específico ("Lágrimas de aço"). Na parte inferior está o miniplayer

Nós ampliaremos o app de amostra para que ele também reproduza vídeos remotamente em um dispositivo com Google Cast. Para isso, precisamos detectar os diversos eventos gerados pelo framework do Google Cast.

Como transmitir mídia

De modo geral, se você quiser reproduzir mídia em um dispositivo de transmissão, precisará fazer o seguinte:

  1. Crie um objeto GCKMediaInformation do SDK do Cast que modele um item de mídia.
  2. O usuário se conecta ao dispositivo de transmissão para iniciar o app receptor.
  3. Carregar o objeto GCKMediaInformation no seu receptor e reproduzir o conteúdo.
  4. Monitorar o status da mídia.
  5. Enviar comandos de reprodução ao receptor com base nas interações do usuário.

A etapa 1 é o mesmo que mapear um objeto para outro. GCKMediaInformation é algo que o SDK do Cast entende, e MediaItem é o encapsulamento do nosso app para um item de mídia. É fácil mapear um MediaItem para um GCKMediaInformation. Já fizemos a etapa 2 na seção anterior. A etapa 3 é fácil de fazer com o SDK do Cast.

A MediaViewController do app de amostra já distingue entre a reprodução local e a remota usando esta enumeração:

enum PlaybackMode: Int {
  case none = 0
  case local
  case remote
}

private var playbackMode = PlaybackMode.none

Neste codelab, não é importante entender exatamente como toda a lógica do player da amostra funciona. É importante compreender que o player de mídia do app precisa ser modificado para estar ciente dos dois locais de reprodução de maneira semelhante.

No momento, o player local está sempre em estado de reprodução local, já que ele ainda não sabe nada sobre os estados de transmissão. Nós precisamos atualizar a IU com base nas transições de estado que ocorrem no framework do Google Cast. Por exemplo, se iniciarmos a transmissão, será necessário parar a reprodução local e desativar alguns controles. Da mesma forma, se pararmos a transmissão quando estivermos nesse controlador de visualização, precisaremos fazer a transição para a reprodução local. Para isso, precisamos detectar os diversos eventos gerados pelo framework do Google Cast.

Gerenciamento da sessão de transmissão

Para o framework do Google Cast, uma sessão combina as etapas de conexão a um dispositivo, inicialização (ou participação), conexão a um aplicativo receptor e inicialização de um canal de controle de mídia, se adequado. O canal de controle de mídia é a forma como o framework do Google Cast envia e recebe mensagens do player de mídia do receptor.

A sessão do Google Cast será iniciada automaticamente quando o usuário selecionar um dispositivo usando o botão "Transmitir" e será interrompida quando o usuário se desconectar. A reconexão a uma sessão receptora devido a problemas de rede também é processada automaticamente pelo framework do Google Cast.

As sessões de transmissão são gerenciadas pelo GCKSessionManager, que pode ser acessado via GCKCastContext.sharedInstance().sessionManager. Os callbacks GCKSessionManagerListener podem ser usados para monitorar eventos de sessão, como criação, suspensão, retomada e encerramento.

Primeiro, precisamos registrar nosso listener de sessão e inicializar algumas variáveis:

class MediaViewController: UIViewController, GCKSessionManagerListener,
  GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {

  ...
  private var sessionManager: GCKSessionManager!
  ...

  required init?(coder: NSCoder) {
    super.init(coder: coder)

    sessionManager = GCKCastContext.sharedInstance().sessionManager

    ...
  }

  override func viewWillAppear(_ animated: Bool) {
    ...

    let hasConnectedSession: Bool = (sessionManager.hasConnectedSession())
    if hasConnectedSession, (playbackMode != .remote) {
      populateMediaInfo(false, playPosition: 0)
      switchToRemotePlayback()
    } else if sessionManager.currentSession == nil, (playbackMode != .local) {
      switchToLocalPlayback()
    }

    sessionManager.add(self)

    ...
  }

  override func viewWillDisappear(_ animated: Bool) {
    ...

    sessionManager.remove(self)
    sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
    ...
    super.viewWillDisappear(animated)
  }

  func switchToLocalPlayback() {
    ...

    sessionManager.currentCastSession?.remoteMediaClient?.remove(self)

    ...
  }

  func switchToRemotePlayback() {
    ...

    sessionManager.currentCastSession?.remoteMediaClient?.add(self)

    ...
  }


  // MARK: - GCKSessionManagerListener

  func sessionManager(_: GCKSessionManager, didStart session: GCKSession) {
    print("MediaViewController: sessionManager didStartSession \(session)")
    setQueueButtonVisible(true)
    switchToRemotePlayback()
  }

  func sessionManager(_: GCKSessionManager, didResumeSession session: GCKSession) {
    print("MediaViewController: sessionManager didResumeSession \(session)")
    setQueueButtonVisible(true)
    switchToRemotePlayback()
  }

  func sessionManager(_: GCKSessionManager, didEnd _: GCKSession, withError error: Error?) {
    print("session ended with error: \(String(describing: error))")
    let message = "The Casting session has ended.\n\(String(describing: error))"
    if let window = appDelegate?.window {
      Toast.displayMessage(message, for: 3, in: window)
    }
    setQueueButtonVisible(false)
    switchToLocalPlayback()
  }

  func sessionManager(_: GCKSessionManager, didFailToStartSessionWithError error: Error?) {
    if let error = error {
      showAlert(withTitle: "Failed to start a session", message: error.localizedDescription)
    }
    setQueueButtonVisible(false)
  }

  func sessionManager(_: GCKSessionManager,
                      didFailToResumeSession _: GCKSession, withError _: Error?) {
    if let window = UIApplication.shared.delegate?.window {
      Toast.displayMessage("The Casting session could not be resumed.",
                           for: 3, in: window)
    }
    setQueueButtonVisible(false)
    switchToLocalPlayback()
  }

  ...
}

Em MediaViewController, queremos saber quando nos conectarmos ou desconectarmos do dispositivo de transmissão para que possamos mudar de ou para o player local. Observe que a conectividade pode ser interrompida não só pela instância do seu aplicativo em execução no dispositivo móvel, como também por outra instância do seu (ou de outro) aplicativo em execução em um dispositivo móvel diferente.

A sessão ativa no momento fica acessível como GCKCastContext.sharedInstance().sessionManager.currentCastSession. As sessões são criadas e eliminadas automaticamente em resposta aos gestos do usuário nas caixas de diálogo do Google Cast.

Como carregar mídia

No SDK do Cast, o GCKRemoteMediaClient fornece um conjunto de APIs convenientes para gerenciar a reprodução de mídia remota no receptor. Para uma GCKCastSession compatível com a reprodução de mídia, uma instância de GCKRemoteMediaClient será criada automaticamente pelo SDK. Ela pode ser acessada como a propriedade remoteMediaClient da instância da GCKCastSession.

Adicione o seguinte código ao MediaViewController.swift para carregar o vídeo selecionado no receptor:

@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener,
  GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
  ...

  @objc func playSelectedItemRemotely() {
    loadSelectedItem(byAppending: false)
  }

  /**
   * Loads the currently selected item in the current cast media session.
   * @param appending If YES, the item is appended to the current queue if there
   * is one. If NO, or if
   * there is no queue, a new queue containing only the selected item is created.
   */
  func loadSelectedItem(byAppending appending: Bool) {
    print("enqueue item \(String(describing: mediaInfo))")
    if let remoteMediaClient = sessionManager.currentCastSession?.remoteMediaClient {
      let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
      mediaQueueItemBuilder.mediaInformation = mediaInfo
      mediaQueueItemBuilder.autoplay = true
      mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
      let mediaQueueItem = mediaQueueItemBuilder.build()
      if appending {
        let request = remoteMediaClient.queueInsert(mediaQueueItem, beforeItemWithID: kGCKMediaQueueInvalidItemID)
        request.delegate = self
      } else {
        let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
        queueDataBuilder.items = [mediaQueueItem]
        queueDataBuilder.repeatMode = remoteMediaClient.mediaStatus?.queueRepeatMode ?? .off

        let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
        mediaLoadRequestDataBuilder.mediaInformation = mediaInfo
        mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()

        let request = remoteMediaClient.loadMedia(with: mediaLoadRequestDataBuilder.build())
        request.delegate = self
      }
    }
  }
  ...
}

Agora, atualize vários métodos existentes para usar a lógica da sessão de transmissão e oferecer suporte à reprodução remota:

required init?(coder: NSCoder) {
  super.init(coder: coder)
  ...
  castMediaController = GCKUIMediaController()
  ...
}

func switchToLocalPlayback() {
  print("switchToLocalPlayback")
  if playbackMode == .local {
    return
  }
  setQueueButtonVisible(false)
  var playPosition: TimeInterval = 0
  var paused: Bool = false
  var ended: Bool = false
  if playbackMode == .remote {
    playPosition = castMediaController.lastKnownStreamPosition
    paused = (castMediaController.lastKnownPlayerState == .paused)
    ended = (castMediaController.lastKnownPlayerState == .idle)
    print("last player state: \(castMediaController.lastKnownPlayerState), ended: \(ended)")
  }
  populateMediaInfo((!paused && !ended), playPosition: playPosition)
  sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
  playbackMode = .local
}

func switchToRemotePlayback() {
  print("switchToRemotePlayback; mediaInfo is \(String(describing: mediaInfo))")
  if playbackMode == .remote {
    return
  }
  // If we were playing locally, load the local media on the remote player
  if playbackMode == .local, (_localPlayerView.playerState != .stopped), (mediaInfo != nil) {
    print("loading media: \(String(describing: mediaInfo))")
    let paused: Bool = (_localPlayerView.playerState == .paused)
    let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
    mediaQueueItemBuilder.mediaInformation = mediaInfo
    mediaQueueItemBuilder.autoplay = !paused
    mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
    mediaQueueItemBuilder.startTime = _localPlayerView.streamPosition ?? 0
    let mediaQueueItem = mediaQueueItemBuilder.build()

    let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
    queueDataBuilder.items = [mediaQueueItem]
    queueDataBuilder.repeatMode = .off

    let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
    mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()

    let request = sessionManager.currentCastSession?.remoteMediaClient?.loadMedia(with: mediaLoadRequestDataBuilder.build())
    request?.delegate = self
  }
  _localPlayerView.stop()
  _localPlayerView.showSplashScreen()
  setQueueButtonVisible(true)
  sessionManager.currentCastSession?.remoteMediaClient?.add(self)
  playbackMode = .remote
}

/* Play has been pressed in the LocalPlayerView. */
func continueAfterPlayButtonClicked() -> Bool {
  let hasConnectedCastSession = sessionManager.hasConnectedCastSession
  if mediaInfo != nil, hasConnectedCastSession() {
    // Display an alert box to allow the user to add to queue or play
    // immediately.
    if actionSheet == nil {
      actionSheet = ActionSheet(title: "Play Item", message: "Select an action", cancelButtonText: "Cancel")
      actionSheet?.addAction(withTitle: "Play Now", target: self,
                             selector: #selector(playSelectedItemRemotely))
    }
    actionSheet?.present(in: self, sourceView: _localPlayerView)
    return false
  }
  return true
}

Agora, execute o app no seu dispositivo móvel. Conecte seu dispositivo com Google Cast e comece a assistir um vídeo. Você verá o vídeo sendo reproduzido no receptor.

7. Minicontrole

A Lista de verificação de design do Google Cast exige que todos os apps do Google Cast ofereçam um minicontrole 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.

Ilustração da parte inferior de um iPhone com o app CastVídeos, com foco no minicontrole

O SDK do Cast oferece uma barra de controle, GCKUIMiniMediaControlsViewController, que pode ser adicionada às cenas em que você quer mostrar os controles permanentes.

Para o app de exemplo, usaremos o GCKUICastContainerViewController, que envolve outro controlador de visualização e adiciona um GCKUIMiniMediaControlsViewController na parte inferior.

Modifique o arquivo AppDelegate.swift e adicione o seguinte código para a condição if useCastContainerViewController no seguinte método:

func application(_: UIApplication,
                 didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  ...
  let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
  guard let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
    as? UINavigationController else { return false }
  let castContainerVC = GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
    as GCKUICastContainerViewController
  castContainerVC.miniMediaControlsItemEnabled = true
  window = UIWindow(frame: UIScreen.main.bounds)
  window?.rootViewController = castContainerVC
  window?.makeKeyAndVisible()
  ...
}

Adicione esta propriedade e setter/getter para controlar a visibilidade do minicontrole (vamos usar isso em uma seção posterior):

var isCastControlBarsEnabled: Bool {
    get {
      if useCastContainerViewController {
        let castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
        return castContainerVC!.miniMediaControlsItemEnabled
      } else {
        let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
        return rootContainerVC!.miniMediaControlsViewEnabled
      }
    }
    set(notificationsEnabled) {
      if useCastContainerViewController {
        var castContainerVC: GCKUICastContainerViewController?
        castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
        castContainerVC?.miniMediaControlsItemEnabled = notificationsEnabled
      } else {
        var rootContainerVC: RootContainerViewController?
        rootContainerVC = (window?.rootViewController as? RootContainerViewController)
        rootContainerVC?.miniMediaControlsViewEnabled = notificationsEnabled
      }
    }
  }

Execute o app e transmita um vídeo. Quando a reprodução começar no receptor, o minicontrole aparecerá na parte inferior de cada cena. Ele pode ser usado para controlar a reprodução remota. Caso você alterne entre a atividade de navegação e a atividade do player local, o estado do minicontrole ficará sincronizado com o status da reprodução de mídia do receptor.

8. Sobreposição introdutória

A Lista de verificação de design do Google Cast exige que um app remetente introduza o botão Transmitir para informar aos usuários que agora ele é compatível com a transmissão e ajuda os novos usuários do Google Cast.

Ilustração de um iPhone executando o app CastVídeos com a sobreposição do botão Transmitir, destacando o botão Transmitir e mostrando a mensagem "Toque para transmitir mídia para sua TV e alto-falantes"

A classe GCKCastContext tem um método, presentCastInstructionsViewControllerOnce, que pode ser usado para destacar o botão Transmitir quando ele for exibido aos usuários pela primeira vez. Adicione o seguinte código a MediaViewController.swift e MediaTableViewController.swift:

override func viewDidLoad() {
  ...

  NotificationCenter.default.addObserver(self, selector: #selector(castDeviceDidChange),
                                         name: NSNotification.Name.gckCastStateDidChange,
                                         object: GCKCastContext.sharedInstance())
}

@objc func castDeviceDidChange(_: Notification) {
  if GCKCastContext.sharedInstance().castState != .noDevicesAvailable {
    // You can present the instructions on how to use Google Cast on
    // the first time the user uses you app
    GCKCastContext.sharedInstance().presentCastInstructionsViewControllerOnce(with: castButton)
  }
}

Execute o app no seu dispositivo móvel. A sobreposição introdutória vai aparecer.

9. 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.

Ilustração de um iPhone com o app CastVídeos exibindo um vídeo com o controle expandido aparecendo na parte inferior

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 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 controle expandido padrão no contexto de transmissão. Modifique AppDelegate.swift para ativar o controle expandido padrão:

import GoogleCast

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  ...

  func application(_: UIApplication,
                   didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    ...
    // Add after the setShareInstanceWith(options) is set.
    GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
    ...
  }
  ...
}

Adicione o seguinte código ao MediaViewController.swift para carregar o controle expandido quando o usuário começar a transmitir um vídeo:

@objc func playSelectedItemRemotely() {
  ...
  appDelegate?.isCastControlBarsEnabled = false
  GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()
}

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

Execute o app e transmita um vídeo. Você verá o controle expandido. Volte à lista de vídeos e, ao clicar no minicontrole, o controle expandido será carregado novamente.

10. Adicionar suporte ao Cast Connect

A biblioteca Cast Connect permite que aplicativos remetentes existentes se comuniquem com aplicativos do Android TV usando o protocolo Cast. O Cast Connect é baseado na infraestrutura do Cast, com seu app Android TV atuando como receptor.

Dependências

No Podfile, verifique se o google-cast-sdk está apontando para 4.4.8 ou para uma versão mais recente, conforme listado abaixo. Se você fez uma modificação no arquivo, execute pod update no console para sincronizar a alteração com o projeto.

pod 'google-cast-sdk', '>=4.4.8'

GCKLaunchOptions

Para iniciar o app Android TV, também conhecido como receptor do Android, precisamos definir a flag androidReceiverCompatible como verdadeira no objeto GCKLaunchOptions. Esse objeto GCKLaunchOptions determina como o receptor é iniciado e transmitido para o GCKCastOptions, que é definido na instância compartilhada usando GCKCastContext.setSharedInstanceWith.

Adicione as linhas abaixo ao AppDelegate.swift:

let options = GCKCastOptions(discoveryCriteria:
                          GCKDiscoveryCriteria(applicationID: kReceiverAppID))
...
/** Following code enables CastConnect */
let launchOptions = GCKLaunchOptions()
launchOptions.androidReceiverCompatible = true
options.launchOptions = launchOptions

GCKCastContext.setSharedInstanceWith(options)

Definir credenciais de lançamento

No lado do remetente, é possível especificar GCKCredentialsData para representar quem está entrando na sessão. O credentials é uma string que pode ser definida pelo usuário, desde que seu app ATV possa entendê-lo. O GCKCredentialsData só é transmitido ao app Android TV durante a inicialização ou o horário de entrada. Se você configurá-lo novamente enquanto estiver conectado, ele não será transmitido para o app para Android TV.

Para definir credenciais de inicialização, é necessário definir GCKCredentialsData a qualquer momento depois de definir GCKLaunchOptions. Para demonstrar isso, vamos adicionar lógica para o botão Creds definir as credenciais que serão transmitidas quando a sessão for estabelecida. Adicione o seguinte código ao MediaTableViewController.swift:

class MediaTableViewController: UITableViewController, GCKSessionManagerListener, MediaListModelDelegate, GCKRequestDelegate {
  ...
  private var credentials: String? = nil
  ...
  override func viewDidLoad() {
    ...
    navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Creds", style: .plain,
                                                       target: self, action: #selector(toggleLaunchCreds))
    ...
    setLaunchCreds()
  }
  ...
  @objc func toggleLaunchCreds(_: Any){
    if (credentials == nil) {
        credentials = "{\"userId\":\"id123\"}"
    } else {
        credentials = nil
    }
    Toast.displayMessage("Launch Credentials: "+(credentials ?? "Null"), for: 3, in: appDelegate?.window)
    print("Credentials set: "+(credentials ?? "Null"))
    setLaunchCreds()
  }
  ...
  func setLaunchCreds() {
    GCKCastContext.sharedInstance()
        .setLaunch(GCKCredentialsData(credentials: credentials))
  }
}

Definir credenciais na solicitação de carregamento

Para processar credentials nos apps para Web e receptores do Android TV, adicione o seguinte código na classe MediaTableViewController.swift na função loadSelectedItem:

let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
...
mediaLoadRequestDataBuilder.credentials = credentials
...

Dependendo do app receptor para o qual o remetente está transmitindo, o SDK vai aplicar automaticamente as credenciais acima à sessão em andamento.

Como testar a Cast Connect

Etapas para instalar o APK do Android TV no Chromecast com Google TV

  1. Encontre o endereço IP do seu dispositivo Android TV. Geralmente, está disponível em Configurações > Rede e Internet > Nome da rede a que o dispositivo está conectado. À direita, serão mostrados os detalhes e o IP do dispositivo na rede.
  2. Use o endereço IP do seu dispositivo para se conectar a ele pelo ADB usando o terminal:
$ adb connect <device_ip_address>:5555
  1. Na janela do terminal, navegue até a pasta de nível superior para encontrar os exemplos de codelab que você baixou no início deste codelab. Exemplo:
$ cd Desktop/ios_codelab_src
  1. Para instalar o arquivo .apk dessa pasta no Android TV, execute o seguinte:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. Agora você verá um app com o nome Transmitir vídeos no menu Seus apps no dispositivo Android TV.
  2. Quando terminar, crie e execute o app em um emulador ou dispositivo móvel. Ao estabelecer uma sessão de transmissão com seu dispositivo Android TV, ele iniciará o app Android Receiver no Android TV. A reprodução de um vídeo do remetente do dispositivo móvel iOS inicia o vídeo no receptor Android e permite que você controle a reprodução usando o controle remoto do dispositivo Android TV.

11. Personalizar widgets do Google Cast

Inicialização

Comece com a pasta App-Done. Adicione o seguinte ao método applicationDidFinishLaunchingWithOptions no arquivo AppDelegate.swift.

func application(_: UIApplication,
                 didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  ...
  let styler = GCKUIStyle.sharedInstance()
  ...
}

Quando terminar de aplicar uma ou mais personalizações, conforme mencionado no restante deste codelab, confirme os estilos chamando o código abaixo

styler.apply()

Como personalizar visualizações do Google Cast

Você pode personalizar todas as visualizações que o Framework do aplicativo Google Cast gerencia aplicando as diretrizes de estilo padrão em todas as visualizações. Como exemplo, vamos alterar a cor da tonalidade do ícone.

styler.castViews.iconTintColor = .lightGray

É possível modificar os padrões por tela, se necessário. Por exemplo, para substituir lightGrayColor pela cor de tonalidade do ícone apenas para o controlador de mídia expandido.

styler.castViews.mediaControl.expandedController.iconTintColor = .green

Alteração de cores

Você pode personalizar a cor do plano de fundo para todas as visualizações (ou individualmente para cada uma). O código a seguir define a cor do plano de fundo como azul para todas as visualizações fornecidas pelo framework do aplicativo Google Cast.

styler.castViews.backgroundColor = .blue
styler.castViews.mediaControl.miniController.backgroundColor = .yellow

Alterar fontes

É possível personalizar fontes para diferentes rótulos nas visualizações de transmissão. Vamos definir todas as fontes como "Courier-Oblique" para fins ilustrativos.

styler.castViews.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 16) ?? UIFont.systemFont(ofSize: 16)
styler.castViews.mediaControl.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 6) ?? UIFont.systemFont(ofSize: 6)

Como alterar imagens de botão padrão

Adicione suas próprias imagens personalizadas ao projeto e atribua-as aos seus botões para estilizá-las.

let muteOnImage = UIImage.init(named: "yourImage.png")
if let muteOnImage = muteOnImage {
  styler.castViews.muteOnImage = muteOnImage
}

Como mudar o tema do botão Transmitir

Também é possível definir um tema para widgets do Cast usando o protocolo UIAppearance. O código a seguir tem como tema o GCKUICastButton em todas as visualizações exibidas:

GCKUICastButton.appearance().tintColor = UIColor.gray

12. Parabéns

Agora você já sabe como adicionar um app de vídeo compatível com o Cast usando os widgets do SDK do Cast no iOS.

Para mais detalhes, consulte o guia do desenvolvedor do iOS Sender.