WebRTCでStunサーバを経由して接続
WebRTCで接続する場合
Webサーバ
通信を実際に行う画面。これがないと始まらない
シグナリングサーバ
ピアツーピア通信を行うもの。基本的にはこれがあれば通信を行うことが可能。ローカルネットワーク内ではこれだけで行けるんじゃないかな?
Stunサーバ
シグナリングサーバでは相手側のグローバルipなどがわからないのでそれを調べたりするのにこれが必要。これとシグナリングサーバを経由して通信する。
一度通信を行えばネットワーク負荷はかからないので通信料はあまりネックにならないとか
Turnサーバ
なんかStunサーバとか使っても通信できない場合の最後の手段的なもの、これはなんかネットワーク負荷がかかるらしい。
環境
さくらのクラウドで以下を用意
Webサーバ
yum update -y rpm -Uvh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm yum -y update yum -y groupinstall "Development Tools" cd /etc/yum.repos.d/ wget http://wing-repo.net/wing/6/EL6.wing.repo wget http://wing-repo.net/wing/extras/6/EL6.wing-extras.repo yum clean all yum -y install yum-priorities yum -y remove git yum -y install git --enablerepo=wing rpm -Uvh http://mirror.webtatic.com/yum/el6/latest.rpm yum -y --enablerepo=epel install re2c libmcrypt libmcrypt-devel yum -y install libxml2-devel bison bison-devel openssl-devel curl-devel libjpeg-devel libpng-devel libmcrypt-devel readline-devel libtidy-devel libxslt-devel httpd-devel enchant-devel libXpm libXpm-devel freetype-devel t1lib t1lib-devel gmp-devel libc-client-devel libicu-devel oniguruma-devel net-snmp net-snmp-devel bzip2-devel yum -y install php55w php55w-bcmath php55w-cli php55w-common php55w-dba php55w-devel php55w-embedded php55w-enchant php55w-fpm php55w-gd php55w-imap php55w-interbase php55w-intl php55w-ldap php55w-mbstring php55w-mcrypt php55w-mssql php55w-mysql php55w-odbc php55w-opcache php55w-pdo php55w-pear.noarch php55w-pecl-apcu php55w-pecl-apcu-devel php55w-pecl-memcache php55w-pecl-xdebug php55w-pgsql php55w-process php55w-pspell php55w-recode php55w-snmp php55w-soap php55w-tidy php55w-xml php55w-xmlrpc service php-fpm start chkconfig php-fpm on rpm -ivh http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm yum -y install nginx --enablerepo=nginx service nginx start chkconfig nginx on
シグナリングサーバ
yum update -y rpm -Uvh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm yum -y update yum -y groupinstall "Development Tools" cd /etc/yum.repos.d/ wget http://wing-repo.net/wing/6/EL6.wing.repo wget http://wing-repo.net/wing/extras/6/EL6.wing-extras.repo yum clean all yum -y install yum-priorities yum -y remove git yum -y install git --enablerepo=wing yum -y install nodejs npm --enablerepo=epel mkdir -p /var/www/app1 cd /var/www/app1 npm install socket.io@0.9 npm install -g pm2
signaling.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('message', function(message) { socket.broadcast.emit('message', message); }); socket.on('disconnect', function() { socket.broadcast.emit('user disconnected'); }); });
起動
m2 start signaling.js
STUNサーバ
yum update -y rpm -Uvh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm yum -y update yum -y groupinstall "Development Tools" cd /etc/yum.repos.d/ wget http://wing-repo.net/wing/6/EL6.wing.repo wget http://wing-repo.net/wing/extras/6/EL6.wing-extras.repo yum clean all yum -y install yum-priorities yum -y remove git yum -y install git --enablerepo=wing mkdir -p /var/www/stun1 cd /var/www/stun1 wget http://turnserver.open-sys.org/downloads/v3.2.4.1/turnserver-3.2.4.1-CentOS6.5-x86_64.tar.gz tar zxf turnserver-3.2.4.1-CentOS6.5-x86_64.tar.gz cd turnserver-3.2.4.1 ./install.sh
/etc/turnserver/turnserver.conf
stun-only
※stunのみ使用にする
※他にも設定はあるけど変更点はこれだけ
起動
/usr/bin/turnserver -o -v -c /etc/turnserver/turnserver.conf
index.htmlの修正
<!DOCTYPE html> <html> <head> <title>WebRTC 1 to 1 signaling</title> </head> <body> <h1>外部テスト</h1> <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="hangUp();">Hang Up</button> <br /> <div> <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> </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://133.242.53.63:9001/socket.io/socket.io.js"></script> <script> var localVideo = document.getElementById('local-video'); var remoteVideo = document.getElementById('remote-video'); var localStream = null; var peerConnection = null; var peerStarted = false; var mediaConstraints = {'mandatory': {'OfferToReceiveAudio':false, 'OfferToReceiveVideo':true }}; // ---- socket ------ // create socket var socketReady = false; var port = 9001; var socket = io.connect('http://133.242.53.63:' + port + '/'); // socket: channel connected socket.on('connect', onOpened) .on('message', onMessage); function onOpened(evt) { console.log('socket opened.'); socketReady = true; } // socket: accept connection request function onMessage(evt) { if (evt.type === 'offer') { console.log("Received offer, set offer, sending answer....") onOffer(evt); } else if (evt.type === 'answer' && peerStarted) { console.log('Received answer, settinng answer SDP'); onAnswer(evt); } else if (evt.type === 'candidate' && peerStarted) { console.log('Received ICE candidate...'); onCandidate(evt); } else if (evt.type === 'user dissconnected' && peerStarted) { console.log("disconnected"); stop(); } } // ----------------- 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 candidate = new RTCIceCandidate({sdpMLineIndex:evt.sdpMLineIndex, sdpMid:evt.sdpMid, candidate:evt.candidate}); console.log("Received Candidate...") console.log(candidate); 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: false}, 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; } ); } // stop local video function stopVideo() { localVideo.src = ""; localStream.stop(); } // ---------------------- connection handling ----------------------- function prepareNewConnection() { // var pc_config = {"iceServers":[]}; var pc_config = {"iceServers":[ {"url":"stun:133.242.48.9:3478"} ]}; var peer = null; try { peer = new webkitRTCPeerConnection(pc_config); } catch (e) { console.log("Failed to create peerConnection, exception: " + e.message); } // send any ice candidates to the other peer peer.onicecandidate = function (evt) { if (evt.candidate) { console.log(evt.candidate); sendCandidate({type: "candidate", sdpMLineIndex: evt.candidate.sdpMLineIndex, sdpMid: evt.candidate.sdpMid, candidate: evt.candidate.candidate} ); } else { console.log("End of candidates. ------------------- phase=" + evt.eventPhase); } }; 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"); 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"); remoteVideo.src = ""; } return peer; } function sendOffer() { peerConnection = prepareNewConnection(); peerConnection.createOffer(function (sessionDescription) { // in case of success peerConnection.setLocalDescription(sessionDescription); console.log("Sending: SDP"); console.log(sessionDescription); sendSDP(sessionDescription); }, function () { // in case of error console.log("Create Offer failed"); }, mediaConstraints); } function setOffer(evt) { if (peerConnection) { console.error('peerConnection alreay exist!'); } peerConnection = prepareNewConnection(); peerConnection.setRemoteDescription(new RTCSessionDescription(evt)); } function sendAnswer(evt) { console.log('sending Answer. Creating remote session description...' ); if (! peerConnection) { console.error('peerConnection NOT exist!'); return; } peerConnection.createAnswer(function (sessionDescription) { // in case of success peerConnection.setLocalDescription(sessionDescription); console.log("Sending: SDP"); console.log(sessionDescription); sendSDP(sessionDescription); }, function () { // in case of error console.log("Create Answer failed"); }, mediaConstraints); } function setAnswer(evt) { if (! peerConnection) { console.error('peerConnection NOT exist!'); return; } 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."); } } // stop the connection upon user request function hangUp() { console.log("Hang up."); stop(); } function stop() { peerConnection.close(); peerConnection = null; peerStarted = false; } </script> </body> </html>
確認
クライアント1:うぃんどws(れおねっと)
クライアント2:まc(うぃまx)
クライアント1:スマホ(3g)
クライアント2:まc(うぃまx)
※なんかスマホ経由のがちょっと怪しいけどまあなんとかできてる感じでした
時は金なり?
テストが終わったらクラウドサーバはすぐに止めたw
お金かかるしw