0
0

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 3 years have passed since last update.

メモ:WebRTCを使ったブラウザ間映像送信

Posted at

映像などをリアルタイムで送受信できる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しか使ってないけど、基本的な部分はなんとなくわかったのでおわり。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?