WebRTCを使用したマルチユーザーチャット

画像



WebRTCは、P2P接続を整理し、ブラウザー間で直接データを転送できるようにするブラウザーが提供するAPIです。 WebRTCを使用して独自のビデオチャットを作成するためのインターネット上のチュートリアルがかなりあります。 たとえば、これ Habréに関する記事です。 ただし、それらはすべて2つのクライアントの接続に制限されています。 この記事では、WebRTCを使用して3人以上のユーザー間でメッセージを接続および交換する方法について説明します。



RTCPeerConnectionインターフェースは、2つのブラウザー間のピアツーピア接続です。 3人以上のユーザーを接続するには、メッシュネットワーク(各ノードが他のすべてのノードに接続されているネットワーク)を整理する必要があります。

次のスキームを使用します。

  1. ページを開くと、 location.hashでルームIDの空き状況を確認します
  2. ルームIDが指定されていない場合、新しい
  3. 指定した会議室に参加したいというメッセージをシグナリングサーバーに送信します
  4. シグナリングサーバーは、新しいユーザーに関する通知をこの部屋の他のクライアントに送信します。
  5. すでにルームにいるクライアントは、SDPオファーに新規参入者を送信します
  6. 初心者がオファーに応答する


0.シグナリングサーバー



ご存知のように、WebRTCはブラウザー間のP2P接続を提供しますが、サービスメッセージを交換するために追加のトランスポートが必要です。 この例では、socket.ioを使用してNode.JSで記述されたWebSocketサーバーがそのようなトランスポートとして機能します。



var socket_io = require("socket.io"); module.exports = function (server) { var users = {}; var io = socket_io(server); io.on("connection", function(socket) { //       socket.on("room", function(message) { var json = JSON.parse(message); //      users[json.id] = socket; if (socket.room !== undefined) { //      - ,    socket.leave(socket.room); } //     socket.room = json.room; socket.join(socket.room); socket.user_id = json.id; //            socket.broadcast.to(socket.room).emit("new", json.id); }); // ,   WebRTC (SDP offer, SDP answer  ICE candidate) socket.on("webrtc", function(message) { var json = JSON.parse(message); if (json.to !== undefined && users[json.to] !== undefined) { //          ,    ... users[json.to].emit("webrtc", message); } else { // ...    socket.broadcast.to(socket.room).emit("webrtc", message); } }); // -  socket.on("disconnect", function() { //   ,     socket.broadcast.to(socket.room).emit("leave", socket.user_id); delete users[socket.user_id]; }); }); };
      
      





1. index.html



ページ自体のソースコードは非常に単純です。 この記事はそれに関するものではないので、私は意図的にレイアウトや他の美しいものに注意を払いませんでした。 誰かがそれを美しくしたいなら、それは難しくありません。



 <html> <head> <title>WebRTC Chat Demo</title> <script src="/socket.io/socket.io.js"></script> </head> <body> <div>Connected to <span id="connection_num">0</span> peers</div> <div><textarea id="message"></textarea><br/><button onclick="sendMessage();">Send</button></div> <div id="room_link"></div> <div id="chatlog"></div> <script type="text/javascript" src="/javascripts/main.js"></script> </body> </html>
      
      





2. main.js



2.0。 ページ要素とWebRTCインターフェイスへのリンクを取得する


 var chatlog = document.getElementById("chatlog"); var message = document.getElementById("message"); var connection_num = document.getElementById("connection_num"); var room_link = document.getElementById("room_link");
      
      





それでも、WebRTCインターフェイスにアクセスするには、ブラウザープレフィックスを使用する必要があります。



 var PeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection; var SessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription; var IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
      
      





2.1。 ルームIDの定義


ここでは、部屋とユーザーの一意の識別子を生成する関数が必要です。 これらの目的でUUIDを使用します。



 function uuid () { var s4 = function() { return Math.floor(Math.random() * 0x10000).toString(16); }; return s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4(); }
      
      





次に、住所から部屋の識別子を取得してみます。 指定しない場合は、新しいものを生成します。 ページに現在の会議室へのリンクを表示し、そのために現在のユーザーの識別子を生成します。



 var ROOM = location.hash.substr(1); if (!ROOM) { ROOM = uuid(); } room_link.innerHTML = "<a href='#"+ROOM+"'>Link to the room</a>"; var ME = uuid();
      
      





2.2。 Webソケット


ページを開くとすぐに、信号サーバーに接続し、部屋に入るようにリクエストを送信し、メッセージハンドラーを指定します。



 // ,           var socket = io.connect("", {"sync disconnect on unload": true}); socket.on("webrtc", socketReceived); socket.on("new", socketNewPeer); //        socket.emit("room", JSON.stringify({id: ME, room: ROOM})); //      ,   WebRTC function sendViaSocket(type, message, to) { socket.emit("webrtc", JSON.stringify({id: ME, to: to, type: type, data: message})); }
      
      





2.3。 PeerConnection設定


ほとんどのプロバイダーは、NAT経由でインターネット接続を提供します。 このため、直接接続はそれほど簡単ではありません。 接続を作成するとき、ブラウザがNATをバイパスするために使用するSTUNおよびTURNサーバーのリストを指定する必要があります。 また、接続のためのいくつかの追加オプションも示します。



 var server = { iceServers: [ {url: "stun:23.21.150.121"}, {url: "stun:stun.l.google.com:19302"}, {url: "turn:numb.viagenie.ca", credential: "your password goes here", username: "example@example.com"} ] }; var options = { optional: [ {DtlsSrtpKeyAgreement: true}, //     Chrome  Firefox {RtpDataChannels: true} //   Firefox   DataChannels API ] }
      
      





2.4。 新しいユーザーを接続する


ルームに新しいごちそうが追加されると、サーバーは新しいメッセージを送信します。 上記のメッセージハンドラーによると、socketNewPeer関数が呼び出されます。



 var peers = {}; function socketNewPeer(data) { peers[data] = { candidateCache: [] }; //    var pc = new PeerConnection(server, options); //   initConnection(pc, data, "offer"); //      peers[data].connection = pc; //  DataChannel        var channel = pc.createDataChannel("mychannel", {}); channel.owner = data; peers[data].channel = channel; //     bindEvents(channel); //  SDP offer pc.createOffer(function(offer) { pc.setLocalDescription(offer); }); } function initConnection(pc, id, sdpType) { pc.onicecandidate = function (event) { if (event.candidate) { //    ICE         peers[id].candidateCache.push(event.candidate); } else { //    ,     ,    //        SDP offer  SDP answer (    )... sendViaSocket(sdpType, pc.localDescription, id); // ...     ICE  for (var i = 0; i < peers[id].candidateCache.length; i++) { sendViaSocket("candidate", peers[id].candidateCache[i], id); } } } pc.oniceconnectionstatechange = function (event) { if (pc.iceConnectionState == "disconnected") { connection_num.innerText = parseInt(connection_num.innerText) - 1; delete peers[id]; } } } function bindEvents (channel) { channel.onopen = function () { connection_num.innerText = parseInt(connection_num.innerText) + 1; }; channel.onmessage = function (e) { chatlog.innerHTML += "<div>Peer says: " + e.data + "</div>"; }; }
      
      





2.5。 SDPオファー、SDPアンサー、ICE候補


これらのメッセージのいずれかを受信すると、対応するメッセージのハンドラーを呼び出します。

 function socketReceived(data) { var json = JSON.parse(data); switch (json.type) { case "candidate": remoteCandidateReceived(json.id, json.data); break; case "offer": remoteOfferReceived(json.id, json.data); break; case "answer": remoteAnswerReceived(json.id, json.data); break; } }
      
      





2.5.0 SDPオファー


 function remoteOfferReceived(id, data) { createConnection(id); var pc = peers[id].connection; pc.setRemoteDescription(new SessionDescription(data)); pc.createAnswer(function(answer) { pc.setLocalDescription(answer); }); } function createConnection(id) { if (peers[id] === undefined) { peers[id] = { candidateCache: [] }; var pc = new PeerConnection(server, options); initConnection(pc, id, "answer"); peers[id].connection = pc; pc.ondatachannel = function(e) { peers[id].channel = e.channel; peers[id].channel.owner = id; bindEvents(peers[id].channel); } } }
      
      





2.5.1 SDPアンサー


 function remoteAnswerReceived(id, data) { var pc = peers[id].connection; pc.setRemoteDescription(new SessionDescription(data)); }
      
      





2.5.2 ICE候補


 function remoteCandidateReceived(id, data) { createConnection(id); var pc = peers[id].connection; pc.addIceCandidate(new IceCandidate(data)); }
      
      





2.6。 メッセージ送信


[ 送信 ]ボタンをクリックすると、 sendMessage関数が呼び出されます。 彼女がすることは、ピアのリストを調べて、指定されたメッセージを全員に送信することです。



 function sendMessage () { var msg = message.value; for (var peer in peers) { if (peers.hasOwnProperty(peer)) { if (peers[peer].channel !== undefined) { try { peers[peer].channel.send(msg); } catch (e) {} } } } chatlog.innerHTML += "<div>Peer says: " + msg + "</div>"; message.value = ""; }
      
      





2.7。 断線


最後に、ページを閉じるときに、開いているすべての接続を閉じるとよいでしょう。



 window.addEventListener("beforeunload", onBeforeUnload); function onBeforeUnload(e) { for (var peer in peers) { if (peers.hasOwnProperty(peer)) { if (peers[peer].channel !== undefined) { try { peers[peer].channel.close(); } catch (e) {} } } } }
      
      





3.ソースのリスト



  1. http://www.html5rocks.com/en/tutorials/webrtc/basics/
  2. https://www.webrtc-experiment.com/docs/WebRTC-PeerConnection.html
  3. https://developer.mozilla.org/en-US/docs/Web/Guide/API/WebRTC/WebRTC_basics



All Articles