1. 總覽
本程式碼研究室會說明如何修改現有的 iOS 影片應用程式,以便在支援 Google Cast 的裝置上投放內容。
什麼是 Google Cast?
Google Cast 可讓使用者將內容從行動裝置投放到電視上。之後,使用者就能利用行動裝置做為電視媒體播放的遙控器。
Google Cast SDK 可讓你擴充應用程式,以控制支援 Google Cast 的裝置 (例如電視或音響系統)。Cast SDK 可讓您根據 Google Cast 設計檢查清單新增必要的 UI 元件。
我們提供了 Google Cast 設計檢查清單,讓您在所有支援平台上都能享有簡單且可預測的投放體驗。
我們要建構的是什麼?
完成本程式碼研究室後,您將擁有可將影片投放到 Google Cast 裝置的 iOS 影片應用程式。
課程內容
- 如何將 Google Cast SDK 新增至影片樣本應用程式。
- 如何新增 Google Cast 裝置的投放按鈕。
- 如何連線至 Cast 裝置及啟動媒體接收器。
- 如何投放影片。
- 如何將 Cast 迷你控制器加入應用程式。
- 如何新增展開的控制器。
- 如何提供簡介重疊廣告。
- 如何自訂 Cast 小工具。
- 如何整合 Cast Connect
軟硬體需求
- 最新的 Xcode。
- 一部搭載 iOS 9 以上版本的行動裝置,或是 Xcode 模擬器。
- USB 資料傳輸線可連接您的行動裝置與開發電腦 (如果使用裝置的話)。
- Google Cast 裝置,例如已設定網際網路連線的 Chromecast 或 Android TV。
- 具備 HDMI 輸入端的電視或螢幕。
- 你必須使用 Chromecast (支援 Google TV) 才能測試 Cast Connect 整合功能,但在程式碼研究室的其他部分則為選用功能。如果您還沒有 Chromecast 支援,請略過本教學課程結尾的「新增 Cast Connect 支援」步驟。
功能
- 您需具備之前的 iOS 開發知識。
- 您也需要具備先前觀看電視節目的知識 :)
您會如何使用這個教學課程?
針對建立 iOS 應用程式的體驗,您會給予什麼評價?
針對觀看電視的體驗,你會給予什麼評價?
2. 取得程式碼範例
您可以將所有程式碼範例下載至您的電腦中...
將下載的 ZIP 檔案解壓縮
3. 執行範例應用程式
首先,讓我們來看看已完成的範例應用程式。這款應用程式是基本的影片播放器。使用者可以選取清單中的影片,然後透過該裝置在本機播放影片,或是將影片投放到 Google Cast 裝置。
下載程式碼後,下列指示說明如何在 Xcode 中開啟並執行已完成的範例應用程式:
常見問題
CocoaPods 設定
如要設定 CocoaPods,請前往主控台,並使用 macOS 上預設的預設 Ruby 進行安裝:
sudo gem install cocoapods
如有任何問題,請參閱官方說明文件,下載並安裝依附元件管理員。
專案設定
- 前往終端機,然後前往程式碼研究室目錄。
- 從 Podfile 安裝依附元件。
cd app-done pod update pod install
- 開啟 Xcode,然後選取 [Open another project...] (開啟其他專案...)。
- 從程式碼範例資料夾中的
app-done
目錄中選取CastVideos-ios.xcworkspace
檔案。
執行應用程式
選取目標和模擬工具,然後執行應用程式:
影片應用程式應會在幾秒鐘後顯示。
當系統顯示接受網路連線時,請務必按一下 [允許]。如果系統不接受這個選項,「投放」圖示就不會顯示。
按一下「投放」按鈕,然後選取您的 Google Cast 裝置。
選取影片,然後按一下播放按鈕。
影片就會在您的 Google Cast 裝置上開始播放。
系統會顯示已展開的控制器。您可以使用播放/暫停按鈕控製播放。
返回影片清單。
畫面底部隨即會顯示迷你控制器。
按一下迷你控制器上的暫停按鈕,即可暫停接收器的視訊。按一下迷你控制器中的播放按鈕,即可繼續播放影片。
按一下「投放」按鈕即可停止投放至 Google Cast 裝置。
4. 準備起始專案
我們需要針對支援下載的應用程式提供 Google Cast 的支援。在本程式碼研究室中,我們會採用以下 Google Cast 術語:
- 寄件者應用程式會在行動裝置或筆記型電腦上執行,
- 接收器應用程式會在 Google Cast 裝置上執行。
專案設定
您現在可以使用 Xcode 在入門專案上進行建構:
- 前往終端機,然後前往程式碼研究室目錄。
- 從 Podfile 安裝依附元件。
cd app-start pod update pod install
- 開啟 Xcode,然後選取 [Open another project...] (開啟其他專案...)。
- 從程式碼範例資料夾中的
app-start
目錄中選取CastVideos-ios.xcworkspace
檔案。
應用程式設計
應用程式會從遠端網路伺服器擷取影片清單,並提供使用者清單以供瀏覽。使用者可以選取影片以查看詳細資訊,或在行動裝置上在本機播放影片。
這個應用程式包含兩個主要檢視畫面控制器:MediaTableViewController
和 MediaViewController.
媒體表格檢視控制器
這個 UITableViewController 會顯示 MediaListModel
例項的影片清單。影片清單和相關中繼資料是透過遠端伺服器以 JSON 檔案的形式代管。MediaListModel
會擷取這個 JSON 並進行處理,以便建立 MediaItem
物件清單。
MediaItem
物件會模擬影片及其相關中繼資料,例如標題、說明、圖片網址以及串流網址。
MediaTableViewController
會建立 MediaListModel
執行個體,然後將該執行個體註冊為 MediaListModelDelegate
,以便在下載媒體中繼資料時接收通知,進而載入表格檢視。
使用者會看到影片縮圖清單,其中包含每部影片的簡短說明。選取項目時,系統會將對應的 MediaItem
傳遞至 MediaViewController
。
媒體檢視控制器
這個檢視控制器會顯示特定影片的中繼資料,並允許使用者在行動裝置上播放影片。
檢視控制器代管 LocalPlayerView
、部分媒體控制項及文字區域,以顯示所選影片的說明。播放器會覆蓋螢幕的上半部,在下方顯示影片的詳細說明,讓使用者可以播放/暫停或播放當地影片播放。
常見問題
5. 新增投放按鈕
支援 Cast 的應用程式會在每個檢視控制器中顯示投放按鈕。按一下「投放」按鈕即可顯示可供使用者選取的 Cast 裝置清單。如果使用者在本機裝置上播放內容,選取投放裝置,或是在該投放裝置上繼續播放內容。在投放工作階段期間,使用者隨時可以按一下 [投放] 按鈕,停止將應用程式投放到 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
中即可將其新增至應用程式的標題列。我們需要將投放按鈕同時新增至 MediaTableViewController
和 MediaViewController
。
將下列程式碼加入 MediaTableViewController.swift
和 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)
...
}
...
}
接下來,請將下列程式碼新增到您的 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)
...
}
...
}
接著,執行應用程式。應用程式導覽列中應該會顯示「投放」按鈕,當您點選按鈕時,系統便會列出您區域網路上的 Cast 裝置。裝置探索是由「GCKCastContext
」自動管理。選擇您的投放裝置,接收器應用程式隨即會載入至投放裝置上。您可在瀏覽活動與本機播放器活動之間進行切換,而且「投放」按鈕的狀態會保持同步。
我們尚未支援任何媒體播放功能,因此你目前還無法透過投放裝置播放影片。按一下「投放」按鈕即可停止投放。
6. 投放影片內容
我們也會將範例應用程式延伸到遠端投放的影片。因此,我們必須監聽 Cast 架構產生的各種事件。
正在投放媒體
整體而言,如要在投放裝置上播放媒體,必須發生以下情況:
- 從 Cast SDK 建立
GCKMediaInformation
物件,建立媒體項目模型。 - 使用者連線至投放裝置以啟動接收器應用程式。
- 將
GCKMediaInformation
物件載入至接收器並播放內容。 - 追蹤媒體狀態。
- 根據使用者互動將播放指令傳送至接收端。
步驟 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 架構結合了連接裝置、啟動 (或加入)、連線至接收器應用程式,以及在適當情況下初始化媒體控制頻道的步驟。媒體控制頻道是 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
起,當我們與 Chromecast 裝置建立連線或中斷連線時,我們會通知您,以便改用或從本機播放器切換至播放器。請注意,除了在行動裝置上執行的應用程式執行個體之外,連線的中斷也會中斷,而另一個 (或其他) 應用程式的執行個體也可能同時在其他行動裝置上執行。
目前的工作階段可使用 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
}
}
}
...
}
現在,請更新各種現有的方法,以使用 Cast Session 邏輯來支援遠端播放:
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. 迷你控制器
「投放設計檢查清單」要求所有投放應用程式都必須提供迷你控制器,在使用者離開目前的內容頁面時才會顯示。迷你控制器可即時存取內容,而且會在目前的投放工作階段中顯示顯示提醒。
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 的使用者。
GCKCastContext
類別提供 presentCastInstructionsViewControllerOnce
方法,可在首次向使用者顯示「投放」按鈕時進行醒目標示。將下列程式碼加入 MediaViewController.swift
和 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)
}
}
在行動裝置上執行應用程式後,您會看見簡介重疊廣告。
9. 已展開控制器
Google Cast 設計檢查清單要求寄件者應用程式針對投放的媒體提供展開的控制器。展開的控制器是全螢幕的全螢幕版本。
展開的控制器顯示全螢幕畫面,可完全控制遠端媒體播放。這個檢視畫面應允許投放應用程式管理投放工作階段的每個可管理方面,但接收器音量控制和工作階段生命週期除外 (連線/停止投放)。以及媒體工作階段的所有狀態資訊 (圖片、標題、子標題等)。
此檢視畫面的功能是由 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 的步驟
- 找出 Android TV 裝置的 IP 位址。通常可在 [設定] > [網路和網際網路] > (您的裝置已連線的網路名稱) 下存取。畫面右側會顯示網路的詳細資訊和裝置的 IP。
- 使用裝置的 IP 位址,透過終端機透過 ADB 連線至裝置:
$ adb connect <device_ip_address>:5555
- 從終端機視窗,前往您在本程式碼研究室開始時下載的程式碼研究室範例的頂層資料夾。例如:
$ cd Desktop/ios_codelab_src
- 執行下列指令,將這個資料夾中的 .apk 檔案安裝至您的 Android TV:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- 您現在應該能在 Android TV 裝置的 [您的應用程式] 選單中,看到以 [投放影片] 這個名稱顯示的應用程式。
- 完成後,請在模擬器或行動裝置上建構並執行應用程式。透過 Android TV 裝置建立投放工作階段時,系統應會在 Android TV 上啟動 Android 接收器應用程式。如要播放 iOS 行動版寄件者的影片,請在 Android 接收器中啟動影片,然後透過遙控器控制 Android TV 裝置的播放。
11. 自訂 Cast 小工具
初始化
以「App-Done」資料夾開始。請將以下內容加入 AppDelegate.swift
檔案的 applicationDidFinishLaunchingWithOptions
方法中。
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let styler = GCKUIStyle.sharedInstance()
...
}
完成本程式碼研究室其餘部分所述的一或多個自訂之後,請呼叫下列程式碼來修訂樣式
styler.apply()
自訂投放檢視畫面
您可以針對各檢視畫面的預設樣式規範,自訂 Cast Application Framework 管理的所有檢視畫面。例如,我們來變更圖示色調顏色。
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」。
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 小工具,透過 Google Cast 投放影片應用程式。
詳情請參閱 iOS 寄件者開發人員指南。