xref: /MusicFree/src/core/trackPlayer/index.ts (revision 15900d057ad4df766b2f9ea5b48f92a8ce2664db)
15500cea7S猫头猫import produce from 'immer';
25500cea7S猫头猫import ReactNativeTrackPlayer, {
35500cea7S猫头猫    Event,
45500cea7S猫头猫    State,
55500cea7S猫头猫    Track,
65500cea7S猫头猫    TrackMetadataBase,
75500cea7S猫头猫    usePlaybackState,
85500cea7S猫头猫    useProgress,
95500cea7S猫头猫} from 'react-native-track-player';
105500cea7S猫头猫import shuffle from 'lodash.shuffle';
115500cea7S猫头猫import Config from '../config';
125500cea7S猫头猫import {
135500cea7S猫头猫    EDeviceEvents,
145500cea7S猫头猫    internalFakeSoundKey,
155500cea7S猫头猫    sortIndexSymbol,
165500cea7S猫头猫    timeStampSymbol,
175500cea7S猫头猫} from '@/constants/commonConst';
185500cea7S猫头猫import {GlobalState} from '@/utils/stateMapper';
195500cea7S猫头猫import delay from '@/utils/delay';
205500cea7S猫头猫import {
215500cea7S猫头猫    isSameMediaItem,
225500cea7S猫头猫    mergeProps,
235500cea7S猫头猫    sortByTimestampAndIndex,
245500cea7S猫头猫} from '@/utils/mediaItem';
255500cea7S猫头猫import Network from '../network';
265500cea7S猫头猫import LocalMusicSheet from '../localMusicSheet';
275500cea7S猫头猫import {SoundAsset} from '@/constants/assetsConst';
285500cea7S猫头猫import {getQualityOrder} from '@/utils/qualities';
295500cea7S猫头猫import musicHistory from '../musicHistory';
305500cea7S猫头猫import getUrlExt from '@/utils/getUrlExt';
315500cea7S猫头猫import {DeviceEventEmitter} from 'react-native';
325500cea7S猫头猫import LyricManager from '../lyricManager';
335500cea7S猫头猫import {MusicRepeatMode} from './common';
345500cea7S猫头猫import {
355500cea7S猫头猫    getMusicIndex,
365500cea7S猫头猫    getPlayList,
375500cea7S猫头猫    getPlayListMusicAt,
385500cea7S猫头猫    isInPlayList,
395500cea7S猫头猫    isPlayListEmpty,
405500cea7S猫头猫    setPlayList,
415500cea7S猫头猫    usePlayList,
425500cea7S猫头猫} from './internal/playList';
435500cea7S猫头猫import {createMediaIndexMap} from '@/utils/mediaIndexMap';
445500cea7S猫头猫import PluginManager from '../pluginManager';
455500cea7S猫头猫import {musicIsPaused} from '@/utils/trackUtils';
465500cea7S猫头猫
475500cea7S猫头猫/** 当前播放 */
485500cea7S猫头猫const currentMusicStore = new GlobalState<IMusic.IMusicItem | null>(null);
495500cea7S猫头猫
505500cea7S猫头猫/** 播放模式 */
515500cea7S猫头猫const repeatModeStore = new GlobalState<MusicRepeatMode>(MusicRepeatMode.QUEUE);
525500cea7S猫头猫
535500cea7S猫头猫/** 音质 */
545500cea7S猫头猫const qualityStore = new GlobalState<IMusic.IQualityKey>('standard');
555500cea7S猫头猫
565500cea7S猫头猫let currentIndex = -1;
575500cea7S猫头猫
585500cea7S猫头猫// TODO: 下个版本最大限制调大一些
595500cea7S猫头猫const maxMusicQueueLength = 1500; // 当前播放最大限制
605500cea7S猫头猫const halfMaxMusicQueueLength = Math.floor(maxMusicQueueLength / 2);
615500cea7S猫头猫const shrinkPlayListToSize = (
625500cea7S猫头猫    queue: IMusic.IMusicItem[],
635500cea7S猫头猫    targetIndex = currentIndex,
645500cea7S猫头猫) => {
655500cea7S猫头猫    // 播放列表上限,太多无法缓存状态
665500cea7S猫头猫    if (queue.length > maxMusicQueueLength) {
675500cea7S猫头猫        if (targetIndex < halfMaxMusicQueueLength) {
685500cea7S猫头猫            queue = queue.slice(0, maxMusicQueueLength);
695500cea7S猫头猫        } else {
705500cea7S猫头猫            const right = Math.min(
715500cea7S猫头猫                queue.length,
725500cea7S猫头猫                targetIndex + halfMaxMusicQueueLength,
735500cea7S猫头猫            );
745500cea7S猫头猫            const left = Math.max(0, right - maxMusicQueueLength);
755500cea7S猫头猫            queue = queue.slice(left, right);
765500cea7S猫头猫        }
775500cea7S猫头猫    }
785500cea7S猫头猫    return queue;
795500cea7S猫头猫};
805500cea7S猫头猫
815500cea7S猫头猫async function setupTrackPlayer() {
825500cea7S猫头猫    const config = Config.get('status.music') ?? {};
835500cea7S猫头猫    console.log('config!!', config);
845500cea7S猫头猫    const {rate, repeatMode, musicQueue, progress, track} = config;
855500cea7S猫头猫
865500cea7S猫头猫    // 状态恢复
875500cea7S猫头猫    if (rate) {
885500cea7S猫头猫        await ReactNativeTrackPlayer.setRate(+rate / 100);
895500cea7S猫头猫    }
905500cea7S猫头猫
915500cea7S猫头猫    if (musicQueue && Array.isArray(musicQueue)) {
925500cea7S猫头猫        addAll(musicQueue, undefined, repeatMode === MusicRepeatMode.SHUFFLE);
935500cea7S猫头猫    }
945500cea7S猫头猫
955500cea7S猫头猫    const currentQuality =
965500cea7S猫头猫        Config.get('setting.basic.defaultPlayQuality') ?? 'standard';
975500cea7S猫头猫
985500cea7S猫头猫    if (track && isInPlayList(track)) {
995500cea7S猫头猫        const newSource = await PluginManager.getByMedia(
1005500cea7S猫头猫            track,
1015500cea7S猫头猫        )?.methods.getMediaSource(track, currentQuality, 0);
1025500cea7S猫头猫        // 重新初始化 获取最新的链接
1035500cea7S猫头猫        track.url = newSource?.url || track.url;
1045500cea7S猫头猫        track.headers = newSource?.headers || track.headers;
1055500cea7S猫头猫
1065500cea7S猫头猫        await setTrackSource(track as Track, false);
1075500cea7S猫头猫        setCurrentMusic(track);
1085500cea7S猫头猫
1095500cea7S猫头猫        if (config?.progress) {
1105500cea7S猫头猫            await ReactNativeTrackPlayer.seekTo(progress!);
1115500cea7S猫头猫        }
1125500cea7S猫头猫    }
1135500cea7S猫头猫
1145500cea7S猫头猫    // 初始化事件
1155500cea7S猫头猫    ReactNativeTrackPlayer.addEventListener(
1165500cea7S猫头猫        Event.PlaybackActiveTrackChanged,
1175500cea7S猫头猫        async evt => {
1185500cea7S猫头猫            if (
1195500cea7S猫头猫                evt.index === 1 &&
1205500cea7S猫头猫                evt.lastIndex === 0 &&
1215500cea7S猫头猫                evt.track?.$ === internalFakeSoundKey
1225500cea7S猫头猫            ) {
1235500cea7S猫头猫                if (repeatModeStore.getValue() === MusicRepeatMode.SINGLE) {
1245500cea7S猫头猫                    await play(null, true);
1255500cea7S猫头猫                } else {
1265500cea7S猫头猫                    // 当前生效的歌曲是下一曲的标记
1275500cea7S猫头猫                    await skipToNext('队列结尾');
1285500cea7S猫头猫                }
1295500cea7S猫头猫            }
1305500cea7S猫头猫        },
1315500cea7S猫头猫    );
1325500cea7S猫头猫
1335500cea7S猫头猫    ReactNativeTrackPlayer.addEventListener(Event.PlaybackError, async () => {
1345500cea7S猫头猫        // 只关心第一个元素
1355500cea7S猫头猫        if ((await ReactNativeTrackPlayer.getActiveTrackIndex()) === 0) {
1365500cea7S猫头猫            failToPlay();
1375500cea7S猫头猫        }
1385500cea7S猫头猫    });
1395500cea7S猫头猫}
1405500cea7S猫头猫
1415500cea7S猫头猫/**
1425500cea7S猫头猫 * 获取自动播放的下一个track
1435500cea7S猫头猫 */
1445500cea7S猫头猫const getFakeNextTrack = () => {
1455500cea7S猫头猫    let track: Track | undefined;
1465500cea7S猫头猫    const repeatMode = repeatModeStore.getValue();
1475500cea7S猫头猫    if (repeatMode === MusicRepeatMode.SINGLE) {
1485500cea7S猫头猫        // 单曲循环
1495500cea7S猫头猫        track = getPlayListMusicAt(currentIndex) as Track;
1505500cea7S猫头猫    } else {
1515500cea7S猫头猫        // 下一曲
1525500cea7S猫头猫        track = getPlayListMusicAt(currentIndex + 1) as Track;
1535500cea7S猫头猫    }
1545500cea7S猫头猫
1555500cea7S猫头猫    if (track) {
1565500cea7S猫头猫        return produce(track, _ => {
1575500cea7S猫头猫            _.url = SoundAsset.fakeAudio;
1585500cea7S猫头猫            _.$ = internalFakeSoundKey;
1595500cea7S猫头猫        });
1605500cea7S猫头猫    } else {
1615500cea7S猫头猫        // 只有列表长度为0时才会出现的特殊情况
1625500cea7S猫头猫        return {url: SoundAsset.fakeAudio, $: internalFakeSoundKey} as Track;
1635500cea7S猫头猫    }
1645500cea7S猫头猫};
1655500cea7S猫头猫
1665500cea7S猫头猫/** 播放失败时的情况 */
1675500cea7S猫头猫async function failToPlay(reason?: string) {
1685500cea7S猫头猫    // 如果自动跳转下一曲, 500s后自动跳转
1695500cea7S猫头猫    if (!Config.get('setting.basic.autoStopWhenError')) {
1705500cea7S猫头猫        await ReactNativeTrackPlayer.reset();
1715500cea7S猫头猫        await delay(500);
1725500cea7S猫头猫        await skipToNext('播放失败' + reason);
1735500cea7S猫头猫    }
1745500cea7S猫头猫}
1755500cea7S猫头猫
1765500cea7S猫头猫// 播放模式相关
1775500cea7S猫头猫const _toggleRepeatMapping = {
1785500cea7S猫头猫    [MusicRepeatMode.SHUFFLE]: MusicRepeatMode.SINGLE,
1795500cea7S猫头猫    [MusicRepeatMode.SINGLE]: MusicRepeatMode.QUEUE,
1805500cea7S猫头猫    [MusicRepeatMode.QUEUE]: MusicRepeatMode.SHUFFLE,
1815500cea7S猫头猫};
1825500cea7S猫头猫/** 切换下一个模式 */
1835500cea7S猫头猫const toggleRepeatMode = () => {
1845500cea7S猫头猫    setRepeatMode(_toggleRepeatMapping[repeatModeStore.getValue()]);
1855500cea7S猫头猫};
1865500cea7S猫头猫
1875500cea7S猫头猫/** 设置音源 */
1885500cea7S猫头猫const setTrackSource = async (track: Track, autoPlay = true) => {
1895500cea7S猫头猫    await ReactNativeTrackPlayer.setQueue([track, getFakeNextTrack()]);
1905500cea7S猫头猫    if (autoPlay) {
1915500cea7S猫头猫        await ReactNativeTrackPlayer.play();
1925500cea7S猫头猫    }
1935500cea7S猫头猫    // 写缓存 TODO: MMKV
1945500cea7S猫头猫    Config.set('status.music.track', track as IMusic.IMusicItem, false);
1955500cea7S猫头猫    Config.set('status.music.progress', 0, false);
1965500cea7S猫头猫};
1975500cea7S猫头猫
1985500cea7S猫头猫/**
1995500cea7S猫头猫 * 添加到播放列表
2005500cea7S猫头猫 * @param musicItems 目标歌曲
2015500cea7S猫头猫 * @param beforeIndex 在第x首歌曲前添加
2025500cea7S猫头猫 * @param shouldShuffle 随机排序
2035500cea7S猫头猫 */
2045500cea7S猫头猫const addAll = (
2055500cea7S猫头猫    musicItems: Array<IMusic.IMusicItem> = [],
2065500cea7S猫头猫    beforeIndex?: number,
2075500cea7S猫头猫    shouldShuffle?: boolean,
2085500cea7S猫头猫) => {
2095500cea7S猫头猫    const now = Date.now();
2105500cea7S猫头猫    let newPlayList: IMusic.IMusicItem[] = [];
2115500cea7S猫头猫    let currentPlayList = getPlayList();
2125500cea7S猫头猫    const _musicItems = musicItems.map((item, index) =>
2135500cea7S猫头猫        produce(item, draft => {
2145500cea7S猫头猫            draft[timeStampSymbol] = now;
2155500cea7S猫头猫            draft[sortIndexSymbol] = index;
2165500cea7S猫头猫        }),
2175500cea7S猫头猫    );
2185500cea7S猫头猫    if (beforeIndex === undefined || beforeIndex < 0) {
2195500cea7S猫头猫        // 1.1. 添加到歌单末尾,并过滤掉已有的歌曲
2205500cea7S猫头猫        newPlayList = currentPlayList.concat(
2215500cea7S猫头猫            _musicItems.filter(item => !isInPlayList(item)),
2225500cea7S猫头猫        );
2235500cea7S猫头猫    } else {
2245500cea7S猫头猫        // 1.2. 新的播放列表,插入
2255500cea7S猫头猫        const indexMap = createMediaIndexMap(_musicItems);
2265500cea7S猫头猫        const beforeDraft = currentPlayList
2275500cea7S猫头猫            .slice(0, beforeIndex)
2285500cea7S猫头猫            .filter(item => !indexMap.has(item));
2295500cea7S猫头猫        const afterDraft = currentPlayList
2305500cea7S猫头猫            .slice(beforeIndex)
2315500cea7S猫头猫            .filter(item => !indexMap.has(item));
2325500cea7S猫头猫
2335500cea7S猫头猫        newPlayList = [...beforeDraft, ..._musicItems, ...afterDraft];
2345500cea7S猫头猫    }
235*15900d05S猫头猫
2365500cea7S猫头猫    // 如果太长了
2375500cea7S猫头猫    if (newPlayList.length > maxMusicQueueLength) {
2385500cea7S猫头猫        newPlayList = shrinkPlayListToSize(
2395500cea7S猫头猫            newPlayList,
2405500cea7S猫头猫            beforeIndex ?? newPlayList.length - 1,
2415500cea7S猫头猫        );
2425500cea7S猫头猫    }
2435500cea7S猫头猫
2445500cea7S猫头猫    // 2. 如果需要随机
2455500cea7S猫头猫    if (shouldShuffle) {
2465500cea7S猫头猫        newPlayList = shuffle(newPlayList);
2475500cea7S猫头猫    }
2485500cea7S猫头猫    // 3. 设置播放列表
2495500cea7S猫头猫    setPlayList(newPlayList);
2505500cea7S猫头猫    const currentMusicItem = currentMusicStore.getValue();
2515500cea7S猫头猫
2525500cea7S猫头猫    // 4. 重置下标
2535500cea7S猫头猫    if (currentMusicItem) {
2545500cea7S猫头猫        currentIndex = getMusicIndex(currentMusicItem);
2555500cea7S猫头猫    }
2565500cea7S猫头猫
2575500cea7S猫头猫    // TODO: 更新播放队列信息
2585500cea7S猫头猫    // 5. 存储更新的播放列表信息
2595500cea7S猫头猫};
2605500cea7S猫头猫
2615500cea7S猫头猫/** 追加到队尾 */
2625500cea7S猫头猫const add = (
2635500cea7S猫头猫    musicItem: IMusic.IMusicItem | IMusic.IMusicItem[],
2645500cea7S猫头猫    beforeIndex?: number,
2655500cea7S猫头猫) => {
2665500cea7S猫头猫    addAll(Array.isArray(musicItem) ? musicItem : [musicItem], beforeIndex);
2675500cea7S猫头猫};
2685500cea7S猫头猫
2695500cea7S猫头猫/**
2705500cea7S猫头猫 * 下一首播放
2715500cea7S猫头猫 * @param musicItem
2725500cea7S猫头猫 */
2735500cea7S猫头猫const addNext = (musicItem: IMusic.IMusicItem | IMusic.IMusicItem[]) => {
2745500cea7S猫头猫    const shouldPlay = isPlayListEmpty();
2755500cea7S猫头猫    add(musicItem, currentIndex + 1);
2765500cea7S猫头猫    if (shouldPlay) {
2775500cea7S猫头猫        play(Array.isArray(musicItem) ? musicItem[0] : musicItem);
2785500cea7S猫头猫    }
2795500cea7S猫头猫};
2805500cea7S猫头猫
2815500cea7S猫头猫const isCurrentMusic = (musicItem: IMusic.IMusicItem) => {
2825500cea7S猫头猫    return isSameMediaItem(musicItem, currentMusicStore.getValue()) ?? false;
2835500cea7S猫头猫};
2845500cea7S猫头猫
2855500cea7S猫头猫const remove = async (musicItem: IMusic.IMusicItem) => {
2865500cea7S猫头猫    const playList = getPlayList();
2875500cea7S猫头猫    let newPlayList: IMusic.IMusicItem[] = [];
2885500cea7S猫头猫    let currentMusic: IMusic.IMusicItem | null = currentMusicStore.getValue();
2895500cea7S猫头猫    const targetIndex = getMusicIndex(musicItem);
2905500cea7S猫头猫    let shouldPlayCurrent: boolean | null = null;
2915500cea7S猫头猫    if (targetIndex === -1) {
2925500cea7S猫头猫        // 1. 这种情况应该是出错了
2935500cea7S猫头猫        return;
2945500cea7S猫头猫    }
2955500cea7S猫头猫    // 2. 移除的是当前项
2965500cea7S猫头猫    if (currentIndex === targetIndex) {
2975500cea7S猫头猫        // 2.1 停止播放,移除当前项
2985500cea7S猫头猫        newPlayList = produce(playList, draft => {
2995500cea7S猫头猫            draft.splice(targetIndex, 1);
3005500cea7S猫头猫        });
3015500cea7S猫头猫        // 2.2 设置新的播放列表,并更新当前音乐
3025500cea7S猫头猫        if (newPlayList.length === 0) {
3035500cea7S猫头猫            currentMusic = null;
3045500cea7S猫头猫            shouldPlayCurrent = false;
3055500cea7S猫头猫        } else {
3065500cea7S猫头猫            currentMusic = newPlayList[currentIndex % newPlayList.length];
3075500cea7S猫头猫            try {
3085500cea7S猫头猫                const state = (await ReactNativeTrackPlayer.getPlaybackState())
3095500cea7S猫头猫                    .state;
3105500cea7S猫头猫                if (musicIsPaused(state)) {
3115500cea7S猫头猫                    shouldPlayCurrent = false;
3125500cea7S猫头猫                } else {
3135500cea7S猫头猫                    shouldPlayCurrent = true;
3145500cea7S猫头猫                }
3155500cea7S猫头猫            } catch {
3165500cea7S猫头猫                shouldPlayCurrent = false;
3175500cea7S猫头猫            }
3185500cea7S猫头猫        }
3195500cea7S猫头猫    } else {
3205500cea7S猫头猫        // 3. 删除
3215500cea7S猫头猫        newPlayList = produce(playList, draft => {
3225500cea7S猫头猫            draft.splice(targetIndex, 1);
3235500cea7S猫头猫        });
3245500cea7S猫头猫    }
3255500cea7S猫头猫
3265500cea7S猫头猫    setPlayList(newPlayList);
3275500cea7S猫头猫    setCurrentMusic(currentMusic);
3285500cea7S猫头猫    Config.set('status.music.musicQueue', playList, false);
3295500cea7S猫头猫    if (shouldPlayCurrent === true) {
3305500cea7S猫头猫        await play(currentMusic, true);
3315500cea7S猫头猫    } else if (shouldPlayCurrent === false) {
3325500cea7S猫头猫        await ReactNativeTrackPlayer.reset();
3335500cea7S猫头猫    }
3345500cea7S猫头猫};
3355500cea7S猫头猫
3365500cea7S猫头猫/**
3375500cea7S猫头猫 * 设置播放模式
3385500cea7S猫头猫 * @param mode 播放模式
3395500cea7S猫头猫 */
3405500cea7S猫头猫const setRepeatMode = (mode: MusicRepeatMode) => {
3415500cea7S猫头猫    const playList = getPlayList();
3425500cea7S猫头猫    let newPlayList;
3435500cea7S猫头猫    if (mode === MusicRepeatMode.SHUFFLE) {
3445500cea7S猫头猫        newPlayList = shuffle(playList);
3455500cea7S猫头猫    } else {
3465500cea7S猫头猫        newPlayList = produce(playList, draft => {
3475500cea7S猫头猫            return sortByTimestampAndIndex(draft);
3485500cea7S猫头猫        });
3495500cea7S猫头猫    }
3505500cea7S猫头猫
3515500cea7S猫头猫    setPlayList(newPlayList);
3525500cea7S猫头猫    const currentMusicItem = currentMusicStore.getValue();
3535500cea7S猫头猫    currentIndex = getMusicIndex(currentMusicItem);
3545500cea7S猫头猫    repeatModeStore.setValue(mode);
3555500cea7S猫头猫    // 更新下一首歌的信息
3565500cea7S猫头猫    ReactNativeTrackPlayer.updateMetadataForTrack(1, getFakeNextTrack());
3575500cea7S猫头猫    // 记录
3585500cea7S猫头猫    Config.set('status.music.repeatMode', mode, false);
3595500cea7S猫头猫};
3605500cea7S猫头猫
3615500cea7S猫头猫/** 清空播放列表 */
3625500cea7S猫头猫const clear = async () => {
3635500cea7S猫头猫    setPlayList([]);
3645500cea7S猫头猫    setCurrentMusic(null);
3655500cea7S猫头猫
3665500cea7S猫头猫    await ReactNativeTrackPlayer.reset();
3675500cea7S猫头猫    Config.set('status.music', {
3685500cea7S猫头猫        repeatMode: repeatModeStore.getValue(),
3695500cea7S猫头猫    });
3705500cea7S猫头猫};
3715500cea7S猫头猫
3725500cea7S猫头猫/** 暂停 */
3735500cea7S猫头猫const pause = async () => {
3745500cea7S猫头猫    await ReactNativeTrackPlayer.pause();
3755500cea7S猫头猫};
3765500cea7S猫头猫
3775500cea7S猫头猫const setCurrentMusic = (musicItem?: IMusic.IMusicItem | null) => {
3785500cea7S猫头猫    if (!musicItem) {
3795500cea7S猫头猫        currentMusicStore.setValue(null);
3805500cea7S猫头猫        currentIndex = -1;
3815500cea7S猫头猫    }
3825500cea7S猫头猫
3835500cea7S猫头猫    currentMusicStore.setValue(musicItem!);
3845500cea7S猫头猫    currentIndex = getMusicIndex(musicItem);
3855500cea7S猫头猫};
3865500cea7S猫头猫
3875500cea7S猫头猫/**
3885500cea7S猫头猫 * 播放
3895500cea7S猫头猫 *
3905500cea7S猫头猫 * 当musicItem 为空时,代表暂停/播放
3915500cea7S猫头猫 *
3925500cea7S猫头猫 * @param musicItem
3935500cea7S猫头猫 * @param forcePlay
3945500cea7S猫头猫 * @returns
3955500cea7S猫头猫 */
3965500cea7S猫头猫const play = async (
3975500cea7S猫头猫    musicItem?: IMusic.IMusicItem | null,
3985500cea7S猫头猫    forcePlay?: boolean,
3995500cea7S猫头猫) => {
4005500cea7S猫头猫    try {
4015500cea7S猫头猫        if (!musicItem) {
4025500cea7S猫头猫            musicItem = currentMusicStore.getValue();
4035500cea7S猫头猫        }
4045500cea7S猫头猫        if (!musicItem) {
4055500cea7S猫头猫            throw new Error(PlayFailReason.PLAY_LIST_IS_EMPTY);
4065500cea7S猫头猫        }
4075500cea7S猫头猫        // 1. 移动网络禁止播放
4085500cea7S猫头猫        if (
4095500cea7S猫头猫            Network.isCellular() &&
4105500cea7S猫头猫            !Config.get('setting.basic.useCelluarNetworkPlay') &&
4115500cea7S猫头猫            !LocalMusicSheet.isLocalMusic(musicItem)
4125500cea7S猫头猫        ) {
4135500cea7S猫头猫            await ReactNativeTrackPlayer.reset();
4145500cea7S猫头猫            throw new Error(PlayFailReason.FORBID_CELLUAR_NETWORK_PLAY);
4155500cea7S猫头猫        }
4165500cea7S猫头猫
4175500cea7S猫头猫        // 2. 如果是当前正在播放的音频
4185500cea7S猫头猫        if (isCurrentMusic(musicItem)) {
4195500cea7S猫头猫            const currentTrack = await ReactNativeTrackPlayer.getTrack(0);
4205500cea7S猫头猫            // 2.1 如果当前有源
4215500cea7S猫头猫            if (
4225500cea7S猫头猫                currentTrack?.url &&
4235500cea7S猫头猫                isSameMediaItem(musicItem, currentTrack as IMusic.IMusicItem)
4245500cea7S猫头猫            ) {
4255500cea7S猫头猫                const currentActiveIndex =
4265500cea7S猫头猫                    await ReactNativeTrackPlayer.getActiveTrackIndex();
4275500cea7S猫头猫                if (currentActiveIndex !== 0) {
4285500cea7S猫头猫                    await ReactNativeTrackPlayer.skip(0);
4295500cea7S猫头猫                }
4305500cea7S猫头猫                if (forcePlay) {
4315500cea7S猫头猫                    // 2.1.1 强制重新开始
4325500cea7S猫头猫                    await ReactNativeTrackPlayer.seekTo(0);
4335500cea7S猫头猫                } else if (
4345500cea7S猫头猫                    (await ReactNativeTrackPlayer.getPlaybackState()).state !==
4355500cea7S猫头猫                    State.Playing
4365500cea7S猫头猫                ) {
4375500cea7S猫头猫                    // 2.1.2 恢复播放
4385500cea7S猫头猫                    await ReactNativeTrackPlayer.play();
4395500cea7S猫头猫                }
4405500cea7S猫头猫                // 这种情况下,播放队列和当前歌曲都不需要变化
4415500cea7S猫头猫                return;
4425500cea7S猫头猫            }
4435500cea7S猫头猫            // 2.2 其他情况:重新获取源
4445500cea7S猫头猫        }
4455500cea7S猫头猫
4465500cea7S猫头猫        // 3. 如果没有在播放列表中,添加到队尾;同时更新列表状态
4475500cea7S猫头猫        const inPlayList = isInPlayList(musicItem);
4485500cea7S猫头猫        if (!inPlayList) {
4495500cea7S猫头猫            add(musicItem);
4505500cea7S猫头猫        }
4515500cea7S猫头猫
4525500cea7S猫头猫        // 4. 更新列表状态和当前音乐
4535500cea7S猫头猫        setCurrentMusic(musicItem);
4545500cea7S猫头猫
4555500cea7S猫头猫        // 5. 获取音源
4565500cea7S猫头猫        let track: IMusic.IMusicItem;
4575500cea7S猫头猫
4585500cea7S猫头猫        // 5.1 通过插件获取音源
4595500cea7S猫头猫        const plugin = PluginManager.getByName(musicItem.platform);
4605500cea7S猫头猫        // 5.2 获取音质排序
4615500cea7S猫头猫        const qualityOrder = getQualityOrder(
4625500cea7S猫头猫            Config.get('setting.basic.defaultPlayQuality') ?? 'standard',
4635500cea7S猫头猫            Config.get('setting.basic.playQualityOrder') ?? 'asc',
4645500cea7S猫头猫        );
4655500cea7S猫头猫        // 5.3 插件返回音源
4665500cea7S猫头猫        let source: IPlugin.IMediaSourceResult | null = null;
4675500cea7S猫头猫        for (let quality of qualityOrder) {
4685500cea7S猫头猫            if (isCurrentMusic(musicItem)) {
4695500cea7S猫头猫                source =
4705500cea7S猫头猫                    (await plugin?.methods?.getMediaSource(
4715500cea7S猫头猫                        musicItem,
4725500cea7S猫头猫                        quality,
4735500cea7S猫头猫                    )) ?? null;
4745500cea7S猫头猫                // 5.3.1 获取到真实源
4755500cea7S猫头猫                if (source) {
4765500cea7S猫头猫                    qualityStore.setValue(quality);
4775500cea7S猫头猫                    break;
4785500cea7S猫头猫                }
4795500cea7S猫头猫            } else {
4805500cea7S猫头猫                // 5.3.2 已经切换到其他歌曲了,
4815500cea7S猫头猫                return;
4825500cea7S猫头猫            }
4835500cea7S猫头猫        }
4845500cea7S猫头猫
4855500cea7S猫头猫        if (!isCurrentMusic(musicItem)) {
4865500cea7S猫头猫            return;
4875500cea7S猫头猫        }
4885500cea7S猫头猫
4895500cea7S猫头猫        if (!source) {
4905500cea7S猫头猫            // 5.4 没有返回源
4915500cea7S猫头猫            if (!musicItem.url) {
4925500cea7S猫头猫                throw new Error(PlayFailReason.INVALID_SOURCE);
4935500cea7S猫头猫            }
4945500cea7S猫头猫            source = {
4955500cea7S猫头猫                url: musicItem.url,
4965500cea7S猫头猫            };
4975500cea7S猫头猫            qualityStore.setValue('standard');
4985500cea7S猫头猫        }
4995500cea7S猫头猫
5005500cea7S猫头猫        // 6. 特殊类型源
5015500cea7S猫头猫        if (getUrlExt(source.url) === '.m3u8') {
5025500cea7S猫头猫            // @ts-ignore
5035500cea7S猫头猫            source.type = 'hls';
5045500cea7S猫头猫        }
5055500cea7S猫头猫        // 7. 合并结果
5065500cea7S猫头猫        track = mergeProps(musicItem, source) as IMusic.IMusicItem;
5075500cea7S猫头猫
5085500cea7S猫头猫        // 8. 新增历史记录
5095500cea7S猫头猫        musicHistory.addMusic(musicItem);
5105500cea7S猫头猫
5115500cea7S猫头猫        // 9. 设置音源
5125500cea7S猫头猫        await setTrackSource(track as Track);
5135500cea7S猫头猫
5145500cea7S猫头猫        // 10. 获取补充信息
5155500cea7S猫头猫        let info: Partial<IMusic.IMusicItem> | null = null;
5165500cea7S猫头猫        try {
5175500cea7S猫头猫            info = (await plugin?.methods?.getMusicInfo?.(musicItem)) ?? null;
5185500cea7S猫头猫        } catch {}
5195500cea7S猫头猫
5205500cea7S猫头猫        // 11. 设置补充信息
5215500cea7S猫头猫        if (info && isCurrentMusic(musicItem)) {
5225500cea7S猫头猫            const mergedTrack = mergeProps(track, info);
5235500cea7S猫头猫            currentMusicStore.setValue(mergedTrack as IMusic.IMusicItem);
5245500cea7S猫头猫            await ReactNativeTrackPlayer.updateMetadataForTrack(
5255500cea7S猫头猫                0,
5265500cea7S猫头猫                mergedTrack as TrackMetadataBase,
5275500cea7S猫头猫            );
5285500cea7S猫头猫        }
5295500cea7S猫头猫
5305500cea7S猫头猫        // 12. 刷新歌词信息
5315500cea7S猫头猫        if (
5325500cea7S猫头猫            !isSameMediaItem(
5335500cea7S猫头猫                LyricManager.getLyricState()?.lyricParser?.getCurrentMusicItem?.(),
5345500cea7S猫头猫                musicItem,
5355500cea7S猫头猫            )
5365500cea7S猫头猫        ) {
5375500cea7S猫头猫            DeviceEventEmitter.emit(EDeviceEvents.REFRESH_LYRIC, true);
5385500cea7S猫头猫        }
5395500cea7S猫头猫    } catch (e: any) {
5405500cea7S猫头猫        const message = e?.message;
5415500cea7S猫头猫        if (
5425500cea7S猫头猫            message === 'The player is not initialized. Call setupPlayer first.'
5435500cea7S猫头猫        ) {
5445500cea7S猫头猫            await ReactNativeTrackPlayer.setupPlayer();
5455500cea7S猫头猫            play(musicItem, forcePlay);
5465500cea7S猫头猫        } else if (message === PlayFailReason.FORBID_CELLUAR_NETWORK_PLAY) {
5475500cea7S猫头猫        } else if (message === PlayFailReason.INVALID_SOURCE) {
5485500cea7S猫头猫            await failToPlay('无效源');
5495500cea7S猫头猫        } else if (message === PlayFailReason.PLAY_LIST_IS_EMPTY) {
5505500cea7S猫头猫            // 队列是空的,不应该出现这种情况
5515500cea7S猫头猫        }
5525500cea7S猫头猫    }
5535500cea7S猫头猫};
5545500cea7S猫头猫
5555500cea7S猫头猫/**
5565500cea7S猫头猫 * 播放音乐,同时替换播放队列
5575500cea7S猫头猫 * @param musicItem 音乐
5585500cea7S猫头猫 * @param newPlayList 替代列表
5595500cea7S猫头猫 */
5605500cea7S猫头猫const playWithReplacePlayList = async (
5615500cea7S猫头猫    musicItem: IMusic.IMusicItem,
5625500cea7S猫头猫    newPlayList: IMusic.IMusicItem[],
5635500cea7S猫头猫) => {
5645500cea7S猫头猫    if (newPlayList.length !== 0) {
5655500cea7S猫头猫        const now = Date.now();
5665500cea7S猫头猫        if (newPlayList.length > maxMusicQueueLength) {
5675500cea7S猫头猫            newPlayList = shrinkPlayListToSize(
5685500cea7S猫头猫                newPlayList,
5695500cea7S猫头猫                newPlayList.findIndex(it => isSameMediaItem(it, musicItem)),
5705500cea7S猫头猫            );
5715500cea7S猫头猫        }
5725500cea7S猫头猫        const playListItems = newPlayList.map((item, index) =>
5735500cea7S猫头猫            produce(item, draft => {
5745500cea7S猫头猫                draft[timeStampSymbol] = now;
5755500cea7S猫头猫                draft[sortIndexSymbol] = index;
5765500cea7S猫头猫            }),
5775500cea7S猫头猫        );
5785500cea7S猫头猫        setPlayList(
5795500cea7S猫头猫            repeatModeStore.getValue() === MusicRepeatMode.SHUFFLE
5805500cea7S猫头猫                ? shuffle(playListItems)
5815500cea7S猫头猫                : playListItems,
5825500cea7S猫头猫        );
5835500cea7S猫头猫        await play(musicItem, true);
5845500cea7S猫头猫    }
5855500cea7S猫头猫};
5865500cea7S猫头猫
5875500cea7S猫头猫const skipToNext = async (reason?: string) => {
5885500cea7S猫头猫    console.log(
5895500cea7S猫头猫        'SkipToNext',
5905500cea7S猫头猫        reason,
5915500cea7S猫头猫        await ReactNativeTrackPlayer.getActiveTrack(),
5925500cea7S猫头猫    );
5935500cea7S猫头猫    if (isPlayListEmpty()) {
5945500cea7S猫头猫        setCurrentMusic(null);
5955500cea7S猫头猫        return;
5965500cea7S猫头猫    }
5975500cea7S猫头猫
5985500cea7S猫头猫    await play(getPlayListMusicAt(currentIndex + 1), true);
5995500cea7S猫头猫};
6005500cea7S猫头猫
6015500cea7S猫头猫const skipToPrevious = async () => {
6025500cea7S猫头猫    if (isPlayListEmpty()) {
6035500cea7S猫头猫        setCurrentMusic(null);
6045500cea7S猫头猫        return;
6055500cea7S猫头猫    }
6065500cea7S猫头猫
6075500cea7S猫头猫    await play(getPlayListMusicAt(currentIndex === -1 ? 0 : currentIndex - 1));
6085500cea7S猫头猫};
6095500cea7S猫头猫
6105500cea7S猫头猫/** 修改当前播放的音质 */
6115500cea7S猫头猫const changeQuality = async (newQuality: IMusic.IQualityKey) => {
6125500cea7S猫头猫    // 获取当前的音乐和进度
6135500cea7S猫头猫    if (newQuality === qualityStore.getValue()) {
6145500cea7S猫头猫        return true;
6155500cea7S猫头猫    }
6165500cea7S猫头猫
6175500cea7S猫头猫    // 获取当前歌曲
6185500cea7S猫头猫    const musicItem = currentMusicStore.getValue();
6195500cea7S猫头猫    if (!musicItem) {
6205500cea7S猫头猫        return false;
6215500cea7S猫头猫    }
6225500cea7S猫头猫    try {
6235500cea7S猫头猫        const progress = await ReactNativeTrackPlayer.getProgress();
6245500cea7S猫头猫        const plugin = PluginManager.getByMedia(musicItem);
6255500cea7S猫头猫        const newSource = await plugin?.methods?.getMediaSource(
6265500cea7S猫头猫            musicItem,
6275500cea7S猫头猫            newQuality,
6285500cea7S猫头猫        );
6295500cea7S猫头猫        if (!newSource?.url) {
6305500cea7S猫头猫            throw new Error(PlayFailReason.INVALID_SOURCE);
6315500cea7S猫头猫        }
6325500cea7S猫头猫        if (isCurrentMusic(musicItem)) {
6335500cea7S猫头猫            const playingState = (
6345500cea7S猫头猫                await ReactNativeTrackPlayer.getPlaybackState()
6355500cea7S猫头猫            ).state;
6365500cea7S猫头猫            await setTrackSource(
6375500cea7S猫头猫                mergeProps(musicItem, newSource) as unknown as Track,
6385500cea7S猫头猫                !musicIsPaused(playingState),
6395500cea7S猫头猫            );
6405500cea7S猫头猫
6415500cea7S猫头猫            await ReactNativeTrackPlayer.seekTo(progress.position ?? 0);
6425500cea7S猫头猫            qualityStore.setValue(newQuality);
6435500cea7S猫头猫        }
6445500cea7S猫头猫        return true;
6455500cea7S猫头猫    } catch {
6465500cea7S猫头猫        // 修改失败
6475500cea7S猫头猫        return false;
6485500cea7S猫头猫    }
6495500cea7S猫头猫};
6505500cea7S猫头猫
6515500cea7S猫头猫enum PlayFailReason {
6525500cea7S猫头猫    /** 禁止移动网络播放 */
6535500cea7S猫头猫    FORBID_CELLUAR_NETWORK_PLAY = 'FORBID_CELLUAR_NETWORK_PLAY',
6545500cea7S猫头猫    /** 播放列表为空 */
6555500cea7S猫头猫    PLAY_LIST_IS_EMPTY = 'PLAY_LIST_IS_EMPTY',
6565500cea7S猫头猫    /** 无效源 */
6575500cea7S猫头猫    INVALID_SOURCE = 'INVALID_SOURCE',
6585500cea7S猫头猫    /** 非当前音乐 */
6595500cea7S猫头猫}
6605500cea7S猫头猫
6615500cea7S猫头猫function useMusicState() {
6625500cea7S猫头猫    const playbackState = usePlaybackState();
6635500cea7S猫头猫
6645500cea7S猫头猫    return playbackState.state;
6655500cea7S猫头猫}
6665500cea7S猫头猫
6675500cea7S猫头猫const TrackPlayer = {
6685500cea7S猫头猫    setupTrackPlayer,
6695500cea7S猫头猫    usePlayList,
6705500cea7S猫头猫    getPlayList,
6715500cea7S猫头猫    addAll,
6725500cea7S猫头猫    add,
6735500cea7S猫头猫    addNext,
6745500cea7S猫头猫    skipToNext,
6755500cea7S猫头猫    skipToPrevious,
6765500cea7S猫头猫    play,
6775500cea7S猫头猫    playWithReplacePlayList,
6785500cea7S猫头猫    pause,
6795500cea7S猫头猫    remove,
6805500cea7S猫头猫    clear,
6815500cea7S猫头猫    useCurrentMusic: currentMusicStore.useValue,
6825500cea7S猫头猫    getCurrentMusic: currentMusicStore.getValue,
6835500cea7S猫头猫    useRepeatMode: repeatModeStore.useValue,
6845500cea7S猫头猫    getRepeatMode: repeatModeStore.getValue,
6855500cea7S猫头猫    toggleRepeatMode,
6865500cea7S猫头猫    usePlaybackState,
6875500cea7S猫头猫    getProgress: ReactNativeTrackPlayer.getProgress,
6885500cea7S猫头猫    useProgress: useProgress,
6895500cea7S猫头猫    seekTo: ReactNativeTrackPlayer.seekTo,
6905500cea7S猫头猫    changeQuality,
6915500cea7S猫头猫    useCurrentQuality: qualityStore.useValue,
6925500cea7S猫头猫    getCurrentQuality: qualityStore.getValue,
6935500cea7S猫头猫    getRate: ReactNativeTrackPlayer.getRate,
6945500cea7S猫头猫    setRate: ReactNativeTrackPlayer.setRate,
6955500cea7S猫头猫    useMusicState,
6965500cea7S猫头猫    reset: ReactNativeTrackPlayer.reset,
6975500cea7S猫头猫};
6985500cea7S猫头猫
6995500cea7S猫头猫export default TrackPlayer;
7005500cea7S猫头猫export {MusicRepeatMode, State as MusicState};
701