Włączanie przesyłania w aplikacji na iOS

1. Omówienie

Logo Google Cast

Dzięki tym ćwiczeniom w Codelabs dowiesz się, jak zmodyfikować istniejącą aplikację wideo na iOS, aby móc przesyłać treści na urządzenie obsługujące Google Cast.

Co to jest Google Cast?

Google Cast umożliwia użytkownikom przesyłanie treści z urządzenia mobilnego na telewizor. Dzięki temu użytkownicy mogą używać swoich urządzeń mobilnych jako pilota do odtwarzania multimediów na telewizorze.

Google Cast SDK umożliwia rozszerzenie aplikacji o możliwość sterowania urządzeniami obsługującymi Google Cast (np. telewizorem lub systemem audio). SDK Cast pozwala dodać niezbędne komponenty interfejsu zgodnie z listą kontrolną projektowania Google Cast.

Znajdziesz tam listę kontrolną projektowania Google Cast, która ułatwia i przewidywalność korzystania z Google Cast na wszystkich obsługiwanych platformach.

Co utworzymy?

Gdy ukończysz to ćwiczenie w Codelabs, będziesz mieć aplikację wideo na iOS, która pozwoli przesyłać filmy na urządzenie Google Cast.

Czego się nauczysz

  • Jak dodać pakiet Google Cast SDK do przykładowej aplikacji wideo.
  • Jak dodać przycisk przesyłania umożliwiający wybór urządzenia Google Cast?
  • Jak połączyć się z urządzeniem przesyłającym i uruchomić odbiornik multimediów.
  • Jak przesyłać filmy.
  • Jak dodać minikontroler Cast do aplikacji.
  • Jak dodać rozwinięty kontroler.
  • Jak umieścić wstępny wstęp.
  • Jak dostosować widżety Cast.
  • Jak zintegrować Cast Connect

Czego potrzebujesz

  • Najnowszy Xcode.
  • Jedno urządzenie mobilne z systemem iOS 9 lub nowszym (lub symulator Xcode).
  • Kabel USB do podłączenia urządzenia mobilnego do komputera programisty (jeśli używasz urządzenia).
  • Urządzenie przesyłające Google Cast, takie jak Chromecast lub Android TV, skonfigurowane z dostępem do internetu.
  • Telewizor lub monitor z wejściem HDMI.
  • Do testowania integracji Cast Connect wymagany jest Chromecast z Google TV, ale w pozostałych ćwiczeniach z programowania jest opcjonalny. Jeśli nie masz takiego urządzenia, możesz pominąć krok Dodaj obsługę Cast Connect pod koniec tego samouczka.

Doświadczenie

  • Niezbędna jest do tego wiedza na temat programowania aplikacji na iOS.
  • Wymagamy też wcześniejszej wiedzy na temat oglądania telewizji. :)

Jak wykorzystasz ten samouczek?

Tylko przeczytać Przeczytać i wykonać ćwiczenia

Jak oceniasz swoje doświadczenia z tworzeniem aplikacji na iOS?

Początkujący Poziom średnio zaawansowany Biegły

Jak oceniasz swoje wrażenia z oglądania telewizji?

Początkujący Poziom średnio zaawansowany Biegły

2. Pobieranie przykładowego kodu

Możesz pobrać cały przykładowy kod na swój komputer...

i rozpakuj pobrany plik ZIP.

3. Uruchamianie przykładowej aplikacji

Logo Apple iOS

Najpierw zobaczmy, jak wygląda ukończona przykładowa aplikacja. Aplikacja jest podstawowym odtwarzaczem wideo. Użytkownik może wybrać film z listy i odtworzyć go lokalnie na urządzeniu lub przesłać na urządzenie przesyłające Google Cast.

Po pobraniu kodu możesz otworzyć i uruchomić gotową przykładową aplikację w Xcode. Aby to zrobić, wykonaj te instrukcje:

Najczęstsze pytania

Konfiguracja CocoaPods

Aby skonfigurować CocoaPods, otwórz konsolę i zainstaluj przeglądarkę za pomocą domyślnej wersji Ruby dostępnej w systemie macOS:

sudo gem install cocoapods

W razie problemów zapoznaj się z oficjalną dokumentacją, aby pobrać i zainstalować menedżera zależności.

Konfigurowanie projektu

  1. Otwórz terminal i przejdź do katalogu ćwiczeń z programowania.
  2. Zainstaluj zależności z pliku Podfile.
cd app-done
pod update
pod install
  1. Otwórz Xcode i wybierz Open another project... (Otwórz inny projekt).
  2. Wybierz plik CastVideos-ios.xcworkspace z katalogu ikona folderuapp-done w folderze przykładowego kodu.

Uruchom aplikację

Wybierz cel i symulator, a następnie uruchom aplikację:

Pasek narzędzi symulatora aplikacji XCode

Aplikacja wideo powinna pojawić się po kilku sekundach.

Pamiętaj, aby kliknąć „Zezwól”. gdy pojawi się powiadomienie o akceptowaniu przychodzących połączeń sieciowych. Jeśli ta opcja nie zostanie zaakceptowana, ikona przesyłania nie będzie wyświetlana.

Okno z prośbą o potwierdzenie z prośbą o zgodę na akceptowanie przychodzących połączeń sieciowych

Kliknij przycisk Cast i wybierz urządzenie Google Cast.

Wybierz film i kliknij przycisk odtwarzania.

Rozpocznie się odtwarzanie filmu na urządzeniu Google Cast.

Wyświetli się rozwinięty kontroler. Możesz sterować odtwarzaniem za pomocą przycisku odtwarzania/wstrzymania.

Wróć do listy filmów.

U dołu ekranu pojawił się teraz minikontroler.

Ilustracja przedstawiająca iPhone'a z aplikacją Castvideo z minikontrolerem widocznym u dołu

Kliknij przycisk wstrzymania na minikontrolerze, aby wstrzymać odtwarzanie wideo na odbiorniku. Kliknij przycisk odtwarzania na minikontrolerze, aby wznowić odtwarzanie filmu.

Aby zatrzymać przesyłanie na urządzenie Google Cast, kliknij przycisk Cast.

4. Przygotowywanie projektu początkowego

Ilustracja przedstawiająca iPhone'a z uruchomioną aplikacją Castvideo

Musisz dodać obsługę Google Cast do pobranej przez Ciebie aplikacji startowej. Oto terminologia dotycząca Google Cast, której będziemy używać w tym ćwiczeniu z programowania:

  • aplikacja nadawca działa na urządzeniu mobilnym lub laptopie,
  • aplikacja odbiornika na urządzeniu Google Cast).

Konfigurowanie projektu

Teraz możesz zacząć budować na podstawie projektu startowego za pomocą Xcode:

  1. Otwórz terminal i przejdź do katalogu codelab.
  2. Zainstaluj zależności z pliku Podfile.
cd app-start
pod update
pod install
  1. Otwórz Xcode i wybierz Open another project... (Otwórz inny projekt).
  2. Wybierz plik CastVideos-ios.xcworkspace z katalogu ikona folderuapp-start w folderze przykładowego kodu.

Projektowanie aplikacji

Aplikacja pobiera listę filmów ze zdalnego serwera WWW i udostępnia użytkownikowi listę do przeglądania. Użytkownicy mogą wybrać film, aby wyświetlić szczegóły lub odtworzyć go lokalnie na urządzeniu mobilnym.

Aplikacja składa się z 2 głównych kontrolerów widoku: MediaTableViewControllerMediaViewController..

MediaTableViewController

Ten obiekt UITableViewController wyświetla listę filmów z instancji MediaListModel. Lista filmów i powiązane z nimi metadane są przechowywane na serwerze zdalnym w postaci pliku JSON. MediaListModel pobiera ten plik JSON i przetwarza go, aby utworzyć listę obiektów MediaItem.

Obiekt MediaItem modeluje film i powiązane z nim metadane, takie jak tytuł, opis, adres URL obrazu i adres URL strumienia.

MediaTableViewController tworzy instancję MediaListModel, a następnie rejestruje się jako MediaListModelDelegate, aby otrzymać informację o pobraniu metadanych multimediów, co umożliwi wczytanie widoku tabeli.

Użytkownik widzi listę miniatur filmów z krótkim opisem każdego filmu. Po wybraniu elementu do funkcji MediaViewController przekazywany jest odpowiedni MediaItem.

MediaViewController

Ten kontroler widoku danych umożliwia wyświetlanie metadanych konkretnego filmu i umożliwia użytkownikowi odtwarzanie tego filmu lokalnie na urządzeniu mobilnym.

Kontroler widoku zawiera obiekt LocalPlayerView, niektóre elementy sterujące multimediami oraz obszar tekstowy do pokazania opisu wybranego filmu. Odtwarzacz zasłania górną część ekranu, pozostawiając miejsce na szczegółowy opis filmu. Pod spodem użytkownik może odtwarzać, wstrzymywać lub przewijać film.

Najczęstsze pytania

5. Dodawanie przycisku Cast

Grafika przedstawiająca górną jedną trzecią iPhone'a z aplikacją CastVideo, na której widać przycisk Cast w prawym górnym rogu

Aplikacja obsługująca Cast wyświetla przycisk Cast na każdym ze swoich kontrolerów widoku. Po kliknięciu tego przycisku wyświetla się lista urządzeń przesyłających, które użytkownik może wybrać. Jeśli użytkownik odtwarzał treści lokalnie na urządzeniu nadawcy, wybranie urządzenia przesyłającego spowoduje uruchomienie lub wznowienie odtwarzania na tym urządzeniu. W dowolnym momencie sesji przesyłania użytkownik może kliknąć przycisk Cast i zatrzymać przesyłanie aplikacji na urządzenie przesyłające. Użytkownik musi mieć możliwość połączenia się z urządzeniem przesyłającym lub jego rozłączenia na dowolnym ekranie aplikacji, co opisano na liście kontrolnej projektowania w Google Cast.

Konfiguracja

Projekt początkowy wymaga tych samych zależności i konfiguracji Xcode co w przypadku ukończonej przykładowej aplikacji. Wróć do tej sekcji i wykonaj te same czynności, aby dodać GoogleCast.framework do początkowego projektu aplikacji.

Zdarzenie inicjujące

Platforma Cast ma globalny obiekt typu singleton – GCKCastContext, który koordynuje wszystkie działania w ramach platformy. Aby można było prawidłowo aktywować automatyczne wznawianie sesji po ponownym uruchomieniu aplikacji nadawcy i rozpocząć skanowanie w poszukiwaniu urządzeń, ten obiekt musi zostać zainicjowany na wczesnym etapie cyklu życia aplikacji, zwykle w metodzie application(_:didFinishLaunchingWithOptions:) jej przedstawiciela.

Podczas inicjowania GCKCastContext należy podać obiekt GCKCastOptions. Ta klasa zawiera opcje, które mają wpływ na działanie platformy. Najważniejszy z nich jest identyfikator aplikacji odbiornika, który służy do filtrowania wyników wykrywania urządzeń przesyłających i uruchamiania aplikacji odbiornika po rozpoczęciu sesji przesyłania.

Metoda application(_:didFinishLaunchingWithOptions:) to także dobre miejsce do skonfigurowania przedstawiciela logowania, który będzie odbierał komunikaty logowania z platformy Cast. Mogą one być przydatne podczas debugowania i rozwiązywania problemów.

Gdy tworzysz własną aplikację obsługującą Google Cast, musisz zarejestrować się jako programista Cast, a potem uzyskać dla niej identyfikator. W tym ćwiczeniu z programowania użyjemy przykładowego identyfikatora aplikacji.

Dodaj do AppDelegate.swift ten kod, aby zainicjować GCKCastContext przy użyciu domyślnego identyfikatora aplikacji użytkownika i dodać rejestrator dla platformy 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)")
    }
  }
}

Przycisk Cast

Po zainicjowaniu GCKCastContext musimy dodać przycisk przesyłania, aby umożliwić użytkownikowi wybranie urządzenia do przesyłania. Pakiet Cast SDK udostępnia komponent przycisku przesyłania o nazwie GCKUICastButton jako podklasę UIButton. Można go dodać do paska tytułu aplikacji, pakując go w element UIBarButtonItem. Musimy dodać przycisk Cast do MediaTableViewController i MediaViewController.

Dodaj do MediaTableViewController.swift i MediaViewController.swift ten kod:

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)

    ...
  }
  ...
}

Następnie dodaj do MediaViewController.swift ten kod:

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)

    ...
  }
  ...
}

Teraz uruchom aplikację. Przycisk Cast powinien pojawić się na pasku nawigacyjnym aplikacji. Kliknięcie go spowoduje wyświetlenie urządzeń przesyłających w sieci lokalnej. Wykrywaniem urządzeń zarządza automatycznie GCKCastContext. Wybierz urządzenie przesyłające. Aplikacja odbiornika załaduje się na to urządzenie. Możesz przechodzić między aktywnością związaną z przeglądaniem a aktywnością lokalnego odtwarzacza, a stan przycisku przesyłania jest zsynchronizowany.

Nie udostępniliśmy obsługi odtwarzania multimediów, więc nie można jeszcze odtwarzać filmów na urządzeniu Cast. Kliknij przycisk Cast, by zatrzymać przesyłanie.

6. Przesyłanie treści wideo

Ilustracja przedstawiająca iPhone'a z uruchomioną aplikacją CastVideo, która pokazuje szczegóły konkretnego filmu („Tears of Steel”). U dołu znajduje się miniodtwarzacz

Rozszerzymy przykładową aplikację o możliwość zdalnego odtwarzania filmów na urządzeniu przesyłającym. Aby to zrobić, musisz nasłuchiwać różnych zdarzeń generowanych przez platformę Cast.

Przesyłanie multimediów

Ogólnie rzecz biorąc, jeśli chcesz odtworzyć multimedia na urządzeniu przesyłającym, musisz spełnić te warunki:

  1. Utwórz w pakiecie SDK Cast obiekt GCKMediaInformation, który modeluje element multimedialny.
  2. Użytkownik łączy się z urządzeniem przesyłającym, aby uruchomić aplikację odbiornika.
  3. Wczytaj obiekt GCKMediaInformation do odbiornika i odtwórz treści.
  4. Śledź stan multimediów.
  5. Wysyłaj polecenia odtwarzania do odbiornika na podstawie interakcji użytkownika.

Etap 1 oznacza mapowanie jednego obiektu na drugi. GCKMediaInformation to zrozumiały pakiet SDK Cast, a MediaItem to kod elementu multimedialnego aplikacji. możemy łatwo zmapować MediaItem na GCKMediaInformation. Krok 2 w poprzedniej sekcji został już wykonany. Krok 3. Możesz łatwo wykonać krok 3 za pomocą pakietu Cast SDK.

Przykładowa aplikacja MediaViewController odróżnia odtwarzanie lokalne od odtwarzania zdalnego za pomocą tego wyliczenia:

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

private var playbackMode = PlaybackMode.none

Z tego ćwiczenia z programowania nie musisz dokładnie wiedzieć, jak działa przykładowa logika odtwarzacza. Pamiętaj, że trzeba zmodyfikować odtwarzacz multimediów w aplikacji, aby w podobny sposób rozpoznawał 2 miejsca odtwarzania.

W tej chwili lokalny odtwarzacz jest zawsze w lokalnym stanie odtwarzania, bo nie wie jeszcze nic o stanach przesyłania. Musimy zaktualizować interfejs na podstawie przejść stanów, które mają miejsce w ramach platformy Cast. Jeśli na przykład zaczniemy przesyłać treści, musimy zatrzymać odtwarzanie na urządzeniu lokalnym i wyłączyć niektóre elementy sterujące. Podobnie, jeśli zakończymy przesyłanie w tym kontrolerze widoku, będziemy musieli przełączyć się na odtwarzanie lokalne. Aby to uwzględnić, musimy nasłuchiwać różnych zdarzeń generowanych przez platformę Cast.

Zarządzanie sesją przesyłania

W przypadku platformy przesyłania sesja przesyłania obejmuje łączenie się z urządzeniem, uruchamianie (lub dołączanie), nawiązywanie połączenia z aplikacją odbiornika oraz w razie potrzeby inicjowanie kanału sterowania multimediami. Kanał sterowania multimediami to sposób, w jaki platforma Cast wysyła i odbiera wiadomości z odbiornika.

Sesja przesyłania rozpocznie się automatycznie, gdy użytkownik wybierze urządzenie, klikając przycisk Cast, i zatrzyma się automatycznie, gdy użytkownik się rozłączy. Ponowne łączenie z sesją odbiornika z powodu problemów z siecią jest również automatycznie obsługiwane przez platformę Cast.

Sesje przesyłania zarządza aplikacja GCKSessionManager, do której można uzyskać dostęp w aplikacji GCKCastContext.sharedInstance().sessionManager. Wywołania zwrotne GCKSessionManagerListener mogą służyć do monitorowania zdarzeń sesji, takich jak utworzenie, zawieszenie, wznowienie i zakończenie.

Najpierw musimy zarejestrować detektor sesji i zainicjować kilka zmiennych:

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()
  }

  ...
}

W przypadku MediaViewController będziemy Cię informować o połączeniu lub rozłączeniu z urządzeniem przesyłającym, aby umożliwić przełączenie się na lokalny odtwarzacz lub z niego. Pamiętaj, że połączenie może zostać zakłócone nie tylko przez wystąpienie aplikacji uruchomionej na urządzeniu mobilnym, ale również przez inne wystąpienie Twojej (lub innej) aplikacji uruchomionej na innym urządzeniu mobilnym.

Bieżąca aktywna sesja jest dostępna jako GCKCastContext.sharedInstance().sessionManager.currentCastSession. Sesje są tworzone i wyłączane automatycznie w odpowiedzi na gesty użytkownika w oknach przesyłania.

Wczytuję multimedia

GCKRemoteMediaClient w pakiecie SDK Cast udostępnia zestaw wygodnych interfejsów API do zarządzania zdalnym odtwarzaniem multimediów na odbiorniku. W przypadku GCKCastSession, który obsługuje odtwarzanie multimediów, pakiet SDK utworzy automatycznie instancję GCKRemoteMediaClient. Można uzyskać do niego dostęp jako właściwość remoteMediaClient instancji GCKCastSession.

Dodaj do pliku MediaViewController.swift ten kod, aby załadować aktualnie wybrany film na odbiorniku:

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

Teraz zaktualizuj różne metody, aby używać logiki sesji przesyłania do obsługi zdalnego odtwarzania:

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
}

Teraz uruchom aplikację na urządzeniu mobilnym. Połącz się z urządzeniem przesyłającym i zacznij odtwarzać film. Film powinien się odtwarzać na odbiorniku.

7. Minikontroler

Lista kontrolna projektowania przesyłania wymaga, by wszystkie aplikacje przesyłające miały minikontroler, który był wyświetlany, gdy użytkownik opuści bieżącą stronę z treściami. Minikontroler zapewnia natychmiastowy dostęp i widoczne przypomnienie o bieżącej sesji przesyłania.

Ilustracja przedstawiająca dolną część iPhone'a z aplikacją CastVideo i ustawionym z widokiem na minikontroler

Pakiet Cast SDK zawiera pasek sterowania GCKUIMiniMediaControlsViewController, który możesz dodać do scen, w których chcesz wyświetlać trwałe elementy sterujące.

W przypadku przykładowej aplikacji użyjemy kontrolera widoku GCKUICastContainerViewController, który otacza inny kontroler widoku i dodaje u dołu przycisk GCKUIMiniMediaControlsViewController.

Zmodyfikuj plik AppDelegate.swift i w metodzie if useCastContainerViewController dodaj ten kod dla warunku if useCastContainerViewController:

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()
  ...
}

Dodaj tę właściwość oraz metody setter/getter, aby kontrolować widoczność mini kontrolera (użyjemy ich w późniejszej sekcji):

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

Uruchom aplikację i prześlij film. Gdy odtwarzanie rozpocznie się na odbiorniku, na dole każdej sceny powinien pojawić się minikontroler. Możesz sterować zdalnym odtwarzaniem za pomocą minikontrolera. Jeśli przechodzisz między aktywnością związaną z przeglądaniem a aktywnością lokalnego odtwarzacza, stan minikontrolera powinien być zsynchronizowany ze stanem odtwarzania multimediów na odbiorniku.

8. Nakładka wprowadzająca

Lista kontrolna dotycząca projektowania w Google Cast wymaga, by aplikacja nadawcy przedstawiła przycisk przesyłania dotychczasowym użytkownikom i poinformowała ich, że obsługuje ona przesyłanie, a także pomaga użytkownikom, którzy dopiero zaczynają korzystać z Google Cast.

Ilustracja przedstawiająca iPhone'a z aplikacją CastVideo z nakładką z przyciskiem Cast, z wyróżnionym przyciskiem Cast i wyświetlaniem komunikatu „Dotknij, by przesłać multimedia na telewizor i głośniki”.

Klasa GCKCastContext zawiera metodę presentCastInstructionsViewControllerOnce, której można użyć do wyróżnienia przycisku Cast, gdy zostanie on wyświetlony użytkownikom po raz pierwszy. Dodaj do MediaViewController.swift i MediaTableViewController.swift ten kod:

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

Uruchom ją na urządzeniu mobilnym, a pojawi się nakładka wprowadzająca.

9. Rozwinięty kontroler

Lista kontrolna projektu Google Cast wymaga, by aplikacja wysyłająca dostarczała rozwinięty kontroler do przesyłanych multimediów. Rozwinięty kontroler to pełnoekranowa wersja minikontrolera.

Ilustracja przedstawiająca iPhone'a z aplikacją CastVideo, który odtwarza film z rozwiniętym kontrolerem widocznym u dołu

Rozwinięty kontroler umożliwia oglądanie na pełnym ekranie, co zapewnia pełną kontrolę nad zdalnym odtwarzaniem multimediów. Ten widok powinien umożliwiać aplikacji przesyłającej zarządzanie wszystkimi aspektami sesji przesyłania, którymi można zarządzać. Wyjątkiem jest regulacja głośności odbiornika i cykl życia sesji (połączenie/zatrzymanie przesyłania). Znajdziesz tu też informacje o stanie sesji multimediów (grafika, tytuł, podtytuł itp.).

Funkcjonalność tego widoku jest implementowana przez klasę GCKUIExpandedMediaControlsViewController.

Najpierw musisz włączyć domyślny rozszerzony kontroler w kontekście przesyłania. Zmień AppDelegate.swift, aby włączyć domyślny rozwinięty kontroler:

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

Dodaj do MediaViewController.swift ten kod, aby wczytać rozwinięty kontroler, gdy użytkownik rozpocznie przesyłanie filmu:

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

Rozwinięty kontroler uruchomi się też automatycznie, gdy użytkownik naciśnie minikontroler.

Uruchom aplikację i prześlij film. Zobaczysz rozwinięty kontroler. Wróć do listy filmów, a gdy klikniesz minikontroler, rozwinięty kontroler zostanie ponownie załadowany.

10. Dodawanie obsługi Cast Connect

Biblioteka Cast Connect pozwala dotychczasowym aplikacjom nadawcy komunikować się z aplikacjami na Androida TV przy użyciu protokołu Cast. Cast Connect opiera się na infrastrukturze Cast, a aplikacja na Androida TV jest odbiornikiem.

Zależności

Sprawdź, czy w elemencie Podfile google-cast-sdk wskazuje wartość 4.4.8 lub wyższą, jak pokazano poniżej. Jeśli plik został zmodyfikowany, uruchom polecenie pod update w konsoli, aby zsynchronizować tę zmianę z projektem.

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

GCKLaunchOptions

Aby uruchomić aplikację na Androida TV (znaną też jako odbiornik Androida), w obiekcie GCKLaunchOptions trzeba ustawić flagę androidReceiverCompatible na „true”. Ten obiekt GCKLaunchOptions określa sposób uruchamiania odbiorcy i jest przekazywany do funkcji GCKCastOptions ustawianych we współdzielonej instancji za pomocą GCKCastContext.setSharedInstanceWith.

Dodaj do AppDelegate.swift te wiersze:

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

GCKCastContext.setSharedInstanceWith(options)

Ustaw dane logowania do uruchamiania

Po stronie nadawcy możesz określić, kto dołącza do sesji, określając GCKCredentialsData. credentials to ciąg znaków, który może być zdefiniowany przez użytkownika, o ile tylko aplikacja ATV go rozumie. Identyfikator GCKCredentialsData jest przekazywany do aplikacji na Androida TV tylko podczas uruchamiania lub dołączania do niej. Jeśli ustawisz je ponownie po nawiązaniu połączenia, nie zostanie ono przekazane do aplikacji na Androida TV.

Aby można było skonfigurować dane logowania do uruchomienia, parametr GCKCredentialsData musi być zdefiniowany w dowolnym momencie po ustawieniu GCKLaunchOptions. Aby to zademonstrować, dodajmy logikę do przycisku Dane uwierzytelniające, która wyznaczy dane logowania, które będą przekazywane po rozpoczęciu sesji. Dodaj do MediaTableViewController.swift ten kod:

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

Ustaw dane logowania przy wczytywaniu żądania

Aby umożliwić obsługę polecenia credentials zarówno w aplikacji internetowej, jak i w aplikacji odbiornika Android TV, dodaj ten kod do klasy MediaTableViewController.swift w funkcji loadSelectedItem:

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

W zależności od aplikacji odbiorcy, do której wysyłane są treści, pakiet SDK automatycznie zastosuje podane powyżej dane logowania do bieżącej sesji.

Testuję Cast Connect

Instalowanie pliku APK Android TV na Chromecastzie z Google TV

  1. Znajdź adres IP urządzenia z Androidem TV. Zwykle ta opcja jest dostępna w sekcji Ustawienia > Sieć i Internet > Nazwa sieci, z którą połączone jest urządzenie. Po prawej stronie zobaczysz szczegóły i adres IP urządzenia w sieci.
  2. Użyj adresu IP, by połączyć się z urządzeniem przez ADB:
$ adb connect <device_ip_address>:5555
  1. W oknie terminala przejdź do folderu najwyższego poziomu z przykładowymi ćwiczeniami z programowania pobranymi na początku tego ćwiczenia. Na przykład:
$ cd Desktop/ios_codelab_src
  1. Zainstaluj plik .apk z tego folderu w Androidzie TV, uruchamiając polecenie:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. Aplikacja Przesyłaj filmy powinna być teraz widoczna w menu Twoje aplikacje na urządzeniu z Androidem TV.
  2. Gdy aplikacja będzie gotowa, skompiluj ją i uruchom w emulatorze lub na urządzeniu mobilnym. Po nawiązaniu sesji przesyłania na urządzeniu z Androidem TV powinna być na nim uruchomiona aplikacja Odbiornik Androida. W przypadku odtwarzania filmu od nadawcy na urządzeniu mobilnym z iOS powinno się ono uruchomić w odbiorniku Android TV i umożliwić sterowanie odtwarzaniem za pomocą pilota do urządzenia z Androidem TV.

11. Dostosuj widżety Cast

Zdarzenie inicjujące

Zacznij od folderu App-Done. Dodaj ten kod do metody applicationDidFinishLaunchingWithOptions w pliku AppDelegate.swift.

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

Gdy skończysz stosować co najmniej 1 dostosowanie, jak opisano w pozostałej części tego ćwiczenia z programowania, zatwierdź style, wywołując ten kod

styler.apply()

Dostosowywanie widoków przesyłania

Możesz dostosowywać wszystkie widoki zarządzane przez platformę Cast Application Framework, korzystając z domyślnych wytycznych dotyczących stylów. Zmieńmy na przykład kolor zabarwienia ikony.

styler.castViews.iconTintColor = .lightGray

W razie potrzeby możesz zastąpić wartości domyślne na poszczególnych ekranach. Możesz na przykład zastąpić kolor LightGrayColor kolorem ikony tylko w rozwiniętym kontrolerze multimediów.

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

Zmiana kolorów

Możesz dostosować kolor tła dla wszystkich widoków (lub pojedynczo dla każdego widoku). Ten kod ustawia kolor tła na niebieskim we wszystkich widokach środowiska Cast Application Framework.

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

Zmiana czcionek

Możesz dostosować czcionki dla różnych etykiet widocznych w widokach przesyłania. Ustawmy wszystkie czcionki na „Courier-Oblique” do celów ilustracyjnych.

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)

Zmiana domyślnych obrazów przycisków

Dodaj własne obrazy niestandardowe do projektu i przypisz je do przycisków, aby określić ich styl.

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

Zmienianie motywu przycisku Cast

Widżety Cast możesz też motywować, korzystając z protokołu UIAppearance Protocol. Następujący motyw kodu zawiera element GCKUICastButton we wszystkich wyświetlanych widokach:

GCKUICastButton.appearance().tintColor = UIColor.gray

12. Gratulacje

Wiesz już, jak włączyć przesyłanie aplikacji wideo, korzystając z widżetów Cast SDK na iOS.

Więcej informacji znajdziesz w przewodniku dla programistów iOS Sender.