Răsfoiți Sursa

下载中心终于解决实时进度更新和下载速度实时显示

onecold 5 luni în urmă
părinte
comite
4ac757ff86

+ 10 - 1
entry/src/main/ets/common/util/FileDeletionWatcher.ets

@@ -5,6 +5,7 @@ import MediaTable from './MediaTable';
 import { VideoItem } from '../../viewmodel/VideoItem';
 import { Utility } from './Utility';
 import { CommonConstants } from '../constants/CommonConstants';
+import { getCueTrackSourceFilePath, isCueSplitItem } from './CueUtils';
 
 const TAG = 'heanup FileDeletionWatcher';
 const WATCH_EVENT_MASK = 0x200 | 0x400 | 0x40 | 0x80 | 0x100; // 删除、目录自删、移出、移入和新建
@@ -300,9 +301,17 @@ export default class FileDeletionWatcher {
     if (!filePath) {
       return;
     }
+    let actualFilePath = filePath;
+    try {
+      const item = await this.mediaTable.queryVideoByFilePath(filePath);
+      if (item && isCueSplitItem(item)) {
+        actualFilePath = getCueTrackSourceFilePath(item);
+      }
+    } catch (_) {
+    }
     let exists = true;
     try {
-      exists = FileUtil.accessSync(filePath);
+      exists = FileUtil.accessSync(actualFilePath);
     } catch (_) {
       exists = false;
     }

+ 38 - 0
entry/src/main/ets/common/util/MediaTable.ets

@@ -254,6 +254,40 @@ export default  class MediaTable {
     this.accountTable.insertData(valueBucket, callback,cover_api);
   }
 
+  public async saveOrUpdateLocalItem(item: VideoItem): Promise<boolean> {
+    try {
+      if (!item || !item.filePath) {
+        return false;
+      }
+
+      if (!item.id) {
+        item.id = item.filePath;
+      }
+      if (!item.parentPath && item.filePath.includes('/')) {
+        item.parentPath = item.filePath.substring(0, item.filePath.lastIndexOf('/'));
+      }
+
+      const exists = await this.existsByFilePath(item.filePath);
+      const bucket = generateBucket(item);
+
+      return await new Promise<boolean>((resolve) => {
+        if (exists) {
+          const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
+          predicates.equalTo(DB_COLUMNS.FILE_PATH, normalizeFilePath(item.filePath));
+          this.accountTable.updateData(predicates, bucket, (success: boolean) => resolve(success));
+          return;
+        }
+
+        this.accountTable.insertData(bucket, (result: relationalStore.ResultSet | number | boolean) => {
+          resolve(result !== false);
+        });
+      });
+    } catch (error) {
+      Logger.error(RdbUtils.RDB_TAG, 'saveOrUpdateLocalItem 失败: ' + (error as Error).message);
+      return false;
+    }
+  }
+
   deleteData(item: VideoItem, callback: Function) {
     let predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
     predicates.equalTo('id', item.id);
@@ -1271,6 +1305,10 @@ export default  class MediaTable {
       if (options?.type !== undefined) {
         conditions.push(`${DB_COLUMNS.TYPE} = ?`);
         params.push(options.type);
+        if (options.type === CommonConstants.TYPE_LOCAL) {
+          conditions.push(`${DB_COLUMNS.FILE_PATH} NOT LIKE ?`);
+          params.push('%.cue');
+        }
       }
       // 文件夹条件
       if (options?.parentPath) {

+ 9 - 0
entry/src/main/ets/common/util/RemotePlayerUtil.ets

@@ -31,6 +31,7 @@ import { Utility } from './Utility';
 import MediaTable from './MediaTable';
 import emitter from '@ohos.events.emitter';
 import { EventConstants } from '../constants/EventConstants';
+import { getCueTrackSourceFilePath, isCueSplitItem } from './CueUtils';
 
 const TAG = 'heanup RemotePlayerUtil';
 const metadataExtractionInFlight: Map<string, Promise<void>> = new Map();
@@ -1059,6 +1060,14 @@ export async function setVideoUrlForSong(
     throw new Error('道理鱼歌曲缺少webdav_account_id,无法构建播放链接');
   }
 
+  if (song.type === CommonConstants.TYPE_LOCAL && isCueSplitItem(song)) {
+    const sourcePath = getCueTrackSourceFilePath(song);
+    if (sourcePath) {
+      Logger.info(TAG, `setVideoUrlForSong CUE分轨映射到源文件: ${sourcePath}`);
+      return sourcePath;
+    }
+  }
+
   Logger.info(TAG, `setVideoUrlForSong fallback直接返回原始路径: ${song.filePath}`);
   return song.filePath;
 }

+ 5 - 2
entry/src/main/ets/pages/ScanFilePage.ets

@@ -17,6 +17,7 @@ import { resourceManager } from '@kit.LocalizationKit'
 import { common, ConfigurationConstant } from '@kit.AbilityKit'
 import { DialogHelper } from '@pura/harmony-dialog'
 import { ButtonFancyModifier, ShadowModifier, SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil'
+import { getCueTrackSourceFilePath, isCueSplitItem } from '../common/util/CueUtils'
 
 
 
@@ -492,7 +493,8 @@ export struct ScanFilePage{
       for (const item of allMusic) {
         // 检查是否是本地音乐(filePath包含包名)
         if (item.filePath&&item.type===CommonConstants.TYPE_LOCAL && item.filePath.includes(this.packName)) {
-          const exists = await FileUtil.accessSync(item.filePath);
+          const actualPath = isCueSplitItem(item) ? getCueTrackSourceFilePath(item) : item.filePath;
+          const exists = await FileUtil.accessSync(actualPath);
           if (!exists) {
             invalidCount++;
             invalidFilePaths.push(item.filePath);
@@ -561,7 +563,8 @@ export struct ScanFilePage{
       for (const item of allMusic) {
         // 检查是否是本地音乐(filePath包含包名)
         if (item.filePath && item.filePath.includes(this.packName)) {
-          const exists = await FileUtil.accessSync(item.filePath);
+          const actualPath = isCueSplitItem(item) ? getCueTrackSourceFilePath(item) : item.filePath;
+          const exists = await FileUtil.accessSync(actualPath);
           if (!exists) {
             // 文件不存在,删除数据库记录
             await new Promise<void>((resolve) => {

+ 3 - 1
entry/src/main/ets/pages/SettingPage.ets

@@ -90,6 +90,8 @@ export struct SettingPage {
   static readonly IS_SHOW_TITLTBAR: string = 'IS_SHOW_TITLTBAR';
   static readonly IS_SHOW_PLAYPAGE_BACK: string = 'IS_SHOW_PLAYPAGE_BACK';
   static readonly IS_SHOW_SLLYRIC: string = 'IS_SHOW_SLLYRIC';
+  static readonly IS_ENABLE_WORD_BY_WORD_LYRIC: string = 'IS_ENABLE_WORD_BY_WORD_LYRIC';
+  static readonly IS_LIGHT_TEXT: string = 'IS_LIGHT_TEXT';
   static readonly IS_CIRCLE_BTN: string = 'isCircleBtn';
   static readonly IS_COVER_TOP: string = 'IS_COVER_TOP';
   static readonly IS_SHOW_HEADER: string = 'IS_SHOW_HEADER';
@@ -1310,7 +1312,7 @@ export struct SettingPage {
 
             // 播放页上滑切歌
             Row() {
-              SymbolGlyph($r('sys.symbol.music_mic_stars'))
+              SymbolGlyph($r('sys.symbol.music_note_circle'))
                 .fontSize(20)
                 .fontColor([this.themeColor])
                 .alignSelf(ItemAlign.Center)

+ 18 - 7
entry/src/main/ets/pages/WebDavMainPage.ets

@@ -2428,13 +2428,6 @@ export struct WebDavMainPage {
     }
     .padding({ top: this.topSafeHeight + 10, left: 10, right: 10, bottom: 2 })
     .width('100%')
-    .bindContentCover($$this.isShowDownloadCenter, this.DownloadCenterBuilder(),
-      {
-        modalTransition:ModalTransition.DEFAULT,
-        onWillDisappear: () => {
-          this.isShowDownloadCenter = false
-        },
-      })
   }
 
 
@@ -2741,6 +2734,24 @@ export struct WebDavMainPage {
       if (this.isMultiSelect || this.isDeletingSelection) {
         this.buildSelectionOverlay()
       }
+
+      if (this.isShowDownloadCenter) {
+        Stack() {
+          Column()
+            .width('100%')
+            .height('100%')
+            .backgroundColor('#66000000')
+            .onClick(() => {
+              this.isShowDownloadCenter = false
+            })
+
+          this.DownloadCenterBuilder()
+        }
+        .width('100%')
+        .height('100%')
+        .zIndex(999)
+        .transition(TransitionEffect.translate({ x: 500 }).animation({ curve: curves.springMotion(0.6, 0.8) }))
+      }
     }
     .width('100%')
     .height('100%')

+ 75 - 36
entry/src/main/ets/view/DownloadCenter.ets

@@ -3,10 +3,53 @@ import { DownloadCenterTask } from '../common/util/DownloadCenterManager';
 import { CommonConstants } from '../common/constants/CommonConstants';
 import { StrUtil } from '@pura/harmony-utils';
 
+@Observed
+class DownloadCenterTaskState implements DownloadCenterTask {
+  taskId: string = '';
+  title: string = '';
+  fileName: string = '';
+  coverPath: string = '';
+  sizeText: string = '';
+  sourceUrl: string = '';
+  targetPath: string = '';
+  downloadDir: string = '';
+  totalBytes: number = 0;
+  downloadedBytes: number = 0;
+  progress: number = 0;
+  speedBytesPerSec: number = 0;
+  status: 'pending' | 'downloading' | 'paused' | 'completed' | 'failed' = 'pending';
+  errorMessage: string = '';
+  createdAt: number = 0;
+  finishedAt: number = 0;
+
+  constructor(task: DownloadCenterTask) {
+    this.apply(task);
+  }
+
+  apply(task: DownloadCenterTask): void {
+    this.taskId = task.taskId;
+    this.title = task.title;
+    this.fileName = task.fileName;
+    this.coverPath = task.coverPath;
+    this.sizeText = task.sizeText;
+    this.sourceUrl = task.sourceUrl;
+    this.targetPath = task.targetPath;
+    this.downloadDir = task.downloadDir;
+    this.totalBytes = task.totalBytes;
+    this.downloadedBytes = task.downloadedBytes;
+    this.progress = task.progress;
+    this.speedBytesPerSec = task.speedBytesPerSec;
+    this.status = task.status;
+    this.errorMessage = task.errorMessage;
+    this.createdAt = task.createdAt;
+    this.finishedAt = task.finishedAt;
+  }
+}
+
 @Component
 struct DownloadCenterTaskRow {
   @Prop themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
-  @Prop task: DownloadCenterTask;
+  @ObjectLink task: DownloadCenterTaskState;
   @Prop isDownloading: boolean = true;
   @Prop refreshVersion: number = 0;
   onPauseTask: (taskId: string) => void = (_taskId: string): void => {};
@@ -227,8 +270,8 @@ export struct DownloadCenter {
   @Prop activeTasksProp: DownloadCenterTask[] = [];
   @Prop completedTasksProp: DownloadCenterTask[] = [];
   @Link selectedIndexes: number[];
-  @State activeTasks: DownloadCenterTask[] = [];
-  @State completedTasks: DownloadCenterTask[] = [];
+  @State activeTasks: DownloadCenterTaskState[] = [];
+  @State completedTasks: DownloadCenterTaskState[] = [];
   @State renderVersion: number = 0;
   onClose: () => void = () => {};
   onPauseTask: (taskId: string) => void = (_taskId: string): void => {};
@@ -309,12 +352,8 @@ export struct DownloadCenter {
   }
 
   private syncTasksFromProps(): void {
-    this.activeTasks = this.activeTasksProp.map((task: DownloadCenterTask): DownloadCenterTask => {
-      return this.cloneTask(task);
-    });
-    this.completedTasks = this.completedTasksProp.map((task: DownloadCenterTask): DownloadCenterTask => {
-      return this.cloneTask(task);
-    });
+    this.activeTasks = this.mergeTaskStates(this.activeTasks, this.activeTasksProp);
+    this.completedTasks = this.mergeTaskStates(this.completedTasks, this.completedTasksProp);
     this.renderVersion = this.tasksVersion;
   }
 
@@ -333,7 +372,7 @@ export struct DownloadCenter {
         .justifyContent(FlexAlign.Center)
       } else {
         List({ space: 10 }) {
-          ForEach(this.activeTasks, (task: DownloadCenterTask) => {
+          ForEach(this.activeTasks, (task: DownloadCenterTaskState) => {
             DownloadCenterTaskRow({
               themeColor: this.themeColor,
               task: task,
@@ -342,7 +381,7 @@ export struct DownloadCenter {
               onPauseTask: this.onPauseTask,
               onResumeTask: this.onResumeTask
             })
-          }, (task: DownloadCenterTask): string => {
+          }, (task: DownloadCenterTaskState): string => {
             return this.getTaskRenderKey(task);
           })
         }
@@ -363,14 +402,14 @@ export struct DownloadCenter {
         .justifyContent(FlexAlign.Center)
       } else {
         List({ space: 10 }) {
-          ForEach(this.completedTasks, (task: DownloadCenterTask) => {
+          ForEach(this.completedTasks, (task: DownloadCenterTaskState) => {
             DownloadCenterTaskRow({
               themeColor: this.themeColor,
               task: task,
               isDownloading: false,
               refreshVersion: this.renderVersion
             })
-          }, (task: DownloadCenterTask): string => {
+          }, (task: DownloadCenterTaskState): string => {
             return this.getTaskRenderKey(task);
           })
         }
@@ -404,13 +443,13 @@ export struct DownloadCenter {
 
   private resolveCurrentDownloadDirectory(): string {
     for (let i = 0; i < this.activeTasks.length; i += 1) {
-      const task: DownloadCenterTask = this.activeTasks[i];
+      const task: DownloadCenterTaskState = this.activeTasks[i];
       if (StrUtil.isNotEmpty(task.downloadDir)) {
         return task.downloadDir;
       }
     }
     for (let i = 0; i < this.completedTasks.length; i += 1) {
-      const task: DownloadCenterTask = this.completedTasks[i];
+      const task: DownloadCenterTaskState = this.completedTasks[i];
       if (StrUtil.isNotEmpty(task.downloadDir)) {
         return task.downloadDir;
       }
@@ -418,29 +457,29 @@ export struct DownloadCenter {
     return '';
   }
 
-  private getTaskRenderKey(task: DownloadCenterTask): string {
-    return task.taskId;
+  private mergeTaskStates(currentTasks: DownloadCenterTaskState[], nextTasks: DownloadCenterTask[]): DownloadCenterTaskState[] {
+    const nextStateMap: Map<string, DownloadCenterTaskState> = new Map<string, DownloadCenterTaskState>();
+    for (let i = 0; i < currentTasks.length; i += 1) {
+      const state: DownloadCenterTaskState = currentTasks[i];
+      nextStateMap.set(state.taskId, state);
+    }
+
+    const mergedTasks: DownloadCenterTaskState[] = [];
+    for (let i = 0; i < nextTasks.length; i += 1) {
+      const nextTask: DownloadCenterTask = nextTasks[i];
+      const existingState: DownloadCenterTaskState | undefined = nextStateMap.get(nextTask.taskId);
+      if (existingState) {
+        existingState.apply(nextTask);
+        mergedTasks.push(existingState);
+        continue;
+      }
+      mergedTasks.push(new DownloadCenterTaskState(nextTask));
+    }
+    return mergedTasks;
   }
 
-  private cloneTask(task: DownloadCenterTask): DownloadCenterTask {
-    return {
-      taskId: task.taskId,
-      title: task.title,
-      fileName: task.fileName,
-      coverPath: task.coverPath,
-      sizeText: task.sizeText,
-      sourceUrl: task.sourceUrl,
-      targetPath: task.targetPath,
-      downloadDir: task.downloadDir,
-      totalBytes: task.totalBytes,
-      downloadedBytes: task.downloadedBytes,
-      progress: task.progress,
-      speedBytesPerSec: task.speedBytesPerSec,
-      status: task.status,
-      errorMessage: task.errorMessage,
-      createdAt: task.createdAt,
-      finishedAt: task.finishedAt
-    };
+  private getTaskRenderKey(task: DownloadCenterTaskState): string {
+    return task.taskId;
   }
 
   private getCurrentTabIndex(): number {