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 await ReactNativeTrackPlayer.reset(); 501 502 // 4.1 刷新歌词信息 503 if ( 504 !isSameMediaItem( 505 LyricManager.getLyricState()?.lyricParser?.getCurrentMusicItem?.(), 506 musicItem, 507 ) 508 ) { 509 DeviceEventEmitter.emit(EDeviceEvents.REFRESH_LYRIC, true); 510 } 511 512 // 5. 获取音源 513 let track: IMusic.IMusicItem; 514 515 // 5.1 通过插件获取音源 516 const plugin = PluginManager.getByName(musicItem.platform); 517 // 5.2 获取音质排序 518 const qualityOrder = getQualityOrder( 519 Config.get('setting.basic.defaultPlayQuality') ?? 'standard', 520 Config.get('setting.basic.playQualityOrder') ?? 'asc', 521 ); 522 // 5.3 插件返回音源 523 let source: IPlugin.IMediaSourceResult | null = null; 524 for (let quality of qualityOrder) { 525 if (isCurrentMusic(musicItem)) { 526 source = 527 (await plugin?.methods?.getMediaSource( 528 musicItem, 529 quality, 530 )) ?? null; 531 // 5.3.1 获取到真实源 532 if (source) { 533 setQuality(quality); 534 535 break; 536 } 537 } else { 538 // 5.3.2 已经切换到其他歌曲了, 539 return; 540 } 541 } 542 543 if (!isCurrentMusic(musicItem)) { 544 return; 545 } 546 547 if (!source) { 548 // 如果有source 549 if (musicItem.source) { 550 for (let quality of qualityOrder) { 551 if (musicItem.source[quality]?.url) { 552 source = musicItem.source[quality]!; 553 setQuality(quality); 554 555 break; 556 } 557 } 558 } 559 560 // 5.4 没有返回源 561 if (!source && !musicItem.url) { 562 throw new Error(PlayFailReason.INVALID_SOURCE); 563 } else { 564 source = { 565 url: musicItem.url, 566 }; 567 setQuality('standard'); 568 } 569 } 570 571 // 6. 特殊类型源 572 if (getUrlExt(source.url) === '.m3u8') { 573 // @ts-ignore 574 source.type = 'hls'; 575 } 576 // 7. 合并结果 577 track = mergeProps(musicItem, source) as IMusic.IMusicItem; 578 579 // 8. 新增历史记录 580 musicHistory.addMusic(musicItem); 581 582 trace('获取音源成功', track); 583 584 // 9. 设置音源 585 await setTrackSource(track as Track); 586 587 // 10. 获取补充信息 588 let info: Partial<IMusic.IMusicItem> | null = null; 589 try { 590 info = (await plugin?.methods?.getMusicInfo?.(musicItem)) ?? null; 591 } catch {} 592 593 // 11. 设置补充信息 594 if (info && isCurrentMusic(musicItem)) { 595 const mergedTrack = mergeProps(track, info); 596 currentMusicStore.setValue(mergedTrack as IMusic.IMusicItem); 597 await ReactNativeTrackPlayer.updateMetadataForTrack( 598 0, 599 mergedTrack as TrackMetadataBase, 600 ); 601 } 602 } catch (e: any) { 603 const message = e?.message; 604 if ( 605 message === 'The player is not initialized. Call setupPlayer first.' 606 ) { 607 await ReactNativeTrackPlayer.setupPlayer(); 608 play(musicItem, forcePlay); 609 } else if (message === PlayFailReason.FORBID_CELLUAR_NETWORK_PLAY) { 610 Toast.warn( 611 '当前禁止移动网络播放音乐,如需播放请去侧边栏-基本设置中修改', 612 ); 613 } else if (message === PlayFailReason.INVALID_SOURCE) { 614 trace('音源为空,播放失败'); 615 await failToPlay(); 616 } else if (message === PlayFailReason.PLAY_LIST_IS_EMPTY) { 617 // 队列是空的,不应该出现这种情况 618 } 619 } 620}; 621 622/** 623 * 播放音乐,同时替换播放队列 624 * @param musicItem 音乐 625 * @param newPlayList 替代列表 626 */ 627const playWithReplacePlayList = async ( 628 musicItem: IMusic.IMusicItem, 629 newPlayList: IMusic.IMusicItem[], 630) => { 631 if (newPlayList.length !== 0) { 632 const now = Date.now(); 633 if (newPlayList.length > maxMusicQueueLength) { 634 newPlayList = shrinkPlayListToSize( 635 newPlayList, 636 newPlayList.findIndex(it => isSameMediaItem(it, musicItem)), 637 ); 638 } 639 const playListItems = newPlayList.map((item, index) => 640 produce(item, draft => { 641 draft[timeStampSymbol] = now; 642 draft[sortIndexSymbol] = index; 643 }), 644 ); 645 setPlayList( 646 repeatModeStore.getValue() === MusicRepeatMode.SHUFFLE 647 ? shuffle(playListItems) 648 : playListItems, 649 ); 650 await play(musicItem, true); 651 } 652}; 653 654const skipToNext = async () => { 655 if (isPlayListEmpty()) { 656 setCurrentMusic(null); 657 return; 658 } 659 660 await play(getPlayListMusicAt(currentIndex + 1), true); 661}; 662 663const skipToPrevious = async () => { 664 if (isPlayListEmpty()) { 665 setCurrentMusic(null); 666 return; 667 } 668 669 await play( 670 getPlayListMusicAt(currentIndex === -1 ? 0 : currentIndex - 1), 671 true, 672 ); 673}; 674 675/** 修改当前播放的音质 */ 676const changeQuality = async (newQuality: IMusic.IQualityKey) => { 677 // 获取当前的音乐和进度 678 if (newQuality === qualityStore.getValue()) { 679 return true; 680 } 681 682 // 获取当前歌曲 683 const musicItem = currentMusicStore.getValue(); 684 if (!musicItem) { 685 return false; 686 } 687 try { 688 const progress = await ReactNativeTrackPlayer.getProgress(); 689 const plugin = PluginManager.getByMedia(musicItem); 690 const newSource = await plugin?.methods?.getMediaSource( 691 musicItem, 692 newQuality, 693 ); 694 if (!newSource?.url) { 695 throw new Error(PlayFailReason.INVALID_SOURCE); 696 } 697 if (isCurrentMusic(musicItem)) { 698 const playingState = ( 699 await ReactNativeTrackPlayer.getPlaybackState() 700 ).state; 701 await setTrackSource( 702 mergeProps(musicItem, newSource) as unknown as Track, 703 !musicIsPaused(playingState), 704 ); 705 706 await ReactNativeTrackPlayer.seekTo(progress.position ?? 0); 707 setQuality(newQuality); 708 } 709 return true; 710 } catch { 711 // 修改失败 712 return false; 713 } 714}; 715 716enum PlayFailReason { 717 /** 禁止移动网络播放 */ 718 FORBID_CELLUAR_NETWORK_PLAY = 'FORBID_CELLUAR_NETWORK_PLAY', 719 /** 播放列表为空 */ 720 PLAY_LIST_IS_EMPTY = 'PLAY_LIST_IS_EMPTY', 721 /** 无效源 */ 722 INVALID_SOURCE = 'INVALID_SOURCE', 723 /** 非当前音乐 */ 724} 725 726function useMusicState() { 727 const playbackState = usePlaybackState(); 728 729 return playbackState.state; 730} 731 732function getPreviousMusic() { 733 const currentMusicItem = currentMusicStore.getValue(); 734 if (!currentMusicItem) { 735 return null; 736 } 737 738 return getPlayListMusicAt(currentIndex - 1); 739} 740 741function getNextMusic() { 742 const currentMusicItem = currentMusicStore.getValue(); 743 if (!currentMusicItem) { 744 return null; 745 } 746 747 return getPlayListMusicAt(currentIndex + 1); 748} 749 750const TrackPlayer = { 751 setupTrackPlayer, 752 usePlayList, 753 getPlayList, 754 addAll, 755 add, 756 addNext, 757 skipToNext, 758 skipToPrevious, 759 play, 760 playWithReplacePlayList, 761 pause, 762 remove, 763 clear, 764 useCurrentMusic: currentMusicStore.useValue, 765 getCurrentMusic: currentMusicStore.getValue, 766 useRepeatMode: repeatModeStore.useValue, 767 getRepeatMode: repeatModeStore.getValue, 768 toggleRepeatMode, 769 usePlaybackState, 770 getProgress: ReactNativeTrackPlayer.getProgress, 771 useProgress: useProgress, 772 seekTo: ReactNativeTrackPlayer.seekTo, 773 changeQuality, 774 useCurrentQuality: qualityStore.useValue, 775 getCurrentQuality: qualityStore.getValue, 776 getRate: ReactNativeTrackPlayer.getRate, 777 setRate: ReactNativeTrackPlayer.setRate, 778 useMusicState, 779 reset: ReactNativeTrackPlayer.reset, 780 getPreviousMusic, 781 getNextMusic, 782}; 783 784export default TrackPlayer; 785export {MusicRepeatMode, State as MusicState}; 786