(前回記事)
yo-tan.hatenablog.com
remote display APIをプロジェクトに追加
前回は、google cast APIのプロジェクトへの取り込みは行なったが、remote display APIはまだでした。このAPIのプロジェクトへの追加から始める。
追加なんて簡単。google cast APIの時と同様に、以下の手順でframeworkを追加。
- プロジェクトルートにGoogleCastRemoteDisplay.framework ディレクトリをコピー
- finder上でGoogleCastRemoteDisplay.frameworkを選択し、xcodeにD&D
あらら。エラーが出る出る。
出てきたエラーをググると、ここにあたった。
plus.google.com
書いてある通り、Other Linker Flagsに以下を追加
("Undefined symbols"エラーを、Other Linker Flagsで解決することが可能なのね・・・)
-lsqlite3
-lz
これで、エラーが30個くらいから4個まで減ったが、まだある。今後はこっち。
stackoverflow.com
書いてある通り、CoreMedia.frameworkをプロジェクトに追加。ビルドエラーなくなった!
ん?こんな、マニュアルに書いてもいない適当な手順で進めていいのかって?
いいんです!悪いのはマニュアル(笑) 動けばOK^^
(appleさんが標準で持っているframeworkを使っているだけだしね)
これでエラーは無くなったが、 警告がまだある。
GCKRemoteDisplaySessionのヘッダ読み込み部分で、
Missing submodule 'AVFoundation.AVAudioTypes'
と言う警告が出る。気持ち悪いので対処。
stackoverflow.com
umbrella headerだけあればいいとのこと(そりゃそうだよね。昔は存在してなかったと言うことか・・・?)なので、警告発生行を、
before:
#import <AVFoundation/AVAudioTypes.h>
after:
#import <AVFoundation/AVFoundation.h>
に変更。
そのとき、「frameworkの中のソース、変更しちゃっていいの?」と言う警告のポップアップが出るが、unlockを押す。
“GCKRemoteDisplaySession.h” is locked for editing and you may not be able to save your changes. Do you want to unlock it?
2箇所変更したら、警告0になりましたー。
remote display のimport時のエラー
remoteDisplayを実装するには、"GCKRemoteDisplayChannelDelegate"と言うプロトコルを使用するのだが、これを下記のように書くと、エラーになってしまう。
import UIKit
import GoogleCast
@objc
class ViewController: UIViewController, GCKDeviceScannerListener, GCKDeviceManagerDelegate, GCKMediaControlChannelDelegate, GCKRemoteDisplayChannelDelegate {
普通に、 "import GoogleCastRemoteDisplay" のimport文がないからそうなってると思うのだが、普通に"import GoogleCastRemoteDisplay" と書こうとしても、「GoogleCastRemoteDisplayなんてものはありません」と怒られる。。。
なんでGoogleCast はimportできてるのに、こっちはできないんだ・・・
サンプルソースを見ると、objective-cなのだが、
#import <GoogleCast/GoogleCast.h>
#import <GoogleCastRemoteDisplay/GoogleCastRemoteDisplay.h>
と書いてある。普通にimportできてるよねぇ。
よくわからないながら、いろいろやっていると、以下をやったらエラーがなくなった!
1. Test.h / Test.mのobjective-cのファイルを作成
2. 作成時に「Bridging Headerを作るか?」と聞かれるのでYES
3. 作成されたTest.hに、上記2つのimport文を記述
remote display のコード実装
ついに、全ての準備は整った。コードです。
remote displayでは、receiverに表示するframeを管理する、frameInputと言うobjectを使うのだが、公式サイトのマニュアルでは、それがmetalをベースとしたGCKMetalVideoFrameInputと言う実装クラスを使って実現している。
今回やろうとしているサイネージアプリでは、単純にUIViewの上に乗っているものをcastしてくれれば良いのだが、そんな仕組みではないのか!?
・・・ありました。
GCKMetalVideoFrameInputの親クラス:GCKVideoFrameInputには、3つの実装クラスがあって、前述のmetalベースのものの他に、以下がある。
GCKOpenGLESVideoFrameInput
GCKViewVideoFrameInput
名前だけで判断すると、GCKViewVideoFrameInputを使用すれば、なんだかうまく行きそう^^;
と言うわけで、もろもろ調べてやってみましたー。
import UIKit
import GoogleCast
@objc
class ViewController: UIViewController, GCKDeviceScannerListener, GCKDeviceManagerDelegate, GCKMediaControlChannelDelegate, GCKRemoteDisplayChannelDelegate {
let APP_ID = "AAAA1111";
var deviceScanner: GCKDeviceScanner?;
var deviceManager: GCKDeviceManager?;
var sessionId: String?;
var remoteDisplaySession: GCKRemoteDisplaySession?;
var remoteDisplayChannel: GCKRemoteDisplayChannel?;
var frameInput: GCKViewVideoFrameInput!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func deviceDidComeOnline(_ device: GCKDevice!) {
print("Device found: \(device.friendlyName)");
deviceManager = GCKDeviceManager(device: device, clientPackageName: Bundle.main.bundleIdentifier)
deviceManager!.delegate = self
deviceManager!.connect()
}
func deviceDidGoOffline(_ device: GCKDevice!) {
print("Device went away: \(device.friendlyName)");
}
func deviceDidChange(_ device: GCKDevice!) {
print("Device did Change: \(device.friendlyName)");
}
func deviceManagerDidConnect(_ deviceManager: GCKDeviceManager!) {
print("didConnect")
let options = GCKLaunchOptions(relaunchIfRunning: true)
deviceManager.launchApplication(APP_ID, with: options)
}
func deviceManager(_ deviceManager: GCKDeviceManager!, didConnectToCastApplication applicationMetadata: GCKApplicationMetadata!, sessionID: String!, launchedApplication: Bool) {
print("Application has launched.")
sessionId = sessionID;
remoteDisplayCast();
}
func remoteDisplayChannelDidConnect(_ channel: GCKRemoteDisplayChannel) {
let configuration = GCKRemoteDisplayConfiguration()
configuration.videoStreamDescriptor.frameRate = .rate15p
do {
try channel.beginSession(with: configuration);
} catch {
print("test")
}
}
func remoteDisplayChannel(_ channel: GCKRemoteDisplayChannel, didBegin session: GCKRemoteDisplaySession) {
print("begin");
remoteDisplaySession = session;
frameInput = GCKViewVideoFrameInput(session: session);
frameInput.view = self.view;
}
func remoteDisplayChannel(_ channel: GCKRemoteDisplayChannel, deviceRejectedConfiguration configuration: GCKRemoteDisplayConfiguration, error: Error?) {
print("rejected");
}
func remoteDisplayCast() {
remoteDisplayChannel = GCKRemoteDisplayChannel();
remoteDisplayChannel?.delegate = self;
deviceManager?.add(remoteDisplayChannel);
}
@IBAction func scan() {
let filterCriteria = GCKFilterCriteria(forAvailableApplicationWithID: APP_ID);
deviceScanner = GCKDeviceScanner(filterCriteria: filterCriteria)
if let deviceScanner = deviceScanner {
deviceScanner.add(self)
deviceScanner.startScan()
deviceScanner.passiveScan = true
}
}
@IBAction func animate() {
let f: () -> () = {
if self.view.backgroundColor == UIColor.white {
self.view.backgroundColor = UIColor.red;
} else {
self.view.backgroundColor = UIColor.white;
}
}
UIView.animate(withDuration: 3, animations: f);
}
}
できた!
ちゃんと画面にviewが表示されました!
animateの処理は、ボタンを押すことで呼び出すようにしていて、単純にviewの色が徐々に変わるアニメーションをつけてます。chromecastでどのくらい滑らかに表示されるかを見るために。
現在テストしているchromecast環境は、Wi-Fiルータとテレビが離れていて、Wi-Fiの扇マークが3つ中2つって感じ。
携帯もほぼ同じ場所にいるので、あまりネットワーク環境としては良くないのだが、それでもそれなりに色の変化はそれっぽく表示されてくれた。
今回のソースは、(自分で)わかりやすくするために、エラー処理を敢えて抜いて描いてますが、きちんとしたアプリにはエラー処理は不可欠なので、そこはreferenceなりみながらやってみてください^^;
では、良いgooglecast lifeを!^^