JavaScript
Node.js
Socket.io
WebRTC
PeerJS

PeerJS+Socket.ioで即席ボイスチャット

More than 1 year has passed since last update.

PeerJSを使用すると、相手にP2Pキーを伝えることで、音声や映像を共有できます。
ただ、PeerJSCloudを利用する場合、ブラウザに表示されたP2Pキーを手動で入力しなければ、共有することができません。

そこで、公式が提供しているPeerJSServerExpress4上に設置し、ページにアクセスしたユーザーを無差別に共有するチャットルームを作成してみます。

環境

  • 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
package.json
{
  "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

依存モジュールがインストールできたら、次へ進みます。

各サーバーの依存関係

まず、Express4Socket.iopeerServerを下記のように依存させ、指定PORTにサーバーを起動するプログラムを作成します。

server.js
// 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を用意し、必要なライブラリがグローバル変数に定義されているか確認してください。

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

マイクの許可

スクリーンショット_2016-02-24_22_01_13.png

デフォルトでは、ブラウザからマイクへのアクセスが禁止されています。これを許可すると、右ウィンドウのように確認用のAudio要素が表示されます。
この状態でマイクに喋ると、マイクの音声がそのまま再生されるはずです。

マイク接続→Socket.io接続→PeerJS接続

PeerJSにはP2Pキーを管理する機能がありません。代わりにSocket.ioでこれを通知できるように、クライアントの識別idであるsocket.idをP2Pキーとして使用します。

index.htmlを下記のように変更します。

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キーを受け取ること出来ます。

server.js
// 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要素を生成すれば、マイク音声の共有が可能です。

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);
    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は動作しません。下記のような警告文が表示されて失敗します。

http上で開発コンソールを開いて入力する
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上でデプロイ出来る環境が必要になります。Herokuc9など。

おわりに

動作確認用リポジトリ: 59naga/peerjs-voicechat - github

友達が居なくて3機以上の動作確認ができませんでした (◞‸◟)


  1. ExpressPeerServer() < express() のような依存。…接続確立時に.emit('connection')