Browse Source

```
fix(cast): 修复投屏播放控制和进度同步问题

- 移除AVSession中的DRM方案配置,简化媒体元数据
- 添加isSwitchingTrack和hasHandledEnd状态标识,优化播放切换逻辑
- 为initAVCast、playNext和playPrevious方法添加duration参数支持
- 实现shouldAutoPlayNextOnStop方法,处理播放停止时的自动下一首逻辑
- 添加seekDone事件监听,实现进度调节完成后的位置更新
- 在LocalMusic页面使用getActiveDuration方法统一获取音频时长
- 修复投屏模式下的进度条拖拽计算问题,替换getDuration为getActiveDuration
- 添加updateCastProgress方法,实现投屏播放时的进度同步更新
- 修复播放暂停和恢复时的状态更新,确保UI状态正确显示
```

chendeben 7 months ago
parent
commit
3c18822609

+ 2 - 4
entry/src/main/ets/controller/AvSessionController.ets

@@ -114,9 +114,7 @@ export class AvSessionController {
       title: curSource.name,
       filter: avSession.ProtocolType.TYPE_CAST_PLUS_STREAM|avSession.ProtocolType.TYPE_DLNA,
       mediaImage: imagePixMap,
-      duration: duration,
-      // 如果是DRM资源,配置支持的DRM uuid 用于设备过滤。非DRM资源不配置。
-      drmSchemes: ['3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c']
+      duration: duration
     };
     if (this.avSession) {
       this.avSession.setAVMetadata(metadata).then(() => {
@@ -203,4 +201,4 @@ export class AvSessionController {
     this.avSession.destroy()
     BackgroundTaskManager.stopContinuousTask(this.context);
   }
-}
+}

+ 59 - 20
entry/src/main/ets/controller/CastController.ets

@@ -56,6 +56,8 @@ export class CastController {
 
   // 文件描述符引用,避免被垃圾回收
   private castFile: fileIo.File | undefined = undefined;
+  private isSwitchingTrack: boolean = false;
+  private hasHandledEnd: boolean = false;
 
   // 回调函数
   private onPlayNext: (() => void) | undefined = undefined;
@@ -89,7 +91,8 @@ export class CastController {
     songList: VideoItem[],
     musicIndex: number,
     startPosition: number,
-    videoUrl: string
+    videoUrl: string,
+    duration?: number
   ): Promise<void> {
    console.info( TAG, '========== initAVCast 开始 ==========');
    console.info( TAG, `initAVCast 参数: songList.length=${songList.length}, musicIndex=${musicIndex}, startPosition=${startPosition}, videoUrl=${videoUrl}`);
@@ -100,7 +103,7 @@ export class CastController {
    console.info( TAG, '✅ 更新内部状态完成');
 
     try {
-      await this.setCastResource(startPosition, videoUrl);
+      await this.setCastResource(startPosition, videoUrl, duration);
      console.info( TAG, '✅ setCastResource 完成');
     } catch (error) {
       let errorMsg = error instanceof Error ? error.message : String(error);
@@ -129,7 +132,7 @@ export class CastController {
    * @param startPosition 开始播放位置(毫秒)
    * @param videoUrl 视频URL
    */
-  public async setCastResource(startPosition: number, videoUrl: string): Promise<void> {
+  public async setCastResource(startPosition: number, videoUrl: string, duration?: number): Promise<void> {
    console.info( TAG, '========== setCastResource 开始 ==========');
 
     if (!this.avCastController || !this.context) {
@@ -166,7 +169,7 @@ export class CastController {
         artist: songItem.artist || '',
         mediaType: 'AUDIO',
         startPosition: startPosition,
-        duration: songItem.videoSize || 0,
+        duration: duration || 0,
       };
      console.info( TAG, '✅ 创建 AVMediaDescription 成功');
 
@@ -204,6 +207,8 @@ export class CastController {
 
       // 设置投播状态为 true
       this.isCastPlaying = true;
+      this.isSwitchingTrack = false;
+      this.hasHandledEnd = false;
       console.info(TAG, '✅✅✅ 投播启动成功,isCastPlaying=true ✅✅✅');
 
     } catch (err) {
@@ -253,6 +258,11 @@ export class CastController {
           } else if (playbackState.state === avSession.PlaybackState.PLAYBACK_STATE_STOP) {
            console.info( TAG, '[state回调] 停止状态,保持 isCastPlaying=' + this.isCastPlaying);
             // STOP 状态不改变 isCastPlaying,确保播放完成时继续播放下一首
+            if (this.shouldAutoPlayNextOnStop()) {
+             console.info(TAG, '[state回调] STOP 且接近播放结束,触发下一首');
+              this.hasHandledEnd = true;
+              this.onPlayNext?.();
+            }
           }
         }
       });
@@ -281,18 +291,26 @@ export class CastController {
         }
       });
 
-      // 4. 监听所有状态变化(调试用)
+      // 4. 监听进度调节完成事件
+      this.avCastController?.on('seekDone', (position: number) => {
+        this.elapsedTime = position;
+       console.info( TAG, `[seekDone回调] position=${position}ms`);
+        this.onPositionChanged?.(this.elapsedTime);
+      });
+
+      // 5. 监听所有状态变化(调试用)
       this.avCastController?.on('playbackStateChange', 'all', (playbackState: avSession.AVPlaybackState) => {
        console.info( TAG, `[all回调] 完整状态: ${JSON.stringify(playbackState)}`);
       });
 
-      // 5. 监听播放完成事件 ⭐ 关键
+      // 6. 监听播放完成事件 ⭐ 关键
       this.avCastController?.on('endOfStream', () => {
        console.info( TAG, '⭐⭐⭐ [endOfStream回调] 播放完成,触发下一首 ⭐⭐⭐');
         console.info( TAG, `[endOfStream回调] 当前 isCastPlaying=${this.isCastPlaying}`);
         // 确保 isCastPlaying 在播放完成时保持为 true,以便下一首能继续投播
         this.isCastPlaying = true;
         console.info( TAG, `[endOfStream回调] 强制设置 isCastPlaying=true`);
+        this.hasHandledEnd = true;
         this.onPlayNext?.();
       });
 
@@ -402,20 +420,25 @@ export class CastController {
    * @param musicIndex 最新的歌曲索引
    * @param videoUrl 最新的视频URL
    */
-  public async playNext(songList: VideoItem[], musicIndex: number, videoUrl: string): Promise<void> {
+  public async playNext(songList: VideoItem[], musicIndex: number, videoUrl: string, duration?: number): Promise<void> {
    console.info( TAG, `playNext: 传入索引=${musicIndex}, 当前内部索引=${this.musicIndex}`);
 
     // 更新内部状态
     this.songList = songList;
     this.musicIndex = musicIndex;
+    this.isSwitchingTrack = true;
 
     // 先停止当前播放
-    await this.setStop();
-   console.info( TAG, 'playNext: stop 成功');
-
-    // 准备并播放下一首
-    await this.setCastResource(0, videoUrl);
-   console.info( TAG, 'playNext: prepare 和 start 完成');
+    try {
+      await this.setStop();
+     console.info( TAG, 'playNext: stop 成功');
+
+      // 准备并播放下一首
+      await this.setCastResource(0, videoUrl, duration);
+     console.info( TAG, 'playNext: prepare 和 start 完成');
+    } finally {
+      this.isSwitchingTrack = false;
+    }
   }
 
   /**
@@ -424,20 +447,25 @@ export class CastController {
    * @param musicIndex 最新的歌曲索引
    * @param videoUrl 最新的视频URL
    */
-  public async playPrevious(songList: VideoItem[], musicIndex: number, videoUrl: string): Promise<void> {
+  public async playPrevious(songList: VideoItem[], musicIndex: number, videoUrl: string, duration?: number): Promise<void> {
    console.info( TAG, `playPrevious: 传入索引=${musicIndex}, 当前内部索引=${this.musicIndex}`);
 
     // 更新内部状态
     this.songList = songList;
     this.musicIndex = musicIndex;
+    this.isSwitchingTrack = true;
 
     // 先停止当前播放
-    await this.setStop();
-   console.info( TAG, 'playPrevious: stop 成功');
-
-    // 准备并播放上一首
-    await this.setCastResource(0, videoUrl);
-   console.info( TAG, 'playPrevious: prepare 和 start 完成');
+    try {
+      await this.setStop();
+     console.info( TAG, 'playPrevious: stop 成功');
+
+      // 准备并播放上一首
+      await this.setCastResource(0, videoUrl, duration);
+     console.info( TAG, 'playPrevious: prepare 和 start 完成');
+    } finally {
+      this.isSwitchingTrack = false;
+    }
   }
 
   /**
@@ -454,6 +482,16 @@ export class CastController {
     return this.isCastPlaying;
   }
 
+  private shouldAutoPlayNextOnStop(): boolean {
+    if (this.isSwitchingTrack || this.hasHandledEnd) {
+      return false;
+    }
+    if (this.duration <= 0 || this.elapsedTime <= 0) {
+      return false;
+    }
+    return (this.duration - this.elapsedTime) <= 1500;
+  }
+
   /**
    * 释放投播控制器资源
    */
@@ -486,6 +524,7 @@ export class CastController {
   public unregisterCastListener(): void {
     try {
       this.avCastController?.off('playbackStateChange');
+      this.avCastController?.off('seekDone');
       this.avCastController?.off('playNext');
       this.avCastController?.off('playPrevious');
       this.avCastController?.off('endOfStream');

+ 75 - 11
entry/src/main/ets/view/LocalMusic.ets

@@ -3976,7 +3976,7 @@ export struct LocalMusic {
               this.mDestroyPage = false;
               this.showLoadIng();
               LogUtils.getInstance().LOGI("slider-->seekValue start:" + value);
-              let seekValue = value * (this.mIjkMediaPlayer.getDuration() / 100);
+              let seekValue = value * (this.getActiveDuration() / 100);
               this.seekTo(seekValue + "");
               LogUtils.getInstance().LOGI("slider-->seekValue end:" + seekValue);
               this.isSeekTo = false;
@@ -4040,7 +4040,7 @@ export struct LocalMusic {
               this.isSeekTo = true;
               this.mDestroyPage = false;
               this.showLoadIng();
-              let seekValue = value * (this.mIjkMediaPlayer.getDuration() / 100);
+              let seekValue = value * (this.getActiveDuration() / 100);
               this.seekTo(seekValue + "");
               this.isSeekTo = false;
 
@@ -4102,7 +4102,7 @@ export struct LocalMusic {
               this.isSeekTo = true;
               this.mDestroyPage = false;
               this.showLoadIng();
-              let seekValue = value * (this.mIjkMediaPlayer.getDuration() / 100);
+              let seekValue = value * (this.getActiveDuration() / 100);
               this.seekTo(seekValue + "");
               this.isSeekTo = false;
 
@@ -4170,7 +4170,7 @@ export struct LocalMusic {
               this.isSeekTo = true;
               this.mDestroyPage = false;
               this.showLoadIng();
-              let seekValue = value * (this.mIjkMediaPlayer.getDuration() / 100);
+              let seekValue = value * (this.getActiveDuration() / 100);
               this.seekTo(seekValue + "");
               this.isSeekTo = false;
 
@@ -4232,7 +4232,7 @@ export struct LocalMusic {
               this.isSeekTo = true;
               this.mDestroyPage = false;
               this.showLoadIng();
-              let seekValue = value * (this.mIjkMediaPlayer.getDuration() / 100);
+              let seekValue = value * (this.getActiveDuration() / 100);
               this.seekTo(seekValue + "");
               this.isSeekTo = false;
 
@@ -4295,7 +4295,7 @@ export struct LocalMusic {
               this.isSeekTo = true;
               this.mDestroyPage = false;
               this.showLoadIng();
-              let seekValue = value * (this.mIjkMediaPlayer.getDuration() / 100);
+              let seekValue = value * (this.getActiveDuration() / 100);
               this.seekTo(seekValue + "");
               this.isSeekTo = false;
 
@@ -10678,7 +10678,7 @@ export struct LocalMusic {
             if (this.currentSong && isRemoteCloudType(this.currentSong.type)) {
               this.showLoadIng();
             }
-            let seekValue = value * (this.mIjkMediaPlayer.getDuration() / 100);
+            let seekValue = value * (this.getActiveDuration() / 100);
             console.info("onecold this.currentSong.type:"+this.currentSong?.type)
             this.seekTo(seekValue + "");
           })
@@ -10958,7 +10958,7 @@ export struct LocalMusic {
             console.info("onecold 开始转圈圈")
             this.showLoadIng();
           }
-          let seekValue = value * (this.mIjkMediaPlayer.getDuration() / 100);
+          let seekValue = value * (this.getActiveDuration() / 100);
           this.seekTo(seekValue + "");
         })
       Text(this.totalTime)
@@ -13409,6 +13409,9 @@ export struct LocalMusic {
   }
 
   private setProgress() {
+    if (this.castControllerWrapper && this.isCastPlaying) {
+      return;
+    }
     let position = this.mIjkMediaPlayer.getCurrentPosition();
     let duration = this.mIjkMediaPlayer.getDuration();
     if (duration <= 0 && this.duration > 0) {
@@ -14340,7 +14343,8 @@ export struct LocalMusic {
         this.songList,
         this.curIndex,
         this.mIjkMediaPlayer.getCurrentPosition(),
-        this.videoUrl
+        this.videoUrl,
+        this.getActiveDuration()
       );
       Logger.info('heanup startCasting', '✅ initAVCast 调用完成');
 
@@ -14350,6 +14354,11 @@ export struct LocalMusic {
 
       // 从 CastController 获取投播状态
       this.isCastPlaying = this.castControllerWrapper.getIsCastPlaying();
+      if (this.isCastPlaying) {
+        this.CONTROL_PlayStatus = PlayStatus.PLAY;
+        this.setIsPlaying(true);
+        this.updateSessionPlayState(true);
+      }
       Logger.info('heanup startCasting', `✅ 投播初始化完成, isCastPlaying=${this.isCastPlaying}`);
 
     } catch (error) {
@@ -14467,11 +14476,13 @@ export struct LocalMusic {
   private updateSessionPlayState(isPlay: boolean): void {
     // if(!this.isBgPlayOpen)
     //   return
+    const elapsedTime = (this.castControllerWrapper && this.isCastPlaying) ? this.currentTime2 * 1000
+      : this.mIjkMediaPlayer.getCurrentPosition();
     Logger.info(TAG, `updateIsPlay isPlay: ${isPlay}`);
     this.setAVSessionPlayState({
       state: isPlay ? avSession.PlaybackState.PLAYBACK_STATE_PLAY : avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
       position: {
-        elapsedTime: this.mIjkMediaPlayer.getCurrentPosition(),
+        elapsedTime: elapsedTime,
         updateTime: new Date().getTime()
       }
     });
@@ -14484,12 +14495,21 @@ export struct LocalMusic {
   private positionChange(position: number) {
     this.currentTime2 = position / 1000;
     this.currentStringTime = secondToTime(Math.floor(position / 1000));
+    if (this.castControllerWrapper && this.isCastPlaying) {
+      this.updateCastProgress(position);
+      if (this.isSeekTo) {
+        this.isSeekTo = false;
+      }
+    }
   }
 
   private playDurationChange(duration: number) {
     this.duration = duration;
     this.durationTime = Math.floor(this.duration / 1000);
     this.durationStringTime = secondToTime((this.durationTime));
+    if (this.castControllerWrapper && this.isCastPlaying) {
+      this.totalTime = this.stringForTime(this.duration);
+    }
   }
   // playbackStateChangeListener 方法已被删除,改用 setPlaybackStateChangeListener 中的内联函数
   private reloadCasting = async () => {
@@ -14662,6 +14682,9 @@ export struct LocalMusic {
         try {
           Logger.info('heanup playOrPause', '投播模式播放');
           await this.castControllerWrapper.setPlaying();
+          this.CONTROL_PlayStatus = PlayStatus.PLAY;
+          this.setIsPlaying(true);
+          this.updateSessionPlayState(true);
           Logger.info('heanup playOrPause', '投播播放成功');
           this.playChange();
         } catch (error) {
@@ -14715,6 +14738,43 @@ export struct LocalMusic {
     this.setSeekToActionProgress(seeTime);
     await this.seekTo(seeTime + "");
   };
+  private getActiveDuration(): number {
+    let duration = this.mIjkMediaPlayer.getDuration();
+    if (this.castControllerWrapper && this.isCastPlaying) {
+      return this.duration > 0 ? this.duration : duration;
+    }
+    if (duration <= 0 && this.duration > 0) {
+      duration = this.duration;
+    }
+    return duration;
+  }
+
+  private updateCastProgress(position: number): void {
+    const duration = this.getActiveDuration();
+    if (duration <= 0) {
+      return;
+    }
+    this.slideEnable = true;
+    let pos = (position / duration) * 100;
+    if (pos > this.PROGRESS_MAX_VALUE) {
+      this.progressValue = this.PROGRESS_MAX_VALUE;
+    } else if (pos < 0) {
+      this.progressValue = 0;
+    } else {
+      this.progressValue = pos;
+    }
+
+    this.totalTime = this.stringForTime(duration);
+    if (position > duration) {
+      position = duration;
+    }
+    this.isCurrentTime = true;
+    this.currentTime = this.stringForTime(position);
+    this.isCurrentTime = false;
+    this.lyricController.updatePosition(position + this.timeOffset * 1000);
+    this.lyricControllerXF.updatePosition(position + this.timeOffset * 1000);
+    this.lyricControllerSingle.updatePosition(position + this.timeOffset * 1000);
+  }
   /**
    * Gesture method onActionUpdate.
    *
@@ -14722,7 +14782,7 @@ export struct LocalMusic {
    */
   private setSeekToActionProgress(position: number) {
     // 投播模式使用this.duration,本地模式使用播放器获取
-    let duration = this.duration > 0 ? this.duration : this.mIjkMediaPlayer.getDuration();
+    let duration = this.getActiveDuration();
     let pos = 0;
     if (duration > 0) {
       this.slideEnable = true;
@@ -14794,6 +14854,9 @@ export struct LocalMusic {
       try {
         Logger.info('heanup pause', '投播模式暂停');
         await this.castControllerWrapper.setPause();
+        this.CONTROL_PlayStatus = PlayStatus.PAUSE;
+        this.setIsPlaying(false);
+        this.updateSessionPlayState(false);
         Logger.info('heanup pause', '投播暂停成功');
       } catch (error) {
         Logger.error('heanup pause', `投播暂停失败: ${error}`);
@@ -14860,6 +14923,7 @@ export struct LocalMusic {
     if (this.castControllerWrapper && this.isCastPlaying) {
       try {
         let seekPos = Number.parseInt(value);
+        this.isSeekTo = true;
         Logger.info('heanup seekTo', `投播模式seek到: ${seekPos}ms, 当前时长: ${this.duration}ms`);
 
         // 检查是否接近末尾