はじめに
音声出力を扱うWebアプリケーションを作っていると、音声出力先を切り替えたくなることはありませんか?
この記事では、Google Chrome限定で、その機能を実現する方法をご紹介します。
前提条件
- 音声出力を扱うWebアプリケーションとして、SkyWayのp2p-videochatサンプルを利用します
- Google Chromeしか機能が実装されていないため、それ以外のブラウザでは動作しません
- Chrome M67 Stableで動作確認済み
デモ
こちらで動作を確認できます。
実装方法
音声出力デバイスの情報を収集する
MediaDevices.enumerateDevices()を利用します。
navigator.mediaDevices.enumerateDevices()
.then(function (devices) { // success
}).catch(function (error) { // error
return;
});
実行すると使用できる入出力メディアデバイスの情報を持つMediaDeviceInfoオブジェクトが配列で返されます。
私の環境で実行すると以下のような配列が返ってきます。どんなディスプレイを使っているとか、カメラを使っているとかもろわかりですねw
kind に着目してください。audiooutputとなっているデバイスが音声出力デバイスです。この情報を利用します。
ユーザにデバイスを選択してもらう
SkyWayの公式サンプルコードには、すでに音声と映像の入力ソースを選択するコードが入っています。今回はそこに音声出力先を切り替えるコードも追記します。
const audioSelect = $('#audioSource');
const videoSelect = $('#videoSource');
const audioDeviceSelect = $('#audioDevice'); // 追記
selectors = [audioSelect, videoSelect, audioDeviceSelect]; // 修正
navigator.mediaDevices.enumerateDevices()
.then(deviceInfos => {
const values = selectors.map(select => select.val() || '');
selectors.forEach(select => {
const children = select.children(':first');
while (children.length) {
select.remove(children);
}
});
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
const option = $('<option>').val(deviceInfo.deviceId);
if (deviceInfo.kind === 'audioinput') {
option.text(deviceInfo.label ||
'Microphone ' + (audioSelect.children().length + 1));
audioSelect.append(option);
} else if (deviceInfo.kind === 'videoinput') {
option.text(deviceInfo.label ||
'Camera ' + (videoSelect.children().length + 1));
videoSelect.append(option);
} else if (deviceInfo.kind === 'audiooutput') { // ここから追記
option.text(deviceInfo.label ||
'Output device ' + (audioDeviceSelect.children().length + 1));
audioDeviceSelect.append(option);
} // ここまで追記
}
selectors.forEach((select, selectorIndex) => {
if (Array.prototype.slice.call(select.children()).some(n => {
return n.value === values[selectorIndex];
})) {
select.val(values[selectorIndex]);
}
});
videoSelect.on('change', step1);
audioSelect.on('change', step1);
audioDeviceSelect.on('change', step1); // 追記
});
修正する場所と追記する場所はコメントの通りです。
音声出力先を切り替える
HTMLMediaElement.setSinkId()を利用します。
const audio = document.createElement('audio');
audio.setSinkId(deviceId);
MediaDevices.enumerateDevices()で取得した音声出力デバイスのdeviceIdを引数として与えることで、音声出力デバイスを切り替えることができます。
今回はこのように利用しています。
if(audioDevice){
$('#their-video').get(0).setSinkId(audioDevice);
}
deviceIdがnullの場合はエラーが出るため、if文で予めチェックしています。
実行してみる
このように切り替えることができるようになります。もちろん、ビデオチャット中に動的に切り替える事もできます。
余談 MediaDevices.enumerateDevices() の実行タイミング
Chrome、Firefoxともに、MediaDevices.getUserMedia()でユーザがカメラとマイクの利用を許可しなければ、labeの値が取得できません。
今回取り上げた音声出力デバイスの情報については、マイクの利用を許可しなければ以下のように、空になります。
labelの値はUIに出力したいですよね。そんな時は、MediaDevices.getUserMedia() → ユーザのアクション → MediaDevices.enumerateDevices()という順番で実行することで、解決できます。Firefoxは前からこのような挙動でしたが、Chromeは最近(同僚に調べてもらったところ恐らくM66から)このような挙動になっているようなので、ご注意下さい。それ以前はユーザの許可無しでlabel情報が取得できていました。
尚、SkyWayの公式サンプルは、ユーザが許可する前にMediaDevices.enumerateDevices()を実行するため、初回アクセス時はlabel情報が取得できません。今回のデモでは暫定的にその部分を修正しています。