Răsfoiți Sursa

修复采样率以及格式

chendeben 1 an în urmă
părinte
comite
dcbffde7da
2 a modificat fișierele cu 454 adăugiri și 19 ștergeri
  1. 334 0
      @cursorrules.mdc
  2. 120 19
      entry/src/main/ets/view/LocalMusic.ets

+ 334 - 0
@cursorrules.mdc

@@ -0,0 +1,334 @@
+---
+description: 
+globs: 
+alwaysApply: false
+---
+# 鸿蒙ArkTS编程规则与最佳实践
+
+## 语法差异规则:ArkTS vs TypeScript
+
+在需要更新经验规则记忆的时候,请及时更新.cursor\rules\cursorrules.mdc文件
+
+### 1. 空值检查规则
+```typescript
+// ❌ 错误: 未对可能为null的对象执行检查
+this.dbObject.executeSql(sql);
+
+// ✅ 正确: 执行null检查
+if (this.dbObject) {
+  this.dbObject.executeSql(sql);
+}
+```
+
+### 2. 解构赋值规则
+```typescript
+// ❌ 错误: ArkTS不支持解构赋值语法
+for (const [key, value] of Object.entries(obj)) {
+  // 处理逻辑
+}
+
+// ✅ 正确: 使用传统循环方式
+const keys = Object.keys(obj);
+for (let i = 0; i < keys.length; i++) {
+  const key = keys[i];
+  const value = obj[key];
+  // 处理逻辑
+}
+```
+
+### 3. 异步API调用规则
+```typescript
+// ❌ 错误: 回调参数类型不匹配
+dbStore.executeSql(sql, (err, result: ResultSet) => {
+  // 处理逻辑
+});
+
+// ✅ 正确: 使用Promise模式
+dbStore.executeSql(sql)
+  .then(() => {
+    // 成功处理
+  })
+  .catch((err: Error) => {
+    // 错误处理
+    console.log(err.message);
+  });
+```
+
+### 4. 计算属性名规则
+```typescript
+// ❌ 错误: 不支持计算属性名语法
+const obj = { [CONSTANT.KEY]: value };
+
+// ✅ 正确: 使用对象属性赋值语法
+const obj = {};
+obj[CONSTANT.KEY] = value;
+```
+
+### 5. 数据类型规则
+```typescript
+// ❌ 错误: 使用any或未指定泛型类型
+const items = new Set();
+const map = new Map();
+
+// ✅ 正确: 明确指定泛型类型
+const items = new Set<string>();
+const map = new Map<string, number>();
+```
+
+### 6. 错误对象类型规则
+```typescript
+// ❌ 错误: 使用隐式any类型的错误对象
+try {
+  // 代码
+} catch (e) {
+  console.log(`错误: ${e}`);
+}
+
+// ✅ 正确: 明确指定错误对象的类型
+try {
+  // 代码
+} catch (e: Error) {
+  console.log(`错误: ${e.message}`);
+}
+```
+
+### 7. 类型声明与对象字面量规则
+```typescript
+// ❌ 错误: 对象字面量不能用作类型声明
+const metadataToUpdate: { 
+  duration?: string, 
+  mimeType?: string, 
+  sampleRate?: string, 
+  trackCount?: string 
+} = { /* 值 */ };
+
+// ✅ 正确: 使用接口或类型别名定义类型
+interface MediaMetadata {
+  duration?: string;
+  mimeType?: string;
+  sampleRate?: string;
+  trackCount?: string;
+}
+
+const metadataToUpdate: MediaMetadata = { /* 值 */ };
+```
+
+## 数据库操作最佳实践
+
+### 1. 表结构升级
+```typescript
+// ❌ 错误: 依赖回调处理的表结构升级
+this.rdbStore.executeSql(tableInfoQuery, (err, result) => {
+  // 处理逻辑
+});
+
+// ✅ 正确: 使用Promise模式并独立处理每个列的添加操作
+this.rdbStore.executeSql(tableInfoQuery)
+  .then(() => {
+    // 为每个需要添加的列单独执行ALTER TABLE
+    Object.keys(columnsToAdd).forEach(column => {
+      const type = columnsToAdd[column];
+      this.rdbStore.executeSql(`ALTER TABLE ${tableName} ADD COLUMN ${column} ${type}`)
+        .then(() => { /* 成功处理 */ })
+        .catch((err: Error) => { /* 错误处理 */ });
+    });
+  });
+```
+
+### 2. 资源释放
+```typescript
+// ❌ 错误: 未关闭ResultSet
+this.rdbStore.query(predicates, (resultSet) => {
+  // 处理逻辑
+});
+
+// ✅ 正确: 确保关闭ResultSet
+this.rdbStore.query(predicates, (resultSet) => {
+  try {
+    // 处理逻辑
+  } finally {
+    resultSet.close();
+  }
+});
+```
+
+### 3. 数据库查询安全处理
+```typescript
+// ❌ 错误: 不安全的列访问
+const value = resultSet.getString(resultSet.getColumnIndex(columnName));
+
+// ✅ 正确: 安全的列访问
+const safeGet = (col: string) => {
+  const index = resultSet.getColumnIndex(col);
+  return index >= 0 ? resultSet.getString(index) || '' : '';
+};
+const value = safeGet(columnName);
+```
+
+## 对象字面量规则
+
+### 1. 复杂对象初始化
+```typescript
+// ❌ 错误: 不支持复杂对象字面量初始化
+const config = {
+  complex: {
+    nested: {
+      value: someValue
+    }
+  }
+};
+
+// ✅ 正确: 分步创建复杂对象
+const config = {};
+config.complex = {};
+config.complex.nested = {};
+config.complex.nested.value = someValue;
+```
+
+### 2. 接口实现
+```typescript
+// ❌ 错误: 在类内部定义接口
+class MediaTable {
+  interface MediaMetadata {
+    duration?: string;
+    mimeType?: string;
+  }
+}
+
+// ✅ 正确: 在类外部定义接口并导出
+export interface MediaMetadata {
+  duration?: string;
+  mimeType?: string;
+}
+
+class MediaTable {
+  // 类实现
+}
+```
+
+## UI数据处理规则
+
+### 1. 空值处理
+```typescript
+// ❌ 错误: 未处理空值或无效值
+Text(this.currentSong.sampleRate)
+
+// ✅ 正确: 安全处理空值和无效值
+Text(Utility.convertToKHz(this.currentSong?.sampleRate || ''))
+```
+
+### 2. 数据转换函数
+```typescript
+// ❌ 错误: 直接在UI中转换数据
+Text(`${Number(this.sampleRate) / 1000} KHz`)
+
+// ✅ 正确: 使用工具函数处理数据格式化
+static convertToKHz(sampleRateHz: string|undefined): string {
+  if(StrUtil.isEmpty(sampleRateHz) || sampleRateHz === undefined) {
+    return '未知';
+  }
+  
+  try {
+    const sampleRateNum = Number(sampleRateHz);
+    if (isNaN(sampleRateNum) || sampleRateNum <= 0) {
+      return '未知';
+    }
+    return `${(sampleRateNum / 1000).toFixed(1)} KHz`;
+  } catch (err) {
+    return '未知';
+  }
+}
+
+// 在UI中使用
+Text(Utility.convertToKHz(this.sampleRate))
+```
+
+## 命名规范
+
+- 类名: PascalCase (例如 MediaTable)
+- 方法名: camelCase (例如 queryByParentPath)
+- 常量: UPPER_SNAKE_CASE (例如 DB_COLUMNS.FILE_PATH)
+- 私有属性: _camelCase (例如 _dbStore)
+- 接口名: PascalCase带I前缀或不带 (例如 IMediaMetadata 或 MediaMetadata)
+
+## 异步编程最佳实践
+
+### 1. 避免回调地狱
+```typescript
+// ❌ 错误: 嵌套回调
+methodA(() => {
+  methodB(() => {
+    methodC(() => {
+      // 更多嵌套...
+    });
+  });
+});
+
+// ✅ 正确: 使用Promise链或async/await
+methodA()
+  .then(() => methodB())
+  .then(() => methodC())
+  .catch(error => console.error(error));
+
+// 或使用async/await
+async function process() {
+  try {
+    await methodA();
+    await methodB();
+    await methodC();
+  } catch (error) {
+    console.error(error);
+  }
+}
+```
+
+### 2. 异步状态更新
+```typescript
+// ❌ 错误: 在异步回调中直接更新状态而没有检查组件是否已销毁
+fetchData(() => {
+  this.data = result; // 组件可能已被销毁
+});
+
+// ✅ 正确: 添加组件生命周期检查
+fetchData(() => {
+  if (!this.mDestroyPage) {
+    this.data = result;
+  }
+});
+```
+
+## 文件和代码组织
+
+### 1. 导入导出规则
+```typescript
+// ❌ 错误: 混合默认导出和命名导出
+export default class MediaTable { ... }
+export interface MediaMetadata { ... }
+
+// ✅ 正确: 明确区分默认导出和命名导出
+// MediaTable.ets
+export default class MediaTable { ... }
+
+// MediaTypes.ets
+export interface MediaMetadata { ... }
+export interface AudioMetadata extends MediaMetadata { ... }
+```
+
+### 2. 文件结构规则
+```
+// ✅ 推荐的文件结构
+/common
+  /constants   // 常量定义
+  /interfaces  // 接口定义
+  /util        // 工具类
+/viewmodel     // 视图模型
+/view          // UI组件
+/controller    // 控制器
+```
+
+
+
+
+
+
+

+ 120 - 19
entry/src/main/ets/view/LocalMusic.ets

@@ -7306,22 +7306,93 @@ export struct LocalMusic {
     this.mDestroyPage = false;
     this.animationState = AnimationStatus.Running
     LogUtils.getInstance().LOGI("startPlayOrResumePlay start this.CONTROL_PlayStatus:" + this.CONTROL_PlayStatus)
+    
+    // 检查并更新当前歌曲的元数据
+    if (this.currentSong && (!this.currentSong.sampleRate || !this.currentSong.mimeType)) {
+      // 异步检查元数据,不阻塞播放流程
+      this.checkAndUpdateMetadata();
+    }
+    
     if (this.CONTROL_PlayStatus == PlayStatus.INIT) {
       this.stopProgressTask();
       this.startProgressTask();
-      this.play(this.videoUrl.toString());
-
-
+      // 确保 videoUrl 不为 undefined
+      if (this.videoUrl) {
+        this.play(this.videoUrl.toString());
+      } else {
+        LogUtils.getInstance().LOGI("无法播放:videoUrl 为空");
+      }
     }
+    
     if (this.CONTROL_PlayStatus == PlayStatus.PAUSE) {
       this.mIjkMediaPlayer.start();
       this.setProgress()
       this.CONTROL_PlayStatus = PlayStatus.PLAY
     }
+    
     this.setIsPlaying(true)
     this.updateSessionPlayState(true)
     this.watchStatus();
   }
+  
+  // 检查并更新元数据的辅助方法
+  private checkAndUpdateMetadata() {
+    // 首先检查 currentSong 是否存在
+    if (!this.currentSong || StrUtil.isEmpty(this.currentSong.filePath)) {
+      console.error('无法更新元数据,当前歌曲或文件路径为空');
+      return;
+    }
+
+    // 创建本地变量存储当前歌曲信息
+    const song = this.currentSong;
+    const filePath = song.filePath;
+    
+    // 使用QueryCallback类型的函数变量明确处理回调
+    let callback = (result: VideoItem[]) => {
+      // 检查当前歌曲是否仍然存在
+      if (!this.currentSong) {
+        console.error('回调期间当前歌曲已变为空');
+        return;
+      }
+      
+      if (result && result.length > 0) {
+        const dbItem = result[0];
+        if (dbItem.sampleRate || dbItem.mimeType) {
+          // 使用try-catch块确保即使出现异常也不会中断程序
+          try {
+            // 再次检查currentSong是否存在
+            if (this.currentSong) {
+              let currentSong = this.currentSong;
+              currentSong.sampleRate = dbItem.sampleRate;
+              currentSong.mimeType = dbItem.mimeType;
+              currentSong.duration = dbItem.duration;
+              currentSong.trackCount = dbItem.trackCount;
+              console.info(`从数据库加载歌曲元数据 - 采样率: ${dbItem.sampleRate}, MIME类型: ${dbItem.mimeType}`);
+            }
+          } catch (e) {
+            console.error(`更新元数据时出错: ${e}`);
+          }
+        } else {
+          // 数据库中也没有,从文件提取
+          if (this.currentSong) {
+            this.refreshCurrentSongMetadata();
+          }
+        }
+      } else {
+        // 数据库中没有这首歌,从文件提取
+        if (this.currentSong) {
+          this.refreshCurrentSongMetadata();
+        }
+      }
+    };
+    
+    // 使用try-catch块包裹数据库查询
+    try {
+      this.table.queryByFilePath(filePath, callback);
+    } catch (e) {
+      console.error(`查询数据库时出错: ${e}`);
+    }
+  }
 
   private completionNum(num: number): string | number {
     if (num < 10) {
@@ -7378,7 +7449,10 @@ export struct LocalMusic {
       position = duration;
     }
     this.isCurrentTime = true;
-    this.lyricController.updatePosition(position + this.timeOffset * 1000)
+    // 确保 lyricController 不为空
+    if (this.lyricController) {
+      this.lyricController.updatePosition(position + this.timeOffset * 1000);
+    }
     this.currentTime = this.stringForTime(position);
     this.isCurrentTime = false
 
@@ -7434,27 +7508,40 @@ export struct LocalMusic {
 
   // 从文件重新提取音频元数据
   private async refreshCurrentSongMetadata() {
+    // 先检查currentSong是否存在
     if (!this.currentSong || StrUtil.isEmpty(this.currentSong.filePath)) {
       console.error('无法刷新元数据,当前歌曲或文件路径为空');
       return;
     }
     
+    // 创建本地变量存储必要信息
+    const song = this.currentSong;
+    const filePath = song.filePath;
+    
     try {
       // 直接从音频文件重新加载完整的元数据
-      console.info(`开始重新提取音频元数据: ${this.currentSong.filePath}`);
+      console.info(`开始重新提取音频元数据: ${filePath}`);
       const refreshedItem = await Utility.uriGetMusicAssetsFromFile(
         this.context, 
-        this.currentSong.filePath, 
+        filePath, 
         CommonConstants.TYPE_LOCAL, 
         false
       );
       
-      // 只更新元数据相关字段,保留其他字段
-      if (this.currentSong) {
-        this.currentSong.duration = refreshedItem.duration;
-        this.currentSong.mimeType = refreshedItem.mimeType;
-        this.currentSong.sampleRate = refreshedItem.sampleRate;
-        this.currentSong.trackCount = refreshedItem.trackCount;
+      // 异步操作结束后再次检查currentSong是否仍然存在
+      if (!this.currentSong) {
+        console.error('异步操作完成后,当前歌曲已变为空');
+        return;
+      }
+      
+      // 使用try-catch块确保即使出现异常也不会中断程序
+      try {
+        // 再次检查currentSong是否存在并更新字段
+        const currentSong = this.currentSong; // 刷新引用
+        currentSong.duration = refreshedItem.duration;
+        currentSong.mimeType = refreshedItem.mimeType;
+        currentSong.sampleRate = refreshedItem.sampleRate;
+        currentSong.trackCount = refreshedItem.trackCount;
         
         // 更新数据库中的记录以确保下次不需要重新提取
         // 创建符合MediaMetadata接口的对象
@@ -7464,15 +7551,29 @@ export struct LocalMusic {
           sampleRate: refreshedItem.sampleRate,
           trackCount: refreshedItem.trackCount
         };
-        this.table.updateMediaMetadata(this.currentSong.filePath, metadataToUpdate, (success: boolean) => {
-          if (success) {
-            console.info('成功更新音频元数据到数据库');
-          } else {
-            console.error('更新音频元数据到数据库失败');
+        
+        // 定义数据库更新回调
+        let updateCallback = (success: boolean) => {
+          try {
+            if (success) {
+              console.info('成功更新音频元数据到数据库');
+            } else {
+              console.error('更新音频元数据到数据库失败');
+            }
+          } catch (e) {
+            console.error(`数据库更新回调中出错: ${e}`);
           }
-        });
+        };
         
-        console.info(`刷新后的采样率: ${this.currentSong.sampleRate}, MIME类型: ${this.currentSong.mimeType}`);
+        // 使用try-catch块包裹数据库更新操作
+        try {
+          this.table.updateMediaMetadata(filePath, metadataToUpdate, updateCallback);
+          console.info(`刷新后的采样率: ${currentSong.sampleRate}, MIME类型: ${currentSong.mimeType}`);
+        } catch (dbErr) {
+          console.error(`更新数据库时出错: ${dbErr}`);
+        }
+      } catch (updateErr) {
+        console.error(`更新元数据字段时出错: ${updateErr}`);
       }
     } catch (err) {
       console.error(`刷新音频元数据失败: ${err}`);