xref: /MusicFree/src/core/trackPlayer/index.ts (revision 5500cea7e936041b68a2f3709a583c2f0181b9e6)
1*5500cea7S猫头猫import produce from 'immer';
2*5500cea7S猫头猫import ReactNativeTrackPlayer, {
3*5500cea7S猫头猫    Event,
4*5500cea7S猫头猫    State,
5*5500cea7S猫头猫    Track,
6*5500cea7S猫头猫    TrackMetadataBase,
7*5500cea7S猫头猫    usePlaybackState,
8*5500cea7S猫头猫    useProgress,
9*5500cea7S猫头猫} from 'react-native-track-player';
10*5500cea7S猫头猫import shuffle from 'lodash.shuffle';
11*5500cea7S猫头猫import Config from '../config';
12*5500cea7S猫头猫import {
13*5500cea7S猫头猫    EDeviceEvents,
14*5500cea7S猫头猫    internalFakeSoundKey,
15*5500cea7S猫头猫    sortIndexSymbol,
16*5500cea7S猫头猫    timeStampSymbol,
17*5500cea7S猫头猫} from '@/constants/commonConst';
18*5500cea7S猫头猫import {GlobalState} from '@/utils/stateMapper';
19*5500cea7S猫头猫import delay from '@/utils/delay';
20*5500cea7S猫头猫import {
21*5500cea7S猫头猫    isSameMediaItem,
22*5500cea7S猫头猫    mergeProps,
23*5500cea7S猫头猫    sortByTimestampAndIndex,
24*5500cea7S猫头猫} from '@/utils/mediaItem';
25*5500cea7S猫头猫import Network from '../network';
26*5500cea7S猫头猫import LocalMusicSheet from '../localMusicSheet';
27*5500cea7S猫头猫import {SoundAsset} from '@/constants/assetsConst';
28*5500cea7S猫头猫import {getQualityOrder} from '@/utils/qualities';
29*5500cea7S猫头猫import musicHistory from '../musicHistory';
30*5500cea7S猫头猫import getUrlExt from '@/utils/getUrlExt';
31*5500cea7S猫头猫import {DeviceEventEmitter} from 'react-native';
32*5500cea7S猫头猫import LyricManager from '../lyricManager';
33*5500cea7S猫头猫import {MusicRepeatMode} from './common';
34*5500cea7S猫头猫import {
35*5500cea7S猫头猫    getMusicIndex,
36*5500cea7S猫头猫    getPlayList,
37*5500cea7S猫头猫    getPlayListMusicAt,
38*5500cea7S猫头猫    isInPlayList,
39*5500cea7S猫头猫    isPlayListEmpty,
40*5500cea7S猫头猫    setPlayList,
41*5500cea7S猫头猫    usePlayList,
42*5500cea7S猫头猫} from './internal/playList';
43*5500cea7S猫头猫import {createMediaIndexMap} from '@/utils/mediaIndexMap';
44*5500cea7S猫头猫import PluginManager from '../pluginManager';
45*5500cea7S猫头猫import {musicIsPaused} from '@/utils/trackUtils';
46*5500cea7S猫头猫
47*5500cea7S猫头猫/** 当前播放 */
48*5500cea7S猫头猫const currentMusicStore = new GlobalState<IMusic.IMusicItem | null>(null);
49*5500cea7S猫头猫
50*5500cea7S猫头猫/** 播放模式 */
51*5500cea7S猫头猫const repeatModeStore = new GlobalState<MusicRepeatMode>(MusicRepeatMode.QUEUE);
52*5500cea7S猫头猫
53*5500cea7S猫头猫/** 音质 */
54*5500cea7S猫头猫const qualityStore = new GlobalState<IMusic.IQualityKey>('standard');
55*5500cea7S猫头猫
56*5500cea7S猫头猫let currentIndex = -1;
57*5500cea7S猫头猫
58*5500cea7S猫头猫// TODO: 下个版本最大限制调大一些
59*5500cea7S猫头猫const maxMusicQueueLength = 1500; // 当前播放最大限制
60*5500cea7S猫头猫const halfMaxMusicQueueLength = Math.floor(maxMusicQueueLength / 2);
61*5500cea7S猫头猫const shrinkPlayListToSize = (
62*5500cea7S猫头猫    queue: IMusic.IMusicItem[],
63*5500cea7S猫头猫    targetIndex = currentIndex,
64*5500cea7S猫头猫) => {
65*5500cea7S猫头猫    // 播放列表上限,太多无法缓存状态
66*5500cea7S猫头猫    if (queue.length > maxMusicQueueLength) {
67*5500cea7S猫头猫        if (targetIndex < halfMaxMusicQueueLength) {
68*5500cea7S猫头猫            queue = queue.slice(0, maxMusicQueueLength);
69*5500cea7S猫头猫        } else {
70*5500cea7S猫头猫            const right = Math.min(
71*5500cea7S猫头猫                queue.length,
72*5500cea7S猫头猫                targetIndex + halfMaxMusicQueueLength,
73*5500cea7S猫头猫            );
74*5500cea7S猫头猫            const left = Math.max(0, right - maxMusicQueueLength);
75*5500cea7S猫头猫            queue = queue.slice(left, right);
76*5500cea7S猫头猫        }
77*5500cea7S猫头猫    }
78*5500cea7S猫头猫    return queue;
79*5500cea7S猫头猫};
80*5500cea7S猫头猫
81*5500cea7S猫头猫async function setupTrackPlayer() {
82*5500cea7S猫头猫    const config = Config.get('status.music') ?? {};
83*5500cea7S猫头猫    console.log('config!!', config);
84*5500cea7S猫头猫    const {rate, repeatMode, musicQueue, progress, track} = config;
85*5500cea7S猫头猫
86*5500cea7S猫头猫    // 状态恢复
87*5500cea7S猫头猫    if (rate) {
88*5500cea7S猫头猫        await ReactNativeTrackPlayer.setRate(+rate / 100);
89*5500cea7S猫头猫    }
90*5500cea7S猫头猫
91*5500cea7S猫头猫    if (musicQueue && Array.isArray(musicQueue)) {
92*5500cea7S猫头猫        addAll(musicQueue, undefined, repeatMode === MusicRepeatMode.SHUFFLE);
93*5500cea7S猫头猫    }
94*5500cea7S猫头猫
95*5500cea7S猫头猫    const currentQuality =
96*5500cea7S猫头猫        Config.get('setting.basic.defaultPlayQuality') ?? 'standard';
97*5500cea7S猫头猫
98*5500cea7S猫头猫    if (track && isInPlayList(track)) {
99*5500cea7S猫头猫        const newSource = await PluginManager.getByMedia(
100*5500cea7S猫头猫            track,
101*5500cea7S猫头猫        )?.methods.getMediaSource(track, currentQuality, 0);
102*5500cea7S猫头猫        // 重新初始化 获取最新的链接
103*5500cea7S猫头猫        track.url = newSource?.url || track.url;
104*5500cea7S猫头猫        track.headers = newSource?.headers || track.headers;
105*5500cea7S猫头猫
106*5500cea7S猫头猫        await setTrackSource(track as Track, false);
107*5500cea7S猫头猫        setCurrentMusic(track);
108*5500cea7S猫头猫
109*5500cea7S猫头猫        if (config?.progress) {
110*5500cea7S猫头猫            await ReactNativeTrackPlayer.seekTo(progress!);
111*5500cea7S猫头猫        }
112*5500cea7S猫头猫    }
113*5500cea7S猫头猫
114*5500cea7S猫头猫    // 初始化事件
115*5500cea7S猫头猫    ReactNativeTrackPlayer.addEventListener(
116*5500cea7S猫头猫        Event.PlaybackActiveTrackChanged,
117*5500cea7S猫头猫        async evt => {
118*5500cea7S猫头猫            if (
119*5500cea7S猫头猫                evt.index === 1 &&
120*5500cea7S猫头猫                evt.lastIndex === 0 &&
121*5500cea7S猫头猫                evt.track?.$ === internalFakeSoundKey
122*5500cea7S猫头猫            ) {
123*5500cea7S猫头猫                if (repeatModeStore.getValue() === MusicRepeatMode.SINGLE) {
124*5500cea7S猫头猫                    await play(null, true);
125*5500cea7S猫头猫                } else {
126*5500cea7S猫头猫                    // 当前生效的歌曲是下一曲的标记
127*5500cea7S猫头猫                    await skipToNext('队列结尾');
128*5500cea7S猫头猫                }
129*5500cea7S猫头猫            }
130*5500cea7S猫头猫        },
131*5500cea7S猫头猫    );
132*5500cea7S猫头猫
133*5500cea7S猫头猫    ReactNativeTrackPlayer.addEventListener(Event.PlaybackError, async () => {
134*5500cea7S猫头猫        // 只关心第一个元素
135*5500cea7S猫头猫        if ((await ReactNativeTrackPlayer.getActiveTrackIndex()) === 0) {
136*5500cea7S猫头猫            failToPlay();
137*5500cea7S猫头猫        }
138*5500cea7S猫头猫    });
139*5500cea7S猫头猫}
140*5500cea7S猫头猫
141*5500cea7S猫头猫/**
142*5500cea7S猫头猫 * 获取自动播放的下一个track
143*5500cea7S猫头猫 */
144*5500cea7S猫头猫const getFakeNextTrack = () => {
145*5500cea7S猫头猫    let track: Track | undefined;
146*5500cea7S猫头猫    const repeatMode = repeatModeStore.getValue();
147*5500cea7S猫头猫    if (repeatMode === MusicRepeatMode.SINGLE) {
148*5500cea7S猫头猫        // 单曲循环
149*5500cea7S猫头猫        track = getPlayListMusicAt(currentIndex) as Track;
150*5500cea7S猫头猫    } else {
151*5500cea7S猫头猫        // 下一曲
152*5500cea7S猫头猫        track = getPlayListMusicAt(currentIndex + 1) as Track;
153*5500cea7S猫头猫    }
154*5500cea7S猫头猫
155*5500cea7S猫头猫    if (track) {
156*5500cea7S猫头猫        return produce(track, _ => {
157*5500cea7S猫头猫            _.url = SoundAsset.fakeAudio;
158*5500cea7S猫头猫            _.$ = internalFakeSoundKey;
159*5500cea7S猫头猫        });
160*5500cea7S猫头猫    } else {
161*5500cea7S猫头猫        // 只有列表长度为0时才会出现的特殊情况
162*5500cea7S猫头猫        return {url: SoundAsset.fakeAudio, $: internalFakeSoundKey} as Track;
163*5500cea7S猫头猫    }
164*5500cea7S猫头猫};
165*5500cea7S猫头猫
166*5500cea7S猫头猫/** 播放失败时的情况 */
167*5500cea7S猫头猫async function failToPlay(reason?: string) {
168*5500cea7S猫头猫    // 如果自动跳转下一曲, 500s后自动跳转
169*5500cea7S猫头猫    if (!Config.get('setting.basic.autoStopWhenError')) {
170*5500cea7S猫头猫        await ReactNativeTrackPlayer.reset();
171*5500cea7S猫头猫        await delay(500);
172*5500cea7S猫头猫        await skipToNext('播放失败' + reason);
173*5500cea7S猫头猫    }
174*5500cea7S猫头猫}
175*5500cea7S猫头猫
176*5500cea7S猫头猫// 播放模式相关
177*5500cea7S猫头猫const _toggleRepeatMapping = {
178*5500cea7S猫头猫    [MusicRepeatMode.SHUFFLE]: MusicRepeatMode.SINGLE,
179*5500cea7S猫头猫    [MusicRepeatMode.SINGLE]: MusicRepeatMode.QUEUE,
180*5500cea7S猫头猫    [MusicRepeatMode.QUEUE]: MusicRepeatMode.SHUFFLE,
181*5500cea7S猫头猫};
182*5500cea7S猫头猫/** 切换下一个模式 */
183*5500cea7S猫头猫const toggleRepeatMode = () => {
184*5500cea7S猫头猫    setRepeatMode(_toggleRepeatMapping[repeatModeStore.getValue()]);
185*5500cea7S猫头猫};
186*5500cea7S猫头猫
187*5500cea7S猫头猫/** 设置音源 */
188*5500cea7S猫头猫const setTrackSource = async (track: Track, autoPlay = true) => {
189*5500cea7S猫头猫    await ReactNativeTrackPlayer.setQueue([track, getFakeNextTrack()]);
190*5500cea7S猫头猫    if (autoPlay) {
191*5500cea7S猫头猫        await ReactNativeTrackPlayer.play();
192*5500cea7S猫头猫    }
193*5500cea7S猫头猫    // 写缓存 TODO: MMKV
194*5500cea7S猫头猫    Config.set('status.music.track', track as IMusic.IMusicItem, false);
195*5500cea7S猫头猫    Config.set('status.music.progress', 0, false);
196*5500cea7S猫头猫};
197*5500cea7S猫头猫
198*5500cea7S猫头猫/**
199*5500cea7S猫头猫 * 添加到播放列表
200*5500cea7S猫头猫 * @param musicItems 目标歌曲
201*5500cea7S猫头猫 * @param beforeIndex 在第x首歌曲前添加
202*5500cea7S猫头猫 * @param shouldShuffle 随机排序
203*5500cea7S猫头猫 */
204*5500cea7S猫头猫const addAll = (
205*5500cea7S猫头猫    musicItems: Array<IMusic.IMusicItem> = [],
206*5500cea7S猫头猫    beforeIndex?: number,
207*5500cea7S猫头猫    shouldShuffle?: boolean,
208*5500cea7S猫头猫) => {
209*5500cea7S猫头猫    const now = Date.now();
210*5500cea7S猫头猫    let newPlayList: IMusic.IMusicItem[] = [];
211*5500cea7S猫头猫    let currentPlayList = getPlayList();
212*5500cea7S猫头猫    const _musicItems = musicItems.map((item, index) =>
213*5500cea7S猫头猫        produce(item, draft => {
214*5500cea7S猫头猫            draft[timeStampSymbol] = now;
215*5500cea7S猫头猫            draft[sortIndexSymbol] = index;
216*5500cea7S猫头猫        }),
217*5500cea7S猫头猫    );
218*5500cea7S猫头猫    if (beforeIndex === undefined || beforeIndex < 0) {
219*5500cea7S猫头猫        // 1.1. 添加到歌单末尾,并过滤掉已有的歌曲
220*5500cea7S猫头猫        newPlayList = currentPlayList.concat(
221*5500cea7S猫头猫            _musicItems.filter(item => !isInPlayList(item)),
222*5500cea7S猫头猫        );
223*5500cea7S猫头猫    } else {
224*5500cea7S猫头猫        // 1.2. 新的播放列表,插入
225*5500cea7S猫头猫        const indexMap = createMediaIndexMap(_musicItems);
226*5500cea7S猫头猫        const beforeDraft = currentPlayList
227*5500cea7S猫头猫            .slice(0, beforeIndex)
228*5500cea7S猫头猫            .filter(item => !indexMap.has(item));
229*5500cea7S猫头猫        const afterDraft = currentPlayList
230*5500cea7S猫头猫            .slice(beforeIndex)
231*5500cea7S猫头猫            .filter(item => !indexMap.has(item));
232*5500cea7S猫头猫
233*5500cea7S猫头猫        newPlayList = [...beforeDraft, ..._musicItems, ...afterDraft];
234*5500cea7S猫头猫    }
235*5500cea7S猫头猫    console.log(newPlayList, 'nnnn');
236*5500cea7S猫头猫    // 如果太长了
237*5500cea7S猫头猫    if (newPlayList.length > maxMusicQueueLength) {
238*5500cea7S猫头猫        newPlayList = shrinkPlayListToSize(
239*5500cea7S猫头猫            newPlayList,
240*5500cea7S猫头猫            beforeIndex ?? newPlayList.length - 1,
241*5500cea7S猫头猫        );
242*5500cea7S猫头猫    }
243*5500cea7S猫头猫
244*5500cea7S猫头猫    // 2. 如果需要随机
245*5500cea7S猫头猫    if (shouldShuffle) {
246*5500cea7S猫头猫        newPlayList = shuffle(newPlayList);
247*5500cea7S猫头猫    }
248*5500cea7S猫头猫    // 3. 设置播放列表
249*5500cea7S猫头猫    setPlayList(newPlayList);
250*5500cea7S猫头猫    const currentMusicItem = currentMusicStore.getValue();
251*5500cea7S猫头猫
252*5500cea7S猫头猫    // 4. 重置下标
253*5500cea7S猫头猫    if (currentMusicItem) {
254*5500cea7S猫头猫        currentIndex = getMusicIndex(currentMusicItem);
255*5500cea7S猫头猫    }
256*5500cea7S猫头猫
257*5500cea7S猫头猫    // TODO: 更新播放队列信息
258*5500cea7S猫头猫    // 5. 存储更新的播放列表信息
259*5500cea7S猫头猫};
260*5500cea7S猫头猫
261*5500cea7S猫头猫/** 追加到队尾 */
262*5500cea7S猫头猫const add = (
263*5500cea7S猫头猫    musicItem: IMusic.IMusicItem | IMusic.IMusicItem[],
264*5500cea7S猫头猫    beforeIndex?: number,
265*5500cea7S猫头猫) => {
266*5500cea7S猫头猫    addAll(Array.isArray(musicItem) ? musicItem : [musicItem], beforeIndex);
267*5500cea7S猫头猫};
268*5500cea7S猫头猫
269*5500cea7S猫头猫/**
270*5500cea7S猫头猫 * 下一首播放
271*5500cea7S猫头猫 * @param musicItem
272*5500cea7S猫头猫 */
273*5500cea7S猫头猫const addNext = (musicItem: IMusic.IMusicItem | IMusic.IMusicItem[]) => {
274*5500cea7S猫头猫    const shouldPlay = isPlayListEmpty();
275*5500cea7S猫头猫    add(musicItem, currentIndex + 1);
276*5500cea7S猫头猫    if (shouldPlay) {
277*5500cea7S猫头猫        play(Array.isArray(musicItem) ? musicItem[0] : musicItem);
278*5500cea7S猫头猫    }
279*5500cea7S猫头猫};
280*5500cea7S猫头猫
281*5500cea7S猫头猫const isCurrentMusic = (musicItem: IMusic.IMusicItem) => {
282*5500cea7S猫头猫    return isSameMediaItem(musicItem, currentMusicStore.getValue()) ?? false;
283*5500cea7S猫头猫};
284*5500cea7S猫头猫
285*5500cea7S猫头猫const remove = async (musicItem: IMusic.IMusicItem) => {
286*5500cea7S猫头猫    const playList = getPlayList();
287*5500cea7S猫头猫    let newPlayList: IMusic.IMusicItem[] = [];
288*5500cea7S猫头猫    let currentMusic: IMusic.IMusicItem | null = currentMusicStore.getValue();
289*5500cea7S猫头猫    const targetIndex = getMusicIndex(musicItem);
290*5500cea7S猫头猫    let shouldPlayCurrent: boolean | null = null;
291*5500cea7S猫头猫    if (targetIndex === -1) {
292*5500cea7S猫头猫        // 1. 这种情况应该是出错了
293*5500cea7S猫头猫        return;
294*5500cea7S猫头猫    }
295*5500cea7S猫头猫    // 2. 移除的是当前项
296*5500cea7S猫头猫    if (currentIndex === targetIndex) {
297*5500cea7S猫头猫        // 2.1 停止播放,移除当前项
298*5500cea7S猫头猫        newPlayList = produce(playList, draft => {
299*5500cea7S猫头猫            draft.splice(targetIndex, 1);
300*5500cea7S猫头猫        });
301*5500cea7S猫头猫        // 2.2 设置新的播放列表,并更新当前音乐
302*5500cea7S猫头猫        if (newPlayList.length === 0) {
303*5500cea7S猫头猫            currentMusic = null;
304*5500cea7S猫头猫            shouldPlayCurrent = false;
305*5500cea7S猫头猫        } else {
306*5500cea7S猫头猫            currentMusic = newPlayList[currentIndex % newPlayList.length];
307*5500cea7S猫头猫            try {
308*5500cea7S猫头猫                const state = (await ReactNativeTrackPlayer.getPlaybackState())
309*5500cea7S猫头猫                    .state;
310*5500cea7S猫头猫                if (musicIsPaused(state)) {
311*5500cea7S猫头猫                    shouldPlayCurrent = false;
312*5500cea7S猫头猫                } else {
313*5500cea7S猫头猫                    shouldPlayCurrent = true;
314*5500cea7S猫头猫                }
315*5500cea7S猫头猫            } catch {
316*5500cea7S猫头猫                shouldPlayCurrent = false;
317*5500cea7S猫头猫            }
318*5500cea7S猫头猫        }
319*5500cea7S猫头猫    } else {
320*5500cea7S猫头猫        // 3. 删除
321*5500cea7S猫头猫        newPlayList = produce(playList, draft => {
322*5500cea7S猫头猫            draft.splice(targetIndex, 1);
323*5500cea7S猫头猫        });
324*5500cea7S猫头猫    }
325*5500cea7S猫头猫
326*5500cea7S猫头猫    setPlayList(newPlayList);
327*5500cea7S猫头猫    setCurrentMusic(currentMusic);
328*5500cea7S猫头猫    Config.set('status.music.musicQueue', playList, false);
329*5500cea7S猫头猫    if (shouldPlayCurrent === true) {
330*5500cea7S猫头猫        await play(currentMusic, true);
331*5500cea7S猫头猫    } else if (shouldPlayCurrent === false) {
332*5500cea7S猫头猫        await ReactNativeTrackPlayer.reset();
333*5500cea7S猫头猫    }
334*5500cea7S猫头猫};
335*5500cea7S猫头猫
336*5500cea7S猫头猫/**
337*5500cea7S猫头猫 * 设置播放模式
338*5500cea7S猫头猫 * @param mode 播放模式
339*5500cea7S猫头猫 */
340*5500cea7S猫头猫const setRepeatMode = (mode: MusicRepeatMode) => {
341*5500cea7S猫头猫    const playList = getPlayList();
342*5500cea7S猫头猫    let newPlayList;
343*5500cea7S猫头猫    if (mode === MusicRepeatMode.SHUFFLE) {
344*5500cea7S猫头猫        newPlayList = shuffle(playList);
345*5500cea7S猫头猫    } else {
346*5500cea7S猫头猫        newPlayList = produce(playList, draft => {
347*5500cea7S猫头猫            return sortByTimestampAndIndex(draft);
348*5500cea7S猫头猫        });
349*5500cea7S猫头猫    }
350*5500cea7S猫头猫
351*5500cea7S猫头猫    setPlayList(newPlayList);
352*5500cea7S猫头猫    const currentMusicItem = currentMusicStore.getValue();
353*5500cea7S猫头猫    currentIndex = getMusicIndex(currentMusicItem);
354*5500cea7S猫头猫    repeatModeStore.setValue(mode);
355*5500cea7S猫头猫    // 更新下一首歌的信息
356*5500cea7S猫头猫    ReactNativeTrackPlayer.updateMetadataForTrack(1, getFakeNextTrack());
357*5500cea7S猫头猫    // 记录
358*5500cea7S猫头猫    Config.set('status.music.repeatMode', mode, false);
359*5500cea7S猫头猫};
360*5500cea7S猫头猫
361*5500cea7S猫头猫/** 清空播放列表 */
362*5500cea7S猫头猫const clear = async () => {
363*5500cea7S猫头猫    setPlayList([]);
364*5500cea7S猫头猫    setCurrentMusic(null);
365*5500cea7S猫头猫
366*5500cea7S猫头猫    await ReactNativeTrackPlayer.reset();
367*5500cea7S猫头猫    Config.set('status.music', {
368*5500cea7S猫头猫        repeatMode: repeatModeStore.getValue(),
369*5500cea7S猫头猫    });
370*5500cea7S猫头猫};
371*5500cea7S猫头猫
372*5500cea7S猫头猫/** 暂停 */
373*5500cea7S猫头猫const pause = async () => {
374*5500cea7S猫头猫    await ReactNativeTrackPlayer.pause();
375*5500cea7S猫头猫};
376*5500cea7S猫头猫
377*5500cea7S猫头猫const setCurrentMusic = (musicItem?: IMusic.IMusicItem | null) => {
378*5500cea7S猫头猫    if (!musicItem) {
379*5500cea7S猫头猫        currentMusicStore.setValue(null);
380*5500cea7S猫头猫        currentIndex = -1;
381*5500cea7S猫头猫    }
382*5500cea7S猫头猫
383*5500cea7S猫头猫    currentMusicStore.setValue(musicItem!);
384*5500cea7S猫头猫    currentIndex = getMusicIndex(musicItem);
385*5500cea7S猫头猫};
386*5500cea7S猫头猫
387*5500cea7S猫头猫/**
388*5500cea7S猫头猫 * 播放
389*5500cea7S猫头猫 *
390*5500cea7S猫头猫 * 当musicItem 为空时,代表暂停/播放
391*5500cea7S猫头猫 *
392*5500cea7S猫头猫 * @param musicItem
393*5500cea7S猫头猫 * @param forcePlay
394*5500cea7S猫头猫 * @returns
395*5500cea7S猫头猫 */
396*5500cea7S猫头猫const play = async (
397*5500cea7S猫头猫    musicItem?: IMusic.IMusicItem | null,
398*5500cea7S猫头猫    forcePlay?: boolean,
399*5500cea7S猫头猫) => {
400*5500cea7S猫头猫    try {
401*5500cea7S猫头猫        if (!musicItem) {
402*5500cea7S猫头猫            musicItem = currentMusicStore.getValue();
403*5500cea7S猫头猫        }
404*5500cea7S猫头猫        if (!musicItem) {
405*5500cea7S猫头猫            throw new Error(PlayFailReason.PLAY_LIST_IS_EMPTY);
406*5500cea7S猫头猫        }
407*5500cea7S猫头猫        // 1. 移动网络禁止播放
408*5500cea7S猫头猫        if (
409*5500cea7S猫头猫            Network.isCellular() &&
410*5500cea7S猫头猫            !Config.get('setting.basic.useCelluarNetworkPlay') &&
411*5500cea7S猫头猫            !LocalMusicSheet.isLocalMusic(musicItem)
412*5500cea7S猫头猫        ) {
413*5500cea7S猫头猫            await ReactNativeTrackPlayer.reset();
414*5500cea7S猫头猫            throw new Error(PlayFailReason.FORBID_CELLUAR_NETWORK_PLAY);
415*5500cea7S猫头猫        }
416*5500cea7S猫头猫
417*5500cea7S猫头猫        // 2. 如果是当前正在播放的音频
418*5500cea7S猫头猫        if (isCurrentMusic(musicItem)) {
419*5500cea7S猫头猫            const currentTrack = await ReactNativeTrackPlayer.getTrack(0);
420*5500cea7S猫头猫            // 2.1 如果当前有源
421*5500cea7S猫头猫            if (
422*5500cea7S猫头猫                currentTrack?.url &&
423*5500cea7S猫头猫                isSameMediaItem(musicItem, currentTrack as IMusic.IMusicItem)
424*5500cea7S猫头猫            ) {
425*5500cea7S猫头猫                const currentActiveIndex =
426*5500cea7S猫头猫                    await ReactNativeTrackPlayer.getActiveTrackIndex();
427*5500cea7S猫头猫                if (currentActiveIndex !== 0) {
428*5500cea7S猫头猫                    await ReactNativeTrackPlayer.skip(0);
429*5500cea7S猫头猫                }
430*5500cea7S猫头猫                if (forcePlay) {
431*5500cea7S猫头猫                    // 2.1.1 强制重新开始
432*5500cea7S猫头猫                    await ReactNativeTrackPlayer.seekTo(0);
433*5500cea7S猫头猫                } else if (
434*5500cea7S猫头猫                    (await ReactNativeTrackPlayer.getPlaybackState()).state !==
435*5500cea7S猫头猫                    State.Playing
436*5500cea7S猫头猫                ) {
437*5500cea7S猫头猫                    // 2.1.2 恢复播放
438*5500cea7S猫头猫                    await ReactNativeTrackPlayer.play();
439*5500cea7S猫头猫                }
440*5500cea7S猫头猫                // 这种情况下,播放队列和当前歌曲都不需要变化
441*5500cea7S猫头猫                return;
442*5500cea7S猫头猫            }
443*5500cea7S猫头猫            // 2.2 其他情况:重新获取源
444*5500cea7S猫头猫        }
445*5500cea7S猫头猫
446*5500cea7S猫头猫        // 3. 如果没有在播放列表中,添加到队尾;同时更新列表状态
447*5500cea7S猫头猫        const inPlayList = isInPlayList(musicItem);
448*5500cea7S猫头猫        if (!inPlayList) {
449*5500cea7S猫头猫            add(musicItem);
450*5500cea7S猫头猫        }
451*5500cea7S猫头猫
452*5500cea7S猫头猫        // 4. 更新列表状态和当前音乐
453*5500cea7S猫头猫        setCurrentMusic(musicItem);
454*5500cea7S猫头猫
455*5500cea7S猫头猫        // 5. 获取音源
456*5500cea7S猫头猫        let track: IMusic.IMusicItem;
457*5500cea7S猫头猫
458*5500cea7S猫头猫        // 5.1 通过插件获取音源
459*5500cea7S猫头猫        const plugin = PluginManager.getByName(musicItem.platform);
460*5500cea7S猫头猫        // 5.2 获取音质排序
461*5500cea7S猫头猫        const qualityOrder = getQualityOrder(
462*5500cea7S猫头猫            Config.get('setting.basic.defaultPlayQuality') ?? 'standard',
463*5500cea7S猫头猫            Config.get('setting.basic.playQualityOrder') ?? 'asc',
464*5500cea7S猫头猫        );
465*5500cea7S猫头猫        // 5.3 插件返回音源
466*5500cea7S猫头猫        let source: IPlugin.IMediaSourceResult | null = null;
467*5500cea7S猫头猫        for (let quality of qualityOrder) {
468*5500cea7S猫头猫            if (isCurrentMusic(musicItem)) {
469*5500cea7S猫头猫                source =
470*5500cea7S猫头猫                    (await plugin?.methods?.getMediaSource(
471*5500cea7S猫头猫                        musicItem,
472*5500cea7S猫头猫                        quality,
473*5500cea7S猫头猫                    )) ?? null;
474*5500cea7S猫头猫                // 5.3.1 获取到真实源
475*5500cea7S猫头猫                if (source) {
476*5500cea7S猫头猫                    qualityStore.setValue(quality);
477*5500cea7S猫头猫                    break;
478*5500cea7S猫头猫                }
479*5500cea7S猫头猫            } else {
480*5500cea7S猫头猫                // 5.3.2 已经切换到其他歌曲了,
481*5500cea7S猫头猫                return;
482*5500cea7S猫头猫            }
483*5500cea7S猫头猫        }
484*5500cea7S猫头猫
485*5500cea7S猫头猫        if (!isCurrentMusic(musicItem)) {
486*5500cea7S猫头猫            return;
487*5500cea7S猫头猫        }
488*5500cea7S猫头猫
489*5500cea7S猫头猫        if (!source) {
490*5500cea7S猫头猫            // 5.4 没有返回源
491*5500cea7S猫头猫            if (!musicItem.url) {
492*5500cea7S猫头猫                throw new Error(PlayFailReason.INVALID_SOURCE);
493*5500cea7S猫头猫            }
494*5500cea7S猫头猫            source = {
495*5500cea7S猫头猫                url: musicItem.url,
496*5500cea7S猫头猫            };
497*5500cea7S猫头猫            qualityStore.setValue('standard');
498*5500cea7S猫头猫        }
499*5500cea7S猫头猫
500*5500cea7S猫头猫        // 6. 特殊类型源
501*5500cea7S猫头猫        if (getUrlExt(source.url) === '.m3u8') {
502*5500cea7S猫头猫            // @ts-ignore
503*5500cea7S猫头猫            source.type = 'hls';
504*5500cea7S猫头猫        }
505*5500cea7S猫头猫        // 7. 合并结果
506*5500cea7S猫头猫        track = mergeProps(musicItem, source) as IMusic.IMusicItem;
507*5500cea7S猫头猫
508*5500cea7S猫头猫        // 8. 新增历史记录
509*5500cea7S猫头猫        musicHistory.addMusic(musicItem);
510*5500cea7S猫头猫
511*5500cea7S猫头猫        // 9. 设置音源
512*5500cea7S猫头猫        await setTrackSource(track as Track);
513*5500cea7S猫头猫
514*5500cea7S猫头猫        // 10. 获取补充信息
515*5500cea7S猫头猫        let info: Partial<IMusic.IMusicItem> | null = null;
516*5500cea7S猫头猫        try {
517*5500cea7S猫头猫            info = (await plugin?.methods?.getMusicInfo?.(musicItem)) ?? null;
518*5500cea7S猫头猫        } catch {}
519*5500cea7S猫头猫
520*5500cea7S猫头猫        // 11. 设置补充信息
521*5500cea7S猫头猫        if (info && isCurrentMusic(musicItem)) {
522*5500cea7S猫头猫            const mergedTrack = mergeProps(track, info);
523*5500cea7S猫头猫            currentMusicStore.setValue(mergedTrack as IMusic.IMusicItem);
524*5500cea7S猫头猫            await ReactNativeTrackPlayer.updateMetadataForTrack(
525*5500cea7S猫头猫                0,
526*5500cea7S猫头猫                mergedTrack as TrackMetadataBase,
527*5500cea7S猫头猫            );
528*5500cea7S猫头猫        }
529*5500cea7S猫头猫
530*5500cea7S猫头猫        // 12. 刷新歌词信息
531*5500cea7S猫头猫        if (
532*5500cea7S猫头猫            !isSameMediaItem(
533*5500cea7S猫头猫                LyricManager.getLyricState()?.lyricParser?.getCurrentMusicItem?.(),
534*5500cea7S猫头猫                musicItem,
535*5500cea7S猫头猫            )
536*5500cea7S猫头猫        ) {
537*5500cea7S猫头猫            DeviceEventEmitter.emit(EDeviceEvents.REFRESH_LYRIC, true);
538*5500cea7S猫头猫        }
539*5500cea7S猫头猫    } catch (e: any) {
540*5500cea7S猫头猫        const message = e?.message;
541*5500cea7S猫头猫        if (
542*5500cea7S猫头猫            message === 'The player is not initialized. Call setupPlayer first.'
543*5500cea7S猫头猫        ) {
544*5500cea7S猫头猫            await ReactNativeTrackPlayer.setupPlayer();
545*5500cea7S猫头猫            play(musicItem, forcePlay);
546*5500cea7S猫头猫        } else if (message === PlayFailReason.FORBID_CELLUAR_NETWORK_PLAY) {
547*5500cea7S猫头猫        } else if (message === PlayFailReason.INVALID_SOURCE) {
548*5500cea7S猫头猫            await failToPlay('无效源');
549*5500cea7S猫头猫        } else if (message === PlayFailReason.PLAY_LIST_IS_EMPTY) {
550*5500cea7S猫头猫            // 队列是空的,不应该出现这种情况
551*5500cea7S猫头猫        }
552*5500cea7S猫头猫    }
553*5500cea7S猫头猫};
554*5500cea7S猫头猫
555*5500cea7S猫头猫/**
556*5500cea7S猫头猫 * 播放音乐,同时替换播放队列
557*5500cea7S猫头猫 * @param musicItem 音乐
558*5500cea7S猫头猫 * @param newPlayList 替代列表
559*5500cea7S猫头猫 */
560*5500cea7S猫头猫const playWithReplacePlayList = async (
561*5500cea7S猫头猫    musicItem: IMusic.IMusicItem,
562*5500cea7S猫头猫    newPlayList: IMusic.IMusicItem[],
563*5500cea7S猫头猫) => {
564*5500cea7S猫头猫    if (newPlayList.length !== 0) {
565*5500cea7S猫头猫        const now = Date.now();
566*5500cea7S猫头猫        if (newPlayList.length > maxMusicQueueLength) {
567*5500cea7S猫头猫            newPlayList = shrinkPlayListToSize(
568*5500cea7S猫头猫                newPlayList,
569*5500cea7S猫头猫                newPlayList.findIndex(it => isSameMediaItem(it, musicItem)),
570*5500cea7S猫头猫            );
571*5500cea7S猫头猫        }
572*5500cea7S猫头猫        const playListItems = newPlayList.map((item, index) =>
573*5500cea7S猫头猫            produce(item, draft => {
574*5500cea7S猫头猫                draft[timeStampSymbol] = now;
575*5500cea7S猫头猫                draft[sortIndexSymbol] = index;
576*5500cea7S猫头猫            }),
577*5500cea7S猫头猫        );
578*5500cea7S猫头猫        setPlayList(
579*5500cea7S猫头猫            repeatModeStore.getValue() === MusicRepeatMode.SHUFFLE
580*5500cea7S猫头猫                ? shuffle(playListItems)
581*5500cea7S猫头猫                : playListItems,
582*5500cea7S猫头猫        );
583*5500cea7S猫头猫        await play(musicItem, true);
584*5500cea7S猫头猫    }
585*5500cea7S猫头猫};
586*5500cea7S猫头猫
587*5500cea7S猫头猫const skipToNext = async (reason?: string) => {
588*5500cea7S猫头猫    console.log(
589*5500cea7S猫头猫        'SkipToNext',
590*5500cea7S猫头猫        reason,
591*5500cea7S猫头猫        await ReactNativeTrackPlayer.getActiveTrack(),
592*5500cea7S猫头猫    );
593*5500cea7S猫头猫    if (isPlayListEmpty()) {
594*5500cea7S猫头猫        setCurrentMusic(null);
595*5500cea7S猫头猫        return;
596*5500cea7S猫头猫    }
597*5500cea7S猫头猫
598*5500cea7S猫头猫    await play(getPlayListMusicAt(currentIndex + 1), true);
599*5500cea7S猫头猫};
600*5500cea7S猫头猫
601*5500cea7S猫头猫const skipToPrevious = async () => {
602*5500cea7S猫头猫    if (isPlayListEmpty()) {
603*5500cea7S猫头猫        setCurrentMusic(null);
604*5500cea7S猫头猫        return;
605*5500cea7S猫头猫    }
606*5500cea7S猫头猫
607*5500cea7S猫头猫    await play(getPlayListMusicAt(currentIndex === -1 ? 0 : currentIndex - 1));
608*5500cea7S猫头猫};
609*5500cea7S猫头猫
610*5500cea7S猫头猫/** 修改当前播放的音质 */
611*5500cea7S猫头猫const changeQuality = async (newQuality: IMusic.IQualityKey) => {
612*5500cea7S猫头猫    // 获取当前的音乐和进度
613*5500cea7S猫头猫    if (newQuality === qualityStore.getValue()) {
614*5500cea7S猫头猫        return true;
615*5500cea7S猫头猫    }
616*5500cea7S猫头猫
617*5500cea7S猫头猫    // 获取当前歌曲
618*5500cea7S猫头猫    const musicItem = currentMusicStore.getValue();
619*5500cea7S猫头猫    if (!musicItem) {
620*5500cea7S猫头猫        return false;
621*5500cea7S猫头猫    }
622*5500cea7S猫头猫    try {
623*5500cea7S猫头猫        const progress = await ReactNativeTrackPlayer.getProgress();
624*5500cea7S猫头猫        const plugin = PluginManager.getByMedia(musicItem);
625*5500cea7S猫头猫        const newSource = await plugin?.methods?.getMediaSource(
626*5500cea7S猫头猫            musicItem,
627*5500cea7S猫头猫            newQuality,
628*5500cea7S猫头猫        );
629*5500cea7S猫头猫        if (!newSource?.url) {
630*5500cea7S猫头猫            throw new Error(PlayFailReason.INVALID_SOURCE);
631*5500cea7S猫头猫        }
632*5500cea7S猫头猫        if (isCurrentMusic(musicItem)) {
633*5500cea7S猫头猫            const playingState = (
634*5500cea7S猫头猫                await ReactNativeTrackPlayer.getPlaybackState()
635*5500cea7S猫头猫            ).state;
636*5500cea7S猫头猫            await setTrackSource(
637*5500cea7S猫头猫                mergeProps(musicItem, newSource) as unknown as Track,
638*5500cea7S猫头猫                !musicIsPaused(playingState),
639*5500cea7S猫头猫            );
640*5500cea7S猫头猫
641*5500cea7S猫头猫            await ReactNativeTrackPlayer.seekTo(progress.position ?? 0);
642*5500cea7S猫头猫            qualityStore.setValue(newQuality);
643*5500cea7S猫头猫        }
644*5500cea7S猫头猫        return true;
645*5500cea7S猫头猫    } catch {
646*5500cea7S猫头猫        // 修改失败
647*5500cea7S猫头猫        return false;
648*5500cea7S猫头猫    }
649*5500cea7S猫头猫};
650*5500cea7S猫头猫
651*5500cea7S猫头猫enum PlayFailReason {
652*5500cea7S猫头猫    /** 禁止移动网络播放 */
653*5500cea7S猫头猫    FORBID_CELLUAR_NETWORK_PLAY = 'FORBID_CELLUAR_NETWORK_PLAY',
654*5500cea7S猫头猫    /** 播放列表为空 */
655*5500cea7S猫头猫    PLAY_LIST_IS_EMPTY = 'PLAY_LIST_IS_EMPTY',
656*5500cea7S猫头猫    /** 无效源 */
657*5500cea7S猫头猫    INVALID_SOURCE = 'INVALID_SOURCE',
658*5500cea7S猫头猫    /** 非当前音乐 */
659*5500cea7S猫头猫}
660*5500cea7S猫头猫
661*5500cea7S猫头猫function useMusicState() {
662*5500cea7S猫头猫    const playbackState = usePlaybackState();
663*5500cea7S猫头猫
664*5500cea7S猫头猫    return playbackState.state;
665*5500cea7S猫头猫}
666*5500cea7S猫头猫
667*5500cea7S猫头猫const TrackPlayer = {
668*5500cea7S猫头猫    setupTrackPlayer,
669*5500cea7S猫头猫    usePlayList,
670*5500cea7S猫头猫    getPlayList,
671*5500cea7S猫头猫    addAll,
672*5500cea7S猫头猫    add,
673*5500cea7S猫头猫    addNext,
674*5500cea7S猫头猫    skipToNext,
675*5500cea7S猫头猫    skipToPrevious,
676*5500cea7S猫头猫    play,
677*5500cea7S猫头猫    playWithReplacePlayList,
678*5500cea7S猫头猫    pause,
679*5500cea7S猫头猫    remove,
680*5500cea7S猫头猫    clear,
681*5500cea7S猫头猫    useCurrentMusic: currentMusicStore.useValue,
682*5500cea7S猫头猫    getCurrentMusic: currentMusicStore.getValue,
683*5500cea7S猫头猫    useRepeatMode: repeatModeStore.useValue,
684*5500cea7S猫头猫    getRepeatMode: repeatModeStore.getValue,
685*5500cea7S猫头猫    toggleRepeatMode,
686*5500cea7S猫头猫    usePlaybackState,
687*5500cea7S猫头猫    getProgress: ReactNativeTrackPlayer.getProgress,
688*5500cea7S猫头猫    useProgress: useProgress,
689*5500cea7S猫头猫    seekTo: ReactNativeTrackPlayer.seekTo,
690*5500cea7S猫头猫    changeQuality,
691*5500cea7S猫头猫    useCurrentQuality: qualityStore.useValue,
692*5500cea7S猫头猫    getCurrentQuality: qualityStore.getValue,
693*5500cea7S猫头猫    getRate: ReactNativeTrackPlayer.getRate,
694*5500cea7S猫头猫    setRate: ReactNativeTrackPlayer.setRate,
695*5500cea7S猫头猫    useMusicState,
696*5500cea7S猫头猫    reset: ReactNativeTrackPlayer.reset,
697*5500cea7S猫头猫};
698*5500cea7S猫头猫
699*5500cea7S猫头猫export default TrackPlayer;
700*5500cea7S猫头猫export {MusicRepeatMode, State as MusicState};
701