VOYAGE GROUP VR室ブログ

VOYAGE GROUP VR室のブログです。コンテンツの紹介や制作方法、イベントレポートなどについて書きます。原則毎週水曜日更新。

リアルタイム通信が出来るWebVRアプリにWebRTCを利用した音声通話機能を追加してみる

あけましておめでとうございます。(今更) @daybysay です。

だいぶ間があいちゃいましたが、今回は前回作成したマルバツゲームに、WebRTCを利用して音声通話機能を追加してみようと思います。

WebRTC周りの実装や使い方についてはHTML5 Expert.jpがねこさんの連載が非常にわかりやすいので、そちらをご参照下さい。

目次

マルバツゲームのおさらい

https://media.giphy.com/media/l0MYtJaZ7wJfXGwQE/giphy.gif

こういうゲームでした。

今回はWebRTCを簡単に実現できるサービスであるSkywayを利用して、こちらに音声での会話機能をつけていきます。

Skywayについて

Skywayは、WebRTCを利用する際に必要なシグナリングや TURN/STUN などの仕組みを 無料(!)で 提供しているイケてるサービスです!

素晴らしいサービスを開発されているNTTコミュニケーションズ様に思いを馳せながら、今回の音声通話機能を開発しました。

Skywayの使い方は SkyWay ドキュメント をご参照下さい。

登録とIDの発行

まずは登録フォームから登録を行いましょう。

仮登録をし、メールのリンクからの本登録完了後にこんな感じでドメインを登録できるようになります。

f:id:DayBySay:20170127000146p:plain

今回はlocalhostで動作確認していたのでloaclhostと、実際にマルバツゲームを公開しているherokuのドメインを登録しています。 (どうせ公開されているのでAPI KEYを隠す意味は無いんだけど一応隠している姿の図

API KEYが発行できればOKです。

音声通話の実装

実装がされているリポジトリはこちらです。

github.com

とりあえずゴリッとコードを貼ります。WebRTC周りの処理にはコメントをつけました。

サーバサイドはnodeで、WebSocketを利用した実装になっています。

やっていることはプレイヤーの管理と、それぞれにのプレイヤーにお互いのピアを表すIDを交換させるくらいです。

また、プレイヤーの2人目がログインした時に、1人目にたいしてWebRTCで繋ぎに行く為のイベントを発行しています。

サーバサイド

var http = require('http');
var socketio = require('socket.io');
var fs = require('fs');
var server = http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type' : 'text/html'});
    res.end(fs.readFileSync(__dirname + '/index.html', 'utf-8'));
}).listen(process.env.PORT || 3000);

var io = socketio.listen(server);
var player1, player2;

io.sockets.on('connection', function(socket) {
    socket.on("connected", function (peerid) {
        if (player1 === undefined) {
            player1 = {};
            player1.socketid = socket.id;
            // WebRTCで利用するピアのIDを保存する処理
            player1.peerid = peerid; 
            io.to(socket.id).emit("set_player", "player1");
            return;
        }

        if (player2 === undefined) {
            player2 = {};
            player2.socketid = socket.id;
            // WebRTCで利用するピアのIDを保存する処理
            player2.peerid = peerid; 
            io.to(socket.id).emit("set_player", "player2");
            // プレイヤー1にコールするイベントを発火するための通知処理
            io.to(socket.id).emit("call", player1.peerid);
            return;
        }
    });

    socket.on("disconnect", function() {
        if (socket.id === player1.socketid) {
            player1 = undefined;
            return;
        }

        if (socket.id == player2.socketid) {
            player2 = undefined;
            return;
        }
    });

    socket.on("click-square", function(square_id, player_id) {
        socket.broadcast.emit("click-square",square_id, player_id);
    });
});

クライアントサイドのJSはこちら。

前半はWebRTCの実装で、中盤以降はが前回に引き続きWebSocketとA-Frameの実装ですね。

ここでは、SkywayからのPeerID取得が成功した後にnodeのサーバにIDを送るという実装になっています。

// ベンダプレフィクスを追加。おまじないですね。
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

// 自身の音声用ストリームを保存しておく変数
var localStream;

// 音声用ストリームの取得処理
navigator.getUserMedia({audio: true, video: false}, function(stream){
    localStream = stream;
}, function(error) {
    alert('Error!: ' + error);
});

var peer = new Peer({key: '自身のAPI KEY いれてね!'});

peer.on('open', function(id) {
    login(peer_id);
});

// プレイヤー2からコールが来た時に呼び出されるコールバック処理。自身のストリームをアンサーとして返す
peer.on('call', function(call) {
    call.answer(localStream);

    call.on('stream', function(stream) {
        // 他のピアからきた音声ストリームをHTMLのaudioタグに渡して出力する
        let audio = document.getElementById("audio");
        audio.srcObject = stream;
    });
});

window.onbeforeunload = function(){
    // ブラウザを閉じる時に接続を切る
    peer.disconnect();
}

var socket = io.connect();
var player_id;

socket.on("set_player", function(player) {
    player_id = player;
    var playersCamera = document.querySelector("#" + player_id);
    playersCamera.setAttribute("camera", "active", true);
});

socket.on("click-square", function(square_id, player_id) {
    click_square(square_id, player_id);
});

// プレイヤー2がプレイヤー1に繋ぎに行くときの処理
socket.on("call", function(peer_id) {
    // プレイヤー1のピアIDにたいして、自身の音声ストリームを渡しつつコールする
    let call = window.peer.call(peer_id, localStream);

    // プレイヤー1からアンサーが返ってきたら、受け取ったストリームをaudioタグで流す
    call.on('stream', function(stream){
        let audio = document.getElementById("audio");
        audio.srcObject = stream;
    });
});

function login(peer_id) {
    socket.emit("connected", peer_id);
}

function click_square(square_id, player_id) {
    var image = player_id === "player1" ? "#cross" : "#circle";
    document.querySelector("#" + square_id).setAttribute("src", image);
}

AFRAME.registerComponent('cursor-listener', {
    init: function () {
        this.el.addEventListener("click", function (evt) {
            click_square(this.id, player_id);
            socket.emit("click-square", this.id, player_id);
        });
    }
});

基本的には2プレイヤーで遊ぶことを前提にしているので、3人以上が接続した場合を考慮した実装にはなっていません。

いつか何人でも入れるWeb SocialVRアプリを作るお・・

動作を確認してみる

まずは下記コマンドを実行します。

$ git clone git@github.com:DayBySay/websocket_vr.git
$ cd websocket_vr/
$ npm install
$ make server
$ open http://localhost:3000

これでブラウザを立ち上げると、下記の様な感じになります。

f:id:DayBySay:20170127003943p:plain

マイク使用の許可を求めてくるので、これを許可した上でタブを複製すると、自分の声がPCから聴こえるようになります!(ハウリング注意!

ちなみにAndriod端末だと実行できたりしますが、iPhoneはWebKitがWebRTCに対応していないために全く動作しません・・

WebKit Feature Status | WebKit

を見る限り開発はしているようなので、完成を待ちましょう!

まとめ

WebVRアプリケーション上にWebRTCを使った音声チャット機能を実装した話でした。

  • Skywayすごいゆえに、WebRTCが簡単に使える
  • Safariが対応して無くて辛い。
  • 音声通話楽しい