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