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