Phoenix Channelを使ってWebRTCのシグナリングをするメモ #beamlangtokyo

More than 1 year has passed since last update.

Elixir初心者向けハンズオン vol2で学んだことを振り返りつつメモ書いています。

前にWebRTCのシグナリングサーバーにMilkcocoaを使って見るメモ #webrtcjp #mlkccaという記事を書いたのですが、これのPhoenix.channel版をやってみます。

内容的にはWebRTC側の要素が多いかも


作ったもの

ビデオチャット - Phoenix Channelでシグナリングをするようにしています。

Elixirハンズオンでやったこれと

WebRTCハンズオンでやったこれ

二つの組み合わせです。


Phoenix Channel

ハンズオンの資料を参照。


PhoenixはWebSocketをそのまま使うのではなく、 WebSocket上に作られた複数のChannel(PhoenixChannel)を使って クライアントとサーバーの双方向通信を行います。



  • WebSocket上に仮想的なレイヤーを作って通信を行う仕組み

  • Socket.ioのルーム機能みたいなものを実装している

  • JS以外にもSwiftなどのクライアントが存在する


WebRTCのシグナリングの修正点

基本はこちらの記事の手順で進めたあとの話になります。


イベント追加

lib/demo_web/channels/chat_channel.exsignalingイベントを追加


lib/demo_web/channels/chat_channel.ex



(省略)

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でそういう機能ないのかな (ありそう)


assets/js/demo.js


'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()を使って送信するようにします。


assets/js/demo.js



(省略)

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が自分のものだった場合は何もしないようにしています。


assets/js/demo.js



(省略)

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などの既存システムとうまく連携できる仕組みがあると過去のシステムの乗り換えハードルが下がって良さそう。(ブリッジする仕組みありそうな気がする)