1import {internalSerializeKey, StorageKeys} from '@/constants/commonConst'; 2import mp3Util, {IBasicMeta} from '@/native/mp3Util'; 3import { 4 getInternalData, 5 InternalDataType, 6 isSameMediaItem, 7} from '@/utils/mediaItem'; 8import StateMapper from '@/utils/stateMapper'; 9import {getStorage, setStorage} from '@/utils/storage'; 10import {useEffect, useState} from 'react'; 11import {FileSystem} from 'react-native-file-access'; 12 13let localSheet: IMusic.IMusicItem[] = []; 14const localSheetStateMapper = new StateMapper(() => localSheet); 15 16export async function setup() { 17 const sheet = await getStorage(StorageKeys.LocalMusicSheet); 18 if (sheet) { 19 let validSheet = []; 20 for (let musicItem of sheet) { 21 const localPath = getInternalData<string>( 22 musicItem, 23 InternalDataType.LOCALPATH, 24 ); 25 if (localPath && (await FileSystem.exists(localPath))) { 26 validSheet.push(musicItem); 27 } 28 } 29 if (validSheet.length !== sheet.length) { 30 await setStorage(StorageKeys.LocalMusicSheet, validSheet); 31 } 32 localSheet = validSheet; 33 } else { 34 await setStorage(StorageKeys.LocalMusicSheet, []); 35 } 36 localSheetStateMapper.notify(); 37} 38 39export async function addMusic( 40 musicItem: IMusic.IMusicItem | IMusic.IMusicItem[], 41) { 42 if (!Array.isArray(musicItem)) { 43 musicItem = [musicItem]; 44 } 45 let newSheet = [...localSheet]; 46 musicItem.forEach(mi => { 47 if (localSheet.findIndex(_ => isSameMediaItem(mi, _)) === -1) { 48 newSheet.push(mi); 49 } 50 }); 51 await setStorage(StorageKeys.LocalMusicSheet, newSheet); 52 localSheet = newSheet; 53 localSheetStateMapper.notify(); 54} 55 56export async function removeMusic( 57 musicItem: IMusic.IMusicItem, 58 deleteOriginalFile = false, 59) { 60 const idx = localSheet.findIndex(_ => isSameMediaItem(_, musicItem)); 61 let newSheet = [...localSheet]; 62 if (idx !== -1) { 63 newSheet.splice(idx, 1); 64 if (deleteOriginalFile && musicItem[internalSerializeKey]?.localPath) { 65 await FileSystem.unlink(musicItem[internalSerializeKey].localPath); 66 } 67 } 68 localSheet = newSheet; 69 localSheetStateMapper.notify(); 70} 71 72function parseFilename(fn: string): Partial<IMusic.IMusicItem> | null { 73 const data = fn.slice(0, fn.lastIndexOf('.')).split('@'); 74 const [platform, id, title, artist] = data; 75 if (!platform || !id) { 76 return null; 77 } 78 return { 79 id, 80 platform, 81 title, 82 artist, 83 }; 84} 85 86/** 从文件夹导入 */ 87async function importFolder(folderPath: string) { 88 const dirFiles = await FileSystem.statDir(folderPath); 89 const musicFiles = dirFiles.filter( 90 _ => _.type === 'file' && _.filename.endsWith('.mp3'), 91 ); 92 93 const musicItems: IMusic.IMusicItem[] = await Promise.all( 94 musicFiles.map(async mf => { 95 let {platform, id, title, artist} = 96 parseFilename(mf.filename) ?? {}; 97 98 let meta: IBasicMeta | null; 99 try { 100 meta = await mp3Util.getBasicMeta(mf.path); 101 } catch { 102 meta = null; 103 } 104 if (!platform || !id) { 105 platform = '本地'; 106 id = await FileSystem.hash(mf.path, 'MD5'); 107 } 108 return { 109 id, 110 platform, 111 title: title ?? meta?.title ?? '未知名称', 112 artist: artist ?? meta?.artist ?? '未知歌手', 113 duration: parseInt(meta?.duration ?? '0') / 1000, 114 album: meta?.album ?? '', 115 artwork: '', 116 [internalSerializeKey]: { 117 localPath: mf.path, 118 }, 119 }; 120 }), 121 ); 122 addMusic(musicItems); 123} 124 125/** 是否为本地音乐 */ 126function isLocalMusic( 127 musicItem: ICommon.IMediaBase | null, 128): IMusic.IMusicItem | undefined { 129 return musicItem 130 ? localSheet.find(_ => isSameMediaItem(_, musicItem)) 131 : undefined; 132} 133 134/** 状态-是否为本地音乐 */ 135function useIsLocal(musicItem: IMusic.IMusicItem | null) { 136 const localMusicState = localSheetStateMapper.useMappedState(); 137 const [isLocal, setIsLocal] = useState<boolean>(!!isLocalMusic(musicItem)); 138 useEffect(() => { 139 if (!musicItem) { 140 setIsLocal(false); 141 } else { 142 setIsLocal(!!isLocalMusic(musicItem)); 143 } 144 }, [localMusicState, musicItem]); 145 return isLocal; 146} 147 148function getMusicList() { 149 return localSheet; 150} 151 152const LocalMusicSheet = { 153 setup, 154 addMusic, 155 removeMusic, 156 importFolder, 157 isLocalMusic, 158 useIsLocal, 159 getMusicList, 160 useMusicList: localSheetStateMapper.useMappedState, 161}; 162 163export default LocalMusicSheet; 164