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