[webRTC for web vol1] まずは1対1で接続

ブラウザでやります。

複数接続はこちら → [webRTC for web vol2](http://qiita.com/nakadoribooks/items/d8a55811fe0b51b8401f)
 
- [iOSはこちら](http://qiita.com/nakadoribooks/items/9f1b1cbe32f2fc6dec70)
- [Androidはこちら](http://qiita.com/nakadoribooks/items/d046fb68d59c7fffbc42)

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

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

# 環境
クロム

# 実装

## wamp
ライブラリは [autobahn](http://crossbar.io/autobahn/)
wampサーバの[とりあえずの実装](http://qiita.com/nakadoribooks/items/9f1b1cbe32f2fc6dec70#wamp%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6heroku%E3%81%AB%E7%BD%AE%E3%81%8F)

```js:wamp.js
/**
 * @param {onReceiveAnswer, onReceiveOffer, onReceiveCandidate} callbacks 
 */
let Wamp = function(callbacks){
    this.callbacks = callbacks
}

Wamp.config = {
    HandshakeEndpint: "wss://nakadoribooks-webrtc.herokuapp.com"
    , Topic: {
       Anser: "com.nakadoribook.webrtc.answer"
        , Offer: "com.nakadoribook.webrtc.offer"
        , Candidate: "com.nakadoribook.webrtc.candidate"
    }
}

Wamp.prototype = {

    // interface
    connect: function(){

        let connection = new autobahn.Connection({
            url: Wamp.config.HandshakeEndpint
        });
        connection.onopen = (session, details) => { this.onOpen(session, details) }
        connection.onclose = (reason, details) => { this.onClose(session, details) }
        connection.open()

        this.connection = connection
    }

    , publishOffer: function(sdp){
        let str = JSON.stringify(sdp)
        this.session.publish(Wamp.config.Topic.Offer, [str]);
    }

    , publishAnswer: function(sdp){
        let str = JSON.stringify(sdp)
        this.session.publish(Wamp.config.Topic.Anser, [str]);
    }

    , publishCandidate: function(candidate){
        let str = JSON.stringify(candidate)
        this.session.publish(Wamp.config.Topic.Candidate, [str]);
    }

    // implements
    , onOpen: function(session, details){
        this.session = session

        // subscribe
        session.subscribe(Wamp.config.Topic.Anser, (args, kwArgs)=>{ this.onReceiveAnswer(args, kwArgs) });
        session.subscribe(Wamp.config.Topic.Offer, (args, kwArgs)=>{ this.onReceiveOffer(args, kwArgs) });
        session.subscribe(Wamp.config.Topic.Candidate, (args, kwArgs)=>{ this.onReceiveCandidate(args, kwArgs) });
    },

    onClose: function(reason, details){
        console.log("onClose")
        console.log(reason)
    }
    
    , onReceiveAnswer: function(args){
        let sdp = JSON.parse(args[0])
        this.callbacks.onReceiveAnswer(sdp)
    }

    , onReceiveOffer: function(args){
        let sdp = JSON.parse(args[0])
        this.callbacks.onReceiveOffer(sdp)
    }

    , onReceiveCandidate: function(args){
        let candidate = JSON.parse(args[0])
        this.callbacks.onReceiveCandidate(candidate)
    }
}
```

## Webrtc

この辺りを参考に
https://developer.mozilla.org/ja/docs/Web/API/RTCPeerConnection

```js:wamp.js
/**
 * @param {onIceCandidate, onLocalStream, onAddedStream} callbacks 
 */
let Webrtc = function(callbacks){
    this.callbacks = callbacks
    this.setupPeerConnection()
    this.setupStream()
}

Webrtc.config = {
    IceUrl: "stun:stun.l.google.com:19302"
}

Webrtc.prototype = {

    // interface ------

    createOffer: function(callback){
        this.peerConnection.createOffer((descriptin) => { 
            this.peerConnection.setLocalDescription(descriptin);
            callback(descriptin)
        }, (error) => {
            
        });
    }

    , receiveAnswer: function(sdp){
        this.peerConnection.setRemoteDescription(sdp).then(()=>{

        })
    }

    , receiveOffer: function(sdp, createdAnswer){

        this.peerConnection.setRemoteDescription(sdp).then(()=>{
            this.peerConnection.createAnswer().then((answerSdp) => {
                this.peerConnection.setLocalDescription(answerSdp)

                createdAnswer(answerSdp)
            })
        })
    }

    , receiveCandidate: function(candidate){
        this.peerConnection.addIceCandidate(candidate)
    }

    // implements -------

    , setupPeerConnection: function(){
        let peerConnection = new RTCPeerConnection({iceServers: [{url: Webrtc.config.IceUrl}]});
        this.peerConnection = peerConnection
        peerConnection.onicecandidate = (event) => {

            let candidate = event.candidate
            if (candidate == null) {
                return;
            }
            
            this.callbacks.onIceCandidate(candidate)
        };
        peerConnection.onaddstream = (event) => {
            let stream = event.stream
            if (stream == null){
                return
            }
            this.callbacks.onAddedStream(stream)
        };

        peerConnection.onicegatheringstatechange = () =>{
            switch(peerConnection.iceGatheringState){
                case "complete":
                    this.callbacks.onGacheringComplete()
                break;
            }
        }
    }

    , setupStream: function(){

        navigator.mediaDevices.getUserMedia({ audio: true, video: true })
        .then((stream) => { 
            this.stream = stream
            this.peerConnection.addStream(this.stream)
            this.callbacks.onLocalStream(stream)
        })
        .catch((err) => { 
            console.error(err)
        });
    }
}
```

## アプリケーション

```js:index.js
let App = function(){
    this.init()
}

App.prototype = {

    offered: false

    , init: function(){
        
        // wamp
        this.wamp = new Wamp({
            onReceiveAnswer: (sdp) => {
                if (!this.offered) { return; }

                this.webrtc.receiveAnswer(sdp)
            }
            , onReceiveOffer: (sdp) => {
                if (this.offered) { return; }

                this.webrtc.receiveOffer(sdp, (answerSdp)=>{
                    this.wamp.publishAnswer(answerSdp)
                })
            }
            , onReceiveCandidate: (candidate) => {
                if(this.offered){ return; }

                this.webrtc.receiveCandidate(candidate)
            }
        })

        // webrtc
        this.webrtc = new Webrtc({
            onIceCandidate: (candidate) => {
                this.wamp.publishCandidate(candidate)
            }
            ,onLocalStream: (stream)=>{
                let localVideo = document.querySelector("#loacalVideo")
                localVideo.src = window.URL.createObjectURL(stream);
                localVideo.play()
            }
            ,onAddedStream: (stream) => {
                let remoteVideo = document.querySelector("#remoteVideo")
                remoteVideo.src = window.URL.createObjectURL(stream);
                remoteVideo.play()
            }
        })

        // event
        this.registerDomEvent()

        // connet
        this.wamp.connect()
    },

    registerDomEvent: function(){
        document.querySelector("#offerButton").addEventListener("click", (event)=>{
            this.offered = true
            this.webrtc.createOffer((descriptin)=>{
                this.wamp.publishOffer(descriptin)
            })
        }, false);
    }
}

window.onload = function(){
    let app = new App()
}
```

```html:index.html
<html>
<body>

    <div style="position:relative;">
        <video id="remoteVideo" width="400" height="300" 
            style="background-color:#cccccc;"
        ></video>
        <video id="loacalVideo" width="100" height="70" 
            style="background-color:#000000;position:absolute;top:0px;left:0px;"
        ></video>
    </div>
    <div>
        <button id="offerButton">CreateOffer</button>
    </div>

    <script src="autobahn.min.js"></script>
    <script src="wamp.js"></script>
    <script src="webrtc.js"></script>
    <script src="index.js"></script>
</body>
</html>
```