10e4173cdS猫头猫import {internalSerializeKey, StorageKeys} from '@/constants/commonConst'; 2afb5c234S猫头猫import mp3Util, {IBasicMeta} from '@/native/mp3Util'; 3*cd669353S猫头猫import {errorLog} from '@/utils/log'; 488772aabS猫头猫import { 588772aabS猫头猫 getInternalData, 688772aabS猫头猫 InternalDataType, 788772aabS猫头猫 isSameMediaItem, 888772aabS猫头猫} from '@/utils/mediaItem'; 9afb5c234S猫头猫import StateMapper from '@/utils/stateMapper'; 10afb5c234S猫头猫import {getStorage, setStorage} from '@/utils/storage'; 110e4173cdS猫头猫import {useEffect, useState} from 'react'; 12*cd669353S猫头猫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猫头猫 57afb5c234S猫头猫export async function removeMusic( 58afb5c234S猫头猫 musicItem: IMusic.IMusicItem, 59afb5c234S猫头猫 deleteOriginalFile = false, 60afb5c234S猫头猫) { 61afb5c234S猫头猫 const idx = localSheet.findIndex(_ => isSameMediaItem(_, musicItem)); 62afb5c234S猫头猫 let newSheet = [...localSheet]; 63afb5c234S猫头猫 if (idx !== -1) { 64afb5c234S猫头猫 newSheet.splice(idx, 1); 650e4173cdS猫头猫 if (deleteOriginalFile && musicItem[internalSerializeKey]?.localPath) { 660e4173cdS猫头猫 await FileSystem.unlink(musicItem[internalSerializeKey].localPath); 67afb5c234S猫头猫 } 68afb5c234S猫头猫 } 69afb5c234S猫头猫 localSheet = newSheet; 70afb5c234S猫头猫 localSheetStateMapper.notify(); 71afb5c234S猫头猫} 72afb5c234S猫头猫 73afb5c234S猫头猫function parseFilename(fn: string): Partial<IMusic.IMusicItem> | null { 74afb5c234S猫头猫 const data = fn.slice(0, fn.lastIndexOf('.')).split('@'); 75afb5c234S猫头猫 const [platform, id, title, artist] = data; 76afb5c234S猫头猫 if (!platform || !id) { 77afb5c234S猫头猫 return null; 78afb5c234S猫头猫 } 79afb5c234S猫头猫 return { 80afb5c234S猫头猫 id, 81afb5c234S猫头猫 platform, 82afb5c234S猫头猫 title, 83afb5c234S猫头猫 artist, 84afb5c234S猫头猫 }; 85afb5c234S猫头猫} 86afb5c234S猫头猫 870e4173cdS猫头猫/** 从文件夹导入 */ 880e4173cdS猫头猫async function importFolder(folderPath: string) { 89afb5c234S猫头猫 const dirFiles = await FileSystem.statDir(folderPath); 90afb5c234S猫头猫 const musicFiles = dirFiles.filter( 91ae543cd2S猫头猫 // todo: flac播放没有声音 92ae543cd2S猫头猫 _ => 93ae543cd2S猫头猫 _.type === 'file' && 94ae543cd2S猫头猫 (_.filename.endsWith('.mp3') || _.filename.endsWith('.flac')), 95afb5c234S猫头猫 ); 96afb5c234S猫头猫 97afb5c234S猫头猫 const musicItems: IMusic.IMusicItem[] = await Promise.all( 98afb5c234S猫头猫 musicFiles.map(async mf => { 99afb5c234S猫头猫 let {platform, id, title, artist} = 100afb5c234S猫头猫 parseFilename(mf.filename) ?? {}; 101afb5c234S猫头猫 102afb5c234S猫头猫 let meta: IBasicMeta | null; 103afb5c234S猫头猫 try { 104afb5c234S猫头猫 meta = await mp3Util.getBasicMeta(mf.path); 105afb5c234S猫头猫 } catch { 106afb5c234S猫头猫 meta = null; 107afb5c234S猫头猫 } 108afb5c234S猫头猫 if (!platform || !id) { 109afb5c234S猫头猫 platform = '本地'; 110afb5c234S猫头猫 id = await FileSystem.hash(mf.path, 'MD5'); 111afb5c234S猫头猫 } 112afb5c234S猫头猫 return { 113afb5c234S猫头猫 id, 114afb5c234S猫头猫 platform, 1152a0db24aS猫头猫 title: title ?? meta?.title ?? mf.filename, 116afb5c234S猫头猫 artist: artist ?? meta?.artist ?? '未知歌手', 117afb5c234S猫头猫 duration: parseInt(meta?.duration ?? '0') / 1000, 1182a0db24aS猫头猫 album: meta?.album ?? '未知专辑', 119afb5c234S猫头猫 artwork: '', 1200e4173cdS猫头猫 [internalSerializeKey]: { 1210e4173cdS猫头猫 localPath: mf.path, 122afb5c234S猫头猫 }, 123afb5c234S猫头猫 }; 124afb5c234S猫头猫 }), 125afb5c234S猫头猫 ); 126afb5c234S猫头猫 addMusic(musicItems); 127afb5c234S猫头猫} 1280e4173cdS猫头猫 129*cd669353S猫头猫function localMediaFilter(_: FileStat) { 130*cd669353S猫头猫 return ( 131*cd669353S猫头猫 _.filename.endsWith('.mp3') || 132*cd669353S猫头猫 _.filename.endsWith('.flac') || 133*cd669353S猫头猫 _.filename.endsWith('.wma') 134*cd669353S猫头猫 ); 135*cd669353S猫头猫} 136*cd669353S猫头猫 137*cd669353S猫头猫// TODO: 需要支持中断&取消 138*cd669353S猫头猫async function getMusicFiles(folderPath: string) { 139*cd669353S猫头猫 try { 140*cd669353S猫头猫 const dirFiles = await FileSystem.statDir(folderPath); 141*cd669353S猫头猫 const musicFiles: FileStat[] = []; 142*cd669353S猫头猫 143*cd669353S猫头猫 await Promise.all( 144*cd669353S猫头猫 dirFiles.map(async item => { 145*cd669353S猫头猫 if (item.type === 'directory') { 146*cd669353S猫头猫 const res = await getMusicFiles(item.path); 147*cd669353S猫头猫 musicFiles.push(...res); 148*cd669353S猫头猫 } else if (localMediaFilter(item)) { 149*cd669353S猫头猫 musicFiles.push(item); 150*cd669353S猫头猫 } 151*cd669353S猫头猫 }), 152*cd669353S猫头猫 ); 153*cd669353S猫头猫 return musicFiles; 154*cd669353S猫头猫 } catch (e: any) { 155*cd669353S猫头猫 errorLog('获取本地文件失败', e?.message); 156*cd669353S猫头猫 return []; 157*cd669353S猫头猫 } 158*cd669353S猫头猫} 159*cd669353S猫头猫 160*cd669353S猫头猫// 导入本地音乐 161*cd669353S猫头猫async function importLocal(folderPaths: string[]) { 162*cd669353S猫头猫 const musics = await Promise.all(folderPaths.map(_ => getMusicFiles(_))); 163*cd669353S猫头猫 const musicList = musics.flat(); 164*cd669353S猫头猫 const metas = await mp3Util.getMediaMeta(musicList.map(_ => _.path)); 165*cd669353S猫头猫 console.log(metas); 166*cd669353S猫头猫 const musicItems = await Promise.all( 167*cd669353S猫头猫 musicList.map(async (musicStat, index) => { 168*cd669353S猫头猫 let {platform, id, title, artist} = 169*cd669353S猫头猫 parseFilename(musicStat.filename) ?? {}; 170*cd669353S猫头猫 const meta = metas[index]; 171*cd669353S猫头猫 if (!platform || !id) { 172*cd669353S猫头猫 platform = '本地'; 173*cd669353S猫头猫 id = await FileSystem.hash(musicStat.path, 'MD5'); 174*cd669353S猫头猫 } 175*cd669353S猫头猫 return { 176*cd669353S猫头猫 id, 177*cd669353S猫头猫 platform, 178*cd669353S猫头猫 title: title ?? meta?.title ?? musicStat.filename, 179*cd669353S猫头猫 artist: artist ?? meta?.artist ?? '未知歌手', 180*cd669353S猫头猫 duration: parseInt(meta?.duration ?? '0') / 1000, 181*cd669353S猫头猫 album: meta?.album ?? '未知专辑', 182*cd669353S猫头猫 artwork: '', 183*cd669353S猫头猫 [internalSerializeKey]: { 184*cd669353S猫头猫 localPath: musicStat.path, 185*cd669353S猫头猫 }, 186*cd669353S猫头猫 }; 187*cd669353S猫头猫 }), 188*cd669353S猫头猫 ); 189*cd669353S猫头猫 addMusic(musicItems); 190*cd669353S猫头猫} 191*cd669353S猫头猫 1920e4173cdS猫头猫/** 是否为本地音乐 */ 1930e4173cdS猫头猫function isLocalMusic( 1940e4173cdS猫头猫 musicItem: ICommon.IMediaBase | null, 1950e4173cdS猫头猫): IMusic.IMusicItem | undefined { 1960e4173cdS猫头猫 return musicItem 1970e4173cdS猫头猫 ? localSheet.find(_ => isSameMediaItem(_, musicItem)) 1980e4173cdS猫头猫 : undefined; 1990e4173cdS猫头猫} 2000e4173cdS猫头猫 2010e4173cdS猫头猫/** 状态-是否为本地音乐 */ 2020e4173cdS猫头猫function useIsLocal(musicItem: IMusic.IMusicItem | null) { 2030e4173cdS猫头猫 const localMusicState = localSheetStateMapper.useMappedState(); 2040e4173cdS猫头猫 const [isLocal, setIsLocal] = useState<boolean>(!!isLocalMusic(musicItem)); 2050e4173cdS猫头猫 useEffect(() => { 2060e4173cdS猫头猫 if (!musicItem) { 2070e4173cdS猫头猫 setIsLocal(false); 2080e4173cdS猫头猫 } else { 2090e4173cdS猫头猫 setIsLocal(!!isLocalMusic(musicItem)); 2100e4173cdS猫头猫 } 2110e4173cdS猫头猫 }, [localMusicState, musicItem]); 2120e4173cdS猫头猫 return isLocal; 2130e4173cdS猫头猫} 2140e4173cdS猫头猫 2150224b881S猫头猫function getMusicList() { 2160224b881S猫头猫 return localSheet; 2170224b881S猫头猫} 2180224b881S猫头猫 2190e4173cdS猫头猫const LocalMusicSheet = { 2200e4173cdS猫头猫 setup, 2210e4173cdS猫头猫 addMusic, 2220e4173cdS猫头猫 removeMusic, 2230e4173cdS猫头猫 importFolder, 224*cd669353S猫头猫 importLocal, 2250e4173cdS猫头猫 isLocalMusic, 2260e4173cdS猫头猫 useIsLocal, 2270224b881S猫头猫 getMusicList, 2280e4173cdS猫头猫 useMusicList: localSheetStateMapper.useMappedState, 2290e4173cdS猫头猫}; 2300e4173cdS猫头猫 2310e4173cdS猫头猫export default LocalMusicSheet; 232