Просмотр исходного кода

修复竞争操作导致的播放状态不同步

chendeben 11 месяцев назад
Родитель
Сommit
dda40c5c1e

+ 28 - 19
entry/src/main/ets/common/service/UnifiedPlayerService.ets

@@ -490,6 +490,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
   private table: MediaTable | undefined = undefined;
   private lastAutoPlayTime: number = 0; // 新增:上次自动播放时间,用于防抖
   private autoPlayDebounceMs: number = 1000; // 自动播放防抖间隔(毫秒)
+  private isPausingInProgress: boolean = false; // 新增:标记是否正在执行暂停操作
   private isManualSongChange: boolean = false; // 新增:是否正在进行手动歌曲切换
 
   // 图片缓存相关属性
@@ -986,14 +987,8 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
 
   async pause(): Promise<void> {
     try {
-      // 检查服务是否已准备就绪
-      if (!this.isAllServicesReady()) {
-        LogUtils.getInstance().LOGI('Heanup UnifiedPlayerService: pause - 服务未就绪');
-        // 对于暂停操作,如果服务未就绪,可以尝试简单的状态更新
-        this.stateModel.updatePlayingState(false);
-        return;
-      }
-
+      // 设置暂停进行中标志,避免状态不匹配误判
+      this.isPausingInProgress = true;
 
       // 保存当前播放位置
       const currentSong = this.playlistModel.getCurrentSong();
@@ -1011,14 +1006,15 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
       this.stopProgressTimer();
 
       // 等待一小段时间确保播放器状态稳定
-      await new Promise<void>(resolve => setTimeout(resolve, 50));
+      // await new Promise<void>(resolve => setTimeout(resolve, 50));
+
+      // 强制更新AVSession状态,确保播控中心显示正确的暂停状态
+      // 注意:在保存状态之前更新AVSession,避免状态不匹配检测的干扰
+      this.updateSessionPlayState(true);
 
       // 保存播放状态变化
       this.saveCurrentState();
 
-
-      this.updateSessionPlayState();
-
       // 更新卡片显示暂停状态
       await this.updateWidgetsForPlayStateChange();
 
@@ -1027,6 +1023,9 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
 
     } catch (error) {
       LogUtils.getInstance().LOGI(`Heanup UnifiedPlayerService pause error: ${error}`);
+    } finally {
+      // 清除暂停进行中标志
+      this.isPausingInProgress = false;
     }
   }
 
@@ -2556,13 +2555,17 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
         const actualPlaying = ijkPlayer ? ijkPlayer.isPlaying() : false;
 
         // 如果状态不一致,以实际播放器状态为准
-        if (currentState.isPlaying !== actualPlaying) {
+        // 但在暂停操作期间跳过检测,避免误判
+        if (currentState.isPlaying !== actualPlaying && !this.isPausingInProgress) {
           LogUtils.getInstance().LOGI(`Heanup UnifiedPlayerService: State mismatch detected during save - StateModel: ${currentState.isPlaying}, Actual: ${actualPlaying}`);
           this.stateModel.updatePlayingState(actualPlaying);
           // 重新获取修正后的状态
           const correctedState = this.stateModel.getState();
           this.dataPersistence.savePlayerState(correctedState);
         } else {
+          if (this.isPausingInProgress && currentState.isPlaying !== actualPlaying) {
+            LogUtils.getInstance().LOGI(`Heanup UnifiedPlayerService: Skipping state mismatch check during pause operation - StateModel: ${currentState.isPlaying}, Actual: ${actualPlaying}`);
+          }
           this.dataPersistence.savePlayerState(currentState);
         }
       } catch (playerError) {
@@ -3910,29 +3913,31 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
     }
 
     if (force) {
-      // 强制更新,立即执行
-      this.doUpdateSessionPlayState();
+      // 强制更新,立即执行,绕过频率限制
+      this.doUpdateSessionPlayState(true);
     } else {
       // 增加防抖延迟到500ms,减少频繁更新
       this.avSessionUpdateTimer = setTimeout(() => {
-        this.doUpdateSessionPlayState();
+        this.doUpdateSessionPlayState(false);
       }, 500);
     }
   }
 
   /**
    * 实际执行AVSession播放状态更新
+   * @param force 是否强制更新,绕过频率限制
    */
-  private doUpdateSessionPlayState(): void {
+  private doUpdateSessionPlayState(force: boolean = false): void {
     try {
       if (!this.avSessionController) {
         LogUtils.getInstance().LOGI('Heanup UnifiedPlayerService: AVSession controller not initialized');
         return;
       }
 
-      // 避免过于频繁的更新(最小间隔1000ms,增加防抖时间)
+      // 避免过于频繁的更新(最小间隔300ms,保证关键操作响应性)
+      // 强制更新时绕过频率限制
       const now = Date.now();
-      if (now - this.lastAvSessionUpdate < 1000) {
+      if (!force && now - this.lastAvSessionUpdate < 300) {
         LogUtils.getInstance().LOGI('Heanup UnifiedPlayerService: Skipping AVSession update due to rate limit');
         return;
       }
@@ -3991,6 +3996,10 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
 
       // 记录上次更新时间
       this.lastAvSessionUpdate = now;
+      
+      // 添加调试日志,便于问题排查
+      const updateType = force ? "FORCED" : "NORMAL";
+      LogUtils.getInstance().LOGI(`Heanup UnifiedPlayerService: AVSession state updated (${updateType}) - isPlaying: ${currentState.isPlaying}, position: ${Math.max(0, currentPosition)}ms, duration: ${duration}ms`);
     } catch (error) {
       LogUtils.getInstance().LOGI(`Heanup UnifiedPlayerService: Failed to update AVSession play state: ${error}`);
     }

+ 67 - 104
entry/src/main/ets/entryability/EntryAbility.ets

@@ -110,11 +110,11 @@ export default class EntryAbility extends UIAbility {
   private awareness: smartMobilityCommon.SmartMobilityAwareness | undefined =
     canIUse("SystemCapability.SmartOptimizer.SmartMobility") ? smartMobilityCommon.getSmartMobilityAwareness() :
       undefined;
-
   // 服务就绪状态缓存,避免在 widget 控制事件中重复检查
   private isServiceFullyReady: boolean = false;
   private lastServiceReadyCheckTime: number = 0;
-  private readonly SERVICE_READY_CACHE_DURATION = 60000; // 缓存有效期60秒
+  private readonly SERVICE_READY_CACHE_DURATION = 60000;
+  // 缓存有效期60秒
   /**
    * 窗口尺寸变化回调函数
    * @param windowSize 新的窗口尺寸对象
@@ -155,7 +155,7 @@ export default class EntryAbility extends UIAbility {
     }
 
   }
-  private unifiedService: UnifiedPlayerService=UnifiedPlayerService.getInstance();
+  private unifiedService: UnifiedPlayerService = UnifiedPlayerService.getInstance();
 
   async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
     AppUtil.init(this.context);
@@ -251,32 +251,22 @@ export default class EntryAbility extends UIAbility {
           hilog.error(0x0000, 'Heanup2', `Failed to release UnifiedPlayerService: ${error}`);
         });
       }
-    } catch (error) {
-      hilog.error(0x0000, 'Heanup2', `Failed to release UnifiedPlayerService: ${error}`);
-    }
-
-    // 清理所有Form ID(应用卸载时)
-    try {
+      // 清理所有Form ID(应用卸载时)
       this.clearAllFormIds();
+      // 注销卡片call事件监听器
+      this.unregisterWidgetCallListeners();
+      hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
+      if (this.awareness) {
+        let types: smartMobilityCommon.SmartMobilityType[] = [smartMobilityCommon.SmartMobilityType.CAR_HOP];
+        // 出行连接状态回调函数
+        const callBack = (info: smartMobilityCommon.SmartMobilityInfo) => {
+          hilog.info(0x0000, 'Received smart mobility info: ', JSON.stringify(info));
+        };
+        // 解注册智慧出行连接状态的监听 示例2
+        canIUse("SystemCapability.SmartOptimizer.SmartMobility") ?this.awareness.off('smartMobilityStatus', types, callBack): hilog.info(0x0000, 'Received smart mobility info: ', 'can not use smart mobility');
+      }
     } catch (error) {
-      hilog.error(0x0000, 'Heanup2', `Failed to clear form IDs: ${error}`);
-    }
-
-    // 注销卡片call事件监听器
-    this.unregisterWidgetCallListeners();
-
-    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
-    if (this.awareness) {
-      let types: smartMobilityCommon.SmartMobilityType[] = [smartMobilityCommon.SmartMobilityType.CAR_HOP];
-      // 出行连接状态回调函数
-      const callBack = (info: smartMobilityCommon.SmartMobilityInfo) => {
-        hilog.info(0x0000, 'Received smart mobility info: ', JSON.stringify(info));
-      };
-      // 解注册智慧出行连接状态的监听 示例2
-      this.awareness.off('smartMobilityStatus', types, callBack);
-            }
-        }catch (error) {
-            console.error(`Failed to getHiCarStatus: ${error.code}, message: ${error.message}`);
+      console.error(`Failed to getHiCarStatus: ${error.code}, message: ${error.message}`);
 
     }
   }
@@ -389,53 +379,55 @@ export default class EntryAbility extends UIAbility {
     hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onBackground end');
   }
 
-    getHiCarStatus(){
-        try {
-            if (!this.awareness){
-                this.awareness=canIUse("SystemCapability.CarService.DistributedEngine")?smartMobilityCommon.getSmartMobilityAwareness():undefined;
-            }
-            console.log('enter getHiCarStatus,awareness:'+JSON.stringify(this.awareness));
-            if(this.awareness){
-
-
-                console.log('enter awareness');
-                // 业务类型
-                let types: smartMobilityCommon.SmartMobilityType[] = [smartMobilityCommon.SmartMobilityType.HICAR];
-                // 获取出行业务连接状态
-                let info = this.awareness.getSmartMobilityStatus(types[0]);
-
-                hilog.info(0x0000, 'getHiCarStatus  info: ', JSON.stringify(info));
-                if(info&&info.status==1){
-                    AppStorage.setOrCreate('isHiCarStatus', true);
-                }else{
-                    AppStorage.setOrCreate('isHiCarStatus', false);
-                }
-
-                // 出行连接状态回调函数
-                const callBack = (info: smartMobilityCommon.SmartMobilityInfo) => {
-                    hilog.info(0x0000, 'getHiCarStatus Received smart mobility info: ', JSON.stringify(info));
-                    if(info&&info.status==1){
-                        AppStorage.setOrCreate('isHiCarStatus', true);
-                    }else{
-                        AppStorage.setOrCreate('isHiCarStatus', false);
-                    }
-                    this.sendChangeEvent()
-                };
-                // 注册智慧出行连接状态的监听
-                this.awareness.on('smartMobilityStatus', types, callBack);
-
-
-            }else{
-                AppStorage.setOrCreate('isHiCarStatus', false);
-            }
-        }catch (error) {
-            console.error(`Failed to getHiCarStatus: ${error.code}, message: ${error.message}`);
+  getHiCarStatus() {
+    try {
+      if (!this.awareness) {
+        this.awareness =
+          canIUse("SystemCapability.CarService.DistributedEngine") ? smartMobilityCommon.getSmartMobilityAwareness() :
+            undefined;
+      }
+      console.log('enter getHiCarStatus,awareness:' + JSON.stringify(this.awareness));
+      if (this.awareness) {
+
 
+        console.log('enter awareness');
+        // 业务类型
+        let types: smartMobilityCommon.SmartMobilityType[] = [smartMobilityCommon.SmartMobilityType.HICAR];
+        // 获取出行业务连接状态
+        let info = this.awareness.getSmartMobilityStatus(types[0]);
+
+        hilog.info(0x0000, 'getHiCarStatus  info: ', JSON.stringify(info));
+        if (info && info.status == 1) {
+          AppStorage.setOrCreate('isHiCarStatus', true);
+        } else {
+          AppStorage.setOrCreate('isHiCarStatus', false);
         }
 
+        // 出行连接状态回调函数
+        const callBack = (info: smartMobilityCommon.SmartMobilityInfo) => {
+          hilog.info(0x0000, 'getHiCarStatus Received smart mobility info: ', JSON.stringify(info));
+          if (info && info.status == 1) {
+            AppStorage.setOrCreate('isHiCarStatus', true);
+          } else {
+            AppStorage.setOrCreate('isHiCarStatus', false);
+          }
+          this.sendChangeEvent()
+        };
+        // 注册智慧出行连接状态的监听
+        this.awareness.on('smartMobilityStatus', types, callBack);
+
+
+      } else {
+        AppStorage.setOrCreate('isHiCarStatus', false);
+      }
+    } catch (error) {
+      console.error(`Failed to getHiCarStatus: ${error.code}, message: ${error.message}`);
 
     }
 
+
+  }
+
   //发送广播通知更新UI
   sendChangeEvent() {
     const eventData: emitter.EventData = {};
@@ -651,12 +643,14 @@ export default class EntryAbility extends UIAbility {
           const widgetIsPlaying = params['widgetIsPlaying'] as boolean;
           if (widgetIsPlaying !== undefined) {
             hilog.info(0x0000, 'Heanup2', `🎵 基于widget状态执行播放控制: ${widgetIsPlaying ? '暂停' : '播放'}`);
-            commandPromise = widgetIsPlaying ? this.unifiedService.pause() : this.unifiedService.startPlayOrResumePlay();
+            commandPromise =
+              widgetIsPlaying ? this.unifiedService.pause() : this.unifiedService.startPlayOrResumePlay();
           } else {
             // 回退到状态检查
             const currentState = this.unifiedService.getCurrentState();
             hilog.info(0x0000, 'Heanup2', `🎵 基于播放器状态执行播放控制: ${currentState.isPlaying ? '暂停' : '播放'}`);
-            commandPromise = currentState.isPlaying ? this.unifiedService.pause() : this.unifiedService.startPlayOrResumePlay();
+            commandPromise =
+              currentState.isPlaying ? this.unifiedService.pause() : this.unifiedService.startPlayOrResumePlay();
           }
           break;
 
@@ -714,7 +708,7 @@ export default class EntryAbility extends UIAbility {
 
       // 如果缓存仍然有效,直接返回缓存结果
       if (this.isServiceFullyReady &&
-          (currentTime - this.lastServiceReadyCheckTime) < this.SERVICE_READY_CACHE_DURATION) {
+        (currentTime - this.lastServiceReadyCheckTime) < this.SERVICE_READY_CACHE_DURATION) {
         hilog.info(0x0000, 'Heanup2', '✅ 使用服务就绪状态缓存');
         return true;
       }
@@ -763,38 +757,6 @@ export default class EntryAbility extends UIAbility {
     }
   }
 
-  /**
-   * 强制数据恢复(用于桌面卡片冷启动场景)
-   */
-  private async forceDataRestoration(unifiedService: UnifiedPlayerService): Promise<void> {
-    try {
-      hilog.info(0x0000, 'Heanup2', '🔄 开始强制数据恢复...');
-
-      // 检查是否已经有数据恢复完成
-      if (unifiedService.isDataRestorationCompleted()) {
-        hilog.info(0x0000, 'Heanup2', '✅ 数据已恢复,无需强制恢复');
-        return;
-      }
-
-      // 调用UnifiedPlayerService的强制数据恢复方法
-      const restored = await unifiedService.forceDataRestoration();
-
-      if (restored) {
-        hilog.info(0x0000, 'Heanup2', '✅ 强制数据恢复成功');
-      } else {
-        hilog.warn(0x0000, 'Heanup2', '⚠️ 强制数据恢复失败,尝试等待');
-        // 如果强制恢复失败,再等待一段时间
-        await unifiedService.waitForDataRestoration(2000);
-      }
-
-      // 额外等待一小段时间,确保播放列表数据完全加载
-      await new Promise<void>(resolve => setTimeout(resolve, 300));
-
-    } catch (error) {
-      hilog.error(0x0000, 'Heanup2', `❌ 强制数据恢复失败: ${error}`);
-    }
-  }
-
 
   /**
    * 异步初始化播放器服务,避免阻塞生命周期
@@ -841,7 +803,8 @@ export default class EntryAbility extends UIAbility {
       // 但要避免在应用销毁时重复保存,因为onDestroy中已经保存过了
       try {
         const unifiedPlayerService = UnifiedPlayerService.getInstance();
-        if (unifiedPlayerService && unifiedPlayerService.isAllServicesReady() && unifiedPlayerService.isServiceInitialized()) {
+        if (unifiedPlayerService && unifiedPlayerService.isAllServicesReady() &&
+        unifiedPlayerService.isServiceInitialized()) {
           // 检查服务是否已经被释放,如果已释放说明是应用销毁流程,不需要再保存
           // 保存当前状态,确保即使应用被强制杀死也能保存正确状态
           await unifiedPlayerService.saveCurrentStateExternal();

+ 86 - 20
entry/src/main/ets/view/LocalMusic.ets

@@ -10966,6 +10966,54 @@ export struct LocalMusic {
     }
   }
 
+  /**
+   * 带状态同步的开始/恢复播放方法
+   * 确保AVSession状态正确同步
+   */
+  private async startPlayOrResumePlayWithStateSync() {
+    await this.startPlayOrResumePlay();
+    this.playChange();
+    
+    // 等待一小段时间确保UnifiedPlayerService完成AVSession状态更新
+    setTimeout(() => {
+      LogUtils.getInstance().LOGI('LocalMusic: startPlayOrResumePlayWithStateSync completed with AVSession sync');
+    }, 100);
+  }
+
+  /**
+   * 带状态同步的暂停播放方法
+   * 确保AVSession状态正确同步
+   */
+  private async pauseWithStateSync() {
+    try {
+      // 保存播放位置
+      this.savePlaybackPosition();
+      
+      // 使用UnifiedPlayerService暂停播放
+      await this.unifiedPlayerService.pause();
+      
+      // 更新本地UI状态
+      this.setProgress();
+      this.mDestroyPage = true;
+      this.CONTROL_PlayStatus = PlayStatus.PAUSE;
+      this.setIsPlaying(false);
+      
+      // 更新UI状态
+      this.playChange();
+      
+      // 更新PiP控制状态
+      if (this.pipController) {
+        this.pipController.updatePiPControlStatus(PiPWindow.PiPControlType.VIDEO_PLAY_PAUSE,
+          PiPWindow.PiPControlStatus.PAUSE);
+      }
+      
+      LogUtils.getInstance().LOGI('LocalMusic: pauseWithStateSync completed with AVSession sync');
+    } catch (error) {
+      LogUtils.getInstance().LOGI(`pauseWithStateSync error: ${error}`);
+      throw new Error(`pauseWithStateSync error: ${error}`);
+    }
+  }
+
   // 播放错误处理
   private handlePlaybackError() {
     this.CONTROL_PlayStatus = PlayStatus.INIT;
@@ -12003,13 +12051,19 @@ export struct LocalMusic {
       return;
     }
 
-    // 本地播放控制
-    if (this.CONTROL_PlayStatus === PlayStatus.PLAY) {
-      this.pause();
-    } else {
-      console.info('onecold startPlayOrResumePlay 11064')
-      this.startPlayOrResumePlay();
-      this.playChange()
+    // 本地播放控制 - 修复:确保AVSession状态同步
+    try {
+      if (this.CONTROL_PlayStatus === PlayStatus.PLAY) {
+        // 暂停播放
+        await this.pauseWithStateSync();
+      } else {
+        // 开始/恢复播放
+        console.info('onecold startPlayOrResumePlay 11064')
+        await this.startPlayOrResumePlayWithStateSync();
+      }
+    } catch (error) {
+      LogUtils.getInstance().LOGI(`playOrPause error: ${error}`);
+      ToastUtil.showToast(`操作失败: ${error}`);
     }
   }
 
@@ -12189,20 +12243,32 @@ export struct LocalMusic {
   }
 
   private async pause() {
-
-    if (this.unifiedPlayerService.getIjkPlayer()?.isPlaying()){
-      this.savePlaybackPosition();
-      this.unifiedPlayerService.pause();
-      this.setProgress();
-      this.mDestroyPage = true;
-      this.CONTROL_PlayStatus = PlayStatus.PAUSE;
-      // 注意:AVSession状态更新由UnifiedPlayerService统一处理,避免冲突
-      this.playChange()
-      if (this.pipController) {
-        this.pipController.updatePiPControlStatus(PiPWindow.PiPControlType.VIDEO_PLAY_PAUSE,
-          PiPWindow.PiPControlStatus.PAUSE);
+    try {
+      if (this.unifiedPlayerService.getIjkPlayer()?.isPlaying()){
+        this.savePlaybackPosition();
+        
+        // 使用UnifiedPlayerService暂停播放(确保AVSession状态同步)
+        await this.unifiedPlayerService.pause();
+        
+        this.setProgress();
+        this.mDestroyPage = true;
+        this.CONTROL_PlayStatus = PlayStatus.PAUSE;
+        this.setIsPlaying(false);
+        
+        // 更新UI状态
+        this.playChange()
+        
+        if (this.pipController) {
+          this.pipController.updatePiPControlStatus(PiPWindow.PiPControlType.VIDEO_PLAY_PAUSE,
+            PiPWindow.PiPControlStatus.PAUSE);
+        }
+        
+        LogUtils.getInstance().LOGI('LocalMusic: pause completed with AVSession sync');
       }
+    } catch (error) {
+      LogUtils.getInstance().LOGI(`pause error: ${error}`);
     }
+  }
 
     // try {
     //   // 使用UnifiedPlayerService暂停播放
@@ -12226,7 +12292,7 @@ export struct LocalMusic {
     //   LogUtils.getInstance().LOGI(`Heanup LocalMusic pause error: ${error}`);
     //   ToastUtil.showToast(`暂停失败: ${error}`);
     // }
-  }
+
 
   private async stop() {
     try {

+ 8 - 0
oh-package-lock.json5

@@ -10,6 +10,7 @@
     "@changwei/chardet@^1.0.0": "@changwei/chardet@1.0.0",
     "@chinalike/popup@^0.0.7": "@chinalike/popup@0.0.7",
     "@keke/color-picker@^1.0.4": "@keke/color-picker@1.0.4",
+    "@mcui/mccharts@^2.8.9": "@mcui/mccharts@2.8.9",
     "@ohos/juniversalchardet@^2.0.2": "@ohos/juniversalchardet@2.0.2",
     "@ohos/lottie@^2.0.23": "@ohos/lottie@2.0.23",
     "@ohos/pinyin4js@^2.0.2": "@ohos/pinyin4js@2.0.2",
@@ -68,6 +69,13 @@
       "resolved": "https://repo.harmonyos.com/ohpm/@keke/color-picker/-/color-picker-1.0.4.har",
       "registryType": "ohpm"
     },
+    "@mcui/mccharts@2.8.9": {
+      "name": "@mcui/mccharts",
+      "version": "2.8.9",
+      "integrity": "sha512-mdpzc6TlYlR/xV+OH3u0Jq5s1UeND8EKQuyXUdtjPMozffrbitPWJcp5N4Fji9v1xs8d/WP62OKnbp79iWMtHA==",
+      "resolved": "https://ohpm.openharmony.cn/ohpm/@mcui/mccharts/-/mccharts-2.8.9.har",
+      "registryType": "ohpm"
+    },
     "@ohos/juniversalchardet@2.0.2": {
       "name": "@ohos/juniversalchardet",
       "version": "2.0.2",

+ 2 - 1
oh-package.json5

@@ -23,7 +23,8 @@
     "@ohos/pinyin4js": "^2.0.2",
     "@ohos/juniversalchardet": "^2.0.2",
     "@sj/ffmpeg": "^1.2.5",
-    "@ohos/lottie": "^2.0.23"
+    "@ohos/lottie": "^2.0.23",
+    "@mcui/mccharts": "^2.8.9"
   },
   "dynamicDependencies": {}
 }