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