9
10

More than 5 years have passed since last update.

Webアプリケーションで音声出力先を切り替える

Last updated at Posted at 2018-07-09

はじめに

音声出力を扱う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

deviceInfos.png

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文で予めチェックしています。

実行してみる

このように切り替えることができるようになります。もちろん、ビデオチャット中に動的に切り替える事もできます。

audiooutputselect.png

余談 MediaDevices.enumerateDevices() の実行タイミング

Chrome、Firefoxともに、MediaDevices.getUserMedia()でユーザがカメラとマイクの利用を許可しなければ、labeの値が取得できません。
今回取り上げた音声出力デバイスの情報については、マイクの利用を許可しなければ以下のように、空になります。

audiodeviceisnull.png

labelの値はUIに出力したいですよね。そんな時は、MediaDevices.getUserMedia() → ユーザのアクション → MediaDevices.enumerateDevices()という順番で実行することで、解決できます。Firefoxは前からこのような挙動でしたが、Chromeは最近(同僚に調べてもらったところ恐らくM66から)このような挙動になっているようなので、ご注意下さい。それ以前はユーザの許可無しでlabel情報が取得できていました。

尚、SkyWayの公式サンプルは、ユーザが許可する前にMediaDevices.enumerateDevices()を実行するため、初回アクセス時はlabel情報が取得できません。今回のデモでは暫定的にその部分を修正しています。

9
10
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
10