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