18fc75cb2S猫头猫import { 28fc75cb2S猫头猫 internalSerializeKey, 38fc75cb2S猫头猫 StorageKeys, 48fc75cb2S猫头猫 supportLocalMediaType, 58fc75cb2S猫头猫} from '@/constants/commonConst'; 68423b2d4S猫头猫import mp3Util, {IBasicMeta} from '@/native/mp3Util'; 788772aabS猫头猫import { 888772aabS猫头猫 getInternalData, 988772aabS猫头猫 InternalDataType, 1088772aabS猫头猫 isSameMediaItem, 1188772aabS猫头猫} from '@/utils/mediaItem'; 12afb5c234S猫头猫import StateMapper from '@/utils/stateMapper'; 13afb5c234S猫头猫import {getStorage, setStorage} from '@/utils/storage'; 14806b2764S猫头猫import {nanoid} from 'nanoid'; 150e4173cdS猫头猫import {useEffect, useState} from 'react'; 16569da2d8S猫头猫import {unlink} from 'react-native-fs'; 17*5353b473S猫头猫import {getInfoAsync, readDirectoryAsync} from 'expo-file-system'; 18*5353b473S猫头猫import {addFileScheme, getFileName} from '@/utils/fileUtils.ts'; 19afb5c234S猫头猫 20afb5c234S猫头猫let localSheet: IMusic.IMusicItem[] = []; 21afb5c234S猫头猫const localSheetStateMapper = new StateMapper(() => localSheet); 22afb5c234S猫头猫 23afb5c234S猫头猫export async function setup() { 2488772aabS猫头猫 const sheet = await getStorage(StorageKeys.LocalMusicSheet); 2588772aabS猫头猫 if (sheet) { 265589cdf3S猫头猫 let validSheet: IMusic.IMusicItem[] = []; 2788772aabS猫头猫 for (let musicItem of sheet) { 2888772aabS猫头猫 const localPath = getInternalData<string>( 2988772aabS猫头猫 musicItem, 3088772aabS猫头猫 InternalDataType.LOCALPATH, 3188772aabS猫头猫 ); 32*5353b473S猫头猫 if (localPath && (await getInfoAsync(localPath)).exists) { 3388772aabS猫头猫 validSheet.push(musicItem); 3488772aabS猫头猫 } 3588772aabS猫头猫 } 3688772aabS猫头猫 if (validSheet.length !== sheet.length) { 3788772aabS猫头猫 await setStorage(StorageKeys.LocalMusicSheet, validSheet); 3888772aabS猫头猫 } 3988772aabS猫头猫 localSheet = validSheet; 40afb5c234S猫头猫 } else { 41afb5c234S猫头猫 await setStorage(StorageKeys.LocalMusicSheet, []); 42afb5c234S猫头猫 } 43afb5c234S猫头猫 localSheetStateMapper.notify(); 44afb5c234S猫头猫} 45afb5c234S猫头猫 46afb5c234S猫头猫export async function addMusic( 47afb5c234S猫头猫 musicItem: IMusic.IMusicItem | IMusic.IMusicItem[], 48afb5c234S猫头猫) { 49afb5c234S猫头猫 if (!Array.isArray(musicItem)) { 50afb5c234S猫头猫 musicItem = [musicItem]; 51afb5c234S猫头猫 } 52afb5c234S猫头猫 let newSheet = [...localSheet]; 53afb5c234S猫头猫 musicItem.forEach(mi => { 54afb5c234S猫头猫 if (localSheet.findIndex(_ => isSameMediaItem(mi, _)) === -1) { 55afb5c234S猫头猫 newSheet.push(mi); 56afb5c234S猫头猫 } 57afb5c234S猫头猫 }); 58afb5c234S猫头猫 await setStorage(StorageKeys.LocalMusicSheet, newSheet); 59afb5c234S猫头猫 localSheet = newSheet; 60afb5c234S猫头猫 localSheetStateMapper.notify(); 61afb5c234S猫头猫} 62afb5c234S猫头猫 63dc160d50S猫头猫function addMusicDraft(musicItem: IMusic.IMusicItem | IMusic.IMusicItem[]) { 64dc160d50S猫头猫 if (!Array.isArray(musicItem)) { 65dc160d50S猫头猫 musicItem = [musicItem]; 66dc160d50S猫头猫 } 67dc160d50S猫头猫 let newSheet = [...localSheet]; 68dc160d50S猫头猫 musicItem.forEach(mi => { 69dc160d50S猫头猫 if (localSheet.findIndex(_ => isSameMediaItem(mi, _)) === -1) { 70dc160d50S猫头猫 newSheet.push(mi); 71dc160d50S猫头猫 } 72dc160d50S猫头猫 }); 73dc160d50S猫头猫 localSheet = newSheet; 74dc160d50S猫头猫 localSheetStateMapper.notify(); 75dc160d50S猫头猫} 76dc160d50S猫头猫 77dc160d50S猫头猫async function saveLocalSheet() { 78dc160d50S猫头猫 await setStorage(StorageKeys.LocalMusicSheet, localSheet); 79dc160d50S猫头猫} 80dc160d50S猫头猫 81afb5c234S猫头猫export async function removeMusic( 82afb5c234S猫头猫 musicItem: IMusic.IMusicItem, 83afb5c234S猫头猫 deleteOriginalFile = false, 84afb5c234S猫头猫) { 85afb5c234S猫头猫 const idx = localSheet.findIndex(_ => isSameMediaItem(_, musicItem)); 86afb5c234S猫头猫 let newSheet = [...localSheet]; 87afb5c234S猫头猫 if (idx !== -1) { 88e65882dbS猫头猫 const localMusicItem = localSheet[idx]; 89afb5c234S猫头猫 newSheet.splice(idx, 1); 90e65882dbS猫头猫 const localPath = 91e65882dbS猫头猫 musicItem[internalSerializeKey]?.localPath ?? 92e65882dbS猫头猫 localMusicItem[internalSerializeKey]?.localPath; 93e65882dbS猫头猫 if (deleteOriginalFile && localPath) { 94483fe585S猫头猫 try { 95569da2d8S猫头猫 await unlink(localPath); 96483fe585S猫头猫 } catch (e: any) { 97483fe585S猫头猫 if (e.message !== 'File does not exist') { 98483fe585S猫头猫 throw e; 99483fe585S猫头猫 } 100483fe585S猫头猫 } 101afb5c234S猫头猫 } 102afb5c234S猫头猫 } 103afb5c234S猫头猫 localSheet = newSheet; 104afb5c234S猫头猫 localSheetStateMapper.notify(); 105cc77e86bS猫头猫 saveLocalSheet(); 106afb5c234S猫头猫} 107afb5c234S猫头猫 108afb5c234S猫头猫function parseFilename(fn: string): Partial<IMusic.IMusicItem> | null { 109afb5c234S猫头猫 const data = fn.slice(0, fn.lastIndexOf('.')).split('@'); 110afb5c234S猫头猫 const [platform, id, title, artist] = data; 111afb5c234S猫头猫 if (!platform || !id) { 112afb5c234S猫头猫 return null; 113afb5c234S猫头猫 } 114afb5c234S猫头猫 return { 115afb5c234S猫头猫 id, 116f55de8c2S猫头猫 platform: platform, 117f55de8c2S猫头猫 title: title ?? '', 118f55de8c2S猫头猫 artist: artist ?? '', 119afb5c234S猫头猫 }; 120afb5c234S猫头猫} 121afb5c234S猫头猫 122*5353b473S猫头猫function localMediaFilter(filename: string) { 123*5353b473S猫头猫 return supportLocalMediaType.some(ext => filename.endsWith(ext)); 124cd669353S猫头猫} 125cd669353S猫头猫 126806b2764S猫头猫let importToken: string | null = null; 127806b2764S猫头猫// 获取本地的文件列表 128806b2764S猫头猫async function getMusicStats(folderPaths: string[]) { 129806b2764S猫头猫 const _importToken = nanoid(); 130806b2764S猫头猫 importToken = _importToken; 131*5353b473S猫头猫 const musicList: string[] = []; 132806b2764S猫头猫 let peek: string | undefined; 133*5353b473S猫头猫 let dirFiles: string[] = []; 134806b2764S猫头猫 while (folderPaths.length !== 0) { 135806b2764S猫头猫 if (importToken !== _importToken) { 136806b2764S猫头猫 throw new Error('Import Broken'); 137806b2764S猫头猫 } 138806b2764S猫头猫 peek = folderPaths.shift() as string; 139cd669353S猫头猫 try { 140*5353b473S猫头猫 dirFiles = await readDirectoryAsync(peek); 141806b2764S猫头猫 } catch { 142806b2764S猫头猫 dirFiles = []; 143806b2764S猫头猫 } 144cd669353S猫头猫 145*5353b473S猫头猫 await Promise.all( 146*5353b473S猫头猫 dirFiles.map(async fileName => { 147*5353b473S猫头猫 const stat = await getInfoAsync(peek + '/' + fileName); 148*5353b473S猫头猫 if (!stat.exists) { 149*5353b473S猫头猫 return; 150cd669353S猫头猫 } 151*5353b473S猫头猫 if (stat.isDirectory && !folderPaths.includes(stat.uri)) { 152*5353b473S猫头猫 folderPaths.push(stat.uri); 153*5353b473S猫头猫 } else if (localMediaFilter(stat.uri)) { 154*5353b473S猫头猫 musicList.push(stat.uri); 155*5353b473S猫头猫 } 156*5353b473S猫头猫 }), 157*5353b473S猫头猫 ); 158cd669353S猫头猫 } 159806b2764S猫头猫 return {musicList, token: _importToken}; 160806b2764S猫头猫} 161806b2764S猫头猫 162806b2764S猫头猫function cancelImportLocal() { 163806b2764S猫头猫 importToken = null; 164cd669353S猫头猫} 165cd669353S猫头猫 166cd669353S猫头猫// 导入本地音乐 1674173d3ebS猫头猫const groupNum = 25; 168806b2764S猫头猫async function importLocal(_folderPaths: string[]) { 169*5353b473S猫头猫 const folderPaths = [..._folderPaths.map(it => addFileScheme(it))]; 170806b2764S猫头猫 const {musicList, token} = await getMusicStats(folderPaths); 171*5353b473S猫头猫 console.log('HI!!!', musicList, folderPaths, _folderPaths); 172806b2764S猫头猫 if (token !== importToken) { 173806b2764S猫头猫 throw new Error('Import Broken'); 174806b2764S猫头猫 } 1758423b2d4S猫头猫 // 分组请求,不然序列化可能出问题 1768423b2d4S猫头猫 let metas: IBasicMeta[] = []; 1778423b2d4S猫头猫 const groups = Math.ceil(musicList.length / groupNum); 1788423b2d4S猫头猫 for (let i = 0; i < groups; ++i) { 1798423b2d4S猫头猫 metas = metas.concat( 1808423b2d4S猫头猫 await mp3Util.getMediaMeta( 181*5353b473S猫头猫 musicList.slice(i * groupNum, (i + 1) * groupNum), 1828423b2d4S猫头猫 ), 1838423b2d4S猫头猫 ); 1848423b2d4S猫头猫 } 185806b2764S猫头猫 if (token !== importToken) { 186806b2764S猫头猫 throw new Error('Import Broken'); 187806b2764S猫头猫 } 188*5353b473S猫头猫 const musicItems: IMusic.IMusicItem[] = await Promise.all( 189*5353b473S猫头猫 musicList.map(async (musicPath, index) => { 190cd669353S猫头猫 let {platform, id, title, artist} = 191*5353b473S猫头猫 parseFilename(getFileName(musicPath, true)) ?? {}; 192cd669353S猫头猫 const meta = metas[index]; 193cd669353S猫头猫 if (!platform || !id) { 194cd669353S猫头猫 platform = '本地'; 195*5353b473S猫头猫 const fileInfo = await getInfoAsync(musicPath, { 196*5353b473S猫头猫 md5: true, 197*5353b473S猫头猫 }); 198*5353b473S猫头猫 id = fileInfo.exists ? fileInfo.md5 : nanoid(); 199cd669353S猫头猫 } 200cd669353S猫头猫 return { 201cd669353S猫头猫 id, 202cd669353S猫头猫 platform, 203*5353b473S猫头猫 title: title ?? meta?.title ?? getFileName(musicPath), 204cd669353S猫头猫 artist: artist ?? meta?.artist ?? '未知歌手', 205*5353b473S猫头猫 duration: parseInt(meta?.duration ?? '0', 10) / 1000, 206cd669353S猫头猫 album: meta?.album ?? '未知专辑', 207cd669353S猫头猫 artwork: '', 208cd669353S猫头猫 [internalSerializeKey]: { 209*5353b473S猫头猫 localPath: musicPath, 210cd669353S猫头猫 }, 211*5353b473S猫头猫 } as IMusic.IMusicItem; 212cd669353S猫头猫 }), 213cd669353S猫头猫 ); 214806b2764S猫头猫 if (token !== importToken) { 215806b2764S猫头猫 throw new Error('Import Broken'); 216806b2764S猫头猫 } 217cd669353S猫头猫 addMusic(musicItems); 218cd669353S猫头猫} 219cd669353S猫头猫 2200e4173cdS猫头猫/** 是否为本地音乐 */ 2210e4173cdS猫头猫function isLocalMusic( 2220e4173cdS猫头猫 musicItem: ICommon.IMediaBase | null, 2230e4173cdS猫头猫): IMusic.IMusicItem | undefined { 2240e4173cdS猫头猫 return musicItem 2250e4173cdS猫头猫 ? localSheet.find(_ => isSameMediaItem(_, musicItem)) 2260e4173cdS猫头猫 : undefined; 2270e4173cdS猫头猫} 2280e4173cdS猫头猫 2290e4173cdS猫头猫/** 状态-是否为本地音乐 */ 2300e4173cdS猫头猫function useIsLocal(musicItem: IMusic.IMusicItem | null) { 2310e4173cdS猫头猫 const localMusicState = localSheetStateMapper.useMappedState(); 2320e4173cdS猫头猫 const [isLocal, setIsLocal] = useState<boolean>(!!isLocalMusic(musicItem)); 2330e4173cdS猫头猫 useEffect(() => { 2340e4173cdS猫头猫 if (!musicItem) { 2350e4173cdS猫头猫 setIsLocal(false); 2360e4173cdS猫头猫 } else { 2370e4173cdS猫头猫 setIsLocal(!!isLocalMusic(musicItem)); 2380e4173cdS猫头猫 } 2390e4173cdS猫头猫 }, [localMusicState, musicItem]); 2400e4173cdS猫头猫 return isLocal; 2410e4173cdS猫头猫} 2420e4173cdS猫头猫 2430224b881S猫头猫function getMusicList() { 2440224b881S猫头猫 return localSheet; 2450224b881S猫头猫} 2460224b881S猫头猫 24754bb1cc8S猫头猫async function updateMusicList(newSheet: IMusic.IMusicItem[]) { 24854bb1cc8S猫头猫 const _localSheet = [...newSheet]; 24954bb1cc8S猫头猫 try { 25054bb1cc8S猫头猫 await setStorage(StorageKeys.LocalMusicSheet, _localSheet); 25154bb1cc8S猫头猫 localSheet = _localSheet; 25254bb1cc8S猫头猫 localSheetStateMapper.notify(); 25354bb1cc8S猫头猫 } catch {} 25454bb1cc8S猫头猫} 25554bb1cc8S猫头猫 2560e4173cdS猫头猫const LocalMusicSheet = { 2570e4173cdS猫头猫 setup, 2580e4173cdS猫头猫 addMusic, 2590e4173cdS猫头猫 removeMusic, 260dc160d50S猫头猫 addMusicDraft, 261dc160d50S猫头猫 saveLocalSheet, 262cd669353S猫头猫 importLocal, 263806b2764S猫头猫 cancelImportLocal, 2640e4173cdS猫头猫 isLocalMusic, 2650e4173cdS猫头猫 useIsLocal, 2660224b881S猫头猫 getMusicList, 2670e4173cdS猫头猫 useMusicList: localSheetStateMapper.useMappedState, 26854bb1cc8S猫头猫 updateMusicList, 2690e4173cdS猫头猫}; 2700e4173cdS猫头猫 2710e4173cdS猫头猫export default LocalMusicSheet; 272