將投放功能整合至 iOS 應用程式

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

本開發人員指南說明如何透過 iOS 傳送方 SDK 為您的 iOS 傳送端應用程式新增 Google Cast 支援。

行動裝置或筆記型電腦是控製播放的「傳送端」,Google Cast 裝置則是「在電視上」顯示內容的接收器

「傳送器架構」是指 Cast 類別程式庫二進位檔,以及執行階段在執行階段顯示的相關資源。「寄件者應用程式」或「投放應用程式」則是指同時在寄件者上執行的應用程式。Web Receiver 應用程式是指在 Web Receiver 上執行的 HTML 應用程式。

傳送者架構會使用非同步回呼設計,通知傳送者應用程式事件,並在 Cast 應用程式生命週期的不同狀態之間進行轉換。

應用程式流程

下列步驟說明傳送端 iOS 應用程式的一般高階執行流程:

  • Cast 架構依據 GCKCastOptions 提供的屬性開始 GCKDiscoveryManager,開始掃描裝置。
  • 當使用者按一下「投放」按鈕時,這個架構會顯示 Cast 對話方塊,其中列出已發現的 Cast 裝置清單。
  • 當使用者選取投放裝置時,這個架構會嘗試在投放裝置上啟動 Web Receiver 應用程式。
  • 該架構會叫用寄件者應用程式中的回呼,以確認 Web 接收器應用程式已啟動。
  • 該架構會在傳送者和 Web Receiver 應用程式之間建立通訊管道。
  • 這個架構使用通訊管道,在網路接收器上載入和控制媒體播放。
  • 該架構會同步處理寄件者和網路接收器之間的媒體播放狀態:當使用者執行傳送者 UI 動作時,該架構會將這些媒體控制要求傳遞給 Web Receiver,而 Web Receiver 傳送媒體狀態更新時,這個架構會更新寄件者 UI 的狀態。
  • 當使用者按一下「Cast」按鈕與 Cast 裝置中斷連線時,該架構就會中斷傳送端應用程式與 Web Receiver 的連線。

如要排解寄件者的問題,您必須啟用記錄功能。

如需 Google Cast iOS 架構中所有類別、方法和事件的完整清單,請參閱 Google Cast iOS API 參考資料。以下各節說明將 Cast 整合至 iOS 應用程式的步驟。

從主執行緒呼叫方法

初始化 Cast 內容

Cast 架構具備全域單例模式物件 GCKCastContext,可協調架構的所有活動。此物件必須在應用程式的生命週期中提早初始化,通常在應用程式委派的 -[application:didFinishLaunchingWithOptions:] 方法中,這樣在寄件者應用程式重新啟動時,自動工作階段繼續作業,才會正確觸發。

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

-[application:didFinishLaunchingWithOptions:] 方法也適合設定記錄委派,以便從架構接收記錄訊息。適用於偵錯及疑難排解。

快速滑動
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate {
  let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID
  let kDebugLoggingEnabled = true

  var window: UIWindow?

  func applicationDidFinishLaunching(_ application: UIApplication) {
    let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
    let options = GCKCastOptions(discoveryCriteria: criteria)
    GCKCastContext.setSharedInstanceWith(options)

    // Enable logger.
    GCKLogger.sharedInstance().delegate = self

    ...
  }

  // MARK: - GCKLoggerDelegate

  func logMessage(_ message: String,
                  at level: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if (kDebugLoggingEnabled) {
      print(function + " - " + message)
    }
  }
}
Objective-C

AppDelegate.h

@interface AppDelegate () <GCKLoggerDelegate>
@end

AppDelegate.m

@implementation AppDelegate

static NSString *const kReceiverAppID = @"AABBCCDD";
static const BOOL kDebugLoggingEnabled = YES;

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                    initWithApplicationID:kReceiverAppID];
  GCKCastOptions *options = [[GCKCastOptions alloc] initWithDiscoveryCriteria:criteria];
  [GCKCastContext setSharedInstanceWithOptions:options];

  // Enable logger.
  [GCKLogger sharedInstance].delegate = self;

  ...

  return YES;
}

...

#pragma mark - GCKLoggerDelegate

- (void)logMessage:(NSString *)message
           atLevel:(GCKLoggerLevel)level
      fromFunction:(NSString *)function
          location:(NSString *)location {
  if (kDebugLoggingEnabled) {
    NSLog(@"%@ - %@, %@", function, message, location);
  }
}

@end

Cast 使用者體驗小工具

Cast iOS SDK 提供符合 Cast 設計檢查清單的小工具:

  • 簡介疊加層GCKCastContext 類別使用 presentCastInstructionsViewControllerOnceWithCastButton 方法,可在網頁接收器首次可用時,將投放按鈕聚焦於該按鈕。傳送者應用程式可以自訂文字、標題文字和關閉按鈕。

  • 投放按鈕:從 Cast iOS 寄件者 SDK 4.6.0 開始,當傳送者的裝置連上 Wi-Fi 時,「投放」按鈕一律會顯示。使用者初次啟動應用程式後輕觸「投放」按鈕時,畫面上會顯示權限對話方塊,讓使用者允許應用程式存取網路,以便存取網路上的裝置。之後,當使用者輕觸投放按鈕時,就會顯示投放對話方塊,當中會列出已探索的裝置。裝置在連線時輕觸投放按鈕時,會顯示目前的媒體中繼資料 (例如標題、錄音室的名稱和縮圖),或讓使用者中斷與投放裝置的連線。當使用者在沒有可用裝置的情況下輕觸投放按鈕時,系統會顯示畫面,向使用者說明找不到裝置的原因和疑難排解方法。

  • 小控制器:當使用者投放內容,並離開目前內容頁面或展開的控制器至寄件者應用程式的另一個畫面時,迷你控制器會顯示在螢幕底部,讓使用者能夠看到目前的投放媒體中繼資料,並控製播放。

  • 展開式控制器:當使用者投放內容時,只要按一下媒體通知或迷你控制器,展開的控制器就會啟動,其中會顯示目前正在播放的媒體中繼資料,並提供多個按鈕來控制媒體播放。

新增投放按鈕

該架構以 UIButton 子類別提供投放按鈕元件。您可以將此程式碼納入 UIBarButtonItem 中,藉此將其新增至應用程式的標題列。一般的 UIViewController 子類別可安裝 Cast 按鈕,如下所示:

快速滑動
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
castButton.tintColor = UIColor.gray
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
Objective-C
GCKUICastButton *castButton = [[GCKUICastButton alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];
castButton.tintColor = [UIColor grayColor];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:castButton];

根據預設,輕觸按鈕即可開啟架構提供的 Cast 對話方塊。

GCKUICastButton 也可以直接新增至分鏡腳本。

設定裝置探索

在架構中,裝置探索會自動執行。除非您實作自訂 UI,否則不需要明確開始或停止探索程序。

架構中的探索是由 GCKDiscoveryManager 類別管理,該類別是 GCKCastContext 的屬性。這個架構提供預設的投放對話方塊元件,可用於選取及控制裝置。這份裝置清單會依裝置友善名稱的字母順序排序。

工作階段管理的運作方式

Cast SDK 介紹 Cast 工作階段的概念,其概念結合了連接裝置、啟動 (或加入) 網路接收器應用程式、連線至該應用程式,以及初始化媒體控制通道的步驟。如要進一步瞭解 Cast 工作階段和 Web Receiver 生命週期,請參閱 Web Receiver 應用程式生命週期指南

工作階段由 GCKSessionManager 類別管理,這是 GCKCastContext 的屬性。個別工作階段以 GCKSession 類別的子類別表示:例如,GCKCastSession 代表投放裝置的工作階段。您可以存取目前使用的有效 Cast 工作階段 (如果有的話),做為 GCKSessionManagercurrentCastSession 屬性。

GCKSessionManagerListener 介面可用來監控工作階段事件,例如工作階段建立、暫停、繼續和終止。當寄件者應用程式進入背景時,架構會自動暫停工作階段,並在應用程式返回前景時繼續工作階段 (或在工作階段處於異常/突然終止後重新啟動)。

如果使用 Cast 對話方塊,系統就會建立工作階段,並自動回應使用者手勢。否則,應用程式可透過 GCKSessionManager 中的方法明確啟動及結束工作階段。

如果應用程式必須對工作階段生命週期事件執行特殊處理作業,可以使用 GCKSessionManager 註冊一或多個 GCKSessionManagerListener 執行個體。GCKSessionManagerListener 是一種通訊協定,可定義這類事件的回呼,例如工作階段開始、工作階段結束等。

變更串流裝置

保留工作階段狀態是串流傳輸的基礎,使用者可以透過語音指令、Google Home 應用程式或智慧螢幕,在不同裝置上移動現有的音訊和影片串流。媒體會在某部裝置上 (來源) 停止播放,並在另一個裝置上 (目的地) 繼續播放。具有最新韌體的所有 Cast 裝置都可以做為串流傳輸的來源或目的地。

如要在串流傳輸期間取得新的目的地裝置,請在 [sessionManager:didResumeCastSession:] 回呼期間使用 GCKCastSession#device 屬性。

如需詳細資訊,請參閱透過 Web 接收器接收串流傳輸

自動重新連線

Cast 架構會新增重新連線邏輯,在許多細微角落情況中自動處理重新連線,例如:

  • 暫時停止連上 Wi-Fi 網路
  • 從裝置睡眠恢復
  • 從背景執行還原作業
  • 在應用程式停止運作時進行復原

媒體控制的運作方式

如果透過支援媒體命名空間的網路接收器應用程式建立 Cast 工作階段,GCKRemoteMediaClient 執行個體將由架構自動建立;它會以 GCKCastSession 執行個體的 remoteMediaClient 屬性存取。

GCKRemoteMediaClient 上向網路接收器發出要求的所有方法都會傳回 GCKRequest 物件,該物件可用於追蹤該要求。可將 GCKRequestDelegate 指派給這個物件,以接收作業的最終結果通知。

GCKRemoteMediaClient 的例項應該可以由應用程式的多個部分共用,且確實可以採用架構的部分內部元件 (例如 Cast 對話方塊和迷你媒體控制項),而共用執行個體。為此,GCKRemoteMediaClient 支援註冊多個 GCKRemoteMediaClientListener

設定媒體中繼資料

GCKMediaMetadata 類別代表要投放的媒體項目相關資訊。下列範例會建立新的電影 GCKMediaMetadata 例項,並設定標題、副標題、錄音室的名稱和兩張圖片。

快速滑動
let metadata = GCKMediaMetadata()
metadata.setString("Big Buck Bunny (2008)", forKey: kGCKMetadataKeyTitle)
metadata.setString("Big Buck Bunny tells the story of a giant rabbit with a heart bigger than " +
  "himself. When one sunny day three rodents rudely harass him, something " +
  "snaps... and the rabbit ain't no bunny anymore! In the typical cartoon " +
  "tradition he prepares the nasty rodents a comical revenge.",
                   forKey: kGCKMetadataKeySubtitle)
metadata.addImage(GCKImage(url: URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg")!,
                           width: 480,
                           height: 360))
Objective-C
GCKMediaMetadata *metadata = [[GCKMediaMetadata alloc]
                                initWithMetadataType:GCKMediaMetadataTypeMovie];
[metadata setString:@"Big Buck Bunny (2008)" forKey:kGCKMetadataKeyTitle];
[metadata setString:@"Big Buck Bunny tells the story of a giant rabbit with a heart bigger than "
 "himself. When one sunny day three rodents rudely harass him, something "
 "snaps... and the rabbit ain't no bunny anymore! In the typical cartoon "
 "tradition he prepares the nasty rodents a comical revenge."
             forKey:kGCKMetadataKeySubtitle];
[metadata addImage:[[GCKImage alloc]
                    initWithURL:[[NSURL alloc] initWithString:@"https://commondatastorage.googleapis.com/"
                                 "gtv-videos-bucket/sample/images/BigBuckBunny.jpg"]
                    width:480
                    height:360]];

如要瞭解如何將圖片與媒體中繼資料搭配使用,請參閱「圖片選擇和快取」一節。

載入媒體

如要載入媒體項目,請使用媒體中繼資料建立 GCKMediaInformation 執行個體。然後,取得目前的 GCKCastSession,並使用其 GCKRemoteMediaClient 在接收器應用程式中載入媒體。接著,您可以使用 GCKRemoteMediaClient 來控制在接收器上執行的媒體播放器應用程式,例如播放、暫停和停止。

快速滑動
let url = URL.init(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
guard let mediaURL = url else {
  print("invalid mediaURL")
  return
}

let mediaInfoBuilder = GCKMediaInformationBuilder.init(contentURL: mediaURL)
mediaInfoBuilder.streamType = GCKMediaStreamType.none;
mediaInfoBuilder.contentType = "video/mp4"
mediaInfoBuilder.metadata = metadata;
mediaInformation = mediaInfoBuilder.build()

guard let mediaInfo = mediaInformation else {
  print("invalid mediaInformation")
  return
}

if let request = sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInfo) {
  request.delegate = self
}
Objective-C
GCKMediaInformationBuilder *mediaInfoBuilder =
  [[GCKMediaInformationBuilder alloc] initWithContentURL:
   [NSURL URLWithString:@"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"]];
mediaInfoBuilder.streamType = GCKMediaStreamTypeNone;
mediaInfoBuilder.contentType = @"video/mp4";
mediaInfoBuilder.metadata = metadata;
self.mediaInformation = [mediaInfoBuilder build];

GCKRequest *request = [self.sessionManager.currentSession.remoteMediaClient loadMedia:self.mediaInformation];
if (request != nil) {
  request.delegate = self;
}

另請參閱使用媒體曲目一節。

4K 影片格式

如要判斷媒體格式,請使用 GCKMediaStatusvideoInfo 屬性取得 GCKVideoInfo 目前的例項。這個執行個體包含 HDR 電視格式的類型,以及像素的寬度和寬度。4K 格式的變體以 hdrType 屬性的列舉值 GCKVideoInfoHDRType 表示。

新增迷你控制器

根據 Cast 設計檢查清單,寄件者應用程式應提供一個永久的控制項 (稱為「迷你控制器」),當使用者離開目前的內容頁面時,畫面上應會顯示這個控制項。迷你控制器提供目前投放工作階段的即時存取和可見提醒。

Cast 架構提供控制列 GCKUIMiniMediaControlsViewController,可新增至您要顯示迷你控制器的場景。

您的寄件者應用程式播放影片或音訊直播時,SDK 會自動顯示迷你控制器中的播放/暫停按鈕,而非播放/停止播放按鈕。

請參閱自訂 iOS 傳送方使用者介面,瞭解寄件者應用程式如何設定投放小工具的外觀。

您可以透過下列兩種方式將迷你控制器新增至寄件者應用程式:

  • 將現有檢視控制器與自己的檢視控制器包裝在一起,讓 Cast 架構以管理迷你控制器的版面配置。
  • 在分鏡腳本中提供子檢視畫面,將迷你控制器小工具加入現有的檢視控制器,藉此自行管理小工具的版面配置。

使用 GCKUICastContainerViewController 進行包裝

第一種方法是使用 GCKUICastContainerViewController 來包裝另一個檢視畫面控制器,並在底部加入 GCKUIMiniMediaControlsViewController。這個方法的有限,因為您無法自訂動畫,也無法設定容器檢視控制器的行為。

第一種方法通常是在應用程式委派的 -[application:didFinishLaunchingWithOptions:] 方法中完成:

快速滑動
func applicationDidFinishLaunching(_ application: UIApplication) {
  ...

  // Wrap main view in the GCKUICastContainerViewController and display the mini controller.
  let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
  let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
  let castContainerVC =
          GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
  castContainerVC.miniMediaControlsItemEnabled = true
  window = UIWindow(frame: UIScreen.main.bounds)
  window!.rootViewController = castContainerVC
  window!.makeKeyAndVisible()

  ...
}
Objective-C
- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  // Wrap main view in the GCKUICastContainerViewController and display the mini controller.
  UIStoryboard *appStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
  UINavigationController *navigationController =
          [appStoryboard instantiateViewControllerWithIdentifier:@"MainNavigation"];
  GCKUICastContainerViewController *castContainerVC =
          [[GCKCastContext sharedInstance] createCastContainerControllerForViewController:navigationController];
  castContainerVC.miniMediaControlsItemEnabled = YES;
  self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
  self.window.rootViewController = castContainerVC;
  [self.window makeKeyAndVisible];
  ...

}
Swift
var castControlBarsEnabled: Bool {
  set(enabled) {
    if let castContainerVC = self.window?.rootViewController as? GCKUICastContainerViewController {
      castContainerVC.miniMediaControlsItemEnabled = enabled
    } else {
      print("GCKUICastContainerViewController is not correctly configured")
    }
  }
  get {
    if let castContainerVC = self.window?.rootViewController as? GCKUICastContainerViewController {
      return castContainerVC.miniMediaControlsItemEnabled
    } else {
      print("GCKUICastContainerViewController is not correctly configured")
      return false
    }
  }
}
Objective-C

AppDelegate.h

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, assign) BOOL castControlBarsEnabled;

@end

AppDelegate.m

@implementation AppDelegate

...

- (void)setCastControlBarsEnabled:(BOOL)notificationsEnabled {
  GCKUICastContainerViewController *castContainerVC;
  castContainerVC =
      (GCKUICastContainerViewController *)self.window.rootViewController;
  castContainerVC.miniMediaControlsItemEnabled = notificationsEnabled;
}

- (BOOL)castControlBarsEnabled {
  GCKUICastContainerViewController *castContainerVC;
  castContainerVC =
      (GCKUICastContainerViewController *)self.window.rootViewController;
  return castContainerVC.miniMediaControlsItemEnabled;
}

...

@end

嵌入現有的檢視控制器

第二種方法則是使用 createMiniMediaControlsViewController 建立 GCKUIMiniMediaControlsViewController 執行個體,然後將該控制器新增至容器檢視控制器,做為子檢視。

在應用程式委派中設定檢視控制器:

快速滑動
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  ...

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
  window?.clipsToBounds = true

  let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
  rootContainerVC?.miniMediaControlsViewEnabled = true

  ...

  return true
}
Objective-C
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  self.window.clipsToBounds = YES;

  RootContainerViewController *rootContainerVC;
  rootContainerVC =
      (RootContainerViewController *)self.window.rootViewController;
  rootContainerVC.miniMediaControlsViewEnabled = YES;

  ...

  return YES;
}

請在根檢視控制器中建立 GCKUIMiniMediaControlsViewController 執行個體,並將其新增至容器檢視控制器,用做子檢視:

快速滑動
let kCastControlBarsAnimationDuration: TimeInterval = 0.20

@objc(RootContainerViewController)
class RootContainerViewController: UIViewController, GCKUIMiniMediaControlsViewControllerDelegate {
  @IBOutlet weak private var _miniMediaControlsContainerView: UIView!
  @IBOutlet weak private var _miniMediaControlsHeightConstraint: NSLayoutConstraint!
  private var miniMediaControlsViewController: GCKUIMiniMediaControlsViewController!
  var miniMediaControlsViewEnabled = false {
    didSet {
      if self.isViewLoaded {
        self.updateControlBarsVisibility()
      }
    }
  }

  var overriddenNavigationController: UINavigationController?

  override var navigationController: UINavigationController? {

    get {
      return overriddenNavigationController
    }

    set {
      overriddenNavigationController = newValue
    }
  }
  var miniMediaControlsItemEnabled = false

  override func viewDidLoad() {
    super.viewDidLoad()
    let castContext = GCKCastContext.sharedInstance()
    self.miniMediaControlsViewController = castContext.createMiniMediaControlsViewController()
    self.miniMediaControlsViewController.delegate = self
    self.updateControlBarsVisibility()
    self.installViewController(self.miniMediaControlsViewController,
                               inContainerView: self._miniMediaControlsContainerView)
  }

  func updateControlBarsVisibility() {
    if self.miniMediaControlsViewEnabled && self.miniMediaControlsViewController.active {
      self._miniMediaControlsHeightConstraint.constant = self.miniMediaControlsViewController.minHeight
      self.view.bringSubview(toFront: self._miniMediaControlsContainerView)
    } else {
      self._miniMediaControlsHeightConstraint.constant = 0
    }
    UIView.animate(withDuration: kCastControlBarsAnimationDuration, animations: {() -> Void in
      self.view.layoutIfNeeded()
    })
    self.view.setNeedsLayout()
  }

  func installViewController(_ viewController: UIViewController?, inContainerView containerView: UIView) {
    if let viewController = viewController {
      self.addChildViewController(viewController)
      viewController.view.frame = containerView.bounds
      containerView.addSubview(viewController.view)
      viewController.didMove(toParentViewController: self)
    }
  }

  func uninstallViewController(_ viewController: UIViewController) {
    viewController.willMove(toParentViewController: nil)
    viewController.view.removeFromSuperview()
    viewController.removeFromParentViewController()
  }

  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "NavigationVCEmbedSegue" {
      self.navigationController = (segue.destination as? UINavigationController)
    }
  }

...
Objective-C

RootContainerViewController.h

static const NSTimeInterval kCastControlBarsAnimationDuration = 0.20;

@interface RootContainerViewController () <GCKUIMiniMediaControlsViewControllerDelegate> {
  __weak IBOutlet UIView *_miniMediaControlsContainerView;
  __weak IBOutlet NSLayoutConstraint *_miniMediaControlsHeightConstraint;
  GCKUIMiniMediaControlsViewController *_miniMediaControlsViewController;
}

@property(nonatomic, weak, readwrite) UINavigationController *navigationController;

@property(nonatomic, assign, readwrite) BOOL miniMediaControlsViewEnabled;
@property(nonatomic, assign, readwrite) BOOL miniMediaControlsItemEnabled;

@end

RootContainerViewController.m

@implementation RootContainerViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  GCKCastContext *castContext = [GCKCastContext sharedInstance];
  _miniMediaControlsViewController =
      [castContext createMiniMediaControlsViewController];
  _miniMediaControlsViewController.delegate = self;

  [self updateControlBarsVisibility];
  [self installViewController:_miniMediaControlsViewController
              inContainerView:_miniMediaControlsContainerView];
}

- (void)setMiniMediaControlsViewEnabled:(BOOL)miniMediaControlsViewEnabled {
  _miniMediaControlsViewEnabled = miniMediaControlsViewEnabled;
  if (self.isViewLoaded) {
    [self updateControlBarsVisibility];
  }
}

- (void)updateControlBarsVisibility {
  if (self.miniMediaControlsViewEnabled &&
      _miniMediaControlsViewController.active) {
    _miniMediaControlsHeightConstraint.constant =
        _miniMediaControlsViewController.minHeight;
    [self.view bringSubviewToFront:_miniMediaControlsContainerView];
  } else {
    _miniMediaControlsHeightConstraint.constant = 0;
  }
  [UIView animateWithDuration:kCastControlBarsAnimationDuration
                   animations:^{
                     [self.view layoutIfNeeded];
                   }];
  [self.view setNeedsLayout];
}

- (void)installViewController:(UIViewController *)viewController
              inContainerView:(UIView *)containerView {
  if (viewController) {
    [self addChildViewController:viewController];
    viewController.view.frame = containerView.bounds;
    [containerView addSubview:viewController.view];
    [viewController didMoveToParentViewController:self];
  }
}

- (void)uninstallViewController:(UIViewController *)viewController {
  [viewController willMoveToParentViewController:nil];
  [viewController.view removeFromSuperview];
  [viewController removeFromParentViewController];
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  if ([segue.identifier isEqualToString:@"NavigationVCEmbedSegue"]) {
    self.navigationController =
        (UINavigationController *)segue.destinationViewController;
  }
}

...

@end

GCKUIMiniMediaControlsViewControllerDelegate 會告知主機檢視畫面的控制器,在顯示迷你控制器時:

快速滑動
  func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController,
                                       shouldAppear _: Bool) {
    updateControlBarsVisibility()
  }
Objective-C
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

新增展開的控制器

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

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

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

首先,請在投放內容中啟用預設的展開控制器。修改應用程式委派功能,以啟用預設展開的控制器:

快速滑動
func applicationDidFinishLaunching(_ application: UIApplication) {
  ..

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

  ...
}
Objective-C
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

將下列程式碼加入檢視畫面控制器,以便在使用者開始投放影片時載入已展開的控制器:

快速滑動
func playSelectedItemRemotely() {
  GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()

  ...

  // Load your media
  sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInformation)
}
Objective-C
- (void)playSelectedItemRemotely {
  [[GCKCastContext sharedInstance] presentDefaultExpandedMediaControls];

  ...

  // Load your media
  [self.sessionManager.currentSession.remoteMediaClient loadMedia:mediaInformation];
}

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

您的寄件者應用程式播放影片或音訊直播時,SDK 會自動在展開的控制器中顯示播放/暫停按鈕,而非播放/暫停按鈕。

請參閱將自訂樣式套用到您的 iOS 應用程式,瞭解寄件者應用程式如何設定 Cast 小工具的外觀。

音量控制

Cast 架構會自動管理寄件者應用程式的音量。這個架構會自動與所提供的 UI 小工具的網頁接收器音量保持同步。如要同步處理應用程式提供的滑桿,請使用 GCKUIDeviceVolumeController

實體按鈕音量控制

傳送端上的實體音量按鈕可用於使用 GCKCastOptions 上的 physicalVolumeButtonsWillControlDeviceVolume 標記,在網路接收器上變更投放工作階段的音量,此設定是在 GCKCastContext 上設定。

快速滑動
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
Objective-C
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                          initWithApplicationID:kReceiverAppID];
GCKCastOptions *options = [[GCKCastOptions alloc]
                                          initWithDiscoveryCriteria :criteria];
options.physicalVolumeButtonsWillControlDeviceVolume = YES;
[GCKCastContext setSharedInstanceWithOptions:options];

處理錯誤

寄件者應用程式必須處理所有錯誤回呼,並為 Cast 生命週期各階段決定最佳回應。應用程式可以向使用者顯示錯誤對話方塊,或是決定結束 Cast 工作階段。

記錄

GCKLogger 是架構用於記錄的單例模式。使用 GCKLoggerDelegate 自訂處理記錄訊息的方式。

使用 GCKLogger 時,SDK 會以偵錯訊息、錯誤和警告的形式產生記錄輸出。這些記錄訊息有助於偵錯及找出問題,根據預設,系統會封鎖記錄輸出,但透過指派 GCKLoggerDelegate,寄件者應用程式可以從 SDK 接收這些訊息,並記錄到系統主控台。

快速滑動
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate {
  let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID
  let kDebugLoggingEnabled = true

  var window: UIWindow?

  func applicationDidFinishLaunching(_ application: UIApplication) {
    ...

    // Enable logger.
    GCKLogger.sharedInstance().delegate = self

    ...
  }

  // MARK: - GCKLoggerDelegate

  func logMessage(_ message: String,
                  at level: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if (kDebugLoggingEnabled) {
      print(function + " - " + message)
    }
  }
}
Objective-C

AppDelegate.h

@interface AppDelegate () <GCKLoggerDelegate>
@end

AppDelegate.m

@implementation AppDelegate

static NSString *const kReceiverAppID = @"AABBCCDD";
static const BOOL kDebugLoggingEnabled = YES;

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  // Enable logger.
  [GCKLogger sharedInstance].delegate = self;

  ...

  return YES;
}

...

#pragma mark - GCKLoggerDelegate

- (void)logMessage:(NSString *)message
           atLevel:(GCKLoggerLevel)level
      fromFunction:(NSString *)function
          location:(NSString *)location {
  if (kDebugLoggingEnabled) {
    NSLog(@"%@ - %@, %@", function, message, location);
  }
}

@end

要同時啟用偵錯和詳細訊息,請在設定委派之後,在程式碼中加入以下這行程式碼 (如前所示):

快速滑動
let filter = GCKLoggerFilter.init()
filter.minimumLevel = GCKLoggerLevel.verbose
GCKLogger.sharedInstance().filter = filter
Objective-C
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setMinimumLevel:GCKLoggerLevelVerbose];
[GCKLogger sharedInstance].filter = filter;

您也可以篩選 GCKLogger 產生的記錄訊息。設定每個類別的最低記錄層級,例如:

快速滑動
let filter = GCKLoggerFilter.init()
filter.setLoggingLevel(GCKLoggerLevel.verbose, forClasses: ["GCKUICastButton",
                                                            "GCKUIImageCache",
                                                            "NSMutableDictionary"])
GCKLogger.sharedInstance().filter = filter
Objective-C
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setLoggingLevel:GCKLoggerLevelVerbose
             forClasses:@[@"GCKUICastButton",
                          @"GCKUIImageCache",
                          @"NSMutableDictionary"
                          ]];
[GCKLogger sharedInstance].filter = filter;

類別名稱可以是常值名稱或 glob 模式,例如 GCKUI\*GCK\*Session