如何使用JavaScript在移动端Safari及全浏览器实现音频录制
适配移动端浏览器的音频录制方案
我明白你遇到的糟心问题——桌面Chrome跑的好好的录制代码,到了iPhone的Safari/Chrome就直接罢工,这主要是移动端浏览器在媒体API支持、权限要求和编码格式上有不少差异。下面我给你梳理问题点,再给出修改后的兼容代码:
核心问题分析
- 用户交互强制要求:iOS浏览器(包括Safari和Chrome)严格要求媒体权限请求(
getUserMedia)必须由用户主动触发(比如点击按钮),原代码页面加载时就直接调用,会被浏览器无情拒绝权限。 - MediaRecorder兼容性:iOS Safari在iOS 14.5之后才支持标准
MediaRecorder,而且只支持部分音频编码格式,原代码用的audio/ogg在iOS上完全不被支持。 - API前缀兼容:部分旧版移动端浏览器可能需要
webkit前缀的API(比如navigator.webkitMediaDevices)。
修改后的兼容代码
'use strict'; const log = console.log.bind(console); const id = val => document.getElementById(val); const ul = id('ul'); const gUMbtn = id('gUMbtn'); const start = id('start'); const stop = id('stop'); let stream, recorder, counter = 1, chunks; // 适配不同浏览器支持的音频格式,优先选iOS支持的audio/mp4(AAC编码) const getSupportedAudioFormat = () => { if (MediaRecorder.isTypeSupported('audio/mp4')) { return { tag: 'audio', type: 'audio/mp4', ext: '.m4a' }; } else if (MediaRecorder.isTypeSupported('audio/wav')) { return { tag: 'audio', type: 'audio/wav', ext: '.wav' }; } else { return { tag: 'audio', type: 'audio/ogg', ext: '.ogg' }; } }; let media = getSupportedAudioFormat(); // 初始化权限请求按钮事件(必须由用户点击触发,符合iOS要求) gUMbtn.onclick = async () => { try { // 兼容不同浏览器的媒体设备API const mediaDevices = navigator.mediaDevices || navigator.webkitMediaDevices; if (!mediaDevices) { alert('当前浏览器不支持音频录制功能'); return; } // 请求音频权限 stream = await mediaDevices.getUserMedia({ audio: true }); // 显示控制按钮 id('btns').style.display = 'inherit'; start.removeAttribute('disabled'); gUMbtn.disabled = true; // 初始化MediaRecorder,使用兼容的格式 recorder = new MediaRecorder(stream, { mimeType: media.type }); recorder.ondataavailable = (e) => { chunks.push(e.data); if (recorder.state === 'inactive') { const blob = new Blob(chunks, { type: media.type }); const url = URL.createObjectURL(blob); // 创建预览和下载链接 makeLink(url); // 上传录制的音频 uploadAudio(blob); } }; recorder.onstop = () => { // 停止录制后释放媒体流,避免资源占用 stream.getTracks().forEach(track => track.stop()); }; } catch (err) { log('获取媒体权限失败:', err); alert('无法获取音频权限,请检查浏览器设置'); } }; start.onclick = () => { start.disabled = true; stop.removeAttribute('disabled'); chunks = []; recorder.start(); }; stop.onclick = () => { stop.disabled = true; recorder.stop(); start.removeAttribute('disabled'); }; // 创建预览和下载元素 function makeLink(url) { const li = document.createElement('li'); const mt = document.createElement(media.tag); const hf = document.createElement('a'); mt.controls = true; mt.src = url; hf.href = url; hf.download = `${counter++}${media.ext}`; hf.textContent = `${hf.download}`; li.appendChild(mt); li.appendChild(hf); ul.appendChild(li); } // 上传音频的函数,优化了错误处理 async function uploadAudio(blob) { try { const reader = new FileReader(); reader.onload = () => { const url = "http://localhost:8080/rest/api/v1/audio/submit"; const payload = JSON.stringify({ recording: reader.result }); const xhttp = new XMLHttpRequest(); xhttp.open("POST", url, true); xhttp.setRequestHeader('Content-Type', 'application/json'); xhttp.onload = () => { if (xhttp.status >= 200 && xhttp.status < 300) { log('音频上传成功'); } else { log('音频上传失败:', xhttp.statusText); } }; xhttp.onerror = () => log('上传请求出错'); xhttp.send(payload); }; reader.readAsDataURL(blob); } catch (err) { log('上传处理失败:', err); } }
关键修改点说明
- 用户交互触发权限请求:把
getUserMedia的调用放到了gUMbtn的点击事件里,完美符合iOS浏览器的权限要求。 - 动态选择支持的音频格式:通过
MediaRecorder.isTypeSupported检测浏览器支持的格式,优先选择iOS支持的audio/mp4,确保录制的音频能在移动端正常播放和处理。 - API前缀兼容:增加了对
navigator.webkitMediaDevices的判断,适配旧版移动端浏览器。 - 媒体流释放:在录制停止后主动停止媒体轨道,避免不必要的资源占用。
- 错误处理优化:增加了更详细的错误提示和捕获,方便调试问题。
- 代码结构优化:把上传逻辑抽成独立函数,让代码更清晰易维护。
额外注意事项
- iOS上的Chrome是基于Safari内核的,所以它的媒体API限制和Safari完全一致,不需要单独区分。
- 确保你的页面是HTTPS协议(localhost除外),因为现代浏览器要求媒体API必须在安全上下文下运行。
- 如果需要兼容iOS 14.5以下的旧版本,可能需要使用第三方库(比如
recorder.js)做降级处理,因为这些版本不支持标准MediaRecorder。
内容的提问来源于stack exchange,提问作者M.Mendix
相关产品推荐
相关产品推荐

