xref: /MusicFree/src/core/lyricManager.ts (revision 41ddce918e1138d8f16e522cc7c19ac86ceca698)
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