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