🧰

OBSのブラウザソースで動くものを作るあなたへ

2024/04/27に公開

私たちはスコラボ(https://www.sucolab.jp) というOBSのブラウザソース上で配信画面を再現するツールを開発しています。
開発の中でOBSのブラウザソースという特殊極まりない動作環境に苦しめられてきました。
この記事はOBSのブラウザソースで動く何かを作ろうとしている人に向けて同じ地獄を見てほしくはないため、何か参考になればという思いで書いています。

現状整理

ブラウザソースって何?

まずはじめに、OBSにおけるブラウザソースというものの実体はChromium Embedded Framework(CEF)と呼ばれるChromiumベースのブラウザをソフト内に組み込むためのフレームワークの一部を変更し、OBS向けに最適化を施し、OBSの一部機能の情報取得・制御を行えるブラウザAPIを追加したものです。

過去には CLR Browser Source Plugin や Browser Plugin などが別途配布されていましたが、後者のBrowser Pluginが2016年ごろにOBSのFull Installerにバンドルされるようになり、それ以降は公式でメンテナンスが進められています。

現在、OBSで利用されているCEFは https://github.com/obsproject/cef 、実際に動作しているブラウザソースのプラグイン本体は https://github.com/obsproject/obs-browser で管理されています。

ちなみにカスタムブラウザドックについてもほぼ同様です

ブラウザソースのChromiumバージョン

ブラウザソースはChromiumベースのブラウザだと言いましたが、それらは最新バージョンのChromiumではありません。
OBS StudioかStreamlabs Desktop、XSplit、OBSのバージョンによってもChromiumのバージョンが異なります。
わかりやすいように表にまとめてみましょう

配信ソフト名 OS バージョンレンジ Chromiumバージョン
OBS Studio MacOS ~ 26.1.0 75.0.3770.100
OBS Studio Windows ~ 27.1.3 75.0.3770.100
OBS Studio Mac OS 26.1.2 ~ 27.1.3 85.0.4183.121
OBS Studio Mac OS / Windows 27.2 ~ 27.2.4 95.0.4638.69
OBS Studio Mac OS / Windows 28.0 ~ 103.0.5060.134
Streamlabs Desktop Windows 1.16.3 103.0.5060.134
Streamlabs Desktop Windows 1.10.0 95.0.4638.69
XSplit Windows 4.4.2211.0404 68.0.3440.106
XSplit Windows 4.5.2402.2304 103.0.5060.134

MacOSのみ一部バージョンが異なるのは26.1.1リリースのタイミングでブラウザソースがクラッシュする問題が発生し、hotfixとして26.1.2リリースのタイミングでMac版のみアップデートがなされたためです。

このようにバージョンアップのタイミングはまちまちで、XSplitに関しては最近までChrome 68系、OBSについても最新版の30.xでもChrome 103系と執筆現在(2024/04/22)での最新バージョンであるChrome 124から21バージョン遅れをとっている状況です。

これらのアップデートが進まないことの背景には、OBSがCEF経由でページの表示をテクスチャとして使用するためにテクスチャ共有を利用しながら、ゲームなどの他のソフトウェアやキャプチャを使用するためにもテクスチャ共有を利用していることと、CEFがOffscreen Rendering(OSR)時にハードウェアアクセラレーションを利用しているとテクスチャ共有が利用できない・破損するという問題があることが綺麗に重なっていることがあります。
この問題に対しOBSは、CEFに独自のパッチを当てテクスチャ共有を利用できるようにしており、CEFの変更に追従しながらこれらのパッチを当てることに限界があるという部分があるのでしょう。

しかし、今年に入ってからOBSがとっているアプローチと似た方法でChromium / CEF側から修正を行おうとしている方がいるように見えるため、この対応を行おうとしている方に深く敬意を表すと共に少し期待をしています。
https://chromium-review.googlesource.com/c/chromium/src/+/5465148

なんて話を2024/04/20あたりに書いてたわけなんですが、4/24にCEF側にShared Texture対応のCLが入っており今後の動きへの期待感がさらに強まっています
https://bitbucket.org/chromiumembedded/cef/commits/260dd0ca24f9a8952f871dece2fcc10fe85d81fe

Chromiumバージョンが古いことにより発生する問題

ここまででブラウザソースというものと内臓されているCEFのChromiumバージョンについてご理解いただけたと思います。
Chromiumのバージョンが古いということは利用できないCSS PropertyやSelector、JSの構文、WebAPIがあるということです。
せっかくなので深刻度が低いバージョン順に見ていきましょう。ピックアップしているものは使いそうなもの、メジャーなもので利用できないものの一部です。
また、すべて見ると大変なのでChrome 68とChrome 85は除外し、103, 95, 75を見てみましょう。

出典

Chrome 103

OBS 28.0以降にはChrome 103系のCEFが搭載されていますが、このバージョンはまだまともにJS, CSS共に利用できるバージョンです。

JS / WebAPI

  • WebGPU (Chrome 113)
  • ML (Chrome 112)
  • SharedStorage (Chrome 117)
  • ViewTransition (Chrome 111)

CSS

  • @container / container (Chrome 105)
  • @scope (Chrome 118)
  • animation-composition (Chrome 112)
  • animation-timeline (Chrome 115)
  • mask-clip / mask-image / mask-mode (Chrome 120)
  • color-mix() (Chrome 111)
  • offset-anchor (Chrome 116)
  • rotate / scale / translate(Chrome 104)
    • transformのものは利用可能
  • text-spacing-trim (Chrome 123)
  • background-clip: text (Chrome 120)
  • text-wrap / white-space-collapse (Chrome 114)
  • scrollbar-color / scrollbar-width (Chrome 121)
  • view-timeline / scroll-timeline (Chrome 115)
  • CSS Subgrid (Chrome 117)
  • grid-template-rows, grid-template-columns Animation (Chrome 107)
  • :has() / :modal (Chrome 105)
  • CSS Nesting (Chrome 120)

HTML

  • popover

最近のCSS Propertyと、WebAPIばかりですね
text-wrap, mask-image, background-clip: text, grid-template-のAnimationなんかは利用することもあるのではないでしょうか?
(私も text-wrapgrid-template-系のアニメーションは利用していてハマりました)

Chrome 95

OBS 27.2.x にはChrome 95系のCEFが搭載されていますが、このバージョンもまだギリギリまともにJS, CSS共に利用できるバージョンです。

JS / WebAPI

  • WebTransport (Chrome 97)
  • Navigation API (Chrome 102)
  • stracturedClone (Chrome 98)

CSS

  • @layer (Chrome 99)
  • font-palette / @font-palette-values (Chrome 101)
  • text-emphasis (Chrome 99)

HTML

  • inert (Chrome 102)

Panda CSSでも使われてる Cascade Layer が使えないのと、stracturedCloneが使えないのが大きいでしょうか?
今はまだあまり使われていませんが、WebTransport やHistory APIの改良とも言えるNavigation APIが使えないのも問題になる場面があるかもしれません。

Chrome 75

OBS 27.1.3 以前には (Mac OSを除き) Chrome 75系のCEFが搭載されていますが、このバージョンの対応は至難の業でしょう...

JS / WebAPI

  • AudioEncoder / AudioDecoder (Chrome 94)
  • VideoDecoder / VideoEncoder (Chrome 94)
  • EyeDropper (Chrome 95)
  • ImageDecoder (Chrome 94)
  • Scheduler (Chrome 94)
  • URLPattern (Chrome 94)
  • reportError (Chrome 95)
  • CompressionStream / DecompressionStream (Chrome 80)
  • Private Class Methods (Chrome 84)
  • Logical Assignment Operator %%=, ||= (Chrome 85)
  • Nullish Coalescing Operator ?? (Chrome 80)
  • Optional Chaining ?. (Chrome 80)

CSS

  • @counter-style (Chrome 91)
  • accent-color (Chrome 93)
  • aspect-ratio (Chrome 88)
  • border-block / border-inline (Chrome 87)
  • inset (Chrome 87)
  • outline (Chrome 94)
  • text-decoration-thickness (Chrome 89)
  • :is() / :where() (Chrome 88)
  • @property (Chrome 85)
  • backdrop-filter (Chrome 76)
  • appearance (Chrome 84)
  • overscroll-behavior-block / overscroll-behavior-inline (Chrome 77)
  • content-visibility (Chrome 85)
  • revert (Chrome 84)
  • Flex Layoutにおける gap (Chrome 84)
  • max(), min(), clamp() (Chrome 79)

HTML

  • Import maps
  • AVIF Image
  • Lazy Loading

そろそろ対応するのも難しくなってきましたね...
JSの構文で利用場面の多い ???.、CSSで利用場面の多い min()max()outlineinsetすら使えなくなってしまいました...

対処法と事例

徐々に利用できないものが増えていくことがわかったところで、それぞれの対処法と実際にスコラボの開発の中で発生した事例をご紹介します

CSS関係

CSS関係の利用できないプロパティなどに対する対処法は大きく分けて

  1. 自動で特定のバージョンで利用できるCSSに変換してくれるツールを利用する
  2. 利用しないように気を付ける
    の2種類あり、どちらのアプローチも取らなくてはいけない場合がほとんどです。

1. 自動で特定のバージョンでも正常に利用できるCSSに変換してくれるツールを利用する

こちらはとても簡単な対処法ですし、普段のフロントエンド開発においても行なっていることだと思います。
PostCSSや最近(?)出てきたLightning CSSなどを利用して特定のバージョンでも正常に利用できるCSSに変換してくれるツールを利用するだけです。

Next.jsなどフレームワークを利用して開発してる場合などはすでに入っている場合が多いのではないでしょうか。
PostCSSを利用している場合はPostCSS Preset Envを利用することで機械的に対応可能なものは対応されます。

機械的に対応可能なものの一例を挙げると

  • inset -> top,bottom,left,right
  • border-block -> border-top,border-bottom
  • Cascade Layer -> :not()を利用して可能な範囲で対応
  • color-mix -> rgb
    などがありますが、基本的には静的に変換を行うためCSSのVariablesなどは同時に利用することはできなくなります。

color-mixなどはCSSのVariablesと併用して利用することがあると思いますが、この場合はcolor-mixを利用せずに同様の効果を与える必要が出てきます。

2. 利用しないように気を付ける

自動で変換できないものなどについては書く段階で気をつける以外にやれることはほぼないでしょう。

text-wrapoverflow-wrapなどに、outlineborderやpseudo-classなどで擬似的に再現するなどの工夫が必要になります。
「CSSの⚪︎⚪︎⚪︎⚪︎⚪︎が全ブラウザ対応!」という感じのタイトルで書かれているテクニックのほとんどは利用できないので、頭の片隅に保存して喜ばない方が良いでしょう。

実際の事例

スコラボではチャット機能と呼ばれるYouTubeのチャットを表示する機能を提供しています。
これらのテンプレートは単一の共通したコンポーネント(骨組み)に対してCSSのみを利用して肉付けをする方式をとっています。
コンポーネント側で一部の情報(フォントサイズなど)をJSを用いて解決してユーザーが自由に変更するようにしていますが、それも基本的にはデータ属性もしくはCSS VariableによってCSS側に受け渡されます。(項目の表示・非表示などはdata属性を確認した上でのcss側の責務としています)

Animation 編
チャットテンプレートの一部は高さ方向にサイズが変動しながら出現するアニメーションをつけているものがあります。
これを以下のコードで実現していました(emotion)

  {
      display: 'grid',
      gridTemplateRows: '0fr',
      animationName: keyframes`
        from {
          grid-template-rows: 0fr;
        }
        to {
          grid-template-rows: 1fr;
        }
      `,
      animationDuration: 'calc(50000ms / var(--animation-speed))',
      animationTimingFunction: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
      animationIterationCount: '1',
      animationFillMode: 'both',
    }

そうするとOBSで表示するとこんな感じにアニメーションがスキップされ、異常に上方向に移動してしまっているような感じになります。
(社内の開発用ツールでの表示のため実際の表示と一部異なる場合があります)

これは grid-template-rowsに対してアニメーションをかけていますが、この機能自体がChrome 107以降でしか利用できないために発生しているものです。

これに対して取れる対応は昔ながらのmax-heightをアニメーションさせる方法のみです(他に方法があればお教えください)
そこで、コメント本体の最終的なheightをJSによって取得させCSS Variableに格納し、その値に対してアニメーションを行います。
また、何かしらの原因でheightの取得が行えなかった場合にもコメントが一切表示されなくなるということを避けるため、アニメーションの最後でmax-heightunsetに変更します

変更後の実際のコードはこちらです。

  {
      display: 'block',
      overflow: 'hidden',
      flexShrink: 0,
      animationName: keyframes`
        0% {
          max-height: 0;
        }
        99% {
          max-height: var(--inner-box-height, 150px);
        }
        100% {
          max-height: unset;
        }
      `,
      animationDuration: 'calc(50000ms / var(--animation-speed))',
      animationTimingFunction: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
      animationIterationCount: '1',
      animationFillMode: 'both',
    }

変更後はこのようにある程度きれいにアニメーションし、異常に移動しているのもなくなっていることがわかるでしょう。

text-wrap編
チャット機能はYoutubeのデータを表示する機能であるため、ユーザーがどんなユーザー名を使用するかを弊社側で指定することができません。
そこで以上に長いユーザー名が使用されていた際にtext-overflow: ellipsisによって省略表示にしています。
そのためテキストの折り返しを抑えるために text-wrap: nowrapを使用していました
そうするとOBS上ではこのような表示になってしまいます。

これはtext-wrapがChrome 114以降でしか利用できないために発生しています。

対応としてはwhite-space: nowrapに置き換え、通常時は同様の表示のまま折り返しのみを禁止することになります。
white-spaceプロパティ自体はもともと、white-space-collapsetext-wrapwhite-space-trimの3つのプロパティに対するショートハンドとして実装予定で、CSS Text Level 3の段階で定義されており、先行して実装されているためこちらを利用しても大きな問題はありません(実際の動作に変化はありません)
対応後のものがこちらです。正しくellipsisが動作し省略されていることがわかるかと思います。

color-mix編
一部のスタイルの外枠は特定のバックグラウンドカラーにグレー(#171717)などを混ぜ合わせることで違和感なく馴染む色にしていることがあります。
これを color-mix(in srgb, var(--accent-color), #171717FF var(--border-strength))などで記述している場合があります。

このcolor-mix構文はChrome 111以降でしか利用できないため、他の方法を取る必要があります。
最も簡単な方法は疑似要素を利用することでしょう。
背景のついた要素とborderを表現する疑似要素の2つに分け、borderを表現する疑似要素のopacityをborderの強さのパラメータに応じて変動させることで擬似的に色が混色されるような見え方に近くなります(実際には少し異なる事があります)

このように場合によってはかなり遠回りをして再現する必要がある場合があったり、どうしても再現できずに表現を変える必要がある場合もあります。

JavaScript関連

JavaScript関連についても基本的にはCSSと同じく

  1. 自動で特定のブラウザバージョンに対応したものにトランスパイルしてくれるツールを利用する
  2. 利用しないように気を付ける
    の2種類を併用する形になります。

1. 自動で特定のブラウザバージョンに対応したものにトランスパイルしてくれるツールを利用する

こちらについてもとても簡単な対処法ですし、普段のフロントエンド開発においても行なっていることだと思います。
BabelやSWCなどのトランスパイラを利用して特定のブラウザのバージョンに対応したものに自動対応することができます。(babelについても@babel/preset-envを利用することで可能です)

機械的に対応可能なもので言うと

  • a ?? b -> a !== null && a !== void 0 ? a : b
  • b &&= a -> b && (b = a)
    が範囲内のバージョンにはあるでしょうか

外部のライブラリなどを利用する場合は特に注意が必要で、cdn経由で利用するのであればそれが該当のブラウザでも動作するようにトランスパイルされているかの確認が必要ですし、bundlerを利用するのであれば該当のブラウザでも動作するようにトランスパイルされていないライブラリをすべてトランスパイルする覚悟を持つ必要がある場合もあります。

2. 利用しないように気を付ける

WebAPIなどについてはこちらを選択する必要がある場合が多いです。

stracturedCloneJSON.parse(JSON.stringify())で書き換える(厳密には動作が異なるため状況によって選択の必要あり)などを行う必要性がある場合が多いです。
ただ、OBSのブラウザソース上でWebGPUを触ることやXR関係を触ること、Audio EncoderやVideoEncoderを触ることはかなり少ないと思いますので特殊な状況でなければあまり気にしなくても良いと思います。

(こちらも実際の事例を書こうかと思いましたが、内容があまりにも昔 & 今やもう発生しないであろう問題だったのでやめておきます...)

開発する上でのTips

さて、ここまではChromeバージョンによる問題や対応策を長々と書いてきましたが、ここからは開発Tips的なものをご紹介したいと思います。

OBSのブラウザソースのDevToolsを開く

OBS内のブラウザソースにもDevToolsというものが存在します。通常では何をどうやっても利用できませんが、起動時にremote-debugging-portオプションをつけてDevToolsを利用するポートを指定することで利用できるようになります。

Windowsの場合はショートカットに記述を足すのが楽かもしれませんが、MacOSの場合は以下のコマンドで可能です

open "OBS.app" --args --remote-debugging-port=9222

# or

open -na "OBS" --args --remote-debugging-port=9222

このコマンド経由で起動後にhttp://localhost:9222にアクセスすると、

のような感じで利用しているブラウザソース・カスタムドックの一覧が出てくるので確認したいブラウザソースのタイトルをクリックすると

このようにDevToolsが利用できるようになります。(いつも通りNetworkやConsoleなども変わりなく利用できます。)
この画面からリロードなども行えますが、クリックやスクロール・画面内の文字列の選択やコピーなどは行えないのでご注意ください。

なぜかOBSでのみうまく動かない時などは一旦DevTools経由でエラーがないかを確認するのと、CSSが正常に適用されているか(⚠️マークがプロパティに表示されていないか、何かによって打ち消されていないか)を確認いただくとスムーズです。

デフォルトのカスタムCSS

OBSのブラウザソースにはデフォルトで以下のカスタムCSSが挿入されています。

body { background-color: rgba(0, 0, 0, 0); margin: 0px auto; overflow: hidden; }

このカスタムCSSはブラウザソースのロード時に必ず<head>の一番最後に<style>を用いて挿入されます。

これによって何かしらそれよりも詳細度の低いもので記述されている場合(0-0-1なので普通はありませんが同一の詳細度の場合・0-0-0の場合があります)は上書きされることに注意する必要があります。

対話モードの制限

ブラウザソースにはクリックなどの操作が行える対話モードというものがあります。
これの中でスクロールやクリック、入力などある程度の操作は行えますがブラウザとしては限定的な動作です。

一般的なブラウザと異なる部分の一例です

  • SelectなどのOS/ブラウザ固有の別なメニューが描画されるはずのものが描画されない(選択はできるが表示がないので困難)
  • Copy & Pasteができない
  • カーソルが一切変化しない

このように入力周りで大きな制限を受けているため、基本的には設定ツールを別途用意する方が良いと思います

OBS内の他ソースの影響は受けない

ブラウザソース内のレンダリングは当たり前ではありますが、OBS内の他ソースの影響を受けることはありません。
これはCSSによって与えられたエフェクト・フィルタに対しても同様で、わかりやすい例としてはbackdrop-filterがあります。
backdrop-filterは要素の背後の領域に何かしらのフィルタをかけることができるものですが、この背後の領域というものがOBSにおいてブラウザソース内のみになります。

サンプルを見てみましょう
(画像は https://www.pakutaso.com/20190249044post-18819.html をお借りしております)

まずは普通にChromeでbackdrop-filterを用いてブラーを以下のようなコードでかけると

    background-color: rgba(255, 255, 255, 0.3);
    backdrop-filter: blur(10px);

このような表示になり、セピアにすると

    background-color: rgba(255, 255, 255, 0.3);
    backdrop-filter: sepia(1);

このようになります。

これがOBS内の画像と組み合わせた場合にも再現されてほしいと思うところですが、実際にはブラーをかけようとしたものは

セピアにしようとしたものは

このようになります。

一切適用されていないことがお分かりいただけるかと思います。
このようにブラウザソース内の事象・合成はブラウザソース内のみで完結し外部の影響を受けることはありません。

半透明が黒ずむ

ブラウザソースで半透明の色を描画すると黒ずむ場合があります。
これはOBSが全てのソースをガンマカラースペースで合成することが原因です。
画像ソースでは「アルファ値を線形空間に適用する」という設定項目を追加することでOBSとしては対処しましたが、この設定はブラウザソースには存在しません。

ブラウザソースにおいてはブレンド方法という設定から同様の効果を得ることができます。
正直にいうとこの設定項目の名称・内容は映像に関連するツールとしては相応しくないと思っていますが、既定がガンマカラースペースでの合成、sRGBオフがリニアカラースペースでの合成を指していると考えると多少わかりやすくなるかと思います。

設定方法: ブラウザソースを右クリック -> ブレンド方法 -> sRGBオフ

実際の見た目の違いはこのようになります。

白でもわかりやすいですが、その他の色(特にパステルカラーの半透明など)だと可愛さやかっこよさなど、それ自体が持っていた色によって表されている良さを潰すほどに色の印象が異なるため、必ず利用者に案内した方が良いと思います。

OBSの情報へのアクセス

ブラウザソースのCEFは拡張されていると初めに書きましたが、これはOSR/Shared Textureのためだけではありません。OBSの情報にアクセスすることができるAPIがグローバルに追加されています。
このAPIを経由することでOBSのバージョンや配信状態などの情報を取得できるほか、そのソースが表示・アクティブにされているかどうかなども確認することができます。

スコラボではこのアクティブ状態の変化を検知してAPIへのリクエスト数を落としたりなど、ユーザーが認識しない部分での最適化を施しています。

パーミッションの変更は必要ですが配信開始や停止なども行えるため、何か面白いものが作れそうだなと思っています。(知らないひとが作ったアプリケーションに配信開始・停止のパーミッションを与えるのはかなり怖いですが...)

APIの詳細は以下をご確認ください。
https://github.com/obsproject/obs-browser?tab=readme-ov-file#obs-browser

最後に

『OBSのブラウザソースで動くものを作るあなたへ』と題してブラウザソースの大変な点やこれから作ろうとしている人に対して何かの参考になればということでTipsをいくつかご紹介しました。

OBS上でブラウザが動くということは多くの可能性があります。WebRTCだって利用できますし、WASMだって動きます。ブラウザでできることは大体できるのです。
ぜひこの記事で興味を持った方は自分の作りたいもの、実現したいこと、使ってほしいものを作ってみてください。

Chromiumのバージョンが違うところなどはいかにも大変そうに見えるかもしれませんが、いろいろな配信者さんに利用されたり、実際にOBS上で動かしたかったものが動いた時の喜びはすごく大きいです。
私は推しの配信者さん・VTuberさんに使ってもらえた時なんて叫ぶほど喜んでいます。

この記事がそんな体験への一歩、配信を盛り上げる一助となれたら光栄です。

スコテック

Discussion