Elixir初心者向けハンズオン vol2で学んだことを振り返りつつメモ書いています。
前にWebRTCのシグナリングサーバーにMilkcocoaを使って見るメモ #webrtcjp #mlkccaという記事を書いたのですが、これのPhoenix.channel版をやってみます。
内容的にはWebRTC側の要素が多いかも
作ったもの
ビデオチャット - Phoenix Channelでシグナリングをするようにしています。
WebRTCハンズオンでやったこれ
二つの組み合わせです。
Phoenix Channel
[ハンズオンの資料](PhoenixはWebSocketをそのまま使うのではなく、 WebSocket上に作られた複数のChannel(PhoenixChannel)を使って クライアントとサーバーの双方向通信を行います。)を参照。
PhoenixはWebSocketをそのまま使うのではなく、 WebSocket上に作られた複数のChannel(PhoenixChannel)を使って クライアントとサーバーの双方向通信を行います。
- WebSocket上に仮想的なレイヤーを作って通信を行う仕組み
- Socket.ioのルーム機能みたいなものを実装している
- JS以外にもSwiftなどのクライアントが存在する
WebRTCのシグナリングの修正点
基本はこちらの記事の手順で進めたあとの話になります。
イベント追加
lib/demo_web/channels/chat_channel.ex
にsignaling
イベントを追加
・
・(省略)
・
def handle_in("signaling", payload, socket) do
broadcast! socket, "signaling", payload
{:reply, {:ok, payload}, socket}
end
・
・(省略)
・
サーバーサイドはこれだけ。
UUIDの生成
今回はdemo
という名前のPhoenixアプリケーションを作成し、assets/js/demo.js
に追記していきました。
Milkcocoa版の時と同様ですが、SDPを送る際に、自分自身に情報が送られるとFailed to set remote answer sdp: Called in wrong state: STATE_INPROGRESS
などのエラーがでるため自分自身には情報が送られてこないようにする必要があります。
アクセスごとにUUIDを付与して自分自信への情報は省くようにします。
Phoenix Channelでそういう機能ないのかな (ありそう)
'use strict';
import {Socket} from "phoenix"
const UUID = uuid();
function uuid() {
let uuid = "", i, random;
for (i = 0; i < 32; i++) {
random = Math.random() * 16 | 0;
if (i == 8 || i == 12 || i == 16 || i == 20) {
uuid += "-"
}
uuid += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16);
}
return uuid;
}
・
・(省略)
・
sdpとIceCandidateの送信
どちらもchan.push()
を使って送信するようにします。
・
・(省略)
・
function sendSdp(sessionDescription) {
console.log('---sending sdp ---');
$textForSendSdp.value = sessionDescription.sdp;
const message = JSON.stringify(sessionDescription);
console.log('sending SDP=' + message);
//ws.send(message);
chan.push('signaling', {body: message, uuid:UUID})
}
・
・(省略)
・
function sendIceCandidate(candidate) {
console.log('---sending ICE candidate ---');
const message = JSON.stringify({ type: 'candidate', ice: candidate });
console.log('sending candidate=' + message);
// ws.send(message);
chan.push('signaling', {body: message, uuid:UUID})
}
・
・(省略)
・
signalingイベントを受け取る処理を追加
SDP情報が送られてきたときの処理です。
if(msg.uuid === UUID) return;
では先ほどのUUID話で送られてきたUUIDが自分のものだった場合は何もしないようにしています。
・
・(省略)
・
chan.on("signaling", msg => {
const message = JSON.parse(msg.body);
if(msg.uuid === UUID) return; //送られてきたUUIDが自分のものだった場合は何もしない
if (message.type === 'offer') {
// offer 受信時
console.log('Received offer ...');
$textToReceiveSdp.value = message.sdp;
const offer = new RTCSessionDescription(message);
setOffer(offer);
}
else if (message.type === 'answer') {
// answer 受信時
console.log('Received answer ...');
$textToReceiveSdp.value = message.sdp;
const answer = new RTCSessionDescription(message);
setAnswer(answer);
}
else if (message.type === 'candidate') {
// ICE candidate 受信時
console.log('Received ICE candidate ...');
const candidate = new RTCIceCandidate(message.ice);
console.log(candidate);
addIceCandidate(candidate);
}
})
・
・(省略)
・
これでPhoenix Channel経由でシグナリングができるようになりました。
所感
Phoenix自体今日初めて触りましたが使い勝手よくErlang VMを利用できる仕組みはすごいですね。(小並感
Socket.ioなどの既存システムとうまく連携できる仕組みがあると過去のシステムの乗り換えハードルが下がって良さそう。(ブリッジする仕組みありそうな気がする)