فهرست منبع

refactor(LocalMusic): 简化加载进度对话框的管理逻辑

- 移除不必要的 loadingProgressDialogId 参数传递
- 简化 DialogHelper.closeDialog 调用为 DialogHelper.closeLoading
- 更新 DialogHelper.showLoadingProgress 和 updateLoading 调用方式
- 将 loadingProgressDialogId 初始化为空字符串

feat(NavidromePage): 添加远程歌曲去重功能

- 引入 jellyfinSongIdSeen、embySongIdSeen、jellyfinSongKeySeen 和 embySongKeySeen 集合
- 在数据加载前清空去重缓存
- 实现基于歌曲 ID 和标题、艺术家、专辑、时长的去重逻辑
- 添加 buildSongDedupKey 方法构建去重键
- 添加 dedupRemoteSongsByKey 方法对视频项目去重
- 优先保留具有专辑
chendeben 7 ماه پیش
والد
کامیت
5b7055f937
2فایلهای تغییر یافته به همراه83 افزوده شده و 13 حذف شده
  1. 8 9
      entry/src/main/ets/view/LocalMusic.ets
  2. 75 4
      entry/src/main/ets/view/NavidromePage.ets

+ 8 - 9
entry/src/main/ets/view/LocalMusic.ets

@@ -1884,10 +1884,7 @@ export struct LocalMusic {
   }
 
   private closeLoadingProgressDialog(): void {
-    if (!this.loadingProgressDialogId) {
-      return;
-    }
-    DialogHelper.closeDialog(this.loadingProgressDialogId);
+    DialogHelper.closeLoading();
     this.loadingProgressDialogId = '';
   }
 
@@ -2959,13 +2956,14 @@ export struct LocalMusic {
 
     // 初始化进度条
     this.progress  = 0;
-    this.loadingProgressDialogId = DialogHelper.showLoadingProgress({
+    DialogHelper.showLoadingProgress({
       progress: this.progress,
       backCancel: false,
       autoCancel: false,
       loadColor: $r('app.color.title_bar_bg'),
       fontColor: $r('app.color.title_bar_bg')
     });
+    this.loadingProgressDialogId = '';
 
     // 计算处理总数用于进度计算
     const totalItems = uris.length;
@@ -2993,14 +2991,14 @@ export struct LocalMusic {
         // 更新进度
         processedItems++;
         this.progress  = Math.floor((processedItems  / totalItems) * 100);
-        DialogHelper.updateLoading(this.loadingProgressDialogId, ` 正在处理 ${this.progress}%`, this.progress);
+        DialogHelper.updateLoading(` 正在处理 ${this.progress}%`, this.progress);
 
       } catch (error) {
         Logger.error(TAG,  'saveVideoDatas failed with err: ' + JSON.stringify(error));
         // 即使出错也更新进度
         processedItems++;
         this.progress  = Math.floor((processedItems  / totalItems) * 100);
-        DialogHelper.updateLoading(this.loadingProgressDialogId, ` 正在处理 ${this.progress}%`, this.progress);
+        DialogHelper.updateLoading(` 正在处理 ${this.progress}%`, this.progress);
       }
     }
     // 关闭进度条
@@ -3021,13 +3019,14 @@ export struct LocalMusic {
 
     let newUris: string[] = [];
     this.progress = 0
-    this.loadingProgressDialogId = DialogHelper.showLoadingProgress({
+    DialogHelper.showLoadingProgress({
       progress: this.progress,
       backCancel: false,
       autoCancel: false,
       loadColor: this.themeColor,
       fontColor: this.themeColor
     });
+    this.loadingProgressDialogId = '';
     // 计算所有文件的总大小
     let totalSize = 0;
     for (let uri of uris) {
@@ -3059,7 +3058,7 @@ export struct LocalMusic {
 
           // 计算总进度
           this.progress = Math.floor((totalRead / totalSize) * 100);
-          DialogHelper.updateLoading(this.loadingProgressDialogId, `正在导入 ${this.progress}%`, this.progress);
+          DialogHelper.updateLoading(`正在导入 ${this.progress}%`, this.progress);
 
           len = await fileIo.read(sourceFile.fd, buffer);
         }

+ 75 - 4
entry/src/main/ets/view/NavidromePage.ets

@@ -109,6 +109,10 @@ export struct NavidromePage {
   private albumCoverLookup: Map<string, string> = new Map();
   private searchTicket: number = 0;
   private readonly REMOTE_PAGE_SIZE: number = 200;
+  private jellyfinSongIdSeen: Set<string> = new Set();
+  private embySongIdSeen: Set<string> = new Set();
+  private jellyfinSongKeySeen: Set<string> = new Set();
+  private embySongKeySeen: Set<string> = new Set();
 
   private isNavidromeAccount(account: WebDavAccount): boolean {
     return account.webType === RemoteDriveType.Navidrome;
@@ -268,6 +272,10 @@ export struct NavidromePage {
     this.artists = [];
     this.albums = [];
     this.playlists = [];
+    this.jellyfinSongIdSeen.clear();
+    this.embySongIdSeen.clear();
+    this.jellyfinSongKeySeen.clear();
+    this.embySongKeySeen.clear();
     this.clearFilter();
     this.coverUrlCache.clear();
     this.albumCoverLookup.clear();
@@ -512,12 +520,26 @@ export struct NavidromePage {
       if (currentTicket !== this.loadTicket) {
         return;
       }
-      const restSongs = this.convertJellyfinSongsToRestSongs(response.items);
+      const uniqueItems: JellyfinSong[] = [];
+      for (let i = 0; i < response.items.length; i++) {
+        const item = response.items[i];
+        if (!item.id) {
+          continue;
+        }
+        const key = this.buildSongDedupKey(item.title, item.artist, item.album, item.durationSeconds);
+        if (this.jellyfinSongIdSeen.has(item.id) || this.jellyfinSongKeySeen.has(key)) {
+          continue;
+        }
+        this.jellyfinSongIdSeen.add(item.id);
+        this.jellyfinSongKeySeen.add(key);
+        uniqueItems.push(item);
+      }
+      const restSongs = this.convertJellyfinSongsToRestSongs(uniqueItems);
       const videoItems = await this.convertSongsToVideoItems(restSongs, account);
       if (currentTicket !== this.loadTicket) {
         return;
       }
-      this.allVideos = [...this.allVideos, ...videoItems];
+      this.allVideos = this.dedupRemoteSongsByKey([...this.allVideos, ...videoItems]);
       this.songNextStart = response.nextStart;
       void ServerLogUtil.info('NavidromeLoad', `Jellyfin 歌曲追加: 本次 ${videoItems.length} 首, 总数 ${this.allVideos.length}`);
     } finally {
@@ -582,12 +604,26 @@ export struct NavidromePage {
       if (currentTicket !== this.loadTicket) {
         return;
       }
-      const restSongs = this.convertEmbySongsToRestSongs(response.items);
+      const uniqueItems: EmbySong[] = [];
+      for (let i = 0; i < response.items.length; i++) {
+        const item = response.items[i];
+        if (!item.id) {
+          continue;
+        }
+        const key = this.buildSongDedupKey(item.title, item.artist, item.album, item.durationSeconds);
+        if (this.embySongIdSeen.has(item.id) || this.embySongKeySeen.has(key)) {
+          continue;
+        }
+        this.embySongIdSeen.add(item.id);
+        this.embySongKeySeen.add(key);
+        uniqueItems.push(item);
+      }
+      const restSongs = this.convertEmbySongsToRestSongs(uniqueItems);
       const videoItems = await this.convertSongsToVideoItems(restSongs, account);
       if (currentTicket !== this.loadTicket) {
         return;
       }
-      this.allVideos = [...this.allVideos, ...videoItems];
+      this.allVideos = this.dedupRemoteSongsByKey([...this.allVideos, ...videoItems]);
       this.songNextStart = response.nextStart;
       void ServerLogUtil.info('NavidromeLoad', `Emby 歌曲追加: 本次 ${videoItems.length} 首, 总数 ${this.allVideos.length}`);
     } finally {
@@ -973,6 +1009,41 @@ export struct NavidromePage {
     return results;
   }
 
+  private buildSongDedupKey(title?: string, artist?: string, album?: string, durationSeconds?: number): string {
+    const safeTitle = title ?? '';
+    const safeArtist = artist ?? '';
+    const safeAlbum = album ?? '';
+    const safeDuration = durationSeconds !== undefined && durationSeconds !== null ? durationSeconds.toString() : '';
+    return `${safeTitle}__${safeArtist}__${safeAlbum}__${safeDuration}`.toLowerCase();
+  }
+
+  private buildSongKeyFromItem(item: VideoItem): string {
+    const safeTitle = item.name ?? '';
+    const safeArtist = item.artist ?? '';
+    const safeAlbum = item.album ?? '';
+    const safeDuration = item.duration ?? '';
+    return `${safeTitle}__${safeArtist}__${safeAlbum}__${safeDuration}`.toLowerCase();
+  }
+
+  private dedupRemoteSongsByKey(items: VideoItem[]): VideoItem[] {
+    const map = new Map<string, VideoItem>();
+    for (let i = 0; i < items.length; i++) {
+      const item = items[i];
+      const key = this.buildSongKeyFromItem(item);
+      const existing = map.get(key);
+      if (!existing) {
+        map.set(key, item);
+        continue;
+      }
+      const existingHasAlbumId = StrUtil.isNotEmpty(existing.navAlbumId);
+      const currentHasAlbumId = StrUtil.isNotEmpty(item.navAlbumId);
+      if (!existingHasAlbumId && currentHasAlbumId) {
+        map.set(key, item);
+      }
+    }
+    return Array.from(map.values());
+  }
+
   private convertApiSongsToRestSongs(songs: NavidromeSong[]): NavidromeRestSong[] {
     return songs.map((song: NavidromeSong): NavidromeRestSong => ({
       id: song.id,