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