Webブラウザだけでビデオ会議的なことを考える

#はじめに
最近のWebブラウザはカメラ画像とか簡単に取得できるらしいのでやってみた。
JavaScriptで、navigator.mediaDevices を使えばいいらしい。
あくまでも「考える」ということで、こうやればできそうという話だけ。
ついでにまずはカメラ画像だけ。

#カメラ画像の取得
navigator.mediaDevices.getUserMedia() でカメラのストリームが取得できて、それを `<video>` に流し込むと表示できる。

具体的なコードは以下の通り。ただ、今のブラウザだと https のページでないと使えない模様。
（httpだと getUserMedia が失敗する）

ちなみにvideo_constraints の中身は video:true だけが最低限の指定。

```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>camera test</title>
</head>
<body onload="startCamera();">

<video id="camera"></video> <!--表示領域-->

<script type="text/JavaScript">
async function startCamera() {
    const video = document.getElementById('camera');
    const video_constraints = {
        audio: false, // とりあえず今はビデオのみ
        video: {
            width: 640, // カメラから取得する画像サイズの指定、このサイズに近いもので、カメラが使えるものが自動選択される
            height: 480,
            facingMode: "user" // スマホとかの場合は自分に向いているカメラを選択
        }
    };
    var stream = null;
    try {
        stream = await navigator.mediaDevices.getUserMedia(video_constraints);
        video.srcObject = stream;
        video.play();
    }
    catch (err) {
        alert(err.message);
    }
}
</script>

</body>
</html>

```

とりあえずこれだけで表示はされるはず。


#データの取り出し
これだと自分のブラウザで表示されるだけで、相手に送ることができない。
MediaRecorderを使えば取り出せるらしい、というわけで、以下のコードで実験。（上のソースからの修正点のみ）
media_rec.start は、引数に時間を指定しないと停止するまでondataavailableが来ない。
時間を指定すると、この時間が経過するたびに ondataavailable が呼び出される。

```
var stream = null;
try {
    stream = await navigator.mediaDevices.getUserMedia(video_constraints);
    var media_rec = new MediaRecorder(stream);
    media_rec.ondataavailable = function(e) {
        console.log(e.data);
    }
    media_rec.start(100); // 100ms ごとにデータを出力
}
catch (err) {
    alert(err.message);
}
```

動かしてみると、コンソールにどんどんそれっぽいデータが出てくる。
ただ、これでできたと思って色々調べたが、どうにもこのデータからカメラ画像を再生することができないっぽい。

で、結局canvas経由で取り出すハメになった。本当は ondataavailable の e.data から作れそうなんだが。

:::note info
その後の調べで MediaRecorder ではなく WebCodecs で近いことができることがわかった。ただ、今のところ標準設定ではChromeでしか使えないっぽい。
:::

手順としては一定時間ごとにcanvasにvideoの画像を書き込み、canvasのtoDataURLで画像データを取り出す。静止画として取り出しているので、他に送って表示するのも簡単。

実験中のコードは以下の通り。

```
<video id="camera"></video>
<canvas id="videoview" width="320" height="240"></canvas> <!-- コンソール出力のデータがでかいので、小さくした -->

    ・
    ・
    ・

async function startCamera() {
    const video = document.getElementById('camera');
    const view_cvs = document.getElementById('videoview');
    const video_constraints = {
        audio: false, // とりあえず今はビデオのみ
        video: {
            width: 320, // カメラから取得する画像サイズの指定、このサイズに近いもので、カメラが使えるものが自動選択される
            height: 240,
            facingMode: "user" // スマホとかの場合は自分に向いているカメラを選択
        }
    };
    var stream = null;
    try {
        stream = await navigator.mediaDevices.getUserMedia(video_constraints);
        video.srcObject = stream;
        media_rec = new MediaRecorder(stream);
        media_rec.ondataavailable = function(e) {
            view_cvs.getContext('2d').drawImage(video, 0, 0);
            var img_data = view_cvs.toDataURL('image/webp');
            console.log(img_data);
        }
        media_rec.start(100);
        video.play();
    }
    catch (err) {
        alert(err.message);
    }
}
```

MediaRecorderが定期的にcanvasのdrawImageを呼び出すことにしかなっていない。setIntervalでいいんじゃないかという気がする。
まあ、ここに来たら video に確実にデータがあるということは利点かもしれないが。

これでコンソールにはゾロゾロとbase64エンコードされた画像データが出力されている。
この画像データの表示は、img.src に突っ込めば良い。具体的にはこんな感じ。

```
<video id="camera"></video>
<canvas id="videoview" width="320" height="240"></canvas>
<img id="testview" width="320" height="240">

    ・
    ・
    ・

async function startCamera() {
    const video = document.getElementById('camera');
    const view_cvs = document.getElementById('videoview');
    const view_img = document.getElementById('testview');
    const video_constraints = {
        audio: false, // とりあえず今はビデオのみ
        video: {
            width: 320,
            height: 240,
            facingMode: "user"
        }
    };
    var stream = null;
    try {
        stream = await navigator.mediaDevices.getUserMedia(video_constraints);
        video.srcObject = stream;
        media_rec = new MediaRecorder(stream);
        media_rec.ondataavailable = function(e) {
            view_cvs.getContext('2d').drawImage(video, 0, 0);
            var img_data = view_cvs.toDataURL('image/webp');
            view_img.src = img_data; // ここで取得したデータを画像として表示
            // console.log(img_data);
        }
        media_rec.start(100);
        video.play();
    }
    catch (err) {
        alert(err.message);
    }
}
```

img_data をWebSocketとかで送ればビデオ会議的なことができるはず。