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

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

行動裝置或筆記型電腦是控製播放的傳送者, Google Cast 裝置是指用於在電視上顯示內容的接收器

傳送者架構是指 Cast 類別程式庫二進位檔,以及相關聯的 向傳送端發出的資源配置要求寄件者應用程式投放應用程式 代表在傳送方上執行的應用程式。網路接收器應用程式 是指在 Web Receiver 上執行的 HTML 應用程式。

傳送方架構採用非同步回呼設計來通知傳送者 事件的應用程式中,以及轉換應用程式在不同狀態的切換

應用程式流程

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

  • Cast 架構開始 GCKDiscoveryManager敬上 建立 Deployment GCKCastOptions 變更為 即可開始掃描裝置。
  • 當使用者按一下「投放」按鈕時,架構就會顯示「投放」 對話方塊中顯示偵測到的投放裝置清單。
  • 使用者選取投放裝置時,架構會嘗試啟動 Cast 裝置上的 Web Receiver 應用程式
  • 該架構會在傳送端應用程式中叫用回呼,以確認 已啟動 Web Receiver 應用程式。
  • 這個架構可在傳送者和傳送者之間建立通訊管道 網路接收器應用程式。
  • 該架構使用通訊管道來載入及控制媒體 並透過 Web Receiver 播放
  • 架構會同步處理傳送者和傳送端的媒體播放狀態 網路接收端:當使用者進行傳送者 UI 動作時,架構會通過 向 Web 接收器發出的媒體控制要求 傳送媒體狀態更新,此架構會更新傳送者 UI 的狀態。
  • 當使用者點選「投放」按鈕中斷與投放裝置的連線時, 這個架構會中斷傳送端應用程式與 Web Receiver 的連結。

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

查看 Google Cast 的所有課程、方法和活動完整清單 iOS 架構,請參閱 Google Cast iOS API 參考資料。下列各節將說明 將 Cast 整合至 iOS 應用程式

主執行緒的呼叫方法

初始化 Cast 環境

Cast 架構有一個全域單例模式物件 GCKCastContext, 協調架構所有的活動這個物件必須初始化 通常處於應用程式的生命週期中 應用程式委派的 -[application:didFinishLaunchingWithOptions:] 方法,因此 在傳送方應用程式重新啟動時,可正確觸發工作階段恢復功能。

GCKCastOptions 初始化 GCKCastContext 時必須提供物件。 此類別包含會影響架構行為的選項。最常出現 重要的是網路接收端應用程式 ID 探索結果,並在投放工作階段 已開始。

-[application:didFinishLaunchingWithOptions:] 方法也是個好地方 設定記錄委派來接收架構中的記錄訊息。 很適合用於偵錯和疑難排解。

Swift
@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)
    }
  }
}
目標 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 時。使用者初次輕觸 (初次啟動應用程式後按下「投放」按鈕上會顯示權限對話方塊) 可讓使用者將應用程式區域網路存取權授予 網路。之後使用者輕觸「投放」按鈕時,系統就會進行投放 對話方塊會列出找到的裝置。使用者輕觸 裝置連線時,投放按鈕上就會顯示目前的 媒體中繼資料,例如標題、錄音室的名稱和縮圖 圖片),或讓使用者中斷與投放裝置的連線。當使用者 沒有可用裝置時輕觸投放按鈕,也就是畫面 會顯示「找不到裝置的原因」 以及如何排解問題

  • Mini Controller: 使用者投放內容且離開目前畫面時 內容頁面,或展開控制器連往傳送者應用程式的其他畫面, 螢幕底部顯示迷你控制器 查看目前投放的媒體中繼資料,並控製播放作業。

  • 展開控制器: 當使用者投放內容時,點選媒體通知或 會啟動展開的控制器,並顯示 目前播放媒體中繼資料,並提供多個按鈕來控制 媒體播放。

新增「投放」按鈕

這個架構提供投放按鈕元件做為 UIButton 子類別。這項服務可以 加入至應用程式標題列的 UIBarButtonItem 中。一般 UIViewController 子類別可安裝「投放」按鈕,如下所示:

Swift
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
castButton.tintColor = UIColor.gray
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
目標 C
GCKUICastButton *castButton = [[GCKUICastButton alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];
castButton.tintColor = [UIColor grayColor];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:castButton];

根據預設,輕觸按鈕即可開啟 這個架構的重點在於

GCKUICastButton敬上 可以直接加入分鏡腳本

設定裝置探索功能

在這個架構中,系統會自動探索裝置。不需要 明確啟動或停止探索程序,除非您實作自訂 UI。

此架構中的探索作業是由類別管理 GCKDiscoveryManager、 這是 GCKCastContext。 架構會提供預設的 Cast 對話方塊元件,用來選取裝置 控管功能裝置清單會依裝置易記名稱的字母順序排列。

工作階段管理的運作方式

Cast SDK 引進投放工作階段的概念 結合了連線至裝置、啟動 (或加入) 網路的步驟 接收端應用程式,連結至該應用程式,以及初始化媒體控制管道。查看網路接收端 應用程式生命週期指南 ,進一步瞭解投放工作階段和網路接收端的生命週期。

課程由課程管理 GCKSessionManager、 這是 GCKCastContext。 個別工作階段是以類別的子類別表示 GCKSession:例如 GCKCastSession 則代表有投放裝置的工作階段。你可以存取目前播放中的投放內容 工作階段 (如有),做為 GCKSessionManagercurrentCastSession 屬性。

GCKSessionManagerListener敬上 介面可用來監控工作階段事件,例如建立工作階段 停權、恢復和終止架構會自動暫停 工作階段期間,傳送者應用程式進入背景並嘗試繼續執行。 當應用程式返回前景時 (或是在 工作階段執行時,應用程式異常終止/突然終止)。

如果使用「投放」對話方塊,系統會建立工作階段並卸除 來自動回應使用者的手勢否則,應用程式可以啟動及結束 明確方法是 GCKSessionManager

應用程式是否需要進行特殊處理,才能回應工作階段生命週期 就可以透過以下指令註冊一或多個 GCKSessionManagerListener 例項 GCKSessionManagerGCKSessionManagerListener 是一種通訊協定,會定義 工作階段開始和工作階段結束等事件的回呼。

變更串流裝置

保留工作階段狀態是串流傳輸的基礎, 使用者可以使用 Google Home 語音指令,切換不同裝置中的現有音訊和影片串流 應用程式或智慧螢幕。在一部裝置 (來源) 上停止播放媒體,然後於另一部裝置 ( 目的地)。凡是裝有最新韌體的投放裝置,都可以做為來源或目的地 變更串流裝置。

如要在串流傳輸期間取得新的目標裝置,請使用 GCKCastSession#device敬上 資源期間 [sessionManager:didResumeCastSession:] 回呼。

詳情請見 透過網路接收器轉移串流裝置 瞭解詳情

自動重新連線

Cast 架構會新增重新連線邏輯,自動處理重新連線 許多細緻的案例,例如:

  • 在 Wi-Fi 連線暫時中斷的情況下復原
  • 從裝置睡眠狀態復原
  • 從背景執行復原程序
  • 在應用程式當機時復原

媒體控制選項的運作方式

如果您使用支援媒體的 Web 接收器應用程式建立投放工作階段 命名空間 GCKRemoteMediaClient敬上 會自動建立這個架構;也能做為 remoteMediaClient 屬性 GCKCastSession 執行個體。

向網路接收端發出要求的所有 GCKRemoteMediaClient 方法 將傳回 GCKRequest 物件, 以便追蹤該要求A 罩杯 GCKRequestDelegate敬上 指派給這個物件,以接收最終通知 作業結果。

系統應該會顯示 GCKRemoteMediaClient 的例項 可能會由應用程式的多個部分共用,而且確實有一些內部元件 「投放」對話方塊和小型媒體控制項等架構的機構 執行個體。在這邊,GCKRemoteMediaClient 可讓您註冊多個 GCKRemoteMediaClientListener

設定媒體中繼資料

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

Swift
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))
目標 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 用於控制接收器上執行的媒體播放器應用程式,例如播放媒體 暫停及停止播放

Swift
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
}
目標 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 影片格式

如要判斷您的媒體影片格式,請使用 videoInfo 屬性 GCKMediaStatus 來取得目前的 GCKVideoInfo。 這個執行個體包含 HDR TV 格式的類型,以及 像素。4K 格式的變化版本會以列舉方式在 hdrType 屬性中指定 值為 GCKVideoInfoHDRType

新增迷你控制器

根據 Cast Design 檢查清單、 傳送者應用程式應提供永久性控制項,稱為 mini 控制器 應該就能在使用者離開目前內容網頁時顯示。 迷你控制器可即時存取 目前的投放工作階段。

Cast 架構提供了控制列 GCKUIMiniMediaControlsViewController、 您可在要顯示迷你控制器的場景中加入這個圖示。

當傳送方應用程式播放影片或音訊直播時,SDK 會自動在播放/暫停按鈕旁顯示播放/停止按鈕 小控制器中

請參閱自訂 iOS 寄件者 UI,瞭解 傳送端應用程式可以設定 Cast 小工具的外觀。

你可以透過下列兩種方式在傳送端應用程式中新增迷你控制器:

  • 透過包裝的方式,讓 Cast 架構管理迷你控制器的版面配置 同時與您現有的檢視控制器搭配使用
  • 將迷你控制器小工具配置到 在分鏡腳本中提供子檢視畫面

使用 GCKUICastContainerViewController

第一種方法是使用 GCKUICastContainerViewController敬上 其中包含另一個檢視控制器,並新增了 GCKUIMiniMediaControlsViewController 。這種做法受到限制,導致您無法自訂 而且無法設定容器檢視控制器的行為。

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

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

  ...
}
目標 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];
  ...

}
快速
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
    }
  }
}
目標 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

嵌入現有檢視控制器

第二種做法是直接將迷你控制器新增到現有檢視畫面 透過 Kubernetes API createMiniMediaControlsViewController敬上 來建立 GCKUIMiniMediaControlsViewController 然後將該執行個體新增為容器檢視控制器的子檢視畫面。

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

Swift
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
}
目標 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 並將其加入容器檢視控制器,做為子檢視:

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

...
目標 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敬上 會在應顯示迷你控制器時通知主機檢視控制器:

Swift
  func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController,
                                       shouldAppear _: Bool) {
    updateControlBarsVisibility()
  }
目標 C
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

新增展開控制器

根據 Google Cast 設計檢查清單要求傳送者應用程式必須提供已展開的 控制器 不同媒體展開的控制器是全螢幕版本 小控制器

展開的控制器為全螢幕檢視畫面,可讓您完全掌控 遠端媒體播放。這個檢視畫面應可讓投放應用程式管理 可管理的投放工作階段部分 (網路接收器磁碟區除外) 控制和工作階段生命週期 (連線/停止投放)。這個平台提供 媒體工作階段的狀態資訊 (圖片、標題、字幕等) )。

這個檢視畫面的功能是由 GCKUIExpandedMediaControlsViewController敬上 類別

首先,您必須在使用者介面中啟用 投放內容。修改應用程式委派,啟用預設展開控制器:

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

將下列程式碼加入檢視控制器,載入展開控制器 使用者開始投放影片時:

Swift
func playSelectedItemRemotely() {
  GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()

  ...

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

  ...

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

當使用者 就會輕觸迷你控制器

當傳送方應用程式播放影片或音訊直播時,SDK 會自動在播放/暫停按鈕旁顯示播放/停止按鈕 。

請參閱將自訂樣式套用至 iOS 裝置 應用程式 ,瞭解傳送者應用程式如何設定 Cast 小工具的外觀。

音量控制項

Cast 架構會自動管理傳送者應用程式的音量。 架構會自動與網路接收端磁碟區同步 提供的 UI 小工具如要同步處理應用程式提供的滑桿,請使用 GCKUIDeviceVolumeController

實體按鈕音量控制

寄件者裝置上的實體音量按鈕可用來變更 從網路接收端取得投放工作階段的音量 有 physicalVolumeButtonsWillControlDeviceVolume 旗標 GCKCastOptions、 設為 GCKCastContext

Swift
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
目標 C
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                          initWithApplicationID:kReceiverAppID];
GCKCastOptions *options = [[GCKCastOptions alloc]
                                          initWithDiscoveryCriteria :criteria];
options.physicalVolumeButtonsWillControlDeviceVolume = YES;
[GCKCastContext setSharedInstanceWithOptions:options];

處理錯誤

傳送方應用程式必須處理所有錯誤回呼並判斷 Cast 生命週期各階段的最佳回應。應用程式可顯示 錯誤訊息或指示結束「投放」工作階段。

記錄

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

SDK 會使用 GCKLogger 產生偵錯形式的記錄輸出內容 訊息、錯誤和警告這些記錄訊息可協助偵錯, ,用於疑難排解及找出問題。預設情況下,記錄輸出為 未封鎖,但如果指派 GCKLoggerDelegate,傳送端應用程式即可接收 並將這些訊息記錄到系統主控台。

Swift
@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)
    }
  }
}
目標 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

如果也要啟用偵錯和詳細訊息,請在程式碼的後方 設定委派項目 (如上所示):

Swift
let filter = GCKLoggerFilter.init()
filter.minimumLevel = GCKLoggerLevel.verbose
GCKLogger.sharedInstance().filter = filter
目標 C
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setMinimumLevel:GCKLoggerLevelVerbose];
[GCKLogger sharedInstance].filter = filter;

另外,您也可以篩選 GCKLogger。 為各類別設定最低記錄等級,例如:

Swift
let filter = GCKLoggerFilter.init()
filter.setLoggingLevel(GCKLoggerLevel.verbose, forClasses: ["GCKUICastButton",
                                                            "GCKUIImageCache",
                                                            "NSMutableDictionary"])
GCKLogger.sharedInstance().filter = filter
目標 C
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setLoggingLevel:GCKLoggerLevelVerbose
             forClasses:@[@"GCKUICastButton",
                          @"GCKUIImageCache",
                          @"NSMutableDictionary"
                          ]];
[GCKLogger sharedInstance].filter = filter;

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