このデベロッパー ガイドでは、iOS Sender SDK を使用して iOS センダーアプリに Google Cast のサポートを追加する方法について説明します。
モバイル デバイスまたはノートパソコンは再生を制御する送信元 で、Google Cast デバイスはテレビにコンテンツを表示する受信側 です。
送信元フレームワーク とは、送信元で実行時に存在する Cast クラス ライブラリのバイナリと関連リソースを指します。送信元アプリ またはキャストアプリ とは、送信元でも実行されているアプリを指します。ウェブ レシーバー アプリ とは、ウェブ レシーバーで実行されている HTML アプリケーションを指します。
送信元フレームワークは、非同期コールバック設計を使用して、イベントを送信元アプリに通知し、Cast アプリのライフサイクルのさまざまな状態を移行します。
アプリケーションの流れ
送信元 iOS アプリの一般的な実行フローは次のとおりです。
- キャスト フレームワークは
GCKDiscoveryManagerで指定されたプロパティに基づいてGCKCastOptionsを起動し、 デバイスのスキャンを開始します。 - ユーザーがキャスト アイコンをクリックすると、フレームワークは検出されたキャスト デバイスのリストを含むキャスト ダイアログを表示します。
- ユーザーがキャスト デバイスを選択すると、フレームワークはキャスト デバイスでウェブ レシーバー アプリを起動しようとします。
- フレームワークは、ウェブ レシーバー アプリが起動したことを確認するために、送信元アプリでコールバックを呼び出します。
- フレームワークは、送信元アプリとウェブ レシーバー アプリの間に通信チャネルを作成します。
- フレームワークは、通信チャネルを使用して、ウェブ レシーバーでメディアの再生を読み込んで制御します。
- フレームワークは、送信元とウェブ レシーバー間でメディア再生の状態を同期します。ユーザーが送信元 UI で操作を行うと、フレームワークはメディア コントロール リクエストをウェブ レシーバーに渡します。ウェブ レシーバーがメディア ステータスの更新を送信すると、フレームワークは送信元 UI の状態を更新します。
- ユーザーがキャスト アイコンをクリックしてキャスト デバイスとの接続を解除すると、フレームワークは送信元アプリとウェブ レシーバーの接続を解除します。
送信元のトラブルシューティングを行うには、ロギングを有効にする必要があります。
Google Cast iOS フレームワークのすべてのクラス、メソッド、イベントの包括的なリストについては、Google Cast iOS API リファレンスをご覧ください。以下のセクションでは、Cast を iOS アプリに統合する手順について説明します。
メインスレッドからメソッドを呼び出す
キャスト コンテキストを初期化する
キャスト フレームワークには、グローバル シングルトン オブジェクトの
GCKCastContextがあり、これによりフレームワークのすべてのアクティビティが
調整されます。このオブジェクトは、アプリのライフサイクルの早い段階で初期化する必要があります。通常はアプリのデリゲートの -[application:didFinishLaunchingWithOptions:] メソッドで初期化し、送信元アプリの再起動時に、セッションの自動再開が適切にトリガーされるようにします。
GCKCastOptions
オブジェクトを初期化するときは、GCKCastContextを指定する必要があります。
このクラスには、フレームワークの動作に影響するオプションが含まれています。最も重要なオプションはウェブ レシーバー アプリの ID で、検出結果をフィルタし、キャスト セッションの開始時にウェブ レシーバー アプリを起動するために使用されます。
-[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) } } }
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
キャスト UX ウィジェット
Cast iOS SDK には、Cast デザイン チェックリストに準拠した次のウィジェットが用意されています。
イントロダクション オーバーレイ:
GCKCastContextクラスにはpresentCastInstructionsViewControllerOnceWithCastButtonメソッドがあります。 このメソッドを使用して、ウェブ レシーバーが初めて使用可能になったときにキャスト アイコンをハイライト表示できます。送信元アプリでは、テキスト、タイトル テキストの位置、[閉じる] ボタンをカスタマイズできます。キャスト アイコン: Cast iOS Sender SDK 4.6.0 以降では、送信元デバイスが Wi-Fi に接続されている場合、キャスト アイコンは常に表示されます 。アプリを最初に起動した後、ユーザーがキャスト ボタンを初めてタップすると、権限ダイアログが表示され、ユーザーはネットワーク上のデバイスへのローカル ネットワーク アクセスをアプリに許可できます。その後、ユーザーがキャスト アイコンをタップすると、検出されたデバイスのリストが表示されるキャスト ダイアログが表示されます。デバイスが接続されているときにユーザーがキャスト アイコンをタップすると、現在のメディア メタデータ(タイトル、録音スタジオの名前、サムネイル画像など)が表示されるか、キャスト デバイスとの接続を解除できます。使用可能なデバイスがないときにユーザーがキャスト ボタンをタップすると、デバイスが見つからない理由とトラブルシューティング方法に関する情報が表示されます。
ミニ コントローラ: ユーザーがコンテンツをキャストしていて、現在の コンテンツ ページまたは拡張コントローラから送信元アプリの別の画面に移動した場合、 画面の下部にミニ コントローラが表示され、現在キャスト中のメディア メタデータを確認して 再生を制御できます。
拡張コントローラ: ユーザーがコンテンツをキャストしているときに、メディア通知または ミニ コントローラをクリックすると、拡張コントローラが起動し、 現在再生中のメディア メタデータが表示され、メディア再生を制御するための ボタンがいくつか表示されます。
キャスト アイコンを追加する
フレームワークは、UIButton サブクラスとしてキャスト アイコン コンポーネントを提供します。アプリのタイトルバーに追加する場合は、UIBarButtonItem でラップします。一般的な UIViewController サブクラスは、次のようにキャスト アイコンをインストールできます。
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24)) castButton.tintColor = UIColor.gray navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
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 SDK では、キャスト セッションという概念が導入されています。キャスト セッションの確立には、デバイスへの接続、ウェブ レシーバー アプリの起動(または参加)、そのアプリへの接続、メディア コントロール チャネルの初期化という手順が含まれます。キャスト セッションとウェブ レシーバーのライフサイクルについて詳しくは、ウェブ レシーバー アプリケーションのライフサイクル ガイド をご覧ください。
セッションは、
GCKSessionManagerクラスによって管理されます。
これは、
GCKCastContextのプロパティです。
個々のセッションは、クラス
GCKSessionのサブクラスで表されます。たとえば、
GCKCastSession
はキャスト デバイスとのセッションを表します。現在アクティブなキャスト セッション(存在する場合)には、GCKSessionManager の currentCastSession プロパティとしてアクセスできます。
GCKSessionManagerListener
インターフェースを使用して、セッションの作成、
停止、再開、終了などのセッション イベントをモニタリングできます。送信元アプリがバックグラウンドに移行すると、フレームワークはセッションを自動的に一時停止し、アプリがフォアグラウンドに戻ると(またはセッションがアクティブな間にアプリが異常終了または強制終了した後に再起動すると)、セッションの再開を試みます。
キャスト ダイアログを使用している場合、セッションはユーザー操作に応じて自動的に作成、終了されます。それ以外の場合、アプリはセッションを明示的に開始および終了できます
メソッドを使用して
GCKSessionManager。
アプリがセッション ライフサイクル
イベントに応じて特別な処理を行う必要がある場合は、GCKSessionManagerListener インスタンスを
GCKSessionManager に 1 つ以上登録できます。GCKSessionManagerListener は、セッションの開始や終了などのイベントのコールバックを定義するプロトコルです。
ストリーミング転送
セッションの状態を維持することは、ストリーミング転送の基本です。ユーザーは、音声コマンド、Google Home アプリ、スマートディスプレイを使用して、既存の音声ストリームと動画ストリームをデバイス間で移動できます。メディアは 1 つのデバイス(送信元)での再生を停止し、別のデバイス(宛先)で続行されます。最新のファームウェアを搭載したキャスト デバイスは、ストリーミング転送の送信元または宛先として使用できます。
ストリーミング転送中に新しい宛先デバイスを取得するには、
GCKCastSession#device
プロパティを
[sessionManager:didResumeCastSession:]
コールバック中に使用します。
詳しくは、 ウェブ レシーバーでのストリーミング転送 をご覧ください。
自動再接続
キャスト フレームワークは、次のような微妙なコーナーケースで再接続を自動的に処理する再接続ロジックを追加します。
- Wi-Fi の一時的な切断から復旧する
- デバイスのスリープから復旧する
- アプリのバックグラウンド化から復旧する
- アプリがクラッシュした場合に復旧する
メディア コントロールの仕組み
メディア
名前空間をサポートするウェブ レシーバー アプリでキャスト セッションが確立されると、GCKRemoteMediaClient
のインスタンスがフレームワークによって自動的に作成されます。このインスタンスには、
remoteMediaClient プロパティとして
GCKCastSession
インスタンスからアクセスできます。
ウェブ レシーバーにリクエストを発行する GCKRemoteMediaClient のすべてのメソッドは、そのリクエストの追跡に使用できる GCKRequest オブジェクトを返します。このオブジェクトに
GCKRequestDelegate
を割り当てて、オペレーションの最終
結果に関する通知を受け取ることができます。
GCKRemoteMediaClient のインスタンスはアプリの複数の部分で共有されることが想定されています。実際、キャスト ダイアログやミニ メディア コントロールなどのフレームワークの内部コンポーネントは、インスタンスを共有しています。そのため、GCKRemoteMediaClient
複数の
GCKRemoteMediaClientListenerの登録をサポートしています。
メディア メタデータを設定する
GCKMediaMetadata
クラスは、キャストするメディア アイテムに関する情報を表します。次の例では、映画の新しい GCKMediaMetadata インスタンスを作成し、タイトル、サブタイトル、録音スタジオの名前、2 つの画像を設定します。
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))
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 }
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 デザイン チェックリストによると、送信元アプリは、ユーザーが現在のコンテンツ ページから移動したときに表示されるミニ コントローラと呼ばれる永続的なコントロールを提供する必要があります。ミニ コントローラは、すばやくアクセスでき、現在の Cast セッションの情報が表示されます。
キャスト フレームワークには、コントロール バー
GCKUIMiniMediaControlsViewController
があり、ミニ コントローラを表示するシーンに追加できます。
送信元アプリが動画または音声のライブ ストリームを再生している場合、SDK はミニ コントローラの再生/一時停止ボタンの代わりに再生/停止ボタンを自動的に表示します。
送信元アプリでキャスト ウィジェットの外観を構成する方法については、iOS Sender UI をカスタマイズするをご覧ください。
ミニ コントローラを送信元アプリに追加する方法は 2 つあります。
- キャスト フレームワークにミニ コントローラのレイアウトを管理させます。既存のビュー コントローラを独自のビュー コントローラでラップします。
- ストーリーボードでサブビューを指定して、既存のビュー コントローラに追加することで、ミニ コントローラ ウィジェットのレイアウトを自分で管理します。
GCKUICastContainerViewController を使用してラップする
1 つ目の方法は、
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() ... }
- (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 } } }
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
既存のビュー コントローラに埋め込む
2 つ目の方法は、ミニ コントローラを既存のビュー
コントローラに直接追加することです。これを行うには、
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 }
- (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) } } ...
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() }
- (void)miniMediaControlsViewController: (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController shouldAppear:(BOOL)shouldAppear { [self updateControlBarsVisibility]; }
拡張コントローラを追加する
Google Cast デザイン チェックリストでは、センダーアプリに、キャストするメディアの拡張 コントローラ を表示するよう規定されています。拡張コントローラは、ミニ コントローラの全画面バージョンです。
拡張コントローラは全画面表示され、リモートのメディア再生をすべて操作できます。このビューを使用して、センダーアプリでキャスト セッションの管理可能なすべての操作を制御できるようにする必要があります(ウェブ レシーバーの音量調節と、接続やキャストの停止などセッションのライフサイクル制御を除く)。このコントローラには、メディア セッションに関するすべてのステータス情報(アートワーク、タイトル、サブタイトルなど)が表示されます。
このビューの機能は、
GCKUIExpandedMediaControlsViewController
クラスによって実装されます。
まず、キャスト コンテキストでデフォルトの拡張コントローラを有効にする必要があります。アプリ デリゲートを変更して、デフォルトの拡張コントローラを有効にします。
func applicationDidFinishLaunching(_ application: UIApplication) { .. GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true ... }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES; .. }
次のコードをビュー コントローラに追加して、ユーザーが動画のキャストを開始したときに拡張コントローラを読み込みます。
func playSelectedItemRemotely() { GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls() ... // Load your media sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInformation) }
- (void)playSelectedItemRemotely { [[GCKCastContext sharedInstance] presentDefaultExpandedMediaControls]; ... // Load your media [self.sessionManager.currentSession.remoteMediaClient loadMedia:mediaInformation]; }
拡張コントローラは、ユーザーがミニ コントローラをタップしたときも自動的に起動します。
送信元アプリが動画または音声のライブ ストリームを再生している場合、SDK は拡張コントローラの再生/一時停止ボタンの代わりに再生/停止ボタンを自動的に表示します。
送信元アプリでキャスト ウィジェットの外観を構成する方法については、iOS アプリにカスタム スタイルを適用するをご覧ください。
音量の調整
キャスト フレームワークは、送信元アプリの音量を自動的に管理します。フレームワークは、指定された UI ウィジェットのウェブ レシーバーの音量と自動的に同期します。アプリが提供するスライダーを同期するには、
GCKUIDeviceVolumeController を使用します。
物理ボタンの音量調節
送信元デバイスの物理的な音量ボタンを使用して、
ウェブ レシーバーの Cast セッションの音量を変更できます。これには、
physicalVolumeButtonsWillControlDeviceVolume フラグを
GCKCastOptionsで使用します。
このフラグは
GCKCastContextで設定されます。
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID) let options = GCKCastOptions(discoveryCriteria: criteria) options.physicalVolumeButtonsWillControlDeviceVolume = true GCKCastContext.setSharedInstanceWith(options)
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc] initWithApplicationID:kReceiverAppID]; GCKCastOptions *options = [[GCKCastOptions alloc] initWithDiscoveryCriteria :criteria]; options.physicalVolumeButtonsWillControlDeviceVolume = YES; [GCKCastContext setSharedInstanceWithOptions:options];
エラーを処理する
送信元アプリは、すべてのエラー コールバックを処理し、Cast ライフサイクルの各段階で最適なレスポンスを決定することが非常に重要です。アプリはユーザーにエラー ダイアログを表示することも、キャスト セッションを終了することもできます。
GCKErrorCode
GCKErrorCodeCancelled など、一部のエラーは意図された動作です。
GCKErrorCodeCancelled で失敗した接続を再試行しないでください。予期しない動作が発生する可能性があります。
ロギング
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) } } }
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
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
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init]; [filter setLoggingLevel:GCKLoggerLevelVerbose forClasses:@[@"GCKUICastButton", @"GCKUIImageCache", @"NSMutableDictionary" ]]; [GCKLogger sharedInstance].filter = filter;
クラス名は、リテラル名または glob パターン(GCKUI\*、GCK\*Session など)にできます。