xref: /MusicFree/src/core/lyricManager.ts (revision 410a159129b1f6a7a1f44fde7bfad9a46f91e161)
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';
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.get('setting.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.get('setting.lyric.showStatusBarLyric')) {
186        LyricUtil.showStatusBarLyric(
187            'MusicFree',
188            Config.get('setting.lyric') ?? {},
189        );
190    }
191
192    refreshLyric();
193}
194
195const LyricManager = {
196    setup,
197    useLyricState: lyricStateStore.useValue,
198    getLyricState: lyricStateStore.getValue,
199    useCurrentLyric: currentLyricStore.useValue,
200    getCurrentLyric: currentLyricStore.getValue,
201    setCurrentLyric: currentLyricStore.setValue,
202    refreshLyric,
203    setLyricLoading: refreshLyric,
204};
205
206export default LyricManager;
207