WebRTCはWeb Real-Time Communicationの略でWEBブラウザ間でリアルタイムに通信を行う際に利用されるテクノロジーです。

WebRTCを利用することでWEBカメラの映像や音声データをブラウザ間(Peer to Peer)で送受信することができるのでWebRTCを利用してWEB会議などのアプリケーションを作成することができます。

本文書ではPeerJSというJavaScriptライブラリを利用してWebRTCを使った通信を実装します。PeerJSの公式ドキュメントを参考に動作確認を進めていき、本文書を読み終えるとPeerJSを利用したブラウザ間のデータ送信とWEBカメラの映像の送受信方法を理解することができます。

PeerJSサーバのインストール

PeerJSのクライアントであるブラウザ間で接続確立するためには接続の仲介を行うサーバが必要となります。PeerJSではサーバの導入が必要ですがデータはサーバを経由して送受信するわけではなくブラウザ間で直接データの送受信を行います。

PeerJSサーバのインストールを行うため任意のフォルダを作成し、npm init -yコマンドを実行してpackage.jsonファイルを作成します。


 % npm init -y

peerJSのサーバのライブラリをインストールします。


 % npm install peer

npxコマンドを利用したpeerJSサーバを起動します。ここではポートの9000番で起動させます。


 % npx peerjs --port 9000
Started PeerServer on ::, port: 9000, path: / (v. 0.6.1

ブラウザからlocalhostのポート9000にアクセスを行います。

ブラウザからlocalhost:9000にアクセスするとJSONでname, description, websiteが戻されることでpeerJSサーバが起動していることを確認することができます。

peerJSサーバの動作確認
peerJSサーバの動作確認

クライアント(ブラウザ)の設定

PeerJSサーバの起動の確認ができたので、ブラウザ間でデータの送受信を行うための設定を行っていきます。

PeerJSのクライアントではcdnを利用します。


<script src="https://unpkg.com/peerjs@1.3.1/dist/peerjs.min.js">

PeerJSではクライアントを識別するためにIDを付与する必要があり、今回は手動で固定のIDを設定するため異なるIDを持つhtmlファイル2つを作成します。

PeerJSサーバへの接続

client1, client2のIDをそれぞれ設定したhtmlファイル(client1.html, client2.html)を2つ準備します。PeerJSサーバへの接続にはPeerオブジェクトの作成を行います。


const peer = new Peer('client1', {
  host: 'localhost',
  port: 9000,
  path: '/'
});

引数にIDを手動で設定しています。2番目の引数にはオブジェクトでPeerJSのサーバについての情報を設定します。client1のIDを持つclient1.htmlファイルを作成します。


<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Client1</title>
	<script src="https://unpkg.com/peerjs@1.3.1/dist/peerjs.min.js"></script>
	<script>
		const peer = new Peer('client1', {
			host: 'localhost',
			port: 9000,
			path: '/'
		});
	</script>
</head>
<body>
	<h1>Client1</h1>
</body>
</html>

client2のIDを持つclient2.htmlファイルを作成します。


<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Client2</title>
	<script src="https://unpkg.com/peerjs@1.3.1/dist/peerjs.min.js"></script>
	<script>
		const peer = new Peer('client2', {
			host: 'localhost',
			port: 9000,
			path: '/'
		});
	</script>
</head>
<body>
	<h1>Client2</h1>
</body>
</html>

client1.htmlとclient2.htmlファイルをブラウザで開くとPeerJSを起動したターミナルには接続のメッセージが表示されます。


 % npx peerjs --port 9000
Started PeerServer on ::, port: 9000, path: / (v. 0.6.1)
Client connected: client1
Client connected: client2

クライアントからのPeerJSサーバへの接続が確認できました。

client1.html, client2.htmlをリロードまたはファイルを閉じるとクライアントのdisconnectedのメッセージがPeerJSサーバを実行したターミナルに表示されます。
fukidashi

クライアント間の接続

クライアントからPeerJSサーバへの接続が完了したので次はクライアント間の接続確認を行います。ここではclient1からclient2へ接続を行います。

PeerJSのクライアントにはいくつかのイベントが設定でき、connectionイベントを使って別のクライアントから接続を確認することができます。接続があるとconnectionイベントが発火され別の処理を実行することができます。接続されるclient2側でconnectionイベントを設定します。


peer.on('connection', ()=>{
  console.log('他のクライアントからの接続あり')
});

client1.htmlファイルではclient2への接続を設定します。その場合はpeer.connectメソッドを利用します。connectメソッドには接続する相手のIDを設定します。


const conn = peer.connect('client2')

各ファイル内で設定が完了後、client2.htmlファイルを開いた後にclient1.htmlファイルを開くとclient1からclient2へ接続が行われるので、connectionイベントで接続を検知しclient2.htmlのデベロッパーツールのコンソールには’他のクライアントからの接続あり’メッセージが表示されます。

接続を行ったclient1側でも接続が完了したら処理を実行できるようにopenイベントを設定します。openイベントは接続が完了されなければ実行されません。


conn.on('open',()=>{
  console.log('client2に接続できました。')
})

client2.htmlファイルを開いた状態で、client1.htmlファイルをリロードまたは開くとコンソールには”client2に接続できました。”と表示されます。

connectメソッドで設定したIDを別のIDに設定すると当たり前ですがメッセージは表示されません。
fukidashi

メッセージの送信

PeerJSサーバ、クライアント間の接続が確認できたので、次はclient1からclient2にメッセージを送信します。メッセージの送信にはsendメソッドを利用します。client2との接続が完了したら、メッセージをclient2に送信します。


conn.on('open',()=>{
  console.log('client2に接続できました。')
  conn.send('こんにちは!')
})

sendメソッドを利用してメッセージを送信しましたが、client2側ではデータを受信するための設定が行われていないため何も反応はありません。

先ほどまではconnectionメソッドのcallback関数の引数には何も設定していませんでしたがcallback関数の引数にはデータコネクションのオブジェクトを受け取ることができるのでデータの受け取りのためにデータコネクションを利用します。dataにはsendメソッドのメッセージが入っています。


peer.on('connection', conn => {
	console.log('他のクライアントからの接続あり')
	conn.on('data', data => {
		console.log(`client1からのメッセージ:${data}`);
	});
});

client2.htmlをリロード後にclient1.htmlファイルを開くとclient2.htmlのコンソールには下記のメッセージが表示されます。client1のsendメソッドで送信した内容が表示され、クライアント間でデータの送受信が行われることが確認できます。

clien1からのメッセージを受信
clien1からのメッセージを受信

client1側でもdataイベントを利用してデータを受信できるように設定を行います。


conn.on('data', data => {
  console.log(`client2からのメッセージ:${data}`);
});

peer.on('connection', conn =>{
  console.log('他のクライアントからの接続あり')
  conn.on('data', data => {
    console.log(`client1からのメッセージ:${data}`);
    conn.send('こんにちはclient2です。');
  });
});

ここまでの設定でPeerJSを利用してブラウザ間でメッセージの送受信を行うことができました。

ユーザのインタラクションによりメッセージを送信できるか確認するためにClient2にボタンを追加します。ボタンをクリックするとclickイベントによりsed_messageメソッドが実行されます。


<body>
  <h1>Client2</h1>
  <input type="button" value="button" onclick="send_message()">
</body>

connectを定義してconnを保存し、send_message内でconnect.sendメソッドを実行します。


const peer = new Peer('client2', {
  host: 'localhost',
  port: 9000,
  path: '/'
});

let connect;
peer.on('connection', conn => {
  console.log('他のクライアントからの接続あり')
  connect = conn
  conn.on('data', data => {
    console.log(`client1からのメッセージ:${data}`);
    conn.send('こんにちはclient2です。');
  });
});

function send_message(){
  connect.send('test')
}

クリックボタンを押すとclient1側でメッセージを受け取ることができます。今回はボタンでしたが、input要素などを追加することで入力した文字を送受信することも可能です。

IDの取得

IDを手動で設定を行ってきましたが、IDを下記のように設定しない場合はIDが生成され利用することができます。IDがわからない別のクライアントと通信する場合はこのIDを伝える仕組みが必要となります。


const peer = new Peer({
  host: 'localhost',
  port: 9000,
  path: '/'
});

peer.on('open', function (id) {
  console.log('My peer ID is: ' + id);
});

WEBカメラの映像を送信

データの送受信方法を確認したので次はWEBカメラの映像をclient1からclient2に送信する方法を確認していきます。

ブラウザ上にWEBカメラの映像を表示する方法については公開ずみの以下の文書を確認してください。

以下のコードでブラウザ上にWEBカメラの映像が表示されます。


<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Client1</title>

</head>
<body>
	<h1>Client1</h1>
	<div>
		<video id="video"></video>
	</div>
	<script src="https://unpkg.com/peerjs@1.3.1/dist/peerjs.min.js"></script>
	<script>
		const peer = new Peer('client1', {
			host: 'localhost',
			port: 9000,
			path: '/'
		});

		const video = document.getElementById("video")
		navigator.mediaDevices.getUserMedia({
			video: true,
			audio: false,
		}).then(stream => {
			video.srcObject = stream;
			video.play()
		}).catch(e => {
			console.log(e)
		})		
	</script>
</body>
</html>

映像を別のクライアントに送信するためにcallメソッドを利用します。引数に接続を行うクライアントのIDとWEBカメラのstreamオブジェクトを指定します。これで映像を送る側の設定は完了です。


const video = document.getElementById("video")
navigator.mediaDevices.getUserMedia({
  video: true,
  audio: false,
}).then(stream => {
  peer.call('client2',stream)
}).catch(e => {
  console.log(e)
})

次に受け取る側の設定を行います。client2側ではcallイベントを設定し、call.answerメソッドの引数にはclient2側のWEBカメラの映像のstreamオブジェクトを設定することができますが、ここではclient1側から映像を受け取るだけで送信は行いません。

引数に何も設定していませんがcall.answerメソッドは必須です。
fukidashi

streamイベントを利用してclient1から受け取ったstreamオブジェクトを使ってブラウザ上に表示させます。ブラウザ上に表示させる方法についてはWEBカメラの映像を表示させる方法と同じです。


<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Client2</title>
</head>
<body>
	<h1>Client2</h1>
	<div>
		<video id="video" muted="muted"></video>
	</div>
	<script src="https://unpkg.com/peerjs@1.3.1/dist/peerjs.min.js"></script>
	<script>
		const peer = new Peer('client2', {
			host: 'localhost',
			port: 9000,
			path: '/'
		});

		const video = document.getElementById("video")

		peer.on('call', function (call) {
			call.answer();
			call.on('stream', stream => {
				video.srcObject = stream;
				video.play();
			});
		});	
	</script>
</body>
</html>

client2.htmlファイルをリロードし、client1.htmlファイルをリロードするとカメラの許可の確認のメッセージが表示されます。許可をクリックするとclient1ではなくデータを受け取った側のclient2側にWEBカメラの映像が表示されます。

カメラの許可確認
カメラの許可確認

ここまでの動作確認で、PeerJSを利用したブラウザ間でのデータの送受信の確認とWEBカメラの映像の送受信方法を確認することができました。