記憶回路断線中。。

SE→情シス→SE→また情シス(笑) IT系の困った事を徒然なるままに。。。

iOSからchromecastへRemote Display #4

Chromecast-2015

 

(前回記事)

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"; //取得したAPP_IDを設定
//	let APP_ID = kGCKMediaDefaultReceiverApplicationID;
	
	var deviceScanner: GCKDeviceScanner?;
	var deviceManager: GCKDeviceManager?;
	
	var sessionId: String?;
	
	var remoteDisplaySession: GCKRemoteDisplaySession?;
	var remoteDisplayChannel: GCKRemoteDisplayChannel?;
	
	var frameInput: GCKViewVideoFrameInput!

	// MARK:- UIViewController

	override func viewDidLoad() {
		super.viewDidLoad()
	}

	override func didReceiveMemoryWarning() {
		super.didReceiveMemoryWarning()
	}
	
	// MARK:- GCKDeviceScannerListener

	func deviceDidComeOnline(_ device: GCKDevice!) {
		print("Device found: \(device.friendlyName)");

		//connect
		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)");
	}

	// MARK:- GCKDeviceManagerDelegate

	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;

//		normalCast();
		remoteDisplayCast();
	}
	
	// MARK:- GCKRemoteDisplayChannelDelegate
	
	func remoteDisplayChannelDidConnect(_ channel: GCKRemoteDisplayChannel) {
		let configuration = GCKRemoteDisplayConfiguration()
		configuration.videoStreamDescriptor.frameRate = .rate15p
		// Customize the configuration as needed.
		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");
	}

	// MARK:- other

	func remoteDisplayCast() {
		remoteDisplayChannel = GCKRemoteDisplayChannel();
		remoteDisplayChannel?.delegate = self;
		deviceManager?.add(remoteDisplayChannel);
	}
	
	@IBAction func scan() {
		// Establish filter criteria.
		let filterCriteria = GCKFilterCriteria(forAvailableApplicationWithID: APP_ID);
		
		// Initialize device scanner.
		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を!^^