Skip to content

Instantly share code, notes, and snippets.

@kaznak
Last active January 26, 2023 06:10
Show Gist options
  • Save kaznak/f268699174cf6365deb342c11e7ae37f to your computer and use it in GitHub Desktop.
Save kaznak/f268699174cf6365deb342c11e7ae37f to your computer and use it in GitHub Desktop.
連載: WebRTCを使ってみよう! (3) -- WebRTC初心者でも簡単にできる!Node.jsで仲介(シグナリング)を作ってみよう

連載: WebRTCを使ってみよう! (3) – WebRTC初心者でも簡単にできる!Node.jsで仲介(シグナリング)を作ってみよう

COMMENT

概要

連載: WebRTCを使ってみよう! (3) WebRTC初心者でも簡単にできる!Node.jsで仲介(シグナリング)を作ってみよう https://html5experts.jp/mganeko/5349/

これを実際に作ってみた。 2017-09-28 時点では結構変わっている部分有ったので色々いじった、のでメモ。

node のインストールには nodebrew を使った方がよさげ。 最初は yum でインストールしたが、 segfault で落ちているのに気付かずだいぶハマった。

カメラ取ったり、jsダウンロードさせたりするのは TLS 必須になってきてるようだ。 あらかじめドメインは用意しておいて、証明書を let’s encrypt などで取った方がよい。 TLS 対応のためにシグナリングサーバはだいぶ変わってしまった。

<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>WebRTC 1 to 1 signaling</title>
</head>
<body>
<button type="button" onclick="startVideo();">Start video</button>
<button type="button" onclick="stopVideo();">Stop video</button>
&nbsp;&nbsp;&nbsp;&nbsp;
<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="https://localhost:3000/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 = 3000;
var socket = io.connect('https://localhost:' + 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.URL.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 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.URL.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>
var fs = require('fs');
var ws = require('socket.io');
var https = require('https');
var host = 'localhost';
var port = 3000;
var key = '/etc/letsencrypt/live/' + host + '/privkey.pem';
var cert = '/etc/letsencrypt/live/' + host + '/cert.pem';
var opts = {
// SSL証明書と秘密鍵を準備する
key: fs.readFileSync(key).toString(),
cert: fs.readFileSync(cert).toString(),
'log level': 3
};
var ssl_server = https.createServer(opts, function(req, res) {
res.end();
});
var server = ws.listen(ssl_server);
// クライアントからの接続イベントを処理
server.on('connection', function(socket) {
// クライアントからのメッセージ受信したとき
socket.on('message', function(data) {
console.log(data);
// 受信したメッセージを全てのクライアントに送信する
socket.broadcast.emit('message', data);
});
// クライアントが切断したとき
socket.on('disconnect', function(){
console.log('connection disconnect');
});
// 通信がクローズしたとき
socket.on('close', function(){
console.log('connection close');
});
// エラーが発生したとき
socket.on('error', function(err){
console.log(err);
});
});
// サーバーを起動し続ける
ssl_server.listen(port, function() {
console.log('Listening on ' + port);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment