将 Cast 集成到您的 iOS 应用中

本开发者指南介绍了如何为 iOS 设备添加 Google Cast 支持 发送应用。

移动设备或笔记本电脑是控制播放的发送方,并且 Google Cast 设备是在电视上显示内容的接收器

发送器框架是指 Cast 类库二进制文件和相关联的 发送器上存在的资源发送方应用投屏应用 指同时在发送端运行的应用。Web 接收器应用 是指在网络接收器上运行的 HTML 应用。

发送者框架使用异步回调设计来告知发送者 事件应用,以及在 Cast 应用生命周期的各种状态之间转换 循环。

应用流程

以下步骤描述了发送者的典型概要执行流程 iOS 应用:

  • Cast 框架启动 GCKDiscoveryManager 其中提供的属性 GCKCastOptions至 开始扫描设备。
  • 当用户点击“投射”按钮时,框架会显示“投射”按钮 对话框,其中包含发现的 Cast 设备列表。
  • 当用户选择 Cast 设备时,框架会尝试启动 Cast 设备上的 Web 接收器应用。
  • 框架调用发送者应用中的回调来确认 Web 接收器应用已启动。
  • 该框架在发送者和 Web 接收器应用。
  • 该框架使用通信渠道加载和控制媒体 在网络接收器上播放。
  • 该框架会在发送器和 Web 接收器:当用户执行发送器界面操作时,框架会传递 向网络接收器发送这些媒体控制请求,以及 发送媒体状态更新时,框架会更新发送者界面的状态。
  • 当用户点击“投射”按钮以断开与投射设备的连接时, 框架将断开发送器应用与网络接收器的连接。

要对发件人进行问题排查,您需要启用日志记录

有关 Google Cast 中所有类、方法和事件的完整列表 iOS 框架,请参阅 Google Cast iOS API 参考文档。以下各部分介绍了相关步骤 了解如何将 Cast 集成到 iOS 应用中。

从主线程调用方法

初始化 Cast 上下文

Cast 框架有一个全局单例对象,即 GCKCastContext, 协调框架的所有活动此对象必须初始化 在应用程序生命周期的早期阶段, -[application:didFinishLaunchingWithOptions:] 方法,因此 确保发件人应用重启时可以正确触发自动会话恢复功能。

GCKCastOptions 对象时,必须提供对象。GCKCastContext 此类包含会影响框架行为的选项。最 其中重要的是 Web 接收器应用 ID 发现结果,并在投放会话 。

-[application:didFinishLaunchingWithOptions:] 方法也是一个不错的选择 设置日志记录委托,以从框架接收日志记录消息。 它们对于调试和问题排查非常有用。

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
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)
    }
  }
}
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 用户体验 widget

Cast iOS SDK 提供了这些符合 Cast 设计的微件 核对清单:

  • 入门叠加层GCKCastContext 类有一个方法, presentCastInstructionsViewControllerOnceWithCastButton、 可用于在网络接收器首次运行时使“投射”按钮突出显示 可用。发送者应用可以自定义标题的文本和位置 文字和“关闭”按钮。

  • Cast Button: 从 Cast iOS 发送器 SDK 4.6.0 开始,“投放”按钮始终可见 当发送方设备连接到 Wi-Fi 时。用户首次点按时 后,在“投放”按钮上,即会显示一个权限对话框 这样用户就可以向此应用授予对本地设备的访问权限 网络。随后,当用户点按投放按钮时,系统会投放一次 对话框,其中会列出发现的设备。当用户点按 投射按钮上会显示当前的 媒体元数据(例如标题、录音室名称和缩略图) 图片)或允许用户断开与投放设备的连接。当用户 在没有任何可用设备时点按投放按钮,屏幕会显示 会显示相关信息,让用户了解找不到设备的原因 以及如何排查问题。

  • 迷你控制器: 当用户在投放内容时离开了当前 内容页面或展开的控制器转移到发送应用中的其他屏幕时, 迷你控制器显示在屏幕底部 查看当前投放的媒体元数据并控制播放。

  • 展开后的控制器: 用户投放内容时,如果他们点击媒体通知或 迷你控制器时,展开的控制器会启动,显示 并提供了多个按钮来控制 媒体播放。

添加“投放”按钮

该框架提供了一个“投射”按钮组件作为 UIButton 子类。它可以 可以将其添加到应用的标题栏中,只需将其封装在 UIBarButtonItem 中即可。典型 UIViewController 子类可以安装“投射”按钮,如下所示:

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
Swift
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];

默认情况下,点按该按钮会打开“投射”对话框,该对话框由 框架。

GCKUICastButton 也可以直接添加到故事板中

配置设备发现

在该框架中,设备发现会自动进行。无需 明确启动或停止发现流程,除非您实现自定义界面。

框架中的发现由 类管理 GCKDiscoveryManager、 它是 GCKCastContext。通过 框架提供了一个默认的“投射”对话框组件,用于选择设备和 控制。设备列表按设备易记名称的字典顺序排序。

会话管理的运作方式

Cast SDK 引入了 Cast 会话的概念,即 它的创建过程中,涉及到连接设备、启动(或加入)Web 的步骤 接收方应用、连接到该应用并初始化媒体控制渠道。查看网络接收器 应用生命周期指南 详细了解 Cast 会话和网络接收器生命周期。

会话由 类管理 GCKSessionManager、 它是 GCKCastContext。 各个会话由类的子类表示。 GCKSession:例如 GCKCastSession 表示与投放设备的会话。您可以访问当前正在投放的 Cast 内容 会话(如果有)设置为 GCKSessionManagercurrentCastSession 属性。

通过 GCKSessionManagerListener 界面可用于监控会话事件,例如会话创建、 暂停、恢复和终止。框架自动挂起 当发送器应用进入后台并尝试恢复时,系统启动的会话数量 当应用返回前台(或在 会话处于活动状态时异常/突然终止应用)。

如果使用“投射”对话框,系统会创建并销毁会话 自动响应用户手势。否则,应用可能会启动和结束 来明确指定这些对象, GCKSessionManager

如果应用需要执行特殊处理以响应会话生命周期 事件,它可以将一个或多个 GCKSessionManagerListener 实例注册到 GCKSessionManagerGCKSessionManagerListener 是一种协议,定义了 会话开始、会话结束等事件的回调。

流式传输

保留会话状态是数据流传输的基础, 用户可以使用语音指令、Google Home 跨设备移动现有音频和视频流 应用或智能显示屏媒体在一台设备(源设备)上停止播放,然后在另一台设备(源设备)上继续播放 目标)。任何具有最新固件的投放设备都可以在 流式传输。

如需在流式传输期间获取新的目标设备,请使用 GCKCastSession#device 属性 [sessionManager:didResumeCastSession:] 回调。

请参阅 通过网络接收器进行流式传输

自动重新连接

Cast 框架添加了重新连接逻辑,以自动处理重新连接 例如:

  • 在 Wi-Fi 暂时丢失的情况下恢复
  • 从设备休眠状态中恢复
  • 从应用后台恢复
  • 在应用崩溃时恢复

媒体控件的工作原理

如果通过支持媒体的 Web 接收器应用建立 Cast 会话 命名空间中 GCKRemoteMediaClient 由框架自动创建它可作为 的 remoteMediaClient 属性 GCKCastSession 实例。

GCKRemoteMediaClient 上向网络接收器发出请求的所有方法 将返回 GCKRequest 对象, 来跟踪相应请求答 GCKRequestDelegate 可以分配给该对象,以接收有关 操作的结果。

GCKRemoteMediaClient 的实例应该 可能会由应用的多个部分共享,而实际上某些内部组件 框架(例如 Cast 对话框和迷你媒体控件)确实会共享 实例。为此,GCKRemoteMediaClient 支持在 YAML 文件中 GCKRemoteMediaClientListener

设置媒体元数据

通过 GCKMediaMetadata 类表示您要投射的媒体项的相关信息。以下 示例创建了电影的新 GCKMediaMetadata 实例并设置标题, 副标题、录音棚的名称和两张图片。

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
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))
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 用于控制在接收器上运行的媒体播放器应用,例如播放; 暂停和停止。

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
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
}
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 视频格式

如需确定媒体使用的是哪种视频格式,请使用 videoInfo 属性: GCKMediaStatus 获取 GCKVideoInfo。 此实例包含 HDR TV 格式的类型以及 像素。4K 格式的变体在 hdrType 属性中由枚举指示 值 GCKVideoInfoHDRType

添加迷你控制器

根据 Cast Design 核对清单, 发送器应用应提供一个名为迷你头像 (Mini) 的持久控件, 控制器 在用户离开当前内容页面时显示。 迷你控制器可提供快速访问和可见提醒, 。

Cast 框架提供了一个控制栏 GCKUIMiniMediaControlsViewController、 您可以将其添加至要显示迷你控制器的场景。

当您的发送器应用正在播放视频或音频直播时,SDK 会 自动显示播放/停止按钮,代替播放/暂停按钮 迷你控制器中

请参阅自定义 iOS 发送者界面以了解您的 发送器应用可以配置 Cast 微件的外观。

您可以通过以下两种方式将迷你控制器添加到发送器应用:

  • 通过封装以下代码,让 Cast 框架管理迷你控制器的布局 现有视图控制器及其自己的视图控制器相关联。
  • 将迷你控制器微件添加到 通过在故事板中提供子视图来自定义现有视图控制器。

使用 GCKUICastContainerViewController 进行封装

第一种方法是使用 GCKUICastContainerViewController 它封装了另一个视图控制器,并添加一个 GCKUIMiniMediaControlsViewController 。此方法的局限性在于您无法自定义 动画,并且无法配置容器视图控制器的行为。

第一种方法通常在 -[application:didFinishLaunchingWithOptions:] 方法:

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
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()

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

}
<ph type="x-smartling-placeholder">
</ph>
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 实例,然后将其作为子视图添加到容器视图控制器。

在应用委托中设置视图控制器:

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
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
}
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 实例,并将其作为子视图添加到容器视图控制器:

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
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)
    }
  }

...
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 告知主机视图控制器迷你控制器应可见时:

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
Swift
  func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController,
                                       shouldAppear _: Bool) {
    updateControlBarsVisibility()
  }
Objective-C
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

添加展开的控制器

Google Cast 设计核对清单要求发送设备应用提供扩展式 控制器 。展开的控制器是 迷你控制器

展开的控制器为全屏视图,可提供对 远程媒体播放。此视图应允许投放应用管理 投放会话的可管理方面(网络接收器音量除外) 控制和会话生命周期(连接/停止投射)。它还提供 有关媒体会话的状态信息(海报图片、标题、副标题等) )。

此视图的功能由 GCKUIExpandedMediaControlsViewController 类。

首先,您需要在 投射上下文。修改应用委托以启用默认的展开控制器:

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
Swift
func applicationDidFinishLaunching(_ application: UIApplication) {
  ..

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

将以下代码添加到您的视图控制器,以加载展开的控制器 当用户开始投射视频时触发:

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
Swift
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 widget 的外观。

音量控制

Cast 框架会自动管理发送器应用的音量。通过 框架会自动与 Web Receiver 卷同步,以便 提供的 UI 微件。要同步应用提供的滑块,请使用 GCKUIDeviceVolumeController

实体按钮音量控制

发送器设备上的实体音量按钮可用于更改 使用 physicalVolumeButtonsWillControlDeviceVolume 标记上 GCKCastOptions, 该值是在 GCKCastContext

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
Swift
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 生命周期的每个阶段提供最佳响应。应用可以显示 错误对话框,也可以决定结束投放会话。

日志记录

GCKLogger 是框架用于日志记录的单例。使用 GCKLoggerDelegate 以自定义处理日志消息的方式。

使用 GCKLogger,SDK 以调试的形式生成日志记录输出 消息、错误和警告。这些日志消息有助于调试, 进行问题排查和发现问题的方法。默认情况下,日志输出为 但通过分配 GCKLoggerDelegate,发送方应用可以接收 并将这些消息从 SDK 中记录下来,并记录到系统控制台。

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
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)
    }
  }
}
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

要同时启用调试消息和详细消息,请将下面这行代码添加到 设置委托(如前所述):

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
Swift
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。 为每个类设置最低日志记录级别,例如:

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph>
Swift
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