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