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