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'; 16cd669353S猫头猫import {FileStat, FileSystem} from 'react-native-file-access'; 17afb5c234S猫头猫 18afb5c234S猫头猫let localSheet: IMusic.IMusicItem[] = []; 19afb5c234S猫头猫const localSheetStateMapper = new StateMapper(() => localSheet); 20afb5c234S猫头猫 21afb5c234S猫头猫export async function setup() { 2288772aabS猫头猫 const sheet = await getStorage(StorageKeys.LocalMusicSheet); 2388772aabS猫头猫 if (sheet) { 2488772aabS猫头猫 let validSheet = []; 2588772aabS猫头猫 for (let musicItem of sheet) { 2688772aabS猫头猫 const localPath = getInternalData<string>( 2788772aabS猫头猫 musicItem, 2888772aabS猫头猫 InternalDataType.LOCALPATH, 2988772aabS猫头猫 ); 3088772aabS猫头猫 if (localPath && (await FileSystem.exists(localPath))) { 3188772aabS猫头猫 validSheet.push(musicItem); 3288772aabS猫头猫 } 3388772aabS猫头猫 } 3488772aabS猫头猫 if (validSheet.length !== sheet.length) { 3588772aabS猫头猫 await setStorage(StorageKeys.LocalMusicSheet, validSheet); 3688772aabS猫头猫 } 3788772aabS猫头猫 localSheet = validSheet; 38afb5c234S猫头猫 } else { 39afb5c234S猫头猫 await setStorage(StorageKeys.LocalMusicSheet, []); 40afb5c234S猫头猫 } 41afb5c234S猫头猫 localSheetStateMapper.notify(); 42afb5c234S猫头猫} 43afb5c234S猫头猫 44afb5c234S猫头猫export async function addMusic( 45afb5c234S猫头猫 musicItem: IMusic.IMusicItem | IMusic.IMusicItem[], 46afb5c234S猫头猫) { 47afb5c234S猫头猫 if (!Array.isArray(musicItem)) { 48afb5c234S猫头猫 musicItem = [musicItem]; 49afb5c234S猫头猫 } 50afb5c234S猫头猫 let newSheet = [...localSheet]; 51afb5c234S猫头猫 musicItem.forEach(mi => { 52afb5c234S猫头猫 if (localSheet.findIndex(_ => isSameMediaItem(mi, _)) === -1) { 53afb5c234S猫头猫 newSheet.push(mi); 54afb5c234S猫头猫 } 55afb5c234S猫头猫 }); 56afb5c234S猫头猫 await setStorage(StorageKeys.LocalMusicSheet, newSheet); 57afb5c234S猫头猫 localSheet = newSheet; 58afb5c234S猫头猫 localSheetStateMapper.notify(); 59afb5c234S猫头猫} 60afb5c234S猫头猫 61dc160d50S猫头猫function addMusicDraft(musicItem: IMusic.IMusicItem | IMusic.IMusicItem[]) { 62dc160d50S猫头猫 if (!Array.isArray(musicItem)) { 63dc160d50S猫头猫 musicItem = [musicItem]; 64dc160d50S猫头猫 } 65dc160d50S猫头猫 let newSheet = [...localSheet]; 66dc160d50S猫头猫 musicItem.forEach(mi => { 67dc160d50S猫头猫 if (localSheet.findIndex(_ => isSameMediaItem(mi, _)) === -1) { 68dc160d50S猫头猫 newSheet.push(mi); 69dc160d50S猫头猫 } 70dc160d50S猫头猫 }); 71dc160d50S猫头猫 localSheet = newSheet; 72dc160d50S猫头猫 localSheetStateMapper.notify(); 73dc160d50S猫头猫} 74dc160d50S猫头猫 75dc160d50S猫头猫async function saveLocalSheet() { 76dc160d50S猫头猫 await setStorage(StorageKeys.LocalMusicSheet, localSheet); 77dc160d50S猫头猫} 78dc160d50S猫头猫 79afb5c234S猫头猫export async function removeMusic( 80afb5c234S猫头猫 musicItem: IMusic.IMusicItem, 81afb5c234S猫头猫 deleteOriginalFile = false, 82afb5c234S猫头猫) { 83afb5c234S猫头猫 const idx = localSheet.findIndex(_ => isSameMediaItem(_, musicItem)); 84afb5c234S猫头猫 let newSheet = [...localSheet]; 85afb5c234S猫头猫 if (idx !== -1) { 86e65882dbS猫头猫 const localMusicItem = localSheet[idx]; 87afb5c234S猫头猫 newSheet.splice(idx, 1); 88e65882dbS猫头猫 const localPath = 89e65882dbS猫头猫 musicItem[internalSerializeKey]?.localPath ?? 90e65882dbS猫头猫 localMusicItem[internalSerializeKey]?.localPath; 91e65882dbS猫头猫 if (deleteOriginalFile && localPath) { 92e65882dbS猫头猫 await FileSystem.unlink(localPath); 93afb5c234S猫头猫 } 94afb5c234S猫头猫 } 95afb5c234S猫头猫 localSheet = newSheet; 96afb5c234S猫头猫 localSheetStateMapper.notify(); 97afb5c234S猫头猫} 98afb5c234S猫头猫 99afb5c234S猫头猫function parseFilename(fn: string): Partial<IMusic.IMusicItem> | null { 100afb5c234S猫头猫 const data = fn.slice(0, fn.lastIndexOf('.')).split('@'); 101afb5c234S猫头猫 const [platform, id, title, artist] = data; 102afb5c234S猫头猫 if (!platform || !id) { 103afb5c234S猫头猫 return null; 104afb5c234S猫头猫 } 105afb5c234S猫头猫 return { 106afb5c234S猫头猫 id, 107*a8557e3dS猫头猫 platform: decodeURIComponent(platform), 108*a8557e3dS猫头猫 title: decodeURIComponent(title ?? ''), 109*a8557e3dS猫头猫 artist: decodeURIComponent(artist ?? ''), 110afb5c234S猫头猫 }; 111afb5c234S猫头猫} 112afb5c234S猫头猫 113cd669353S猫头猫function localMediaFilter(_: FileStat) { 1148fc75cb2S猫头猫 return supportLocalMediaType.some(ext => _.filename.endsWith(ext)); 115cd669353S猫头猫} 116cd669353S猫头猫 117806b2764S猫头猫let importToken: string | null = null; 118806b2764S猫头猫// 获取本地的文件列表 119806b2764S猫头猫async function getMusicStats(folderPaths: string[]) { 120806b2764S猫头猫 const _importToken = nanoid(); 121806b2764S猫头猫 importToken = _importToken; 122806b2764S猫头猫 const musicList: FileStat[] = []; 123806b2764S猫头猫 let peek: string | undefined; 124806b2764S猫头猫 let dirFiles: FileStat[] = []; 125806b2764S猫头猫 while (folderPaths.length !== 0) { 126806b2764S猫头猫 if (importToken !== _importToken) { 127806b2764S猫头猫 throw new Error('Import Broken'); 128806b2764S猫头猫 } 129806b2764S猫头猫 peek = folderPaths.shift() as string; 130cd669353S猫头猫 try { 131806b2764S猫头猫 dirFiles = await FileSystem.statDir(peek); 132806b2764S猫头猫 } catch { 133806b2764S猫头猫 dirFiles = []; 134806b2764S猫头猫 } 135cd669353S猫头猫 136806b2764S猫头猫 dirFiles.forEach(item => { 13722c09412S猫头猫 if (item.type === 'directory' && !folderPaths.includes(item.path)) { 138806b2764S猫头猫 folderPaths.push(item.path); 139cd669353S猫头猫 } else if (localMediaFilter(item)) { 140806b2764S猫头猫 musicList.push(item); 141cd669353S猫头猫 } 142806b2764S猫头猫 }); 143cd669353S猫头猫 } 144806b2764S猫头猫 return {musicList, token: _importToken}; 145806b2764S猫头猫} 146806b2764S猫头猫 147806b2764S猫头猫function cancelImportLocal() { 148806b2764S猫头猫 importToken = null; 149cd669353S猫头猫} 150cd669353S猫头猫 151cd669353S猫头猫// 导入本地音乐 152bb9b2f7bS猫头猫const groupNum = 50; 153806b2764S猫头猫async function importLocal(_folderPaths: string[]) { 154806b2764S猫头猫 const folderPaths = [..._folderPaths]; 155806b2764S猫头猫 const {musicList, token} = await getMusicStats(folderPaths); 156806b2764S猫头猫 if (token !== importToken) { 157806b2764S猫头猫 throw new Error('Import Broken'); 158806b2764S猫头猫 } 1598423b2d4S猫头猫 // 分组请求,不然序列化可能出问题 1608423b2d4S猫头猫 let metas: IBasicMeta[] = []; 1618423b2d4S猫头猫 const groups = Math.ceil(musicList.length / groupNum); 1628423b2d4S猫头猫 for (let i = 0; i < groups; ++i) { 1638423b2d4S猫头猫 metas = metas.concat( 1648423b2d4S猫头猫 await mp3Util.getMediaMeta( 1658423b2d4S猫头猫 musicList 1668423b2d4S猫头猫 .slice(i * groupNum, (i + 1) * groupNum) 1678423b2d4S猫头猫 .map(_ => _.path), 1688423b2d4S猫头猫 ), 1698423b2d4S猫头猫 ); 1708423b2d4S猫头猫 } 171806b2764S猫头猫 if (token !== importToken) { 172806b2764S猫头猫 throw new Error('Import Broken'); 173806b2764S猫头猫 } 174cd669353S猫头猫 const musicItems = await Promise.all( 175cd669353S猫头猫 musicList.map(async (musicStat, index) => { 176cd669353S猫头猫 let {platform, id, title, artist} = 177cd669353S猫头猫 parseFilename(musicStat.filename) ?? {}; 178cd669353S猫头猫 const meta = metas[index]; 179cd669353S猫头猫 if (!platform || !id) { 180cd669353S猫头猫 platform = '本地'; 181cd669353S猫头猫 id = await FileSystem.hash(musicStat.path, 'MD5'); 182cd669353S猫头猫 } 183cd669353S猫头猫 return { 184cd669353S猫头猫 id, 185cd669353S猫头猫 platform, 186cd669353S猫头猫 title: title ?? meta?.title ?? musicStat.filename, 187cd669353S猫头猫 artist: artist ?? meta?.artist ?? '未知歌手', 188cd669353S猫头猫 duration: parseInt(meta?.duration ?? '0') / 1000, 189cd669353S猫头猫 album: meta?.album ?? '未知专辑', 190cd669353S猫头猫 artwork: '', 191cd669353S猫头猫 [internalSerializeKey]: { 192cd669353S猫头猫 localPath: musicStat.path, 193cd669353S猫头猫 }, 194cd669353S猫头猫 }; 195cd669353S猫头猫 }), 196cd669353S猫头猫 ); 197806b2764S猫头猫 if (token !== importToken) { 198806b2764S猫头猫 throw new Error('Import Broken'); 199806b2764S猫头猫 } 200cd669353S猫头猫 addMusic(musicItems); 201cd669353S猫头猫} 202cd669353S猫头猫 2030e4173cdS猫头猫/** 是否为本地音乐 */ 2040e4173cdS猫头猫function isLocalMusic( 2050e4173cdS猫头猫 musicItem: ICommon.IMediaBase | null, 2060e4173cdS猫头猫): IMusic.IMusicItem | undefined { 2070e4173cdS猫头猫 return musicItem 2080e4173cdS猫头猫 ? localSheet.find(_ => isSameMediaItem(_, musicItem)) 2090e4173cdS猫头猫 : undefined; 2100e4173cdS猫头猫} 2110e4173cdS猫头猫 2120e4173cdS猫头猫/** 状态-是否为本地音乐 */ 2130e4173cdS猫头猫function useIsLocal(musicItem: IMusic.IMusicItem | null) { 2140e4173cdS猫头猫 const localMusicState = localSheetStateMapper.useMappedState(); 2150e4173cdS猫头猫 const [isLocal, setIsLocal] = useState<boolean>(!!isLocalMusic(musicItem)); 2160e4173cdS猫头猫 useEffect(() => { 2170e4173cdS猫头猫 if (!musicItem) { 2180e4173cdS猫头猫 setIsLocal(false); 2190e4173cdS猫头猫 } else { 2200e4173cdS猫头猫 setIsLocal(!!isLocalMusic(musicItem)); 2210e4173cdS猫头猫 } 2220e4173cdS猫头猫 }, [localMusicState, musicItem]); 2230e4173cdS猫头猫 return isLocal; 2240e4173cdS猫头猫} 2250e4173cdS猫头猫 2260224b881S猫头猫function getMusicList() { 2270224b881S猫头猫 return localSheet; 2280224b881S猫头猫} 2290224b881S猫头猫 23054bb1cc8S猫头猫async function updateMusicList(newSheet: IMusic.IMusicItem[]) { 23154bb1cc8S猫头猫 const _localSheet = [...newSheet]; 23254bb1cc8S猫头猫 try { 23354bb1cc8S猫头猫 await setStorage(StorageKeys.LocalMusicSheet, _localSheet); 23454bb1cc8S猫头猫 localSheet = _localSheet; 23554bb1cc8S猫头猫 localSheetStateMapper.notify(); 23654bb1cc8S猫头猫 } catch {} 23754bb1cc8S猫头猫} 23854bb1cc8S猫头猫 2390e4173cdS猫头猫const LocalMusicSheet = { 2400e4173cdS猫头猫 setup, 2410e4173cdS猫头猫 addMusic, 2420e4173cdS猫头猫 removeMusic, 243dc160d50S猫头猫 addMusicDraft, 244dc160d50S猫头猫 saveLocalSheet, 245cd669353S猫头猫 importLocal, 246806b2764S猫头猫 cancelImportLocal, 2470e4173cdS猫头猫 isLocalMusic, 2480e4173cdS猫头猫 useIsLocal, 2490224b881S猫头猫 getMusicList, 2500e4173cdS猫头猫 useMusicList: localSheetStateMapper.useMappedState, 25154bb1cc8S猫头猫 updateMusicList, 2520e4173cdS猫头猫}; 2530e4173cdS猫头猫 2540e4173cdS猫头猫export default LocalMusicSheet; 255