Safari下,AudioNode节点disconnect后,再次connect无效的问题

2020年02月19日Web前端

在修复项目issue时,在手机端的safari下发现了个录音暂停后,再次录音无效的问题。

问题

去除掉冗余代码,直接看代码:

<body>
    <button id="start">开始</button>
    <button id="pause">暂停</button>
    <button id="resume">恢复</button>
    <button id="stop">结束</button>
</body>
<script>
let context = null;
let createScript = null;
let recorder = null;
let audioInput = null;
let stream = null;

document.getElementById('start').addEventListener('click', start)
document.getElementById('pause').addEventListener('click', pause)
document.getElementById('resume').addEventListener('click', resume)
document.getElementById('stop').addEventListener('click', stop)

function start() {
    context = new (window.AudioContext || window.webkitAudioContext)();
    createScript = context.createScriptProcessor || context.createJavaScriptNode;
    recorder = createScript.apply(context, [4096, 1, 1]);

    navigator.mediaDevices.getUserMedia({
        audio: true
    }).then(stream => {
        audioInput = context.createMediaStreamSource(stream);
        stream = stream;
    }).then(() => {
        audioInput.connect(recorder);
        recorder.connect(context.destination);
    });
    
    recorder.onaudioprocess = e => {
        let lData = e.inputBuffer.getChannelData(0);
        console.log('回调获取数据', lData);
    }
}

function pause() {
    audioInput.disconnect();
    recorder.disconnect();
}

function resume() {
    audioInput.connect(recorder);
    recorder.connect(context.destination);
}

function stop() {
    context.close();
    if (stream && stream.getTracks) {
        stream.getTracks().forEach(track => track.stop());
        stream = null;
    }
}
</script>

开启录音后,先暂停录音,再恢复录音,在safari下,回调中获取的都是0,

而在chrome或firefox都可以获取到正常的音频数据。

注:如果safari也能获取到数据,可以暂停久一点。

解决方案

在stackoverflow上也找到了类似的问题:AudioNode.disconnect() followed by .connect() not working in Safari

也就确认了这是safari下的bug。

所以我们此处就需要抛弃disconnect,毕竟他会导致最后数据搜集错误的问题。

let isPause = false;

function start() {
    // ....
    recorder.onaudioprocess = e => {
        if (isPause) {
            return;
        }
        let lData = e.inputBuffer.getChannelData(0);
        console.log('回调获取数据', lData);
    }
    // ...
}

function pause() {
    isPause = true;
}

function resume() {
    isPause = false;
}

代码其他地方不变,新增isPause表示是否暂停,而onaudioprocess回调仅对不暂停时的数据进行处理,这样就可以达到兼容了。

详细代码见:github