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

WordPress前端录音上传后生成0字节文件问题求助

问题分析与解决方案

核心原因:0字节文件的根源

1. 重复绑定停止录制事件导致数据未收集

你的JS代码中给#stopRecord按钮绑定了两次click事件:

  • 第一次事件调用mediaRecorder.stop()并更新按钮状态;
  • 第二次事件再次调用mediaRecorder.stop(),并立即尝试用audioChunks创建Blob上传。

MediaRecorder.stop()是异步操作:调用后浏览器会先触发ondataavailable返回最后一段录音数据,再触发onstop回调。第二次事件中,你在调用stop()后立刻创建Blob,此时audioChunks还没来得及收集到录音数据,生成的Blob自然是0字节。

2. Blob类型与录制格式不匹配

录制时你通过MediaRecorder.isTypeSupported判断使用audio/mp3或audio/webm,但上传时硬编码了{ type: 'audio/mp3' }创建Blob。如果浏览器不支持MP3(比如Firefox默认只支持WebM),Blob类型与实际录制数据不匹配,会导致文件损坏或无法识别。

3. 未等待录制完成就执行上传

即使只绑定一次事件,在stop点击事件里直接上传也是不安全的,必须等待MediaRecorder.onstop回调确认所有数据块已收集完成后再处理。


修复后的代码示例

JavaScript(audio.js)

let mediaRecorder;
let audioChunks = [];
let currentMimeType;

navigator.mediaDevices.getUserMedia({ audio: true })
  .then(stream => {
    // 统一保存录制使用的MIME类型
    currentMimeType = MediaRecorder.isTypeSupported('audio/mp3') ? 'audio/mp3' : 'audio/webm';
    mediaRecorder = new MediaRecorder(stream, { mimeType: currentMimeType });

    mediaRecorder.ondataavailable = event => {
      audioChunks.push(event.data);
    };

    mediaRecorder.onstop = () => {
      const audioBlob = new Blob(audioChunks, { type: currentMimeType });
      const audioUrl = URL.createObjectURL(audioBlob);
      document.querySelector("#audioPreview").src = audioUrl;
      
      // 录制完成后触发上传逻辑
      uploadAudio(audioBlob);
      
      // 重置数据块
      audioChunks = [];
    };
  });

document.addEventListener('DOMContentLoaded', (event) => {
  // 录制开始事件
  document.querySelector("#startRecord").addEventListener("click", () => {
    mediaRecorder.start();
    document.querySelector("#startRecord").disabled = true;
    document.querySelector("#stopRecord").disabled = false;
    audioChunks = [];
  });

  // 仅绑定一次停止录制事件
  document.querySelector("#stopRecord").addEventListener("click", () => {
    mediaRecorder.stop();
    document.querySelector("#startRecord").disabled = false;
    document.querySelector("#stopRecord").disabled = true;
  });
});

// 生成带后缀的随机文件名
function generateFilename(postID) {
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let startRandomString = '';
  for (let i = 0; i < 3; i++) {
    startRandomString += characters.charAt(Math.floor(Math.random() * characters.length));
  }
  // 根据MIME类型添加后缀
  const extension = currentMimeType === 'audio/mp3' ? '.mp3' : '.webm';
  return `${startRandomString}_${postID}${extension}`;
}

// 独立的上传函数
function uploadAudio(audioBlob) {
  const postID = document.querySelector("#startRecord").getAttribute("data-postid");
  const formData = new FormData();
  formData.append('action', 'upload_audio');
  formData.append('audioFile', audioBlob, generateFilename(postID));
  
  fetch(AudioRecorderAjax.url, {
    method: 'POST',
    body: formData,
  })
  .then(response => response.text())
  .then(data => console.log(data))
  .catch(error => console.error(error));
}

PHP(functions.php)

$dirlib = 'lib0';

// 注册脚本并传递全局变量
function enqueue_audio_recorder_script() {
    global $dirlib;
    // 获取当前文章ID
    $post_id = get_the_ID();
    
    wp_enqueue_script('audio-recorder', get_stylesheet_directory_uri() . '/'.$dirlib.'/audio.js', array(), null, true);

    wp_localize_script('audio-recorder', 'AudioRecorderAjax', array(
        'url' => admin_url('admin-post.php'),
        'postID' => $post_id 
    ));
}
add_action('wp_enqueue_scripts', 'enqueue_audio_recorder_script');

// 短代码
function audioupload_shortcode() {
    $post_id = get_the_ID() ?: 0;
    $html = '<button id="startRecord" data-postid="'.esc_attr($post_id).'">Iniciar Grabación</button>
             <button id="stopRecord" disabled>Detener Grabación</button>
             <audio id="audioPreview" controls></audio>';
    return $html;
}
add_shortcode('audioupload', 'audioupload_shortcode');

// 音频上传处理
function handle_audio_upload() {
    // 检查文件是否存在且无错误
    if (!isset($_FILES['audioFile']) || $_FILES['audioFile']['error'] !== UPLOAD_ERR_OK) {
        echo 'upload_error';
        wp_die();
    }

    $upload_dir = wp_upload_dir();
    $filename = basename($_FILES['audioFile']['name']);
    $upload_path = trailingslashit($upload_dir['path']) . $filename;

    // 使用WordPress内置函数处理上传更安全
    if (wp_handle_upload($_FILES['audioFile'], array('test_form' => false))) {
        echo 'success';
    } else {
        echo 'move_error';
    }
    wp_die(); 
}

add_action('admin_post_nopriv_upload_audio', 'handle_audio_upload'); 
add_action('admin_post_upload_audio', 'handle_audio_upload'); 

WordPress + JavaScript 常见陷阱

  1. 事件重复绑定:如果脚本被多次加载(比如短代码多次调用),会导致事件重复绑定,触发多次操作,像你遇到的重复stop调用就是典型案例。可以用removeEventListener或者事件委托避免。

  2. 异步操作时序问题:MediaRecorder、fetch等都是异步操作,必须等待回调完成再执行后续逻辑,不能同步调用。

  3. WordPress全局变量传递错误:wp_localize_script中的变量必须在函数内正确定义(比如$post_id要通过get_the_ID()获取,而不是直接用全局$post,因为在wp_enqueue_scripts钩子中全局$post可能未初始化)。

  4. 文件上传安全问题:直接使用move_uploaded_file不如WordPress内置的wp_handle_upload安全,后者会自动处理权限、文件类型验证等。

  5. MIME类型兼容性:不同浏览器对MediaRecorder支持的音频格式不同,不要硬编码格式,必须动态判断并统一使用。

  6. 脚本加载时机:确保脚本在DOM加载完成后再绑定事件,或者使用DOMContentLoaded回调,避免找不到DOM元素。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 19:44:53