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'; 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 62// TODO: 下个版本最大限制调大一些 63const maxMusicQueueLength = 10000; // 当前播放最大限制 64const halfMaxMusicQueueLength = Math.floor(maxMusicQueueLength / 2); 65const shrinkPlayListToSize = ( 66 queue: IMusic.IMusicItem[], 67 targetIndex = currentIndex, 68) => { 69 // 播放列表上限,太多无法缓存状态 70 if (queue.length > maxMusicQueueLength) { 71 if (targetIndex < halfMaxMusicQueueLength) { 72 queue = queue.slice(0, maxMusicQueueLength); 73 } else { 74 const right = Math.min( 75 queue.length, 76 targetIndex + halfMaxMusicQueueLength, 77 ); 78 const left = Math.max(0, right - maxMusicQueueLength); 79 queue = queue.slice(left, right); 80 } 81 } 82 return queue; 83}; 84 85let hasSetupListener = false; 86 87// TODO: 删除 88function migrate() { 89 const config = Config.get('status.music'); 90 if (!config) { 91 return; 92 } 93 const {rate, repeatMode, musicQueue, progress, track} = config; 94 PersistStatus.set('music.rate', rate); 95 PersistStatus.set('music.repeatMode', repeatMode); 96 PersistStatus.set('music.playList', musicQueue); 97 PersistStatus.set('music.progress', progress); 98 PersistStatus.set('music.musicItem', track); 99 Config.set('status.music', undefined); 100} 101 102async function setupTrackPlayer() { 103 migrate(); 104 105 const rate = PersistStatus.get('music.rate'); 106 const musicQueue = PersistStatus.get('music.playList'); 107 const repeatMode = PersistStatus.get('music.repeatMode'); 108 const progress = PersistStatus.get('music.progress'); 109 const track = PersistStatus.get('music.musicItem'); 110 const quality = 111 PersistStatus.get('music.quality') || 112 Config.get('setting.basic.defaultPlayQuality') || 113 'standard'; 114 115 // 状态恢复 116 if (rate) { 117 ReactNativeTrackPlayer.setRate(+rate / 100); 118 } 119 if (repeatMode) { 120 repeatModeStore.setValue(repeatMode as MusicRepeatMode); 121 } 122 123 if (musicQueue && Array.isArray(musicQueue)) { 124 addAll(musicQueue, undefined, repeatMode === MusicRepeatMode.SHUFFLE); 125 } 126 127 if (track && isInPlayList(track)) { 128 if (!Config.get('setting.basic.autoPlayWhenAppStart')) { 129 track.isInit = true; 130 } 131 132 // 异步 133 PluginManager.getByMedia(track) 134 ?.methods.getMediaSource(track, quality, 0) 135 .then(async newSource => { 136 track.url = newSource?.url || track.url; 137 track.headers = newSource?.headers || track.headers; 138 139 if (isSameMediaItem(currentMusicStore.getValue(), track)) { 140 await setTrackSource(track as Track, false); 141 } 142 }); 143 setCurrentMusic(track); 144 145 if (progress) { 146 // 异步 147 ReactNativeTrackPlayer.seekTo(progress); 148 } 149 } 150 151 if (!hasSetupListener) { 152 ReactNativeTrackPlayer.addEventListener( 153 Event.PlaybackActiveTrackChanged, 154 async evt => { 155 if ( 156 evt.index === 1 && 157 evt.lastIndex === 0 && 158 evt.track?.$ === internalFakeSoundKey 159 ) { 160 trace('队列末尾,播放下一首'); 161 if (repeatModeStore.getValue() === MusicRepeatMode.SINGLE) { 162 await play(null, true); 163 } else { 164 // 当前生效的歌曲是下一曲的标记 165 await skipToNext(); 166 } 167 } 168 }, 169 ); 170 171 ReactNativeTrackPlayer.addEventListener( 172 Event.PlaybackError, 173 async e => { 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 // 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 | null | undefined) => { 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 if ( 401 (prevMode === MusicRepeatMode.SHUFFLE && 402 mode !== MusicRepeatMode.SHUFFLE) || 403 (mode === MusicRepeatMode.SHUFFLE && 404 prevMode !== MusicRepeatMode.SHUFFLE) 405 ) { 406 if (mode === MusicRepeatMode.SHUFFLE) { 407 newPlayList = shuffle(playList); 408 } else { 409 newPlayList = sortByTimestampAndIndex(playList, true); 410 } 411 setPlayList(newPlayList); 412 } 413 414 const currentMusicItem = currentMusicStore.getValue(); 415 currentIndex = getMusicIndex(currentMusicItem); 416 repeatModeStore.setValue(mode); 417 // 更新下一首歌的信息 418 ReactNativeTrackPlayer.updateMetadataForTrack(1, getFakeNextTrack()); 419 // 记录 420 PersistStatus.set('music.repeatMode', mode); 421}; 422 423/** 清空播放列表 */ 424const clear = async () => { 425 setPlayList([]); 426 setCurrentMusic(null); 427 428 await ReactNativeTrackPlayer.reset(); 429 PersistStatus.set('music.musicItem', undefined); 430 PersistStatus.set('music.progress', 0); 431}; 432 433/** 暂停 */ 434const pause = async () => { 435 await ReactNativeTrackPlayer.pause(); 436}; 437 438/** 设置音源 */ 439const setTrackSource = async (track: Track, autoPlay = true) => { 440 if (!track.artwork?.trim()?.length) { 441 track.artwork = undefined; 442 } 443 await ReactNativeTrackPlayer.setQueue([track, getFakeNextTrack()]); 444 PersistStatus.set('music.musicItem', track as IMusic.IMusicItem); 445 PersistStatus.set('music.progress', 0); 446 if (autoPlay) { 447 await ReactNativeTrackPlayer.play(); 448 } 449}; 450 451const setCurrentMusic = (musicItem?: IMusic.IMusicItem | null) => { 452 if (!musicItem) { 453 currentIndex = -1; 454 currentMusicStore.setValue(null); 455 PersistStatus.set('music.musicItem', undefined); 456 PersistStatus.set('music.progress', 0); 457 return; 458 } 459 currentIndex = getMusicIndex(musicItem); 460 currentMusicStore.setValue(musicItem); 461}; 462 463const setQuality = (quality: IMusic.IQualityKey) => { 464 qualityStore.setValue(quality); 465 PersistStatus.set('music.quality', quality); 466}; 467/** 468 * 播放 469 * 470 * 当musicItem 为空时,代表暂停/播放 471 * 472 * @param musicItem 473 * @param forcePlay 474 * @returns 475 */ 476const play = async ( 477 musicItem?: IMusic.IMusicItem | null, 478 forcePlay?: boolean, 479) => { 480 try { 481 if (!musicItem) { 482 musicItem = currentMusicStore.getValue(); 483 } 484 if (!musicItem) { 485 throw new Error(PlayFailReason.PLAY_LIST_IS_EMPTY); 486 } 487 // 1. 移动网络禁止播放 488 if ( 489 Network.isCellular() && 490 !Config.get('setting.basic.useCelluarNetworkPlay') && 491 !LocalMusicSheet.isLocalMusic(musicItem) 492 ) { 493 await ReactNativeTrackPlayer.reset(); 494 throw new Error(PlayFailReason.FORBID_CELLUAR_NETWORK_PLAY); 495 } 496 497 // 2. 如果是当前正在播放的音频 498 if (isCurrentMusic(musicItem)) { 499 const currentTrack = await ReactNativeTrackPlayer.getTrack(0); 500 // 2.1 如果当前有源 501 if ( 502 currentTrack?.url && 503 isSameMediaItem(musicItem, currentTrack as IMusic.IMusicItem) 504 ) { 505 const currentActiveIndex = 506 await ReactNativeTrackPlayer.getActiveTrackIndex(); 507 if (currentActiveIndex !== 0) { 508 await ReactNativeTrackPlayer.skip(0); 509 } 510 if (forcePlay) { 511 // 2.1.1 强制重新开始 512 await ReactNativeTrackPlayer.seekTo(0); 513 } 514 const currentState = ( 515 await ReactNativeTrackPlayer.getPlaybackState() 516 ).state; 517 if (currentState === State.Stopped) { 518 await setTrackSource(currentTrack); 519 } 520 if (currentState !== State.Playing) { 521 // 2.1.2 恢复播放 522 await ReactNativeTrackPlayer.play(); 523 } 524 // 这种情况下,播放队列和当前歌曲都不需要变化 525 return; 526 } 527 // 2.2 其他情况:重新获取源 528 } 529 530 // 3. 如果没有在播放列表中,添加到队尾;同时更新列表状态 531 const inPlayList = isInPlayList(musicItem); 532 if (!inPlayList) { 533 add(musicItem); 534 } 535 536 // 4. 更新列表状态和当前音乐 537 setCurrentMusic(musicItem); 538 await ReactNativeTrackPlayer.reset(); 539 540 // 4.1 刷新歌词信息 541 if ( 542 !isSameMediaItem( 543 LyricManager.getLyricState()?.lyricParser?.musicItem, 544 musicItem, 545 ) 546 ) { 547 DeviceEventEmitter.emit(EDeviceEvents.REFRESH_LYRIC, true); 548 } 549 550 // 5. 获取音源 551 let track: IMusic.IMusicItem; 552 553 // 5.1 通过插件获取音源 554 const plugin = PluginManager.getByName(musicItem.platform); 555 // 5.2 获取音质排序 556 const qualityOrder = getQualityOrder( 557 Config.get('setting.basic.defaultPlayQuality') ?? 'standard', 558 Config.get('setting.basic.playQualityOrder') ?? 'asc', 559 ); 560 // 5.3 插件返回音源 561 let source: IPlugin.IMediaSourceResult | null = null; 562 for (let quality of qualityOrder) { 563 if (isCurrentMusic(musicItem)) { 564 source = 565 (await plugin?.methods?.getMediaSource( 566 musicItem, 567 quality, 568 )) ?? null; 569 // 5.3.1 获取到真实源 570 if (source) { 571 setQuality(quality); 572 break; 573 } 574 } else { 575 // 5.3.2 已经切换到其他歌曲了, 576 return; 577 } 578 } 579 580 if (!isCurrentMusic(musicItem)) { 581 return; 582 } 583 if (!source) { 584 // 如果有source 585 if (musicItem.source) { 586 for (let quality of qualityOrder) { 587 if (musicItem.source[quality]?.url) { 588 source = musicItem.source[quality]!; 589 setQuality(quality); 590 591 break; 592 } 593 } 594 } 595 // 5.4 没有返回源 596 if (!source && !musicItem.url) { 597 // 插件失效的情况 598 if (Config.get('setting.basic.tryChangeSourceWhenPlayFail')) { 599 // 重试 600 const similarMusic = await getSimilarMusic( 601 musicItem, 602 'music', 603 () => !isCurrentMusic(musicItem), 604 ); 605 606 if (similarMusic) { 607 const similarMusicPlugin = 608 PluginManager.getByMedia(similarMusic); 609 610 for (let quality of qualityOrder) { 611 if (isCurrentMusic(musicItem)) { 612 source = 613 (await similarMusicPlugin?.methods?.getMediaSource( 614 similarMusic, 615 quality, 616 )) ?? null; 617 // 5.4.1 获取到真实源 618 if (source) { 619 setQuality(quality); 620 break; 621 } 622 } else { 623 // 5.4.2 已经切换到其他歌曲了, 624 return; 625 } 626 } 627 } 628 629 if (!source) { 630 throw new Error(PlayFailReason.INVALID_SOURCE); 631 } 632 } else { 633 throw new Error(PlayFailReason.INVALID_SOURCE); 634 } 635 } else { 636 source = { 637 url: musicItem.url, 638 }; 639 setQuality('standard'); 640 } 641 } 642 643 // 6. 特殊类型源 644 if (getUrlExt(source.url) === '.m3u8') { 645 // @ts-ignore 646 source.type = 'hls'; 647 } 648 // 7. 合并结果 649 track = mergeProps(musicItem, source) as IMusic.IMusicItem; 650 651 // 8. 新增历史记录 652 musicHistory.addMusic(musicItem); 653 654 trace('获取音源成功', track); 655 // 9. 设置音源 656 await setTrackSource(track as Track); 657 658 // 10. 获取补充信息 659 let info: Partial<IMusic.IMusicItem> | null = null; 660 try { 661 info = (await plugin?.methods?.getMusicInfo?.(musicItem)) ?? null; 662 if ( 663 (typeof info?.url === 'string' && info.url.trim() === '') || 664 (info?.url && typeof info.url !== 'string') 665 ) { 666 delete info.url; 667 } 668 } catch {} 669 670 // 11. 设置补充信息 671 if (info && isCurrentMusic(musicItem)) { 672 const mergedTrack = mergeProps(track, info); 673 currentMusicStore.setValue(mergedTrack as IMusic.IMusicItem); 674 await ReactNativeTrackPlayer.updateMetadataForTrack( 675 0, 676 mergedTrack as TrackMetadataBase, 677 ); 678 } 679 } catch (e: any) { 680 const message = e?.message; 681 if ( 682 message === 'The player is not initialized. Call setupPlayer first.' 683 ) { 684 await ReactNativeTrackPlayer.setupPlayer(); 685 play(musicItem, forcePlay); 686 } else if (message === PlayFailReason.FORBID_CELLUAR_NETWORK_PLAY) { 687 if (getCurrentDialog()?.name !== 'SimpleDialog') { 688 showDialog('SimpleDialog', { 689 title: '流量提醒', 690 content: 691 '当前非WIFI环境,侧边栏设置中打开【使用移动网络播放】功能后可继续播放', 692 }); 693 } 694 695 // Toast.warn( 696 // '当前禁止移动网络播放音乐,如需播放请去侧边栏-基本设置中修改', 697 // ); 698 } else if (message === PlayFailReason.INVALID_SOURCE) { 699 trace('音源为空,播放失败'); 700 await failToPlay(); 701 } else if (message === PlayFailReason.PLAY_LIST_IS_EMPTY) { 702 // 队列是空的,不应该出现这种情况 703 } 704 } 705}; 706 707/** 708 * 播放音乐,同时替换播放队列 709 * @param musicItem 音乐 710 * @param newPlayList 替代列表 711 */ 712const playWithReplacePlayList = async ( 713 musicItem: IMusic.IMusicItem, 714 newPlayList: IMusic.IMusicItem[], 715) => { 716 if (newPlayList.length !== 0) { 717 const now = Date.now(); 718 if (newPlayList.length > maxMusicQueueLength) { 719 newPlayList = shrinkPlayListToSize( 720 newPlayList, 721 newPlayList.findIndex(it => isSameMediaItem(it, musicItem)), 722 ); 723 } 724 725 newPlayList.forEach((it, index) => { 726 it[timeStampSymbol] = now; 727 it[sortIndexSymbol] = index; 728 }); 729 730 setPlayList( 731 repeatModeStore.getValue() === MusicRepeatMode.SHUFFLE 732 ? shuffle(newPlayList) 733 : newPlayList, 734 ); 735 await play(musicItem, true); 736 } 737}; 738 739const skipToNext = async () => { 740 if (isPlayListEmpty()) { 741 setCurrentMusic(null); 742 return; 743 } 744 745 await play(getPlayListMusicAt(currentIndex + 1), true); 746}; 747 748const skipToPrevious = async () => { 749 if (isPlayListEmpty()) { 750 setCurrentMusic(null); 751 return; 752 } 753 754 await play( 755 getPlayListMusicAt(currentIndex === -1 ? 0 : currentIndex - 1), 756 true, 757 ); 758}; 759 760/** 修改当前播放的音质 */ 761const changeQuality = async (newQuality: IMusic.IQualityKey) => { 762 // 获取当前的音乐和进度 763 if (newQuality === qualityStore.getValue()) { 764 return true; 765 } 766 767 // 获取当前歌曲 768 const musicItem = currentMusicStore.getValue(); 769 if (!musicItem) { 770 return false; 771 } 772 try { 773 const progress = await ReactNativeTrackPlayer.getProgress(); 774 const plugin = PluginManager.getByMedia(musicItem); 775 const newSource = await plugin?.methods?.getMediaSource( 776 musicItem, 777 newQuality, 778 ); 779 if (!newSource?.url) { 780 throw new Error(PlayFailReason.INVALID_SOURCE); 781 } 782 if (isCurrentMusic(musicItem)) { 783 const playingState = ( 784 await ReactNativeTrackPlayer.getPlaybackState() 785 ).state; 786 await setTrackSource( 787 mergeProps(musicItem, newSource) as unknown as Track, 788 !musicIsPaused(playingState), 789 ); 790 791 await ReactNativeTrackPlayer.seekTo(progress.position ?? 0); 792 setQuality(newQuality); 793 } 794 return true; 795 } catch { 796 // 修改失败 797 return false; 798 } 799}; 800 801enum PlayFailReason { 802 /** 禁止移动网络播放 */ 803 FORBID_CELLUAR_NETWORK_PLAY = 'FORBID_CELLUAR_NETWORK_PLAY', 804 /** 播放列表为空 */ 805 PLAY_LIST_IS_EMPTY = 'PLAY_LIST_IS_EMPTY', 806 /** 无效源 */ 807 INVALID_SOURCE = 'INVALID_SOURCE', 808 /** 非当前音乐 */ 809} 810 811function useMusicState() { 812 const playbackState = usePlaybackState(); 813 814 return playbackState.state; 815} 816 817function getPreviousMusic() { 818 const currentMusicItem = currentMusicStore.getValue(); 819 if (!currentMusicItem) { 820 return null; 821 } 822 823 return getPlayListMusicAt(currentIndex - 1); 824} 825 826function getNextMusic() { 827 const currentMusicItem = currentMusicStore.getValue(); 828 if (!currentMusicItem) { 829 return null; 830 } 831 832 return getPlayListMusicAt(currentIndex + 1); 833} 834 835const TrackPlayer = { 836 setupTrackPlayer, 837 usePlayList, 838 getPlayList, 839 addAll, 840 add, 841 addNext, 842 skipToNext, 843 skipToPrevious, 844 play, 845 playWithReplacePlayList, 846 pause, 847 remove, 848 clear, 849 useCurrentMusic: currentMusicStore.useValue, 850 getCurrentMusic: currentMusicStore.getValue, 851 useRepeatMode: repeatModeStore.useValue, 852 getRepeatMode: repeatModeStore.getValue, 853 toggleRepeatMode, 854 usePlaybackState, 855 getProgress: ReactNativeTrackPlayer.getProgress, 856 useProgress: useProgress, 857 seekTo: ReactNativeTrackPlayer.seekTo, 858 changeQuality, 859 useCurrentQuality: qualityStore.useValue, 860 getCurrentQuality: qualityStore.getValue, 861 getRate: ReactNativeTrackPlayer.getRate, 862 setRate: ReactNativeTrackPlayer.setRate, 863 useMusicState, 864 reset: ReactNativeTrackPlayer.reset, 865 getPreviousMusic, 866 getNextMusic, 867}; 868 869export default TrackPlayer; 870export {MusicRepeatMode, State as MusicState}; 871