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