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