37
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-02-24

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')

37
34
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?