1. 概要
この Codelab では、Google Cast 対応デバイスでコンテンツをキャストするように既存の iOS 動画アプリを変更する方法について説明します。
Google Cast とは
Google Cast では、ユーザーはモバイル デバイスからテレビにコンテンツをキャストできます。ユーザーは自分のモバイル デバイスをリモコンとして使い、テレビでのメディア再生を行うことが可能です。
Google Cast SDK を使用すると、アプリを拡張して Google Cast 対応デバイス(テレビやサウンド システムなど)を操作できます。Cast SDK を使用すると、Google Cast デザイン チェックリストに基づいて必要な UI コンポーネントを追加できます。
Google Cast デザイン チェックリストは、サポートされているすべてのプラットフォームにわたって、Cast ユーザー エクスペリエンスをシンプルで予測可能なものにするために使用します。
達成目標
この Codelab を完了すると、iOS 動画アプリで Google Cast デバイスに動画をキャストできるようになります。
学習内容
- サンプル動画アプリに Google Cast SDK を追加する方法
- Google Cast デバイスを選択するキャスト アイコンを追加する方法
- キャスト デバイスに接続してメディア レシーバーを起動する方法
- 動画をキャストする方法
- Cast ミニ コントローラをアプリに追加する方法
- 拡張コントローラを追加する方法
- 案内用のオーバーレイを提供する方法
- キャスト ウィジェットをカスタマイズする方法
- Cast Connect の統合方法
必要なもの
- 最新の Xcode。
- iOS 9 以降(または Xcode シミュレータ)を搭載した 1 台のモバイル デバイス。
- モバイル デバイスを開発用コンピュータに接続するための USB データケーブル(デバイスを使用している場合)。
- Chromecast や Android TV など、インターネットにアクセスできる Google Cast デバイス。
- HDMI 入力対応のテレビまたはモニター
- Chromecast with Google TV は、Cast Connect の統合のテストに必要ですが、残りの Codelab ではオプションです。お持ちでない場合は、このチュートリアルの最後にある Cast Connect サポートの追加の手順をスキップしてください。
テスト
- iOS 開発に関する以前の知識が必要です。
- 一般的なテレビの視聴経験も必要です。
このチュートリアルの利用方法をお選びください。
iOS アプリの開発経験についてどう思われますか?
テレビ視聴のご経験についてお答えください。
2. サンプルコードを取得する
すべてのサンプルコードをパソコンにダウンロードできます。
ダウンロードした ZIP ファイルを解凍します。
3. サンプルアプリを実行する
まず、完成したサンプルアプリがどのようなものか見てみましょう。このアプリは基本的な動画プレーヤーです。ユーザーはリストから動画を選択し、デバイス上でローカルに再生するか、Google Cast デバイスにキャストできます。
ダウンロードしたコードを使用して、完成したサンプルアプリを Xcode で開いて実行する方法について説明します。
よくある質問
CocoaPods の設定
CocoaPods を設定するには、コンソールに移動し、macOS で利用可能なデフォルトの Ruby を使用してインストールします。
sudo gem install cocoapods
問題がある場合は、公式ドキュメントを参照して依存関係マネージャーをダウンロードしてインストールしてください。
プロジェクトの設定
- ターミナルに移動して、Codelab ディレクトリに移動します。
- Podfile から依存関係をインストールします。
cd app-done pod update pod install
- Xcode を開き、[Open another project...] を選択します。
- サンプルコード フォルダの
app-done
ディレクトリからCastVideos-ios.xcworkspace
ファイルを選択します。
アプリを実行する
ターゲットとシミュレータを選択してアプリを実行します。
数秒後に動画アプリが表示されます。
受信ネットワーク接続の許可に関する通知が表示されたら、[Allow] をクリックしてください。このオプションに対応していない場合、キャスト アイコンは表示されません。
キャスト アイコンをクリックし、使用する Google Cast デバイスを選択します。
動画を選択し、再生ボタンをクリックします。
Google Cast デバイスで動画の再生が開始されます。
拡張コントローラが表示されます。再生と一時停止ボタンを使用して再生をコントロールできます。
動画のリストに戻ります。
画面の下部にミニ コントローラが表示されるようになりました。
ミニ コントローラの一時停止ボタンをクリックすると、レシーバーの動画が一時停止します。ミニ コントローラの再生ボタンをクリックして、動画の再生を再開します。
キャスト ボタンをクリックすると、Google Cast デバイスへのキャストが停止します。
4. 開始プロジェクトを準備する
ダウンロードした開始用アプリに Google Cast のサポートを追加する必要があります。この Codelab では、以下の Google Cast の用語を使用します。
- 送信側アプリはモバイル デバイスやノートパソコンで動作します。
- レシーバー アプリは Google Cast デバイスで動作します。
プロジェクトの設定
これで、Xcode を使用してスターター プロジェクトの上に構築する準備が整いました。
- ターミナルに移動して、Codelab ディレクトリに移動します。
- Podfile から依存関係をインストールします。
cd app-start pod update pod install
- Xcode を開き、[Open another project...] を選択します。
- サンプルコード フォルダの
app-start
ディレクトリからCastVideos-ios.xcworkspace
ファイルを選択します。
アプリの設計
アプリはリモートのウェブサーバーから動画のリストを取得し、ユーザーがブラウジングできるようにリストを提供します。動画を選択すると、その詳細情報を表示したり、モバイル デバイスで動画をローカルに再生したりできます。
アプリは、MediaTableViewController
と MediaViewController.
の 2 つのメインビュー コントローラで構成されています。
MediaTableViewController
この UITableViewController は、MediaListModel
インスタンスの動画のリストを表示します。動画のリストと関連するメタデータは、JSON ファイルとしてリモート サーバー上でホストされます。MediaListModel
はこの JSON を取得して処理し、MediaItem
オブジェクトのリストを作成します。
MediaItem
オブジェクトは、動画とそれに関連するメタデータ(タイトル、説明、画像の URL、ストリームの URL など)をモデル化します。
MediaTableViewController
が MediaListModel
インスタンスを作成して自身を MediaListModelDelegate
として登録し、メディア メタデータがダウンロードされたときに通知を受け取り、テーブルビューを読み込めるようにします。
動画のサムネイルのリストと、各動画の簡単な説明が表示されます。アイテムを選択すると、対応する MediaItem
が MediaViewController
に渡されます。
MediaViewController
このビュー コントローラは、特定の動画のメタデータを表示し、ユーザーがモバイル デバイスで動画をローカルに再生できるようにします。
ビュー コントローラは、LocalPlayerView
、一部のメディア コントロール、テキストエリアをホストし、選択した動画の説明を表示します。プレーヤーは画面の上部を覆い、その下に動画の詳細情報が表示される余地があります。ユーザーはこの画面でローカル動画の再生、一時停止、再生を行えます。
よくある質問
5. キャスト アイコンを追加する
Cast 対応アプリには、それぞれのビュー コントローラにキャストボタンが表示されます。キャスト アイコンをクリックすると、ユーザーが選択できるキャスト デバイスのリストが表示されます。送信側のデバイスでコンテンツをローカルで再生している場合は、キャスト デバイスを選択すると、そのキャスト デバイスで再生が開始または再開されます。キャスト セッション中、ユーザーはいつでもキャスト アイコンをクリックして、キャスト デバイスへのアプリのキャストを停止できます。Google Cast デザイン チェックリストで説明されているように、ユーザーはアプリのどの画面からでもキャスト デバイスに接続できる、またはキャスト デバイスとの接続を解除できる必要があります。
設定
開始プロジェクトには、完成したサンプルアプリの場合と同じ依存関係と Xcode の設定が必要です。このセクションに戻り、同じ手順で開始アプリ プロジェクトに GoogleCast.framework
を追加します。
初期化
キャスト フレームワークには、シングル サインオン オブジェクト GCKCastContext
があります。このオブジェクトは、フレームワークのすべてのアクティビティを調整します。このオブジェクトは、アプリのライフサイクルの早い段階で(通常はアプリ デリゲートの application(_:didFinishLaunchingWithOptions:)
メソッドで)初期化する必要があります。これにより、送信者アプリの再起動時に自動的にセッション再開がトリガーされ、デバイスのスキャンを開始できます。
GCKCastContext
を初期化するときに、GCKCastOptions
オブジェクトを指定する必要があります。このクラスには、フレームワークの動作に影響するオプションが含まれています。最も重要なのはレシーバー アプリケーション ID です。この ID を使用して、キャスト デバイスの検出結果をフィルタし、キャスト セッションの開始時にレシーバー アプリを起動します。
application(_:didFinishLaunchingWithOptions:)
メソッドは、キャスト フレームワークからロギング メッセージを受信するようにロギング デリゲートを設定するのにも役立ちます。デバッグやトラブルシューティングに役立ちます。
独自の Cast 対応アプリを開発する場合は、Cast デベロッパーとして登録してからアプリのアプリ ID を取得する必要があります。この Codelab では、サンプルのアプリ ID を使用します。
次のコードを AppDelegate.swift
に追加して、ユーザーのデフォルトから取得したアプリケーション ID で GCKCastContext
を初期化し、Google Cast フレームワークのロガーを追加します。
import GoogleCast
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
fileprivate var enableSDKLogging = true
...
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID))
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
window?.clipsToBounds = true
setupCastLogging()
...
}
...
func setupCastLogging() {
let logFilter = GCKLoggerFilter()
let classesToLog = ["GCKDeviceScanner", "GCKDeviceProvider", "GCKDiscoveryManager", "GCKCastChannel",
"GCKMediaControlChannel", "GCKUICastButton", "GCKUIMediaController", "NSMutableDictionary"]
logFilter.setLoggingLevel(.verbose, forClasses: classesToLog)
GCKLogger.sharedInstance().filter = logFilter
GCKLogger.sharedInstance().delegate = self
}
}
...
// MARK: - GCKLoggerDelegate
extension AppDelegate: GCKLoggerDelegate {
func logMessage(_ message: String,
at _: GCKLoggerLevel,
fromFunction function: String,
location: String) {
if enableSDKLogging {
// Send SDK's log messages directly to the console.
print("\(location): \(function) - \(message)")
}
}
}
キャスト アイコン
GCKCastContext
が初期化されたので、ユーザーがキャスト デバイスを選択できるようにキャスト アイコンを追加する必要があります。Cast SDK には、UIButton
サブクラスとして GCKUICastButton
というキャスト ボタン コンポーネントが用意されています。これを UIBarButtonItem
でラップすると、アプリケーションのタイトルバーに追加できます。MediaTableViewController
と MediaViewController
の両方にキャスト アイコンを追加する必要があります。
MediaTableViewController.swift
と MediaViewController.swift
に次のコードを追加します。
import GoogleCast
@objc(MediaTableViewController)
class MediaTableViewController: UITableViewController, GCKSessionManagerListener,
MediaListModelDelegate, GCKRequestDelegate {
private var castButton: GCKUICastButton!
...
override func viewDidLoad() {
print("MediaTableViewController - viewDidLoad")
super.viewDidLoad()
...
castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
width: CGFloat(24), height: CGFloat(24)))
// Overwrite the UIAppearance theme in the AppDelegate.
castButton.tintColor = UIColor.white
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
...
}
...
}
次に、MediaViewController.swift
に次のコードを追加します。
import GoogleCast
@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener, GCKRemoteMediaClientListener,
LocalPlayerViewDelegate, GCKRequestDelegate {
private var castButton: GCKUICastButton!
...
override func viewDidLoad() {
super.viewDidLoad()
print("in MediaViewController viewDidLoad")
...
castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
width: CGFloat(24), height: CGFloat(24)))
// Overwrite the UIAppearance theme in the AppDelegate.
castButton.tintColor = UIColor.white
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
...
}
...
}
アプリを実行すると、アプリのナビゲーション バーにキャスト ボタンが表示されます。このボタンをクリックすると、ローカル ネットワーク上のキャスト デバイスが一覧表示されます。デバイスの検出は GCKCastContext
によって自動的に管理されます。キャスト デバイスを選択すると、サンプルのレシーバー アプリがキャスト デバイスに読み込まれます。閲覧アクティビティとローカル プレーヤー アクティビティの間を移動できるようになり、キャスト アイコンの状態が同期されます。
メディア再生に関するサポートは設定していないため、まだキャスト デバイスで動画を再生することはできません。キャストを停止するには、キャスト ボタンをクリックします。
6. 動画コンテンツのキャスト
キャスト デバイスでも動画をリモートで再生できるようにサンプルアプリを拡張します。そのためには、キャスト フレームワークによって生成された各種イベントをリッスンする必要があります。
メディアのキャスト
キャスト デバイスでメディアを再生する大まかな流れは次のとおりです。
- メディア アイテムをモデル化する Cast SDK から
GCKMediaInformation
オブジェクトを作成します。 - ユーザーがキャスト デバイスに接続してレシーバー アプリを起動します。
GCKMediaInformation
オブジェクトをレシーバーに読み込み、コンテンツを再生します。- メディアのステータスを追跡します。
- ユーザーの操作に基づいて再生コマンドをレシーバーに送信します。
ステップ 1 では、あるオブジェクトを別のオブジェクトにマッピングします。GCKMediaInformation
は Cast SDK が認識するもので、MediaItem
はアプリのメディア アイテムのカプセル化です。MediaItem
は簡単に GCKMediaInformation
にマッピングできます。前のセクションのステップ 2 はすでに完了しています。ステップ 3 は Cast SDK を使用して簡単に行うことができます。
サンプルアプリ MediaViewController
では、すでに次の列挙型を使って、ローカル再生とリモート再生を区別しています。
enum PlaybackMode: Int {
case none = 0
case local
case remote
}
private var playbackMode = PlaybackMode.none
この Codelab では、すべてのサンプル プレーヤーにおけるロジックの仕組みを正確に理解する必要はありません。ここで重要なのは、2 つの再生場所を同じように認識するようにアプリのメディア プレーヤーを変更する必要があるということです。
現時点では、ローカル プレーヤーはキャスト状態について何も認識していないため、常にローカルの再生状態になっています。キャスト フレームワークで発生する状態遷移に基づいて UI を更新する必要があります。たとえば、キャストを開始するときは、ローカルの再生を停止して、いくつかのコントロールを無効にする必要があります。同様に、このビュー コントローラを使用しているときにキャストを停止した場合は、ローカル再生に移行する必要があります。これに対応するには、キャスト フレームワークによって生成された各種イベントをリッスンする必要があります。
キャスト セッションの管理
キャスト フレームワークの場合、キャスト セッションは、デバイスへの接続、起動(または参加)、レシーバー アプリへの接続、メディア コントロール チャネルの初期化(該当する場合)の 2 つの手順で構成されます。メディア コントロール チャネルは、キャスト フレームワークがレシーバー メディア プレーヤーからメッセージを送受信する方法です。
キャスト アイコンからデバイスを選択すると自動的にキャスト セッションが開始され、ユーザーが切断すると自動的に停止します。ネットワークの問題によるレシーバー セッションへの再接続も、キャスト フレームワークによって自動的に処理されます。
キャスト セッションは、GCKCastContext.sharedInstance().sessionManager
を介してアクセスできる GCKSessionManager
によって管理されます。GCKSessionManagerListener
コールバックは、作成、停止、再開、終了などのセッション イベントをモニタリングするために使用できます。
まず、セッション リスナーを登録して、いくつかの変数を初期化する必要があります。
class MediaViewController: UIViewController, GCKSessionManagerListener,
GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
...
private var sessionManager: GCKSessionManager!
...
required init?(coder: NSCoder) {
super.init(coder: coder)
sessionManager = GCKCastContext.sharedInstance().sessionManager
...
}
override func viewWillAppear(_ animated: Bool) {
...
let hasConnectedSession: Bool = (sessionManager.hasConnectedSession())
if hasConnectedSession, (playbackMode != .remote) {
populateMediaInfo(false, playPosition: 0)
switchToRemotePlayback()
} else if sessionManager.currentSession == nil, (playbackMode != .local) {
switchToLocalPlayback()
}
sessionManager.add(self)
...
}
override func viewWillDisappear(_ animated: Bool) {
...
sessionManager.remove(self)
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
...
super.viewWillDisappear(animated)
}
func switchToLocalPlayback() {
...
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
...
}
func switchToRemotePlayback() {
...
sessionManager.currentCastSession?.remoteMediaClient?.add(self)
...
}
// MARK: - GCKSessionManagerListener
func sessionManager(_: GCKSessionManager, didStart session: GCKSession) {
print("MediaViewController: sessionManager didStartSession \(session)")
setQueueButtonVisible(true)
switchToRemotePlayback()
}
func sessionManager(_: GCKSessionManager, didResumeSession session: GCKSession) {
print("MediaViewController: sessionManager didResumeSession \(session)")
setQueueButtonVisible(true)
switchToRemotePlayback()
}
func sessionManager(_: GCKSessionManager, didEnd _: GCKSession, withError error: Error?) {
print("session ended with error: \(String(describing: error))")
let message = "The Casting session has ended.\n\(String(describing: error))"
if let window = appDelegate?.window {
Toast.displayMessage(message, for: 3, in: window)
}
setQueueButtonVisible(false)
switchToLocalPlayback()
}
func sessionManager(_: GCKSessionManager, didFailToStartSessionWithError error: Error?) {
if let error = error {
showAlert(withTitle: "Failed to start a session", message: error.localizedDescription)
}
setQueueButtonVisible(false)
}
func sessionManager(_: GCKSessionManager,
didFailToResumeSession _: GCKSession, withError _: Error?) {
if let window = UIApplication.shared.delegate?.window {
Toast.displayMessage("The Casting session could not be resumed.",
for: 3, in: window)
}
setQueueButtonVisible(false)
switchToLocalPlayback()
}
...
}
MediaViewController
で、キャスト デバイスに接続または切断されたときにローカル プレーヤーとの切り替えを行えることを通知したいと考えています。モバイル デバイスで実行されているアプリのインスタンスだけでなく、別のモバイル デバイスで実行されているお使いの(または別の)アプリの別のインスタンスによっても、接続が中断されることがあります。
現在アクティブなセッションは、GCKCastContext.sharedInstance().sessionManager.currentCastSession
としてアクセスできます。キャスト ダイアログで行ったユーザーの操作に応じてセッションが作成され、自動的に破棄されます。
メディアの読み込み
Cast SDK には、GCKRemoteMediaClient
に、レシーバーでのリモート メディア再生を管理するための便利な API が用意されています。メディア再生をサポートする GCKCastSession
の場合、GCKRemoteMediaClient
のインスタンスは SDK によって自動的に作成されます。GCKCastSession
インスタンスの remoteMediaClient
プロパティとしてアクセスできます。
次のコードを MediaViewController.swift
に追加して、レシーバーに現在選択されている動画を読み込みます。
@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener,
GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
...
@objc func playSelectedItemRemotely() {
loadSelectedItem(byAppending: false)
}
/**
* Loads the currently selected item in the current cast media session.
* @param appending If YES, the item is appended to the current queue if there
* is one. If NO, or if
* there is no queue, a new queue containing only the selected item is created.
*/
func loadSelectedItem(byAppending appending: Bool) {
print("enqueue item \(String(describing: mediaInfo))")
if let remoteMediaClient = sessionManager.currentCastSession?.remoteMediaClient {
let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
mediaQueueItemBuilder.mediaInformation = mediaInfo
mediaQueueItemBuilder.autoplay = true
mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
let mediaQueueItem = mediaQueueItemBuilder.build()
if appending {
let request = remoteMediaClient.queueInsert(mediaQueueItem, beforeItemWithID: kGCKMediaQueueInvalidItemID)
request.delegate = self
} else {
let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
queueDataBuilder.items = [mediaQueueItem]
queueDataBuilder.repeatMode = remoteMediaClient.mediaStatus?.queueRepeatMode ?? .off
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.mediaInformation = mediaInfo
mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()
let request = remoteMediaClient.loadMedia(with: mediaLoadRequestDataBuilder.build())
request.delegate = self
}
}
}
...
}
キャスト セッション ロジックを使用してリモート再生をサポートするように、既存の各種のメソッドを更新しました。
required init?(coder: NSCoder) {
super.init(coder: coder)
...
castMediaController = GCKUIMediaController()
...
}
func switchToLocalPlayback() {
print("switchToLocalPlayback")
if playbackMode == .local {
return
}
setQueueButtonVisible(false)
var playPosition: TimeInterval = 0
var paused: Bool = false
var ended: Bool = false
if playbackMode == .remote {
playPosition = castMediaController.lastKnownStreamPosition
paused = (castMediaController.lastKnownPlayerState == .paused)
ended = (castMediaController.lastKnownPlayerState == .idle)
print("last player state: \(castMediaController.lastKnownPlayerState), ended: \(ended)")
}
populateMediaInfo((!paused && !ended), playPosition: playPosition)
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
playbackMode = .local
}
func switchToRemotePlayback() {
print("switchToRemotePlayback; mediaInfo is \(String(describing: mediaInfo))")
if playbackMode == .remote {
return
}
// If we were playing locally, load the local media on the remote player
if playbackMode == .local, (_localPlayerView.playerState != .stopped), (mediaInfo != nil) {
print("loading media: \(String(describing: mediaInfo))")
let paused: Bool = (_localPlayerView.playerState == .paused)
let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
mediaQueueItemBuilder.mediaInformation = mediaInfo
mediaQueueItemBuilder.autoplay = !paused
mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
mediaQueueItemBuilder.startTime = _localPlayerView.streamPosition ?? 0
let mediaQueueItem = mediaQueueItemBuilder.build()
let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
queueDataBuilder.items = [mediaQueueItem]
queueDataBuilder.repeatMode = .off
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()
let request = sessionManager.currentCastSession?.remoteMediaClient?.loadMedia(with: mediaLoadRequestDataBuilder.build())
request?.delegate = self
}
_localPlayerView.stop()
_localPlayerView.showSplashScreen()
setQueueButtonVisible(true)
sessionManager.currentCastSession?.remoteMediaClient?.add(self)
playbackMode = .remote
}
/* Play has been pressed in the LocalPlayerView. */
func continueAfterPlayButtonClicked() -> Bool {
let hasConnectedCastSession = sessionManager.hasConnectedCastSession
if mediaInfo != nil, hasConnectedCastSession() {
// Display an alert box to allow the user to add to queue or play
// immediately.
if actionSheet == nil {
actionSheet = ActionSheet(title: "Play Item", message: "Select an action", cancelButtonText: "Cancel")
actionSheet?.addAction(withTitle: "Play Now", target: self,
selector: #selector(playSelectedItemRemotely))
}
actionSheet?.present(in: self, sourceView: _localPlayerView)
return false
}
return true
}
次に、モバイル デバイスでアプリを実行します。キャスト デバイスに接続して動画の再生を開始します。動画がレシーバーで再生されます。
7. ミニ コントローラ
キャスト デザイン チェックリストでは、ユーザーが現在のコンテンツ ページから離れるときに、すべてのキャストアプリにミニ コントローラを表示する必要があります。ミニ コントローラはすぐにアクセスできるもので、現在のキャスト セッションに関するアラートを目に見える形で表示します。
Cast SDK ではコントロール バー GCKUIMiniMediaControlsViewController
を使用できるため、永続的コントロールを表示したいシーンに追加できます。
サンプルアプリでは、別のビュー コントローラをラップして下部に GCKUIMiniMediaControlsViewController
を追加する GCKUICastContainerViewController
を使用します。
AppDelegate.swift
ファイルを変更し、次のメソッドの if useCastContainerViewController
条件に以下のコードを追加します。
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
guard let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
as? UINavigationController else { return false }
let castContainerVC = GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
as GCKUICastContainerViewController
castContainerVC.miniMediaControlsItemEnabled = true
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = castContainerVC
window?.makeKeyAndVisible()
...
}
このプロパティとセッター/ゲッターを追加して、ミニ コントローラの公開設定を制御します(後のセクションで使用します)。
var isCastControlBarsEnabled: Bool {
get {
if useCastContainerViewController {
let castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
return castContainerVC!.miniMediaControlsItemEnabled
} else {
let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
return rootContainerVC!.miniMediaControlsViewEnabled
}
}
set(notificationsEnabled) {
if useCastContainerViewController {
var castContainerVC: GCKUICastContainerViewController?
castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
castContainerVC?.miniMediaControlsItemEnabled = notificationsEnabled
} else {
var rootContainerVC: RootContainerViewController?
rootContainerVC = (window?.rootViewController as? RootContainerViewController)
rootContainerVC?.miniMediaControlsViewEnabled = notificationsEnabled
}
}
}
アプリを実行し、動画をキャストする。レシーバーで再生が開始すると、各シーンの下部にミニ コントローラが表示されます。ミニ コントローラを使ってリモート再生を操作できます。閲覧アクティビティとローカル プレーヤー アクティビティ間を移動する場合は、ミニ コントローラの状態をレシーバーのメディア再生ステータスと同期させる必要があります。
8. 紹介オーバーレイ
Google Cast デザイン チェックリストでは、送信側アプリが既存のユーザーにキャスト アイコンを導入するようユーザーに求めて、送信側アプリがキャストをサポートするようになったことと、Google Cast を初めて使用するユーザーが
GCKCastContext
クラスには presentCastInstructionsViewControllerOnce
メソッドがあります。このメソッドを使用すると、ユーザーに初めてキャスト アイコンが表示されたときに、キャスト アイコンをハイライト表示できます。MediaViewController.swift
と MediaTableViewController.swift
に次のコードを追加します。
override func viewDidLoad() {
...
NotificationCenter.default.addObserver(self, selector: #selector(castDeviceDidChange),
name: NSNotification.Name.gckCastStateDidChange,
object: GCKCastContext.sharedInstance())
}
@objc func castDeviceDidChange(_: Notification) {
if GCKCastContext.sharedInstance().castState != .noDevicesAvailable {
// You can present the instructions on how to use Google Cast on
// the first time the user uses you app
GCKCastContext.sharedInstance().presentCastInstructionsViewControllerOnce(with: castButton)
}
}
モバイル デバイスでアプリを実行すると、概要オーバーレイが表示されます。
9. コントローラを開きました
Google Cast デザイン チェックリストでは、送信側アプリに、キャスト中のメディアの拡張コントローラが必要です。拡張コントローラは、ミニ コントローラの全画面バージョンです。
拡張コントローラは、リモート メディア再生を完全に制御できる全画面ビューです。このビューでは、キャスト アプリがキャスト セッションのあらゆる管理できる要素を管理する必要があります。ただし、レシーバーの音量調整とセッションのライフサイクル(接続/停止)は例外です。また、メディア セッションに関するすべてのステータス(アートワーク、タイトル、サブタイトルなど)も提供します。
このビューの機能は GCKUIExpandedMediaControlsViewController
クラスによって実装されています。
まず、キャスト コンテキストでデフォルトの拡張コントローラを有効にする必要があります。AppDelegate.swift
を変更して、デフォルトの拡張コントローラを有効にします。
import GoogleCast
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
// Add after the setShareInstanceWith(options) is set.
GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
...
}
...
}
ユーザーが動画のキャストを開始したときに拡張コントローラを読み込むには、MediaViewController.swift
に次のコードを追加します。
@objc func playSelectedItemRemotely() {
...
appDelegate?.isCastControlBarsEnabled = false
GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()
}
また、ユーザーがミニ コントローラをタップしたときに、拡張コントローラが自動的に起動します。
アプリを実行し、動画をキャストする。拡張コントローラが表示されます。動画のリストに戻り、ミニ コントローラをクリックすると、展開されたコントローラが再度読み込まれます。
10. Cast Connect のサポートを追加する
Cast Connect ライブラリを使用すると、既存の送信側アプリがキャスト プロトコル経由で Android TV アプリケーションと通信できます。Cast Connect は Cast インフラストラクチャ上に構築されており、Android TV アプリがレシーバーとして機能します。
依存関係
Podfile
で、次のように google-cast-sdk
が 4.4.8
以上を指していることを確認してください。ファイルに変更を加えた場合は、コンソールから pod update
を実行して、変更をプロジェクトと同期します。
pod 'google-cast-sdk', '>=4.4.8'
GCKLaunchOptions
Android TV アプリ(Android レシーバーとも呼ばれます)を起動するには、GCKLaunchOptions
オブジェクトで androidReceiverCompatible
フラグを true に設定する必要があります。この GCKLaunchOptions
オブジェクトは、レシーバーがどのように起動されるかを指定し、GCKCastContext.setSharedInstanceWith
を使用して共有インスタンスに設定された GCKCastOptions
に渡されます。
AppDelegate.swift
に次の行を追加します。
let options = GCKCastOptions(discoveryCriteria:
GCKDiscoveryCriteria(applicationID: kReceiverAppID))
...
/** Following code enables CastConnect */
let launchOptions = GCKLaunchOptions()
launchOptions.androidReceiverCompatible = true
options.launchOptions = launchOptions
GCKCastContext.setSharedInstanceWith(options)
起動認証情報の設定
送信者側では、セッションに参加しているユーザーを表す GCKCredentialsData
を指定できます。credentials
は、ATV アプリが認識できるのであれば、ユーザー定義可能な文字列です。GCKCredentialsData
は、リリース時または参加時にのみ Android TV アプリに渡されます。接続中にもう一度設定すると、Android TV アプリには渡されません。
起動認証情報を設定するには、GCKLaunchOptions
を設定した後に GCKCredentialsData
を定義する必要があります。これを実演するため、[Creds] ボタンのロジックを追加して、セッションの確立時に渡される認証情報を設定します。MediaTableViewController.swift
に次のコードを追加します。
class MediaTableViewController: UITableViewController, GCKSessionManagerListener, MediaListModelDelegate, GCKRequestDelegate {
...
private var credentials: String? = nil
...
override func viewDidLoad() {
...
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Creds", style: .plain,
target: self, action: #selector(toggleLaunchCreds))
...
setLaunchCreds()
}
...
@objc func toggleLaunchCreds(_: Any){
if (credentials == nil) {
credentials = "{\"userId\":\"id123\"}"
} else {
credentials = nil
}
Toast.displayMessage("Launch Credentials: "+(credentials ?? "Null"), for: 3, in: appDelegate?.window)
print("Credentials set: "+(credentials ?? "Null"))
setLaunchCreds()
}
...
func setLaunchCreds() {
GCKCastContext.sharedInstance()
.setLaunch(GCKCredentialsData(credentials: credentials))
}
}
読み込みリクエストへの認証情報の設定
ウェブアプリと Android TV レシーバー アプリの両方で credentials
を処理するには、MediaTableViewController.swift
クラスの loadSelectedItem
関数に次のコードを追加します。
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
...
mediaLoadRequestDataBuilder.credentials = credentials
...
送信者がキャスト先のレシーバ アプリに応じて、SDK は実行中のセッションに上記の認証情報を自動的に適用します。
Cast Connect のテスト
Chromecast with Google TV に Android TV APK をインストールする手順
- Android TV デバイスの IP アドレスを確認します。通常は、[設定] > [ネットワークとインターネット] >(デバイスが接続されているネットワーク名)で確認できます。右側に、詳細情報とネットワーク上のデバイスの IP が表示されます。
- お使いのデバイスの IP アドレスを使用して、ターミナルから ADB 経由でデバイスに接続します。
$ adb connect <device_ip_address>:5555
- ターミナル ウィンドウで、この Codelab の開始時にダウンロードした Codelab サンプルの最上位フォルダに移動します。次に例を示します。
$ cd Desktop/ios_codelab_src
- 次のコマンドを実行して、このフォルダ内の .apk ファイルを Android TV にインストールします。
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- これで、Android TV デバイスの [マイアプリ] メニューに [動画をキャスト] という名前でアプリが表示されます。
- 完了したら、エミュレータまたはモバイル デバイスでアプリをビルドして実行します。Android TV デバイスでキャスト セッションを確立すると、Android TV 上で Android レシーバー アプリが起動し、iOS モバイル デバイスで動画を再生する場合は、Android レシーバーで動画を再生し、Android TV デバイスのリモコンを使用して再生を操作できるようにします。
11. キャスト ウィジェットをカスタマイズする
初期化
App-Done フォルダから始めます。AppDelegate.swift
ファイルの applicationDidFinishLaunchingWithOptions
メソッドに以下を追加します。
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let styler = GCKUIStyle.sharedInstance()
...
}
この Codelab の残りの部分で説明したように、1 つ以上のカスタマイズの適用が完了したら、以下のコードを呼び出してスタイルを commit します。
styler.apply()
キャストビューのカスタマイズ
ビュー全体にデフォルトのスタイル設定ガイドラインを適用すると、キャスト アプリケーション フレームワークが管理するすべてのビューをカスタマイズできます。例として、アイコンの色合いを変更してみましょう。
styler.castViews.iconTintColor = .lightGray
必要に応じて、デフォルト値を画面ごとにオーバーライドできます。たとえば、展開されたメディア コントローラのみで使用するアイコンの色合いを lightGrayColor でオーバーライドする場合などです。
styler.castViews.mediaControl.expandedController.iconTintColor = .green
色の変更
すべてのビューに対して、またはビューごとに個別に背景色をカスタマイズできます。次のコードは、Cast Application Framework が提供するすべてのビューの背景色を青色に設定します。
styler.castViews.backgroundColor = .blue
styler.castViews.mediaControl.miniController.backgroundColor = .yellow
フォントを変更する
キャストビューに表示されるさまざまなラベルのフォントをカスタマイズできます。わかりやすくするために、すべてのフォントを「定型文」に設定します。
styler.castViews.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 16) ?? UIFont.systemFont(ofSize: 16)
styler.castViews.mediaControl.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 6) ?? UIFont.systemFont(ofSize: 6)
デフォルトのボタン画像を変更する
独自のカスタム画像をプロジェクトに追加し、画像をボタンに割り当ててスタイルを設定する。
let muteOnImage = UIImage.init(named: "yourImage.png")
if let muteOnImage = muteOnImage {
styler.castViews.muteOnImage = muteOnImage
}
キャスト アイコンのテーマを変更する
UIAppearance プロトコルを使用して、キャスト ウィジェットのテーマを設定することもできます。次のコードは、表示されるすべてのビューで GCKUICastButton をテーマにします。
GCKUICastButton.appearance().tintColor = UIColor.gray
12. 完了
iOS で Cast SDK ウィジェットを使用して動画アプリをキャスト対応にする方法を学びました。
詳しくは、iOS Sender デベロッパー ガイドをご覧ください。