为什么对方接听电话后自动播放音频?

Why does audio automatically play after a peer accepts the call?

当我有两个使用 peerjs 相互呼叫的对等点时,一旦对等点接受呼叫,发起呼叫的对等点的音频就会开始播放,即使我没有明确设置自动播放或调用 play()。此问题出现在 chrome 浏览器中。

<!DOCTYPE html>
<html>
    <head>
        <script src="https://unpkg.com/peerjs@1.3.1/dist/peerjs.min.js"></script>
    </head>
    <body>
        <h1 id='peerid'></h1>
        <input type='text' id='in'/>
        <button onclick="call()">Call</button>
        <button onclick="debug()">debug</button>
        <button onclick="printtime()">time</button>
        <button onclick="printauto()">auto</button>
    </body>
        <script type='text/javascript'>
            let a = new Audio();
            a.onplay = () => {
                console.log('play');
            };
            a.onplaying = () => {
                console.log('playing');
            };
            a.onseeking = () => {
                console.log('seeking');
            };
            a.onseeked = () => {
                console.log('seeked');
            };
            let m = new MediaStream();
            a.srcObject = m;
            let header = document.getElementById('peerid');
            let i = document.getElementById('in');
            let peer = new Peer();
            peer.on('open', (id) => {
                header.innerHTML = id;
            });
            peer.on('call', (call) => {
                call.answer();
                call.on('stream', (stream) => {
                    stream.getAudioTracks().forEach(track => m.addTrack(track));
                });
            });
            let call = () => {
                navigator.mediaDevices.getUserMedia({ audio: true }).then(mediaStream => {
                    let call = peer.call(i.value, mediaStream);
                    call.on('stream', (stream) => {
                        stream.getAudioTracks().forEach(track => m.addTrack(track));
                    });
                }, rejected => {
                    console.log('rejected');
                }).catch(e => console.error(e));
            };
            let debug = () => {
                console.log(a.paused);
            };
            let printtime = () => {
                console.log(a.currentTime);
            };
            let printauto = () => {
                console.log(a.autoplay);
            };
        </script>
</html>

要复制这个,

  1. 使用网络服务器提供上面的 html 代码(例如:python3 -m http.server
  2. 打开服务于此页面的两个选项卡
  3. 将粗体标识符从一个选项卡复制到另一个选项卡,然后单击“呼叫”
  4. 它应该要求访问您的麦克风(这可能需要一段时间,请稍等片刻,或者只是向通话按钮发送垃圾邮件)
  5. 一旦它可以访问您的麦克风,另一个选项卡应该会自动播放音频(音频将是您自己的声音),而音频元素不会自动播放为真或在您可以看到的任何地方调用 play()

我不知道为什么会这样。如果您在音频暂停或自动播放打开时打印出来,它们都会打印暂停为真(即使音频仍在播放)和自动播放为假(即使音频仍在播放)。 None 的事件都会触发。这是 chrome 错误吗?还是我只是愚蠢? (我假设是后者)

这是一个 Chrome 错误,我打开了 https://crbug.com/1279299
该错误的要点是,在设置为 HTMLAudioElement 的 srcObject 的 MediaSource 上添加音轨将强制自动播放该 MediaStream,即使该元素实际上从未告诉它...

所以可以像这样进行较小的复制:

const a = new Audio();
const m = new MediaStream();
a.srcObject = m;
a.volume = 0.1; // volume still works...

onclick = e => {
  const ctx = new AudioContext();
  const osc = ctx.createOscillator();
  const out = ctx.createMediaStreamDestination();
  osc.connect(out);
  osc.start(0);
  out.stream.getAudioTracks().forEach(t => m.addTrack(t));
};
Click to start

知道这一点,解决方法也很明显:不要将元素的 srcObject 设置为这个空的 MediaStream,将其直接设置为从对等点接收的一个流:

call.on('stream', (stream) => {
  a.srcObject = stream;
});