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