xref: /MusicFree/src/core/lyricManager.ts (revision eea2f34f4c3284a4b0fcee0326ae6a3b9c7272ee)
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';
15
16const lyricStateStore = new GlobalState<{
17    loading: boolean;
18    lyricParser?: LyricParser;
19    lyrics: ILyric.IParsedLrc;
20    translationLyrics?: ILyric.IParsedLrc;
21    meta?: Record<string, string>;
22    hasTranslation: boolean;
23}>({
24    loading: true,
25    lyrics: [],
26    hasTranslation: false,
27});
28
29const currentLyricStore = new GlobalState<ILyric.IParsedLrcItem | null>(null);
30const loadingState = {
31    loading: true,
32    lyrics: [],
33    hasTranslation: false,
34};
35
36function setLyricLoading() {
37    lyricStateStore.setValue(loadingState);
38}
39
40// 重新获取歌词
41async function refreshLyric(fromStart?: boolean, forceRequest = false) {
42    const musicItem = TrackPlayer.getCurrentMusic();
43    try {
44        if (!musicItem) {
45            lyricStateStore.setValue({
46                loading: false,
47                lyrics: [],
48                hasTranslation: false,
49            });
50
51            currentLyricStore.setValue({
52                lrc: 'MusicFree',
53                time: 0,
54            });
55
56            return;
57        }
58
59        const currentParserMusicItem = lyricStateStore
60            .getValue()
61            ?.lyricParser?.getCurrentMusicItem();
62
63        let lrcSource: ILyric.ILyricSource | null | undefined;
64        if (
65            forceRequest ||
66            !isSameMediaItem(currentParserMusicItem, musicItem)
67        ) {
68            lyricStateStore.setValue(loadingState);
69            currentLyricStore.setValue(null);
70
71            lrcSource = await PluginManager.getByMedia(
72                musicItem,
73            )?.methods?.getLyric(musicItem);
74        } else {
75            lrcSource = lyricStateStore.getValue().lyricParser!.lrcSource;
76        }
77
78        if (!lrcSource && Config.get('setting.lyric.autoSearchLyric')) {
79            const keyword = musicItem.alias || musicItem.title;
80            const plugins = PluginManager.getSearchablePlugins('lyric');
81
82            for (let plugin of plugins) {
83                const realtimeMusicItem = TrackPlayer.getCurrentMusic();
84                if (!isSameMediaItem(musicItem, realtimeMusicItem)) {
85                    return;
86                }
87                const results = await plugin.methods
88                    .search(keyword, 1, 'lyric')
89                    .catch(() => null);
90                if (results?.data[0]) {
91                    lrcSource = await plugin.methods
92                        .getLyric(results.data[0])
93                        .catch(() => null);
94                    if (lrcSource) {
95                        break;
96                    }
97                }
98            }
99        }
100
101        const realtimeMusicItem = TrackPlayer.getCurrentMusic();
102        if (isSameMediaItem(musicItem, realtimeMusicItem)) {
103            if (lrcSource) {
104                const mediaExtra = MediaExtra.get(musicItem);
105                const parser = new LyricParser(lrcSource, musicItem, {
106                    offset: (mediaExtra?.lyricOffset || 0) * -1,
107                });
108
109                lyricStateStore.setValue({
110                    loading: false,
111                    lyricParser: parser,
112                    lyrics: parser.getLyric(),
113                    translationLyrics: lrcSource.translation
114                        ? parser.getTranslationLyric()
115                        : undefined,
116                    meta: parser.getMeta(),
117                    hasTranslation: !!lrcSource.translation,
118                });
119                // 更新当前状态的歌词
120                const currentLyric = fromStart
121                    ? parser.getLyric()[0]
122                    : parser.getPosition(
123                          (await TrackPlayer.getProgress()).position,
124                      ).lrc;
125                currentLyricStore.setValue(currentLyric || null);
126            } else {
127                // 没有歌词
128                lyricStateStore.setValue({
129                    loading: false,
130                    lyrics: [],
131                    hasTranslation: false,
132                });
133            }
134        }
135    } catch (e) {
136        console.log(e, 'LRC');
137        const realtimeMusicItem = TrackPlayer.getCurrentMusic();
138        if (isSameMediaItem(musicItem, realtimeMusicItem)) {
139            // 异常情况
140            lyricStateStore.setValue({
141                loading: false,
142                lyrics: [],
143                hasTranslation: false,
144            });
145        }
146    }
147}
148
149// 获取歌词
150async function setup() {
151    DeviceEventEmitter.addListener(EDeviceEvents.REFRESH_LYRIC, refreshLyric);
152
153    if (Config.get('setting.lyric.showStatusBarLyric')) {
154        LyricUtil.showStatusBarLyric(
155            'MusicFree',
156            Config.get('setting.lyric') ?? {},
157        );
158    }
159
160    refreshLyric();
161}
162
163const LyricManager = {
164    setup,
165    useLyricState: lyricStateStore.useValue,
166    getLyricState: lyricStateStore.getValue,
167    useCurrentLyric: currentLyricStore.useValue,
168    getCurrentLyric: currentLyricStore.getValue,
169    setCurrentLyric: currentLyricStore.setValue,
170    refreshLyric,
171    setLyricLoading,
172};
173
174export default LyricManager;
175