Explorar el Código

修复上一首播放异常

chendeben hace 1 año
padre
commit
ddebc68f10

+ 38 - 0
entry/src/main/ets/common/service/UnifiedPlayerService.ets

@@ -273,6 +273,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
   private table: MediaTable | undefined = undefined;
   private lastAutoPlayTime: number = 0; // 新增:上次自动播放时间,用于防抖
   private autoPlayDebounceMs: number = 1000; // 自动播放防抖间隔(毫秒)
+  private isManualSongChange: boolean = false; // 新增:是否正在进行手动歌曲切换
 
   private constructor() {
     this.playerManager = PlayerManager.getInstance();
@@ -698,6 +699,9 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
   // 播放列表控制方法
   async playNext(): Promise<void> {
     try {
+      // 设置手动歌曲切换标志,防止自动播放干扰
+      this.isManualSongChange = true;
+      
       // 首先检查服务是否已准备就绪
       if (!this.isAllServicesReady()) {
         LogUtils.getInstance().LOGI('UnifiedPlayerService: playNext - 服务未就绪,等待服务初始化完成');
@@ -705,6 +709,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
         const isReady = await this.waitForAllServicesReady(500, false);
         if (!isReady) {
           LogUtils.getInstance().LOGI('UnifiedPlayerService: Services not ready for playNext, skipping');
+          this.isManualSongChange = false;
           return; // 直接返回,不抛出错误
         }
       }
@@ -712,10 +717,12 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
       const playMode = this.stateModel.getState().playMode;
 
       if (!this.playlistModel.hasNext(playMode)) {
+        this.isManualSongChange = false;
         return;
       }
       const moved = this.playlistModel.moveToNext(playMode);
       if (!moved) {
+        this.isManualSongChange = false;
         return;
       }
 
@@ -744,11 +751,19 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
 
     } catch (error) {
       await this.handlePlaybackError(error as Error, PlayerErrorType.PLAYBACK_ERROR);
+    } finally {
+      // 确保在任何情况下都清除手动切换标志
+      setTimeout(() => {
+        this.isManualSongChange = false;
+      }, 1000); // 延迟1秒清除,确保播放器状态稳定
     }
   }
 
   async playPrevious(): Promise<void> {
     try {
+      // 设置手动歌曲切换标志,防止自动播放干扰
+      this.isManualSongChange = true;
+      
       // 首先检查服务是否已准备就绪
       if (!this.isAllServicesReady()) {
         LogUtils.getInstance().LOGI('UnifiedPlayerService: playPrevious - 服务未就绪,等待服务初始化完成');
@@ -756,6 +771,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
         if (!isReady) {
           const error = new Error('Services not ready for playPrevious');
           await this.handlePlaybackError(error, PlayerErrorType.INITIALIZATION_ERROR);
+          this.isManualSongChange = false;
           return;
         }
       }
@@ -763,6 +779,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
       const playMode = this.stateModel.getState().playMode;
 
       if (!this.playlistModel.hasPrevious(playMode)) {
+        this.isManualSongChange = false;
         return;
       }
 
@@ -772,6 +789,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
 
       const moved = this.playlistModel.moveToPrevious(playMode);
       if (!moved) {
+        this.isManualSongChange = false;
         return;
       }
 
@@ -800,12 +818,21 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
 
     } catch (error) {
       await this.handlePlaybackError(error as Error, PlayerErrorType.PLAYBACK_ERROR);
+    } finally {
+      // 确保在任何情况下都清除手动切换标志
+      setTimeout(() => {
+        this.isManualSongChange = false;
+      }, 1000); // 延迟1秒清除,确保播放器状态稳定
     }
   }
   async playSongAtIndex(index: number): Promise<void> {
     try {
+      // 设置手动歌曲切换标志,防止自动播放干扰
+      this.isManualSongChange = true;
+      
       const success = this.playlistModel.playSongAtIndex(index);
       if (!success) {
+        this.isManualSongChange = false;
         throw new Error(`Invalid song index: ${index}`);
       }
 
@@ -826,6 +853,11 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
 
     } catch (error) {
       throw new Error;
+    } finally {
+      // 确保在任何情况下都清除手动切换标志
+      setTimeout(() => {
+        this.isManualSongChange = false;
+      }, 1000); // 延迟1秒清除,确保播放器状态稳定
     }
   }
 
@@ -2310,6 +2342,12 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
    */
   private async handleAutoPlayOnCompletion(): Promise<void> {
     try {
+      // 检查是否正在进行手动歌曲切换,如果是则跳过自动播放
+      if (this.isManualSongChange) {
+        LogUtils.getInstance().LOGI('UnifiedPlayerService: Manual song change in progress, skipping auto-play');
+        return;
+      }
+      
       // 防抖检查:避免快速连续的自动播放调用
       const currentTime = Date.now();
       if (currentTime - this.lastAutoPlayTime < this.autoPlayDebounceMs) {

+ 114 - 0
播放上一首问题修复.md

@@ -0,0 +1,114 @@
+# 播放上一首问题修复
+
+## 问题描述
+用户点击"播放上一首"按钮时,虽然播放列表显示有上一首歌曲(`hasNext=true, hasPrevious=true, totalCount=101`),但实际播放的却是下一首歌曲。
+
+## 问题分析
+
+### 日志分析
+从日志中可以看到以下异常流程:
+1. 用户点击"播放上一首"
+2. 系统正确识别有上一首歌曲可播放
+3. 但随即触发了 `Playback completed` 事件
+4. 这导致自动播放逻辑启动,播放了下一首歌曲而不是上一首
+
+### 根本原因
+问题出现在歌曲切换的时序上:
+
+1. `playPrevious()` 调用 `stopSilently()` 停止当前播放
+2. 然后调用 `playNewSongDirectly()` 开始播放新歌曲
+3. 但是停止播放器时触发的 `onPlaybackCompleted` 回调仍然会被执行
+4. 这个回调触发了自动播放逻辑 `handleAutoPlayOnCompletion()`
+5. 自动播放逻辑在顺序播放模式下会播放下一首歌曲
+6. 结果就是用户想要的上一首歌曲被自动播放的下一首歌曲覆盖了
+
+### 竞态条件
+这是一个典型的竞态条件问题:
+- 手动歌曲切换操作(playPrevious/playNext)
+- 自动播放逻辑(onPlaybackCompleted 触发的 handleAutoPlayOnCompletion)
+
+两者同时执行,导致预期外的行为。
+
+## 修复方案
+
+### 1. 添加手动切换标志
+在 `UnifiedPlayerService` 中添加一个标志来标识当前是否正在进行手动歌曲切换:
+
+```typescript
+private isManualSongChange: boolean = false; // 新增:是否正在进行手动歌曲切换
+```
+
+### 2. 在手动切换方法中设置标志
+在 `playNext()`、`playPrevious()` 和 `playSongAtIndex()` 方法中:
+
+```typescript
+async playPrevious(): Promise<void> {
+  try {
+    // 设置手动歌曲切换标志,防止自动播放干扰
+    this.isManualSongChange = true;
+    
+    // ... 原有逻辑 ...
+    
+  } catch (error) {
+    await this.handlePlaybackError(error as Error, PlayerErrorType.PLAYBACK_ERROR);
+  } finally {
+    // 确保在任何情况下都清除手动切换标志
+    setTimeout(() => {
+      this.isManualSongChange = false;
+    }, 1000); // 延迟1秒清除,确保播放器状态稳定
+  }
+}
+```
+
+### 3. 在自动播放逻辑中检查标志
+在 `handleAutoPlayOnCompletion()` 方法开头添加检查:
+
+```typescript
+private async handleAutoPlayOnCompletion(): Promise<void> {
+  try {
+    // 检查是否正在进行手动歌曲切换,如果是则跳过自动播放
+    if (this.isManualSongChange) {
+      LogUtils.getInstance().LOGI('UnifiedPlayerService: Manual song change in progress, skipping auto-play');
+      return;
+    }
+    
+    // ... 原有的自动播放逻辑 ...
+  }
+}
+```
+
+## 修复效果
+
+### 修复前的流程
+1. 用户点击"上一首" → `playPrevious()`
+2. `stopSilently()` 停止当前播放
+3. `playNewSongDirectly()` 开始播放上一首
+4. **问题**:`onPlaybackCompleted()` 被触发(来自刚停止的歌曲)
+5. `handleAutoPlayOnCompletion()` 执行自动播放逻辑
+6. 结果:播放了下一首歌曲(覆盖了上一首)
+
+### 修复后的流程
+1. 用户点击"上一首" → `playPrevious()`
+2. 设置 `isManualSongChange = true`
+3. `stopSilently()` 停止当前播放
+4. `playNewSongDirectly()` 开始播放上一首
+5. `onPlaybackCompleted()` 被触发(来自刚停止的歌曲)
+6. `handleAutoPlayOnCompletion()` 检查到 `isManualSongChange = true`,跳过自动播放
+7. 1秒后清除 `isManualSongChange` 标志
+8. 结果:正确播放上一首歌曲
+
+## 相关方法
+- `playNext()` - 播放下一首
+- `playPrevious()` - 播放上一首  
+- `playSongAtIndex()` - 播放指定索引歌曲
+- `handleAutoPlayOnCompletion()` - 自动播放逻辑
+
+## 测试验证
+修复后需要验证:
+1. 点击"上一首"按钮能正确播放上一首歌曲
+2. 点击"下一首"按钮能正确播放下一首歌曲
+3. 歌曲自然播放完成后的自动播放功能仍然正常工作
+4. 各种播放模式(顺序、随机、单曲循环等)下的切换都正常
+
+## 相关文件
+- `entry/src/main/ets/common/service/UnifiedPlayerService.ets`