[webRTC for ios vol3] tricke ICE

※ 2017/4/6 編集 Answer部分

前回の普通に接続するやつはこちら。
http://qiita.com/nakadoribooks/items/9f1b1cbe32f2fc6dec70

今回は [Trickle ICE](https://html5experts.jp/mganeko/19814/) やります。
接続が早くなるやつ。

## 成果物
https://github.com/nakadoribooks/webrtc-ios/releases/tag/v0.0.3

<img src="https://j.gifs.com/lOWP5M.gif">

## 参考
- http://www.utali.io/entry/2016/09/07/142102
- https://mike-neko.github.io/blog/webrtc-ios/


## [前回](http://qiita.com/nakadoribooks/items/9f1b1cbe32f2fc6dec70)からの変更

```diff:v2_v3.diff
diff --git a/webrtcExample/ViewController.swift b/webrtcExample/ViewController.swift
index fff27b8..a560ddb 100644
--- a/webrtcExample/ViewController.swift
+++ b/webrtcExample/ViewController.swift
@@ -43,18 +43,18 @@ class ViewController: UIViewController {
                 self.wamp.publishAnswer(sdp: localSdp)
             }
             
+        }, didGenerateCandidate: { (candidate) in
+            
+            self.wamp.publishCandidate(candidate: candidate)
+            
         }, didReceiveRemoteStream: { () in
             self.stateWebrtcConnected()
         })
         
-        wamp.connect(onConnected: {
-            
+        wamp.connect(onConnected: { 
             self.stateConnected()
-            
         }, onReceiveAnswer: { (answerSdp) in
             
-            print("onReceiveAnswer")
-            
             self.webRTC.receiveAnswer(remoteSdp: answerSdp)
             
         }, onReceiveOffer: { (offerSdp) in
@@ -63,13 +63,15 @@ class ViewController: UIViewController {
                 return;
             }
             
-            print("onReceiveOffer")
-            
             self.stateReceivedOffer()
             self.webRTC.receiveOffer(remoteSdp: offerSdp)
             
+        }, onReceiveCandidate: { (candidate) in
+            
+            self.webRTC.receiveCandidate(candidate: candidate)
+            
         })
-
+        
     }
     
     private func changeButton(title:String, color:UIColor, enabled:Bool){
@@ -103,7 +105,6 @@ class ViewController: UIViewController {
         
         stateOffering()
         
-        
         webRTC.createOffer()
     }
     
diff --git a/webrtcExample/wamp/Wamp.swift b/webrtcExample/wamp/Wamp.swift
index 9714d4f..76a0e89 100644
--- a/webrtcExample/wamp/Wamp.swift
+++ b/webrtcExample/wamp/Wamp.swift
@@ -15,20 +15,27 @@ class Wamp: NSObject, SwampSessionDelegate {
     
     private static let AnswerTopic = "com.nakadoribook.webrtc.answer"
     private static let OfferTopic = "com.nakadoribook.webrtc.offer"
+    private static let CandidateTopic = "com.nakadoribook.webrtc.candidate"
     
     private var swampSession:SwampSession?
     private var onConnected:(()->())?
     private var onReceiveAnswer:((_ sdp:NSDictionary)->())?
     private var onReceiveOffer:((_ sdp:NSDictionary)->())?
+    private var onReceiveCandidate:((_ sdp:NSDictionary)->())?
     
     private override init() {
         super.init()        
     }
     
-    func connect(onConnected:@escaping (()->()), onReceiveAnswer:@escaping ((_ sdp:NSDictionary)->()), onReceiveOffer:@escaping ((_ sdp:NSDictionary)->())){
+    func connect(onConnected:@escaping (()->())
+        , onReceiveAnswer:@escaping ((_ sdp:NSDictionary)->())
+        , onReceiveOffer:@escaping ((_ sdp:NSDictionary)->())
+        , onReceiveCandidate:@escaping ((_ candidate:NSDictionary)->())
+        ){
         self.onConnected = onConnected
         self.onReceiveAnswer = onReceiveAnswer
         self.onReceiveOffer = onReceiveOffer
+        self.onReceiveCandidate = onReceiveCandidate
 
 //        let swampTransport = WebSocketSwampTransport(wsEndpoint:  URL(string: "wss://nakadoribooks-webrtc.herokuapp.com")!)
         let swampTransport = WebSocketSwampTransport(wsEndpoint:  URL(string: "ws://192.168.1.2:8000")!)
@@ -47,11 +54,16 @@ class Wamp: NSObject, SwampSessionDelegate {
         swampSession?.publish(Wamp.AnswerTopic, options: [:], args: [sdp], kwargs: [:])
     }
     
+    func publishCandidate(candidate:NSDictionary){
+        swampSession?.publish(Wamp.CandidateTopic, options: [:], args: [candidate], kwargs: [:])
+    }
+    
     // MARK: private
     
     private func subscribe(){
         _subscribeOffer()
         _subscribeAnswer()
+        _subscribeCandidate()
     }
     
     private func _subscribeOffer(){
@@ -89,6 +101,25 @@ class Wamp: NSObject, SwampSessionDelegate {
         })
     }
     
+    private func _subscribeCandidate(){
+        swampSession?.subscribe(Wamp.CandidateTopic, onSuccess: { (subscription) in
+            
+        }, onError: { (details, error) in
+            print("onError: \(error)")
+        }, onEvent: { (details, results, kwResults) in
+            
+            guard let candidate = results?.first as? NSDictionary else{
+                print("no args")
+                return;
+            }
+            
+            print("receiveCandidate:\(candidate)")
+            if let callback = self.onReceiveCandidate{
+                callback(candidate)
+            }
+        })
+    }
+    
     // MARK: SwampSessionDelegate
     
     func swampSessionHandleChallenge(_ authMethod: String, extra: [String: Any]) -> String{
diff --git a/webrtcExample/webrtc/WebRTC.swift b/webrtcExample/webrtc/WebRTC.swift
index 0a5b51b..f511c48 100644
--- a/webrtcExample/webrtc/WebRTC.swift
+++ b/webrtcExample/webrtc/WebRTC.swift
@@ -10,6 +10,8 @@ import UIKit
 
 class WebRTC: NSObject, RTCPeerConnectionDelegate, RTCEAGLVideoViewDelegate {
 
+    private var didGenerateCandidate:((_ candidate:NSDictionary)->())?
+
     private var didReceiveRemoteStream:(()->())?
     private var onCreatedLocalSdp:((_ localSdp:NSDictionary)->())?
     
@@ -45,8 +47,12 @@ class WebRTC: NSObject, RTCPeerConnectionDelegate, RTCEAGLVideoViewDelegate {
         setupLocalStream()
     }
     
-    func connect(iceServerUrlList:[String], onCreatedLocalSdp:@escaping ((_ localSdp:NSDictionary)->()), didReceiveRemoteStream:@escaping (()->())){
+    func connect(iceServerUrlList:[String]
+        , onCreatedLocalSdp:@escaping ((_ localSdp:NSDictionary)->())
+        , didGenerateCandidate:@escaping ((_ localSdp:NSDictionary)->())
+        , didReceiveRemoteStream:@escaping (()->())){
         self.onCreatedLocalSdp = onCreatedLocalSdp
+        self.didGenerateCandidate = didGenerateCandidate
         self.didReceiveRemoteStream = didReceiveRemoteStream
         
         let configuration = RTCConfiguration()
@@ -65,7 +71,18 @@ class WebRTC: NSObject, RTCPeerConnectionDelegate, RTCEAGLVideoViewDelegate {
         _receiveOffer(remoteSdp: remoteSdp)
     }
     
+    func receiveCandidate(candidate:NSDictionary){
+        guard let candidate = candidate as? [AnyHashable:Any]
+            , let rtcCandidate = RTCIceCandidate(fromJSONDictionary: candidate) else{
+            print("invalid candiate")
+            return
+        }
+        
+        self.peerConnection?.add(rtcCandidate)
+    }
+    
     // Offerを作る
+
     func createOffer(){
         _createOffer()
     }
@@ -83,7 +100,7 @@ class WebRTC: NSObject, RTCPeerConnectionDelegate, RTCEAGLVideoViewDelegate {
         
         // 1. remote SDP を登録
         peerConnection?.setRemoteDescription(sdp, completionHandler: { (error) in
- 
+            
         })
     }
     
@@ -109,10 +126,16 @@ class WebRTC: NSObject, RTCPeerConnectionDelegate, RTCEAGLVideoViewDelegate {
                 // 3.ローカルにSDPを登録
                 self.peerConnection?.setLocalDescription(sdp, completionHandler: { (error) in
                     
+                    // 3. answer を送る
+                    guard let callback = self.onCreatedLocalSdp, let localDescription = WebRTCUtil.jsonFromDescription(description: self.peerConnection?.localDescription) else{
+                        print("no localDescription")
+                        return ;
+                    }
+                    
+                    callback(localDescription)
+                    self.onCreatedLocalSdp = nil
                 })
                 
-                // 4. peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState)
-                //    で complete になったら Answerを送る
             })
         })
     }
@@ -132,8 +155,15 @@ class WebRTC: NSObject, RTCPeerConnectionDelegate, RTCEAGLVideoViewDelegate {
                 
             })
             
-            // 3. peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) 
-            //    で complete になったら offerを送る
+            // 3. offer を送る
+            guard let callback = self.onCreatedLocalSdp, let localDescription = WebRTCUtil.jsonFromDescription(description: self.peerConnection?.localDescription) else{
+                print("no localDescription")
+                return ;
+            }
+    
+            callback(localDescription)
+            self.onCreatedLocalSdp = nil
+            
         })
     }
     
@@ -183,10 +213,20 @@ class WebRTC: NSObject, RTCPeerConnectionDelegate, RTCEAGLVideoViewDelegate {
     public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection){}
     public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream){}
     public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState){}
-    public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate){ }
     public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]){}
     public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel){}
     public func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState){}
+    public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState){}
+    
+    // for Trickle ice
+    public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate){
+        print("didGenerate candidate")
+        
+        if let didGenerateCandidate = self.didGenerateCandidate, let candidateJson = WebRTCUtil.jsonFromData(data: candidate.jsonData()){
+            print("didGenerateCandidate")
+            didGenerateCandidate(candidateJson)
+        }
+    }
     
     public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream){
         print("peerConnection didAdd stream:")
@@ -209,22 +249,6 @@ class WebRTC: NSObject, RTCPeerConnectionDelegate, RTCEAGLVideoViewDelegate {
         }
     }
     
-    public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState){
-        print("peerConnection didChange newState: RTCIceGatheringState, \(newState)")
-        
-        if newState != .complete{
-            return;
-        }
-        
-        guard let callback = self.onCreatedLocalSdp, let localDescription = WebRTCUtil.jsonFromDescription(description: self.peerConnection?.localDescription) else{
-            print("no localDescription")
-            return ;
-        }
-        
-        callback(localDescription)
-        self.onCreatedLocalSdp = nil
-    }
-    
     // MARK: RTCEAGLVideoViewDelegate
     
     func videoView(_ videoView: RTCEAGLVideoView, didChangeVideoSize size: CGSize) {

```

以上。

## 次

いったん、IOSは以上で。
次はAndroidやる予定。

# 写ってる湯呑みは [楢岡焼](http://www.naraokayaki.com/)

>時代とともに作る品物は大物中心から小物、つまり食器中心へと移ってきましたが、それでも人々の暮らしに密着した焼物を作り続けてきたことに変わりはありません。土を活かし、装飾を控え、使いやすさと温もり、そして美しさを追及しながら現在に至ります。
http://www.naraokayaki.com/feature.html

使いやすさと、ぬくもり、そして美しさ。
ものづくりの大先輩ですね。
湯呑みめっちゃかっこいいので、オススメです。