xref: /MusicFree/src/core/trackPlayer/index.ts (revision 9677305be11b30a8953a6c14fd24375953a2309d)
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';
49
50/** 当前播放 */
51const currentMusicStore = new GlobalState<IMusic.IMusicItem | null>(null);
52
53/** 播放模式 */
54const repeatModeStore = new GlobalState<MusicRepeatMode>(MusicRepeatMode.QUEUE);
55
56/** 音质 */
57const qualityStore = new GlobalState<IMusic.IQualityKey>('standard');
58
59let currentIndex = -1;
60
61// TODO: 下个版本最大限制调大一些
62const maxMusicQueueLength = 1500; // 当前播放最大限制
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    const _musicItems = musicItems.map((item, index) =>
270        produce(item, draft => {
271            draft[timeStampSymbol] = now;
272            draft[sortIndexSymbol] = index;
273        }),
274    );
275    if (beforeIndex === undefined || beforeIndex < 0) {
276        // 1.1. 添加到歌单末尾,并过滤掉已有的歌曲
277        newPlayList = currentPlayList.concat(
278            _musicItems.filter(item => !isInPlayList(item)),
279        );
280    } else {
281        // 1.2. 新的播放列表,插入
282        const indexMap = createMediaIndexMap(_musicItems);
283        const beforeDraft = currentPlayList
284            .slice(0, beforeIndex)
285            .filter(item => !indexMap.has(item));
286        const afterDraft = currentPlayList
287            .slice(beforeIndex)
288            .filter(item => !indexMap.has(item));
289
290        newPlayList = [...beforeDraft, ..._musicItems, ...afterDraft];
291    }
292
293    // 如果太长了
294    if (newPlayList.length > maxMusicQueueLength) {
295        newPlayList = shrinkPlayListToSize(
296            newPlayList,
297            beforeIndex ?? newPlayList.length - 1,
298        );
299    }
300
301    // 2. 如果需要随机
302    if (shouldShuffle) {
303        newPlayList = shuffle(newPlayList);
304    }
305    // 3. 设置播放列表
306    setPlayList(newPlayList);
307    const currentMusicItem = currentMusicStore.getValue();
308
309    // 4. 重置下标
310    if (currentMusicItem) {
311        currentIndex = getMusicIndex(currentMusicItem);
312    }
313
314    // TODO: 更新播放队列信息
315    // 5. 存储更新的播放列表信息
316};
317
318/** 追加到队尾 */
319const add = (
320    musicItem: IMusic.IMusicItem | IMusic.IMusicItem[],
321    beforeIndex?: number,
322) => {
323    addAll(Array.isArray(musicItem) ? musicItem : [musicItem], beforeIndex);
324};
325
326/**
327 * 下一首播放
328 * @param musicItem
329 */
330const addNext = (musicItem: IMusic.IMusicItem | IMusic.IMusicItem[]) => {
331    const shouldPlay = isPlayListEmpty();
332    add(musicItem, currentIndex + 1);
333    if (shouldPlay) {
334        play(Array.isArray(musicItem) ? musicItem[0] : musicItem);
335    }
336};
337
338const isCurrentMusic = (musicItem: IMusic.IMusicItem) => {
339    return isSameMediaItem(musicItem, currentMusicStore.getValue()) ?? false;
340};
341
342const remove = async (musicItem: IMusic.IMusicItem) => {
343    const playList = getPlayList();
344    let newPlayList: IMusic.IMusicItem[] = [];
345    let currentMusic: IMusic.IMusicItem | null = currentMusicStore.getValue();
346    const targetIndex = getMusicIndex(musicItem);
347    let shouldPlayCurrent: boolean | null = null;
348    if (targetIndex === -1) {
349        // 1. 这种情况应该是出错了
350        return;
351    }
352    // 2. 移除的是当前项
353    if (currentIndex === targetIndex) {
354        // 2.1 停止播放,移除当前项
355        newPlayList = produce(playList, draft => {
356            draft.splice(targetIndex, 1);
357        });
358        // 2.2 设置新的播放列表,并更新当前音乐
359        if (newPlayList.length === 0) {
360            currentMusic = null;
361            shouldPlayCurrent = false;
362        } else {
363            currentMusic = newPlayList[currentIndex % newPlayList.length];
364            try {
365                const state = (await ReactNativeTrackPlayer.getPlaybackState())
366                    .state;
367                if (musicIsPaused(state)) {
368                    shouldPlayCurrent = false;
369                } else {
370                    shouldPlayCurrent = true;
371                }
372            } catch {
373                shouldPlayCurrent = false;
374            }
375        }
376    } else {
377        // 3. 删除
378        newPlayList = produce(playList, draft => {
379            draft.splice(targetIndex, 1);
380        });
381    }
382
383    setPlayList(newPlayList);
384    setCurrentMusic(currentMusic);
385    if (shouldPlayCurrent === true) {
386        await play(currentMusic, true);
387    } else if (shouldPlayCurrent === false) {
388        await ReactNativeTrackPlayer.reset();
389    }
390};
391
392/**
393 * 设置播放模式
394 * @param mode 播放模式
395 */
396const setRepeatMode = (mode: MusicRepeatMode) => {
397    const playList = getPlayList();
398    let newPlayList;
399    const prevMode = repeatModeStore.getValue();
400
401    if (
402        (prevMode === MusicRepeatMode.SHUFFLE &&
403            mode !== MusicRepeatMode.SHUFFLE) ||
404        (mode === MusicRepeatMode.SHUFFLE &&
405            prevMode !== MusicRepeatMode.SHUFFLE)
406    ) {
407        if (mode === MusicRepeatMode.SHUFFLE) {
408            newPlayList = shuffle(playList);
409        } else {
410            newPlayList = sortByTimestampAndIndex(playList, true);
411        }
412        setPlayList(newPlayList);
413    }
414
415    const currentMusicItem = currentMusicStore.getValue();
416    currentIndex = getMusicIndex(currentMusicItem);
417    repeatModeStore.setValue(mode);
418    // 更新下一首歌的信息
419    ReactNativeTrackPlayer.updateMetadataForTrack(1, getFakeNextTrack());
420    // 记录
421    PersistStatus.set('music.repeatMode', mode);
422};
423
424/** 清空播放列表 */
425const clear = async () => {
426    setPlayList([]);
427    setCurrentMusic(null);
428
429    await ReactNativeTrackPlayer.reset();
430    PersistStatus.set('music.musicItem', undefined);
431    PersistStatus.set('music.progress', 0);
432};
433
434/** 暂停 */
435const pause = async () => {
436    await ReactNativeTrackPlayer.pause();
437};
438
439/** 设置音源 */
440const setTrackSource = async (track: Track, autoPlay = true) => {
441    if (!track.artwork?.trim()?.length) {
442        track.artwork = undefined;
443    }
444    await ReactNativeTrackPlayer.setQueue([track, getFakeNextTrack()]);
445    PersistStatus.set('music.musicItem', track as IMusic.IMusicItem);
446    PersistStatus.set('music.progress', 0);
447    if (autoPlay) {
448        await ReactNativeTrackPlayer.play();
449    }
450};
451
452const setCurrentMusic = (musicItem?: IMusic.IMusicItem | null) => {
453    if (!musicItem) {
454        currentIndex = -1;
455        currentMusicStore.setValue(null);
456        PersistStatus.set('music.musicItem', undefined);
457        PersistStatus.set('music.progress', 0);
458        return;
459    }
460    currentIndex = getMusicIndex(musicItem);
461    currentMusicStore.setValue(musicItem);
462};
463
464const setQuality = (quality: IMusic.IQualityKey) => {
465    qualityStore.setValue(quality);
466    PersistStatus.set('music.quality', quality);
467};
468/**
469 * 播放
470 *
471 * 当musicItem 为空时,代表暂停/播放
472 *
473 * @param musicItem
474 * @param forcePlay
475 * @returns
476 */
477const play = async (
478    musicItem?: IMusic.IMusicItem | null,
479    forcePlay?: boolean,
480) => {
481    try {
482        if (!musicItem) {
483            musicItem = currentMusicStore.getValue();
484        }
485        if (!musicItem) {
486            throw new Error(PlayFailReason.PLAY_LIST_IS_EMPTY);
487        }
488        // 1. 移动网络禁止播放
489        if (
490            Network.isCellular() &&
491            !Config.get('setting.basic.useCelluarNetworkPlay') &&
492            !LocalMusicSheet.isLocalMusic(musicItem)
493        ) {
494            await ReactNativeTrackPlayer.reset();
495            throw new Error(PlayFailReason.FORBID_CELLUAR_NETWORK_PLAY);
496        }
497
498        // 2. 如果是当前正在播放的音频
499        if (isCurrentMusic(musicItem)) {
500            const currentTrack = await ReactNativeTrackPlayer.getTrack(0);
501            // 2.1 如果当前有源
502            if (
503                currentTrack?.url &&
504                isSameMediaItem(musicItem, currentTrack as IMusic.IMusicItem)
505            ) {
506                const currentActiveIndex =
507                    await ReactNativeTrackPlayer.getActiveTrackIndex();
508                if (currentActiveIndex !== 0) {
509                    await ReactNativeTrackPlayer.skip(0);
510                }
511                if (forcePlay) {
512                    // 2.1.1 强制重新开始
513                    await ReactNativeTrackPlayer.seekTo(0);
514                }
515                const currentState = (
516                    await ReactNativeTrackPlayer.getPlaybackState()
517                ).state;
518                if (currentState === State.Stopped) {
519                    await setTrackSource(currentTrack);
520                }
521                if (currentState !== State.Playing) {
522                    // 2.1.2 恢复播放
523                    await ReactNativeTrackPlayer.play();
524                }
525                // 这种情况下,播放队列和当前歌曲都不需要变化
526                return;
527            }
528            // 2.2 其他情况:重新获取源
529        }
530
531        // 3. 如果没有在播放列表中,添加到队尾;同时更新列表状态
532        const inPlayList = isInPlayList(musicItem);
533        if (!inPlayList) {
534            add(musicItem);
535        }
536
537        // 4. 更新列表状态和当前音乐
538        setCurrentMusic(musicItem);
539        await ReactNativeTrackPlayer.reset();
540
541        // 4.1 刷新歌词信息
542        if (
543            !isSameMediaItem(
544                LyricManager.getLyricState()?.lyricParser?.getCurrentMusicItem?.(),
545                musicItem,
546            )
547        ) {
548            DeviceEventEmitter.emit(EDeviceEvents.REFRESH_LYRIC, true);
549        }
550
551        // 5. 获取音源
552        let track: IMusic.IMusicItem;
553
554        // 5.1 通过插件获取音源
555        const plugin = PluginManager.getByName(musicItem.platform);
556        // 5.2 获取音质排序
557        const qualityOrder = getQualityOrder(
558            Config.get('setting.basic.defaultPlayQuality') ?? 'standard',
559            Config.get('setting.basic.playQualityOrder') ?? 'asc',
560        );
561        // 5.3 插件返回音源
562        let source: IPlugin.IMediaSourceResult | null = null;
563        for (let quality of qualityOrder) {
564            if (isCurrentMusic(musicItem)) {
565                source =
566                    (await plugin?.methods?.getMediaSource(
567                        musicItem,
568                        quality,
569                    )) ?? null;
570                // 5.3.1 获取到真实源
571                if (source) {
572                    setQuality(quality);
573                    break;
574                }
575            } else {
576                // 5.3.2 已经切换到其他歌曲了,
577                return;
578            }
579        }
580
581        if (!isCurrentMusic(musicItem)) {
582            return;
583        }
584
585        if (!source) {
586            // 如果有source
587            if (musicItem.source) {
588                for (let quality of qualityOrder) {
589                    if (musicItem.source[quality]?.url) {
590                        source = musicItem.source[quality]!;
591                        setQuality(quality);
592
593                        break;
594                    }
595                }
596            }
597
598            // 5.4 没有返回源
599            if (!source && !musicItem.url) {
600                throw new Error(PlayFailReason.INVALID_SOURCE);
601            } else {
602                source = {
603                    url: musicItem.url,
604                };
605                setQuality('standard');
606            }
607        }
608
609        // 6. 特殊类型源
610        if (getUrlExt(source.url) === '.m3u8') {
611            // @ts-ignore
612            source.type = 'hls';
613        }
614        // 7. 合并结果
615        track = mergeProps(musicItem, source) as IMusic.IMusicItem;
616
617        // 8. 新增历史记录
618        musicHistory.addMusic(musicItem);
619
620        trace('获取音源成功', track);
621        // 9. 设置音源
622        await setTrackSource(track as Track);
623
624        // 10. 获取补充信息
625        let info: Partial<IMusic.IMusicItem> | null = null;
626        try {
627            info = (await plugin?.methods?.getMusicInfo?.(musicItem)) ?? null;
628            if (
629                (typeof info?.url === 'string' && info.url.trim() === '') ||
630                (info?.url && typeof info.url !== 'string')
631            ) {
632                delete info.url;
633            }
634        } catch {}
635
636        // 11. 设置补充信息
637        if (info && isCurrentMusic(musicItem)) {
638            const mergedTrack = mergeProps(track, info);
639            currentMusicStore.setValue(mergedTrack as IMusic.IMusicItem);
640            await ReactNativeTrackPlayer.updateMetadataForTrack(
641                0,
642                mergedTrack as TrackMetadataBase,
643            );
644        }
645    } catch (e: any) {
646        const message = e?.message;
647        console.log(message, 'MMM', e);
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
662            // Toast.warn(
663            //     '当前禁止移动网络播放音乐,如需播放请去侧边栏-基本设置中修改',
664            // );
665        } else if (message === PlayFailReason.INVALID_SOURCE) {
666            trace('音源为空,播放失败');
667            await failToPlay();
668        } else if (message === PlayFailReason.PLAY_LIST_IS_EMPTY) {
669            // 队列是空的,不应该出现这种情况
670        }
671    }
672};
673
674/**
675 * 播放音乐,同时替换播放队列
676 * @param musicItem 音乐
677 * @param newPlayList 替代列表
678 */
679const playWithReplacePlayList = async (
680    musicItem: IMusic.IMusicItem,
681    newPlayList: IMusic.IMusicItem[],
682) => {
683    if (newPlayList.length !== 0) {
684        const now = Date.now();
685        if (newPlayList.length > maxMusicQueueLength) {
686            newPlayList = shrinkPlayListToSize(
687                newPlayList,
688                newPlayList.findIndex(it => isSameMediaItem(it, musicItem)),
689            );
690        }
691        const playListItems = newPlayList.map((item, index) =>
692            produce(item, draft => {
693                draft[timeStampSymbol] = now;
694                draft[sortIndexSymbol] = index;
695            }),
696        );
697        setPlayList(
698            repeatModeStore.getValue() === MusicRepeatMode.SHUFFLE
699                ? shuffle(playListItems)
700                : playListItems,
701        );
702        await play(musicItem, true);
703    }
704};
705
706const skipToNext = async () => {
707    if (isPlayListEmpty()) {
708        setCurrentMusic(null);
709        return;
710    }
711
712    await play(getPlayListMusicAt(currentIndex + 1), true);
713};
714
715const skipToPrevious = async () => {
716    if (isPlayListEmpty()) {
717        setCurrentMusic(null);
718        return;
719    }
720
721    await play(
722        getPlayListMusicAt(currentIndex === -1 ? 0 : currentIndex - 1),
723        true,
724    );
725};
726
727/** 修改当前播放的音质 */
728const changeQuality = async (newQuality: IMusic.IQualityKey) => {
729    // 获取当前的音乐和进度
730    if (newQuality === qualityStore.getValue()) {
731        return true;
732    }
733
734    // 获取当前歌曲
735    const musicItem = currentMusicStore.getValue();
736    if (!musicItem) {
737        return false;
738    }
739    try {
740        const progress = await ReactNativeTrackPlayer.getProgress();
741        const plugin = PluginManager.getByMedia(musicItem);
742        const newSource = await plugin?.methods?.getMediaSource(
743            musicItem,
744            newQuality,
745        );
746        if (!newSource?.url) {
747            throw new Error(PlayFailReason.INVALID_SOURCE);
748        }
749        if (isCurrentMusic(musicItem)) {
750            const playingState = (
751                await ReactNativeTrackPlayer.getPlaybackState()
752            ).state;
753            await setTrackSource(
754                mergeProps(musicItem, newSource) as unknown as Track,
755                !musicIsPaused(playingState),
756            );
757
758            await ReactNativeTrackPlayer.seekTo(progress.position ?? 0);
759            setQuality(newQuality);
760        }
761        return true;
762    } catch {
763        // 修改失败
764        return false;
765    }
766};
767
768enum PlayFailReason {
769    /** 禁止移动网络播放 */
770    FORBID_CELLUAR_NETWORK_PLAY = 'FORBID_CELLUAR_NETWORK_PLAY',
771    /** 播放列表为空 */
772    PLAY_LIST_IS_EMPTY = 'PLAY_LIST_IS_EMPTY',
773    /** 无效源 */
774    INVALID_SOURCE = 'INVALID_SOURCE',
775    /** 非当前音乐 */
776}
777
778function useMusicState() {
779    const playbackState = usePlaybackState();
780
781    return playbackState.state;
782}
783
784function getPreviousMusic() {
785    const currentMusicItem = currentMusicStore.getValue();
786    if (!currentMusicItem) {
787        return null;
788    }
789
790    return getPlayListMusicAt(currentIndex - 1);
791}
792
793function getNextMusic() {
794    const currentMusicItem = currentMusicStore.getValue();
795    if (!currentMusicItem) {
796        return null;
797    }
798
799    return getPlayListMusicAt(currentIndex + 1);
800}
801
802const TrackPlayer = {
803    setupTrackPlayer,
804    usePlayList,
805    getPlayList,
806    addAll,
807    add,
808    addNext,
809    skipToNext,
810    skipToPrevious,
811    play,
812    playWithReplacePlayList,
813    pause,
814    remove,
815    clear,
816    useCurrentMusic: currentMusicStore.useValue,
817    getCurrentMusic: currentMusicStore.getValue,
818    useRepeatMode: repeatModeStore.useValue,
819    getRepeatMode: repeatModeStore.getValue,
820    toggleRepeatMode,
821    usePlaybackState,
822    getProgress: ReactNativeTrackPlayer.getProgress,
823    useProgress: useProgress,
824    seekTo: ReactNativeTrackPlayer.seekTo,
825    changeQuality,
826    useCurrentQuality: qualityStore.useValue,
827    getCurrentQuality: qualityStore.getValue,
828    getRate: ReactNativeTrackPlayer.getRate,
829    setRate: ReactNativeTrackPlayer.setRate,
830    useMusicState,
831    reset: ReactNativeTrackPlayer.reset,
832    getPreviousMusic,
833    getNextMusic,
834};
835
836export default TrackPlayer;
837export {MusicRepeatMode, State as MusicState};
838