1const timeReg = /\[[\d:.]+\]/g; 2const metaReg = /\[(.+)\:(.+)\]/g; 3 4export default class LyricParser { 5 private lastIndex: number = 0; 6 private lrcItems: Array<ILyric.IParsedLrcItem>; 7 private meta: Record<string, any>; 8 private currentMusicItem?: IMusic.IMusicItem; 9 10 constructor(raw: string, currentMusicItem?: IMusic.IMusicItem) { 11 raw = raw.trim(); 12 this.currentMusicItem = currentMusicItem; 13 const rawLrcItems: Array<ILyric.IParsedLrcItem> = []; 14 const rawLrcs = raw.split(timeReg) ?? []; 15 const rawTimes = raw.match(timeReg) ?? []; 16 const len = rawTimes.length; 17 18 this.meta = this.parseMeta(rawLrcs[0].trim()); 19 rawLrcs.shift(); 20 21 let counter = 0; 22 let j, lrc; 23 for (let i = 0; i < len; ++i) { 24 counter = 0; 25 while (rawLrcs[0] === '') { 26 ++counter; 27 rawLrcs.shift(); 28 } 29 lrc = rawLrcs[0]?.trim?.() ?? ''; 30 for (j = i; j < i + counter; ++j) { 31 rawLrcItems.push({ 32 time: this.parseTime(rawTimes[j]), 33 lrc, 34 }); 35 } 36 i += counter; 37 if (i < len) { 38 rawLrcItems.push({ 39 time: this.parseTime(rawTimes[i]), 40 lrc, 41 }); 42 } 43 rawLrcs.shift(); 44 } 45 this.lrcItems = rawLrcItems.sort((a, b) => a.time - b.time); 46 if (this.lrcItems.length === 0 && raw.length) { 47 this.lrcItems = raw.split('\n').map(_ => ({ 48 time: 0, 49 lrc: _, 50 })); 51 } 52 53 for (let i = 0; i < this.lrcItems.length; ++i) { 54 this.lrcItems[i].index = i; 55 } 56 } 57 58 getPosition(position: number): { 59 lrc?: ILyric.IParsedLrcItem; 60 index: number; 61 } { 62 position = position - (this.meta?.offset ?? 0); 63 let index; 64 /** 最前面 */ 65 if (!this.lrcItems[0] || position < this.lrcItems[0].time) { 66 this.lastIndex = 0; 67 return { 68 lrc: undefined, 69 index: -1, 70 }; 71 } 72 for ( 73 index = this.lastIndex; 74 index < this.lrcItems.length - 1; 75 ++index 76 ) { 77 if ( 78 position >= this.lrcItems[index].time && 79 position < this.lrcItems[index + 1].time 80 ) { 81 this.lastIndex = index; 82 return { 83 lrc: this.lrcItems[index], 84 index, 85 }; 86 } 87 } 88 89 for (index = 0; index < this.lastIndex; ++index) { 90 if ( 91 position >= this.lrcItems[index].time && 92 position < this.lrcItems[index + 1].time 93 ) { 94 this.lastIndex = index; 95 return { 96 lrc: this.lrcItems[index], 97 index, 98 }; 99 } 100 } 101 102 index = this.lrcItems.length - 1; 103 this.lastIndex = index; 104 return { 105 lrc: this.lrcItems[index], 106 index, 107 }; 108 } 109 110 getCurrentMusicItem() { 111 return this.currentMusicItem; 112 } 113 114 getLyric() { 115 return this.lrcItems; 116 } 117 118 getMeta() { 119 return this.meta; 120 } 121 122 parseMeta(metaStr: string) { 123 if (metaStr === '') { 124 return {}; 125 } 126 const metaArr = metaStr.match(metaReg) ?? []; 127 const meta: any = {}; 128 let k, v; 129 for (let m of metaArr) { 130 k = m.substring(1, m.indexOf(':')); 131 v = m.substring(k.length + 2, m.length - 1); 132 if (k === 'offset') { 133 meta[k] = +v / 1000; 134 } else { 135 meta[k] = v; 136 } 137 } 138 return meta; 139 } 140 141 /** [xx:xx.xx] => x s */ 142 parseTime(timeStr: string): number { 143 let result = 0; 144 const nums = timeStr.slice(1, timeStr.length - 1).split(':'); 145 for (let i = 0; i < nums.length; ++i) { 146 result = result * 60 + +nums[i]; 147 } 148 return result; 149 } 150} 151