このデベロッパー ガイドでは、iOS Sender SDK を使用して iOS 送信側アプリに Google Cast のサポートを追加する方法について説明します。
モバイル デバイスやノートパソコンは再生を制御する送信側、Google Cast デバイスはテレビにコンテンツを表示する受信側となります。
送信側フレームワークは、送信側で実行時に存在する Cast クラス ライブラリ バイナリと関連リソースを指します。センダーアプリまたはキャストアプリは、センダーでも実行されているアプリを指します。ウェブ レシーバー アプリとは、ウェブ レシーバーで実行される HTML アプリケーションのことです。
送信側フレームワークは、非同期コールバック設計を使用して、送信側アプリにイベントを通知し、キャスト アプリのライフサイクルのさまざまな状態間を遷移します。
アプリケーションの流れ
送信側 iOS アプリの一般的な実行フローの概要は次のとおりです。
- Cast フレームワークは、
GCKCastOptions
で提供されたプロパティに基づいてGCKDiscoveryManager
を開始し、デバイスのスキャンを開始します。 - ユーザーがキャスト アイコンをクリックすると、フレームワークは検出されたキャスト デバイスのリストを含むキャスト ダイアログを表示します。
- ユーザーがキャスト デバイスを選択すると、フレームワークはキャスト デバイスで Web レシーバー アプリを起動しようとします。
- フレームワークは、送信側アプリでコールバックを呼び出して、ウェブ レシーバー アプリが起動したことを確認します。
- フレームワークは、送信側アプリとウェブ レシーバー アプリの間に通信チャネルを作成します。
- フレームワークは、通信チャネルを使用して Web レシーバーでメディア再生を読み込んで制御します。
- フレームワークは、送信側とウェブ レシーバーの間でメディア再生状態を同期します。ユーザーが送信側の UI アクションを行うと、フレームワークはメディア コントロール リクエストをウェブ レシーバーに渡し、ウェブ レシーバーがメディア ステータスの更新を送信すると、フレームワークは送信側の UI の状態を更新します。
- ユーザーがキャスト アイコンをクリックしてキャスト デバイスから切断すると、フレームワークはセンダーアプリをウェブ レシーバーから切断します。
送信者の問題を解決するには、ロギングを有効にする必要があります。
Google Cast iOS フレームワークのすべてのクラス、メソッド、イベントの一覧については、Google Cast iOS API リファレンスをご覧ください。以下のセクションでは、iOS アプリに Cast を統合する手順について説明します。
メインスレッドからメソッドを呼び出す
キャスト コンテキストを初期化する
キャスト フレームワークには、グローバル シングルトン オブジェクトの GCKCastContext
があり、これによりフレームワークのすべてのアクティビティが調整されます。このオブジェクトは、アプリのライフサイクルの早い段階で初期化する必要があります。通常はアプリのデリゲートの -[application:didFinishLaunchingWithOptions:]
メソッドで初期化し、センダーアプリの再起動時に、セッションの自動再開が適切にトリガーされるようにします。
GCKCastContext
を初期化するときは、GCKCastOptions
オブジェクトを指定する必要があります。このクラスには、フレームワークの動作に影響するオプションが含まれています。最も重要なオプションはウェブ レシーバー アプリケーション 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
サブクラスは、次のように Cast ボタンをインストールできます。
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 を実装する場合を除き、検出プロセスを明示的に開始または停止する必要はありません。
フレームワーク内の検出は、GCKCastContext
のプロパティである GCKDiscoveryManager
クラスによって管理されます。フレームワークは、デバイスの選択と制御のためのデフォルトの Cast ダイアログ コンポーネントを提供します。デバイスリストは、デバイスのフレンドリ名で辞書順に並べ替えられます。
セッション管理の仕組み
Cast SDK では、キャスト セッションという概念が導入されています。キャスト セッションの確立では、デバイスへの接続、ウェブ レシーバー アプリの起動(または参加)、そのアプリへの接続、メディア コントロール チャネルの初期化というプロセスが組み合わされます。Cast セッションと Web レシーバのライフサイクルについて詳しくは、Web レシーバのアプリケーション ライフサイクル ガイドをご覧ください。
セッションは GCKSessionManager
クラスによって管理されます。このクラスは GCKCastContext
のプロパティです。個々のセッションは、GCKSession
クラスのサブクラスで表されます。たとえば、GCKCastSession
はキャスト デバイスとのセッションを表します。現在アクティブな Cast セッション(存在する場合)には、GCKSessionManager
の currentCastSession
プロパティとしてアクセスできます。
GCKSessionManagerListener
インターフェースは、セッションの作成、停止、再開、終了などのセッション イベントのモニタリングに使用できます。フレームワークは、送信側アプリがバックグラウンドに移行するとセッションを自動的に一時停止し、アプリがフォアグラウンドに戻るとセッションの再開を試みます(または、セッションがアクティブな状態でアプリが異常終了または突然終了した後にアプリが再起動された場合)。
キャスト ダイアログが使用されている場合、セッションはユーザー操作に応じて自動的に作成、終了されます。それ以外の場合、アプリは GCKSessionManager
のメソッドを介してセッションを明示的に開始および終了できます。
セッション ライフサイクル イベントに応答して特別な処理を行う必要がある場合、アプリは GCKSessionManager
に 1 つ以上の GCKSessionManagerListener
インスタンスを登録できます。GCKSessionManagerListener
は、セッションの開始や終了などのイベントのコールバックを定義するプロトコルです。
ストリーミング転送
セッションの状態を保持することは、ストリーム転送の基本です。ストリーム転送では、音声コマンド、Google Home アプリ、スマート ディスプレイを使用して、既存の音声ストリームと動画ストリームをデバイス間で移動できます。メディアの再生が 1 つのデバイス(ソース)で停止し、別のデバイス(宛先)で続行されます。最新のファームウェアを搭載した Cast デバイスは、ストリーム転送の転送元または転送先として機能します。
ストリーム転送中に新しい宛先デバイスを取得するには、[sessionManager:didResumeCastSession:]
コールバック中に GCKCastSession#device
プロパティを使用します。
詳しくは、ウェブ レシーバでのストリーム転送をご覧ください。
自動再接続
キャスト フレームワークは、次のような多くの微妙なコーナーケースで再接続を自動的に処理する再接続ロジックを追加します。
- Wi-Fi の一時的な損失から復元する
- デバイスのスリープから復帰する
- アプリのバックグラウンド化から復元する
- アプリがクラッシュした場合に復元する
メディア コントロールの仕組み
メディア名前空間をサポートするウェブ レシーバー アプリで Cast セッションが確立されると、GCKRemoteMediaClient
のインスタンスがフレームワークによって自動的に作成されます。これは GCKCastSession
インスタンスの remoteMediaClient
プロパティとしてアクセスできます。
Web レシーバにリクエストを発行する 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 動画形式
メディアの動画形式を判断するには、GCKMediaStatus
の videoInfo
プロパティを使用して、GCKVideoInfo
の現在のインスタンスを取得します。このインスタンスには、HDR テレビ形式のタイプと、高さと幅(ピクセル単位)が含まれます。4K 形式のバリエーションは、hdrType
プロパティで列挙値 GCKVideoInfoHDRType
によって示されます。
ミニ コントローラを追加する
Cast デザイン チェックリストによると、センダーアプリは、ユーザーが現在のコンテンツ ページから移動するときに表示されるミニ コントローラと呼ばれる永続的なコントロールを提供する必要があります。ミニ コントローラは、すばやくアクセスでき、現在のキャスト セッションの情報が表示されます。
Cast フレームワークには、コントロール バー GCKUIMiniMediaControlsViewController
があり、ミニ コントローラを表示するシーンに追加できます。
センダーアプリが動画や音声のライブ配信を再生している場合、SDK はミニ コントローラの再生/一時停止ボタンの代わりに再生/停止ボタンを自動的に表示します。
センダーアプリで Cast ウィジェットの外観を構成する方法については、iOS センダー UI をカスタマイズするをご覧ください。
ミニ コントローラを送信側アプリに追加する方法は 2 つあります。
- 既存のビュー コントローラを独自のビュー コントローラでラップして、キャスト フレームワークにミニ コントローラのレイアウトを管理させます。
- ミニ コントローラ ウィジェットのレイアウトを自分で管理するには、ストーリーボードでサブビューを指定して、既存のビュー コントローラに追加します。
GCKUICastContainerViewController を使用してラップする
1 つ目の方法は、別のビュー コントローラをラップし、最下部に GCKUIMiniMediaControlsViewController
を追加する GCKUICastContainerViewController
を使用することです。このアプローチでは、アニメーションをカスタマイズしたり、コンテナ ビュー コントローラの動作を構成したりすることはできません。
通常、この最初の方法はアプリ デリゲートの -[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 デザイン チェックリストでは、センダーアプリに、キャストするメディアの拡張コントローラを表示するよう規定されています。拡張コントローラは、ミニ コントローラの全画面バージョンです。
拡張コントローラは全画面表示され、リモートのメディア再生をすべて操作できます。このビューを使用して、センダーアプリでキャスト セッションの管理可能なすべての操作を制御できるようにする必要があります(Web レシーバーの音量調節と、接続やキャストの停止などセッションのライフサイクル制御を除く)。このコントローラには、メディア セッションに関するすべてのステータス情報(アートワーク、タイトル、サブタイトルなど)が表示されます。
このビューの機能は、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 アプリにカスタム スタイルを適用するをご覧ください。
音量の調整
Cast フレームワークは、送信側アプリの音量を自動的に管理します。フレームワークは、指定された UI ウィジェットのウェブ レシーバーの音量と自動的に同期します。アプリが提供するスライダーを同期するには、GCKUIDeviceVolumeController
を使用します。
物理ボタンによる音量調節
送信側デバイスの物理的な音量ボタンを使用して、GCKCastContext
で設定された GCKCastOptions
の physicalVolumeButtonsWillControlDeviceVolume
フラグを使用して、ウェブ レシーバの Cast セッションの音量を変更できます。
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];
エラーを処理する
送信側アプリがすべてのエラー コールバックを処理し、キャストのライフサイクルの各ステージで最適なレスポンスを決定することは非常に重要です。アプリは、ユーザーにエラー ダイアログを表示するか、キャスト セッションを終了するかを決定できます。
ロギング
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;
クラス名は、リテラル名またはグロブ パターン(GCKUI\*
や GCK\*Session
など)にできます。