音频时长需在loadedmetadata事件后获取,因初始duration为NaN;应使用URL.createObjectURL创建blob URL并监听该事件,避免跨域、主线程阻塞及内存泄漏问题。

上传音频文件后 duration 一直是 NaN?
直接读 audio.duration 肯定是 NaN,因为浏览器还没加载元数据。这不是代码写错了,而是音频资源必须触发 loadedmetadata(或 canplaythrough)事件后,duration 才会变成有效数值。
常见错误现象:用户选完文件立刻 console.log(audio.duration) → 输出 NaN;或者用 new Audio(fileUrl) 但没监听事件就取值。
- 必须用
URL.createObjectURL(file)把File转成可播放的 blob URL,不能直接传后端返回的地址(可能跨域或未就绪) - 推荐监听
loadedmetadata:它比canplaythrough更早触发,只等元数据(含时长),不等全部音频数据加载完,更快更轻量 - 别在
load或onload上取值——Audio对象没有这个事件
用 document.createElement('audio') 动态计算时长
这是最可控、不依赖 DOM 渲染的方式,适合纯 JS 处理上传流程。
实操要点:
立即学习“前端免费学习笔记(深入)”;
- 创建 audio 元素后立即设置
src为 blob URL,否则事件监听无效 - 必须在设置
src后再绑定loadedmetadata,顺序错会导致漏事件 - 记得在回调里调用
URL.revokeObjectURL()防止内存泄漏 - 如果用户反复上传,每次都要新建 audio 实例,复用旧实例可能残留状态
简短示例:
function getAudioDuration(file) {
return new Promise((resolve, reject) => {
const audio = document.createElement('audio');
const url = URL.createObjectURL(file);
audio.src = url;
audio.addEventListener('loadedmetadata', () => {
resolve(audio.duration);
URL.revokeObjectURL(url);
audio.remove();
});
audio.addEventListener('error', () => {
reject(new Error('无法解析音频文件'));
URL.revokeObjectURL(url);
audio.remove();
});
});
}
// 使用
inputElement.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (file && file.type.startsWith('audio/')) {
try {
const duration = await getAudioDuration(file);
console.log('时长(秒):', Math.round(duration));
// 此处可提交 duration 到后端
} catch (err) {
console.error(err.message);
}
}
});
为什么不用 <input type="file"> + <audio controls> 直接显示?
能显示,但不适合「上传并自动计算」这个动作。原因很实际:
-
<audio>标签加controls是给用户操作用的,不是为程序提取数据设计的;你得等用户点播放才触发元数据加载,无法保证自动执行 - 如果只加
preload="metadata"不加controls,部分浏览器(尤其是 Safari)可能根本不加载元数据,loadedmetadata永远不触发 - 动态插入的
<audio>如果没加controls且不在可视区域,Chrome 可能降级加载策略,导致duration延迟甚至失败
所以,别为了“看起来有播放器”而牺牲稳定性。后台静默计算,结果拿到再决定是否渲染控件,更可靠。
移动端 iOS Safari 特别容易卡在哪?
不是时长算不出来,而是 URL.createObjectURL(file) 在某些 iOS 版本(尤其是 iOS 16.5–17.2)对大音频文件(>50MB)会同步阻塞主线程,导致页面卡死几秒,loadedmetadata 延迟触发甚至超时。
应对方式很具体:
- 上传前先用
file.size检查,超过 30MB 就提示“文件过大,请压缩” - 避免在
change回调里直接处理,改用setTimeout(..., 0)让出主线程 - 不要在同一个函数里连续创建多个
Audio实例——iOS 对 blob URL 的 GC 很慢,容易内存溢出
真正难的不是“怎么取 duration”,而是确保它在各种设备上都稳定、不卡、不报错。多数问题出在资源生命周期管理,而不是 API 调用本身。



















