/** * 获取音频时长秒- 混合方案 * 1. 优先二进制解析快速 * 2. 降级使用 InnerAudioContext准确 */ export async function getAudioDuration(filePath) { // 方法1二进制解析 try { const duration await parseByBinary(filePath); if (duration 0) return duration; } catch (e) { console.warn(二进制解析失败, e); } // 方法2降级方案 return parseByAudioContext(filePath); } // 二进制解析 function parseByBinary(filePath) { return new Promise((resolve, reject) { const fs uni.getFileSystemManager(); fs.readFile({ filePath, success: (res) { const uint8 new Uint8Array(res.data); const duration parseAudioDuration(uint8); if (duration 0) resolve(duration); else reject(new Error(二进制解析返回0)); }, fail: reject }); }); } // 降级方案InnerAudioContext 静音播放 function parseByAudioContext(filePath) { return new Promise((resolve) { const audio uni.createInnerAudioContext(); audio.src filePath; audio.volume 0; // 静音 let resolved false; let timer null; const cleanup () { if (timer) clearInterval(timer); audio.destroy(); }; audio.onCanplay(() { audio.play(); }); audio.onPlay(() { audio.pause(); // 立即暂停不发声 // 轮询 duration timer setInterval(() { const dur audio.duration; if (dur dur 0) { if (!resolved) { resolved true; cleanup(); resolve(dur); } } }, 100); }); audio.onError(() { if (!resolved) { resolved true; cleanup(); resolve(0); } }); // 10秒超时 setTimeout(() { if (!resolved) { resolved true; cleanup(); resolve(0); } }, 10000); }); } // 以下是原有的二进制解析函数 function parseAudioDuration(uint8) { // 判断格式 if (isMP3(uint8)) return parseMP3(uint8); if (isWAV(uint8)) return parseWAV(uint8); if (isM4A(uint8)) return parseM4A(uint8); return 0; } function isMP3(uint8) { return uint8[0] 0xff (uint8[1] 0xe0) 0xe0; } function isWAV(uint8) { return uint8[8] 0x57 uint8[9] 0x41 uint8[10] 0x56 uint8[11] 0x45; } function isM4A(uint8) { // M4A 文件以 ftyp box 开头 return uint8[4] 0x66 uint8[5] 0x74 uint8[6] 0x79 uint8[7] 0x70; } // 修正版 MP3 解析 function parseMP3(uint8) { try { let offset 0; // 跳过 ID3v2 标签 if (uint8[0] 0x49 uint8[1] 0x44 uint8[2] 0x33) { const tagSize ((uint8[6] 0x7f) 21) | ((uint8[7] 0x7f) 14) | ((uint8[8] 0x7f) 7) | (uint8[9] 0x7f); offset 10 tagSize; } // 寻找第一帧同步头 let frameHeader null; for (let i offset; i Math.min(uint8.length, offset 4096); i) { if (uint8[i] 0xff (uint8[i 1] 0xe0) 0xe0) { frameHeader (uint8[i] 24) | (uint8[i 1] 16) | (uint8[i 2] 8) | uint8[i 3]; break; } } if (!frameHeader) return 0; // 解析比特率索引 const bitrateIndex (frameHeader 12) 0x0f; const bitrates [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]; const bitrate bitrates[bitrateIndex] || 128; const fileSize uint8.length; return Math.round((fileSize * 8) / (bitrate * 1000)); } catch (e) { return 0; } } // 修正版 WAV 解析 function parseWAV(uint8) { try { let sampleRate 0, blockAlign 0, dataSize 0; let offset 12; // 跳过 RIFF...WAVE while (offset 8 uint8.length) { const chunkId String.fromCharCode(...uint8.slice(offset, offset 4)); const chunkSize uint8[offset 4] | (uint8[offset 5] 8) | (uint8[offset 6] 16) | (uint8[offset 7] 24); if (chunkId fmt ) { if (offset 24 uint8.length) { sampleRate uint8[offset 12] | (uint8[offset 13] 8) | (uint8[offset 14] 16) | (uint8[offset 15] 24); blockAlign uint8[offset 20] | (uint8[offset 21] 8); } } else if (chunkId data) { dataSize chunkSize; break; } offset 8 chunkSize; } if (sampleRate blockAlign dataSize) { return dataSize / (sampleRate * blockAlign); } return 0; } catch (e) { return 0; } } // AI-MODIFIED-START // 修改时间: 2026-04-29 17:30:00 // 修改工具: Cursor // 修改说明: 重写 M4A 解析支持嵌套 box 结构 /** * 读取大端序 32 位整数 */ function readUint32BE(uint8, offset) { return (uint8[offset] 24) | (uint8[offset 1] 16) | (uint8[offset 2] 8) | uint8[offset 3]; } /** * 读取大端序 64 位整数返回高 32 位足够表示时长 */ function readUint64BE(uint8, offset) { // 如果高 32 位为 0直接读低 32 位否则返回高 32 位 * 2^32 低 32 位可能溢出用 BigInt但这里用 Number 近似 const high readUint32BE(uint8, offset); const low readUint32BE(uint8, offset 4); if (high 0) return low; // 对于通常的时长high 不会太大这里用 Number 近似 return high * 0x100000000 low; } /** * 递归解析 M4A (MP4) box 结构查找 mvhd box * param {Uint8Array} uint8 文件数据 * param {number} offset 起始偏移 * param {number} end 结束偏移可选 * returns {Object|null} 包含 timescale 和 duration 的对象 */ function findMvhdBox(uint8, offset 0, end null) { if (end null) end uint8.length; let i offset; while (i 8 end) { // 读取 box 大小和类型 const boxSize readUint32BE(uint8, i); const boxType String.fromCharCode(...uint8.slice(i 4, i 8)); // 如果 boxSize 为 0 或 1需要特殊处理通常不会遇到 if (boxSize 0) break; // 文件结束标志 if (boxSize 1) { // 64 位大小少见跳过 i 16; continue; } // 检查是否为 mvhd box if (boxType mvhd) { // mvhd 结构: // - version (1 byte) // - flags (3 bytes) // - creation_time (4 bytes) // - modification_time (4 bytes) // - timescale (4 bytes) // - duration (4 or 8 bytes) const version uint8[i 8]; const timescale readUint32BE(uint8, i 20); let duration; if (version 1) { // 64 位 duration duration readUint64BE(uint8, i 24); } else { // 32 位 duration duration readUint32BE(uint8, i 24); } if (timescale 0 duration 0) { return { timescale, duration }; } return null; } // 如果是容器 boxmoov, trak, mdia 等递归查找 const containerTypes [moov, trak, mdia, minf, stbl]; if (containerTypes.includes(boxType)) { const result findMvhdBox(uint8, i 8, i boxSize); if (result) return result; } // 移动到下一个 box i boxSize; } return null; } /** * M4A 时长解析修正版 */ function parseM4A(uint8) { try { // 跳过 ftyp box前 8 字节是 size ftyp let offset 0; // 可选验证并跳过 ftyp if (offset 8 uint8.length) { const type String.fromCharCode(...uint8.slice(offset 4, offset 8)); if (type ftyp) { const ftypSize readUint32BE(uint8, offset); offset ftypSize; } } // 递归查找 mvhd box const mvhd findMvhdBox(uint8, offset); if (mvhd mvhd.timescale 0) { const durationSec mvhd.duration / mvhd.timescale; // 返回整数秒四舍五入 return Math.round(durationSec); } return 0; } catch (e) { console.warn(parseM4A error:, e); return 0; } } // AI-MODIFIED-END async processAudioInfo(formatted) { try { const rawDuration await getAudioDuration(formatted.url); const duration Number(rawDuration); // 如果解析失败但未抛错rawDuration 可能是 null/undefined/NaN if (!Number.isFinite(duration) || duration 0) { formatted.durationSeconds null; // 标记为无效 formatted.duration --; // 显示 -- return formatted; } formatted.durationSeconds duration; formatted.duration this.formatTime(duration); } catch (err) { // 解析失败显示 -- formatted.durationSeconds null; // 标记为无效 formatted.duration --; // 显示 -- } return formatted; }, // AI-MODIFIED-END