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