@@ -223,90 +223,95 @@ export class LocalMusicService {
223223 }
224224 } ;
225225
226- // 处理文件 (新增/更新)
227- const tasks = entries . map ( ( entry ) => {
228- return this . limit ( async ( ) => {
229- const filePath = entry . path ;
230- const stats = entry . stats ;
231- if ( ! stats ) return ;
232- /** 修改时间 */
233- const mtime = stats . mtimeMs ;
234- /** 文件大小 */
235- const size = stats . size ;
236- // 小于 1MB 的文件不处理
237- if ( size < 1024 * 1024 ) return ;
238- scannedPaths . add ( filePath ) ;
239- /** 缓存 */
240- const cached = this . db . tracks [ filePath ] ;
241- // 判断是否可以使用缓存
242- let useCache = false ;
243- if ( cached && cached . mtime === mtime && cached . size === size ) {
244- useCache = true ;
245- // 额外检查:如果记录中有封面,验证封面文件是否真实存在
246- if ( cached . cover && ! existsSync ( join ( coverDir , cached . cover ) ) ) {
247- useCache = false ;
226+ // 分批处理扫描任务,避免内存溢出
227+ const PROCESS_BATCH_SIZE = 200 ; // 每批处理200个文件
228+ for ( let i = 0 ; i < entries . length ; i += PROCESS_BATCH_SIZE ) {
229+ const chunk = entries . slice ( i , i + PROCESS_BATCH_SIZE ) ;
230+ const tasks = chunk . map ( ( entry ) => {
231+ return this . limit ( async ( ) => {
232+ const filePath = entry . path ;
233+ const stats = entry . stats ;
234+ if ( ! stats ) return ;
235+ /** 修改时间 */
236+ const mtime = stats . mtimeMs ;
237+ /** 文件大小 */
238+ const size = stats . size ;
239+ // 小于 1MB 的文件不处理
240+ if ( size < 1024 * 1024 ) return ;
241+ scannedPaths . add ( filePath ) ;
242+ /** 缓存 */
243+ const cached = this . db . tracks [ filePath ] ;
244+ // 判断是否可以使用缓存
245+ let useCache = false ;
246+ if ( cached && cached . mtime === mtime && cached . size === size ) {
247+ useCache = true ;
248+ // 额外检查:如果记录中有封面,验证封面文件是否真实存在
249+ if ( cached . cover && ! existsSync ( join ( coverDir , cached . cover ) ) ) {
250+ useCache = false ;
251+ }
248252 }
249- }
250- // 只有当缓存存在 && 修改时间没变 && 文件大小没变 && 封面存在 -> 才跳过
251- if ( useCache ) {
252- processedCount ++ ;
253- // 添加到批量缓冲区
254- tracksBuffer . push ( cached ! ) ;
255- // 达到批量大小,发送一批
256- if ( tracksBuffer . length >= BATCH_SIZE ) {
257- flushBatch ( ) ;
258- }
259- // 节流发送进度
260- if ( processedCount % 10 === 0 || processedCount === totalFiles ) {
261- onProgress ?.( processedCount , totalFiles ) ;
262- }
263- return ;
264- }
265- // 解析元数据
266- try {
267- const id = this . getFileId ( filePath ) ;
268- const metadata = await parseFile ( filePath ) ;
269- // 过滤规则
270- // 时长 < 30s
271- if ( metadata . format . duration && metadata . format . duration < 30 ) return ;
272- // 时长 > 2h (7200s)
273- if ( metadata . format . duration && metadata . format . duration > 7200 ) return ;
274- // 提取封面
275- const coverPath = await this . extractCover ( metadata , id ) ;
276- // 构建音乐数据
277- const track : MusicTrack = {
278- id,
279- path : filePath ,
280- title : metadata . common . title || basename ( filePath ) ,
281- artist : metadata . common . artist || "Unknown Artist" ,
282- album : metadata . common . album || "Unknown Album" ,
283- duration : ( metadata . format . duration || 0 ) * 1000 ,
284- mtime,
285- size,
286- cover : coverPath ,
287- bitrate : metadata . format . bitrate ?? 0 ,
288- } ;
289- // 添加到数据库
290- this . db . tracks [ filePath ] = track ;
291- isDirty = true ;
292- // 添加到批量缓冲区
293- tracksBuffer . push ( track ) ;
294- // 达到批量大小,发送一批
295- if ( tracksBuffer . length >= BATCH_SIZE ) {
296- flushBatch ( ) ;
253+ // 只有当缓存存在 && 修改时间没变 && 文件大小没变 && 封面存在 -> 才跳过
254+ if ( useCache ) {
255+ processedCount ++ ;
256+ // 添加到批量缓冲区
257+ tracksBuffer . push ( cached ! ) ;
258+ // 达到批量大小,发送一批
259+ if ( tracksBuffer . length >= BATCH_SIZE ) {
260+ flushBatch ( ) ;
261+ }
262+ // 节流发送进度
263+ if ( processedCount % 10 === 0 || processedCount === totalFiles ) {
264+ onProgress ?.( processedCount , totalFiles ) ;
265+ }
266+ return ;
297267 }
298- } catch ( err ) {
299- console . warn ( `Parse error [${ filePath } ]:` , err ) ;
300- } finally {
301- processedCount ++ ;
302- // 节流发送进度
303- if ( processedCount % 10 === 0 || processedCount === totalFiles ) {
304- onProgress ?.( processedCount , totalFiles ) ;
268+ // 解析元数据
269+ try {
270+ const id = this . getFileId ( filePath ) ;
271+ const metadata = await parseFile ( filePath ) ;
272+ // 过滤规则
273+ // 时长 < 30s
274+ if ( metadata . format . duration && metadata . format . duration < 30 ) return ;
275+ // 时长 > 2h (7200s)
276+ if ( metadata . format . duration && metadata . format . duration > 7200 ) return ;
277+ // 提取封面
278+ const coverPath = await this . extractCover ( metadata , id ) ;
279+ // 构建音乐数据
280+ const track : MusicTrack = {
281+ id,
282+ path : filePath ,
283+ title : metadata . common . title || basename ( filePath ) ,
284+ artist : metadata . common . artist || "Unknown Artist" ,
285+ album : metadata . common . album || "Unknown Album" ,
286+ duration : ( metadata . format . duration || 0 ) * 1000 ,
287+ mtime,
288+ size,
289+ cover : coverPath ,
290+ bitrate : metadata . format . bitrate ?? 0 ,
291+ } ;
292+ // 添加到数据库
293+ this . db . tracks [ filePath ] = track ;
294+ isDirty = true ;
295+ // 添加到批量缓冲区
296+ tracksBuffer . push ( track ) ;
297+ // 达到批量大小,发送一批
298+ if ( tracksBuffer . length >= BATCH_SIZE ) {
299+ flushBatch ( ) ;
300+ }
301+ } catch ( err ) {
302+ console . warn ( `Parse error [${ filePath } ]:` , err ) ;
303+ } finally {
304+ processedCount ++ ;
305+ // 节流发送进度
306+ if ( processedCount % 10 === 0 || processedCount === totalFiles ) {
307+ onProgress ?.( processedCount , totalFiles ) ;
308+ }
305309 }
306- }
310+ } ) ;
307311 } ) ;
308- } ) ;
309- await Promise . all ( tasks ) ;
312+ // 等待当前批次完成,释放闭包引用,避免内存积压
313+ await Promise . all ( tasks ) ;
314+ }
310315 // 发送最后一批数据
311316 flushBatch ( ) ;
312317 // 清理脏数据 (处理文件删除 或 移除文件夹的情况)
0 commit comments