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