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 常见陷阱
事件重复绑定:如果脚本被多次加载(比如短代码多次调用),会导致事件重复绑定,触发多次操作,像你遇到的重复stop调用就是典型案例。可以用
removeEventListener或者事件委托避免。异步操作时序问题:MediaRecorder、fetch等都是异步操作,必须等待回调完成再执行后续逻辑,不能同步调用。
WordPress全局变量传递错误:
wp_localize_script中的变量必须在函数内正确定义(比如$post_id要通过get_the_ID()获取,而不是直接用全局$post,因为在wp_enqueue_scripts钩子中全局$post可能未初始化)。文件上传安全问题:直接使用
move_uploaded_file不如WordPress内置的wp_handle_upload安全,后者会自动处理权限、文件类型验证等。MIME类型兼容性:不同浏览器对MediaRecorder支持的音频格式不同,不要硬编码格式,必须动态判断并统一使用。
脚本加载时机:确保脚本在DOM加载完成后再绑定事件,或者使用
DOMContentLoaded回调,避免找不到DOM元素。
内容的提问来源于stack exchange,提问作者kabbaltrapa

