🎙️

getUserMediaのdefaultが切り替わらない問題について考える

に公開

概要

getUserMedia({ audio: { deviceId: 'default' } }) を使っているときに、OSの規定マイクを切り替えても反映されないことがある事に気づき調査をしていました。

devicechange イベントは発火されるものの、再取得しても以前のマイクが返ってくることがあり、意図したデバイスで録音されないという不具合でした。

この問題の解決に向けて行ったこと、考えたことについてまとめてみました。

問題の発生条件

  • getUserMedia({ audio: { deviceId: 'default' } }) を使って音声ストリームを取得
  • OSのサウンド設定から「規定のマイク」を別のデバイスに切り替える
  • 再度 getUserMedia({ audio: { deviceId: 'default' } }) を呼び出す
  • しかし取得されるのは以前のデバイスのまま

実際、navigator.mediaDevices.enumerateDevices()labelgroupId は新しいデバイスになっていても、getUserMedia はなぜか前のデバイスを返します。

原因:Chromiumの既知バグ

Chromium の Issue Tracker にも報告があります(Issue #40199570

  • "default""communications" は仮想的な deviceId として扱われます
  • 確認しましたが"communications"も同じ挙動をしました
  • getUserMedia() は、過去に開いた有効な "default" のストリームをキャッシュして再利用しているようです
  • そのため、OSでdefaultを切り替えても新しい実体に切り替わらない
  • 既定のデバイスが取り外される等で無効になった場合は切り替わります

対処方法

恐らく明確にこの方法が正解!という方法は無いと思います。
そのため他の通話アプリ等を参考に誤魔化す方法を考えてみました。

■何もしない漢気方式

現状の自社での対応、Discordのブラウザ版が恐らくこの方法です。
何も対応をしない…システム側でのデフォルト設定がアプリ起動後に変えられたら表示と違うマイクが使われてしまいます。
リロードをすることで直るためユーザー頼りです…。悔しい…。

■Reload方式

deviceChange イベント発火時にgetUserMediaenumerateDevicesdefaultの値に差異があったらリロードを促すという方法です。
恐らくこれは最終手段です。何もしないよりはユーザーフレンドリーで工数も少なくできそうだなぁという考えから思いつきました。

ただ、差異があるかどうかの判定の取り方が少し難しいです。
deviceIddefaultになってしまうため、他で判定できる方法はgroupIdになるのですが、これはInputが2つ以上あるデバイスだと判定が難しくなるためあまり信用できるやり方ではないと思われます。

■default, communicationsを利用しない方式

初回接続時にenumerateDevicesからdefaultのデバイスと同じデバイスを検索し、deviceIdを特定し保存する方法です。
Discordのアプリ版はこの方式を取っている気がします。
enumerateDevicesで取得できるMediaDeviceInfoにはdeviceId, groupIdの他にlabelが存在します。
labelは表示用に用いる値ではありますが、
デフォルト表示では
default - マイク([deviceName])([Vendor ID]:[Product ID])
通常のデバイスID表示では
マイク([deviceName])([Vendor ID]:[Product ID])
このようになっています。

そのため、groupIdfilter後、endsWithで判定を取ることで特定することができる気がしています。
最悪見つからなかった場合はfilter後のリストの[0]にしておき、警告を出すでも良さそうな気がします。

ただぱっと思いつく限りでも問題点が2つあります。

  • defaultのデバイスが無効化された時

無効化されたdeviceIdを指定するとdefaultが取得されます。
そのため、deviceIdenumerateDevicesに存在しない場合、defaultdeviceId特定処理をもう一度走らせるのが無難な気がします。

  • labelはマイクの許可が必要

Discordのアプリ版でのみ対応されているのはこれが原因かもしれません。

■更新時に頑張る方式

gatherはこの方法かなぁと思っています。
システムのマイクを切り替えると、「切り替えています…」と表示され、なにか頑張っています…
上2つを組み合わせるとできなくもないのかなぁと思います。
差異を検知し、そのタイミングでdefaultdeviceIdを特定、それを設定するという方法です。

まとめ

明確な解決方法を掲示できず申し訳ない気持ちでいっぱいです…むしろ詳しい人がいたら教えていただきたいです。
自社のツールでは現状判定を行っておらず、製品化に向けての対応でどれかを試して見たいと思っています。
Discordのブラウザ版が対応されていないところを見ると確実な対応はできないのかなぁと少し自信はありませんが、より正確な調査が完了し次第改めて記事にまとめさせていただきます。

普通にChromium側で対応して…欲しい…ナァ…

PortalKey Tech Blog

Discussion