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