You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Safari环境下使用Axios录制发送音频至Laravel后端异常排查

音频录制提交至Laravel后端:Safari浏览器音频文件缺失问题排查与解决

问题概述

开发的音频录制并上传至Laravel后端功能,在Chrome、Firefox中运行正常,但在Safari浏览器提交表单后,后端接收到的音频文件为空。

相关代码

音频录制代码

const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
const recorder = new MediaRecorder(stream)
const chunks = []

recorder.addEventListener('dataavailable', (event) => {
  chunks.push(event.data)
})

// 注:原代码未展示recorder.start()和stop()逻辑,这可能是关键遗漏点
const audioBlob = new Blob(chunks, { type: 'audio/webm' })
const timestamp = new Date().getTime()
const filename = `audio_${timestamp}.wav`
const audioFile = new File([audioBlob], filename, { type: 'audio/wav' })

表单发送代码

const formData = new FormData()
formData.append('audio', audioFile, filename)

console.log('formData:', formData.getAll('audio'))
// 控制台输出:File {name: "audio_1686845222667.wav", lastModified: 1686845222667, webkitRelativePath: "", size: 1758, type: "audio/wav", …}

const config = {
  headers: {
    //'Content-Type': 'multipart/form-data',
    'Content-Type': 'audio/wav',
  },
}

const response = await axios.post('/recordaudio', formData, config)

请求内容对比

Safari中异常的请求内容

------WebKitFormBoundaryAOTnIX35RdvABYSP
Content-Disposition: form-data; name="audio"; filename="audio_1686845222667.wav"
Content-Type: audio/wav


------WebKitFormBoundaryAOTnIX35RdvABYSP--

Firefox中正常的请求内容

-----------------------------285208455512739247982911496103
Content-Disposition: form-data; name="audio"; filename="audio_1686846593105.wav"
Content-Type: audio/wav

OggS���������wb����mSsOpusHead8»�����OggS����������wb���ü*c9OpusTags���libopus unknown������ENCODER=Mozilla110.0.1OggS��@8������wb���!áµÿÿÿ3ÿ2ÿ1ÿ0ÿ4ÿ2ÿ3ÿ4ÿ4ÿ3ÿ5ÿ3øÿþøÿþøpêÀp¯qXºàm
æÐ9
pDëÀQ¨>øåYÜÙî󥳤Âo70³AqlÂÕþIëRÎ¥SÊTo_ÉVºa·þ}v2Iò5åíPDð&^Øu¼ë)âEÝ æÂ¡)R `ìÆ<׺r׿®%ìát/¼xÓ§uîh£¼nÅÕÕNÄD0QD4ò$sEVæûÖâIyU7(
»~x3ôöo)'3æi);r&    6û?á_ÏâË aÕÇ9è2 7â���������������������������������������������������������������������������������������������"T   õ3¿úRõ?Á!ýÏÐ{üQçüQ8µÙ%!§¼¸K�L±Y¢¿Ül¨#S°úSN§6®Þ,à\*&ñ´&
ÅØ/Öè:uñRÇB;J³  
...
-----------------------------285208455512739247982911496103--

已尝试方案

更换过不同文件类型及Content-Type值(multipart/form-data、audio/wav),问题未解决。

问题分析与解决方案

1. 错误的Content-Type设置

手动设置Content-Type: audio/wav会破坏FormData的自动编码逻辑,尤其是Safari对multipart/form-data的边界(boundary)处理更严格。浏览器在提交FormData时会自动生成包含boundary的multipart/form-data头部,手动设置会丢失关键的boundary信息,导致后端无法解析出文件内容。

修复:
移除手动设置的Content-Type,让axios自动处理头部:

const response = await axios.post('/recordaudio', formData)

2. MediaRecorder录制流程不完整

原代码未展示recorder.start()和recorder.stop()的调用逻辑,若未正确触发录制流程,chunks数组会为空,生成的Blob自然没有内容。此外,Safari对MediaRecorder的格式支持与Chrome/Firefox不同:

  • Chrome/Firefox支持audio/webm格式
  • Safari仅支持audio/mp4(AAC编码)格式

修复:
完善录制流程,并适配Safari的格式要求:

const stream = await navigator.mediaDevices.getUserMedia({ audio: true })

// 检测浏览器支持的MediaRecorder格式
const supportedMimeType = MediaRecorder.isTypeSupported('audio/mp4') 
  ? 'audio/mp4' 
  : 'audio/webm';

const recorder = new MediaRecorder(stream, { mimeType: supportedMimeType })
const chunks = []

recorder.addEventListener('dataavailable', (event) => {
  if (event.data.size > 0) { // 只添加非空数据块
    chunks.push(event.data)
  }
})

// 启动录制
recorder.start()

// 示例:录制5秒后停止,实际需根据业务逻辑触发stop()
setTimeout(() => {
  recorder.stop()
}, 5000)

// 等待录制结束后生成文件并提交
recorder.addEventListener('stop', async () => {
  if (chunks.length === 0) {
    console.error('录制内容为空')
    return
  }

  const audioBlob = new Blob(chunks, { type: supportedMimeType })
  const timestamp = new Date().getTime()
  // 根据格式设置正确的文件名后缀
  const ext = supportedMimeType === 'audio/mp4' ? 'm4a' : 'webm'
  const filename = `audio_${timestamp}.${ext}`
  const audioFile = new File([audioBlob], filename, { type: supportedMimeType })

  const formData = new FormData()
  formData.append('audio', audioFile, filename)

  const response = await axios.post('/recordaudio', formData)
})

3. 验证Blob有效性

Safari中直接将Blob转为File时,若Blob为空或格式不兼容,可能生成无效文件。需确保生成Blob后先验证其大小:

const audioBlob = new Blob(chunks, { type: supportedMimeType })
if (audioBlob.size === 0) {
  console.error('生成的音频Blob为空')
  return
}

4. Laravel后端接收适配

确保Laravel后端的路由和控制器正确处理multipart/form-data请求,示例控制器代码:

public function store(Request $request)
{
    if (!$request->hasFile('audio')) {
        return response()->json(['error' => '未接收到音频文件'], 400);
    }

    $audioFile = $request->file('audio');
    // 验证文件类型
    $validated = $request->validate([
        'audio' => 'required|file|mimes:webm,m4a,wav',
    ]);

    // 保存文件逻辑
    $path = $audioFile->store('audio', 'public');
    return response()->json(['path' => $path]);
}

内容的提问来源于stack exchange,提问作者01011001

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 02:54:54