Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ブラウザ単体でmp4書き出すまで - muddy-web - 2024-12

yue
December 10, 2024

ブラウザ単体でmp4書き出すまで - muddy-web - 2024-12

yue

December 10, 2024
Tweet

More Decks by yue

Other Decks in Programming

Transcript

  1. 3 1. VRoid / VRoid Hub紹介 2. 実装になった経緯 3. 仕様と実装のバランス

    a. ブラウザの制限 b. デバイスの制限 4. リリース後のトラブル・修正 内容
  2. 4

  3. 5

  4. 6

  5. 9

  6. 11 要件整理1 : 出力フォーマット • 静的な画像以外、動くものも欲しい ◦ gif / 動画問わず

    ◦ 実現可能性は早い段階で確かめたい • SNSでの共有しやすさ重視 ◦ 一回撮影したものの編集も考えたい • スピード感重視 ◦ リリースまで~2月
  7. 13 選択肢ではなかった • サーバーサイドエンコード ◦ サーバーを用意するのに時間がかかりすぎて間に合わない ◦ 転送するデータの容量もデカいので UXが良くない ◦

    お金もかかり続ける • Animated AVIF ◦ encodingコストが高い ◦ 認知しているユーザーが少ない • Animated PNG ◦ 投稿として対応しているSNSが少なく、最終的に検証も実装されなかった
  8. 15 ティア表 • 共有しやすさ ◦ mp4 > gif > webm

    > av1f / apng • 実装難易度 ◦ webm > gif > mp4 > av1f / apng • 編集しやすさ ◦ mp4 > webm > gif / av1f / apng
  9. 18 MediaRecorder / Canvas.captureStream • MediaRecorderはリアルタイムの記録であるため、フレーム落ちが発生するとフ レーム落ちしている様子も記録されてしまう懸念があった • CanvasCaptureMediaStreamTrack.requestFrame() でフレームを制御すれば

    この問題は回避できる事がわかった ◦ ただしFirefoxではこのメソッドは未実装 • MediaRecorderではブラウザ毎対応しているコーデックがばらばらで、 Xに直接シェ ア出来るのはmp4書き出せるSafariだけだった ◦ Safari→mp4 ◦ Chrome→WebM(VP8/9) ◦ Firefox→WebM(VP8)
  10. 19 MediaRecorder / Canvas.captureStream const videoType = [ // mimeType,

    ext ['video/mp4', 'mp4'], // Safari Only ['video/webm;codecs="vp9"', 'webm'], ['video/webm;codecs="vp8"', 'webm'], ].find((type) => MediaRecorder.isTypeSupported(type[0]));
  11. 21 その他の実装を試す • recordrtc ◦ unpkg.com上のスクリプトの実行をContent-Security-Policyで許可する必要 があった ▪ それはできれば避けたかったので最終手段にした •

    mattdesl/mp4-wasm ◦ WebCodecs APIから吐き出されたチャンクをMP4にmux出来そうだったので試 した ▪ 出来た ▪ しかしWindowsだけ特定な処理がクラッシュしてしまうことが発覚 • wasmの中でプラットフォーム固有な問題に遭 遇するのが初めて
  12. 23 仕様整理1: 出力フォーマット このような対応表になった • Chrome / Edge ◦ 仕組み:

    WebCodecs + canvas-record ◦ フォーマット: mp4 • Safari ◦ 仕組み: MediaRecorder ◦ フォーマット: mp4 • Firefox ◦ 仕組み: MediaRecorder ◦ フォーマット: webm (vp9 or vp8 / vp9優先)
  13. 25 要件整理2: 出力サイズ • 固定アスペクト比の出力が欲しい ◦ 16:9 ◦ 9:16 •

    固定サイズの出力が欲しい ◦ X(Twitter)サムネ 1200x630 ◦ VRoidHubサムネ 900x1200 • できるだけ出力クオリティを保ちたい
  14. 26 実装上のチャレンジ • 様々なサイズ ◦ 枠のサイズ ▪ = 親Elementのcss上のサイズ ◦

    表示サイズ ▪ = 親の中でアスペクト比適用後css上のサイズ ◦ レンダリングサイズ ▪ DPR(device pixel ratio)によって表示サイズとレンダリングサイズ実は 違う
  15. 28 実装上のチャレンジ • アスペクト比の固定 ◦ object-fitのjs側実装が必要 ◦ → object-fit-math ▪

    fitAndPosition(canvas, output) • 出力サイズ ◦ ユーザーの設定によって最終的に計算されるもの ◦ DPRを考慮したい
  16. 29 出力サイズ決め // 枠のサイズそのままの場合、表示エリアと WebGLの仕様をケアする const outputResolution = { // 出力サイズ

    width: nearEvenize(viewerRect.width // 枠のサイズ), // 奇数を避ける height: nearEvenize(viewerRect.height) };  // 枠内のレンダリングサイズ const renderPosition = fitAndPosition(viewerRect, outputResolution, 'contain'); renderPosition.width = Math.ceil(renderPosition.width); // 少数を避ける renderPosition.height = Math.ceil(renderPosition.height); renderPosition.x = Math.ceil(renderPosition.x); renderPosition.y = Math.ceil(renderPosition.y);
  17. 33 いい感じにする // try render 2x if needed for better

    quality but avoid dpr over 2 which is too large for mobile to render if (isMobile) { const dprLimit = Math.min(window.devicePixelRatio, 2); outputResolution.width = nearEvenize(renderPosition.width * dprLimit); outputResolution.height = nearEvenize(renderPosition.height * dprLimit); } アスペクト比を維持した状態の表示サイズ * min(dpr, 2) dprが2超えた場合を2にする
  18. 34 仕様整理2: 出力サイズ • PC ◦ 表示サイズ=1000x1000, 16:9を選択, dpr=2 ▪

    出力サイズ ⇒ 1920×1080 * 2 = 3840×2160 ◦ 表示サイズ=1000x1000, 1:1を選択, dpr=2 ▪ 出力サイズ ⇒ 1080×1080 * 2 = 2160×2160 • モバイル ◦ 表示サイズ=160x160, 16:9を選択, dpr=3 ▪ 出力サイズ ⇒ 160x90 * min(3, 2) = 320x180 ◦ 表示サイズ=300x200, 1:1を選択, dpr=1 ▪ 出力サイズ ⇒ 200x200 * min(1, 2) = 200x200 ※将来的に調整される可能性は あります
  19. 40 以下のコード覚えてますか? const videoType = [ // mimeType, ext ['video/mp4',

    'mp4'], // Safari Only ['video/webm;codecs="vp9"', 'webm'], ['video/webm;codecs="vp8"', 'webm'], ].find((type) => MediaRecorder.isTypeSupported(type[0]));
  20. 41 • MediaRecorderではブラウザ毎対応しているコーデックがばらばらで、 Xに直接シェア出来 るのはSafariだけだった ◦ Safari→mp4 ◦ Chrome→WebM(VP8/9) ◦

    Firefox→WebM(VP8) と説明したが、 chrome v126から MediaRecorder.isTypeSupported('video/mp4') がtrueになったため、 分岐ロジックが壊れた。 https://chromestatus.com/feature/5163469011943424 トラブル1
  21. なぜかaxiosをuninstallすると撮影が失敗する • 経路として canvas-record -> h264-mp4-encoder -> embuild/dist/h264-mp4-encoder.web.js でビルドされたnodeのbufferモジュールが読み 込まれる

    ◦ h264-mp4-encoderの中身はembuild経由でビルドされているため、簡単に patchで きなかったので一旦戻した。引き続き対応中。 トラブル2
  22. 45