WebRTCはじめました：コピペ編

# WebRTCでブラウザ間通信
[「WebRTCはじめました：Webカメラ編」](http://qiita.com/shinido/items/a80e5706f466b81646e2)でWebカメラからストリームの取得ができたので，いよいよWebRTCの本番，ブラウザ間通信によってストリームの転送を実装．
普通はnodejsとか使って最初のシグナリングを行うけれど，まずはコピペによるシグナリングから始める．
基本的にWebRTCに関する実装はlibwebrtc.jsという形でまとめておく．
WebRTCの細かい内容については説明しないので適宜調べてください．

# 配信側：コネクションの作成
まず，呼び出す側の実装は下記のようになる．
カメラが取得できたらmainが呼び出される．カメラの取得方法等は[「WebRTCはじめました：Webカメラ編」](http://qiita.com/shinido/items/a80e5706f466b81646e2)を参照．

```html:CopyPasteCam.html
<!DOCTYPE html>
<html>

<head>
  <title>Camera</title>
  <script src="js/libwebrtc.js"></script>
</head>

<body>
  <div>
    <video id="video" autoplay="1" />
  </div>
  <form id="control"></form>
  <form>
    <textarea id="textarea" rows=5 cols=50></textarea>
    <input type="button" id="button" onclick="receive()" value="onAnswer"></input>
  </form>
  <script>
    var video = document.getElementById("video");
    var control = document.getElementById("control");
    var localStream = null;
    var connection = null;

    var msg_obj = [];
    var textarea = document.getElementById("textarea");

    // 指定されたメッセージをテキストエリアに表示する
    // 追記するためにリストを使っているけれど，直接今のvalueに足してもよい
    function appendMessage(msg) {
      msg_obj.push(msg);
      textarea.value = JSON.stringify(msg_obj);
    }

    getVideoSources(function(cam) {
      console.log("cam", cam);
      var b = document.createElement("input");
      b.type = "button";
      b.value = cam.name;
      b.onclick = getMain(cam.id);
      control.appendChild(b);
      console.log('add button');
    });

    function getMain(cam_id) {
      return function() {
        main(cam_id);
      };
    }

    function main(cam_id) {
      navigator.getUserMedia({
        audio: false,
        video: {  // とりあえず試す場合は「video : true」としてカメラの指定を行わなければよい
          optional: [{
            sourceId: cam_id
          }]
        }
      }, function(stream) { // success
        console.log("Start Video", stream);
        localStream = stream;
        video.src = URL.createObjectURL(stream);
        video.play();
        video.volume = 0;
        webrtc();
      }, function(e) { // error
        console.error("Error on start video: " + e.code);
      });
    };

    function webrtc() {
      var stop;
      msg_obj = [];
      // 相手に配信するストリーム，メッセージを伝えるメソッド，エラーコールバックを指定
      connection = start_webrtc(localStream, appendMessage, function() {
        console.log("Error");
      });
    }

    // Answerを受け取った時に呼び出されるメソッド
    function receive(){
      var msg = JSON.parse(textarea.value);
      msg.some(function(v, i){
        if(v.type==msg_type.answer){
          on_answer(connection, msg);
        }
      });
    }
  </script>
</body>

</html>
```

カメラ取得ができた後はライブラリ化されたstart_webrtc()メソッドを呼び出すだけにしている．
受信側に伝えるメッセージはコールバックを通してテキストエリアに表示される．
ユーザはテキストエリアの内容をコピーして受信側のテキストエリアにペーストする．
受信側で処理が終わってAnswerのテキストが取得できると，それをテキストエリアにペーストしてボタンを押す．

webrtc()メソッドは下記の通り．

```js:libwebrtc.js
var msg_type = {
  offer: "offer",
  candidate: "candidate",
  answer: "answer"
}

// 配信側はこのメソッドを呼び出す．引数は，相手に配信するストリーム，受信側にメッセージを伝える場合のコールバック，エラーコールバック．
function start_webrtc(stream, sendMsg, error) {
  var connection = new RTCPeerConnection(ice_config);
  connection.addStream(stream);
  connection.createOffer(
    function(des) {
      // Offerに成功すると呼び出される．
      connection.setLocalDescription(des);
      // ローカルにDescriptionをセットし，次は受信側connectionのRemoteにセットする必要があるため，メッセージコールバックを呼び出す．
      sendMsg(des);
    },
    function() {
      // エラーの場合
      error();
    }, mediaC
  );
  
  connection.onicecandidate = function(evt) {
    if (evt.candidate) {
      // candidateの場合は受信側に伝える
      sendMsg({
        type: msg_type.candidate,
        sdpMLineIndex: evt.candidate.sdpMLineIndex,
        sdpMid: evt.candidate.sdpMid,
        candidate: evt.candidate.candidate
      });
    } else {
      // それ以外の場合は全てのcandidateが揃った場合なので，リストにcandidateを貯めてここで相手側に伝えても構わない
    }
  }

  return connection;
}

function on_answer(connection, msg) {
  // connectionにAnswerをセットして通信を開始する
  connection.setRemoteDescription(new RTCSessionDescription(msg));
}
```

onicecandidateは複数回呼ばれる点に注意．
例えばvideoの通信路とaudioの通信路といった形で別々に呼ばれる．まとめて送る方が合理的だけれど，とりあえずこのライブラリでは余計なことをせず，呼び出す側に任せている．

適当にtextareaを作っておいて，そこに相手に渡すメッセージを追記していく．
単純に追記して改行でパースしてもいいけれど，面倒なのでJSONのリストで管理している．


# 受信側：コネクションの作成

受信側の実装は下記の通り．
ちなみに受信側はカメラを取得しないのでSSLである必要は無い．

```html:CopyPasteViewer.html
<!DOCTYPE html>
<html>

<head>
  <title>Viewer</title>
  <script src="js/libwebrtc.js"></script>
</head>

<body>
  <div>
    <video id="remote" autoplay="1"></video>
  </div>
  <form>
    <textarea id="textarea" rows=5 cols=50></textarea>
    <input type="button" id="button" onclick="receive()" value="onOffer"></input>
  </form>
  <script>
    var remote = document.getElementById("remote");
    var control = document.getElementById("control");

    var msg_obj = [];
    var textarea = document.getElementById("textarea");

    function appendMessage(msg) {
      msg_obj.push(msg);
      console.log("TEXT", msg_obj);
      textarea.value = JSON.stringify(msg_obj);
    }

    // メッセージを受け取ると呼び出される
    function receive() {
      msg_obj = [];
      var msg = JSON.parse(textarea.value);
      console.log("msg", msg);
      var connection = null;
      var candidate = [];
      // テキストエリアに入力されたメッセージはリストになっているのでtype別に分解する
      msg.some(function(v, i) {
          // offerの場合は1つしか無い（はず）なのでon_offerを呼び出す
        if (v.type == msg_type.offer) {
          // offerに成功するとAnswerを配信側に伝える必要があるため，テキストエリアを更新する
          connection = on_offer(v, remote, appendMessage, function() {
            console.log("Error");
          });
        } else if (v.type == msg_type.candidate) {
          // candidateは複数あるのでリストに追記する
          candidate.push(v);
        }
      });

      if (connection != null) {
        // 受け取ったcandidateを全てセットする．
        candidate.some(function(v, i) {
          on_icecandidate(connection, v);
        });
      }

    }
  </script>
</body>

</html>
```

受信側のテキストエリアにメッセージをペーストしたらonOfferのボタンを押下してreceive()
メソッドを呼び出す．
JSONのリストになっているはずなので，分解してon_offerとon_icecandidateを呼び出す．
複数のカメラを閲覧することを考えるとreceiveしてからvideoタグを作成して追加する方がスマートかもしれない．
Offerに成功したら今度はAnswerを配信側に伝えなければならないので，Answerをテキストエリアに表示させる．
ユーザは再度このメッセージをコピーし，配信側のテキストエリアにペースト，onAnswerのボタンを押下して完了する．

on_offerとon_icecandidateの実装は下記の通り．

```js:libwebrtc.js
// offerを受け取ったら呼び出すメソッド．受け取ったメッセージ，ストリームを表示するvideo要素，相手にメッセージを渡すコールバックを引数に指定する．
function on_offer(offer_msg, viewer, sendMsg) {
  var connection = new RTCPeerConnection(ice_config);
  connection.addEventListener("addstream", function(evt) {
    // ストリームが追加されると呼び出される
    // 実際には接続の処理が完了し，ICEの通信路が確立されてからになる
    console.log("Add stream");
    // evt.streamに相手側のストリームが格納されているのでblob情報に変換してviewerのsrcに指定する
    // canvasでお絵かきする場合はフレーム毎にvideoをcanvasに書き出す
    viewer.src = URL.createObjectURL(evt.stream);
    console.log("stream src", viewer.src);
  }, false);

  connection.addEventListener("removestream", function(evt) {
    // ストリームが削除された場合に呼び出される．接続が切れた場合にも呼び出される（っぽい）
    viewer.src = "";
  }, false);

  // 受け取ったOfferをRemoteにセットする
  connection.setRemoteDescription(new RTCSessionDescription(offer_msg));
  // Answerを作成
  connection.createAnswer(
    function(des) {
      // 成功したらLocalにセットして配信側にAnswerを伝える
      connection.setLocalDescription(des);
      sendMsg(des);
    },
    function() {
      // error
    }, mediaC);

  return connection;
}

function on_icecandidate(connection, candidate) {
  connection.addIceCandidate(new RTCIceCandidate(candidate));
}
```

基本的には受け取った情報をセットして，相手に伝える情報をコールバックするだけなのでとても簡単．

# まとめ
まずはコピペでシグナリングしてWebRTCの通信を開始．
コピペした部分をなんらかの方法で相手に伝えればいいので，nodejsなりなんなりを使えばいい．
で，次は予告した通りこれをQRコードでやってみる．