[悪用禁止] ブラウザだけでカメラ、キャプチャ、VideoやCanvasを録画してダウンロード

# TL;DR
## MediaStreamの取得
```js
// mediaStreamの取得(video/canvas)
stream = document.querySelector('canvas').captureStream()

// mediaStreamの取得(画面キャプチャー)
stream = await navigator.mediaDevices.getDisplayMedia()

// mediaStreamの取得(Webカメラ)
stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
```

## MediaRecorderの作成
```js
function processMediaWhenStop(stream, onStop) {
  const mime = MediaRecorder.isTypeSupported("video/webm; codecs=vp9") ? "video/webm; codecs=vp9" : "video/webm";
  const mediaRecorder = new MediaRecorder(stream, { mimeType: mime })
  // start record
  const chunks = []
  mediaRecorder.addEventListener('dataavailable', e => chunks.push(e.data))

  // call on stop callback
  mediaRecorder.addEventListener('stop', async () => {
    const blob = new Blob(chunks, { type: chunks[0].type })
    await onStop(blob)
  })

  return mediaRecorder
}
```

## onStop Callbackの一例(ファイルとしてダウンロード)
```js
function downloadWebm(blob) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'video.webm';
  a.click();
}
```

## おまけ: FFmpegで`.mp4`に変換
```sh
$ ffmpeg -i video.webm -c:v copy -c:a copy video.mp4
```

# はじめに
昔[JS直書きでGoogle DoodleのCanvasを録画してGifに](https://qiita.com/jerrywdlee/items/f5fdb6587dde184236ef)という記事を書きましたが、最近はより汎用性の高い方法を見つかりました。

# 操作Demo
[Three.jsのサンプル](https://threejs.org/examples/#webgl_animation_skinning_blending)を拝借いたします。

## 関数投入
`processMediaWhenStop()`と`downloadWebm()`を先に投入します。

![Cursor_と_three_js_examples_と_app_js_—_ES6_demos.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/602bed30-e156-ffc1-4a2c-9e65cea36786.png)

## 録画開始
該当するcanvasまたはvideoを特定出来たら、下記のコードを一気にコンソールに投入します。

```js
stream = document.querySelector('canvas').captureStream()
recorder = processMediaWhenStop(stream, downloadWebm)
recorder.start() // 録画開始
```

![Cursor_と_three_js_examples.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/3362ec78-4458-fb78-abb6-cdd79f98a8a9.png)

## 録画終了&ファイルDL
手動で`recorder.stop()`をCallすれば、勝手に録画が停止し、`video.webm`というファイルがダウンロードされます。

![Cursor_と_three_js_examples.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/252adfef-9a90-aa01-e481-17c087b7cb44.png)

![Cursor_と_ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/b01cc330-ac86-4c1b-a980-cdd7874e85da.png)

こんなビデオファイルがDLされます。
![Cursor_と_video__1__webm_と_three_js_examples.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/b80a718a-3258-bcc9-faf9-2042ad2ee27b.png)

## 必要であれば、MP4に変換
```sh
$ ffmpeg -i video.webm -c:v copy -c:a copy video.mp4
```
上記コマンドで一瞬で出来ます。

```
ffmpeg version 6.0 Copyright (c) 2000-2023 the FFmpeg developers
  built with Apple clang version 13.0.0 (clang-1300.0.29.30)
  configuration: --prefix=/usr/local/Cellar/ffmpeg/6.0 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libaribb24 --enable-libbluray --enable-libdav1d --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libsvtav1 --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox --enable-audiotoolbox
  libavutil      58.  2.100 / 58.  2.100
  libavcodec     60.  3.100 / 60.  3.100
  libavformat    60.  3.100 / 60.  3.100
  libavdevice    60.  1.100 / 60.  1.100
  libavfilter     9.  3.100 /  9.  3.100
  libswscale      7.  1.100 /  7.  1.100
  libswresample   4. 10.100 /  4. 10.100
  libpostproc    57.  1.100 / 57.  1.100
Input #0, matroska,webm, from 'video.webm':
  Metadata:
    encoder         : Chrome
  Duration: N/A, start: 0.000000, bitrate: N/A
  Stream #0:0(eng): Video: vp9 (Profile 0), yuv420p(tv), 1039x860, SAR 1:1 DAR 1039:860, 1k tbr, 1k tbn (default)
    Metadata:
      alpha_mode      : 1
Output #0, mp4, to 'video.mp4':
  Metadata:
    encoder         : Lavf60.3.100
  Stream #0:0(eng): Video: vp9 (Profile 0) (vp09 / 0x39307076), yuv420p(tv), 1039x860 [SAR 1:1 DAR 1039:860], q=2-31, 1k tbr, 16k tbn (default)
    Metadata:
      alpha_mode      : 1
Stream mapping:
  Stream #0:0 -> #0:0 (copy)
Press [q] to stop, [?] for help
frame=  580 fps=0.0 q=-1.0 Lsize=    1833kB time=00:00:21.70 bitrate= 691.7kbits/s speed=2.2e+03x
video:1825kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.397173%
```

![Cursor_と_ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/e82bb8fa-7142-cb1d-66fd-b778c549b464.png)

## 余談1: FFmpeg.wasmについて
理論上[FFmpeg.wasm](https://github.com/ffmpegwasm/ffmpeg.wasm)を活用すれば、ブラウザのみでMP4ファイルの出力が可能ですが、実際やってみたら以下の問題が発覚しました。

１、 FFmpeg.wasmを利用するには、該当ページのHEADに下記の記述がないと動きません。
```
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
```
なので、そこら中のサイトですぐでも録画したいことは出来ないです。

２、FFmpeg.wasmの変換速度が恐ろしく遅いです。
先ほどの`video.webm`をコンソール版のFFmpegで変換する際の速度は、2200倍速でした。実際の所要時間も一瞬でした。
しかしFFmpeg.wasmだと、約0.02倍速でした(つまり1分間の動画を変換するには、50倍の50分間が必要です)。

![Cursor_と_FFMPEG_WASM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/a8a44233-c5e6-35d4-04e0-28675cda9121.png)


## 余談2: GIFを出力
FFmpegはGIF画像としての出力も可能ですが、`webm`ファイルから直接変換はできないみたいです。
一回`mp4`ファイルに変換してから、下記のコマンドで変換すればできます。

```sh
ffmpeg -i video.mp4 -r 5 -vf scale=640:-1 video.gif
```

![output3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/0c936501-498b-3741-2ed9-e9240da8a713.gif)


詳しい設定はこちらの記事を読んでください: [FFmpegで動画をGIFに変換](https://qiita.com/wMETAw/items/fdb754022aec1da88e6e)


# 参考
- [画面共有と同時にPC内の音声も共有したい](https://support.skyway.io/hc/ja/community/posts/360028941674-%E7%94%BB%E9%9D%A2%E5%85%B1%E6%9C%89%E3%81%A8%E5%90%8C%E6%99%82%E3%81%ABPC%E5%86%85%E3%81%AE%E9%9F%B3%E5%A3%B0%E3%82%82%E5%85%B1%E6%9C%89%E3%81%97%E3%81%9F%E3%81%84)
- [【FFmpeg】webM形式の動画をmp4に無劣化で変換する](https://note.com/naosim/n/n955339575399)
- [ReferenceError: SharedArrayBuffer is not defined #263](https://github.com/ffmpegwasm/ffmpeg.wasm/issues/263)
- [getUserMedia()以外でMediaStreamを用意するには](https://lealog.hateblo.jp/entry/2018/02/19/160808)