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