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