* 解析 LRC 文本为歌词对象数组 * @param {string} lrcText - LRC 格式的歌词文本 * @param {number} [timeOffset=0.5] - 时间偏移量(秒),用于调整高亮时机 * @returns {Array<{time: number, english: string, chinese: string, fullText: string}>}
(lrcText, timeOffset = 0.5)
| 19 | * @returns {Array<{time: number, english: string, chinese: string, fullText: string}>} |
| 20 | */ |
| 21 | static parse(lrcText, timeOffset = 0.5) { |
| 22 | if (!lrcText || typeof lrcText !== 'string') { |
| 23 | return []; |
| 24 | } |
| 25 | |
| 26 | const lines = lrcText.split('\n'); |
| 27 | const lyrics = []; |
| 28 | |
| 29 | for (const line of lines) { |
| 30 | // 跳过空行和注释 |
| 31 | if (!line.trim() || line.trim().startsWith('#')) { |
| 32 | continue; |
| 33 | } |
| 34 | |
| 35 | // 匹配 LRC 时间标签: [mm:ss.xx] 或 [mm:ss.xxx] |
| 36 | // 支持格式: [00:12.34]Content | [1:30.500]Content |
| 37 | const match = line.match(/\[(\d{1,2}):(\d{2})\.(\d{2,3})\](.+)/); |
| 38 | if (!match) { |
| 39 | continue; |
| 40 | } |
| 41 | |
| 42 | try { |
| 43 | const minutes = parseInt(match[1], 10); |
| 44 | const seconds = parseInt(match[2], 10); |
| 45 | const milliseconds = parseInt(match[3].padEnd(3, '0'), 10); |
| 46 | |
| 47 | // 确保秒数在有效范围内 |
| 48 | if (seconds > 59) continue; |
| 49 | |
| 50 | // 计算总时间(秒) |
| 51 | let time = minutes * 60 + seconds + milliseconds / 1000; |
| 52 | |
| 53 | // 应用时间偏移,使高亮提前出现 |
| 54 | time -= timeOffset; |
| 55 | |
| 56 | const text = match[4].trim(); |
| 57 | |
| 58 | // 使用 | 分隔英文和中文 |
| 59 | const parts = text.split('|').map(p => p.trim()); |
| 60 | const english = parts[0] || ''; |
| 61 | const chinese = parts[1] || ''; |
| 62 | |
| 63 | if (english) { |
| 64 | lyrics.push({ |
| 65 | time, |
| 66 | english, |
| 67 | chinese, |
| 68 | fullText: text, |
| 69 | }); |
| 70 | } |
| 71 | } catch (error) { |
| 72 | console.warn(`Failed to parse lyric line: ${line}`, error); |
| 73 | continue; |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | // 按时间排序 |
| 78 | return lyrics.sort((a, b) => a.time - b.time); |
no outgoing calls
no test coverage detected