🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

JavaScriptで音声の出力先を変えたい

先日のUGでもみんな気になってる感じがあり、個人的にも気になっており、現状をまとめておこうかと思い。

`setSinkId()`について

const devices = await navigator.mediaDevices.enumerateDevices();
const [{ deviceId }] = devices.filter(device => device.kind === 'audiooutput');

const $audio = document.createElement('audio');
await $audio.setSinkId(deviceId);

というように、`deviceId`を指定して、その要素の出力先を変えられるAPIがいちおう存在します。
入力を変えるのに`getUserMedia()`を使うので、その反対って感じ。

`enumerateDevices()`で、`kind`が`audiooutput`なものが返ってくるので、それを使う。

Audio Output Devices API

ちなみにこいつらはまとめて「Audio Output Devices API」といいます。

`HTMLMediaElement.prototype.setSinkId()`なので、`audio`でも`video`でも使える。

肝心のブラウザサポートは?

Chrome

実装済み。

https://chromestatus.com/features/4621603249848320

デスクトップ限定ではあるものの、M49から入ってるっぽい。

Firefox

実装されてるけど、フラグの指定が必要とのこと。

Bug List: setSinkId

そして、静的な音声の再生には使えるけど、WebAudioやWebRTCにはまだ非対応らしい。

Safari

未実装。

179415 – Get/set audio output devices -- mediaDevices.enumerateDevices() & HTMLMediaElement.setSinkId()

macSafariですら気配ないので、iOS Safariに来るのはいったいいつの日か・・・!

Chromeでしか動かない問題

幸か不幸かChromeでは実装されてるので、他のブラウザの利用者から「なんでできないの?」って言われる問題。

このAPIに限らず、ブラウザAPIではあるあるネタであり昔からずっとあるやつです。

これは機能をリリースする前に各ブラウザの互換性を調べるしかなくて、Polyfillもできないのであればすっぱり諦めるくらいがよいかなと思ってます。どうしようもないので。

まぁブラウザではなくOSの設定を変えればよいという話ではあるので、そこをUIでなんとかできなくも・・ない・・?

Bluetoothヘッドセット問題 その1

マイクだけ認識される問題。

たとえばFirefoxとかで、Bluetoothヘッドセットをつないだ状態で、`enumerateDevices()`する。

すると、`audioinput`にそれが現れるので、`getUserMedia()`で利用ちゃう、みたいなケース。

  • 入力: ヘッドセット
  • 出力: 本体スピーカー

というちぐはぐな状態になってしまう。

対策としてはやはり、

  • 手動でOS側の出力先を変える
  • もう外部デバイス切り替えをUIで実装しない
    • 内部デバイスは`gUM()`前に取れるやつなのでそれだけ使う

このへんは要件次第ってやつよね・・。

Bluetoothヘッドセット問題 その2

たとえばiOSSafariだと、Bluetoothヘッドセットをつないでも、`enumerateDevices()`で出てこない。
`audiooutput`だけでなく、`audioinput`にも出てこない。

ただヘッドセットが認識されると、自動的にマイクもスピーカーも切り替わるはず。
そこが上手くいかない場合は、またしてもOSの設定を見直すしかない。

iOSなら、

これを自動にしておくだけでよいはず。

まとめ

そういう意味では、

  • 外部デバイスの切り替えはOSの設定でやるようにしてしまう
  • UIで実装しない

のが、もしかして一番キレイな解決策やったりするんかな・・・とか最近は思ってます。

あとはWebを捨ててNativeでやるとか?(そういうAPIたぶんあると思うので)

Web屋は実装されるのをひたすら待つ。