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 } catch {} 629 630 // 11. 设置补充信息 631 if (info && isCurrentMusic(musicItem)) { 632 const mergedTrack = mergeProps(track, info); 633 currentMusicStore.setValue(mergedTrack as IMusic.IMusicItem); 634 await ReactNativeTrackPlayer.updateMetadataForTrack( 635 0, 636 mergedTrack as TrackMetadataBase, 637 ); 638 } 639 } catch (e: any) { 640 const message = e?.message; 641 console.log(message, 'MMM', e); 642 if ( 643 message === 'The player is not initialized. Call setupPlayer first.' 644 ) { 645 await ReactNativeTrackPlayer.setupPlayer(); 646 play(musicItem, forcePlay); 647 } else if (message === PlayFailReason.FORBID_CELLUAR_NETWORK_PLAY) { 648 if (getCurrentDialog()?.name !== 'SimpleDialog') { 649 showDialog('SimpleDialog', { 650 title: '流量提醒', 651 content: 652 '当前非WIFI环境,打开【使用移动网络播放】功能后可继续播放', 653 }); 654 } 655 656 // Toast.warn( 657 // '当前禁止移动网络播放音乐,如需播放请去侧边栏-基本设置中修改', 658 // ); 659 } else if (message === PlayFailReason.INVALID_SOURCE) { 660 trace('音源为空,播放失败'); 661 await failToPlay(); 662 } else if (message === PlayFailReason.PLAY_LIST_IS_EMPTY) { 663 // 队列是空的,不应该出现这种情况 664 } 665 } 666}; 667 668/** 669 * 播放音乐,同时替换播放队列 670 * @param musicItem 音乐 671 * @param newPlayList 替代列表 672 */ 673const playWithReplacePlayList = async ( 674 musicItem: IMusic.IMusicItem, 675 newPlayList: IMusic.IMusicItem[], 676) => { 677 if (newPlayList.length !== 0) { 678 const now = Date.now(); 679 if (newPlayList.length > maxMusicQueueLength) { 680 newPlayList = shrinkPlayListToSize( 681 newPlayList, 682 newPlayList.findIndex(it => isSameMediaItem(it, musicItem)), 683 ); 684 } 685 const playListItems = newPlayList.map((item, index) => 686 produce(item, draft => { 687 draft[timeStampSymbol] = now; 688 draft[sortIndexSymbol] = index; 689 }), 690 ); 691 setPlayList( 692 repeatModeStore.getValue() === MusicRepeatMode.SHUFFLE 693 ? shuffle(playListItems) 694 : playListItems, 695 ); 696 await play(musicItem, true); 697 } 698}; 699 700const skipToNext = async () => { 701 if (isPlayListEmpty()) { 702 setCurrentMusic(null); 703 return; 704 } 705 706 await play(getPlayListMusicAt(currentIndex + 1), true); 707}; 708 709const skipToPrevious = async () => { 710 if (isPlayListEmpty()) { 711 setCurrentMusic(null); 712 return; 713 } 714 715 await play( 716 getPlayListMusicAt(currentIndex === -1 ? 0 : currentIndex - 1), 717 true, 718 ); 719}; 720 721/** 修改当前播放的音质 */ 722const changeQuality = async (newQuality: IMusic.IQualityKey) => { 723 // 获取当前的音乐和进度 724 if (newQuality === qualityStore.getValue()) { 725 return true; 726 } 727 728 // 获取当前歌曲 729 const musicItem = currentMusicStore.getValue(); 730 if (!musicItem) { 731 return false; 732 } 733 try { 734 const progress = await ReactNativeTrackPlayer.getProgress(); 735 const plugin = PluginManager.getByMedia(musicItem); 736 const newSource = await plugin?.methods?.getMediaSource( 737 musicItem, 738 newQuality, 739 ); 740 if (!newSource?.url) { 741 throw new Error(PlayFailReason.INVALID_SOURCE); 742 } 743 if (isCurrentMusic(musicItem)) { 744 const playingState = ( 745 await ReactNativeTrackPlayer.getPlaybackState() 746 ).state; 747 await setTrackSource( 748 mergeProps(musicItem, newSource) as unknown as Track, 749 !musicIsPaused(playingState), 750 ); 751 752 await ReactNativeTrackPlayer.seekTo(progress.position ?? 0); 753 setQuality(newQuality); 754 } 755 return true; 756 } catch { 757 // 修改失败 758 return false; 759 } 760}; 761 762enum PlayFailReason { 763 /** 禁止移动网络播放 */ 764 FORBID_CELLUAR_NETWORK_PLAY = 'FORBID_CELLUAR_NETWORK_PLAY', 765 /** 播放列表为空 */ 766 PLAY_LIST_IS_EMPTY = 'PLAY_LIST_IS_EMPTY', 767 /** 无效源 */ 768 INVALID_SOURCE = 'INVALID_SOURCE', 769 /** 非当前音乐 */ 770} 771 772function useMusicState() { 773 const playbackState = usePlaybackState(); 774 775 return playbackState.state; 776} 777 778function getPreviousMusic() { 779 const currentMusicItem = currentMusicStore.getValue(); 780 if (!currentMusicItem) { 781 return null; 782 } 783 784 return getPlayListMusicAt(currentIndex - 1); 785} 786 787function getNextMusic() { 788 const currentMusicItem = currentMusicStore.getValue(); 789 if (!currentMusicItem) { 790 return null; 791 } 792 793 return getPlayListMusicAt(currentIndex + 1); 794} 795 796const TrackPlayer = { 797 setupTrackPlayer, 798 usePlayList, 799 getPlayList, 800 addAll, 801 add, 802 addNext, 803 skipToNext, 804 skipToPrevious, 805 play, 806 playWithReplacePlayList, 807 pause, 808 remove, 809 clear, 810 useCurrentMusic: currentMusicStore.useValue, 811 getCurrentMusic: currentMusicStore.getValue, 812 useRepeatMode: repeatModeStore.useValue, 813 getRepeatMode: repeatModeStore.getValue, 814 toggleRepeatMode, 815 usePlaybackState, 816 getProgress: ReactNativeTrackPlayer.getProgress, 817 useProgress: useProgress, 818 seekTo: ReactNativeTrackPlayer.seekTo, 819 changeQuality, 820 useCurrentQuality: qualityStore.useValue, 821 getCurrentQuality: qualityStore.getValue, 822 getRate: ReactNativeTrackPlayer.getRate, 823 setRate: ReactNativeTrackPlayer.setRate, 824 useMusicState, 825 reset: ReactNativeTrackPlayer.reset, 826 getPreviousMusic, 827 getNextMusic, 828}; 829 830export default TrackPlayer; 831export {MusicRepeatMode, State as MusicState}; 832