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