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