支援 iOS 應用程式投放功能

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

1. 總覽

Google Cast 標誌

本程式碼研究室會說明如何修改現有的 iOS 影片應用程式,以便在支援 Google Cast 的裝置投放內容。

什麼是 Google Cast?

Google Cast 可讓使用者將行動裝置的內容投放到電視上。之後,使用者就能利用行動裝置做為電視媒體的遙控器。

Google Cast SDK 可讓你擴充應用程式,以控制支援 Google Cast 的裝置 (例如電視或音響系統)。Cast SDK 可讓您根據 Google Cast 設計檢查清單新增必要的 UI 元件。

提供 Google Cast 設計檢查清單,以便在所有支援的平台上提供簡單且可預測的 Cast 使用者體驗。

我們要建構的是什麼?

完成本程式碼研究室後,您將能夠使用可將影片投放到 Google Cast 裝置的 iOS 影片應用程式。

課程內容

  • 如何將 Google Cast SDK 新增至影片樣本應用程式。
  • 如何新增 Google Cast 裝置的投放按鈕。
  • 如何連線至投放裝置並啟動媒體接收器。
  • 如何投放影片。
  • 如何將 Cast 迷你控制器新增至應用程式。
  • 如何新增展開的控制器。
  • 如何提供簡介重疊廣告。
  • 如何自訂投放小工具。
  • 如何整合 Cast Connect

軟硬體需求

  • 最新的 Xcode
  • 一部搭載 iOS 9 以上版本的裝置 (或 Xcode 模擬工具)。
  • 一向用於將行動裝置連接至開發電腦的 USB 資料傳輸線 (如果使用裝置)。
  • Google Cast 裝置,例如已設定網路連線的 ChromecastAndroid TV
  • 具備 HDMI 輸入端的電視或螢幕。
  • 必須使用 Chromecast (支援 Google TV) 才能測試 Cast Connect 整合作業,但在程式碼研究室的其餘部分則為選用功能。如果沒有電視,歡迎在本教學課程結束時略過新增 Cast Connect 支援步驟。

功能

  • 您必須先具備之前的 iOS 開發知識。
  • 您也需要具備先前觀看電視節目的知識 :)

您如何使用這個教學課程?

唯讀閱讀 閱讀並完成練習

針對建立 iOS 應用程式的體驗,您會給予什麼評價?

新手 中級 專業知識

針對觀看電視的體驗,你會給予什麼評價?

新手 中級 專業知識

2. 取得程式碼範例

您可以將所有程式碼範例下載至電腦上...

將下載的 ZIP 檔案解壓縮。

3. 執行範例應用程式

Apple iOS 標誌

首先,請查看完成的範例應用程式外觀。這款應用程式是基本的影片播放器。使用者可以選取清單中的影片,然後在裝置上在裝置上播放影片,或將其投放到 Google Cast 裝置。

下載程式碼後,該操作說明將說明如何在 Xcode 中開啟並執行已完成的範例應用程式:

常見問題

CocoaPods 設定

如要設定 CocoaPods,請在 macOS 中使用預設的 Ruby 進行安裝:

sudo gem install cocoapods

如有任何問題,請參閱官方說明文件,下載並安裝依附元件管理員。

專案設定

  1. 前往終端機,然後前往程式碼研究室目錄。
  2. 從 Podfile 安裝依附元件。
cd app-done
pod update
pod install
  1. 開啟 Xcode,然後選取 [開啟其他專案...]
  2. 請在程式碼範例資料夾中的 資料夾圖示app-done 目錄中選取 CastVideos-ios.xcworkspace 檔案。

執行應用程式

選取目標和模擬工具,然後執行應用程式:

XCode 應用程式模擬工具工具列

影片應用程式應該會在幾秒後顯示。

請務必在顯示接受網路連線的網路通知時,按一下「允許」並允許。如果不接受這個選項,則不會顯示「投放」圖示。

確認接受傳入網路連線的權限確認對話方塊

按一下「投放」按鈕,然後選取你的 Google Cast 裝置。

選取影片,然後按一下播放按鈕。

影片就會開始在 Google Cast 裝置上播放。

系統會顯示已展開的控制器。您可以使用播放/暫停按鈕控製播放功能。

返回影片清單。

畫面底部隨即會顯示迷你控制器。

插圖:iPhone 執行 CastVideo 應用程式,底部有迷你控制器

按一下迷你控制器中的暫停按鈕,以暫停接收端上的影片。按一下迷你控制器中的播放按鈕,即可繼續播放影片。

按一下「投放」按鈕即可停止投放內容到 Google Cast 裝置。

4. 準備起始專案

插圖:iPhone 執行 CastVideo 應用程式

我們需要針對你下載的啟動應用程式新增 Google Cast 支援。我們在本程式碼研究室中會使用以下 Google Cast 術語:

  • 寄件者應用程式位於行動裝置或筆記型電腦上執行
  • 接收端應用程式會在 Google Cast 裝置上執行。

專案設定

現在,您已準備好使用 Xcode 在頂端範例專案上進行建構:

  1. 前往終端機,然後前往程式碼研究室目錄。
  2. 從 Podfile 安裝依附元件。
cd app-start
pod update
pod install
  1. 開啟 Xcode,然後選取 [開啟其他專案...]
  2. 請在程式碼範例資料夾中的 資料夾圖示app-start 目錄中選取 CastVideos-ios.xcworkspace 檔案。

應用程式設計

應用程式會從遠端網路伺服器擷取影片清單,並提供使用者清單以供瀏覽。使用者可以選取影片來查看詳細資料,也可以在行動裝置上在本機播放影片。

這個應用程式包含兩個主要檢視畫面控制器:MediaTableViewControllerMediaViewController.

媒體表格檢視控制器

這個 UITableViewController 會顯示 MediaListModel 執行個體的影片清單。影片清單和相關中繼資料是由遠端伺服器會以 JSON 檔案代管。MediaListModel 會擷取這個 JSON 並處理,以便建立 MediaItem 物件清單。

MediaItem 物件會製作影片及其相關中繼資料的範本,例如影片標題、說明、圖片網址,以及串流網址。

MediaTableViewController 會建立 MediaListModel 執行個體,然後自行註冊為 MediaListModelDelegate 以在下載媒體中繼資料時接收通知,以便載入表格檢視。

使用者會看到影片縮圖清單,以及每部影片的簡短說明。選取項目時,系統會將對應的 MediaItem 傳遞至 MediaViewController

媒體檢視控制器

這個檢視畫面控制器會顯示特定影片的中繼資料,並允許使用者在行動裝置上播放影片。

檢視控制器會顯示 LocalPlayerView、部分媒體控制項和文字區域,以顯示所選影片的說明。播放器會覆蓋螢幕的上半部,在影片下方顯示使用者播放影片的詳細說明,方便他們播放/暫停或播放當地影片。

常見問題

5. 新增投放按鈕

顯示 iPhone 上三部執行 CastVideos 應用程式的圖示,其中右上角顯示「投放」按鈕

支援 Cast 的應用程式會在每個檢視控制器中顯示投放按鈕。按一下「投放」按鈕,即可查看使用者能選取的投放裝置清單。如果使用者在本機裝置上播放內容,請選取投放裝置,或是從該投放裝置上繼續播放內容。在投放工作階段期間,使用者隨時可以按一下「投放」按鈕,然後停止將應用程式投放到「投放」裝置。如 Google Cast 設計檢查清單所述,使用者必須能夠在應用程式的任一畫面中與 Cast 裝置連線或中斷連線,

設定

啟動專案需要的依附元件和 Xcode 設定方式與已完成的範例應用程式相同。請返回該部分,並按照上述步驟將 GoogleCast.framework 新增至開始的應用程式專案。

初始化

Cast 架構包含全域單例模式物件 GCKCastContext,負責協調架構的所有活動。此物件必須在應用程式的生命週期早期初始化,通常在應用程式委派的 application(_:didFinishLaunchingWithOptions:) 方法中,這樣寄件者應用程式重新啟動時才會自動繼續作業,因此可以正確觸發,而裝置也可以開始掃描。

初始化 GCKCastContext 時,必須提供 GCKCastOptions 物件。這個類別包含會影響架構行為的選項。最重要的是接收器應用程式 ID,可用來篩選 Cast 裝置探索結果,並在投放工作階段啟動時啟動接收器應用程式。

application(_:didFinishLaunchingWithOptions:) 方法也有助於設定記錄委派,以便從 Cast 架構接收記錄訊息。適用於偵錯和疑難排解。

自行開發支援 Cast 的應用程式時,您必須註冊為 Cast 開發人員,然後為應用程式取得應用程式 ID。在本程式碼研究室中,我們將使用範例應用程式 ID。

將下列程式碼加進 AppDelegate.swift,以便透過使用者預設值的應用程式 ID 初始化 GCKCastContext,並為 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)")
    }
  }
}

投放按鈕

GCKCastContext 已初始化,請新增「投放」按鈕,讓使用者選取投放裝置。Cast SDK 提供名為 GCKUICastButton 的投放按鈕元件,做為 UIButton 子類別。在 UIBarButtonItem 中,即可加入應用程式的標題列。我們需要將投放按鈕同時新增至 MediaTableViewControllerMediaViewController

將下列程式碼新增至 MediaTableViewController.swiftMediaViewController.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)

    ...
  }
  ...
}

接著,將下列程式碼新增到您的 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)

    ...
  }
  ...
}

接著,執行應用程式。該應用程式的導覽列應該會顯示「投放」按鈕,當你點選後,就會在區域網路上列出投放裝置。GCKCastContext 會自動管理裝置探索作業。選取投放裝置,取樣裝置應用程式隨即會在投放裝置上載入。你可以在瀏覽活動與本機播放器活動之間進行導覽,同時讓「投放」按鈕狀態保持同步。

我們尚未支援任何媒體播放功能,因此你尚未透過投放裝置播放影片。按一下「投放」按鈕即可停止投放。

6. 投放影片內容

插圖顯示 iPhone 上執行的 CastVideo 應用程式,其中顯示特定影片的詳細資料 (##39;Tears of Steel')。底部的迷你播放器

我們也會擴大範例應用程式的投放範圍,以便透過 Cast 裝置從遠端播放影片。因此必須監聽投放架構產生的各種事件。

投放媒體

整體而言,如要在投放裝置上播放媒體,必須發生以下情況:

  1. 使用 Cast SDK 建立 GCKMediaInformation 物件,藉此建立媒體項目。
  2. 使用者連線至投放裝置,以啟動接收器應用程式。
  3. GCKMediaInformation 物件載入至接收器並播放內容。
  4. 追蹤媒體狀態。
  5. 根據使用者互動將播放指令傳送至接收端。

步驟 1 會將一個物件對應至另一個物件;GCKMediaInformation 是 Cast SDK 能夠理解的內容,MediaItem 則是應用程式封裝的媒體項目封裝工具;您可以輕鬆將 MediaItem 對應至 GCKMediaInformation。我們已經按照上一節的步驟步驟 2 完成。步驟 3 可讓您輕鬆使用 Cast SDK。

範例應用程式 MediaViewController 已使用這個列舉區分本機與遠端播放:

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

private var playbackMode = PlaybackMode.none

在本程式碼研究室中,您不需要瞭解所有範例播放器邏輯的運作方式。請務必瞭解,您的應用程式的媒體播放器必須採用類似的方式修改兩個播放位置。

由於本機播放器尚未得知投放狀態,因此一律位於本機播放狀態。我們必須根據投放架構中發生的狀態轉換,更新使用者介面。舉例來說,如果我們開始投放內容,就必須停止本機播放並停用部分控制項。同樣地,當我們在這類檢視控制器中停止投放時,就必須轉換為本機播放。我們需要處理 Cast 架構產生的各種事件,才能進行處理。

投放工作階段管理

如果是投放架構,「投放」工作階段會結合連線至裝置、啟動 (或加入)、連線至接收器應用程式,以及視情況初始化媒體控制頻道的步驟。媒體控制頻道是 Cast 架構從接收端媒體播放器收發訊息的方式。

使用者選取「投放」按鈕後,系統就會自動開始投放工作階段,並在使用者中斷連線時自動停止投放工作階段。因網路問題而重新連線至接收器工作階段,也會由 Cast 架構自動處理。

投放工作階段是由 GCKSessionManager 管理,您可以透過 GCKCastContext.sharedInstance().sessionManager 存取。GCKSessionManagerListener 回呼可用於監控工作階段事件,例如建立、停權、繼續和終止。

首先,我們需要註冊工作階段監聽器並初始化部分變數:

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

  ...
}

MediaViewController 中,我們得知想要與 Cast 裝置連線或中斷連線時,可以切換使用本機播放器或與本機播放器連線。請注意,不僅在行動裝置上執行的應用程式執行個體也會中斷連線,也可能影響 (或其他) 應用程式在其他行動裝置上運作的狀況。

目前使用中的工作階段可透過 GCKCastContext.sharedInstance().sessionManager.currentCastSession 存取。系統會根據「投放」對話方塊的使用者手勢,建立並自動關閉工作階段。

正在載入媒體

在 Cast SDK 中,GCKRemoteMediaClient 提供一組便利的 API,可用於管理接收器上的遠端媒體播放。對於支援媒體播放的 GCKCastSession,SDK 會自動建立 GCKRemoteMediaClient 的執行個體。其可做為 GCKCastSession 執行個體的 remoteMediaClient 屬性。

將下列程式碼新增至 MediaViewController.swift,以便在接收器上載入目前選取的影片:

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

現在,請更新各種現有方法,以使用投放工作階段邏輯支援遠端播放:

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
}

現在,請在行動裝置上執行應用程式。連線至投放裝置,然後播放影片。您應該會在接收器中看見播放影片。

7. 迷你控制器

「投放設計檢查清單」要求所有投放應用程式都必須提供迷你控制器,讓使用者離開目前的內容頁面時才會顯示。這個迷你控制器提供目前投放工作階段的即時存取功能,以及可見的提醒。

插圖:iPhone 底部的底部顯示 Cast 應用程式,並以迷你控制器為主

Cast SDK 提供控制列 GCKUIMiniMediaControlsViewController,您可以將這個控制項新增至要顯示永久控制項的場景。

在範例應用程式中,我們將使用 GCKUICastContainerViewController,來包裝另一個檢視畫面控制器,並在底部新增 GCKUIMiniMediaControlsViewController

修改 AppDelegate.swift 檔案,並在下列方法中加入 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()
  ...
}

請新增這項屬性和 setter/getter 來控制迷你控制器的顯示方式 (我們會在後續章節中使用這些屬性):

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

執行應用程式並投放影片。在接收器上開始播放時,每個場景的底部會顯示迷你控制器。你可以使用迷你控制器控制遠端播放。如果您在瀏覽活動與本機播放器活動之間進行導覽,迷你控制器狀態應與接收器媒體播放狀態保持同步。

8. 入門重疊

Google Cast 設計檢查清單要求傳送者應用程式必須將投放按鈕導入給現有使用者,以便瞭解傳送者應用程式現在支援投放功能,也能協助使用者剛使用 Google Cast。

插圖:iPhone 執行 Cast 應用程式後,投放按鈕顯示「投放」按鈕,並醒目顯示「投放」按鈕並顯示訊息 ##99;將媒體投放到電視和揚聲器'

GCKCastContext 類別提供 presentCastInstructionsViewControllerOnce 方法,可在首次向使用者顯示時醒目顯示「投放」按鈕。將下列程式碼新增至 MediaViewController.swiftMediaTableViewController.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)
  }
}

在行動裝置上執行應用程式後,您會看見簡介重疊廣告。

9. 已展開的控制器

Google Cast 設計檢查清單要求寄件者應用程式會針對投放的媒體提供展開的控制器。展開的控制器是迷你控制器的全螢幕版本。

插圖:iPhone 執行 CastCast 應用程式,並播放影片,下方顯示展開的控制器

展開的控制器會顯示全螢幕檢視畫面,可讓你全面控制遠端媒體播放。這個檢視畫面應允許投放應用程式管理投放工作階段的每個可管理方面,但接收器音量控制和工作階段生命週期除外 (連線/停止投放)。同時也提供媒體工作階段的所有狀態資訊 (圖片、標題、子標題等)。

這個檢視畫面的功能是由 GCKUIExpandedMediaControlsViewController 類別實作。

首先,請在投放內容中啟用預設的展開控制器。修改 AppDelegate.swift 以啟用預設展開控制器:

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

將下列程式碼加進 MediaViewController.swift,以便在使用者開始投放影片時載入展開的控制器:

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

使用者輕觸迷你控制器時,系統也會自動展開展開的控制器。

執行應用程式並投放影片。您應該會看到展開的控制器。返回影片清單,當您點選迷你控制器時,系統會重新載入展開的控制器。

10. 新增 Cast Connect 支援

Cast Connect 程式庫可讓現有的寄件者應用程式透過 Cast 通訊協定與 Android TV 應用程式進行通訊。Cast Connect 是以 Cast 基礎架構為基礎建構而成,Android TV 應用程式可做為接收器使用。

依附元件

Podfile 中,確認 google-cast-sdk 指向 4.4.8 或更高的值,如下所示。如果您已修改檔案,請在主控台中執行 pod update,將變更與專案同步。

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

GCKLaunchOptions

如要啟動 Android TV 應用程式 (又稱為 Android 接收器),您必須在 GCKLaunchOptions 物件中將 androidReceiverCompatible 旗標設為 true。此 GCKLaunchOptions 物件會指定接收器的啟動方式,並傳送至使用 GCKCastContext.setSharedInstanceWith 在共用執行個體中設定的 GCKCastOptions

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)

設定啟動憑證

在傳送方端,您可以指定 GCKCredentialsData 代表參與工作階段的使用者。credentials 是使用者可定義的字串,前提是 ATV 應用程式能夠理解。只有在啟動或加入時,系統才會將 GCKCredentialsData 傳送到您的 Android TV 應用程式。如果連線時再次設定,系統不會將內容傳送到 Android TV 應用程式。

如要設定啟動憑證,必須在設定 GCKLaunchOptions 後隨時定義 GCKCredentialsData。為了示範這一點,我們為「Creds」按鈕新增邏輯,以在工作階段建立時傳送憑證。將下列程式碼新增到您的 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))
  }
}

在載入要求中設定憑證

為了同時處理網頁與 Android TV 接收器應用程式的 credentials,請在 loadSelectedItem 函式的 MediaTableViewController.swift 類別中加入下列程式碼:

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

視傳送者的接收端應用程式而定,SDK 會自動將上述憑證套用至進行中的工作階段。

正在測試 Cast Connect

在 Chromecast (支援 Google TV) 上安裝 Android TV APK 的步驟

  1. 找出 Android TV 裝置的 IP 位址。通常可在 [設定] > [網路和裝置] (位於 [裝置連上的網路]) 中找到。畫面右側會顯示詳細資料和網路上的 IP 位址。
  2. 使用終端機的 IP 位址,透過裝置的 ADB 連線至裝置:
$ adb connect <device_ip_address>:5555
  1. 從終端機視窗,前往您在本程式碼研究室開始時下載的程式碼研究室範例的頂層資料夾。例如:
$ cd Desktop/ios_codelab_src
  1. 執行下列指令,將這個資料夾中的 .apk 檔案安裝至您的 Android TV:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. 您現在可以在 Android TV 裝置的「您的應用程式」選單中,看到以「投放影片」的名稱顯示的應用程式。
  2. 完成後,請在模擬器或行動裝置上建構並執行應用程式。透過 Android TV 裝置建立投放工作階段時,系統應會在 Android TV 上啟動 Android 接收器應用程式。如要播放 iOS 行動版寄件者的影片,請在 Android 接收器中啟動影片,然後透過遙控器操控 Android TV 裝置。

11. 自訂投放小工具

初始化

先使用 App-Done 資料夾。請將以下內容加入 AppDelegate.swift 檔案的 applicationDidFinishLaunchingWithOptions 方法中。

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

完成本程式碼研究室其餘章節所述的一或多個自訂作業後,請呼叫下列程式碼來修訂樣式

styler.apply()

自訂投放檢視畫面

你可以自訂每個檢視畫面的預設樣式準則,藉此自訂 Cast 應用程式架構管理的所有檢視畫面。例如,讓我們變更圖示的色彩。

styler.castViews.iconTintColor = .lightGray

如有需要,您可以覆寫個別畫面的預設值。例如,僅覆寫展開媒體控制器的圖示色調色彩的 LightGrayColor。

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

變更顏色

你可以自訂所有檢視畫面的背景顏色 (或是為每個檢視畫面分別設定)。以下程式碼會針對所有 Cast Application Framework 提供的檢視畫面,將背景顏色設為藍色。

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

變更字型

你也可以針對投放檢視畫面中顯示的各種標籤自訂字型。因此,為了呈現說明,請將所有字型設為「Courier-Oblique&#39」。

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)

變更預設按鈕的圖片

在專案中新增自訂圖片,並將圖片指派給您的按鈕來設定樣式。

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

變更投放按鈕主題

你也可以使用 UIAppearance 通訊協定為 Cast 小工具建立主題。以下程式碼會在所有顯示的畫面上顯示 GCKUICastButton:

GCKUICastButton.appearance().tintColor = UIColor.gray

12. 恭喜

你現已瞭解在 iOS 裝置上使用 Cast SDK 小工具,如何投放影片應用程式。

詳情請參閱 iOS 寄件者開發人員指南。