PeerJSを使用すると、相手にP2Pキーを伝えることで、音声や映像を共有できます。
ただ、PeerJSCloudを利用する場合、ブラウザに表示されたP2Pキーを手動で入力しなければ、共有することができません。
そこで、公式が提供しているPeerJSServerをExpress4上に設置し、ページにアクセスしたユーザーを無差別に共有するチャットルームを作成してみます。
環境
- NodeJS v5.7.0
- Npm v3.6.0
- GoogleChrome 48.0.2564.116 (OSX 10.11.3 64-bit)
下記package.json
を使用します。
mkdir voicechat
cd voicechat
touch package.json #下記を copy&paste
{
"main": "server.js",
"private": true,
"dependencies": {
"express": "^4.13.4",
"peer": "^0.2.8",
"socket.io": "^1.4.5"
},
"scripts": {
"server": "node server.js"
}
}
npm install
依存モジュールがインストールできたら、次へ進みます。
各サーバーの依存関係
まず、Express4とSocket.io、peerServerを下記のように依存させ、指定PORT
にサーバーを起動するプログラムを作成します。
// Dependencies
const express = require('express');
const createHttpServer = require('http').createServer;
const createIo = require('socket.io');
const createPeerServer = require('peer').ExpressPeerServer;
// Environment
const port = process.env.PORT || 59798;
// Routes
const app = express();
const httpServer = createHttpServer(app);
const io = createIo(httpServer);
const peerServer = createPeerServer(httpServer);
app.use('/api', peerServer);
app.use(express.static(__dirname));
// Boot
httpServer.listen(port, () => {
console.log(`Boot on http://localhost:${port}`);
});
同時に、以下のindex.html
を用意し、必要なライブラリがグローバル変数に定義されているか確認してください。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Voicechat</title>
<script src="/socket.io/socket.io.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/peerjs/0.3.14/peer.js"></script>
<script>
navigator.webkitGetUserMedia({audio:true}, function(stream) {
var audio = new Audio;
audio.src = URL.createObjectURL(stream);
audio.controls = true;
audio.play();
document.body.appendChild(audio);
}, function(error) {
alert('Failed to get local stream' ,error);
});
</script>
</head>
<body>
</body>
</html>
package.json
からサーバーを起動します。
npm start
# Boot on http://localhost:59798
マイクの許可
デフォルトでは、ブラウザからマイクへのアクセスが禁止されています。これを許可すると、右ウィンドウのように確認用のAudio要素が表示されます。
この状態でマイクに喋ると、マイクの音声がそのまま再生されるはずです。
マイク接続→Socket.io接続→PeerJS接続
PeerJSにはP2Pキーを管理する機能がありません。代わりにSocket.ioでこれを通知できるように、クライアントの識別idであるsocket.id
をP2Pキーとして使用します。
index.html
を下記のように変更します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Voicechat</title>
<script src="/socket.io/socket.io.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/peerjs/0.3.14/peer.js"></script>
<script>
navigator.webkitGetUserMedia({ audio: true }, function (stream) {
var peer;
var socket = io();
socket.on('connect', function () {
var options = {
host: location.hostname,
port: location.port,
path: '/api', // app.use('/api', peerServer);と同じ位置になるように
};
peer = new Peer(socket.id, options);
});
}, function(error) {
alert('Failed to get local stream', error);
});
</script>
</head>
<body>
</body>
</html>
クライアントがnew Peer
を実行すると、サーバーはpeerServer.on('connection')
1からP2Pキーを受け取ること出来ます。
// Dependencies
const express = require('express');
const createHttpServer = require('http').createServer;
const createIo = require('socket.io');
const createPeerServer = require('peer').ExpressPeerServer;
// Environment
const port = process.env.PORT || 59798;
// Routes
const app = express();
const httpServer = createHttpServer(app);
const io = createIo(httpServer);
const peerServer = createPeerServer(httpServer);
app.use('/api', peerServer);
app.use(express.static(__dirname));
// Boot
httpServer.listen(port, () => {
console.log(`Boot on http://localhost:${port}`);
});
// Manage p2p keys
const keys = [];
peerServer.on('connection', (key) => {
keys.push(key);
console.log('connected', keys);
io.emit('keys', keys);// 接続中の全てのクライアントへ`keys`イベントを発行
});
peerServer.on('disconnect', (key) => {
const index = keys.indexOf(key);
if (index > -1) {
keys.splice(index, 1);
}
console.log('disconnect', keys);
io.emit('keys', keys);// 同上
});
npm start
# Boot on http://localhost:59798
# connected [ 'ZJ4vZ-bmXXE405AyAAAA' ]
→待機→共有
前述のserver.js
により、接続が更新される度にkeys
イベントを受信しますので、受信したP2Pを元にAudio要素を生成すれば、マイク音声の共有が可能です。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Voicechat</title>
<script src="/socket.io/socket.io.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/peerjs/0.3.14/peer.js"></script>
<script>
navigator.webkitGetUserMedia({ audio: true }, function (stream) {
var peer;
var socket = io();
socket.on('connect', function () {
var options = {
host: location.hostname,
port: location.port,
path: '/api', // app.use('/api', peerServer);と同じ位置になるように
};
peer = new Peer(socket.id, options);
peer.on('call', function (call) {
console.log('%sにcallされました', call.peer);
call.answer(stream);
});
});
// 受信した全てのP2Pkeyをaudio要素に変換する
socket.on('keys', function (keys) {
var audios = document.querySelector('#audios');
audios.innerHTML = '';
var index = keys.indexOf(socket.id);
if (index > -1) {
keys.splice(index, 1);// 自分自身を無視
}
keys.forEach(function (key) {
var call = peer.call(key, stream);
if (call === undefined) {
console.log(key + 'なんて居ません');
return;
}
call.on('stream', function (remoteStream) {
var audio = new Audio;
audio.src = URL.createObjectURL(remoteStream);
audio.controls = true;
audio.play();
audios.appendChild(audio);
});
});
});
}, function (error) {
alert('Failed to get local stream', error);
});
</script>
</head>
<body>
<section id="audios">
<p>他のユーザーを待っています…</p>
</section>
</body>
</html>
その他注意事項
https://
かlocalhost
上でなければ、getUserMediaは動作しません。下記のような警告文が表示されて失敗します。
navigator.webkitGetUserMedia({audio:true},x => null,x => null)
// getUserMedia() no longer works on insecure origins. To use this feature, you should consider switching your application to a secure origin, such as HTTPS. See https://goo.gl/rStTGz for more details.
そのため、https上でデプロイ出来る環境が必要になります。Herokuやc9など。
おわりに
動作確認用リポジトリ: 59naga/peerjs-voicechat - github
友達が居なくて3機以上の動作確認ができませんでした (◞‸◟)
-
ExpressPeerServer() < express() のような依存。…接続確立時に.emit('connection') ↩