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