Włączanie przesyłania w aplikacji na iOS

1. Opis

Logo Google Cast

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

Co to jest Google Cast?

Google Cast pozwala użytkownikom przesyłać 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.

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

Dostępna jest lista kontrolna projektu Google Cast, dzięki której korzystanie z Cast jest proste i przewidywalne na wszystkich obsługiwanych platformach.

Co będziemy tworzyć?

Po ukończeniu tego ćwiczenia z programowania otrzymasz aplikację wideo na iOS, która będzie mogła przesyłać filmy na urządzenie Google Cast.

Czego się nauczysz

  • Jak dodać pakiet SDK Google Cast do przykładowej aplikacji wideo
  • Jak dodać przycisk Cast pozwalający wybrać urządzenie Google Cast.
  • Jak połączyć się z urządzeniem przesyłającym i uruchomić odbiornik multimediów.
  • Jak przesłać film.
  • Jak dodać minikontroler Cast do aplikacji
  • Jak dodać rozszerzony kontroler.
  • Jak dodać nakładkę początkową
  • Jak dostosować widżety Cast
  • Jak zintegrować Cast Connect

Czego potrzebujesz

  • Najnowszy Xcode.
  • Jedno urządzenie mobilne z systemem iOS 9 lub nowszym (albo symulator Xcode).
  • Kabel USB do transmisji danych, aby podłączyć urządzenie mobilne do komputera programisty (jeśli używasz urządzenia).
  • Urządzenie przesyłające Google Cast, takie jak Chromecast czy Android TV, 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łej części Codelabs jest on opcjonalny. Jeśli go nie masz, możesz pominąć krok Dodawanie obsługi Cast Connect pod koniec tego samouczka.

Funkcja

  • Aby z nich korzystać, musisz mieć wiedzę z zakresu programowania na iOS.
  • Potrzebna będzie też wcześniejsza wiedza o oglądaniu telewizji :)

Jak będziesz korzystać z tego samouczka?

Przeczytaj tylko do końca Przeczytaj go i wykonaj ćwiczenia

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

Początkujący Średni Średni

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

Początkujący Średni Średni

2. Pobieranie przykładowego kodu

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

i rozpakuj pobrany plik ZIP.

3. Uruchamianie przykładowej aplikacji

Logo Apple iOS

Zobaczmy najpierw, jak wygląda kompletna przykładowa aplikacja. Ta aplikacja to podstawowy odtwarzacz wideo. Użytkownik może wybrać film z listy, a potem odtworzyć go lokalnie na urządzeniu lub przesłać na urządzenie przesyłające Google Cast.

Po pobraniu kodu możesz wykonać poniższe instrukcje, aby dowiedzieć się, jak otworzyć i uruchomić gotową aplikację próbną w Xcode:

Najczęstsze pytania

Konfiguracja CocoaPods

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

sudo gem install cocoapods

Jeśli masz problemy, 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 z przykładowym kodem.

Uruchom aplikację

Wybierz wartość docelową i symulator, a potem uruchom aplikację:

Pasek narzędzi symulatora aplikacji XCode

Aplikacja wideo powinna pojawić się po kilku sekundach.

Gdy pojawi się powiadomienie o akceptowaniu przychodzących połączeń sieciowych, nie zapomnij kliknąć „Zezwól”. Jeśli ta opcja nie zostanie zaakceptowana, ikona przesyłania się nie pojawi.

Okno z potwierdzeniem 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. Do sterowania odtwarzaniem możesz używać przycisku odtwarzania/wstrzymywania.

Wróć do listy filmów.

Minikontroler jest teraz widoczny u dołu ekranu.

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

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

Kliknij przycisk Cast, by zatrzymać przesyłanie na urządzenie Google Cast.

4. Przygotuj projekt początkowy

Ilustracja przedstawiająca iPhone'a z aplikacją Castvideos

Musimy dodać obsługę Google Cast do pobranej aplikacji startowej. Oto niektóre z terminologii związanej z Google Cast, których będziemy używać w tym ćwiczeniu z programowania:

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

Konfigurowanie projektu

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

  1. Otwórz terminal i przejdź do katalogu ćwiczeń z programowania.
  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 z przykładowym kodem.

Projektowanie aplikacji

Aplikacja pobiera listę filmów ze zdalnego serwera WWW i udostępnia listę, którą użytkownik może przeglądać. Użytkownicy mogą wybrać film, aby zobaczyć szczegóły, lub odtworzyć go lokalnie na urządzeniu mobilnym.

Aplikacja składa się z 2 kontrolerów widoku głównego: MediaTableViewController i MediaViewController.

MediaTableViewController

Ten UITableViewController wyświetla listę filmów z instancji MediaListModel. Lista filmów i powiązane z nimi metadane są przechowywane na serwerze zdalnym jako plik 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 wystąpienie MediaListModel, a następnie rejestruje się jako MediaListModelDelegate, aby otrzymywać informacje o pobraniu metadanych multimediów, aby wczytać widok tabeli.

Użytkownikowi wyświetla się lista miniatur filmów wraz z krótkim opisem każdego z nich. Po wybraniu elementu odpowiednie MediaItem jest przekazywane do funkcji MediaViewController.

MediaViewController

Kontroler widoku wyświetla metadane konkretnego filmu i umożliwia użytkownikowi odtwarzanie filmu lokalnie na urządzeniu mobilnym.

Kontroler widoku zawiera obiekt LocalPlayerView, niektóre elementy sterujące multimediami oraz obszar tekstowy do wyświetlania opisu wybranego filmu. Odtwarzacz zakrywa górną część ekranu, pozostawiając miejsce na szczegółowy opis filmu. Użytkownik może włączyć lub wstrzymać odtwarzanie albo przewinąć film do momentu lokalnego odtwarzania.

Najczęstsze pytania

5. Dodawanie przycisku Cast

Grafika przedstawiająca górną trzecią część iPhone'a z aplikacją Castvideos, w tym przycisk Cast w prawym górnym rogu

Aplikacja obsługująca Cast wyświetla przycisk Cast na każdym kontrolerze widoku. Gdy klikniesz przycisk Cast, pojawi 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 rozpocznie lub wznowi odtwarzanie na tym urządzeniu. W każdej chwili podczas 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 ich odłączenia na dowolnym ekranie aplikacji zgodnie z opisem na liście kontrolnej projektowania 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 przesyłania zawiera globalny obiekt typu singleton GCKCastContext, który koordynuje wszystkie działania platformy. Ten obiekt musi zostać zainicjowany na wczesnym etapie cyklu życia aplikacji, zwykle za pomocą metody application(_:didFinishLaunchingWithOptions:) delegata aplikacji, tak aby automatyczne wznowienie sesji po ponownym uruchomieniu aplikacji nadawcy mogło zostać prawidłowo aktywowane i uruchomione skanowanie w poszukiwaniu urządzeń.

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

Metoda application(_:didFinishLaunchingWithOptions:) pozwala też skonfigurować przedstawiciela logowania, który będzie odbierać komunikaty logowania z platformy przesyłania. Przydają się one podczas debugowania i rozwiązywania problemów.

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

Dodaj do pliku AppDelegate.swift ten kod, aby zainicjować GCKCastContext przy użyciu identyfikatora aplikacji podanego przez użytkownika jako domyślnego, i dodać rejestrator na potrzeby 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 Cast, by umożliwić użytkownikowi wybranie urządzenia przesyłającego. Pakiet SDK Cast udostępnia komponent przycisku przesyłania o nazwie GCKUICastButton jako podklasę UIButton. Można go dodać do paska tytułu aplikacji za pomocą kodu UIBarButtonItem. Musimy dodać przycisk Cast zarówno do aplikacji MediaTableViewController, jak i do aplikacji MediaViewController.

Dodaj ten kod do MediaTableViewController.swift i 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)

    ...
  }
  ...
}

Następnie dodaj ten kod do urządzenia 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)

    ...
  }
  ...
}

Teraz uruchom aplikację. Na pasku nawigacyjnym aplikacji powinien być widoczny przycisk Cast, a po jego kliknięciu pojawi się lista urządzeń przesyłających w Twojej sieci lokalnej. Wykrywaniem urządzeń zarządza automatycznie GCKCastContext. Wybierz urządzenie przesyłające, a przykładowa aplikacja odbiornika zostanie na nim załadowana. Możesz przechodzić między aktywnością przeglądania a aktywnością lokalnego odtwarzacza, a stan przycisku przesyłania jest zsynchronizowany.

Nie obsługujemy odtwarzania multimediów, więc nie możesz jeszcze odtwarzać filmów na urządzeniu przesyłającym. Kliknij przycisk Cast, by zatrzymać przesyłanie.

6. Przesyłanie treści wideo

Grafika przedstawiająca iPhone'a z aplikacją Castvideos, która przedstawia szczegóły konkretnego filmu („Tears of Steel”). Na dole znajduje się miniodtwarzacz

Udostępnimy przykładową aplikację, aby umożliwić zdalne odtwarzanie filmów na urządzeniach przesyłających. Aby to zrobić, musimy wsłuchiwać się w różne zdarzenia generowane przez platformę przesyłania.

Przesyłanie multimediów

Ogólnie, jeśli chcesz odtworzyć multimedia na urządzeniu przesyłającym, musisz wykonać te czynności:

  1. Z pakietu SDK Cast utwórz obiekt GCKMediaInformation, który będzie modelował element multimedialny.
  2. Użytkownik łączy się z urządzeniem przesyłającym, by uruchomić aplikację odbiornika.
  3. Wczytaj obiekt GCKMediaInformation do odbiornika i odtwórz treści.
  4. Śledzenie stanu multimediów.
  5. Wysyłaj polecenia odtwarzania do odbiornika na podstawie interakcji użytkownika.

Krok 1 polega na zmapowaniu jednego obiektu na drugi. Pakiet GCKMediaInformation jest zrozumiały dla pakietu SDK Cast, a MediaItem to kodowanie elementu multimedialnego w naszej aplikacji. Możemy z łatwością zmapować obiekt MediaItem na obiekt GCKMediaInformation. Krok 2 z poprzedniej sekcji został już wykonany. Krok 3 można łatwo zrobić za pomocą pakietu SDK Cast.

W przykładowej aplikacji MediaViewController można już odróżnić odtwarzanie lokalne i zdalne przy użyciu tej wyliczenia:

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

private var playbackMode = PlaybackMode.none

W tym ćwiczeniu z programowania nie ma potrzeby dokładnego zrozumienia, jak działa cała przykładowa logika odtwarzacza. Pamiętaj, że konieczne będzie zmodyfikowanie odtwarzacza w aplikacji, aby rozpoznawał 2 miejsca odtwarzania w podobny sposób.

W tej chwili lokalny odtwarzacz jest zawsze w lokalnym stanie odtwarzania, ponieważ nie ma jeszcze żadnych informacji o stanach przesyłania. Musimy aktualizować interfejs odpowiednio do zmian stanu w platformie przesyłania. Jeśli na przykład rozpocznie się przesyłanie, musimy zatrzymać odtwarzanie lokalne i wyłączyć niektóre elementy sterujące. Podobnie jeśli zatrzymujemy przesyłanie, gdy korzystamy z kontrolera widoku, musimy przejść na odtwarzanie lokalne. Aby to zrobić, musimy nasłuchiwać różnych zdarzeń generowanych przez platformę przesyłania.

Zarządzanie sesjami przesyłania

W przypadku platformy przesyłania sesja obejmuje etapy połączenia z urządzeniem, uruchamiania (lub dołączania), łączenia się z aplikacją odbierającą i inicjowania kanału sterowania multimediami (jeśli ma to zastosowanie). Kanał sterowania multimediami służy do wysyłania i odbierania wiadomości przez platformę przesyłania z odtwarzacza.

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

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

Najpierw musimy zarejestrować nasz 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 MediaViewController kraju będziemy Cię informować o połączeniu lub przerwaniu połączenia z urządzeniem przesyłającym, abyśmy mogli przełączyć się na lokalny odtwarzacz lub z niego zrezygnować. Pamiętaj, że łączność może zostać zakłócona nie tylko przez wystąpienie aplikacji uruchomionej na urządzeniu mobilnym, ale także przez inne wystąpienie tej aplikacji uruchomionej na innym urządzeniu mobilnym.

Aktualnie aktywna sesja jest dostępna jako GCKCastContext.sharedInstance().sessionManager.currentCastSession. Sesje są tworzone i usuwane automatycznie w odpowiedzi na gesty użytkownika wyświetlane 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 automatycznie utworzy wystąpienie elementu GCKRemoteMediaClient. Jest dostępna jako właściwość remoteMediaClient instancji GCKCastSession.

Dodaj do aplikacji MediaViewController.swift ten kod, aby wczytać 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 istniejące metody, aby obsługiwały zdalne odtwarzanie, korzystając z logiki sesji przesyłania:

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 być odtwarzany na odbiorniku.

7. Minikontroler

Lista kontrolna projektu przesyłania wymaga, aby we wszystkich aplikacjach Cast był dostępny minikontroler, gdy użytkownik opuści stronę, na której obecnie znajdują się treści. Minikontroler zapewnia natychmiastowy dostęp i wyświetla przypomnienie o bieżącej sesji przesyłania.

Grafika przedstawiająca dolną część iPhone'a z aplikacją Castvideos, która przedstawia minikontroler

Pakiet SDK Cast zawiera pasek sterowania GCKUIMiniMediaControlsViewController, który można dodać do scen, w których mają być widoczne stałe elementy sterujące.

W przykładowej aplikacji użyjemy elementu GCKUICastContainerViewController, który pakuje inny kontroler widoku i dodaje GCKUIMiniMediaControlsViewController na dole.

Zmodyfikuj plik AppDelegate.swift i dodaj ten kod warunku if useCastContainerViewController, korzystając z tej metody:

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ść i metodę szeregowania, aby kontrolować widoczność minikontrolera (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 rozpocznie się odtwarzanie na odbiorniku, u dołu każdej sceny powinien pojawić się minikontroler. Odtwarzaniem zdalnym możesz sterować za pomocą minikontrolera. Jeśli przełączasz się między aktywnością przeglądania a aktywnością w odtwarzaczu lokalnym, stan minikontrolera powinien być zsynchronizowany ze stanem odtwarzania multimediów przez odbiornik.

8. Nakładka wprowadzająca

Lista kontrolna projektu Google Cast wymaga, aby aplikacja nadawcy poinformowała użytkowników o tym, że przycisk Cast może przesyłać treści, i poinformuje ich, że aplikacja nadawcy obsługuje przesyłanie, a także pomaga użytkownikom, którzy dopiero zaczynają korzystać z Google Cast.

Ilustracja przedstawiająca iPhone'a z aplikacją Castvideos z nakładką przycisku Cast, wyróżnionym przyciskiem Cast i komunikatem „Dotknij, aby przesyłać multimedia na telewizor i głośniki”

Klasa GCKCastContext ma metodę presentCastInstructionsViewControllerOnce, której można użyć do podświetlenia przycisku przesyłania, gdy zostanie on wyświetlony użytkownikom. Dodaj ten kod do MediaViewController.swift i 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)
  }
}

Po uruchomieniu aplikacji na urządzeniu mobilnym powinna wyświetlić się nakładka z wprowadzeniem.

9. Rozwinięty kontroler

Lista kontrolna projektu Google Cast wymaga, aby aplikacja nadawcy zapewniła rozszerzony kontroler do przesyłanych multimediów. Rozwinięty kontroler to pełnoekranowa wersja minikontrolera.

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

Rozwinięty kontroler to widok pełnoekranowy, który 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 (łączenie/zatrzymywanie przesyłania). Znajdziesz w nim też wszystkie informacje o stanie sesji multimediów (grafiki, tytuł, podtytuł itd.).

Funkcje tego widoku są implementowane przez klasę GCKUIExpandedMediaControlsViewController.

Najpierw musisz włączyć domyślny rozwinięty kontroler w kontekście przesyłania. Zmodyfikuj 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 pliku MediaViewController.swift ten kod, aby wczytywać rozwinięty kontroler, gdy użytkownik rozpocznie przesyłanie filmu:

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

Rozwinięty kontroler zostanie uruchomiony automatycznie, gdy użytkownik naciśnie minikontroler.

Uruchom aplikację i prześlij film. Powinien być widoczny rozwinięty kontroler. Wróć do listy filmów. Gdy klikniesz minikontroler, rozwinięty kontroler zostanie ponownie wczytany.

10. Dodawanie obsługi Cast Connect

Biblioteka Cast Connect umożliwia dotychczasowym aplikacjom nadawcy komunikację z aplikacjami na Androida TV przez protokół Cast. Cast Connect działa na bazie infrastruktury przesyłania, a aplikacja na Androida TV działa jako odbiornik.

Zależności

Upewnij się, że w usłudze Podfile pole google-cast-sdk wskazuje wartość 4.4.8 lub wyższą, jak podano poniżej. Jeśli plik został zmodyfikowany, uruchom polecenie pod update w konsoli, aby zsynchronizować zmiany ze swoim projektem.

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

GCKLaunchOptions

Aby uruchomić aplikację Android TV (określaną też jako odbiornik Androida), musimy ustawić w obiekcie GCKLaunchOptions flagę androidReceiverCompatible na true. Ten obiekt GCKLaunchOptions określa sposób uruchamiania odbiornika i jest przekazywany do obiektów GCKCastOptions, które są ustawiane w udostępnionej instancji za pomocą GCKCastContext.setSharedInstanceWith.

Dodaj do dokumentu 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ć GCKCredentialsData, aby reprezentował, kto dołącza do sesji. Ciąg credentials jest ciągiem zdefiniowanym przez użytkownika, pod warunkiem że jest on zrozumiały dla aplikacji ATV. Element GCKCredentialsData jest przekazywany do aplikacji na Androida TV tylko w czasie jej uruchomienia lub dołączenia. Jeśli skonfigurujesz je ponownie po połączeniu z siecią, nie zostaną one przekazane do aplikacji na Androida TV.

Aby ustawić dane logowania do uruchamiania, w dowolnym momencie po skonfigurowaniu danych logowania GCKLaunchOptions musi być zdefiniowane GCKCredentialsData. Aby to zademonstrować, dodajmy funkcje logiczne przycisku Creds pozwalające na ustawienie danych logowania, które będą przekazywane po rozpoczęciu sesji. Dodaj do karty 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 w żądaniu obciążenia

Aby obsługiwać credentials w aplikacjach internetowych i na odbiornikach Android TV, dodaj w klasie MediaTableViewController.swift ten kod w sekcji loadSelectedItem:

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

W zależności od aplikacji odbiorcy, do której nadawca przesyła treści, pakiet SDK automatycznie zastosuje podane wyżej dane logowania do trwającej sesji.

Testowanie Cast Connect

Instalowanie pakietu APK na Androida TV na urządzeniu Chromecast z Google TV

  1. Znajdź adres IP swojego urządzenia z Androidem TV. Zwykle znajdziesz je w sekcji Ustawienia > Sieć i internet > (nazwa sieci, z którą połączone jest urządzenie). Po prawej stronie wyświetlą się szczegóły oraz adres IP urządzenia połączony z siecią.
  2. Użyj adresu IP urządzenia, aby połączyć się z nim przez ADB za pomocą terminala:
$ adb connect <device_ip_address>:5555
  1. W oknie terminala przejdź do folderu najwyższego poziomu na przykłady ćwiczeń z programowania pobrane na początku tego ćwiczenia z programowania. Na przykład:
$ cd Desktop/ios_codelab_src
  1. Zainstaluj plik .apk w tym folderze na Androidzie TV, uruchamiając polecenie:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. Aplikacja powinna być teraz widoczna pod nazwą Przesyłaj filmy w menu Twoje aplikacje na urządzeniu z Androidem TV.
  2. Po zakończeniu skompiluj i uruchom aplikację w emulatorze lub na urządzeniu mobilnym. Po rozpoczęciu sesji przesyłania na urządzeniu z Androidem TV aplikacja powinna uruchomić się na tym urządzeniu. Odtworzenie filmu od nadawcy z urządzenia z iOS powinno go uruchomić w odbiorniku Androida 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 Gotowe aplikacji. Dodaj poniższy kod do metody applicationDidFinishLaunchingWithOptions w pliku AppDelegate.swift.

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

Po wprowadzeniu co najmniej jednego dostosowania zgodnie z opisem w pozostałej części tego ćwiczenia z programowania zatwierdź style, wywołując poniższy kod.

styler.apply()

Dostosowywanie widoków Cast

Możesz dostosować wszystkie widoki, którymi zarządza platforma aplikacji Cast, korzystając z domyślnych wskazówek dotyczących stylu w różnych widokach. Zmieńmy na przykład kolor odcienia ikony.

styler.castViews.iconTintColor = .lightGray

W razie potrzeby możesz zastąpić ustawienia domyślne dla poszczególnych ekranów. Możesz na przykład zastąpić kolor jasnoszarego koloru ikony w przypadku rozwiniętego kontrolera multimediów.

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

Zmiana kolorów

Możesz dostosować kolor tła dla wszystkich widoków (lub osobno dla każdego widoku). Poniższy kod ustawia kolor tła na niebieski dla wszystkich widoków dostępnych w platformie 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. W celach ilustracyjnych użyjemy wszystkich czcionek jako „Courier-Oblique”.

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 do projektu własne obrazy niestandardowe i przypisz je do przycisków, aby ustalić 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ż umieszczać w motywach tematycznych, korzystając z protokołu UIPreview Protocol. Ten kod tworzy motywy GCKUICastButton we wszystkich wyświetleniach:

GCKUICastButton.appearance().tintColor = UIColor.gray

12. Gratulacje

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

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