iOSでのChromCast対応 ② Cast端末にアクセスする


前回で準備が完了しましたので、実際に単純な情報をCastしてみようと思います。
完了していない方は[こちら](http://qiita.com/ikemai/items/4eaa73df3811fe9bfdcc)

今回は、押したボタンの色を送り、TVに側の背景色を変えるテストアプリを作ってみます。
言語: `Swift2.0`
環境: `xCode7.3(bata)`

### 関連記事はこちら。
* [iOSでのChromCast対応 ① 準備](http://qiita.com/ikemai/items/4eaa73df3811fe9bfdcc)
* [iOSでのChromCast対応 ③ 接続先に映像を送ってみる](http://qiita.com/ikemai/items/570edacba8f911cac433)
 
# GoogleCast端末にアクセスする
## ① まずはログをセット

``` swift
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        GCKLogger.sharedInstance().delegate = self
        return true
    }
}

extension AppDelegate: GCKLoggerDelegate {
    
    func logFromFunction(function: UnsafePointer<Int8>, message: String!) {
        let functionName = String.fromCString(function)
        print(functionName! + " - " + message)
    }
}
```
## ② 端末検索準備をする

今回は、Cast側の画面(Receiverアプリ)のカスタマイズは必要無いのでデフォルトのID(`kGCKMediaDefaultReceiverApplicationID`)を使用します

```swift
    override func viewDidLoad() {
        super.viewDidLoad()
        // Establish filter criteria.
        let filterCriteria = GCKFilterCriteria(forAvailableApplicationWithID: kGCKMediaDefaultReceiverApplicationID)
        // Initialize device scanner.
        let scanner = GCKDeviceScanner(filterCriteria: filterCriteria)
        scanner.addListener(self)
        scanner.startScan()
        deviceScanner = scanner
    }
```

#### @トラブルシューティング
ここで不明なエラーが発生。起動時に落ちてしまう。
エラーは`unrecognized selector sent to instance`と出てる。『そんな関数ないよ』？
どうやらここ`scanner.startScan()`でかえってくるイベントが受信できていないようだ。

と、悩んでいたら[このサイト](http://stackoverflow.com/questions/22693171/error-when-try-to-start-scanning-chromecast-device)で解決策を入手。
`Other Linker Flags`に`-ObjC`を追加で治りました。

## ③ 端末の接続状態を知る
`GCKDeviceScannerListener`をセット

``` swift
//
// MARK: - GCKDeviceScannerListener
//
extension ViewController: GCKDeviceScannerListener {
    
    func deviceDidComeOnline(device: GCKDevice) {
        print("Device found: \(device.friendlyName)")
        setApeerCastButton()
    }
    
    func deviceDidGoOffline(device: GCKDevice) {
        print("Device went away: \(device.friendlyName)")
        setDisapeerCastButton()
    }
    
    func deviceDidChange(device: GCKDevice) {
        print("deviceDidChange()")
    }
}
```
ここでは、あらかじめ作成しておいたボタンを消したり出したりすることにした。

``` swift
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        chromecastButton = UIButton(type: .Custom)
        chromecastButton.addTarget(self, action: "didDownCastButton:", forControlEvents: .TouchDown)
        chromecastButton.frame = CGRectMake(0, 20, 39, 34)
        chromecastButton.setImage(nil, forState: .Normal)
        view.addSubview(chromecastButton)
    }

    private func setApeerCastButton() {
        chromecastButton.setImage(buttonImage, forState: .Normal)
    }
    private func setDisapeerCastButton() {
        chromecastButton.setImage(buttonImage, forState: .Normal)
    }
}
```

これで接続できるdeviceを検出した時(`deviceDidComeOnline()`)と検出できなかった時(`deviceDidGoOffline()`)の動作が決まった。

## ④ 接続可能deviceの一覧を出す
`UIActionSheet`を使って、接続可能端末の一覧を出します。

```swift

    @IBAction func didDownCastButton(sender:AnyObject) {
        if selectedDevice == nil {
            showDeviceList()
        } else {
            showSelectedDevice()
        }
    }
    
    private func showDeviceList() {
        let sheet : UIActionSheet = UIActionSheet(title: "Connect to Device", delegate: self, cancelButtonTitle: nil, destructiveButtonTitle: nil)
        
        if let devices = deviceScanner?.devices {
            for device in devices  {
                sheet.addButtonWithTitle(device.friendlyName)
            }
        }
        // Add the cancel button at the end so that indexes of the titles map to the array index.
        sheet.addButtonWithTitle(cancelTitle)
        sheet.cancelButtonIndex = sheet.numberOfButtons - 1
        sheet.showInView(self.view)
    }
    
    private func showSelectedDevice() {
        updateStatsFromDevice()
        let friendlyName = "Casting to \(selectedDevice!.friendlyName)"
        
        let sheet : UIActionSheet = UIActionSheet(title: friendlyName, delegate: self, cancelButtonTitle: nil, destructiveButtonTitle: nil)
        var buttonIndex = 0
        
        if let info = mediaInformation {
            sheet.addButtonWithTitle(info.metadata.objectForKey(kGCKMetadataKeyTitle) as? String)
            buttonIndex++
        }
        
        // Offer disconnect option.
        sheet.addButtonWithTitle(disconnectTitle)
        sheet.addButtonWithTitle(cancelTitle)
        sheet.destructiveButtonIndex = buttonIndex++
        sheet.cancelButtonIndex = buttonIndex
        
        sheet.showInView(view)
    }
```

`UIActionSheetDelegate`・`GCKDeviceManagerDelegate`をセット

```swift

//
// MARK: UIActionSheetDelegate
//
extension ViewController: UIActionSheetDelegate {
    
    func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int) {
        print("actionSheet() buttonIndex = \(buttonIndex)")
        if (buttonIndex == actionSheet.cancelButtonIndex) { return }
        
        if (selectedDevice == nil) {
            if (buttonIndex < deviceScanner?.devices.count) {
                selectedDevice = deviceScanner?.devices[buttonIndex] as? GCKDevice
                print("Selected device: \(selectedDevice!.friendlyName)")
                connectToDevice()
            }
        } else if (actionSheet.buttonTitleAtIndex(buttonIndex) == disconnectTitle) {
            // Disconnect button.
            deviceManager?.leaveApplication()
            deviceManager?.disconnect()
            deviceDisconnected()
            updateButtonStates()
        }
    }
}

//
// MARK: GCKDeviceManagerDelegate
//
extension ViewController: GCKDeviceManagerDelegate {
    
    func deviceManagerDidConnect(deviceManager: GCKDeviceManager!) {
        print("deviceManagerDidConnect()")

        updateButtonStates()
        deviceManager.launchApplication(receiverAppID)
    }
    
    func deviceManager(deviceManager: GCKDeviceManager,
        didConnectToCastApplication applicationMetadata: GCKApplicationMetadata,
        sessionID: String,
        launchedApplication: Bool) {
            print("Application has launched.")
            mediaControlChannel = GCKMediaControlChannel()
            mediaControlChannel?.delegate = self
            deviceManager.addChannel(mediaControlChannel)
            mediaControlChannel?.requestStatus()
    }
    
    func deviceManager(deviceManager: GCKDeviceManager!, didFailToConnectToApplicationWithError error: NSError) {
        print("Received notification that device failed to connect to application.")
        setDisConnecteView(error)
    }
    
    func deviceManager(deviceManager: GCKDeviceManager!, didFailToConnectWithError error: NSError!) {
        print("Received notification that device failed to connect.")
        setDisConnecteView(error)
    }
    
    func deviceManager(deviceManager: GCKDeviceManager!, didDisconnectWithError error: NSError!) {
        print("Received notification that device disconnected.")
        setDisConnecteView(error)
    }
    
    func deviceManager(deviceManager: GCKDeviceManager!, didReceiveApplicationMetadata metadata: GCKApplicationMetadata!) {
        applicationMetadata = metadata
    }
    
    private func setDisConnecteView(error: NSError?) {
        if let error = error { showError(error) }
        deviceDisconnected()
        updateButtonStates()
    }
    
    private func connectToDevice() {
        if (selectedDevice == nil) { return }
        let identifier = NSBundle.mainBundle().infoDictionary?["CFBundleIdentifier"] as! String
        deviceManager = GCKDeviceManager(device: selectedDevice, clientPackageName: identifier)
        print("connectToDevice() deviceManager = \(deviceManager)")
        deviceManager?.delegate = self
        deviceManager?.connect()
    }
    
    private func deviceDisconnected() {
        print("deviceDisconnected()")
        selectedDevice = nil
        deviceManager = nil
    }
}
```

これでボタン押下時にdeviceの選択ができるようになった

接続していない時(左)・すでに接続している時(右)
![スクリーンショット 2015-09-11 20.06.18.png](https://qiita-image-store.s3.amazonaws.com/0/80770/8578fe49-b275-19f0-8877-8a5a7816df5b.png)

***

これでCast端末への接続はできました。
次は[iOSでのChromCast対応 ③ 接続先に映像を送ってみる](http://qiita.com/ikemai/items/570edacba8f911cac433)をやります。


-----------------------------------

## 参考サイト
* 公式：基本的なiOSでの使い方が書かれています[iOS App Development](https://developers.google.com/cast/docs/ios_sender)
* 公式サンプル [CastHelloVideo-ios](https://github.com/googlecast/CastVideos-ios)
* [ChromecastのPreview SDKでChromecastDemoを動かすまで (iOS)](http://qiita.com/makoto_kw/items/12620b9242e7e8402523)
* [Chromecast - Google Cast SDKでオレオレPlay Musicをつくる](http://qiita.com/kubotaku1119/items/08895d2017dfa1523846)