xref: /MusicFree/src/core/trackPlayer/index.ts (revision 1795823514ebd83a3e0f24980c9076bd96e6d14e)
1import {produce} from 'immer';
2import ReactNativeTrackPlayer, {
3    Event,
4    State,
5    Track,
6    TrackMetadataBase,
7    usePlaybackState,
8    useProgress,
9} from 'react-native-track-player';
10import shuffle from 'lodash.shuffle';
11import Config from '../config';
12import {
13    EDeviceEvents,
14    internalFakeSoundKey,
15    sortIndexSymbol,
16    timeStampSymbol,
17} from '@/constants/commonConst';
18import {GlobalState} from '@/utils/stateMapper';
19import delay from '@/utils/delay';
20import {
21    isSameMediaItem,
22    mergeProps,
23    sortByTimestampAndIndex,
24} from '@/utils/mediaItem';
25import Network from '../network';
26import LocalMusicSheet from '../localMusicSheet';
27import {SoundAsset} from '@/constants/assetsConst';
28import {getQualityOrder} from '@/utils/qualities';
29import musicHistory from '../musicHistory';
30import getUrlExt from '@/utils/getUrlExt';
31import {DeviceEventEmitter} from 'react-native';
32import LyricManager from '../lyricManager';
33import {MusicRepeatMode} from './common';
34import {
35    getMusicIndex,
36    getPlayList,
37    getPlayListMusicAt,
38    isInPlayList,
39    isPlayListEmpty,
40    setPlayList,
41    usePlayList,
42} from './internal/playList';
43import {createMediaIndexMap} from '@/utils/mediaIndexMap';
44import PluginManager from '../pluginManager';
45import {musicIsPaused} from '@/utils/trackUtils';
46import {errorLog, trace} from '@/utils/log';
47import PersistStatus from '../persistStatus';
48import {getCurrentDialog, showDialog} from '@/components/dialogs/useDialog';
49import getSimilarMusic from '@/utils/getSimilarMusic';
50
51/** 当前播放 */
52const currentMusicStore = new GlobalState<IMusic.IMusicItem | null>(null);
53
54/** 播放模式 */
55const repeatModeStore = new GlobalState<MusicRepeatMode>(MusicRepeatMode.QUEUE);
56
57/** 音质 */
58const qualityStore = new GlobalState<IMusic.IQualityKey>('standard');
59
60let currentIndex = -1;
61
62const maxMusicQueueLength = 10000; // 当前播放最大限制
63const halfMaxMusicQueueLength = Math.floor(maxMusicQueueLength / 2);
64const shrinkPlayListToSize = (
65    queue: IMusic.IMusicItem[],
66    targetIndex = currentIndex,
67) => {
68    // 播放列表上限,太多无法缓存状态
69    if (queue.length > maxMusicQueueLength) {
70        if (targetIndex < halfMaxMusicQueueLength) {
71            queue = queue.slice(0, maxMusicQueueLength);
72        } else {
73            const right = Math.min(
74                queue.length,
75                targetIndex + halfMaxMusicQueueLength,
76            );
77            const left = Math.max(0, right - maxMusicQueueLength);
78            queue = queue.slice(left, right);
79        }
80    }
81    return queue;
82};
83
84let hasSetupListener = false;
85
86// TODO: 删除
87function migrate() {
88    const config = Config.get('status.music');
89    if (!config) {
90        return;
91    }
92    const {rate, repeatMode, musicQueue, progress, track} = config;
93    PersistStatus.set('music.rate', rate);
94    PersistStatus.set('music.repeatMode', repeatMode);
95    PersistStatus.set('music.playList', musicQueue);
96    PersistStatus.set('music.progress', progress);
97    PersistStatus.set('music.musicItem', track);
98    Config.set('status.music', undefined);
99}
100
101async function setupTrackPlayer() {
102    migrate();
103
104    const rate = PersistStatus.get('music.rate');
105    const musicQueue = PersistStatus.get('music.playList');
106    const repeatMode = PersistStatus.get('music.repeatMode');
107    const progress = PersistStatus.get('music.progress');
108    const track = PersistStatus.get('music.musicItem');
109    const quality =
110        PersistStatus.get('music.quality') ||
111        Config.get('setting.basic.defaultPlayQuality') ||
112        'standard';
113
114    // 状态恢复
115    if (rate) {
116        ReactNativeTrackPlayer.setRate(+rate / 100);
117    }
118    if (repeatMode) {
119        repeatModeStore.setValue(repeatMode as MusicRepeatMode);
120    }
121
122    if (musicQueue && Array.isArray(musicQueue)) {
123        addAll(musicQueue, undefined, repeatMode === MusicRepeatMode.SHUFFLE);
124    }
125
126    if (track && isInPlayList(track)) {
127        if (!Config.get('setting.basic.autoPlayWhenAppStart')) {
128            track.isInit = true;
129        }
130
131        // 异步
132        PluginManager.getByMedia(track)
133            ?.methods.getMediaSource(track, quality, 0)
134            .then(async newSource => {
135                track.url = newSource?.url || track.url;
136                track.headers = newSource?.headers || track.headers;
137
138                if (isSameMediaItem(currentMusicStore.getValue(), track)) {
139                    await setTrackSource(track as Track, false);
140                }
141            });
142        setCurrentMusic(track);
143
144        if (progress) {
145            // 异步
146            ReactNativeTrackPlayer.seekTo(progress);
147        }
148    }
149
150    if (!hasSetupListener) {
151        ReactNativeTrackPlayer.addEventListener(
152            Event.PlaybackActiveTrackChanged,
153            async evt => {
154                if (
155                    evt.index === 1 &&
156                    evt.lastIndex === 0 &&
157                    evt.track?.$ === internalFakeSoundKey
158                ) {
159                    trace('队列末尾,播放下一首');
160                    if (repeatModeStore.getValue() === MusicRepeatMode.SINGLE) {
161                        await play(null, true);
162                    } else {
163                        // 当前生效的歌曲是下一曲的标记
164                        await skipToNext();
165                    }
166                }
167            },
168        );
169
170        ReactNativeTrackPlayer.addEventListener(
171            Event.PlaybackError,
172            async e => {
173                errorLog('播放出错', e.message);
174                // WARNING: 不稳定,报错的时候有可能track已经变到下一首歌去了
175                const currentTrack =
176                    await ReactNativeTrackPlayer.getActiveTrack();
177                if (currentTrack?.isInit) {
178                    // HACK: 避免初始失败的情况
179                    ReactNativeTrackPlayer.updateMetadataForTrack(0, {
180                        ...currentTrack,
181                        // @ts-ignore
182                        isInit: undefined,
183                    });
184                    return;
185                }
186
187                if (
188                    (await ReactNativeTrackPlayer.getActiveTrackIndex()) ===
189                        0 &&
190                    e.message &&
191                    e.message !== 'android-io-file-not-found'
192                ) {
193                    trace('播放出错', {
194                        message: e.message,
195                        code: e.code,
196                    });
197
198                    failToPlay();
199                }
200            },
201        );
202
203        hasSetupListener = true;
204    }
205}
206
207/**
208 * 获取自动播放的下一个track
209 */
210const getFakeNextTrack = () => {
211    let track: Track | undefined;
212    const repeatMode = repeatModeStore.getValue();
213    if (repeatMode === MusicRepeatMode.SINGLE) {
214        // 单曲循环
215        track = getPlayListMusicAt(currentIndex) as Track;
216    } else {
217        // 下一曲
218        track = getPlayListMusicAt(currentIndex + 1) as Track;
219    }
220
221    if (track) {
222        return produce(track, _ => {
223            _.url = SoundAsset.fakeAudio;
224            _.$ = internalFakeSoundKey;
225            if (!_.artwork?.trim()?.length) {
226                _.artwork = undefined;
227            }
228        });
229    } else {
230        // 只有列表长度为0时才会出现的特殊情况
231        return {url: SoundAsset.fakeAudio, $: internalFakeSoundKey} as Track;
232    }
233};
234
235/** 播放失败时的情况 */
236async function failToPlay() {
237    // 如果自动跳转下一曲, 500s后自动跳转
238    if (!Config.get('setting.basic.autoStopWhenError')) {
239        await ReactNativeTrackPlayer.reset();
240        await delay(500);
241        await skipToNext();
242    }
243}
244
245// 播放模式相关
246const _toggleRepeatMapping = {
247    [MusicRepeatMode.SHUFFLE]: MusicRepeatMode.SINGLE,
248    [MusicRepeatMode.SINGLE]: MusicRepeatMode.QUEUE,
249    [MusicRepeatMode.QUEUE]: MusicRepeatMode.SHUFFLE,
250};
251/** 切换下一个模式 */
252const toggleRepeatMode = () => {
253    setRepeatMode(_toggleRepeatMapping[repeatModeStore.getValue()]);
254};
255
256/**
257 * 添加到播放列表
258 * @param musicItems 目标歌曲
259 * @param beforeIndex 在第x首歌曲前添加
260 * @param shouldShuffle 随机排序
261 */
262const addAll = (
263    musicItems: Array<IMusic.IMusicItem> = [],
264    beforeIndex?: number,
265    shouldShuffle?: boolean,
266) => {
267    const now = Date.now();
268    let newPlayList: IMusic.IMusicItem[] = [];
269    let currentPlayList = getPlayList();
270    musicItems.forEach((item, index) => {
271        item[timeStampSymbol] = now;
272        item[sortIndexSymbol] = index;
273    });
274
275    if (beforeIndex === undefined || beforeIndex < 0) {
276        // 1.1. 添加到歌单末尾,并过滤掉已有的歌曲
277        newPlayList = currentPlayList.concat(
278            musicItems.filter(item => !isInPlayList(item)),
279        );
280    } else {
281        // 1.2. 新的播放列表,插入
282        const indexMap = createMediaIndexMap(musicItems);
283        const beforeDraft = currentPlayList
284            .slice(0, beforeIndex)
285            .filter(item => !indexMap.has(item));
286        const afterDraft = currentPlayList
287            .slice(beforeIndex)
288            .filter(item => !indexMap.has(item));
289
290        newPlayList = [...beforeDraft, ...musicItems, ...afterDraft];
291    }
292
293    // 如果太长了
294    if (newPlayList.length > maxMusicQueueLength) {
295        newPlayList = shrinkPlayListToSize(
296            newPlayList,
297            beforeIndex ?? newPlayList.length - 1,
298        );
299    }
300
301    // 2. 如果需要随机
302    if (shouldShuffle) {
303        newPlayList = shuffle(newPlayList);
304    }
305    // 3. 设置播放列表
306    setPlayList(newPlayList);
307    const currentMusicItem = currentMusicStore.getValue();
308
309    // 4. 重置下标
310    if (currentMusicItem) {
311        currentIndex = getMusicIndex(currentMusicItem);
312    }
313};
314
315/** 追加到队尾 */
316const add = (
317    musicItem: IMusic.IMusicItem | IMusic.IMusicItem[],
318    beforeIndex?: number,
319) => {
320    addAll(Array.isArray(musicItem) ? musicItem : [musicItem], beforeIndex);
321};
322
323/**
324 * 下一首播放
325 * @param musicItem
326 */
327const addNext = (musicItem: IMusic.IMusicItem | IMusic.IMusicItem[]) => {
328    const shouldPlay = isPlayListEmpty();
329    add(musicItem, currentIndex + 1);
330    if (shouldPlay) {
331        play(Array.isArray(musicItem) ? musicItem[0] : musicItem);
332    }
333};
334
335const isCurrentMusic = (musicItem: IMusic.IMusicItem | null | undefined) => {
336    return isSameMediaItem(musicItem, currentMusicStore.getValue()) ?? false;
337};
338
339const remove = async (musicItem: IMusic.IMusicItem) => {
340    const playList = getPlayList();
341    let newPlayList: IMusic.IMusicItem[] = [];
342    let currentMusic: IMusic.IMusicItem | null = currentMusicStore.getValue();
343    const targetIndex = getMusicIndex(musicItem);
344    let shouldPlayCurrent: boolean | null = null;
345    if (targetIndex === -1) {
346        // 1. 这种情况应该是出错了
347        return;
348    }
349    // 2. 移除的是当前项
350    if (currentIndex === targetIndex) {
351        // 2.1 停止播放,移除当前项
352        newPlayList = produce(playList, draft => {
353            draft.splice(targetIndex, 1);
354        });
355        // 2.2 设置新的播放列表,并更新当前音乐
356        if (newPlayList.length === 0) {
357            currentMusic = null;
358            shouldPlayCurrent = false;
359        } else {
360            currentMusic = newPlayList[currentIndex % newPlayList.length];
361            try {
362                const state = (await ReactNativeTrackPlayer.getPlaybackState())
363                    .state;
364                shouldPlayCurrent = !musicIsPaused(state);
365            } catch {
366                shouldPlayCurrent = false;
367            }
368        }
369    } else {
370        // 3. 删除
371        newPlayList = produce(playList, draft => {
372            draft.splice(targetIndex, 1);
373        });
374    }
375
376    setPlayList(newPlayList);
377    setCurrentMusic(currentMusic);
378    if (shouldPlayCurrent === true) {
379        await play(currentMusic, true);
380    } else if (shouldPlayCurrent === false) {
381        await ReactNativeTrackPlayer.reset();
382    }
383};
384
385/**
386 * 设置播放模式
387 * @param mode 播放模式
388 */
389const setRepeatMode = (mode: MusicRepeatMode) => {
390    const playList = getPlayList();
391    let newPlayList;
392    const prevMode = repeatModeStore.getValue();
393    if (
394        (prevMode === MusicRepeatMode.SHUFFLE &&
395            mode !== MusicRepeatMode.SHUFFLE) ||
396        (mode === MusicRepeatMode.SHUFFLE &&
397            prevMode !== MusicRepeatMode.SHUFFLE)
398    ) {
399        if (mode === MusicRepeatMode.SHUFFLE) {
400            newPlayList = shuffle(playList);
401        } else {
402            newPlayList = sortByTimestampAndIndex(playList, true);
403        }
404        setPlayList(newPlayList);
405    }
406
407    const currentMusicItem = currentMusicStore.getValue();
408    currentIndex = getMusicIndex(currentMusicItem);
409    repeatModeStore.setValue(mode);
410    // 更新下一首歌的信息
411    ReactNativeTrackPlayer.updateMetadataForTrack(1, getFakeNextTrack());
412    // 记录
413    PersistStatus.set('music.repeatMode', mode);
414};
415
416/** 清空播放列表 */
417const clear = async () => {
418    setPlayList([]);
419    setCurrentMusic(null);
420
421    await ReactNativeTrackPlayer.reset();
422    PersistStatus.set('music.musicItem', undefined);
423    PersistStatus.set('music.progress', 0);
424};
425
426/** 暂停 */
427const pause = async () => {
428    await ReactNativeTrackPlayer.pause();
429};
430
431/** 设置音源 */
432const setTrackSource = async (track: Track, autoPlay = true) => {
433    if (!track.artwork?.trim()?.length) {
434        track.artwork = undefined;
435    }
436    await ReactNativeTrackPlayer.setQueue([track, getFakeNextTrack()]);
437    PersistStatus.set('music.musicItem', track as IMusic.IMusicItem);
438    PersistStatus.set('music.progress', 0);
439    if (autoPlay) {
440        await ReactNativeTrackPlayer.play();
441    }
442};
443
444const setCurrentMusic = (musicItem?: IMusic.IMusicItem | null) => {
445    if (!musicItem) {
446        currentIndex = -1;
447        currentMusicStore.setValue(null);
448        PersistStatus.set('music.musicItem', undefined);
449        PersistStatus.set('music.progress', 0);
450        return;
451    }
452    currentIndex = getMusicIndex(musicItem);
453    currentMusicStore.setValue(musicItem);
454};
455
456const setQuality = (quality: IMusic.IQualityKey) => {
457    qualityStore.setValue(quality);
458    PersistStatus.set('music.quality', quality);
459};
460/**
461 * 播放
462 *
463 * 当musicItem 为空时,代表暂停/播放
464 *
465 * @param musicItem
466 * @param forcePlay
467 * @returns
468 */
469const play = async (
470    musicItem?: IMusic.IMusicItem | null,
471    forcePlay?: boolean,
472) => {
473    try {
474        if (!musicItem) {
475            musicItem = currentMusicStore.getValue();
476        }
477        if (!musicItem) {
478            throw new Error(PlayFailReason.PLAY_LIST_IS_EMPTY);
479        }
480        // 1. 移动网络禁止播放
481        if (
482            Network.isCellular() &&
483            !Config.get('setting.basic.useCelluarNetworkPlay') &&
484            !LocalMusicSheet.isLocalMusic(musicItem)
485        ) {
486            await ReactNativeTrackPlayer.reset();
487            throw new Error(PlayFailReason.FORBID_CELLUAR_NETWORK_PLAY);
488        }
489
490        // 2. 如果是当前正在播放的音频
491        if (isCurrentMusic(musicItem)) {
492            const currentTrack = await ReactNativeTrackPlayer.getTrack(0);
493            // 2.1 如果当前有源
494            if (
495                currentTrack?.url &&
496                isSameMediaItem(musicItem, currentTrack as IMusic.IMusicItem)
497            ) {
498                const currentActiveIndex =
499                    await ReactNativeTrackPlayer.getActiveTrackIndex();
500                if (currentActiveIndex !== 0) {
501                    await ReactNativeTrackPlayer.skip(0);
502                }
503                if (forcePlay) {
504                    // 2.1.1 强制重新开始
505                    await ReactNativeTrackPlayer.seekTo(0);
506                }
507                const currentState = (
508                    await ReactNativeTrackPlayer.getPlaybackState()
509                ).state;
510                if (currentState === State.Stopped) {
511                    await setTrackSource(currentTrack);
512                }
513                if (currentState !== State.Playing) {
514                    // 2.1.2 恢复播放
515                    await ReactNativeTrackPlayer.play();
516                }
517                // 这种情况下,播放队列和当前歌曲都不需要变化
518                return;
519            }
520            // 2.2 其他情况:重新获取源
521        }
522
523        // 3. 如果没有在播放列表中,添加到队尾;同时更新列表状态
524        const inPlayList = isInPlayList(musicItem);
525        if (!inPlayList) {
526            add(musicItem);
527        }
528
529        // 4. 更新列表状态和当前音乐
530        setCurrentMusic(musicItem);
531        await ReactNativeTrackPlayer.reset();
532
533        // 4.1 刷新歌词信息
534        if (
535            !isSameMediaItem(
536                LyricManager.getLyricState()?.lyricParser?.musicItem,
537                musicItem,
538            )
539        ) {
540            DeviceEventEmitter.emit(EDeviceEvents.REFRESH_LYRIC, true);
541        }
542
543        // 5. 获取音源
544        let track: IMusic.IMusicItem;
545
546        // 5.1 通过插件获取音源
547        const plugin = PluginManager.getByName(musicItem.platform);
548        // 5.2 获取音质排序
549        const qualityOrder = getQualityOrder(
550            Config.get('setting.basic.defaultPlayQuality') ?? 'standard',
551            Config.get('setting.basic.playQualityOrder') ?? 'asc',
552        );
553        // 5.3 插件返回音源
554        let source: IPlugin.IMediaSourceResult | null = null;
555        for (let quality of qualityOrder) {
556            if (isCurrentMusic(musicItem)) {
557                source =
558                    (await plugin?.methods?.getMediaSource(
559                        musicItem,
560                        quality,
561                    )) ?? null;
562                // 5.3.1 获取到真实源
563                if (source) {
564                    setQuality(quality);
565                    break;
566                }
567            } else {
568                // 5.3.2 已经切换到其他歌曲了,
569                return;
570            }
571        }
572
573        if (!isCurrentMusic(musicItem)) {
574            return;
575        }
576        if (!source) {
577            // 如果有source
578            if (musicItem.source) {
579                for (let quality of qualityOrder) {
580                    if (musicItem.source[quality]?.url) {
581                        source = musicItem.source[quality]!;
582                        setQuality(quality);
583
584                        break;
585                    }
586                }
587            }
588            // 5.4 没有返回源
589            if (!source && !musicItem.url) {
590                // 插件失效的情况
591                if (Config.get('setting.basic.tryChangeSourceWhenPlayFail')) {
592                    // 重试
593                    const similarMusic = await getSimilarMusic(
594                        musicItem,
595                        'music',
596                        () => !isCurrentMusic(musicItem),
597                    );
598
599                    if (similarMusic) {
600                        const similarMusicPlugin =
601                            PluginManager.getByMedia(similarMusic);
602
603                        for (let quality of qualityOrder) {
604                            if (isCurrentMusic(musicItem)) {
605                                source =
606                                    (await similarMusicPlugin?.methods?.getMediaSource(
607                                        similarMusic,
608                                        quality,
609                                    )) ?? null;
610                                // 5.4.1 获取到真实源
611                                if (source) {
612                                    setQuality(quality);
613                                    break;
614                                }
615                            } else {
616                                // 5.4.2 已经切换到其他歌曲了,
617                                return;
618                            }
619                        }
620                    }
621
622                    if (!source) {
623                        throw new Error(PlayFailReason.INVALID_SOURCE);
624                    }
625                } else {
626                    throw new Error(PlayFailReason.INVALID_SOURCE);
627                }
628            } else {
629                source = {
630                    url: musicItem.url,
631                };
632                setQuality('standard');
633            }
634        }
635
636        // 6. 特殊类型源
637        if (getUrlExt(source.url) === '.m3u8') {
638            // @ts-ignore
639            source.type = 'hls';
640        }
641        // 7. 合并结果
642        track = mergeProps(musicItem, source) as IMusic.IMusicItem;
643
644        // 8. 新增历史记录
645        musicHistory.addMusic(musicItem);
646
647        trace('获取音源成功', track);
648        // 9. 设置音源
649        await setTrackSource(track as Track);
650
651        // 10. 获取补充信息
652        let info: Partial<IMusic.IMusicItem> | null = null;
653        try {
654            info = (await plugin?.methods?.getMusicInfo?.(musicItem)) ?? null;
655            if (
656                (typeof info?.url === 'string' && info.url.trim() === '') ||
657                (info?.url && typeof info.url !== 'string')
658            ) {
659                delete info.url;
660            }
661        } catch {}
662
663        // 11. 设置补充信息
664        if (info && isCurrentMusic(musicItem)) {
665            const mergedTrack = mergeProps(track, info);
666            currentMusicStore.setValue(mergedTrack as IMusic.IMusicItem);
667            await ReactNativeTrackPlayer.updateMetadataForTrack(
668                0,
669                mergedTrack as TrackMetadataBase,
670            );
671        }
672    } catch (e: any) {
673        const message = e?.message;
674        if (
675            message === 'The player is not initialized. Call setupPlayer first.'
676        ) {
677            await ReactNativeTrackPlayer.setupPlayer();
678            play(musicItem, forcePlay);
679        } else if (message === PlayFailReason.FORBID_CELLUAR_NETWORK_PLAY) {
680            if (getCurrentDialog()?.name !== 'SimpleDialog') {
681                showDialog('SimpleDialog', {
682                    title: '流量提醒',
683                    content:
684                        '当前非WIFI环境,侧边栏设置中打开【使用移动网络播放】功能后可继续播放',
685                });
686            }
687        } else if (message === PlayFailReason.INVALID_SOURCE) {
688            trace('音源为空,播放失败');
689            await failToPlay();
690        } else if (message === PlayFailReason.PLAY_LIST_IS_EMPTY) {
691            // 队列是空的,不应该出现这种情况
692        }
693    }
694};
695
696/**
697 * 播放音乐,同时替换播放队列
698 * @param musicItem 音乐
699 * @param newPlayList 替代列表
700 */
701const playWithReplacePlayList = async (
702    musicItem: IMusic.IMusicItem,
703    newPlayList: IMusic.IMusicItem[],
704) => {
705    if (newPlayList.length !== 0) {
706        const now = Date.now();
707        if (newPlayList.length > maxMusicQueueLength) {
708            newPlayList = shrinkPlayListToSize(
709                newPlayList,
710                newPlayList.findIndex(it => isSameMediaItem(it, musicItem)),
711            );
712        }
713
714        newPlayList.forEach((it, index) => {
715            it[timeStampSymbol] = now;
716            it[sortIndexSymbol] = index;
717        });
718
719        setPlayList(
720            repeatModeStore.getValue() === MusicRepeatMode.SHUFFLE
721                ? shuffle(newPlayList)
722                : newPlayList,
723        );
724        await play(musicItem, true);
725    }
726};
727
728const skipToNext = async () => {
729    if (isPlayListEmpty()) {
730        setCurrentMusic(null);
731        return;
732    }
733
734    await play(getPlayListMusicAt(currentIndex + 1), true);
735};
736
737const skipToPrevious = async () => {
738    if (isPlayListEmpty()) {
739        setCurrentMusic(null);
740        return;
741    }
742
743    await play(
744        getPlayListMusicAt(currentIndex === -1 ? 0 : currentIndex - 1),
745        true,
746    );
747};
748
749/** 修改当前播放的音质 */
750const changeQuality = async (newQuality: IMusic.IQualityKey) => {
751    // 获取当前的音乐和进度
752    if (newQuality === qualityStore.getValue()) {
753        return true;
754    }
755
756    // 获取当前歌曲
757    const musicItem = currentMusicStore.getValue();
758    if (!musicItem) {
759        return false;
760    }
761    try {
762        const progress = await ReactNativeTrackPlayer.getProgress();
763        const plugin = PluginManager.getByMedia(musicItem);
764        const newSource = await plugin?.methods?.getMediaSource(
765            musicItem,
766            newQuality,
767        );
768        if (!newSource?.url) {
769            throw new Error(PlayFailReason.INVALID_SOURCE);
770        }
771        if (isCurrentMusic(musicItem)) {
772            const playingState = (
773                await ReactNativeTrackPlayer.getPlaybackState()
774            ).state;
775            await setTrackSource(
776                mergeProps(musicItem, newSource) as unknown as Track,
777                !musicIsPaused(playingState),
778            );
779
780            await ReactNativeTrackPlayer.seekTo(progress.position ?? 0);
781            setQuality(newQuality);
782        }
783        return true;
784    } catch {
785        // 修改失败
786        return false;
787    }
788};
789
790enum PlayFailReason {
791    /** 禁止移动网络播放 */
792    FORBID_CELLUAR_NETWORK_PLAY = 'FORBID_CELLUAR_NETWORK_PLAY',
793    /** 播放列表为空 */
794    PLAY_LIST_IS_EMPTY = 'PLAY_LIST_IS_EMPTY',
795    /** 无效源 */
796    INVALID_SOURCE = 'INVALID_SOURCE',
797    /** 非当前音乐 */
798}
799
800function useMusicState() {
801    const playbackState = usePlaybackState();
802
803    return playbackState.state;
804}
805
806function getPreviousMusic() {
807    const currentMusicItem = currentMusicStore.getValue();
808    if (!currentMusicItem) {
809        return null;
810    }
811
812    return getPlayListMusicAt(currentIndex - 1);
813}
814
815function getNextMusic() {
816    const currentMusicItem = currentMusicStore.getValue();
817    if (!currentMusicItem) {
818        return null;
819    }
820
821    return getPlayListMusicAt(currentIndex + 1);
822}
823
824const TrackPlayer = {
825    setupTrackPlayer,
826    usePlayList,
827    getPlayList,
828    addAll,
829    add,
830    addNext,
831    skipToNext,
832    skipToPrevious,
833    play,
834    playWithReplacePlayList,
835    pause,
836    remove,
837    clear,
838    useCurrentMusic: currentMusicStore.useValue,
839    getCurrentMusic: currentMusicStore.getValue,
840    useRepeatMode: repeatModeStore.useValue,
841    getRepeatMode: repeatModeStore.getValue,
842    toggleRepeatMode,
843    usePlaybackState,
844    getProgress: ReactNativeTrackPlayer.getProgress,
845    useProgress: useProgress,
846    seekTo: ReactNativeTrackPlayer.seekTo,
847    changeQuality,
848    useCurrentQuality: qualityStore.useValue,
849    getCurrentQuality: qualityStore.getValue,
850    getRate: ReactNativeTrackPlayer.getRate,
851    setRate: ReactNativeTrackPlayer.setRate,
852    useMusicState,
853    reset: ReactNativeTrackPlayer.reset,
854    getPreviousMusic,
855    getNextMusic,
856};
857
858export default TrackPlayer;
859export {MusicRepeatMode, State as MusicState};
860