映像などをリアルタイムで送受信できるWebRTCは触ったことがないのでざっと概要を見てみる。
少し古いがこことか、node-webrtcとかで内容を確認しつつ
これを試す。
OBSの設定
試そうとしている環境は仮想サーバでカメラが付いていないのでOBS+仮想カメラプラグインを利用する。
OBSからダウンロード
使い方はここ
最新バージョンは仮想カメラプラグインが最初から組み込まれているが、インストーラーからインストールしたほうが良い。
Enable CoreAudio AAC encoder (Windows)が出る場合
この手順を試してみる。
iTunesのExeダウンロードして、7Zipで解凍、AppleApplicationSupport.msiとAppleApplicationSupport.msiを実行
※12.10.8がこれが出来るiTunesの最後のバージョンらしい。ここから旧バージョンをDLできる。
node.js
フォルダを作成して'npm install express'
普通の画面を出すまでは単なるExpressの使い方なのでサンプルにあるファイルを作成する。
ejsは触った事がないが、今覚えるつもりもないので'npm install ejs'、ソースはコピーして対応。
見た感じJSPのjavascript版といった感じ。
<script>
const ROOM_ID = "<%= roomId %>";
</script>
ここでroomIdが未定義とのエラーが出たのでとりあえず固定値で対応。
実際には後からルームIDを一意に取得する処理を追加する。
cssとjsはページだけでは中身が分からないのでサンプル画面から引っこ抜くか、
gitから取得する。
ここまでで画面は綺麗に表示はされる。
続いてUUIDを用いたルームID。
'npm install uuid'して.jsを変更。.ejsのroomIdも元に戻す。
const { v4: uuidv4 } = require('uuid');
app.get('/', (req, res) => {
res.redirect(`/${uuidv4()}`);
});
app.get('/:room', (req, res) => {
res.render('room', { roomId: req.param.room });
});
http://localhost:3000/a06b02fb-4780-403f-963e-a05f559decb6
のようなURLにリダイレクトされるようになった。
ブラウザでのカメラ取得
OBSの仮想カメラをブラウザから取得する方法。
今回は仮想カメラのみで音声はないのでaudioはfalseにした。
流れとしては
navigator.mediaDevicesのオブジェクトでgetUserMediaで取得する。
audio,videoの設定が可能で、{ audio: true, video: { width: 1280, height: 720 } }
のようにして解像度も設定出来る。
video: { width: {min: 640, ideal: 1280}, height: {min: 480,ideal: 720} }
で可能なら高画質でといった指定も可能。
facingModeのようにフロントカメラを使って欲しいという指示も出せる。
getUserMediaはpromiseを返すのでthen,catchでコントロールできる。
取得したstreamをvideo要素のsrcObjectに放り込めばOK
autoplayを設定していなければvideo.play()で明示的な再生をする。
明示的な再生の方が良いが、こことかを見るとplay出来ない場合もあるのでlaysinline muted autoplay
を設定してもいいのかも。
streamは.getVideoTracks()[0].enabled、getAudioTracks()[0].enabledで有効無効を切り替え可能。
という感じだが、ここでは以下のコードになっていた。(catchはしていない)
socket(WebSocket)とpeer(WebRTC)については次の節。
const myVideo = document.createElement("video");
let myVideoStream;
navigator.mediaDevices
.getUserMedia({
audio: false,
video: true,
})
.then((stream) => {
myVideoStream = stream;
addVideoStream(myVideo, stream);
peer.on("call", (call) => {
略;
});
socket.on("user-connected", (userId) => {
略;
});
});
const addVideoStream = (video, stream) => {
video.srcObject = stream;
video.addEventListener("loadedmetadata", async () => {
await video.play();
videoGrid.append(video);
});
};
あまり関係はないがscript.js上でbackBtnとshowChatは存在しておらずエラーが出たのでコメントアウト。
WebRTC
既に上のコードでWebSocketsとWebRTCが出ている。これはnode.js側と連携して使う。
サーバ側WebSocketはsoket.ioを使うが、RTCはpeerjs。
こことかここで解説している。
WebRTCはブラウザ間で通信を行うが、その際のIPアドレスなどを伝える手段としてサーバが必要となる。
これはシグナリングサーバと呼ばれている。
そのシグナリングを行うのにWebSocketを使う、というのが大まかな流れ。
クライアント側から見る。
本体の流れはこんな感じのようだが、peerjsを使っているので簡略化されている、はず。解説もある。
クライアント側ではpeerjs.min.jsを読み込んだ上でPeerで接続情報を作成する。
var peer = new Peer(undefined, {
path: "/peerjs",
host: "/",
port: "3000",
});
最初の引数を空にしておくことで、Peerが自動的にIDを割り振ってくれる。
指定する事も出来る。
接続が正常に行われた場合の処理はpeer.on("open")で設定。
このスクリプトではWebSocketでルームにJOINして、参加している人(自分以外)にPeerのIDを送信する処理がサーバで行われる。
peer.on("open", (id) => {
socket.emit("join-room", ROOM_ID, id, user);
});
bradcast等の話はここ参考
socket.to(roomId).broadcast.emit
はエラーになったので
socket.broadcast.to(roomId).emit
に変更。
io.on('connection', (socket) => {
socket.on('join-room', (roomId, userId) => {
socket.join(roomId);
socket.broadcast.to(roomId).emit('user-connected', userId);
});
});
ここで配信されたソケットはsocket.onで受け取られて、接続要求が投げられる。
socket.on("user-connected", (userId) => {
connectToNewUser(userId, stream); //このstreamはmediaDevicesのもの
});
const connectToNewUser = (userId, stream) => {
const call = peer.call(userId, stream);
const video = document.createElement("video");
call.on("stream", (userVideoStream) => {
addVideoStream(video, userVideoStream);
});
};
で処理される。
peer.callで新規接続ユーザに対して映像送信を設定。
call.on("stream"
でcallするが、応答があった場合にcallbackでストリーム受信時の処理を書く。
ここでは受信したストリームをvideo要素にセット。
なお、自分自身のカメラ取得が成功した直後の処理で
peer.on("call")
で誰かからcallがあった場合の処理を記述している。
ここでは自分のstream=カメラ映像を返す。
同時に相手にもCallして、相手の映像を受け取っている。
peer.on("call", (call) => {
call.answer(stream);
const video = document.createElement("video");
call.on("stream", (userVideoStream) => {
addVideoStream(video, userVideoStream);
});
});
微妙にうまくいかない場合もあるが、一応2つのクライアント間で映像のやりとりは出来た。
今回は動画なのでCallを利用しているが、connectionで普通にテキストメッセージもP2Pで送信する事が可能。らしい。
peer.on('connection', conn =>{
conn.on('data', data => {
console.log(`別ユーザからのメッセージ:${data}`);
conn.send('メッセージ返送');
});
});
その他
デバイスを選択したい場合はここにあるが、
navigator.mediaDevices.enumerateDevices().then(function(devices) {...}
でデバイス一覧を取得出来る。
deviceId,kind,labelの情報を持っている。
IDを指定して選択する場合は以下のように指定すれば良さそう。
var constraints = {
audio: { deviceId: audioId },
video: { deviceId: videoId }
};
navigator.mediaDevices.getUserMedia(
constraints
).then(function(stream) {
}
また、切断処理についてはここが生のRTCの場合は参考になる。
peerjsの場合はここを見るに
一般的には他人が抜けた場合にはpeerLeaveで捉える模様。
自分の場合はcall.on('close')で切断を検知する。call.close()時に発火?
call.on('peerLeave', function(peerId){
removeVideo(peerId);
});
window.closeでwebSocketで飛ばすとかしてleaveを通知した方が確実なのかはまでは調べ切れていない。
(あと無応答になってからのタイムアウト時の挙動とか)
peerjsしか使ってないけど、基本的な部分はなんとなくわかったのでおわり。