WEBページ(ブラウザ)の録音・録画機能(MediaRecorder)を使用する際に考えないといけないフォーマットのこと

2024/12/04に公開

趣旨

いろんなブラウザで「MediaRecorder.isTypeSupported」を試してみましたー

経緯

最近はブラウザで録音、録画する要件が増えている。便利なことにそういった機能のサポートは進んでいる。
とはいえ、OSやブラウザ毎にその仕様は、特にその細かい箇所が全然違っていて、厳密に扱うのはあまりにも大変である。

取り合えずの結果

ブラウザでナウい録音・録画をする場合「MediaRecorder」を使用する。結構便利ではある。

前提知識

新画像ファイルフォーマット戦争を勝ち抜いたGoogleのWebPであるが、同様の動画ファイルフォーマット戦争において、やはりGoogleのWebMという形式が活躍し始めている。WebMはビデオタグで使用するだけでストリーミング再生してくれる非常にありがたい存在ではある。
Can I Use?」を見ても、最近ようやくiOSもWebMに対応し、これにて一件落着,,,とはいかないのがこの界隈である。

そもそも、録音するならまずmp3、録画するならまずmp4に対応したうえで、+αに対応してくれたらよいのだが、ことはそんなに単純ではない。そもそもMediaRecorderでの録音は映像をオフにした動画であり、音声ファイルにはできない。また例えば、Google Chromeが対応しているのは主にWebMで、mp4に対応したのは結構最近だ。

結果

現在使用している環境で指定したフォーマットが使えるかどうかは「MediaRecorder.isTypeSupported」で調べることができる。この時、例えば「video/webm」で調べると「Windows:Mozilla Firefoix」ではサポートしていると言われる。が、ご存じの通り、動画フォーマットは外見が同じでも中身のコーデックが違ったりする。サポートしているといっても全てのコーデックをサポートしているわけではない。ある程度傾向の定まったmp4は兎も角、走りのWebMについてはコーデックまで注意しないとうまくいかない。
なお、音声のWebMはWebAと呼んで区別したりするが、今回は同じくWebMとしている。

以下はそれぞれの環境において各フォーマットについて「MediaRecorder.isTypeSupported」を実行した結果である。それぞれのブラウザやOSは実行時の最新を使用している。

● Windows、Android:Google Chrome
video/webm:〇(動画)
audio/webm:〇(動画)
video/webm;codecs=vp8:〇(映像)
video/webm;codecs=vp9:〇(映像)
video/webm;codecs=daala:×(動画)
video/webm;codecs=h264:〇(映像)
audio/webm;codecs=opus:〇(音声)
video/mpeg:×(動画)
video/mp4:〇(動画、対応したのはわりと最近)

● Windows:Mozilla Firefoix
video/webm:〇
audio/webm:〇
video/webm;codecs=vp8:〇
video/webm;codecs=vp9:×
video/webm;codecs=daala:×
video/webm;codecs=h264:×
audio/webm;codecs=opus:〇
video/mpeg:×
video/mp4:×

● iPhone、iPad:Safari
video/webm:×
audio/webm:×
video/webm;codecs=vp8:×
video/webm;codecs=daala:×
video/webm;codecs=h264:×
audio/webm;codecs=opus:×
video/mpeg:×
video/mp4:〇

結果を受けて

まずもっていえることは、全体で共通して使えるフォーマットが無い、ということ。Google Chromeは一番色々な形式に対応しているが、iOSではWebMが使用できず、Firefoxではmp4が使用できない。
なお、「Can I Use?」で対応されていると表示されるのは再生についてであり、上記の対応は録画機能についてである。

iOSではmp4、それ以外ではWebM使えばよいですか? ×

答えはNoである、というか、そうしてもよいが、それは意外と大変である。
どういうことかというと、WindowsなどのGoogle Chromeでコーデックを指定せずにWebMで録画すると、なんと「vp9」形式になる。これはWebMの皮を被ってはいるが、FirefoxでもiOSでも再生できない。FirefoxやiOSが対応しているのは恐らく「vp8」と「h264」辺りであろう。

と、いうわけで、私がお勧めの方法は、まずmp4の対応を調べてmp4での録画に対応していればmp4で録画する。mp4録画に対応していない場合はWebMで録画する。mp4に対応していないのは基本的にWindows等のFirefoxになる。FirefoxはWebMに対応しており、初期状態では「vp8」で録画する。これは一般的な全ての最新環境で再生可能である。
というながれが良いのではないだろうか。

音声ファイルについて

さて、これまでは基本的に動画ファイルを扱ってきた、が、最新フォーマットとはいえ、動画ファイルは動画ファイルである。ストレージや通信量を節約する場合はmp3などに変換するのが良い。
が、前の記事に書いたが、例えばFirebase Functionsで変換するとして、場合によっては1,2分の変換時間が必要になる。

それを解決する方法として、変換が終わるまでは動画ファイルを再生する、という方法をとることができるだろう。幸いAudioタグは動画ファイルを設定すれば動画の音声のみを再生できる。
というわけで、変換できたかを自動で判定して切り替える実装をしたい。

一番最初に思いつくシンプルな方法は次のような形式だろう。

<audio controls>
  <source src="audio.mp3" type="audio/mpeg" />
  <source src="audio.mp4" type="audio/mp4" />
  <source src="audio.webm" type="audio/webm" />
  <p>
    再生できません(又は変換中です)
  </p>
</audio>

この方法はWindows等のGoogle Chromeでは正しく動作する。上から順に検証して再生できない場合は次の方法を試してくれる。
が、iOSではうまくいかない。恐らくiOSの場合はファイルのフォーマットだけを検証し、ファイルが存在しない場合には対応してくれない。

では、JSで「new Audio()」を実際に呼び出して再生できるか検証してみれば、、、
これもWindows等のGoogle Chromeではうまくいくが、iOSの場合は上手くいかない。恐らく、ユーザの操作による音声再生以外を却下する為であろう。

本当にiOSのブラウザはひどい。iOSが滅びればWEB業界はもっともっと発展するだろうに,,,

最終的に私が辿り着いたのは次の方法である。

const exts: string[] = ['mp3', 'mp4', 'webm'];
const checkFileExistence = async (src) => {
	let s = src.split('.');
	s.pop()
	let src = s.join('.');
	try {
		for (let ext of exts) {
			const response = await fetch(src + '.' + ext);
			if (response.status === 200) {
				return src + '.' + ext;
				break;
			} else {
				//...
			}
		}
	} catch (error) {
		//...
	}
}

つまり、一つずつfetchしてみてファイルの存在をチェックする方法である。
めんどくさい,,,
なお、fetchするためにCORSの設定も必要なので気を付けてくださいまし。
一部の人にはiOSは堅牢で安全、みたいな捉え方をしている人がいるが、こういった不親切なシステムに対応するために寧ろセキュリティ上のリスクを強要される場合もあり、全くiOSというのはメリットがない。

iOSのメリットって充電が長持ちすることと、アプリが開発しやすいこと以外に何かあるの?
仕事上両方使っているが、操作性も悪いし日本人がなぜiPhoneが好きなのか全くわからん。

最後に

最後ほぼ愚痴になってしまった、信者の皆様大変申し訳ございませんorz

Discussion