xref: /MusicFree/src/core/trackPlayer/index.ts (revision 4ade83edc3d07d31371dc1e6b3750001ca6d44da)
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
501        // 5. 获取音源
502        let track: IMusic.IMusicItem;
503
504        // 5.1 通过插件获取音源
505        const plugin = PluginManager.getByName(musicItem.platform);
506        // 5.2 获取音质排序
507        const qualityOrder = getQualityOrder(
508            Config.get('setting.basic.defaultPlayQuality') ?? 'standard',
509            Config.get('setting.basic.playQualityOrder') ?? 'asc',
510        );
511        // 5.3 插件返回音源
512        let source: IPlugin.IMediaSourceResult | null = null;
513        for (let quality of qualityOrder) {
514            if (isCurrentMusic(musicItem)) {
515                source =
516                    (await plugin?.methods?.getMediaSource(
517                        musicItem,
518                        quality,
519                    )) ?? null;
520                // 5.3.1 获取到真实源
521                if (source) {
522                    setQuality(quality);
523
524                    break;
525                }
526            } else {
527                // 5.3.2 已经切换到其他歌曲了,
528                return;
529            }
530        }
531
532        if (!isCurrentMusic(musicItem)) {
533            return;
534        }
535
536        if (!source) {
537            // 如果有source
538            if (musicItem.source) {
539                for (let quality of qualityOrder) {
540                    if (musicItem.source[quality]?.url) {
541                        source = musicItem.source[quality]!;
542                        setQuality(quality);
543
544                        break;
545                    }
546                }
547            }
548
549            // 5.4 没有返回源
550            if (!source && !musicItem.url) {
551                throw new Error(PlayFailReason.INVALID_SOURCE);
552            } else {
553                source = {
554                    url: musicItem.url,
555                };
556                setQuality('standard');
557            }
558        }
559
560        // 6. 特殊类型源
561        if (getUrlExt(source.url) === '.m3u8') {
562            // @ts-ignore
563            source.type = 'hls';
564        }
565        // 7. 合并结果
566        track = mergeProps(musicItem, source) as IMusic.IMusicItem;
567
568        // 8. 新增历史记录
569        musicHistory.addMusic(musicItem);
570
571        trace('获取音源成功', track);
572
573        // 9. 设置音源
574        await setTrackSource(track as Track);
575
576        // 10. 获取补充信息
577        let info: Partial<IMusic.IMusicItem> | null = null;
578        try {
579            info = (await plugin?.methods?.getMusicInfo?.(musicItem)) ?? null;
580        } catch {}
581
582        // 11. 设置补充信息
583        if (info && isCurrentMusic(musicItem)) {
584            const mergedTrack = mergeProps(track, info);
585            currentMusicStore.setValue(mergedTrack as IMusic.IMusicItem);
586            await ReactNativeTrackPlayer.updateMetadataForTrack(
587                0,
588                mergedTrack as TrackMetadataBase,
589            );
590        }
591
592        // 12. 刷新歌词信息
593        if (
594            !isSameMediaItem(
595                LyricManager.getLyricState()?.lyricParser?.getCurrentMusicItem?.(),
596                musicItem,
597            )
598        ) {
599            DeviceEventEmitter.emit(EDeviceEvents.REFRESH_LYRIC, true);
600        }
601    } catch (e: any) {
602        const message = e?.message;
603        if (
604            message === 'The player is not initialized. Call setupPlayer first.'
605        ) {
606            await ReactNativeTrackPlayer.setupPlayer();
607            play(musicItem, forcePlay);
608        } else if (message === PlayFailReason.FORBID_CELLUAR_NETWORK_PLAY) {
609            Toast.warn(
610                '当前禁止移动网络播放音乐,如需播放请去侧边栏-基本设置中修改',
611            );
612        } else if (message === PlayFailReason.INVALID_SOURCE) {
613            trace('音源为空,播放失败');
614            await failToPlay();
615        } else if (message === PlayFailReason.PLAY_LIST_IS_EMPTY) {
616            // 队列是空的,不应该出现这种情况
617        }
618    }
619};
620
621/**
622 * 播放音乐,同时替换播放队列
623 * @param musicItem 音乐
624 * @param newPlayList 替代列表
625 */
626const playWithReplacePlayList = async (
627    musicItem: IMusic.IMusicItem,
628    newPlayList: IMusic.IMusicItem[],
629) => {
630    if (newPlayList.length !== 0) {
631        const now = Date.now();
632        if (newPlayList.length > maxMusicQueueLength) {
633            newPlayList = shrinkPlayListToSize(
634                newPlayList,
635                newPlayList.findIndex(it => isSameMediaItem(it, musicItem)),
636            );
637        }
638        const playListItems = newPlayList.map((item, index) =>
639            produce(item, draft => {
640                draft[timeStampSymbol] = now;
641                draft[sortIndexSymbol] = index;
642            }),
643        );
644        setPlayList(
645            repeatModeStore.getValue() === MusicRepeatMode.SHUFFLE
646                ? shuffle(playListItems)
647                : playListItems,
648        );
649        await play(musicItem, true);
650    }
651};
652
653const skipToNext = async () => {
654    if (isPlayListEmpty()) {
655        setCurrentMusic(null);
656        return;
657    }
658
659    await play(getPlayListMusicAt(currentIndex + 1), true);
660};
661
662const skipToPrevious = async () => {
663    if (isPlayListEmpty()) {
664        setCurrentMusic(null);
665        return;
666    }
667
668    await play(
669        getPlayListMusicAt(currentIndex === -1 ? 0 : currentIndex - 1),
670        true,
671    );
672};
673
674/** 修改当前播放的音质 */
675const changeQuality = async (newQuality: IMusic.IQualityKey) => {
676    // 获取当前的音乐和进度
677    if (newQuality === qualityStore.getValue()) {
678        return true;
679    }
680
681    // 获取当前歌曲
682    const musicItem = currentMusicStore.getValue();
683    if (!musicItem) {
684        return false;
685    }
686    try {
687        const progress = await ReactNativeTrackPlayer.getProgress();
688        const plugin = PluginManager.getByMedia(musicItem);
689        const newSource = await plugin?.methods?.getMediaSource(
690            musicItem,
691            newQuality,
692        );
693        if (!newSource?.url) {
694            throw new Error(PlayFailReason.INVALID_SOURCE);
695        }
696        if (isCurrentMusic(musicItem)) {
697            const playingState = (
698                await ReactNativeTrackPlayer.getPlaybackState()
699            ).state;
700            await setTrackSource(
701                mergeProps(musicItem, newSource) as unknown as Track,
702                !musicIsPaused(playingState),
703            );
704
705            await ReactNativeTrackPlayer.seekTo(progress.position ?? 0);
706            setQuality(newQuality);
707        }
708        return true;
709    } catch {
710        // 修改失败
711        return false;
712    }
713};
714
715enum PlayFailReason {
716    /** 禁止移动网络播放 */
717    FORBID_CELLUAR_NETWORK_PLAY = 'FORBID_CELLUAR_NETWORK_PLAY',
718    /** 播放列表为空 */
719    PLAY_LIST_IS_EMPTY = 'PLAY_LIST_IS_EMPTY',
720    /** 无效源 */
721    INVALID_SOURCE = 'INVALID_SOURCE',
722    /** 非当前音乐 */
723}
724
725function useMusicState() {
726    const playbackState = usePlaybackState();
727
728    return playbackState.state;
729}
730
731function getPreviousMusic() {
732    const currentMusicItem = currentMusicStore.getValue();
733    if (!currentMusicItem) {
734        return null;
735    }
736
737    return getPlayListMusicAt(currentIndex - 1);
738}
739
740function getNextMusic() {
741    const currentMusicItem = currentMusicStore.getValue();
742    if (!currentMusicItem) {
743        return null;
744    }
745
746    return getPlayListMusicAt(currentIndex + 1);
747}
748
749const TrackPlayer = {
750    setupTrackPlayer,
751    usePlayList,
752    getPlayList,
753    addAll,
754    add,
755    addNext,
756    skipToNext,
757    skipToPrevious,
758    play,
759    playWithReplacePlayList,
760    pause,
761    remove,
762    clear,
763    useCurrentMusic: currentMusicStore.useValue,
764    getCurrentMusic: currentMusicStore.getValue,
765    useRepeatMode: repeatModeStore.useValue,
766    getRepeatMode: repeatModeStore.getValue,
767    toggleRepeatMode,
768    usePlaybackState,
769    getProgress: ReactNativeTrackPlayer.getProgress,
770    useProgress: useProgress,
771    seekTo: ReactNativeTrackPlayer.seekTo,
772    changeQuality,
773    useCurrentQuality: qualityStore.useValue,
774    getCurrentQuality: qualityStore.getValue,
775    getRate: ReactNativeTrackPlayer.getRate,
776    setRate: ReactNativeTrackPlayer.setRate,
777    useMusicState,
778    reset: ReactNativeTrackPlayer.reset,
779    getPreviousMusic,
780    getNextMusic,
781};
782
783export default TrackPlayer;
784export {MusicRepeatMode, State as MusicState};
785