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'; 49 50/** 当前播放 */ 51const currentMusicStore = new GlobalState<IMusic.IMusicItem | null>(null); 52 53/** 播放模式 */ 54const repeatModeStore = new GlobalState<MusicRepeatMode>(MusicRepeatMode.QUEUE); 55 56/** 音质 */ 57const qualityStore = new GlobalState<IMusic.IQualityKey>('standard'); 58 59let currentIndex = -1; 60 61// TODO: 下个版本最大限制调大一些 62const maxMusicQueueLength = 1500; // 当前播放最大限制 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 const _musicItems = musicItems.map((item, index) => 270 produce(item, draft => { 271 draft[timeStampSymbol] = now; 272 draft[sortIndexSymbol] = index; 273 }), 274 ); 275 if (beforeIndex === undefined || beforeIndex < 0) { 276 // 1.1. 添加到歌单末尾,并过滤掉已有的歌曲 277 newPlayList = currentPlayList.concat( 278 _musicItems.filter(item => !isInPlayList(item)), 279 ); 280 } else { 281 // 1.2. 新的播放列表,插入 282 const indexMap = createMediaIndexMap(_musicItems); 283 const beforeDraft = currentPlayList 284 .slice(0, beforeIndex) 285 .filter(item => !indexMap.has(item)); 286 const afterDraft = currentPlayList 287 .slice(beforeIndex) 288 .filter(item => !indexMap.has(item)); 289 290 newPlayList = [...beforeDraft, ..._musicItems, ...afterDraft]; 291 } 292 293 // 如果太长了 294 if (newPlayList.length > maxMusicQueueLength) { 295 newPlayList = shrinkPlayListToSize( 296 newPlayList, 297 beforeIndex ?? newPlayList.length - 1, 298 ); 299 } 300 301 // 2. 如果需要随机 302 if (shouldShuffle) { 303 newPlayList = shuffle(newPlayList); 304 } 305 // 3. 设置播放列表 306 setPlayList(newPlayList); 307 const currentMusicItem = currentMusicStore.getValue(); 308 309 // 4. 重置下标 310 if (currentMusicItem) { 311 currentIndex = getMusicIndex(currentMusicItem); 312 } 313 314 // TODO: 更新播放队列信息 315 // 5. 存储更新的播放列表信息 316}; 317 318/** 追加到队尾 */ 319const add = ( 320 musicItem: IMusic.IMusicItem | IMusic.IMusicItem[], 321 beforeIndex?: number, 322) => { 323 addAll(Array.isArray(musicItem) ? musicItem : [musicItem], beforeIndex); 324}; 325 326/** 327 * 下一首播放 328 * @param musicItem 329 */ 330const addNext = (musicItem: IMusic.IMusicItem | IMusic.IMusicItem[]) => { 331 const shouldPlay = isPlayListEmpty(); 332 add(musicItem, currentIndex + 1); 333 if (shouldPlay) { 334 play(Array.isArray(musicItem) ? musicItem[0] : musicItem); 335 } 336}; 337 338const isCurrentMusic = (musicItem: IMusic.IMusicItem) => { 339 return isSameMediaItem(musicItem, currentMusicStore.getValue()) ?? false; 340}; 341 342const remove = async (musicItem: IMusic.IMusicItem) => { 343 const playList = getPlayList(); 344 let newPlayList: IMusic.IMusicItem[] = []; 345 let currentMusic: IMusic.IMusicItem | null = currentMusicStore.getValue(); 346 const targetIndex = getMusicIndex(musicItem); 347 let shouldPlayCurrent: boolean | null = null; 348 if (targetIndex === -1) { 349 // 1. 这种情况应该是出错了 350 return; 351 } 352 // 2. 移除的是当前项 353 if (currentIndex === targetIndex) { 354 // 2.1 停止播放,移除当前项 355 newPlayList = produce(playList, draft => { 356 draft.splice(targetIndex, 1); 357 }); 358 // 2.2 设置新的播放列表,并更新当前音乐 359 if (newPlayList.length === 0) { 360 currentMusic = null; 361 shouldPlayCurrent = false; 362 } else { 363 currentMusic = newPlayList[currentIndex % newPlayList.length]; 364 try { 365 const state = (await ReactNativeTrackPlayer.getPlaybackState()) 366 .state; 367 if (musicIsPaused(state)) { 368 shouldPlayCurrent = false; 369 } else { 370 shouldPlayCurrent = true; 371 } 372 } catch { 373 shouldPlayCurrent = false; 374 } 375 } 376 } else { 377 // 3. 删除 378 newPlayList = produce(playList, draft => { 379 draft.splice(targetIndex, 1); 380 }); 381 } 382 383 setPlayList(newPlayList); 384 setCurrentMusic(currentMusic); 385 if (shouldPlayCurrent === true) { 386 await play(currentMusic, true); 387 } else if (shouldPlayCurrent === false) { 388 await ReactNativeTrackPlayer.reset(); 389 } 390}; 391 392/** 393 * 设置播放模式 394 * @param mode 播放模式 395 */ 396const setRepeatMode = (mode: MusicRepeatMode) => { 397 const playList = getPlayList(); 398 let newPlayList; 399 const prevMode = repeatModeStore.getValue(); 400 401 if ( 402 (prevMode === MusicRepeatMode.SHUFFLE && 403 mode !== MusicRepeatMode.SHUFFLE) || 404 (mode === MusicRepeatMode.SHUFFLE && 405 prevMode !== MusicRepeatMode.SHUFFLE) 406 ) { 407 if (mode === MusicRepeatMode.SHUFFLE) { 408 newPlayList = shuffle(playList); 409 } else { 410 newPlayList = sortByTimestampAndIndex(playList, true); 411 } 412 setPlayList(newPlayList); 413 } 414 415 const currentMusicItem = currentMusicStore.getValue(); 416 currentIndex = getMusicIndex(currentMusicItem); 417 repeatModeStore.setValue(mode); 418 // 更新下一首歌的信息 419 ReactNativeTrackPlayer.updateMetadataForTrack(1, getFakeNextTrack()); 420 // 记录 421 PersistStatus.set('music.repeatMode', mode); 422}; 423 424/** 清空播放列表 */ 425const clear = async () => { 426 setPlayList([]); 427 setCurrentMusic(null); 428 429 await ReactNativeTrackPlayer.reset(); 430 PersistStatus.set('music.musicItem', undefined); 431 PersistStatus.set('music.progress', 0); 432}; 433 434/** 暂停 */ 435const pause = async () => { 436 await ReactNativeTrackPlayer.pause(); 437}; 438 439/** 设置音源 */ 440const setTrackSource = async (track: Track, autoPlay = true) => { 441 if (!track.artwork?.trim()?.length) { 442 track.artwork = undefined; 443 } 444 await ReactNativeTrackPlayer.setQueue([track, getFakeNextTrack()]); 445 PersistStatus.set('music.musicItem', track as IMusic.IMusicItem); 446 PersistStatus.set('music.progress', 0); 447 if (autoPlay) { 448 await ReactNativeTrackPlayer.play(); 449 } 450}; 451 452const setCurrentMusic = (musicItem?: IMusic.IMusicItem | null) => { 453 if (!musicItem) { 454 currentIndex = -1; 455 currentMusicStore.setValue(null); 456 PersistStatus.set('music.musicItem', undefined); 457 PersistStatus.set('music.progress', 0); 458 return; 459 } 460 currentIndex = getMusicIndex(musicItem); 461 currentMusicStore.setValue(musicItem); 462}; 463 464const setQuality = (quality: IMusic.IQualityKey) => { 465 qualityStore.setValue(quality); 466 PersistStatus.set('music.quality', quality); 467}; 468/** 469 * 播放 470 * 471 * 当musicItem 为空时,代表暂停/播放 472 * 473 * @param musicItem 474 * @param forcePlay 475 * @returns 476 */ 477const play = async ( 478 musicItem?: IMusic.IMusicItem | null, 479 forcePlay?: boolean, 480) => { 481 try { 482 if (!musicItem) { 483 musicItem = currentMusicStore.getValue(); 484 } 485 if (!musicItem) { 486 throw new Error(PlayFailReason.PLAY_LIST_IS_EMPTY); 487 } 488 // 1. 移动网络禁止播放 489 if ( 490 Network.isCellular() && 491 !Config.get('setting.basic.useCelluarNetworkPlay') && 492 !LocalMusicSheet.isLocalMusic(musicItem) 493 ) { 494 await ReactNativeTrackPlayer.reset(); 495 throw new Error(PlayFailReason.FORBID_CELLUAR_NETWORK_PLAY); 496 } 497 498 // 2. 如果是当前正在播放的音频 499 if (isCurrentMusic(musicItem)) { 500 const currentTrack = await ReactNativeTrackPlayer.getTrack(0); 501 // 2.1 如果当前有源 502 if ( 503 currentTrack?.url && 504 isSameMediaItem(musicItem, currentTrack as IMusic.IMusicItem) 505 ) { 506 const currentActiveIndex = 507 await ReactNativeTrackPlayer.getActiveTrackIndex(); 508 if (currentActiveIndex !== 0) { 509 await ReactNativeTrackPlayer.skip(0); 510 } 511 if (forcePlay) { 512 // 2.1.1 强制重新开始 513 await ReactNativeTrackPlayer.seekTo(0); 514 } 515 const currentState = ( 516 await ReactNativeTrackPlayer.getPlaybackState() 517 ).state; 518 if (currentState === State.Stopped) { 519 await setTrackSource(currentTrack); 520 } 521 if (currentState !== State.Playing) { 522 // 2.1.2 恢复播放 523 await ReactNativeTrackPlayer.play(); 524 } 525 // 这种情况下,播放队列和当前歌曲都不需要变化 526 return; 527 } 528 // 2.2 其他情况:重新获取源 529 } 530 531 // 3. 如果没有在播放列表中,添加到队尾;同时更新列表状态 532 const inPlayList = isInPlayList(musicItem); 533 if (!inPlayList) { 534 add(musicItem); 535 } 536 537 // 4. 更新列表状态和当前音乐 538 setCurrentMusic(musicItem); 539 await ReactNativeTrackPlayer.reset(); 540 541 // 4.1 刷新歌词信息 542 if ( 543 !isSameMediaItem( 544 LyricManager.getLyricState()?.lyricParser?.getCurrentMusicItem?.(), 545 musicItem, 546 ) 547 ) { 548 DeviceEventEmitter.emit(EDeviceEvents.REFRESH_LYRIC, true); 549 } 550 551 // 5. 获取音源 552 let track: IMusic.IMusicItem; 553 554 // 5.1 通过插件获取音源 555 const plugin = PluginManager.getByName(musicItem.platform); 556 // 5.2 获取音质排序 557 const qualityOrder = getQualityOrder( 558 Config.get('setting.basic.defaultPlayQuality') ?? 'standard', 559 Config.get('setting.basic.playQualityOrder') ?? 'asc', 560 ); 561 // 5.3 插件返回音源 562 let source: IPlugin.IMediaSourceResult | null = null; 563 for (let quality of qualityOrder) { 564 if (isCurrentMusic(musicItem)) { 565 source = 566 (await plugin?.methods?.getMediaSource( 567 musicItem, 568 quality, 569 )) ?? null; 570 // 5.3.1 获取到真实源 571 if (source) { 572 setQuality(quality); 573 break; 574 } 575 } else { 576 // 5.3.2 已经切换到其他歌曲了, 577 return; 578 } 579 } 580 581 if (!isCurrentMusic(musicItem)) { 582 return; 583 } 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 598 // 5.4 没有返回源 599 if (!source && !musicItem.url) { 600 throw new Error(PlayFailReason.INVALID_SOURCE); 601 } else { 602 source = { 603 url: musicItem.url, 604 }; 605 setQuality('standard'); 606 } 607 } 608 609 // 6. 特殊类型源 610 if (getUrlExt(source.url) === '.m3u8') { 611 // @ts-ignore 612 source.type = 'hls'; 613 } 614 // 7. 合并结果 615 track = mergeProps(musicItem, source) as IMusic.IMusicItem; 616 617 // 8. 新增历史记录 618 musicHistory.addMusic(musicItem); 619 620 trace('获取音源成功', track); 621 // 9. 设置音源 622 await setTrackSource(track as Track); 623 624 // 10. 获取补充信息 625 let info: Partial<IMusic.IMusicItem> | null = null; 626 try { 627 info = (await plugin?.methods?.getMusicInfo?.(musicItem)) ?? null; 628 if ( 629 (typeof info?.url === 'string' && info.url.trim() === '') || 630 (info?.url && typeof info.url !== 'string') 631 ) { 632 delete info.url; 633 } 634 } catch {} 635 636 // 11. 设置补充信息 637 if (info && isCurrentMusic(musicItem)) { 638 const mergedTrack = mergeProps(track, info); 639 currentMusicStore.setValue(mergedTrack as IMusic.IMusicItem); 640 await ReactNativeTrackPlayer.updateMetadataForTrack( 641 0, 642 mergedTrack as TrackMetadataBase, 643 ); 644 } 645 } catch (e: any) { 646 const message = e?.message; 647 console.log(message, 'MMM', e); 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 662 // Toast.warn( 663 // '当前禁止移动网络播放音乐,如需播放请去侧边栏-基本设置中修改', 664 // ); 665 } else if (message === PlayFailReason.INVALID_SOURCE) { 666 trace('音源为空,播放失败'); 667 await failToPlay(); 668 } else if (message === PlayFailReason.PLAY_LIST_IS_EMPTY) { 669 // 队列是空的,不应该出现这种情况 670 } 671 } 672}; 673 674/** 675 * 播放音乐,同时替换播放队列 676 * @param musicItem 音乐 677 * @param newPlayList 替代列表 678 */ 679const playWithReplacePlayList = async ( 680 musicItem: IMusic.IMusicItem, 681 newPlayList: IMusic.IMusicItem[], 682) => { 683 if (newPlayList.length !== 0) { 684 const now = Date.now(); 685 if (newPlayList.length > maxMusicQueueLength) { 686 newPlayList = shrinkPlayListToSize( 687 newPlayList, 688 newPlayList.findIndex(it => isSameMediaItem(it, musicItem)), 689 ); 690 } 691 const playListItems = newPlayList.map((item, index) => 692 produce(item, draft => { 693 draft[timeStampSymbol] = now; 694 draft[sortIndexSymbol] = index; 695 }), 696 ); 697 setPlayList( 698 repeatModeStore.getValue() === MusicRepeatMode.SHUFFLE 699 ? shuffle(playListItems) 700 : playListItems, 701 ); 702 await play(musicItem, true); 703 } 704}; 705 706const skipToNext = async () => { 707 if (isPlayListEmpty()) { 708 setCurrentMusic(null); 709 return; 710 } 711 712 await play(getPlayListMusicAt(currentIndex + 1), true); 713}; 714 715const skipToPrevious = async () => { 716 if (isPlayListEmpty()) { 717 setCurrentMusic(null); 718 return; 719 } 720 721 await play( 722 getPlayListMusicAt(currentIndex === -1 ? 0 : currentIndex - 1), 723 true, 724 ); 725}; 726 727/** 修改当前播放的音质 */ 728const changeQuality = async (newQuality: IMusic.IQualityKey) => { 729 // 获取当前的音乐和进度 730 if (newQuality === qualityStore.getValue()) { 731 return true; 732 } 733 734 // 获取当前歌曲 735 const musicItem = currentMusicStore.getValue(); 736 if (!musicItem) { 737 return false; 738 } 739 try { 740 const progress = await ReactNativeTrackPlayer.getProgress(); 741 const plugin = PluginManager.getByMedia(musicItem); 742 const newSource = await plugin?.methods?.getMediaSource( 743 musicItem, 744 newQuality, 745 ); 746 if (!newSource?.url) { 747 throw new Error(PlayFailReason.INVALID_SOURCE); 748 } 749 if (isCurrentMusic(musicItem)) { 750 const playingState = ( 751 await ReactNativeTrackPlayer.getPlaybackState() 752 ).state; 753 await setTrackSource( 754 mergeProps(musicItem, newSource) as unknown as Track, 755 !musicIsPaused(playingState), 756 ); 757 758 await ReactNativeTrackPlayer.seekTo(progress.position ?? 0); 759 setQuality(newQuality); 760 } 761 return true; 762 } catch { 763 // 修改失败 764 return false; 765 } 766}; 767 768enum PlayFailReason { 769 /** 禁止移动网络播放 */ 770 FORBID_CELLUAR_NETWORK_PLAY = 'FORBID_CELLUAR_NETWORK_PLAY', 771 /** 播放列表为空 */ 772 PLAY_LIST_IS_EMPTY = 'PLAY_LIST_IS_EMPTY', 773 /** 无效源 */ 774 INVALID_SOURCE = 'INVALID_SOURCE', 775 /** 非当前音乐 */ 776} 777 778function useMusicState() { 779 const playbackState = usePlaybackState(); 780 781 return playbackState.state; 782} 783 784function getPreviousMusic() { 785 const currentMusicItem = currentMusicStore.getValue(); 786 if (!currentMusicItem) { 787 return null; 788 } 789 790 return getPlayListMusicAt(currentIndex - 1); 791} 792 793function getNextMusic() { 794 const currentMusicItem = currentMusicStore.getValue(); 795 if (!currentMusicItem) { 796 return null; 797 } 798 799 return getPlayListMusicAt(currentIndex + 1); 800} 801 802const TrackPlayer = { 803 setupTrackPlayer, 804 usePlayList, 805 getPlayList, 806 addAll, 807 add, 808 addNext, 809 skipToNext, 810 skipToPrevious, 811 play, 812 playWithReplacePlayList, 813 pause, 814 remove, 815 clear, 816 useCurrentMusic: currentMusicStore.useValue, 817 getCurrentMusic: currentMusicStore.getValue, 818 useRepeatMode: repeatModeStore.useValue, 819 getRepeatMode: repeatModeStore.getValue, 820 toggleRepeatMode, 821 usePlaybackState, 822 getProgress: ReactNativeTrackPlayer.getProgress, 823 useProgress: useProgress, 824 seekTo: ReactNativeTrackPlayer.seekTo, 825 changeQuality, 826 useCurrentQuality: qualityStore.useValue, 827 getCurrentQuality: qualityStore.getValue, 828 getRate: ReactNativeTrackPlayer.getRate, 829 setRate: ReactNativeTrackPlayer.setRate, 830 useMusicState, 831 reset: ReactNativeTrackPlayer.reset, 832 getPreviousMusic, 833 getNextMusic, 834}; 835 836export default TrackPlayer; 837export {MusicRepeatMode, State as MusicState}; 838