1/** 2 * 管理当前歌曲的歌词 3 */ 4 5import { isSameMediaItem } from "@/utils/mediaItem"; 6import PluginManager from "./pluginManager"; 7import LyricParser, { IParsedLrcItem } from "@/utils/lrcParser"; 8import { GlobalState } from "@/utils/stateMapper"; 9import { EDeviceEvents } from "@/constants/commonConst"; 10import { DeviceEventEmitter } from "react-native"; 11import Config from "./config.ts"; 12import LyricUtil from "@/native/lyricUtil"; 13import TrackPlayer from "./trackPlayer"; 14import MediaExtra from "./mediaExtra"; 15import minDistance from "@/utils/minDistance"; 16 17interface ILyricState { 18 loading: boolean; 19 lyricParser?: LyricParser; 20 lyrics: IParsedLrcItem[]; 21 meta?: Record<string, string>; 22 hasTranslation: boolean; 23} 24 25const defaultLyricState = { 26 loading: true, 27 lyrics: [], 28 hasTranslation: false 29}; 30 31const lyricStateStore = new GlobalState<ILyricState>(defaultLyricState); 32const currentLyricStore = new GlobalState<IParsedLrcItem | null>(null); 33 34function resetLyricState() { 35 lyricStateStore.setValue(defaultLyricState); 36} 37 38// 重新获取歌词 39async function refreshLyric(fromStart?: boolean, forceRequest = false) { 40 const musicItem = TrackPlayer.getCurrentMusic(); 41 try { 42 if (!musicItem) { 43 lyricStateStore.setValue({ 44 loading: false, 45 lyrics: [], 46 hasTranslation: false 47 }); 48 49 currentLyricStore.setValue({ 50 index: 0, 51 lrc: "MusicFree", 52 time: 0 53 }); 54 55 return; 56 } 57 58 const currentParserMusicItem = 59 lyricStateStore.getValue()?.lyricParser?.musicItem; 60 61 let lrcSource: ILyric.ILyricSource | null | undefined; 62 if ( 63 forceRequest || 64 !isSameMediaItem(currentParserMusicItem, musicItem) 65 ) { 66 resetLyricState(); 67 currentLyricStore.setValue(null); 68 69 lrcSource = await PluginManager.getByMedia( 70 musicItem 71 )?.methods?.getLyric(musicItem); 72 } else { 73 lrcSource = lyricStateStore.getValue().lyricParser?.lyricSource; 74 } 75 76 if (!lrcSource && Config.getConfig("lyric.autoSearchLyric")) { 77 const keyword = musicItem.alias || musicItem.title; 78 const plugins = PluginManager.getSearchablePlugins("lyric"); 79 80 let distance = Infinity; 81 let minDistanceMusicItem; 82 let targetPlugin; 83 84 for (let plugin of plugins) { 85 const realtimeMusicItem = TrackPlayer.getCurrentMusic(); 86 if ( 87 !isSameMediaItem(musicItem, realtimeMusicItem) || 88 plugin.name === musicItem.platform 89 ) { 90 return; 91 } 92 const results = await plugin.methods 93 .search(keyword, 1, "lyric") 94 .catch(() => null); 95 96 // 取前两个 97 const firstTwo = results?.data?.slice(0, 2) || []; 98 99 for (let item of firstTwo) { 100 if ( 101 item.title === keyword && 102 item.artist === musicItem.artist 103 ) { 104 distance = 0; 105 minDistanceMusicItem = item; 106 targetPlugin = plugin; 107 break; 108 } else { 109 const dist = 110 minDistance(keyword, musicItem.title) + 111 minDistance(item.artist, musicItem.artist); 112 if (dist < distance) { 113 distance = dist; 114 minDistanceMusicItem = item; 115 targetPlugin = plugin; 116 } 117 } 118 } 119 120 if (distance === 0) { 121 break; 122 } 123 } 124 if (minDistanceMusicItem && targetPlugin) { 125 lrcSource = await targetPlugin.methods 126 .getLyric(minDistanceMusicItem) 127 .catch(() => null); 128 } 129 } 130 131 const realtimeMusicItem = TrackPlayer.getCurrentMusic(); 132 if (isSameMediaItem(musicItem, realtimeMusicItem)) { 133 if (lrcSource) { 134 const mediaExtra = MediaExtra.get(musicItem); 135 const parser = new LyricParser(lrcSource.rawLrc!, { 136 extra: { 137 offset: (mediaExtra?.lyricOffset || 0) * -1 138 }, 139 musicItem: musicItem, 140 lyricSource: lrcSource, 141 translation: lrcSource.translation 142 }); 143 144 lyricStateStore.setValue({ 145 loading: false, 146 lyricParser: parser, 147 lyrics: parser.getLyricItems(), 148 hasTranslation: parser.hasTranslation, 149 meta: parser.getMeta() 150 }); 151 // 更新当前状态的歌词 152 const currentLyric = fromStart 153 ? parser.getLyricItems()[0] 154 : parser.getPosition( 155 (await TrackPlayer.getProgress()).position 156 ); 157 currentLyricStore.setValue(currentLyric || null); 158 } else { 159 // 没有歌词 160 lyricStateStore.setValue({ 161 loading: false, 162 lyrics: [], 163 hasTranslation: false 164 }); 165 } 166 } 167 } catch (e) { 168 console.log(e, "LRC"); 169 const realtimeMusicItem = TrackPlayer.getCurrentMusic(); 170 if (isSameMediaItem(musicItem, realtimeMusicItem)) { 171 // 异常情况 172 lyricStateStore.setValue({ 173 loading: false, 174 lyrics: [], 175 hasTranslation: false 176 }); 177 } 178 } 179} 180 181// 获取歌词 182async function setup() { 183 DeviceEventEmitter.addListener(EDeviceEvents.REFRESH_LYRIC, refreshLyric); 184 185 if (Config.getConfig("lyric.showStatusBarLyric")) { 186 187 const statusBarLyricConfig = { 188 topPercent: Config.getConfig("lyric.topPercent"), 189 leftPercent: Config.getConfig("lyric.leftPercent"), 190 align: Config.getConfig("lyric.align"), 191 color: Config.getConfig("lyric.color"), 192 backgroundColor: Config.getConfig("lyric.backgroundColor"), 193 widthPercent: Config.getConfig("lyric.widthPercent"), 194 fontSize: Config.getConfig("lyric.fontSize") 195 }; 196 LyricUtil.showStatusBarLyric( 197 "MusicFree", 198 statusBarLyricConfig ?? {} 199 ); 200 } 201 202 refreshLyric(); 203} 204 205const LyricManager = { 206 setup, 207 useLyricState: lyricStateStore.useValue, 208 getLyricState: lyricStateStore.getValue, 209 useCurrentLyric: currentLyricStore.useValue, 210 getCurrentLyric: currentLyricStore.getValue, 211 setCurrentLyric: currentLyricStore.setValue, 212 refreshLyric, 213 setLyricLoading: refreshLyric 214}; 215 216export default LyricManager; 217