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