10e4173cdS猫头猫import {internalSerializeKey, StorageKeys} from '@/constants/commonConst'; 2806b2764S猫头猫import mp3Util from '@/native/mp3Util'; 388772aabS猫头猫import { 488772aabS猫头猫 getInternalData, 588772aabS猫头猫 InternalDataType, 688772aabS猫头猫 isSameMediaItem, 788772aabS猫头猫} from '@/utils/mediaItem'; 8afb5c234S猫头猫import StateMapper from '@/utils/stateMapper'; 9afb5c234S猫头猫import {getStorage, setStorage} from '@/utils/storage'; 10806b2764S猫头猫import {nanoid} from 'nanoid'; 110e4173cdS猫头猫import {useEffect, useState} from 'react'; 12cd669353S猫头猫import {FileStat, FileSystem} from 'react-native-file-access'; 13afb5c234S猫头猫 14afb5c234S猫头猫let localSheet: IMusic.IMusicItem[] = []; 15afb5c234S猫头猫const localSheetStateMapper = new StateMapper(() => localSheet); 16afb5c234S猫头猫 17afb5c234S猫头猫export async function setup() { 1888772aabS猫头猫 const sheet = await getStorage(StorageKeys.LocalMusicSheet); 1988772aabS猫头猫 if (sheet) { 2088772aabS猫头猫 let validSheet = []; 2188772aabS猫头猫 for (let musicItem of sheet) { 2288772aabS猫头猫 const localPath = getInternalData<string>( 2388772aabS猫头猫 musicItem, 2488772aabS猫头猫 InternalDataType.LOCALPATH, 2588772aabS猫头猫 ); 2688772aabS猫头猫 if (localPath && (await FileSystem.exists(localPath))) { 2788772aabS猫头猫 validSheet.push(musicItem); 2888772aabS猫头猫 } 2988772aabS猫头猫 } 3088772aabS猫头猫 if (validSheet.length !== sheet.length) { 3188772aabS猫头猫 await setStorage(StorageKeys.LocalMusicSheet, validSheet); 3288772aabS猫头猫 } 3388772aabS猫头猫 localSheet = validSheet; 34afb5c234S猫头猫 } else { 35afb5c234S猫头猫 await setStorage(StorageKeys.LocalMusicSheet, []); 36afb5c234S猫头猫 } 37afb5c234S猫头猫 localSheetStateMapper.notify(); 38afb5c234S猫头猫} 39afb5c234S猫头猫 40afb5c234S猫头猫export async function addMusic( 41afb5c234S猫头猫 musicItem: IMusic.IMusicItem | IMusic.IMusicItem[], 42afb5c234S猫头猫) { 43afb5c234S猫头猫 if (!Array.isArray(musicItem)) { 44afb5c234S猫头猫 musicItem = [musicItem]; 45afb5c234S猫头猫 } 46afb5c234S猫头猫 let newSheet = [...localSheet]; 47afb5c234S猫头猫 musicItem.forEach(mi => { 48afb5c234S猫头猫 if (localSheet.findIndex(_ => isSameMediaItem(mi, _)) === -1) { 49afb5c234S猫头猫 newSheet.push(mi); 50afb5c234S猫头猫 } 51afb5c234S猫头猫 }); 52afb5c234S猫头猫 await setStorage(StorageKeys.LocalMusicSheet, newSheet); 53afb5c234S猫头猫 localSheet = newSheet; 54afb5c234S猫头猫 localSheetStateMapper.notify(); 55afb5c234S猫头猫} 56afb5c234S猫头猫 57*dc160d50S猫头猫function addMusicDraft(musicItem: IMusic.IMusicItem | IMusic.IMusicItem[]) { 58*dc160d50S猫头猫 if (!Array.isArray(musicItem)) { 59*dc160d50S猫头猫 musicItem = [musicItem]; 60*dc160d50S猫头猫 } 61*dc160d50S猫头猫 let newSheet = [...localSheet]; 62*dc160d50S猫头猫 musicItem.forEach(mi => { 63*dc160d50S猫头猫 if (localSheet.findIndex(_ => isSameMediaItem(mi, _)) === -1) { 64*dc160d50S猫头猫 newSheet.push(mi); 65*dc160d50S猫头猫 } 66*dc160d50S猫头猫 }); 67*dc160d50S猫头猫 localSheet = newSheet; 68*dc160d50S猫头猫 localSheetStateMapper.notify(); 69*dc160d50S猫头猫} 70*dc160d50S猫头猫 71*dc160d50S猫头猫async function saveLocalSheet() { 72*dc160d50S猫头猫 await setStorage(StorageKeys.LocalMusicSheet, localSheet); 73*dc160d50S猫头猫} 74*dc160d50S猫头猫 75afb5c234S猫头猫export async function removeMusic( 76afb5c234S猫头猫 musicItem: IMusic.IMusicItem, 77afb5c234S猫头猫 deleteOriginalFile = false, 78afb5c234S猫头猫) { 79afb5c234S猫头猫 const idx = localSheet.findIndex(_ => isSameMediaItem(_, musicItem)); 80afb5c234S猫头猫 let newSheet = [...localSheet]; 81afb5c234S猫头猫 if (idx !== -1) { 82afb5c234S猫头猫 newSheet.splice(idx, 1); 830e4173cdS猫头猫 if (deleteOriginalFile && musicItem[internalSerializeKey]?.localPath) { 840e4173cdS猫头猫 await FileSystem.unlink(musicItem[internalSerializeKey].localPath); 85afb5c234S猫头猫 } 86afb5c234S猫头猫 } 87afb5c234S猫头猫 localSheet = newSheet; 88afb5c234S猫头猫 localSheetStateMapper.notify(); 89afb5c234S猫头猫} 90afb5c234S猫头猫 91afb5c234S猫头猫function parseFilename(fn: string): Partial<IMusic.IMusicItem> | null { 92afb5c234S猫头猫 const data = fn.slice(0, fn.lastIndexOf('.')).split('@'); 93afb5c234S猫头猫 const [platform, id, title, artist] = data; 94afb5c234S猫头猫 if (!platform || !id) { 95afb5c234S猫头猫 return null; 96afb5c234S猫头猫 } 97afb5c234S猫头猫 return { 98afb5c234S猫头猫 id, 99afb5c234S猫头猫 platform, 100afb5c234S猫头猫 title, 101afb5c234S猫头猫 artist, 102afb5c234S猫头猫 }; 103afb5c234S猫头猫} 104afb5c234S猫头猫 105cd669353S猫头猫function localMediaFilter(_: FileStat) { 106cd669353S猫头猫 return ( 107cd669353S猫头猫 _.filename.endsWith('.mp3') || 108cd669353S猫头猫 _.filename.endsWith('.flac') || 109d17d2b77S猫头猫 _.filename.endsWith('.wma') || 110d17d2b77S猫头猫 _.filename.endsWith('.wav') || 111d17d2b77S猫头猫 _.filename.endsWith('.m4a') || 11274d0cf81S猫头猫 _.filename.endsWith('.ogg') || 11395f955b7S猫头猫 _.filename.endsWith('.acc') || 1148fd127b4S猫头猫 _.filename.endsWith('.aac') || 115*dc160d50S猫头猫 _.filename.endsWith('.ape') || 116*dc160d50S猫头猫 _.filename.endsWith('.m4s') 117cd669353S猫头猫 ); 118cd669353S猫头猫} 119cd669353S猫头猫 120806b2764S猫头猫let importToken: string | null = null; 121806b2764S猫头猫// 获取本地的文件列表 122806b2764S猫头猫async function getMusicStats(folderPaths: string[]) { 123806b2764S猫头猫 const _importToken = nanoid(); 124806b2764S猫头猫 importToken = _importToken; 125806b2764S猫头猫 const musicList: FileStat[] = []; 126806b2764S猫头猫 let peek: string | undefined; 127806b2764S猫头猫 let dirFiles: FileStat[] = []; 128806b2764S猫头猫 while (folderPaths.length !== 0) { 129806b2764S猫头猫 if (importToken !== _importToken) { 130806b2764S猫头猫 throw new Error('Import Broken'); 131806b2764S猫头猫 } 132806b2764S猫头猫 peek = folderPaths.shift() as string; 133cd669353S猫头猫 try { 134806b2764S猫头猫 dirFiles = await FileSystem.statDir(peek); 135806b2764S猫头猫 } catch { 136806b2764S猫头猫 dirFiles = []; 137806b2764S猫头猫 } 138cd669353S猫头猫 139806b2764S猫头猫 dirFiles.forEach(item => { 14022c09412S猫头猫 if (item.type === 'directory' && !folderPaths.includes(item.path)) { 141806b2764S猫头猫 folderPaths.push(item.path); 142cd669353S猫头猫 } else if (localMediaFilter(item)) { 143806b2764S猫头猫 musicList.push(item); 144cd669353S猫头猫 } 145806b2764S猫头猫 }); 146cd669353S猫头猫 } 147806b2764S猫头猫 return {musicList, token: _importToken}; 148806b2764S猫头猫} 149806b2764S猫头猫 150806b2764S猫头猫function cancelImportLocal() { 151806b2764S猫头猫 importToken = null; 152cd669353S猫头猫} 153cd669353S猫头猫 154cd669353S猫头猫// 导入本地音乐 155806b2764S猫头猫async function importLocal(_folderPaths: string[]) { 156806b2764S猫头猫 const folderPaths = [..._folderPaths]; 157806b2764S猫头猫 const {musicList, token} = await getMusicStats(folderPaths); 158806b2764S猫头猫 if (token !== importToken) { 159806b2764S猫头猫 throw new Error('Import Broken'); 160806b2764S猫头猫 } 161cd669353S猫头猫 const metas = await mp3Util.getMediaMeta(musicList.map(_ => _.path)); 162806b2764S猫头猫 if (token !== importToken) { 163806b2764S猫头猫 throw new Error('Import Broken'); 164806b2764S猫头猫 } 165cd669353S猫头猫 const musicItems = await Promise.all( 166cd669353S猫头猫 musicList.map(async (musicStat, index) => { 167cd669353S猫头猫 let {platform, id, title, artist} = 168cd669353S猫头猫 parseFilename(musicStat.filename) ?? {}; 169cd669353S猫头猫 const meta = metas[index]; 170cd669353S猫头猫 if (!platform || !id) { 171cd669353S猫头猫 platform = '本地'; 172cd669353S猫头猫 id = await FileSystem.hash(musicStat.path, 'MD5'); 173cd669353S猫头猫 } 174cd669353S猫头猫 return { 175cd669353S猫头猫 id, 176cd669353S猫头猫 platform, 177cd669353S猫头猫 title: title ?? meta?.title ?? musicStat.filename, 178cd669353S猫头猫 artist: artist ?? meta?.artist ?? '未知歌手', 179cd669353S猫头猫 duration: parseInt(meta?.duration ?? '0') / 1000, 180cd669353S猫头猫 album: meta?.album ?? '未知专辑', 181cd669353S猫头猫 artwork: '', 182cd669353S猫头猫 [internalSerializeKey]: { 183cd669353S猫头猫 localPath: musicStat.path, 184cd669353S猫头猫 }, 185cd669353S猫头猫 }; 186cd669353S猫头猫 }), 187cd669353S猫头猫 ); 188806b2764S猫头猫 if (token !== importToken) { 189806b2764S猫头猫 throw new Error('Import Broken'); 190806b2764S猫头猫 } 191cd669353S猫头猫 addMusic(musicItems); 192cd669353S猫头猫} 193cd669353S猫头猫 1940e4173cdS猫头猫/** 是否为本地音乐 */ 1950e4173cdS猫头猫function isLocalMusic( 1960e4173cdS猫头猫 musicItem: ICommon.IMediaBase | null, 1970e4173cdS猫头猫): IMusic.IMusicItem | undefined { 1980e4173cdS猫头猫 return musicItem 1990e4173cdS猫头猫 ? localSheet.find(_ => isSameMediaItem(_, musicItem)) 2000e4173cdS猫头猫 : undefined; 2010e4173cdS猫头猫} 2020e4173cdS猫头猫 2030e4173cdS猫头猫/** 状态-是否为本地音乐 */ 2040e4173cdS猫头猫function useIsLocal(musicItem: IMusic.IMusicItem | null) { 2050e4173cdS猫头猫 const localMusicState = localSheetStateMapper.useMappedState(); 2060e4173cdS猫头猫 const [isLocal, setIsLocal] = useState<boolean>(!!isLocalMusic(musicItem)); 2070e4173cdS猫头猫 useEffect(() => { 2080e4173cdS猫头猫 if (!musicItem) { 2090e4173cdS猫头猫 setIsLocal(false); 2100e4173cdS猫头猫 } else { 2110e4173cdS猫头猫 setIsLocal(!!isLocalMusic(musicItem)); 2120e4173cdS猫头猫 } 2130e4173cdS猫头猫 }, [localMusicState, musicItem]); 2140e4173cdS猫头猫 return isLocal; 2150e4173cdS猫头猫} 2160e4173cdS猫头猫 2170224b881S猫头猫function getMusicList() { 2180224b881S猫头猫 return localSheet; 2190224b881S猫头猫} 2200224b881S猫头猫 22154bb1cc8S猫头猫async function updateMusicList(newSheet: IMusic.IMusicItem[]) { 22254bb1cc8S猫头猫 const _localSheet = [...newSheet]; 22354bb1cc8S猫头猫 try { 22454bb1cc8S猫头猫 await setStorage(StorageKeys.LocalMusicSheet, _localSheet); 22554bb1cc8S猫头猫 localSheet = _localSheet; 22654bb1cc8S猫头猫 localSheetStateMapper.notify(); 22754bb1cc8S猫头猫 } catch {} 22854bb1cc8S猫头猫} 22954bb1cc8S猫头猫 2300e4173cdS猫头猫const LocalMusicSheet = { 2310e4173cdS猫头猫 setup, 2320e4173cdS猫头猫 addMusic, 2330e4173cdS猫头猫 removeMusic, 234*dc160d50S猫头猫 addMusicDraft, 235*dc160d50S猫头猫 saveLocalSheet, 236cd669353S猫头猫 importLocal, 237806b2764S猫头猫 cancelImportLocal, 2380e4173cdS猫头猫 isLocalMusic, 2390e4173cdS猫头猫 useIsLocal, 2400224b881S猫头猫 getMusicList, 2410e4173cdS猫头猫 useMusicList: localSheetStateMapper.useMappedState, 24254bb1cc8S猫头猫 updateMusicList, 2430e4173cdS猫头猫}; 2440e4173cdS猫头猫 2450e4173cdS猫头猫export default LocalMusicSheet; 246