Safari搭配AirPods时MediaRecorder仅触发一次有效dataavailable事件的问题咨询
替代MediaRecorder获取getUserMedia原始音频数据的方案
针对你遇到的Safari搭配AirPods时MediaRecorder仅首次返回有效数据的兼容性问题,你可以通过Web Audio API直接获取原始音频数据,以下是两种可行方案:
方案一:使用ScriptProcessorNode(兼容性较好)
通过AudioContext创建ScriptProcessorNode,可实时获取音频流的原始PCM数据,该API虽标记为废弃,但目前仍在多数浏览器中兼容可用。
代码示例
document.querySelector("#micTest").addEventListener('click', async function(){ if ( window.stream ) { this.innerText = "Test Microphone"; window.audioContext?.close(); window.stream.getAudioTracks().forEach(track => track.stop()); window.stream = null; return; } else { this.innerText = "Stop Test"; } if (!navigator.mediaDevices) throw new Error('unsupported mediaDevices'); await navigator.mediaDevices.getUserMedia({ audio: true, video: false }); const devices = await navigator.mediaDevices.enumerateDevices(); const devStr = devices.map((d) => `${d.deviceId}:${d.label}`).join('\n'); const deviceId = window.prompt(`choose device: ${devStr}`); if (!deviceId) return; const constraints = { audio: { deviceId }, video: false }; const stream = await navigator.mediaDevices.getUserMedia(constraints); window.stream = stream; // 保留原音频播放功能 const audio = document.createElement('audio'); audio.controls = true; audio.autoplay = true; audio.srcObject = stream; // 初始化Web Audio API获取原始数据 const audioContext = new (window.AudioContext || window.webkitAudioContext)(); window.audioContext = audioContext; const source = audioContext.createMediaStreamSource(stream); // 创建处理器节点,缓冲区大小可选256/512/1024/2048/4096/8192/16384 const processor = audioContext.createScriptProcessor(4096, 1, 1); source.connect(processor); processor.connect(audioContext.destination); // 实时获取原始PCM数据 processor.onaudioprocess = function(e) { const inputData = e.inputBuffer.getChannelData(0); // inputData为Float32Array,取值范围[-1, 1],即原始音频采样数据 console.log('原始音频数据长度:', inputData.length); // 可在此处执行数据存储、分析等操作 }; stream.oninactive = function() { console.log('Stream ended'); audioContext.close(); }; });
方案二:使用AudioWorklet(现代标准,推荐)
AudioWorklet是Web Audio API的现代替代方案,性能更优,适合长期维护的项目。
代码示例
- 创建单独的AudioWorklet处理器文件(如
audio-processor.js):
class RawAudioProcessor extends AudioWorkletProcessor { process(inputs, outputs, parameters) { const inputData = inputs[0][0]; if (inputData) { // 将原始数据发送到主线程 this.port.postMessage(inputData); } return true; } } registerProcessor('raw-audio-processor', RawAudioProcessor);
- 主线程代码修改:
document.querySelector("#micTest").addEventListener('click', async function(){ if ( window.stream ) { this.innerText = "Test Microphone"; window.audioContext?.close(); window.stream.getAudioTracks().forEach(track => track.stop()); window.stream = null; return; } else { this.innerText = "Stop Test"; } if (!navigator.mediaDevices) throw new Error('unsupported mediaDevices'); await navigator.mediaDevices.getUserMedia({ audio: true, video: false }); const devices = await navigator.mediaDevices.enumerateDevices(); const devStr = devices.map((d) => `${d.deviceId}:${d.label}`).join('\n'); const deviceId = window.prompt(`choose device: ${devStr}`); if (!deviceId) return; const constraints = { audio: { deviceId }, video: false }; const stream = await navigator.mediaDevices.getUserMedia(constraints); window.stream = stream; // 保留原音频播放功能 const audio = document.createElement('audio'); audio.controls = true; audio.autoplay = true; audio.srcObject = stream; // 初始化AudioWorklet const audioContext = new (window.AudioContext || window.webkitAudioContext)(); window.audioContext = audioContext; await audioContext.audioWorklet.addModule('audio-processor.js'); const source = audioContext.createMediaStreamSource(stream); const processor = new AudioWorkletNode(audioContext, 'raw-audio-processor'); source.connect(processor); processor.connect(audioContext.destination); // 接收来自Worklet的原始数据 processor.port.onmessage = function(e) { const rawData = e.data; // Float32Array格式的PCM数据 console.log('原始音频数据长度:', rawData.length); // 执行数据处理逻辑 }; stream.oninactive = function() { console.log('Stream ended'); audioContext.close(); }; });
注意事项
- 两种方案获取的均为PCM原始采样数据(Float32Array,取值范围[-1, 1]),可根据需求自行编码为WAV等格式。
- Safari环境下使用AudioWorklet需确保页面为HTTPS协议(本地localhost开发除外)。
内容的提问来源于stack exchange,提问作者Mikael Finstad
相关产品推荐
相关产品推荐

