こんにちは! 前回はシグナリングサーバーを動かして、WebRTCでPeer-to-Peer通信をつなぐ処理を作りました。最後に書いた通り、前回の実装ではサーバーあたり2人だけしか同時に通知できません。今回はこれをもっと実用的にしていきましょう。 ※今回もNode学園祭2013で発表した内容と共通の部分が多いです。その時の資料も併せてご参照ください。
※こちらの記事は2014年に書かれました。2016年8月のアップデート記事がありますので、そちらもご参照ください。
複数会議室を作ろう
前回作ったのは、いわばカップル1組限定サイトのシングルテナントアプリでした(左)。これを複数組が共存できる、マルチテナント(複数会議室)のアプリに改造します(右)。
複数組が共存できない理由は、シグナリングの通信が同じシグナリングサーバーに接続している全員に飛んでしまうからです。これを混線しないように分離してあげる必要があります。シグナリングサーバーで利用しているsocket.ioでは、これを簡単に実現できるroom機能があります。
- roomに入室する… socket.join()
- roomから退室する… socket.leave()
- room内だけにメッセージを送る… socket.broadcast.to.emit()
まずクライアント側を一部手直しします。
function onOpened(evt) { console.log('socket opened.'); socketReady = true;ソケット接続が確立したら、シグナリングサーバーに対して入室要求(enter)を送っています。ここでgetRoomName()はアプリケーション側で実装する部分で、何らかの方法で会議室名を取得して返します。 手抜きなサンプルとしてはこんな感じでしょうか。URLの?以降をそのまま切り出して返しています。var roomname = getRoomName(); // 会議室名を取得する socket.emit('enter', roomname);
}
function getRoomName() { // たとえば、 URLに ?roomname とする var url = document.location.href; var args = url.split('?'); if (args.length > 1) { var room = args[1]; if (room != "") { return room; } } return "_defaultroom"; }
ついでにもう少し直しましょう。実は前回までのサンプルではカメラしかアクセスしていません。マイクはアクセスしていないので、声が聞こえません。今回はマイクも取得するように一カ所だけ修正します。※webkitGetUserMedia()の引数を変更
// start local video function startVideo() { navigator.webkitGetUserMedia({video: true, audio: true}, // <--- audio: true に変更 function (stream) { // success localStream = stream; localVideo.src = window.webkitURL.createObjectURL(stream); localVideo.play(); localVideo.volume = 0; }, function (error) { // error console.error('An error occurred: [CODE ' + error.code + ']'); return; } ); }※同一PC上で複数の2つのウィンドウ/タブを開いて通信する場合、ハウリングしやすいので音量を絞るか、ヘッドフォンを利用してください。
今度はシグナリングサーバー側も修正しましょう。クライアントからの入室要求(enter)に対応するのと、会議室内だけに通信する部分です。
// 入室 socket.on('enter', function(roomname) { socket.set('roomname', roomname); socket.join(roomname); });シグナリングサーバーを起動しなおして、HTMLをリロードすれば、複数会議室に対応したマルチテナントアプリの完成です。 URLの後ろに ?room1 や ?room2 などのように会議室名を指定すれば、 その部屋の人と通信できます。socket.on('message', function(message) { emitMessage('message', message); });
socket.on('disconnect', function() { emitMessage('user disconnected'); });
// 会議室名が指定されていたら、室内だけに通知 function emitMessage(type, message) { var roomname; socket.get('roomname', function(err, _room) { roomname = _room; });
if (roomname) { socket.broadcast.to(roomname).emit(type, message); } else { socket.broadcast.emit(type, message); } }
複数人で通信してみたい
次は2人だけでなく、複数人で同時に話せるようにしてみたいと思います。こんな感じです。
複数のPeer-to-Peer通信を扱うには
複数人と通信するには、クライアント側(ブラウザ側)に相手の数だけPeerConnectionが必要です。それを管理するための便宜上のクラスを作ります。 通信状況や、相手のID(socket.ioが割り振る)を保持します。
var MAX_CONNECTION_COUNT = 3; var connections = {}; // Connection hash function Connection() { // Connection Class var self = this; var id = ""; // socket.id of partner var peerconnection = null; // RTCPeerConnection instance var established = false; // is Already Established var iceReady = false; }ついでに、複数のConnectionを格納する配列と、それを管理する関数群も用意します。ここに挙げた2つ以外に、getConnectionCount(), isConnectPossible(), deleteConnection(id), などなど。(詳細は最後に全ソースを掲載します)function getConnection(id) { var con = null; con = connections[id]; return con; }
function addConnection(id, connection) { connections[id] = connection; }
シグナリングを手直し
シグナリングの流れも手直しが必要です。
今までのシグナリングでは、最初にOffer SDPを送る際に同じ部屋の全員に送っていました(broadcast)。すると全員からAnswer SDPが返ってきてしまうので、情報が衝突してしまいます。
そこで、まず部屋に誰が居るかを確認し(call-response)、一人ずつ個別にOffer-Answerのやり取りをする必要があります。
では、クライアント側のソースを直していきましょう。
function call() { if (! isLocalStreamStarted()) return; socket.json.send({type: "call"}); }call()で全員にbroadcastし、受け取った側はonMessage()の中でcallを受け取ると、responseを相手を特定して送り返します。発信側はreponseを受け取ると、その相手に対してoffer SDPを送っています。 sendOffer()の中身もちょっと変わります。function onMessage(evt) { var id = evt.from; var target = evt.sendto; var conn = getConnection(id);
if (evt.type === 'call') { if (! isLocalStreamStarted()) return; if (conn) return; // already connected
if (isConnectPossible()) { socket.json.send({type: "response", sendto: id }); } else { console.warn('max connections. so ignore call'); }
} else if (evt.type === 'response') { sendOffer(id); return; } }
function sendOffer(id) { var conn = getConnection(id); // <--- すでに作成済のコネクションを探す if (!conn) { conn = prepareNewConnection(id); }同様に、sendAnswer()もちょっと変えます。複数のコネクションに対応するのと、送る相手を指定するのが変更点です。conn.peerconnection.createOffer(function (sessionDescription) { // in case of success conn.iceReady = true; conn.peerconnection.setLocalDescription(sessionDescription); sessionDescription.sendto = conn.id; // <--- 送る相手を指定する sendSDP(sessionDescription); }, function () { // in case of error console.log("Create Offer failed"); }, mediaConstraints); conn.iceReady = true; }
function sendAnswer(evt) { console.log('sending Answer. Creating remote session description...' ); var id = evt.from; var conn = getConnection(id); // <--- すでに作成済のコネクションを探す if (! conn) { console.error('peerConnection not exist!'); return }conn.peerconnection.createAnswer(function (sessionDescription) { // in case of success conn.iceReady = true; conn.peerconnection.setLocalDescription(sessionDescription); sessionDescription.sendto = id; // <--- 送る相手を指定する sendSDP(sessionDescription); }, function () { // in case of error console.log("Create Answer failed"); }, mediaConstraints); conn.iceReady = true; }
さらに、SDPを覚える処理も複数セッションに対応させます。
function setOffer(evt) { var id = evt.from; var conn = getConnection(id); if (! conn) { conn = prepareNewConnection(id); conn.peerconnection.setRemoteDescription(new RTCSessionDescription(evt)); } else { console.error('peerConnection alreay exist!'); } }function setAnswer(evt) { var id = evt.from; var conn = getConnection(id); if (! conn) { console.error('peerConnection not exist!'); return } conn.peerconnection.setRemoteDescription(new RTCSessionDescription(evt)); }
引き続きConnectionを生成する処理も修正します。今まではPeerConnectionを直接返していましたが、今回はConnectionのインスタンスを生成し、そこにPeerConnectionを保持させます。また、Candidateの送信時にも相手先を指定します。
function prepareNewConnection(id) { var pc_config = {"iceServers":[]}; var peer = null; try { peer = new webkitRTCPeerConnection(pc_config); } catch (e) { console.log("Failed to create PeerConnection, exception: " + e.message); } var conn = new Connection(); // <--- Connectionを作成し、PeerConnectionを保持させる conn.id = id; conn.peerconnection = peer; peer.id = id; addConnection(id, conn);// send any ice candidates to the other peer peer.onicecandidate = function (evt) { if (evt.candidate) { console.log(evt.candidate); sendCandidate({type: "candidate", sendto: conn.id, // <-- 送信先を指定 sdpMLineIndex: evt.candidate.sdpMLineIndex, sdpMid: evt.candidate.sdpMid, candidate: evt.candidate.candidate}); } else { console.log("End of candidates. ------------------- phase=" + evt.eventPhase); conn.established = true; } };
// ... }
Candidateの送信部分を変更したので、Candidateを受信した処理も変更しましょう。 onCandidate()も複数コネクションに対応させます。
function onCandidate(evt) { var id = evt.from; var conn = getConnection(id); if (! conn) { console.error('peerConnection not exist!'); return; }// --- check if ice ready --- if (! conn.iceReady) { console.warn("PeerConn is not ICE ready, so ignore"); return; }
var candidate = new RTCIceCandidate({sdpMLineIndex:evt.sdpMLineIndex, sdpMid:evt.sdpMid, candidate:evt.candidate}); console.log("Received Candidate...") console.log(candidate); conn.peerconnection.addIceCandidate(candidate); }
さてさて、クライアント側の修正はいったん終わりにして、次はシグナリングサーバー側を修正します。前半では部屋の中だけに送信する機能を加えましたが、次は特定の相手にだけ送信できるようにします。
socket.on('message', function(message) { // 送信元のidをメッセージに追加(相手が分かるように) message.from = socket.id;ここまででいったん動かしてみましょう。まだ映像が2人までしか出ませんが、通信はできるはずです。// 送信先が指定されているか? var target = message.sendto; if (target) { // 送信先が指定されていた場合は、その相手のみに送信 io.sockets.socket(target).emit('message', message); return; } // 特に指定がなければ、ブロードキャスト emitMessage('message', message);
});
複数の映像を扱えるようにしよう
ここまでで複数人相手に通信をできるようにしました。でも通信できても映像は見えていません。ちゃんと見えるようにしましょう。 まずHTMLに複数のvideoタグを配置します。
<div style="position: relative;"> <video id="local-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"></video> <!-- <video id="remote-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"></video> --> <video id="webrtc-remote-video-0" autoplay style="position: absolute; top: 250px; left: 0px; width: 320px; height: 240px; border: 1px solid black; "></video> <video id="webrtc-remote-video-1" autoplay style="position: absolute; top: 250px; left: 330px; width: 320px; height: 240px; border: 1px solid black; "></video> <video id="webrtc-remote-video-2" autoplay style="position: absolute; top: 0px; left: 330px; width: 320px; height: 240px; border: 1px solid black; " ></video> </div>
その複数のvideoタグを扱えるような関数群を追加します。※本当は動的にタグを作成、削除するのがかっこいいのですが…。
var localVideo = document.getElementById('local-video'); //var remoteVideo = document.getElementById('remote-video'); var localStream = null; var mediaConstraints = {'mandatory': {'OfferToReceiveAudio':false, 'OfferToReceiveVideo':true }};※ソース全体は最後に記載します。// ---- multi people video & audio ---- var videoElementsInUse = {}; var videoElementsStandBy = {}; pushVideoStandBy(getVideoForRemote(0)); pushVideoStandBy(getVideoForRemote(1)); pushVideoStandBy(getVideoForRemote(2));
function getVideoForRemote(index) { var elementID = 'webrtc-remote-video-' + index; var element = document.getElementById(elementID); return element; }
function getAudioForRemote(index) { var elementID = 'webrtc-remote-audio-' + index; var element = document.getElementById(elementID); return element; }
// ---- video element management --- function pushVideoStandBy(element) { videoElementsStandBy[element.id] = element; }
function popVideoStandBy() { var element = null; for (var id in videoElementsStandBy) { element = videoElementsStandBy[id]; delete videoElementsStandBy[id]; return element; } return null; }
function pushVideoInUse(id, element) { videoElementsInUse[id] = element; }
function popVideoInUse(id) { element = videoElementsInUse[id]; delete videoElementsInUse[id]; return element; }
function attachVideo(id, stream) { console.log('try to attach video. id=' + id); var videoElement = popVideoStandBy(); if (videoElement) { videoElement.src = window.URL.createObjectURL(stream); console.log("videoElement.src=" + videoElement.src); pushVideoInUse(id, videoElement); videoElement.style.display = 'block'; } else { console.error('--- no video element stand by.'); } }
function detachVideo(id) { console.log('try to detach video. id=' + id); var videoElement = popVideoInUse(id); if (videoElement) { videoElement.pause(); videoElement.src = ""; console.log("videoElement.src=" + videoElement.src); pushVideoStandBy(videoElement); } else { console.warn('warning --- no video element using with id=' + id); } }
// ...
これで準備が整いました。早速接続してみましょう。 [Start video]ボタンを押して、[Connect]を押す、という操作を一人ずつ行ってください。一人、また一人と接続され、最大4人まで通信できます。
補足 (2014/03/09追記)
Twitter経由でご指摘をいただきました。User B/Cからresponseではなく、Offerを送れば良いのでは?
アドバイスに従うと、次のように改善されます。
- 現状:User Aからcall, User B/Cからresponse, User AからOffer, User B/CからAnswer
- 改善:User Aからcallme, User B/CからOffer, User AからAnswer
確かにその通りです。メッセージのやり取りが片道分少なくなり、すっきりしますね。 ご指摘ありがとうございました。
次回は
次回は最終回の予定です。NATやFirewallを越えて通信するための、STUN/TURNについて説明したいと思います。
今回のソースコード
シグナリングサーバー (node.js)
var port = 9001; var io = require('socket.io').listen(port); console.log((new Date()) + " Server is listening on port " + port);io.sockets.on('connection', function(socket) { // 入室 socket.on('enter', function(roomname) { socket.set('roomname', roomname); socket.join(roomname); });
socket.on('message', function(message) { // 送信元のidをメッセージに追加(相手が分かるように) message.from = socket.id;
// 送信先が指定されているか? var target = message.sendto; if (target) { // 送信先が指定されていた場合は、その相手のみに送信 io.sockets.socket(target).emit('message', message); return; } // 特に指定がなければ、ブロードキャスト emitMessage('message', message);
});
socket.on('disconnect', function() { emitMessage('user disconnected'); });
// 会議室名が指定されていたら、室内だけに通知 function emitMessage(type, message) { var roomname; socket.get('roomname', function(err, _room) { roomname = _room; });
if (roomname) { socket.broadcast.to(roomname).emit(type, message); } else { socket.broadcast.emit(type, message); }
} });
クライアント側 (HTML, JavaScript)
<!DOCTYPE html> <html> <head> <title>WebRTC 4</title>
</head> <body> <button type="button" onclick="startVideo();">Start video</button> <button type="button" onclick="stopVideo();">Stop video</button> <!-- <button type="button" onclick="connect();">Connect</button> --> <button type="button" onclick="call();">Connect</button> <button type="button" onclick="hangUp();">Hang Up</button> <br /> <div style="position: relative;"> <video id="local-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"></video> <!-- <video id="remote-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"></video> --> <video id="webrtc-remote-video-0" autoplay style="position: absolute; top: 250px; left: 0px; width: 320px; height: 240px; border: 1px solid black; "></video> <video id="webrtc-remote-video-1" autoplay style="position: absolute; top: 250px; left: 330px; width: 320px; height: 240px; border: 1px solid black; "></video> <video id="webrtc-remote-video-2" autoplay style="position: absolute; top: 0px; left: 330px; width: 320px; height: 240px; border: 1px solid black; " ></video> </div><!--- <p> SDP to send:<br /> <textarea id="text-for-send-sdp" rows="5" cols="100" disabled="1">SDP to send</textarea> </p> <p> SDP to receive:<br /> <textarea id="text-for-receive-sdp" rows="5" cols="100"></textarea><br /> <button type="button" onclick="onSDP();">Receive SDP</button> </p>
<p> ICE Candidate to send:<br /> <textarea id="text-for-send-ice" rows="5" cols="100" disabled="1">ICE Candidate to send</textarea> </p> <p>
ICE Candidates to receive:<br /> <textarea id="text-for-receive-ice" rows="5" cols="100"></textarea><br /> <button type="button" onclick="onICE();">Receive ICE Candidates</button> </p> ---><!---- socket ------> <script src="http://localhost:9001/socket.io/socket.io.js"></script>
<script> var localVideo = document.getElementById('local-video'); //var remoteVideo = document.getElementById('remote-video'); var localStream = null; var mediaConstraints = {'mandatory': {'OfferToReceiveAudio':false, 'OfferToReceiveVideo':true }};
// ---- multi people video & audio ---- var videoElementsInUse = {}; var videoElementsStandBy = {}; pushVideoStandBy(getVideoForRemote(0)); pushVideoStandBy(getVideoForRemote(1)); pushVideoStandBy(getVideoForRemote(2));
function getVideoForRemote(index) { var elementID = 'webrtc-remote-video-' + index; var element = document.getElementById(elementID); return element; }
// ---- video element management --- function pushVideoStandBy(element) { videoElementsStandBy[element.id] = element; }
function popVideoStandBy() { var element = null; for (var id in videoElementsStandBy) { element = videoElementsStandBy[id]; delete videoElementsStandBy[id]; return element; } return null; }
function pushVideoInUse(id, element) { videoElementsInUse[id] = element; }
function popVideoInUse(id) { element = videoElementsInUse[id]; delete videoElementsInUse[id]; return element; }
function attachVideo(id, stream) { console.log('try to attach video. id=' + id); var videoElement = popVideoStandBy(); if (videoElement) { videoElement.src = window.URL.createObjectURL(stream); console.log("videoElement.src=" + videoElement.src); pushVideoInUse(id, videoElement); videoElement.style.display = 'block'; } else { console.error('--- no video element stand by.'); } }
function detachVideo(id) { console.log('try to detach video. id=' + id); var videoElement = popVideoInUse(id); if (videoElement) { videoElement.pause(); videoElement.src = ""; console.log("videoElement.src=" + videoElement.src); pushVideoStandBy(videoElement); } else { console.warn('warning --- no video element using with id=' + id); } }
function detachAllVideo() { var element = null; for (var id in videoElementsInUse) { detachVideo(id); } }
function getFirstVideoInUse() { var element = null; for (var id in videoElementsInUse) { element = videoElementsInUse[id]; return element; } return null; }
function getVideoCountInUse() { var count = 0; for (var id in videoElementsInUse) { count++; } return count; }
function isLocalStreamStarted() { if (localStream) { return true; } else { return false; } }
// -------------- multi connections -------------------- var MAX_CONNECTION_COUNT = 3; var connections = {}; // Connection hash function Connection() { // Connection Class var self = this; var id = ""; // socket.id of partner var peerconnection = null; // RTCPeerConnection instance var established = false; // is Already Established var iceReady = false; }
function getConnection(id) { var con = null; con = connections[id]; return con; }
function addConnection(id, connection) { connections[id] = connection; }
function getConnectionCount() { var count = 0; for (var id in connections) { count++; }
console.log('getConnectionCount=' + count); return count;
}
function isConnectPossible() { if (getConnectionCount() < MAX_CONNECTION_COUNT) return true; else return false; }
function getConnectionIndex(id_to_lookup) { var index = 0; for (var id in connections) { if (id == id_to_lookup) { return index; }
index++; } // not found return -1;
}
function deleteConnection(id) { delete connections[id]; }
function stopAllConnections() { for (var id in connections) { var conn = connections[id]; conn.peerconnection.close(); conn.peerconnection = null; delete connections[id]; } }
function stopConnection(id) { var conn = connections[id]; if(conn) { console.log('stop and delete connection with id=' + id); conn.peerconnection.close(); conn.peerconnection = null; delete connections[id]; } else { console.log('try to stop connection, but not found id=' + id); } }
function isPeerStarted() { if (getConnectionCount() > 0) { return true; } else { return false; } }
// ---- socket ------ // create socket var socketReady = false; var port = 9001; var socket = io.connect('http://localhost:' + port + '/');
// socket: channel connected socket.on('connect', onOpened) .on('message', onMessage);
function onOpened(evt) { console.log('socket opened.'); socketReady = true;
var roomname = getRoomName(); // 会議室名を取得する socket.emit('enter', roomname); console.log('enter to ' + roomname);
}
// socket: accept connection request function onMessage(evt) { var id = evt.from; var target = evt.sendto; var conn = getConnection(id);
if (evt.type === 'call') { if (! isLocalStreamStarted()) { return; } if (conn) { return; // already connected } if (isConnectPossible()) { socket.json.send({type: "response", sendto: id }); } else { console.warn('max connections. so ignore call'); } return; } else if (evt.type === 'response') { sendOffer(id); return; } else if (evt.type === 'offer') { console.log("Received offer, set offer, sending answer....") onOffer(evt); } else if (evt.type === 'answer' && isPeerStarted()) { // ** console.log('Received answer, settinng answer SDP'); onAnswer(evt); } else if (evt.type === 'candidate' && isPeerStarted()) { // ** console.log('Received ICE candidate...'); onCandidate(evt); } else if (evt.type === 'user dissconnected' && isPeerStarted()) { // ** console.log("disconnected"); //stop(); detachVideo(id); // force detach video stopConnection(id); }
}
function getRoomName() { // たとえば、 URLに ?roomname とする var url = document.location.href; var args = url.split('?'); if (args.length > 1) { var room = args[1]; if (room != "") { return room; } } return "_defaultroom"; }
// ----------------- handshake -------------- //var textForSendSDP = document.getElementById('text-for-send-sdp'); //var textForSendICE = document.getElementById('text-for-send-ice'); //var textToReceiveSDP = document.getElementById('text-for-receive-sdp'); //var textToReceiveICE = document.getElementById('text-for-receive-ice'); //var iceSeparator = '------ ICE Candidate -------'; //var CR = String.fromCharCode(13);
/*-- function onSDP() { var text = textToReceiveSDP.value; var evt = JSON.parse(text); if (peerConnection) { onAnswer(evt); } else { onOffer(evt); }
//textToReceiveSDP.value ="";
} --*/
//--- multi ICE candidate --- /*-- function onICE() { var text = textToReceiveICE.value; var arr = text.split(iceSeparator); for (var i = 1, len = arr.length; i < len; i++) { var evt = JSON.parse(arr[i]); onCandidate(evt); }
textToReceiveICE.value ="";
} ---*/
function onOffer(evt) { console.log("Received offer...") console.log(evt); setOffer(evt); sendAnswer(evt); //peerStarted = true; -- }
function onAnswer(evt) { console.log("Received Answer...") console.log(evt); setAnswer(evt); }
function onCandidate(evt) { var id = evt.from; var conn = getConnection(id); if (! conn) { console.error('peerConnection not exist!'); return; }
// --- check if ice ready --- if (! conn.iceReady) { console.warn("PeerConn is not ICE ready, so ignore"); return; } var candidate = new RTCIceCandidate({sdpMLineIndex:evt.sdpMLineIndex, sdpMid:evt.sdpMid, candidate:evt.candidate}); console.log("Received Candidate...") console.log(candidate); conn.peerconnection.addIceCandidate(candidate);
}
function sendSDP(sdp) { var text = JSON.stringify(sdp); console.log("---sending sdp text ---"); console.log(text); //textForSendSDP.value = text;
// send via socket socket.json.send(sdp);
}
function sendCandidate(candidate) { var text = JSON.stringify(candidate); console.log("---sending candidate text ---"); console.log(text); //textForSendICE.value = (textForSendICE.value + CR + iceSeparator + CR + text + CR); //textForSendICE.scrollTop = textForSendICE.scrollHeight;
// send via socket socket.json.send(candidate);
}
// ---------------------- video handling ----------------------- // start local video function startVideo() { navigator.webkitGetUserMedia({video: true, audio: true}, function (stream) { // success localStream = stream; localVideo.src = window.webkitURL.createObjectURL(stream); localVideo.play(); localVideo.volume = 0; }, function (error) { // error console.error('An error occurred:'); console.error(error); return; } ); }
// stop local video function stopVideo() { localVideo.src = ""; localStream.stop(); }
// ---------------------- connection handling ----------------------- function prepareNewConnection(id) { var pc_config = {"iceServers":[]}; var peer = null; try { peer = new webkitRTCPeerConnection(pc_config); } catch (e) { console.log("Failed to create PeerConnection, exception: " + e.message); } var conn = new Connection(); conn.id = id; conn.peerconnection = peer; peer.id = id; addConnection(id, conn);
// send any ice candidates to the other peer peer.onicecandidate = function (evt) { if (evt.candidate) { console.log(evt.candidate); sendCandidate({type: "candidate", sendto: conn.id, sdpMLineIndex: evt.candidate.sdpMLineIndex, sdpMid: evt.candidate.sdpMid, candidate: evt.candidate.candidate}); } else { console.log("End of candidates. ------------------- phase=" + evt.eventPhase); conn.established = true; } }; console.log('Adding local stream...'); peer.addStream(localStream); peer.addEventListener("addstream", onRemoteStreamAdded, false); peer.addEventListener("removestream", onRemoteStreamRemoved, false) // when remote adds a stream, hand it on to the local video element function onRemoteStreamAdded(event) { console.log("Added remote stream"); attachVideo(this.id, event.stream); //remoteVideo.src = window.webkitURL.createObjectURL(event.stream); } // when remote removes a stream, remove it from the local video element function onRemoteStreamRemoved(event) { console.log("Remove remote stream"); detachVideo(this.id); //remoteVideo.pause(); //remoteVideo.src = ""; } return conn;
}
function sendOffer(id) { var conn = getConnection(id); if (!conn) { conn = prepareNewConnection(id); }
conn.peerconnection.createOffer(function (sessionDescription) { // in case of success conn.iceReady = true; conn.peerconnection.setLocalDescription(sessionDescription); sessionDescription.sendto = id; sendSDP(sessionDescription); }, function () { // in case of error console.log("Create Offer failed"); }, mediaConstraints); conn.iceReady = true;
}
function setOffer(evt) { var id = evt.from; var conn = getConnection(id); if (! conn) { conn = prepareNewConnection(id); conn.peerconnection.setRemoteDescription(new RTCSessionDescription(evt)); } else { console.error('peerConnection alreay exist!'); } }
function sendAnswer(evt) { console.log('sending Answer. Creating remote session description...' ); var id = evt.from; var conn = getConnection(id); if (! conn) { console.error('peerConnection not exist!'); return }
conn.peerconnection.createAnswer(function (sessionDescription) { // in case of success conn.iceReady = true; conn.peerconnection.setLocalDescription(sessionDescription); sessionDescription.sendto = id; sendSDP(sessionDescription); }, function () { // in case of error console.log("Create Answer failed"); }, mediaConstraints); conn.iceReady = true;
}
function setAnswer(evt) { var id = evt.from; var conn = getConnection(id); if (! conn) { console.error('peerConnection not exist!'); return } conn.peerconnection.setRemoteDescription(new RTCSessionDescription(evt)); }
// -------- handling user UI event ----- /----- // start the connection upon user request function connect() { if (!peerStarted && localStream && socketReady) { // ** //if (!peerStarted && localStream) { // -- sendOffer(); peerStarted = true; } else { alert("Local stream not running yet - try again."); } } ----------/
// call others before connecting peer function call() { if (! isLocalStreamStarted()) { alert("Local stream not running yet. Please [Start Video] or [Start Screen]."); return; } if (! socketReady) { alert("Socket is not connected to server. Please reload and try again."); return; }
// call others, in same room console.log("call others in same room, befeore offer"); socket.json.send({type: "call"});
}
// stop the connection upon user request function hangUp() { console.log("Hang up."); socket.json.send({type: "bye"}); detachAllVideo(); stopAllConnections(); }
/-- function stop() { peerConnection.close(); peerConnection = null; //peerStarted = false; -- } --/
</script> </body> </html>