このデベロッパー ガイドでは、iOS Sender SDK を使用して iOS 送信者アプリに Google Cast のサポートを追加する方法について説明します。
モバイル デバイスまたはノートパソコンは再生操作を行う「送信側」であり、Google Cast デバイスはテレビ上にコンテンツを表示する「レシーバー」です。
送信者フレームワークとは、実行時に送信者に表示される Cast クラス ライブラリ バイナリと関連リソースを指します。送信者アプリまたはキャストアプリとは、送信者で実行されるアプリを意味します。ウェブレシーバー アプリは、ウェブレシーバーで実行される HTML アプリケーションを指します。
送信側フレームワークは、非同期コールバック設計を使用して、送信側アプリにイベントを通知し、キャストアプリのライフサイクルのさまざまな状態の間で遷移します。
アプリケーションの流れ
送信者の iOS アプリに対する一般的な実行フローの概要は次のとおりです。
- キャスト フレームワークは、
GCKCastOptions
で提供されるプロパティに基づいてGCKDiscoveryManager
を開始し、デバイスのスキャンを開始します。 - ユーザーがキャスト ボタンをクリックすると、検出されたキャスト デバイスのリストが記載されたキャスト ダイアログが表示されます。
- ユーザーがキャスト デバイスを選択すると、フレームワークはキャスト デバイスでウェブレシーバー アプリの起動を試みます。
- フレームワークは送信側のアプリでコールバックを呼び出して、Web Receiver アプリが起動されたことを確認します。
- フレームワークは、送信者アプリと Web Receiver アプリ間の通信チャネルを作成します。
- フレームワークは通信チャネルを使用して、ウェブ レシーバでのメディア再生の読み込みと制御を行います。
- フレームワークは送信者とウェブ受信者の間でメディアの再生状態を同期します。ユーザーが送信者の UI 操作を行うと、フレームワークはメディア制御リクエストをウェブ受信者に送信し、ウェブ受信者がメディア ステータスの更新を送信すると、フレームワークが送信者 UI の状態を更新します。
- ユーザーがキャスト アイコンをクリックしてキャスト デバイスから切断すると、フレームワークは送信側アプリとウェブレシーバーとの接続を解除します。
送信者のトラブルシューティングを行うには、ロギングを有効にする必要があります。
Google Cast iOS フレームワーク内のすべてのクラス、メソッド、イベントの完全なリストについては、Google Cast iOS API リファレンスをご覧ください。以降のセクションでは、キャストを iOS アプリに統合する手順を説明します。
メインスレッドからメソッドを呼び出す
キャスト コンテキストを初期化する
キャスト フレームワークには、フレームワークのすべてのアクティビティを調整するグローバル シングルトン オブジェクト GCKCastContext
があります。このオブジェクトは、アプリのライフサイクルの早い段階で(通常はアプリ デリゲートの -[application:didFinishLaunchingWithOptions:]
メソッドで)初期化する必要があります。これにより、送信者アプリの再起動時の自動セッション再開が正しくトリガーされます。
GCKCastContext
を初期化するときに、GCKCastOptions
オブジェクトを指定する必要があります。このクラスには、フレームワークの動作に影響するオプションが含まれています。最も重要なのは Web Receiver アプリケーション ID です。この ID を使用して、検出結果をフィルタし、キャスト セッションの開始時に Web Receiver アプリを起動します。
また、-[application:didFinishLaunchingWithOptions:]
メソッドは、フレームワークからロギング メッセージを受信するようにロギング デリゲートを設定するのに適しています。デバッグやトラブルシューティングに役立ちます。
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate { let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID let kDebugLoggingEnabled = true var window: UIWindow? func applicationDidFinishLaunching(_ application: UIApplication) { let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID) let options = GCKCastOptions(discoveryCriteria: criteria) GCKCastContext.setSharedInstanceWith(options) // Enable logger. GCKLogger.sharedInstance().delegate = self ... } // MARK: - GCKLoggerDelegate func logMessage(_ message: String, at level: GCKLoggerLevel, fromFunction function: String, location: String) { if (kDebugLoggingEnabled) { print(function + " - " + message) } } }
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 には、キャスト デザイン チェックリストに準拠したウィジェットが用意されています。
入門オーバーレイ:
GCKCastContext
クラスには、presentCastInstructionsViewControllerOnceWithCastButton
メソッドが用意されています。このメソッドを使用すると、ウェブレシーバーが初めて使用可能になったときにキャスト アイコンにスポットライトを当てることができます。送信者のアプリは、テキスト、タイトル テキストの位置、閉じるボタンをカスタマイズできます。キャスト アイコン: Cast iOS センダー 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 を実装しない限り、検出プロセスを明示的に開始または停止する必要はありません。
フレームワーク内の検出は、GCKCastContext
のプロパティであるクラス GCKDiscoveryManager
によって管理されます。このフレームワークには、デバイスの選択や制御用のデフォルトのキャスト ダイアログ コンポーネントが用意されています。デバイスリストは、わかりやすい名前順に並べられます。
セッション管理の仕組み
Cast SDK では、キャスト セッションのコンセプトが導入されています。つまり、デバイスへの接続、ウェブレシーバー アプリの起動(または参加)、アプリへの接続、メディア コントロール チャネルの初期化というステップが確立されます。キャスト セッションとウェブレシーバーのライフサイクルについて詳しくは、ウェブレシーバーのアプリケーション ライフサイクル ガイドをご覧ください。
セッションは、GCKCastContext
のプロパティである GCKSessionManager
クラスによって管理されます。個々のセッションは、GCKSession
クラスのサブクラスで表されます。たとえば、GCKCastSession
はキャスト デバイスとのセッションを表します。現在アクティブなキャスト セッション(存在する場合)には、GCKSessionManager
の currentCastSession
プロパティとしてアクセスできます。
GCKSessionManagerListener
インターフェースを使用すると、セッションの作成、停止、再開、終了などのセッション イベントをモニタリングできます。送信者のアプリがバックグラウンドに移動した場合、フレームワークはセッションを一時停止し、アプリがフォアグラウンドに戻ると(セッションがアクティブなときにアプリの異常/突然のアプリの終了後に再起動するように)、再開を試みます。
キャスト ダイアログを使用すると、ユーザーの操作に応じてセッションが作成され、自動的に破棄されます。そうしないと、アプリは GCKSessionManager
のメソッドを使用して、セッションを明示的に開始および終了できます。
アプリがセッション ライフサイクル イベントに応じて特別な処理を行う必要がある場合は、1 つ以上の GCKSessionManagerListener
インスタンスを GCKSessionManager
に登録できます。GCKSessionManagerListener
は、セッションの開始や終了などのイベントのコールバックを定義するプロトコルです。
ストリーミング転送
セッション状態を維持することはストリーム転送の基礎であり、ユーザーは音声コマンド、Google Home アプリ、スマートディスプレイを使用して、既存の音声や動画のストリームをデバイス間で移動できます。メディアは、あるデバイス(ソース)で再生を停止し、別のデバイス(宛先)で再生を続けます。最新のファームウェアを搭載したキャスト デバイスは、ストリーミング転送でソースまたは宛先として機能できます。
ストリーム転送中に新しい宛先デバイスを取得するには、[sessionManager:didResumeCastSession:]
コールバック中に GCKCastSession#device
プロパティを使用します。
詳細については、ウェブレシーバーでのストリーム転送をご覧ください。
自動再接続
キャスト フレームワークには、次のような微妙なケースで再接続を自動的に処理するための再接続ロジックが追加されています。
- Wi-Fi の一時的な障害から回復する
- デバイスのスリープから回復する
- アプリのバックグラウンドから復元する
- アプリがクラッシュした場合に復元する
メディア コントロールの仕組み
メディア名前空間をサポートするウェブ レシーバ アプリでキャスト セッションが確立されている場合、フレームワークは GCKRemoteMediaClient
のインスタンスを自動的に作成します。このインスタンスは、GCKCastSession
インスタンスの remoteMediaClient
プロパティとしてアクセスできます。
ウェブ レシーバにリクエストを発行する 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 TV 形式のタイプと高さと幅(ピクセル単位)が含まれています。4K 形式のバリアントは、hdrType
プロパティにおいて列挙値 GCKVideoInfoHDRType
によって示されます。
ミニ コントローラを追加する
キャスト デザイン チェックリストによれば、送信側アプリは、現在のコンテンツ ページからユーザーが移動したときに表示されるミニ コントローラと呼ばれる永続的なコントロールを提供する必要があります。ミニ コントローラは、現在のキャスト セッションへの即時アクセスと目に見えるリマインダーを提供します。
キャスト フレームワークにはコントロール バー GCKUIMiniMediaControlsViewController
が用意されており、ミニ コントローラを表示するシーンに追加できます。
送信側アプリが動画や音声のライブ配信を再生すると、SDK はミニ コントローラの再生/一時停止ボタンの代わりに再生/停止ボタンを自動的に表示します。
キャストアプリでの外観については、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 Receiver の音量調整とセッションのライフサイクル(接続/停止)は例外です。また、メディア セッションに関するすべてのステータス情報(アートワーク、タイトル、サブタイトルなど)も提供します。
このビューの機能は、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
を使用します。
物理ボタンの音量調節
送信側のデバイスの物理音量ボタンを使用すると、GCKCastContext
に設定されている GCKCastOptions
の physicalVolumeButtonsWillControlDeviceVolume
フラグを使用して、ウェブレシーバーでのキャスト セッションの音量を変更できます。
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
を使用して、ログメッセージの処理方法をカスタマイズします。
SDK は GCKLogger
を使用して、デバッグ メッセージ、エラー、警告の形式でログ出力を生成します。これらのログメッセージは、デバッグや問題のトラブルシューティング、識別に役立ちます。デフォルトではログ出力は抑制されていますが、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
)。