chendeben 1 жил өмнө
parent
commit
ac36f7aadd

+ 0 - 84
entry/src/main/ets/common/service/PlayerStateModel.ets

@@ -1,5 +1,4 @@
 import { VideoItem } from '../../viewmodel/VideoItem';
-import { WidgetData, PlayProgress, PlayState, SongInfo, PlaylistState, WidgetConfig, WidgetSize, WidgetTheme } from '../widget/WidgetTypes';
 import { LogUtils } from '@ohos/ijkplayer';
 
 /**
@@ -38,7 +37,6 @@ export interface PlayerState {
 export interface PlayerStateListener {
   onStateChanged(state: PlayerState): void;
   onSongChanged(song: VideoItem): void;
-  onProgressChanged(progress: PlayProgress): void;
   onError(error: PlayerError): void;
 }
 
@@ -162,10 +160,6 @@ export class PlayerStateModel {
     if (positionChanged || durationChanged) {
       this.state.currentPosition = currentPosition;
       this.state.duration = duration;
-
-      if (positionChanged || durationChanged) {
-        this.notifyProgressChanged();
-      }
     }
   }
 
@@ -285,63 +279,6 @@ export class PlayerStateModel {
 
     return true;
   }
-  /**
-   * 转换为卡片数据格式
-   */
-  toWidgetData(currentSong?: VideoItem): WidgetData {
-    const playState: PlayState = {
-      isPlaying: this.state.isPlaying,
-      isPaused: this.state.isPaused,
-      isLoading: this.state.isLoading
-    };
-
-    const songInfo: SongInfo = currentSong ? {
-      id: currentSong.id || '',
-      title: currentSong.name || '未知歌曲',
-      artist: currentSong.artist || '未知艺术家',
-      album: currentSong.album || '未知专辑',
-      coverImagePath: currentSong.pixelMapPath || '',
-      duration: currentSong.duration ? parseInt(currentSong.duration) : 0
-    } : {
-      id: '',
-      title: '未知歌曲',
-      artist: '未知艺术家',
-      album: '未知专辑',
-      coverImagePath: '',
-      duration: 0
-    };
-
-    const progress: PlayProgress = {
-      currentPosition: this.state.currentPosition,
-      duration: this.state.duration,
-      percentage: this.state.duration > 0 ? (this.state.currentPosition / this.state.duration) * 100 : 0,
-      currentTimeText: this.formatTime(Math.floor(this.state.currentPosition / 1000)),
-      totalTimeText: this.formatTime(Math.floor(this.state.duration / 1000))
-    };
-
-    const playlistState: PlaylistState = {
-      hasNext: this.state.hasNext || false,
-      hasPrevious: this.state.hasPrevious || false,
-      currentIndex: this.state.currentIndex,
-      totalCount: this.state.totalCount || 0
-    };
-
-    const config: WidgetConfig = {
-      size: WidgetSize.MEDIUM,
-      theme: WidgetTheme.AUTO,
-      showProgress: true,
-      showCover: true
-    };
-
-    return {
-      playState,
-      currentSong: songInfo,
-      progress,
-      playlist: playlistState,
-      config
-    };
-  }
-
   /**
    * 格式化时间显示
    */
@@ -382,27 +319,6 @@ export class PlayerStateModel {
     });
   }
 
-  /**
-   * 通知进度变化
-   */
-  private notifyProgressChanged(): void {
-    const progress: PlayProgress = {
-      currentPosition: this.state.currentPosition,
-      duration: this.state.duration,
-      percentage: this.state.duration > 0 ? (this.state.currentPosition / this.state.duration) * 100 : 0,
-      currentTimeText: this.formatTime(Math.floor(this.state.currentPosition / 1000)),
-      totalTimeText: this.formatTime(Math.floor(this.state.duration / 1000))
-    };
-
-    this.listeners.forEach(listener => {
-      try {
-        listener.onProgressChanged(progress);
-      } catch (error) {
-        LogUtils.getInstance().LOGI(`PlayerStateModel: Error notifying progress change: ${error}`);
-      }
-    });
-  }
-
   /**
    * 通知歌曲变化
    */

+ 297 - 80
entry/src/main/ets/common/service/UnifiedPlayerService.ets

@@ -2,15 +2,16 @@ import { VideoItem } from '../../viewmodel/VideoItem';
 import { PlayerManager, IPlayerManager, PlayerStateCallback } from './PlayerManager';
 import { PlayerStateModel, PlayerState, PlayerStateListener, PlayMode, PlayerError, PlayerErrorType } from './PlayerStateModel';
 import { PlaylistModel } from './PlaylistModel';
-import { PlayProgress, WidgetData, WidgetSize, WidgetTheme } from '../widget/WidgetTypes';
 import { IjkMediaPlayer, LogUtils, InterruptEvent, InterruptHintType } from '@ohos/ijkplayer';
 import { common } from '@kit.AbilityKit';
-import { PreferencesUtil, StrUtil } from '@pura/harmony-utils';
+import { PreferencesUtil as PuraPreferencesUtil, StrUtil } from '@pura/harmony-utils';
+import { PreferencesUtil } from '../utils/PreferencesUtil';
 import { DataPersistenceService, PlaylistSyncService, PlaylistSyncListener, IDataPersistenceService, PlaylistData,
   PlayerStateData } from './DataPersistenceService';
 import { ErrorRecoveryStrategy, IErrorRecoveryStrategy, ErrorContext, ErrorRecoveryAction } from './ErrorRecoveryStrategy';
 import { AvSessionController } from '../../controller/AvSessionController';
 import { avSession } from '@kit.AVSessionKit';
+import { formProvider, formBindingData } from '@kit.FormKit';
 import json from '@ohos.util.json';
 import MediaTable from '../util/MediaTable';
 
@@ -43,6 +44,58 @@ export interface InitializationValidation {
   warnings: string[];
 }
 
+/**
+ * 卡片播放状态数据接口
+ */
+export interface WidgetPlayerState {
+  isPlaying: boolean;
+  isPaused: boolean;
+  isLoading: boolean;
+  currentPosition: number;
+  duration: number;
+  hasNext: boolean;
+  hasPrevious: boolean;
+  playMode: number;
+}
+
+/**
+ * 卡片播放列表信息接口
+ */
+export interface WidgetPlaylistInfo {
+  currentIndex: number;
+  totalCount: number;
+}
+
+/**
+ * 卡片时间信息接口
+ */
+export interface WidgetTimeInfo {
+  currentTimeText: string;
+  totalTimeText: string;
+  progressPercentage: number;
+}
+
+/**
+ * 卡片数据接口
+ */
+export interface WidgetFormData {
+  // VideoItem数据
+  id: string;
+  name: string;
+  artist: string;
+  album: string;
+  pixelMapPath: string;
+  duration: number;
+  filePath: string;
+  // 扩展数据
+  imgName: string;
+  imageColorHex: string;
+  // 状态数据
+  playerState: WidgetPlayerState;
+  playlistInfo: WidgetPlaylistInfo;
+  timeInfo: WidgetTimeInfo;
+}
+
 /**
  * 统一播放器服务接口
  * 提供统一的播放控制接口,替代LocalMusic中的播放器逻辑
@@ -249,6 +302,42 @@ export interface IPlayerService {
  * 统一播放器服务实现
  * 整合PlayerManager、PlayerStateModel和PlaylistModel
  */
+interface widgeData {
+  // VideoItem数据平铺
+  id: string;
+  name: string;
+  artist: string;
+  album: string;
+  pixelMapPath: string;
+  duration: number;
+  filePath: string;
+  imgName: string;
+  imageColorHex: string;
+
+  // PlayerState数据平铺
+  isPlaying: boolean;
+  isPaused: boolean;
+  isLoading: boolean;
+  currentPosition: number;
+  hasNext: boolean;
+  hasPrevious: boolean;
+  playMode: number;
+
+  // 播放列表信息平铺
+  currentIndex: number;
+  totalCount: number;
+
+  // 时间信息平铺
+  currentTimeText: string;
+  totalTimeText: string;
+  progressPercentage: number;
+
+  // 保持嵌套结构兼容性
+  playerState: WidgetPlayerState;
+  playlistInfo: WidgetPlaylistInfo;
+  timeInfo: WidgetTimeInfo;
+}
+
 export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListener, PlayerStateCallback {
   private static instance: UnifiedPlayerService | null = null;
   private playerManager: IPlayerManager;
@@ -259,6 +348,11 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
   private errorRecovery: IErrorRecoveryStrategy;
   private avSessionController: AvSessionController | null = null;
   private context: common.UIAbilityContext | null = null;
+  
+  // 防重复调用机制
+  private isUpdatingForms: boolean = false;
+  private lastUpdateTime: number = 0;
+  private readonly UPDATE_THROTTLE_MS: number = 100; // 100ms内只允许一次更新
   private progressTimer: number = -1;
   private isInitialized: boolean = false;
   private isDataRestored: boolean = false; // 新增:数据恢复完成标识
@@ -269,7 +363,6 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
   private lastAvMetadataUpdate: number = 0; // 新增:上次元数据更新时间
   private avSessionUpdateTimer: number = -1; // 新增:系统播控更新防抖定时器
   private avMetadataUpdateTimer: number = -1; // 新增:元数据更新防抖定时器
-  private lastUpdateTime: number=0;
   private favList: VideoItem[]=[];
   private table: MediaTable | undefined = undefined;
   private lastAutoPlayTime: number = 0; // 新增:上次自动播放时间,用于防抖
@@ -499,6 +592,8 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
     this.startProgressTimer();
     this.updateSessionPlayState();
     this.updateWidgetsForStateChange();
+    // 同步更新所有桌面卡片状态
+    this.updateAllForms();
   }
 
   /**
@@ -516,6 +611,9 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
       this.saveCurrentState();
       this.updateSessionPlayState();
       await this.updateWidgetsForPlayStateChange();
+      
+      // 同步更新所有桌面卡片状态
+      await this.updateAllForms();
     } catch (error) {
       throw new Error(`Resume from pause failed: ${error}`);
     }
@@ -645,6 +743,9 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
       // 更新卡片显示暂停状态
       await this.updateWidgetsForPlayStateChange();
 
+      // 同步更新所有桌面卡片状态
+      await this.updateAllForms();
+
     } catch (error) {
       LogUtils.getInstance().LOGI(`UnifiedPlayerService pause error: ${error}`);
     }
@@ -780,6 +881,9 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
         await this.updateWidgetsForSongChange(newSong, true);
       }
 
+      // 同步更新所有桌面卡片状态
+      await this.updateAllForms();
+
     } catch (error) {
       await this.handlePlaybackError(error as Error, PlayerErrorType.PLAYBACK_ERROR);
     } finally {
@@ -822,6 +926,9 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
         this.stateModel.updateCurrentSong(currentSong);
       }
 
+      // 同步更新所有桌面卡片状态
+      await this.updateAllForms();
+
     } catch (error) {
       throw new Error;
     } finally {
@@ -1444,6 +1551,21 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
           LogUtils.getInstance().LOGI(`AVSession seek command failed: ${error}`);
         });
       });
+
+      // 播放模式设置监听
+      avSession.on('setLoopMode', (mode: number) => {
+        LogUtils.getInstance().LOGI(`UnifiedPlayerService: AVSession setLoopMode command received: ${mode}`);
+        try {
+          this.setPlayMode(mode);
+          // 更新AvSession播放状态以反映新的播放模式
+          this.updateSessionPlayState();
+          // 同步更新桌面卡片
+          this.updateAllForms();
+        } catch (error) {
+          LogUtils.getInstance().LOGI(`AVSession setLoopMode command failed: ${error}`);
+        }
+      });
+
       LogUtils.getInstance().LOGI('UnifiedPlayerService: AVSession listeners set up successfully');
     } catch (error) {
       LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to set AVSession listeners: ${error}`);
@@ -1787,7 +1909,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
       const playbackPosition = (duration - position < threshold) ? 0 : position;
       const  currentSong = this.playlistModel.getCurrentSong();
       if (currentSong!=null) {
-        PreferencesUtil.putSync(currentSong.filePath, playbackPosition);
+        PuraPreferencesUtil.putSync(currentSong.filePath, playbackPosition);
       }
 
     }
@@ -1802,7 +1924,7 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
     //
     // }
     if (song!= null){
-      const position = PreferencesUtil.getNumberSync(song.filePath, 0);
+      const position: number = PuraPreferencesUtil.getNumberSync(song.filePath, 0);
       if (position > 0) {
         await this.playerManager.seekToPosition(position.toString());
       }
@@ -2053,18 +2175,6 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
 
   // ==================== StateChangeCallback 接口实现 ====================
 
-  /**
-   * 状态变化回调(来自StateSyncService的本地通知)
-   */
-  onStateChanged?(state: PlayerState): void {
-    try {
-      LogUtils.getInstance().LOGI(`UnifiedPlayerService: Received state change notification - isPlaying=${state.isPlaying}`);
-      // 这里可以处理来自其他组件的状态变化通知
-      // 通常情况下,状态变化是由本服务发起的,所以这里主要用于调试和监控
-    } catch (error) {
-      LogUtils.getInstance().LOGI(`UnifiedPlayerService onStateChanged error: ${error}`);
-    }
-  }
 
   /**
    * 歌曲变化回调(来自StateSyncService的本地通知)
@@ -2078,17 +2188,6 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
     }
   }
 
-  /**
-   * 进度变化回调(来自StateSyncService的本地通知)
-   */
-  onProgressChanged?(progress: PlayProgress): void {
-    try {
-      // LogUtils.getInstance().LOGI(`UnifiedPlayerService: Received progress change notification - ${progress.percentage.toFixed(1)}%`);
-      // 这里可以处理来自其他组件的进度变化通知
-    } catch (error) {
-      LogUtils.getInstance().LOGI(`UnifiedPlayerService onProgressChanged error: ${error}`);
-    }
-  }
 
   // ==================== WidgetControlCallback 接口实现 ====================
 
@@ -2348,6 +2447,9 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
     // 播放开始时更新卡片显示播放状态
     this.updateWidgetsForPlayStateChange();
 
+    // 同步更新所有桌面卡片状态
+    this.updateAllForms();
+
     LogUtils.getInstance().LOGI('UnifiedPlayerService: New song playback started successfully');
   }
 
@@ -2363,6 +2465,9 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
     // 更新卡片显示播放完成状态
     this.updateWidgetsForPlayStateChange();
 
+    // 同步更新所有桌面卡片状态
+    this.updateAllForms();
+
     // 异步处理自动播放逻辑,避免阻塞主线程
     setTimeout(() => {
       this.handleAutoPlayOnCompletion().catch(() => {
@@ -2520,10 +2625,170 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
     }
   }
 
-  updateAllForms() {
-    let formData=this.getCurrentSong();
-    let songState=this.getCurrentState();
-    throw new Error('Method not implemented.');
+  /**
+   * 更新所有桌面卡片状态
+   * 将VideoItem和PlayerState数据传入桌面卡片,控制卡片的显示
+   */
+  async updateAllForms() {
+    try {
+      const now = Date.now();
+      
+      // 防重复调用:如果正在更新或距离上次更新时间太近,则跳过
+      if (this.isUpdatingForms || (now - this.lastUpdateTime) < this.UPDATE_THROTTLE_MS) {
+        LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Skipping duplicate call (last update: ${now - this.lastUpdateTime}ms ago)`);
+        return;
+      }
+      
+      this.isUpdatingForms = true;
+      this.lastUpdateTime = now;
+      
+      const formData: VideoItem | null = this.getCurrentSong(); // VideoItem
+      const songState: PlayerState = this.getCurrentState(); // PlayerState
+      
+      if (!formData) {
+        LogUtils.getInstance().LOGI('UnifiedPlayerService updateAllForms: No current song data');
+        return;
+      }
+
+      // 从持久化存储获取所有formId
+      const context = AppStorage.get('context') as common.UIAbilityContext;
+      if (!context) {
+        LogUtils.getInstance().LOGI('UnifiedPlayerService updateAllForms: No context available');
+        return;
+      }
+
+      const preferencesUtil: PreferencesUtil = PreferencesUtil.getInstance();
+      const prefs = await preferencesUtil.getPreferences(context);
+      const formIds: string[] = await preferencesUtil.getFormIds(prefs);
+
+      if (formIds.length === 0) {
+        LogUtils.getInstance().LOGI('UnifiedPlayerService updateAllForms: No form IDs found');
+        return;
+      }
+
+      // 构建卡片数据,包含VideoItem和PlayerState信息
+      const widgetPlayerState: WidgetPlayerState = {
+        isPlaying: songState.isPlaying || false,
+        isPaused: songState.isPaused || true,
+        isLoading: songState.isLoading || false,
+        currentPosition: songState.currentPosition || 0,
+        duration: songState.duration || 0,
+        hasNext: songState.hasNext || false,
+        hasPrevious: songState.hasPrevious || false,
+        playMode: songState.playMode || 0
+      };
+      
+      const widgetPlaylistInfo: WidgetPlaylistInfo = {
+        currentIndex: this.getCurrentIndex(),
+        totalCount: this.getPlaylist().length
+      };
+      
+      const widgetTimeInfo: WidgetTimeInfo = {
+        currentTimeText: this.formatTimeDisplay(Math.floor((songState.currentPosition || 0) / 1000)),
+        totalTimeText: this.formatTimeDisplay(Math.floor((songState.duration || 0) / 1000)),
+        progressPercentage: this.calculateProgressPercentage(songState.currentPosition || 0, songState.duration || 0)
+      };
+
+
+      // 平铺数据结构,确保桌面卡片能正确接收
+      const flatWidgetData: widgeData = {
+        // VideoItem数据平铺
+        id: formData.id || '',
+        name: formData.name || '',
+        artist: formData.artist || '',
+        album: formData.album || '',
+        pixelMapPath: formData.pixelMapPath || '',
+        duration: typeof formData.duration === 'string' ? parseInt(formData.duration || '0') : (formData.duration || 0),
+        filePath: formData.filePath || '',
+        imgName: '',
+        imageColorHex: '2A2A2A',
+        
+        // PlayerState数据平铺
+        isPlaying: widgetPlayerState.isPlaying,
+        isPaused: widgetPlayerState.isPaused,
+        isLoading: widgetPlayerState.isLoading,
+        currentPosition: widgetPlayerState.currentPosition,
+        hasNext: widgetPlayerState.hasNext,
+        hasPrevious: widgetPlayerState.hasPrevious,
+        playMode: widgetPlayerState.playMode,
+        
+        // 播放列表信息平铺
+        currentIndex: widgetPlaylistInfo.currentIndex,
+        totalCount: widgetPlaylistInfo.totalCount,
+        
+        // 时间信息平铺
+        currentTimeText: widgetTimeInfo.currentTimeText,
+        totalTimeText: widgetTimeInfo.totalTimeText,
+        progressPercentage: widgetTimeInfo.progressPercentage,
+        
+        // 保持嵌套结构兼容性
+        playerState: widgetPlayerState,
+        playlistInfo: widgetPlaylistInfo,
+        timeInfo: widgetTimeInfo
+      };
+
+      // 记录更新统计
+      let successCount = 0;
+      let failedCount = 0;
+      const invalidFormIds: string[] = [];
+
+      // 逐个更新所有卡片
+      for (const formId of formIds) {
+        try {
+          const formBindingDataInstance = formBindingData.createFormBindingData(flatWidgetData);
+          await formProvider.updateForm(formId, formBindingDataInstance);
+          successCount++;
+          LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Updated form ${formId} - isPlaying=${flatWidgetData.isPlaying}, hasNext=${flatWidgetData.hasNext}, hasPrevious=${flatWidgetData.hasPrevious}`);
+        } catch (error) {
+          failedCount++;
+          const errorStr: string = error?.toString() || '';
+          
+          // 检查是否是Form ID不存在的错误,并且增加更严格的判断条件
+          if ((errorStr.includes('does not exist') || errorStr.includes('不存在')) && 
+              !errorStr.includes('timeout') && !errorStr.includes('busy')) {
+            // 只有明确是"不存在"错误且不是临时性错误时才标记为无效
+            invalidFormIds.push(formId);
+            LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Form ${formId} does not exist, will be removed from storage`);
+          } else {
+            LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Failed to update form ${formId}: ${error} (not marking as invalid)`);
+          }
+        }
+      }
+
+      // 自动清理无效的Form ID
+      if (invalidFormIds.length > 0) {
+        try {
+          await preferencesUtil.removeFormIds(prefs, invalidFormIds);
+          LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Removed ${invalidFormIds.length} invalid form IDs: ${invalidFormIds.join(', ')}`);
+        } catch (error) {
+          LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Failed to clean invalid form IDs: ${error}`);
+        }
+      }
+
+      LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Updated ${successCount}/${formIds.length} forms with song "${formData.name}", playing: ${songState.isPlaying}, failed: ${failedCount}, cleaned: ${invalidFormIds.length}`);
+    } catch (error) {
+      LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms error: ${error}`);
+    } finally {
+      // 重置更新标志
+      this.isUpdatingForms = false;
+    }
+  }
+
+  /**
+   * 计算播放进度百分比
+   */
+  private calculateProgressPercentage(current: number, total: number): number {
+    if (total <= 0) return 0;
+    return Math.min(100, Math.max(0, (current / total) * 100));
+  }
+
+  /**
+   * 格式化时间显示
+   */
+  private formatTimeDisplay(seconds: number): string {
+    const mins = Math.floor(seconds / 60);
+    const secs = Math.floor(seconds % 60);
+    return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
   }
 
   /**
@@ -2720,54 +2985,6 @@ export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListene
     }
   }
 
-  /**
-   * 设置当前播放模式到AVSession
-   */
-  private setCurrentPlayMode(): void {
-    try {
-      if (!this.avSessionController) {
-        return;
-      }
-
-      const currentSong = this.playlistModel.getCurrentSong();
-      if (!currentSong) {
-        return;
-      }
-
-      // 修复duration转换问题 - 优先从播放器获取实际duration
-      let duration = 0;
-      try {
-        const ijkPlayer = this.playerManager.getIjkPlayer();
-        if (ijkPlayer) {
-          const playerDuration = ijkPlayer.getDuration();
-          if (playerDuration > 0) {
-            duration = playerDuration;
-            LogUtils.getInstance().LOGI(`UnifiedPlayerService: Using player duration: ${duration}ms`);
-          } else {
-            // 备用方案:解析VideoItem中的duration
-            duration = currentSong.duration ? this.parseDuration(currentSong.duration) : 0;
-            LogUtils.getInstance().LOGI(`UnifiedPlayerService: Using parsed duration: ${duration}ms from '${currentSong.duration}'`);
-          }
-        } else {
-          duration = currentSong.duration ? this.parseDuration(currentSong.duration) : 0;
-          LogUtils.getInstance().LOGI(`UnifiedPlayerService: No player available, using parsed duration: ${duration}ms`);
-        }
-      } catch (error) {
-        duration = currentSong.duration ? this.parseDuration(currentSong.duration) : 0;
-        LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error getting player duration, using parsed: ${duration}ms, error: ${error}`);
-      }
-
-      // 获取歌词内容(如果有的话)
-      const lyricContent = ''; // 这里可以根据需要获取歌词内容
-
-      this.avSessionController.setAVMetadataMusic(currentSong, duration, lyricContent);
-
-      LogUtils.getInstance().LOGI(`UnifiedPlayerService: AVSession metadata updated for ${currentSong.name} with duration ${duration}ms`);
-
-    } catch (error) {
-      LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to set current play mode: ${error}`);
-    }
-  }
 
   /**
    * 更新AVSession元数据(带防抖和智能更新)

+ 39 - 2
entry/src/main/ets/common/utils/PreferencesUtil.ets

@@ -29,17 +29,20 @@ export class PreferencesUtil {
    * 获取 Preferences 实例
    */
   public async getPreferences(context: Context): Promise<preferences.Preferences> {
-    const contextKey = context.toString();
+    // 使用固定的key而不是context.toString(),确保所有Form共享同一个Preferences实例
+    const contextKey = PREFERENCES_NAME;
     
     if (!this.preferencesMap.has(contextKey)) {
       try {
         const prefs = await preferences.getPreferences(context, PREFERENCES_NAME);
         this.preferencesMap.set(contextKey, prefs);
-        hilog.info(0x0000, TAG, 'Preferences instance created successfully');
+        hilog.info(0x0000, TAG, `Preferences instance created successfully with key: ${contextKey}`);
       } catch (error) {
         hilog.error(0x0000, TAG, `Failed to get preferences: ${JSON.stringify(error)}`);
         throw new Error(`Failed to get preferences: ${JSON.stringify(error)}`);
       }
+    } else {
+      hilog.info(0x0000, TAG, `Reusing existing Preferences instance with key: ${contextKey}`);
     }
     
     return this.preferencesMap.get(contextKey)!;
@@ -87,6 +90,40 @@ export class PreferencesUtil {
     }
   }
 
+  /**
+   * 批量移除多个 Form ID 从存储列表
+   */
+  public async removeFormIds(prefs: preferences.Preferences, formIdsToRemove: string[]): Promise<void> {
+    if (formIdsToRemove.length === 0) {
+      return;
+    }
+
+    try {
+      const formIds = await this.getFormIds(prefs);
+      const originalCount = formIds.length;
+      
+      // 过滤掉要删除的Form ID
+      const filteredFormIds = formIds.filter(id => !formIdsToRemove.includes(id));
+      
+      if (filteredFormIds.length < originalCount) {
+        await prefs.put(FORM_IDS_KEY, JSON.stringify(filteredFormIds));
+        await prefs.flush();
+        
+        const removedCount = originalCount - filteredFormIds.length;
+        hilog.info(0x0000, TAG, `Removed ${removedCount} form IDs: ${formIdsToRemove.join(', ')}, remaining: ${filteredFormIds.length}`);
+        
+        // 清理相关数据
+        for (const formId of formIdsToRemove) {
+          await this.clearFormData(prefs, formId);
+        }
+      } else {
+        hilog.warn(0x0000, TAG, `No matching form IDs found for removal: ${formIdsToRemove.join(', ')}`);
+      }
+    } catch (error) {
+      hilog.error(0x0000, TAG, `Failed to remove form IDs: ${error}`);
+    }
+  }
+
   /**
    * 获取所有存储的 Form ID
    */

+ 0 - 114
entry/src/main/ets/common/widget/WidgetTypeHelpers.ets

@@ -1,114 +0,0 @@
-import { WidgetData, PlayState, SongInfo, PlayProgress, PlaylistState, WidgetConfig } from './WidgetTypes';
-
-/**
- * Widget类型辅助工具
- * 提供类型安全的对象创建和转换方法
- */
-export class WidgetTypeHelpers {
-  
-  /**
-   * 创建默认的PlayState
-   */
-  static createDefaultPlayState(): PlayState {
-    return {
-      isPlaying: false,
-      isPaused: true,
-      isLoading: false
-    };
-  }
-
-  /**
-   * 创建默认的SongInfo
-   */
-  static createDefaultSongInfo(): SongInfo {
-    return {
-      id: '',
-      title: '暂无播放',
-      artist: '未知艺术家',
-      album: '未知专辑',
-      coverImagePath: '',
-      duration: 0
-    };
-  }
-
-  /**
-   * 创建默认的PlayProgress
-   */
-  static createDefaultPlayProgress(): PlayProgress {
-    return {
-      currentPosition: 0,
-      duration: 0,
-      percentage: 0,
-      currentTimeText: '00:00',
-      totalTimeText: '00:00'
-    };
-  }
-
-  /**
-   * 创建默认的PlaylistState
-   */
-  static createDefaultPlaylistState(): PlaylistState {
-    return {
-      hasNext: false,
-      hasPrevious: false,
-      currentIndex: 0,
-      totalCount: 0
-    };
-  }
-
-  /**
-   * 创建默认的WidgetConfig
-   */
-  static createDefaultWidgetConfig(): WidgetConfig {
-    return {
-      size: 'medium',
-      theme: 'auto',
-      showProgress: true,
-      showCover: true
-    };
-  }
-
-  /**
-   * 创建默认的WidgetData
-   */
-  static createDefaultWidgetData(): WidgetData {
-    return {
-      playState: WidgetTypeHelpers.createDefaultPlayState(),
-      currentSong: WidgetTypeHelpers.createDefaultSongInfo(),
-      progress: WidgetTypeHelpers.createDefaultPlayProgress(),
-      playlist: WidgetTypeHelpers.createDefaultPlaylistState(),
-      config: WidgetTypeHelpers.createDefaultWidgetConfig()
-    };
-  }
-
-  /**
-   * 创建事件发布数据
-   */
-  static createEventPublishData(data: Object): EventPublishData {
-    return {
-      bundleName: 'com.xgplayer.ttmusic.hm',
-      data: JSON.stringify(data)
-    };
-  }
-
-  /**
-   * 创建Want参数
-   */
-  static createWantParameters(params: Record<string, string>): Record<string, Object> {
-    const result: Record<string, Object> = {};
-    const keys = Object.keys(params);
-    for (let i = 0; i < keys.length; i++) {
-      const key = keys[i];
-      result[key] = params[key];
-    }
-    return result;
-  }
-}
-
-/**
- * 事件发布数据接口
- */
-export interface EventPublishData {
-  bundleName: string;
-  data: string;
-}

+ 0 - 366
entry/src/main/ets/common/widget/WidgetTypes.ets

@@ -1,366 +0,0 @@
-/**
- * 卡片相关的数据类型定义
- */
-
-/**
- * 卡片尺寸枚举
- */
-export enum WidgetSize {
-  SMALL = 'small',    // 1x2
-  MEDIUM = 'medium',  // 2x4
-  SQUARE = 'square',  // 2x2
-  RECTANGLE = 'rectangle', // 2x4
-  LARGE = 'large'     // 4x3 (已删除)
-}
-
-/**
- * 卡片主题枚举
- */
-export enum WidgetTheme {
-  AUTO = 'auto',
-  LIGHT = 'light',
-  DARK = 'dark'
-}
-
-/**
- * 播放控制命令枚举
- */
-export enum WidgetCommand {
-  PLAY_PAUSE = 'PLAY_PAUSE',
-  NEXT_SONG = 'NEXT_SONG',
-  PREV_SONG = 'PREV_SONG',
-  SEEK_TO = 'SEEK_TO',
-  OPEN_APP = 'OPEN_APP',
-  OPEN_PLAYER = 'OPEN_PLAYER'
-}
-
-/**
- * 播放状态接口
- */
-export interface PlayState {
-  isPlaying: boolean;
-  isPaused: boolean;
-  isLoading: boolean;
-}
-
-/**
- * 歌曲信息接口
- */
-export interface SongInfo {
-  id: string;
-  title: string;
-  artist: string;
-  album: string;
-  coverImagePath: string;
-  duration: number;
-}
-
-/**
- * 播放进度接口
- */
-export interface PlayProgress {
-  currentPosition: number;
-  duration: number;
-  percentage: number;
-  currentTimeText: string;
-  totalTimeText: string;
-}
-
-/**
- * 播放列表状态接口
- */
-export interface PlaylistState {
-  hasNext: boolean;
-  hasPrevious: boolean;
-  currentIndex: number;
-  totalCount: number;
-}
-
-/**
- * 投播信息接口
- */
-export interface CastingInfo {
-  isCasting: boolean;
-  deviceName: string;
-  deviceType: number;
-}
-
-/**
- * 卡片配置接口
- */
-export interface WidgetConfig {
-  size: WidgetSize | string;
-  theme: WidgetTheme | string;
-  showProgress: boolean;
-  showCover: boolean;
-}
-
-/**
- * 图片文件信息接口
- */
-export interface ImageFileInfo {
-  fileName: string;
-  memoryUri: string;
-  fd: number;
-}
-
-/**
- * 卡片数据接口
- */
-export interface WidgetData {
-  playState: PlayState;
-  currentSong: SongInfo;
-  progress: PlayProgress;
-  playlist: PlaylistState;
-  config: WidgetConfig;
-  castingInfo?: CastingInfo;
-  imageFileInfo?: ImageFileInfo; // 图片文件信息,用于本地图片处理
-}
-
-/**
- * 卡片控制参数接口
- */
-export interface WidgetControlParams {
-  position?: number;
-  songId?: string;
-  percentage?: number;
-}
-
-/**
- * 卡片控制消息接口
- */
-export interface WidgetControlMessage {
-  command: WidgetCommand;
-  params?: WidgetControlParams;
-}
-
-/**
- * 卡片错误类型枚举
- */
-export enum WidgetErrorType {
-  COMMUNICATION_ERROR = 'communication_error',
-  DATA_SYNC_ERROR = 'data_sync_error',
-  CONTROL_COMMAND_ERROR = 'control_command_error',
-  LAYOUT_ERROR = 'layout_error'
-}
-
-/**
- * 卡片错误接口
- */
-export interface WidgetError {
-  type: WidgetErrorType;
-  message: string;
-  code: number;
-  timestamp: number;
-}
-
-/**
- * 事件数据接口
- */
-export interface EventData {
-  command: WidgetCommand;
-  params: WidgetControlParams;
-  timestamp: number;
-  source: string;
-}
-
-/**
- * 请求数据接口
- */
-export interface RequestData {
-  timestamp: number;
-  source: String;
-}
-
-/**
- * 格式化后的卡片数据接口
- */
-export interface FormattedWidgetData {
-  isPlaying: boolean;
-  isPaused: boolean;
-  isLoading: boolean;
-  songTitle: string;
-  songArtist: string;
-  songAlbum: string;
-  coverImage: string;
-  currentTime: string;
-  totalTime: string;
-  progressPercentage: number;
-  hasNext: boolean;
-  hasPrevious: boolean;
-  showProgress: boolean;
-  showCover: boolean;
-  widgetSize: string;
-  timestamp: number;
-  // 新增字段用于支持网络图片
-  imgName?: string;  // 图片文件名,用于memory://协议
-  formImages?: Record<string, number>;  // 图片文件描述符映射
-  // 新增字段用于支持封面颜色
-  imageColorHex?: string;  // 从封面图片提取的主要颜色
-}
-
-/**
- * 偏好设置数据接口
- */
-export class PreferencesData {
-}
-
-
-
-/**
- * 进度跳转参数接口
- */
-export interface SeekParams {
-  percentage: number;
-}
-
-/**
- * 卡片操作数据接口
- */
-export interface WidgetActionData {
-  action: string;
-  params: Object;
-}
-
-/**
- * 缓存统计信息接口
- */
-export interface CacheStats {
-  size: number;
-  hitRate: number;
-  lastUpdate: number;
-}
-
-/**
- * 卡片事件处理参数接口
- */
-export interface WidgetEventParams {
-  action: string;
-  params?: Object;
-}
-
-/**
- * 进度跳转参数接口(扩展)
- */
-export interface WidgetSeekParams {
-  percentage: number;
-}
-
-/**
- * 容器内边距接口
- */
-export interface ContainerPadding {
-  left: number;
-  right: number;
-  top: number;
-  bottom: number;
-}
-
-/**
- * 按钮尺寸接口
- */
-export interface ButtonSize {
-  width: number;
-  height: number;
-}
-
-/**
- * 字体大小配置接口
- */
-export interface FontSizeConfig {
-  title: number;
-  artist: number;
-  time: number;
-}
-
-/**
- * 间距配置接口
- */
-export interface SpacingConfig {
-  horizontal: number;
-  vertical: number;
-}
-
-/**
- * 显示元素配置接口
- */
-export interface ShowElementsConfig {
-  progress: boolean;
-  cover: boolean;
-  album: boolean;
-  time: boolean;
-}
-
-/**
- * 文本长度配置接口
- */
-export interface MaxTextLengthConfig {
-  title: number;
-  artist: number;
-  album: number;
-}
-
-/**
- * 响应式布局参数接口
- */
-export interface ResponsiveLayoutParams {
-  containerPadding: ContainerPadding;
-  buttonSize: ButtonSize;
-  playButtonSize: ButtonSize;
-  fontSize: FontSizeConfig;
-  spacing: SpacingConfig;
-  showElements: ShowElementsConfig;
-}
-
-/**
- * 卡片尺寸配置接口
- */
-export interface WidgetSizeConfig {
-  size: WidgetSize;
-  displayElements: string[];
-  maxTextLength: MaxTextLengthConfig;
-  layoutPriority: string[];
-  animationDuration: number;
-}
-
-/**
- * 动画配置接口
- */
-export interface AnimationConfig {
-  duration: number;
-  curve: string;
-  delay?: number;
-  iterations?: number;
-}
-
-/**
- * 尺寸变化监听器接口
- */
-export interface SizeChangeListener {
-  onSizeChanged(formId: string, oldSize: WidgetSize, newSize: WidgetSize): void;
-}
-
-/**
- * 播放状态变化数据接口
- */
-export interface PlayStateChangeData {
-  playState: PlayState;
-  progress: PlayProgress;
-}
-
-/**
- * 歌曲变化数据接口
- */
-export interface SongChangeData {
-  currentSong: SongInfo;
-}
-
-/**
- * 播放器状态广播数据接口
- */
-export interface PlayerStateBroadcastData {
-  playState: PlayState;
-  currentSong: SongInfo;
-  progress: PlayProgress;
-  playlist: PlaylistState;
-}

+ 0 - 140
entry/src/main/ets/common/widget/WidgetUtils.ets

@@ -1,140 +0,0 @@
-import { hilog } from '@kit.PerformanceAnalysisKit';
-import { WidgetActionData } from './WidgetTypes';
-
-const TAG = 'Heanup WidgetUtils';
-
-/**
- * 卡片工具函数
- */
-
-/**
- * 向卡片发送操作事件
- * @param context 卡片上下文
- * @param data 事件数据
- */
-export function postCardAction(context: Object, data: WidgetActionData): void {
-  try {
-    const message = JSON.stringify(data);
-    hilog.info(0x0000, TAG, `=== Starting postCardAction ===`);
-    hilog.info(0x0000, TAG, `Action: ${data.action}`);
-    hilog.info(0x0000, TAG, `Message: ${message}`);
-    hilog.info(0x0000, TAG, `Context type: ${typeof context}`);
-    
-    // 检查全局对象的可用属性
-    const globalObj: ESObject = globalThis as ESObject;
-    const globalKeys: string[] = Object.keys(globalObj);
-    hilog.info(0x0000, TAG, `Global object keys: ${globalKeys.slice(0, 10).join(', ')}...`);
-    
-    // 方法1: 尝试使用全局postCardAction函数
-    if (globalObj.postCardAction && typeof globalObj.postCardAction === 'function') {
-      hilog.info(0x0000, TAG, `Found global postCardAction function`);
-      globalObj.postCardAction(context, message);
-      hilog.info(0x0000, TAG, `✅ Card action posted via global function`);
-      return;
-    } else {
-      hilog.warn(0x0000, TAG, `Global postCardAction not found or not a function`);
-    }
-    
-    // 方法2: 检查是否存在postCardAction全局函数
-    try {
-      if (typeof postCardAction !== 'undefined') {
-        hilog.info(0x0000, TAG, `Found direct postCardAction function`);
-        (postCardAction as Function)(context, message);
-        hilog.info(0x0000, TAG, `✅ Card action posted via direct function`);
-        return;
-      }
-    } catch (refError) {
-      hilog.warn(0x0000, TAG, `Direct postCardAction reference failed: ${refError}`);
-    }
-    
-    // 方法3: 检查context是否有postCardAction方法
-    const contextObj: ESObject = context as ESObject;
-    if (contextObj && typeof contextObj === 'object') {
-      const contextKeys = Object.keys(contextObj);
-      hilog.info(0x0000, TAG, `Context keys: ${contextKeys.slice(0, 5).join(', ')}...`);
-      
-      if (contextObj.postCardAction && typeof contextObj.postCardAction === 'function') {
-        hilog.info(0x0000, TAG, `Found context postCardAction method`);
-        contextObj.postCardAction(message);
-        hilog.info(0x0000, TAG, `✅ Card action posted via context method`);
-        return;
-      }
-    }
-    
-    // 方法4: 尝试通过不同的全局对象路径
-    const alternativePaths: string[] = ['window', 'self', 'global'];
-    for (const path of alternativePaths) {
-      const pathObj: ESObject = globalObj[path] as ESObject;
-      if (pathObj && pathObj.postCardAction && typeof pathObj.postCardAction === 'function') {
-        hilog.info(0x0000, TAG, `Found postCardAction via ${path}`);
-        pathObj.postCardAction(context, message);
-        hilog.info(0x0000, TAG, `✅ Card action posted via ${path}`);
-        return;
-      }
-    }
-    
-    // 如果所有方法都失败,记录详细的调试信息
-    hilog.error(0x0000, TAG, `❌ No available method to post card action`);
-    hilog.error(0x0000, TAG, `Global postCardAction exists: ${!!globalObj.postCardAction}`);
-    hilog.error(0x0000, TAG, `Global postCardAction type: ${typeof globalObj.postCardAction}`);
-    hilog.error(0x0000, TAG, `Context is object: ${typeof contextObj === 'object'}`);
-    hilog.error(0x0000, TAG, `Context postCardAction exists: ${!!(contextObj && contextObj.postCardAction)}`);
-    
-  } catch (error) {
-    hilog.error(0x0000, TAG, `❌ Failed to post card action: ${error}`);
-  }
-}
-
-/**
- * 格式化时间显示
- * @param seconds 秒数
- * @returns 格式化的时间字符串 (mm:ss)
- */
-export function formatTime(seconds: number): string {
-  const mins = Math.floor(seconds / 60);
-  const secs = Math.floor(seconds % 60);
-  return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
-}
-
-/**
- * 计算播放进度百分比
- * @param current 当前位置
- * @param total 总时长
- * @returns 百分比 (0-100)
- */
-export function calculatePercentage(current: number, total: number): number {
-  if (total <= 0) return 0;
-  return Math.min(100, Math.max(0, (current / total) * 100));
-}
-
-/**
- * 截断文本
- * @param text 原始文本
- * @param maxLength 最大长度
- * @returns 截断后的文本
- */
-export function truncateText(text: string, maxLength: number): string {
-  if (text.length <= maxLength) {
-    return text;
-  }
-  return text.substring(0, maxLength - 1) + '…';
-}
-
-/**
- * 验证卡片数据完整性
- * @param data 卡片数据
- * @returns 是否有效
- */
-export function validateWidgetData(data: Object): boolean {
-  try {
-    // 简化验证逻辑,避免索引访问
-    if (!data) {
-      hilog.warn(0x0000, TAG, `Data is null or undefined`);
-      return false;
-    }
-    return true;
-  } catch (error) {
-    hilog.error(0x0000, TAG, `Error validating widget data: ${error}`);
-    return false;
-  }
-}

+ 173 - 115
entry/src/main/ets/entryformability/EntryFormAbility.ets

@@ -2,13 +2,46 @@ import { formBindingData, FormExtensionAbility, formProvider } from '@kit.FormKi
 import { Want } from '@kit.AbilityKit';
 import { hilog } from '@kit.PerformanceAnalysisKit';
 
-import { WidgetData, WidgetSize } from '../common/widget/WidgetTypes';
 import { PreferencesUtil } from '../common/utils/PreferencesUtil';
-import { UnifiedPlayerService } from '../common/service/UnifiedPlayerService';
+import { UnifiedPlayerService, WidgetFormData } from '../common/service/UnifiedPlayerService';
 import { VideoItem } from '../viewmodel/VideoItem';
 
 const TAG = 'Heanup EntryFormAbility';
 
+/**
+ * 卡片数据接口 - 用于EntryFormAbility的数据构建
+ */
+interface FormWidgetData {
+  // VideoItem数据平铺
+  id: string;
+  name: string;
+  artist: string;
+  album: string;
+  pixelMapPath: string;
+  duration: number;
+  filePath: string;
+  imgName: string;
+  imageColorHex: string;
+
+  // PlayerState数据平铺
+  isPlaying: boolean;
+  isPaused: boolean;
+  isLoading: boolean;
+  currentPosition?: number;
+  hasNext: boolean;
+  hasPrevious: boolean;
+  playMode?: number;
+
+  // 播放列表信息平铺
+  currentIndex?: number;
+  totalCount?: number;
+
+  // 时间信息平铺
+  currentTimeText?: string;
+  totalTimeText?: string;
+  progressPercentage?: number;
+}
+
 
 
 
@@ -26,38 +59,40 @@ export default class EntryFormAbility extends FormExtensionAbility {
    * 卡片创建时调用
    */
   onAddForm(want: Want): formBindingData.FormBindingData {
-
-
     // 检查参数有效性
     if (!want || !want.parameters) {
       hilog.error(0x0000, TAG, 'FormAbility onAddForm want or want.parameters is undefined');
-      return formBindingData.createFormBindingData('');
+      return formBindingData.createFormBindingData({});
     }
 
     // 初始化服务
     try {
-
       this.initializeServices();
-
     } catch (error) {
       hilog.error(0x0000, TAG, `Heanup EntryFormAbility failed to initialize services: ${error}`);
     }
 
     const formId = want.parameters?.['ohos.extra.param.key.form_identity'] as string;
-
+    
+    // 增加详细日志
+    console.info(`[EntryFormAbility] onAddForm called with formId: ${formId}`);
+    console.info(`[EntryFormAbility] want.parameters:`, JSON.stringify(want.parameters));
 
     // 持久化保存 Form ID(异步执行,不阻塞返回)
     this.saveFormIdToPersistence(formId).then(() => {
       console.info('[Heanup] saveFormIdToPersistence success:'+ formId);
-    })
-
-
-    // 初始化卡片数据
-    this.initializeWidget(formId);
-
+      // Form ID保存后,延迟一段时间再触发数据更新,确保Form已准备好
+      setTimeout(() => {
+        console.info(`[EntryFormAbility] Triggering delayed update for form ${formId}`);
+        this.unifiedPlayerService.updateAllForms();
+      }, 500); // 延迟500ms
+    }).catch((error: Error) => {
+      console.error('[Heanup] saveFormIdToPersistence failed:'+ error.message);
+    });
 
     // 返回初始数据作为临时显示
     const adaptedData = this.buildWidgetData();
+    console.info(`[EntryFormAbility] onAddForm returning data for ${formId}:`, JSON.stringify(adaptedData));
 
     return formBindingData.createFormBindingData(adaptedData);
   }
@@ -66,19 +101,22 @@ export default class EntryFormAbility extends FormExtensionAbility {
    * 卡片更新时调用
    */
   onUpdateForm(formId: string): void {
-    // 只更新指定的卡片,避免重复更新
-    this.updateWidgetData(formId);
+    console.info(`[EntryFormAbility] onUpdateForm called for ${formId}`);
+    // 使用统一的更新机制
+    this.unifiedPlayerService.updateAllForms();
   }
 
   /**
    * 卡片删除时调用
    */
   onRemoveForm(formId: string): void {
-
-
+    console.info(`[EntryFormAbility] onRemoveForm called for ${formId}`);
+    
     // 从持久化存储中移除 Form ID(异步执行)
     this.removeFormIdFromPersistence(formId).then(() => {
-
+      console.info(`[EntryFormAbility] Form ID ${formId} removed from persistence`);
+    }).catch((error: Error) => {
+      console.error(`[EntryFormAbility] Failed to remove Form ID ${formId}: ${error.message}`);
     });
   }
 
@@ -86,40 +124,36 @@ export default class EntryFormAbility extends FormExtensionAbility {
    * 卡片可见性变化时调用
    */
   onVisibilityChange(newStatus: Record<string, number>): void {
-
+    console.info(`[EntryFormAbility] onVisibilityChange:`, JSON.stringify(newStatus));
 
     const formIds = Object.keys(newStatus);
+    let hasVisibleForm = false;
+    
     for (let i = 0; i < formIds.length; i++) {
       const formId = formIds[i];
       const isVisible = newStatus[formId] === 1;
 
-
       if (isVisible) {
-        // 卡片变为可见时,更新数据
-        this.updateWidgetData(formId);
+        hasVisibleForm = true;
       }
     }
+    
+    // 如果有卡片变为可见,触发一次统一更新
+    if (hasVisibleForm) {
+      this.unifiedPlayerService.updateAllForms();
+    }
   }
 
-
   /**
    * 卡片配置更新时调用
    */
   onConfigurationUpdate(newConfig: Object): void {
-
+    console.info(`[EntryFormAbility] onConfigurationUpdate:`, JSON.stringify(newConfig));
 
     // 更新所有卡片以适应新配置
     this.unifiedPlayerService.updateAllForms();
   }
 
-  /**
-   * 实现SizeChangeListener接口
-   * 处理卡片尺寸变化事件
-   */
-  onSizeChanged(formId: string, oldSize: WidgetSize, newSize: WidgetSize): void {
-
-  }
-
   /**
    * 处理卡片尺寸变化(系统调用)
    * @param newStatus 新的尺寸状态
@@ -137,116 +171,129 @@ export default class EntryFormAbility extends FormExtensionAbility {
     if (!this.unifiedPlayerService) {
       this.unifiedPlayerService = UnifiedPlayerService.getInstance();
     }
-
   }
 
-
-  /**
-   * 直接更新卡片(无网络图片)
-   */
-  private updateWidgetDirectly(formId: string, adaptedData: VideoItem, retryCount: number = 0): void {
-    const formData = formBindingData.createFormBindingData(adaptedData);
-    formProvider.updateForm(formId, formData).then(() => {
-
-    }).catch(() => {
-      hilog.error(0x0000, TAG, `Heanup widget ${formId} update failed, retry count: ${retryCount}`);
-
-      // 重试机制:最多重试2次
-      if (retryCount < 2) {
-        setTimeout(() => {
-          this.updateWidgetDirectly(formId, adaptedData, retryCount + 1);
-        }, 1000 * (retryCount + 1)); // 递增延迟
+  private buildWidgetData(): FormWidgetData {
+    // 构建与UnifiedPlayerService一致的数据结构
+    try {
+      const currentSong = this.unifiedPlayerService.getCurrentSong();
+      const currentState = this.unifiedPlayerService.getCurrentState();
+      
+      if (!currentSong) {
+        const defaultData: FormWidgetData = {
+          id: '',
+          name: 'Dream It Possible',
+          artist: 'Delacey',
+          album: '未知专辑',
+          pixelMapPath: '',
+          duration: 0,
+          filePath: '',
+          imgName: '',
+          imageColorHex: '2A2A2A',
+          isPlaying: false,
+          isPaused: true,
+          isLoading: false,
+          hasNext: false,
+          hasPrevious: false
+        };
+        return defaultData;
       }
-    });
-  }
-
 
-  /**
-   * 获取默认卡片数据
-   */
-  private getDefaultWidgetData(): WidgetData {
-    return {
-      playState: {
-        isPlaying: false,
-        isPaused: true,
-        isLoading: false
-      },
-      currentSong: {
+      // 返回平铺数据结构,与widget组件期望的格式一致
+      const widgetData: FormWidgetData = {
+        // VideoItem数据
+        id: currentSong.id || '',
+        name: currentSong.name || '',
+        artist: currentSong.artist || '',
+        album: currentSong.album || '',
+        pixelMapPath: currentSong.pixelMapPath || '',
+        duration: typeof currentSong.duration === 'string' ? parseInt(currentSong.duration || '0') : (currentSong.duration || 0),
+        filePath: currentSong.filePath || '',
+        imgName: '',
+        imageColorHex: '2A2A2A',
+        
+        // PlayerState数据平铺
+        isPlaying: currentState.isPlaying || false,
+        isPaused: currentState.isPaused || true,
+        isLoading: currentState.isLoading || false,
+        hasNext: currentState.hasNext || false,
+        hasPrevious: currentState.hasPrevious || false,
+        playMode: currentState.playMode || 0,
+        
+        // 播放列表信息
+        currentIndex: this.unifiedPlayerService.getCurrentIndex(),
+        totalCount: this.unifiedPlayerService.getPlaylist().length,
+        
+        // 时间信息
+        currentTimeText: this.formatTimeDisplay(Math.floor((currentState.currentPosition || 0) / 1000)),
+        totalTimeText: this.formatTimeDisplay(Math.floor((currentState.duration || 0) / 1000)),
+        progressPercentage: this.calculateProgressPercentage(currentState.currentPosition || 0, currentState.duration || 0)
+      };
+      return widgetData;
+    } catch (error) {
+      console.error(`[EntryFormAbility] Failed to build widget data: ${error}`);
+      const errorData: FormWidgetData = {
         id: '',
-        title: '暂无播放',
-        artist: '未知艺术家',
-        album: '未知专辑',
-        coverImagePath: '',
-        duration: 0
-      },
-      progress: {
-        currentPosition: 0,
+        name: 'Error',
+        artist: 'Unknown',
+        album: 'Unknown',
+        pixelMapPath: '',
         duration: 0,
-        percentage: 0,
-        currentTimeText: '00:00',
-        totalTimeText: '00:00'
-      },
-      playlist: {
+        filePath: '',
+        imgName: '',
+        imageColorHex: '2A2A2A',
+        isPlaying: false,
+        isPaused: true,
+        isLoading: false,
         hasNext: false,
-        hasPrevious: false,
-        currentIndex: 0,
-        totalCount: 0
-      },
-      config: {
-        size: 'medium' as WidgetSize,
-        theme: 'auto' as string,
-        showProgress: true,
-        showCover: true
-      }
-    };
+        hasPrevious: false
+      };
+      return errorData;
+    }
   }
 
   /**
-   * 初始化卡片
+   * 格式化时间显示
    */
-  private async initializeWidget(formId: string): Promise<void> {
-    try {
-      // 立即更新一次卡片数据
-      await this.updateWidgetData(formId);
-    } catch (error) {
-      hilog.error(0x0000, TAG, `Failed to initialize widget ${formId}: ${error}`);
-    }
+  private formatTimeDisplay(seconds: number): string {
+    const mins = Math.floor(seconds / 60);
+    const secs = Math.floor(seconds % 60);
+    return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
   }
 
   /**
-   * 更新卡片数据(简化版本)
+   * 计算播放进度百分比
    */
-  private async updateWidgetData(formId: string): Promise<void> {
-    try {
-      // 适配数据到当前尺寸并直接更新
-      const adaptedData = this.buildWidgetData();
-      const formData = formBindingData.createFormBindingData(adaptedData);
-      await formProvider.updateForm(formId, formData);
-
-
-    } catch (error) {
-      hilog.error(0x0000, TAG, `Failed to update widget ${formId}: ${error}`);
-    }
+  private calculateProgressPercentage(current: number, total: number): number {
+    if (total <= 0) return 0;
+    return Math.min(100, Math.max(0, (current / total) * 100));
   }
 
-  private buildWidgetData():VideoItem{
-    let currentSong=this.unifiedPlayerService.getCurrentSong() as VideoItem;
-		return currentSong;
-	}
-
 
   /**
    * 保存 Form ID 到持久化存储
    */
   private async saveFormIdToPersistence(formId: string): Promise<void> {
     try {
-
+      console.info(`[EntryFormAbility] Starting to save Form ID: ${formId}`);
+      console.info(`[EntryFormAbility] Context available: ${!!this.context}`);
+      
       const preferencesUtil = PreferencesUtil.getInstance();
       const prefs = await preferencesUtil.getPreferences(this.context);
+      
+      // 保存前查看当前的Form ID列表
+      const currentFormIds = await preferencesUtil.getFormIds(prefs);
+      console.info(`[EntryFormAbility] Current Form IDs before save: ${currentFormIds.join(', ')}`);
+      
       await preferencesUtil.addFormId(prefs, formId);
+      
+      // 保存后再次查看Form ID列表
+      const updatedFormIds = await preferencesUtil.getFormIds(prefs);
+      console.info(`[EntryFormAbility] Form IDs after save: ${updatedFormIds.join(', ')}`);
 
     } catch (error) {
       hilog.error(0x0000, TAG, `❌ Failed to save Form ID: ${error}`);
+      console.error(`[EntryFormAbility] Error details:`, error);
     }
   }
 
@@ -255,13 +302,24 @@ export default class EntryFormAbility extends FormExtensionAbility {
    */
   private async removeFormIdFromPersistence(formId: string): Promise<void> {
     try {
-
+      console.info(`[EntryFormAbility] Starting to remove Form ID: ${formId}`);
+      
       const preferencesUtil = PreferencesUtil.getInstance();
       const prefs = await preferencesUtil.getPreferences(this.context);
+      
+      // 移除前查看当前的Form ID列表
+      const currentFormIds = await preferencesUtil.getFormIds(prefs);
+      console.info(`[EntryFormAbility] Current Form IDs before remove: ${currentFormIds.join(', ')}`);
+      
       await preferencesUtil.removeFormId(prefs, formId);
+      
+      // 移除后再次查看Form ID列表
+      const updatedFormIds = await preferencesUtil.getFormIds(prefs);
+      console.info(`[EntryFormAbility] Form IDs after remove: ${updatedFormIds.join(', ')}`);
 
     } catch (error) {
       hilog.error(0x0000, TAG, `❌ Failed to remove Form ID: ${error}`);
+      console.error(`[EntryFormAbility] Error details:`, error);
     }
   }
 }

+ 0 - 244
entry/src/main/ets/view/LocalMusic.ets

@@ -37,7 +37,6 @@ import { effectKit } from '@kit.ArkGraphics2D';
 import { ColorConversion } from '../common/util/ColorConversion';
 import { LyricController, LyricParser, LyricView2 } from '@seagazer/cclyric';
 import { AvSessionController } from '../controller/AvSessionController';
-import { WidgetData, PlayProgress } from '../common/widget/WidgetTypes';
 import { PlayerStateListener, PlayerState, PlayerError } from '../common/service/PlayerStateModel';
 
 import {
@@ -71,7 +70,6 @@ import { TextNodeController } from './PipLyricTextBuilder';
 import { IndexerView } from './IndexerView';
 import { KeyCode } from '@kit.InputKit';
 import { KnockController } from '../controller/KnockController';
-import { WidgetCommand, EventData, WidgetControlParams } from '../common/widget/WidgetTypes';
 import { UnifiedPlayerService, IPlayerService } from '../common/service/UnifiedPlayerService';
 // import { FFmpeg, FFProgressMessageParser } from '@sj/ffmpeg';
 const TAG = 'LocalMusic';
@@ -464,102 +462,6 @@ export struct LocalMusic {
           LogUtils.getInstance().LOGI(`LocalMusic: Set existing playlist to UnifiedPlayerService - ${this.songList.length} songs`);
         }
       }
-
-      // 添加状态监听器,保持UI同步
-      class LocalMusicStateListener implements PlayerStateListener {
-        private localMusic: LocalMusic;
-
-        constructor(localMusic: LocalMusic) {
-          this.localMusic = localMusic;
-        }
-
-        onStateChanged(state: PlayerState): void {
-          LogUtils.getInstance().LOGI(`LocalMusic: StateListener triggered - isPlaying: ${state.isPlaying}, isPaused: ${state.isPaused}`);
-
-          // 直接使用状态模型的状态,这是最权威的状态源
-          const isPlaying = state.isPlaying;
-          const isPaused = state.isPaused;
-
-          // 同步播放状态到LocalMusic的UI状态
-          const previousStatus: PlayStatus = this.localMusic.CONTROL_PlayStatus;
-
-          // 根据状态模型确定UI状态
-          if (isPlaying) {
-            this.localMusic.CONTROL_PlayStatus = PlayStatus.PLAY;
-          } else if (isPaused) {
-            this.localMusic.CONTROL_PlayStatus = PlayStatus.PAUSE;
-          } else {
-            this.localMusic.CONTROL_PlayStatus = PlayStatus.INIT;
-          }
-
-          // 更新播放状态相关的UI
-          this.localMusic.setIsPlaying(isPlaying);
-          this.localMusic.updateSessionPlayState(isPlaying);
-
-          LogUtils.getInstance().LOGI(`LocalMusic: State sync - Previous: ${previousStatus}, New: ${this.localMusic.CONTROL_PlayStatus}, isPlaying: ${isPlaying}`);
-
-          // 强制触发UI更新,无论状态是否变化
-          this.localMusic.playChange();
-          this.localMusic.watchStatus();
-
-          // 更新动画状态
-          if (isPlaying) {
-            this.localMusic.animationState = AnimationStatus.Running;
-            this.localMusic.mDestroyPage = false;
-          } else {
-            this.localMusic.animationState = AnimationStatus.Paused;
-            this.localMusic.mDestroyPage = true;
-          }
-
-          LogUtils.getInstance().LOGI(`LocalMusic: UI update completed - CONTROL_PlayStatus: ${this.localMusic.CONTROL_PlayStatus}, globalIsPlaying: ${this.localMusic.isPlaying}`);
-
-          // 同步播放模式
-          if (this.localMusic.playType !== state.playMode) {
-            this.localMusic.playType = state.playMode;
-            this.localMusic.setCurrentPlayMode();
-          }
-
-          // 同步音量和速度
-          this.localMusic.volume = state.volume;
-          this.localMusic.playSpeed = state.speed;
-
-          LogUtils.getInstance().LOGI(`LocalMusic: State synchronized - isPlaying=${state.isPlaying}, mode=${state.playMode}`);
-        }
-
-        onSongChanged(song: VideoItem): void {
-          // 立即重置时间显示状态,防止新歌曲时间显示被上一首歌曲的时间"卡住"
-          this.localMusic.oldSeconds = 0;
-          this.localMusic.currentTime = "00:00";
-          this.localMusic.lastSongPath = song.filePath; // 更新当前歌曲路径
-          this.localMusic.justSwitched = true; // 标记歌曲刚刚切换
-          // 同步当前歌曲信息
-          this.localMusic.currentSong = song;
-          this.localMusic.videoUrl = song.filePath;
-          this.localMusic.name = song.name;
-          this.localMusic.artist = song.artist;
-          this.localMusic.cover = song.pixelMapPath;
-
-          // 更新当前索引
-          const currentIndex: number = this.localMusic.unifiedPlayerService.getCurrentIndex();
-          this.localMusic.curIndex = currentIndex;
-          // 更新最近播放时间
-          this.localMusic.updateLastPlayTimeStr(song.filePath);
-        }
-
-        onProgressChanged(progress: PlayProgress): void {
-          // 同步进度更新到UI
-          this.localMusic.syncProgressFromService(progress);
-
-        }
-
-        onError(error: PlayerError): void {
-          this.localMusic.handlePlaybackError();
-        }
-      }
-
-      const stateListener = new LocalMusicStateListener(this);
-      this.unifiedPlayerService.addStateListener(stateListener);
-
       // 不设置LocalMusic自己的AVSession监听器,完全依赖UnifiedPlayerService
       // UnifiedPlayerService已经设置了AVSession监听器,状态变化会通过StateListener传播到LocalMusic
       LogUtils.getInstance().LOGI('LocalMusic: Relying on UnifiedPlayerService for AVSession handling');
@@ -570,9 +472,6 @@ export struct LocalMusic {
           const currentState = this.unifiedPlayerService.getCurrentState();
           const actuallyPlaying = this.unifiedPlayerService.getActualPlayingState();
           LogUtils.getInstance().LOGI(`LocalMusic: Syncing current state - StateModel: ${currentState.isPlaying}, ActualPlayer: ${actuallyPlaying}`);
-
-          // 手动触发状态同步,确保UI显示正确(使用实际播放器状态)
-          stateListener.onStateChanged(currentState);
         } catch (error) {
           LogUtils.getInstance().LOGI(`LocalMusic: Failed to sync current state: ${error}`);
         }
@@ -697,22 +596,6 @@ export struct LocalMusic {
 
     });
 
-    // 监听卡片控制事件(来自EntryAbility的转发)
-    let eventWidgetControl: emitter.InnerEvent = { eventId: 9001 }
-    emitter.on(eventWidgetControl, (eventData: emitter.EventData) => {
-      LogUtils.getInstance().LOGI(`🎵 LocalMusic received widget control via emitter: ${JSON.stringify(eventData.data)}`);
-      if (eventData.data && (eventData.data as Record<string, Object>)['command']) {
-        // 将data转换为EventData类型
-        const widgetEventData = eventData.data as Record<string, Object>;
-        const typedEventData: EventData = {
-          command: widgetEventData['command'] as WidgetCommand,
-          params: (widgetEventData['params'] as WidgetControlParams) || {},
-          timestamp: (widgetEventData['timestamp'] as number) || Date.now(),
-          source: (widgetEventData['source'] as string) || 'unknown'
-        };
-      }
-    });
-
     let event: Callback<InterruptEvent> = (event) => {
       LogUtils.getInstance().LOGI(`onecold 与其他应用音频冲突 被截断点event: ${JSON.stringify(event)}`);
       this.savePlaybackPosition()
@@ -10520,133 +10403,6 @@ export struct LocalMusic {
     }
   }
 
-  // 同步进度更新到UI(从UnifiedPlayerService)
-  private syncProgressFromService(progress: PlayProgress) {
-    try {
-      // 检测歌曲是否发生切换(额外保障机制)
-      const currentSongPath = this.currentSong?.filePath || "";
-      let isSongChanged = false;
-      if (this.lastSongPath !== currentSongPath && currentSongPath !== "") {
-        console.log(`Heanup 在进度更新中检测到歌曲切换: ${this.lastSongPath} -> ${currentSongPath}`);
-        console.log(`Heanup 歌曲切换前 oldSeconds: ${this.oldSeconds}, currentTime: ${this.currentTime}`);
-        // 立即重置时间显示状态,防止新歌曲时间显示被上一首歌曲的时间"卡住"
-        this.oldSeconds = 0;
-        this.currentTime = "00:00";
-        this.lastSongPath = currentSongPath;
-        this.justSwitched = true; // 标记歌曲刚刚切换
-        isSongChanged = true;
-        console.log(`Heanup 歌曲切换后 oldSeconds: ${this.oldSeconds}, currentTime: ${this.currentTime}`);
-        // 歌曲切换时,强制将播放位置重置为0,忽略服务报告的位置
-        progress.currentPosition = 0;
-        console.log(`Heanup 歌曲切换,强制progress.currentPosition为0`);
-
-        // 立即返回,不更新进度,等待新歌曲开始播放
-        return;
-      }
-
-      // 直接使用ijkplayer获取播放进度,避免状态同步延迟
-      try {
-        const ijkPlayer = this.unifiedPlayerService.getIjkPlayer();
-        if (ijkPlayer && ijkPlayer.isPlaying()) {
-          const currentPosition = ijkPlayer.getCurrentPosition();
-          const duration = ijkPlayer.getDuration();
-
-          if (duration > 0 && currentPosition >= 0) {
-            // 更新进度条
-            this.slideEnable = true;
-            let curPercent = currentPosition / duration;
-            let pos = curPercent * 100;
-            if (pos > this.PROGRESS_MAX_VALUE) {
-              this.progressValue = this.PROGRESS_MAX_VALUE;
-            } else {
-              this.progressValue = pos;
-            }
-
-            // 更新时间显示
-            this.totalTime = this.stringForTime(duration);
-            console.log(`Heanup 当前时间 (直接从ijkplayer获取) - ${currentPosition} / ${duration}`)
-            console.log(`Heanup 更新前 - oldSeconds: ${this.oldSeconds}, currentTime: ${this.currentTime}`)
-            this.isCurrentTime = true;
-            this.currentTime = this.stringForTime(currentPosition);
-            this.isCurrentTime = false;
-            console.log(`Heanup 更新后 - oldSeconds: ${this.oldSeconds}, currentTime: ${this.currentTime}`)
-
-            // 继续执行后续的歌词更新等逻辑
-          }
-        }
-      } catch (error) {
-        console.error('LocalMusic: 获取ijkplayer播放进度失败', error);
-        // 如果直接获取失败,回退到使用服务状态
-        this.updateProgressFromServiceState(progress);
-        return;
-      }
-
-      // 如果直接获取ijkplayer失败或者不在播放状态,使用服务状态
-      this.updateProgressFromServiceState(progress);
-
-      // 更新歌词位置
-      const lyricPosition = progress.currentPosition + this.timeOffset * 1000;
-      if (this.lyricController) {
-        this.lyricController.updatePosition(lyricPosition);
-      }
-      if (this.lyricControllerXF) {
-        this.lyricControllerXF.updatePosition(lyricPosition);
-      }
-      if (this.lyricControllerSingle) {
-        this.lyricControllerSingle.updatePosition(lyricPosition);
-      }
-
-      // 更新随机颜色(如果正在播放)
-      const currentState = this.unifiedPlayerService.getCurrentState();
-      if (currentState.isPlaying) {
-        this.randomColor =
-          `rgb(${Math.floor(Math.random() * 256)},${Math.floor(Math.random() * 256)},${Math.floor(Math.random() * 256)})`;
-      } else {
-        this.randomColor = 'rbg(0,0,0)';
-      }
-
-      // 检查是否需要跳到下一首(片尾跳过功能)
-      if (this.isOpenJump && progress.duration > this.jumpEndTime * 1000) {
-        if (progress.currentPosition >= progress.duration - this.jumpEndTime * 1000) {
-          this.playNext();
-        }
-      }
-
-      LogUtils.getInstance().LOGI(`LocalMusic: Progress synchronized from service - ${progress.currentPosition}/${progress.duration}ms`);
-    } catch (error) {
-      LogUtils.getInstance().LOGI(`LocalMusic syncProgressFromService error: ${error}`);
-    }
-  }
-
-  /**
-   * 从服务状态更新播放进度(回退方案)
-   */
-  private updateProgressFromServiceState(progress: PlayProgress) {
-    // 更新进度条
-    if (progress.duration > 0) {
-      this.slideEnable = true;
-      let curPercent = progress.currentPosition / progress.duration;
-      let pos = curPercent * 100;
-      if (pos > this.PROGRESS_MAX_VALUE) {
-        this.progressValue = this.PROGRESS_MAX_VALUE;
-      } else {
-        this.progressValue = pos;
-      }
-    }
-
-    // 更新时间显示
-    this.totalTime = this.stringForTime(progress.duration);
-    if (progress.currentPosition > progress.duration) {
-      progress.currentPosition = progress.duration;
-    }
-    console.log(`Heanup 当前时间 (从服务状态获取) - ${progress.currentPosition} / ${progress.duration}`)
-    console.log(`Heanup 更新前 - oldSeconds: ${this.oldSeconds}, currentTime: ${this.currentTime}`)
-    this.isCurrentTime = true;
-    this.currentTime = this.stringForTime(progress.currentPosition);
-    this.isCurrentTime = false;
-    console.log(`Heanup 更新后 - oldSeconds: ${this.oldSeconds}, currentTime: ${this.currentTime}`)
-  }
-
   updateLastPlayTimeStr(filePath: string) {
     let lastPlayTime = DateUtil.getTodayStr('yyyy-MM-dd HH:mm:ss')
     this.table.updateLastPlayedStrByFilePath(filePath, lastPlayTime, (success: boolean, error?: string) => {

+ 36 - 14
entry/src/main/ets/widget/pages/PlayerWidgetMedium.ets

@@ -30,23 +30,45 @@ const MediumPlayControlAlignRules: Record<string, Record<string, string | Vertic
   'right': { 'anchor': '__container__', 'align': HorizontalAlign.End }
 };
 
-let mediumStorageUpdateCall = new LocalStorage();
-
-@Entry(mediumStorageUpdateCall)
+@Entry
 @Component
 struct PlayerWidgetMedium {
-  // 卡片数据属性 - 使用LocalStorageProp与FormExtensionAbility通信
+  // 卡片数据属性 - 适配新的数据结构
   @LocalStorageProp('formId') formId: string = '202501';
+  
+  // VideoItem 主要数据
+  @LocalStorageProp('id') songId: string = '';
+  @LocalStorageProp('name') songTitle: string = 'Dream It Possible';
+  @LocalStorageProp('artist') songArtist: string = 'Delacey';
+  @LocalStorageProp('album') songAlbum: string = '未知专辑';
+  @LocalStorageProp('pixelMapPath') coverImage: string = '';
+  @LocalStorageProp('duration') songDuration: number = 0;
+  @LocalStorageProp('filePath') songFilePath: string = '';
+  
+  // PlayerState 播放状态数据
+  @LocalStorageProp('playerState') playerState: Record<string, Object> = {};
+  
+  // 平铺状态数据(优先使用)
   @LocalStorageProp('isPlaying') isPlaying: boolean = false;
-  @LocalStorageProp('songTitle') songTitle: string = 'Dream It Possible';
-  @LocalStorageProp('songArtist') songArtist: string = 'Delacey';
-  @LocalStorageProp('coverImage') coverImage: string = '';
+  @LocalStorageProp('isPaused') isPaused: boolean = true;
+  @LocalStorageProp('isLoading') isLoading: boolean = false;
   @LocalStorageProp('hasNext') hasNext: boolean = false;
   @LocalStorageProp('hasPrevious') hasPrevious: boolean = false;
-  @LocalStorageProp('isLoading') isLoading: boolean = false;
-  @LocalStorageProp('isFavorite') isFavorite: boolean = false; // Favorite status
-  @LocalStorageProp('imageColorHex') imageColorHex: string = '2A2A2A';
+  
+  // 播放列表信息
+  @LocalStorageProp('playlistInfo') playlistInfo: Record<string, Object> = {};
+  @LocalStorageProp('currentIndex') currentIndex: number = 0;
+  @LocalStorageProp('totalCount') totalCount: number = 0;
+  
+  // 时间信息
+  @LocalStorageProp('timeInfo') timeInfo: Record<string, Object> = {};
+  @LocalStorageProp('currentTimeText') currentTimeText: string = '00:00';
+  @LocalStorageProp('totalTimeText') totalTimeText: string = '00:00';
+  @LocalStorageProp('progressPercentage') progressPercentage: number = 0;
+  
+  // 兼容性属性(用于获取复合状态)
   @LocalStorageProp('imgName') imgName: string = ''; // 图片文件名,用于memory://协议
+  @LocalStorageProp('isFavorite') isFavorite: boolean = false;
 
   build() {
     Stack() {
@@ -257,23 +279,23 @@ struct PlayerWidgetMedium {
    * 获取专辑封面
    */
   private getCoverImage(): Resource | string {
-    console.info(`Heanup PlayerWidgetSquare: getCoverImage called, coverImage='${this.coverImage}', imgName='${this.imgName}'`);
+    console.info(`Heanup PlayerWidgetMedium: getCoverImage called, coverImage='${this.coverImage}', imgName='${this.imgName}'`);
 
     // 优先使用通过formImages传递的本地图片(支持网络图片下载后的显示)
     if (this.imgName && this.imgName.trim() !== '') {
       const memoryUrl = 'memory://' + this.imgName;
-      console.info(`Heanup PlayerWidgetSquare: Using memory image: ${memoryUrl}`);
+      console.info(`Heanup PlayerWidgetMedium: Using memory image: ${memoryUrl}`);
       return memoryUrl;
     }
 
     // 如果有coverImage且不是网络URL,使用本地路径
     if (this.coverImage && this.coverImage.trim() !== '' && !this.isNetworkUrl(this.coverImage)) {
-      console.info(`Heanup PlayerWidgetSquare: Using local cover image: ${this.coverImage}`);
+      console.info(`Heanup PlayerWidgetMedium: Using local cover image: ${this.coverImage}`);
       return this.coverImage;
     }
 
     // 默认使用内置图片
-    console.info(`Heanup PlayerWidgetSquare: Using default cover image`);
+    console.info(`Heanup PlayerWidgetMedium: Using default cover image`);
     return $r('app.media.ic_avatar4'); // 使用默认专辑封面
   }
 }

+ 33 - 160
entry/src/main/ets/widget/pages/PlayerWidgetRectangle.ets

@@ -25,43 +25,45 @@ const RectanglePlayControlAlignRules: Record<string, Record<string, string | Ver
   'right': { 'anchor': '__container__', 'align': HorizontalAlign.End }
 };
 
-let rectangleStorageUpdateCall = new LocalStorage();
-
-@Entry(rectangleStorageUpdateCall)
+@Entry
 @Component
 struct PlayerWidgetRectangle {
-  // 卡片数据属性 - 使用LocalStorageProp与FormExtensionAbility通信
+  // 卡片数据属性 - 适配新的数据结构
   @LocalStorageProp('formId') formId: string = '202504'; // New formId for this card
+
+  // VideoItem 主要数据
+  @LocalStorageProp('id') songId: string = '';
+  @LocalStorageProp('name') songTitle: string = 'Dream It Possible';
+  @LocalStorageProp('artist') songArtist: string = 'Delacey';
+  @LocalStorageProp('album') songAlbum: string = '未知专辑';
+  @LocalStorageProp('pixelMapPath') coverImage: string = '';
+  @LocalStorageProp('duration') songDuration: number = 0;
+  @LocalStorageProp('filePath') songFilePath: string = '';
+
+  // PlayerState 播放状态数据
+  @LocalStorageProp('playerState') playerState: Record<string, Object> = {};
+
+  // 平铺状态数据(优先使用)
   @LocalStorageProp('isPlaying') isPlaying: boolean = false;
-  @LocalStorageProp('songTitle') songTitle: string = 'Dream It Possible';
-  @LocalStorageProp('songArtist') songArtist: string = 'Delacey';
-  @LocalStorageProp('coverImage') coverImage: string = '';
+  @LocalStorageProp('isPaused') isPaused: boolean = true;
+  @LocalStorageProp('isLoading') isLoading: boolean = false;
   @LocalStorageProp('hasNext') hasNext: boolean = false;
   @LocalStorageProp('hasPrevious') hasPrevious: boolean = false;
-  @LocalStorageProp('isLoading') isLoading: boolean = false;
-  @LocalStorageProp('isFavorite') isFavorite: boolean = false; // Favorite status
-  @LocalStorageProp('imageColorHex') imageColorHex: string = '2A2A2A';
-  @LocalStorageProp('imgName') imgName: string = ''; // 图片文件名,用于memory://协议
 
-  /**
-   * 格式化歌曲标题显示
-   */
-  private getDisplayTitle(): string {
-    if (!this.songTitle || this.songTitle.trim() === '') {
-      return '暂无播放';
-    }
-    return this.songTitle;
-  }
+  // 播放列表信息
+  @LocalStorageProp('playlistInfo') playlistInfo: Record<string, Object> = {};
+  @LocalStorageProp('currentIndex') currentIndex: number = 0;
+  @LocalStorageProp('totalCount') totalCount: number = 0;
 
-  /**
-   * 格式化艺术家显示
-   */
-  private getDisplayArtist(): string {
-    if (!this.songArtist || this.songArtist.trim() === '') {
-      return '未知艺术家';
-    }
-    return this.songArtist;
-  }
+  // 时间信息
+  @LocalStorageProp('timeInfo') timeInfo: Record<string, Object> = {};
+  @LocalStorageProp('currentTimeText') currentTimeText: string = '00:00';
+  @LocalStorageProp('totalTimeText') totalTimeText: string = '00:00';
+  @LocalStorageProp('progressPercentage') progressPercentage: number = 0;
+
+  // 兼容性属性(用于获取复合状态)
+  @LocalStorageProp('imgName') imgName: string = ''; // 图片文件名,用于memory://协议
+  @LocalStorageProp('isFavorite') isFavorite: boolean = false;
   /**
    * 检查是否为网络URL
    */
@@ -69,135 +71,6 @@ struct PlayerWidgetRectangle {
     return url.startsWith('http://') || url.startsWith('https://');
   }
 
-  /**
-   * 生成渐变色
-   * 根据封面图片的主色调生成合适的渐变背景
-   */
-  private generateGradientColors(): [string, number][] {
-    // 如果没有封面或颜色信息,使用默认渐变
-    if (!this.imageColorHex || this.imageColorHex.trim() === '' || this.imageColorHex === '2A2A2A') {
-      return this.getDefaultGradientColors();
-    }
-
-    const baseColor = this.imageColorHex;
-    
-    // 解析RGB值
-    const r = parseInt(baseColor.substring(0, 2), 16);
-    const g = parseInt(baseColor.substring(2, 4), 16);
-    const b = parseInt(baseColor.substring(4, 6), 16);
-    
-    // 计算亮度,用于判断是深色还是浅色
-    const brightness = (r * 299 + g * 587 + b * 114) / 1000;
-    
-    // 根据亮度调整渐变策略
-    if (brightness < 60) {
-      // 深色封面:从更深的色调渐变到原色调
-      return this.generateDarkGradient(r, g, b);
-    } else if (brightness > 180) {
-      // 浅色封面:降低亮度,创建柔和渐变
-      return this.generateLightGradient(r, g, b);
-    } else {
-      // 中等亮度:标准渐变
-      return this.generateStandardGradient(r, g, b);
-    }
-  }
-
-  /**
-   * 生成深色渐变(适用于深色封面)
-   */
-  private generateDarkGradient(r: number, g: number, b: number): [string, number][] {
-    // 生成更深的起始色
-    const darkerR = Math.max(0, Math.floor(r * 0.2));
-    const darkerG = Math.max(0, Math.floor(g * 0.2));
-    const darkerB = Math.max(0, Math.floor(b * 0.2));
-    
-    // 中间色稍微提亮
-    const midR = Math.floor(r * 0.6);
-    const midG = Math.floor(g * 0.6);
-    const midB = Math.floor(b * 0.6);
-    
-    // 终点色适度提亮
-    const lightR = Math.min(255, Math.floor(r * 1.2));
-    const lightG = Math.min(255, Math.floor(g * 1.2));
-    const lightB = Math.min(255, Math.floor(b * 1.2));
-    
-    const darkerColor = `#ff${darkerR.toString(16).padStart(2, '0')}${darkerG.toString(16).padStart(2, '0')}${darkerB.toString(16).padStart(2, '0')}`;
-    const midColor = `#ff${midR.toString(16).padStart(2, '0')}${midG.toString(16).padStart(2, '0')}${midB.toString(16).padStart(2, '0')}`;
-    const lightColor = `#ff${lightR.toString(16).padStart(2, '0')}${lightG.toString(16).padStart(2, '0')}${lightB.toString(16).padStart(2, '0')}`;
-    
-    return [
-      [darkerColor, 0.0],
-      [midColor, 0.6],
-      [lightColor, 1.0]
-    ];
-  }
-
-  /**
-   * 生成浅色渐变(适用于浅色封面)
-   */
-  private generateLightGradient(r: number, g: number, b: number): [string, number][] {
-    // 大幅降低亮度作为起始色
-    const darkerR = Math.floor(r * 0.3);
-    const darkerG = Math.floor(g * 0.3);
-    const darkerB = Math.floor(b * 0.3);
-    
-    // 中间色适度降低亮度
-    const midR = Math.floor(r * 0.5);
-    const midG = Math.floor(g * 0.5);
-    const midB = Math.floor(b * 0.5);
-    
-    // 终点色保持相对较暗,避免过亮
-    const endR = Math.floor(r * 0.7);
-    const endG = Math.floor(g * 0.7);
-    const endB = Math.floor(b * 0.7);
-    
-    const darkerColor = `#ff${darkerR.toString(16).padStart(2, '0')}${darkerG.toString(16).padStart(2, '0')}${darkerB.toString(16).padStart(2, '0')}`;
-    const midColor = `#ff${midR.toString(16).padStart(2, '0')}${midG.toString(16).padStart(2, '0')}${midB.toString(16).padStart(2, '0')}`;
-    const endColor = `#ff${endR.toString(16).padStart(2, '0')}${endG.toString(16).padStart(2, '0')}${endB.toString(16).padStart(2, '0')}`;
-    
-    return [
-      [darkerColor, 0.0],
-      [midColor, 0.5],
-      [endColor, 1.0]
-    ];
-  }
-
-  /**
-   * 生成标准渐变(适用于中等亮度封面)
-   */
-  private generateStandardGradient(r: number, g: number, b: number): [string, number][] {
-    // 生成较深的颜色作为渐变起始
-    const darkerR = Math.max(0, Math.floor(r * 0.4));
-    const darkerG = Math.max(0, Math.floor(g * 0.4));
-    const darkerB = Math.max(0, Math.floor(b * 0.4));
-    
-    // 生成中等亮度的颜色
-    const midR = Math.floor(r * 0.7);
-    const midG = Math.floor(g * 0.7);
-    const midB = Math.floor(b * 0.7);
-    
-    const darkerColor = `#ff${darkerR.toString(16).padStart(2, '0')}${darkerG.toString(16).padStart(2, '0')}${darkerB.toString(16).padStart(2, '0')}`;
-    const midColor = `#ff${midR.toString(16).padStart(2, '0')}${midG.toString(16).padStart(2, '0')}${midB.toString(16).padStart(2, '0')}`;
-    const originalColor = `#ff${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
-    
-    return [
-      [darkerColor, 0.0],
-      [midColor, 0.5],
-      [originalColor, 1.0]
-    ];
-  }
-
-  /**
-   * 获取默认渐变色(当没有封面时使用)
-   */
-  private getDefaultGradientColors(): [string, number][] {
-    return [
-      ['#ff1a1a1a', 0.0],  // 深灰色
-      ['#ff2a2a2a', 0.5],  // 中灰色
-      ['#ff3a3a3a', 1.0]   // 浅灰色
-    ];
-  }
-
   /**
    * 获取专辑封面
    */
@@ -225,7 +98,7 @@ struct PlayerWidgetRectangle {
   build() {
     RelativeContainer() {
       // 歌曲标题
-      Text(this.getDisplayTitle())
+      Text(this.songTitle)
         .fontSize(16)
         .fontWeight(FontWeight.Bold)
         .width('60%')
@@ -237,7 +110,7 @@ struct PlayerWidgetRectangle {
         .id('musicTitle')
 
       // 艺术家名称
-      Text(this.getDisplayArtist())
+      Text(this.songArtist)
         .fontSize(12)
         .fontColor('#CCFFFFFF')
         .fontWeight(FontWeight.Normal)

+ 36 - 12
entry/src/main/ets/widget/pages/PlayerWidgetSmall.ets

@@ -7,39 +7,64 @@
 @Entry
 @Component
 struct PlayerWidgetSmall {
-  // 卡片数据属性 - 使用LocalStorageProp与FormExtensionAbility通信
+  // 卡片数据属性 - 适配新的数据结构
   @LocalStorageProp('formId') formId: string = '202501';
+
+  // VideoItem 主要数据
+  @LocalStorageProp('id') songId: string = '';
+  @LocalStorageProp('name') songTitle: string = 'Dream It Possible';
+  @LocalStorageProp('artist') songArtist: string = 'Delacey';
+  @LocalStorageProp('album') songAlbum: string = '未知专辑';
+  @LocalStorageProp('pixelMapPath') coverImage: string = '';
+  @LocalStorageProp('duration') songDuration: number = 0;
+  @LocalStorageProp('filePath') songFilePath: string = '';
+
+  // PlayerState 播放状态数据
+  @LocalStorageProp('playerState') playerState: Record<string, Object> = {};
+
+  // 平铺状态数据(优先使用)
   @LocalStorageProp('isPlaying') isPlaying: boolean = false;
-  @LocalStorageProp('songTitle') songTitle: string = 'Dream It Possible';
-  @LocalStorageProp('songArtist') songArtist: string = 'Delacey';
-  @LocalStorageProp('coverImage') coverImage: string = '';
+  @LocalStorageProp('isPaused') isPaused: boolean = true;
+  @LocalStorageProp('isLoading') isLoading: boolean = false;
   @LocalStorageProp('hasNext') hasNext: boolean = false;
   @LocalStorageProp('hasPrevious') hasPrevious: boolean = false;
-  @LocalStorageProp('isLoading') isLoading: boolean = false;
-  @LocalStorageProp('imageColorHex') imageColorHex: string = '2A2A2A';
+
+  // 播放列表信息
+  @LocalStorageProp('playlistInfo') playlistInfo: Record<string, Object> = {};
+  @LocalStorageProp('currentIndex') currentIndex: number = 0;
+  @LocalStorageProp('totalCount') totalCount: number = 0;
+
+  // 时间信息
+  @LocalStorageProp('timeInfo') timeInfo: Record<string, Object> = {};
+  @LocalStorageProp('currentTimeText') currentTimeText: string = '00:00';
+  @LocalStorageProp('totalTimeText') totalTimeText: string = '00:00';
+  @LocalStorageProp('progressPercentage') progressPercentage: number = 0;
+
+  // 兼容性属性(用于获取复合状态)
   @LocalStorageProp('imgName') imgName: string = ''; // 图片文件名,用于memory://协议
+  @LocalStorageProp('isFavorite') isFavorite: boolean = false;
 
   /**
    * 获取专辑封面
    */
   private getCoverImage(): Resource | string {
-    console.info(`Heanup PlayerWidgetSquare: getCoverImage called, coverImage='${this.coverImage}', imgName='${this.imgName}'`);
+    console.info(`Heanup PlayerWidgetSmall: getCoverImage called, coverImage='${this.coverImage}', imgName='${this.imgName}'`);
 
     // 优先使用通过formImages传递的本地图片(支持网络图片下载后的显示)
     if (this.imgName && this.imgName.trim() !== '') {
       const memoryUrl = 'memory://' + this.imgName;
-      console.info(`Heanup PlayerWidgetSquare: Using memory image: ${memoryUrl}`);
+      console.info(`Heanup PlayerWidgetSmall: Using memory image: ${memoryUrl}`);
       return memoryUrl;
     }
 
     // 如果有coverImage且不是网络URL,使用本地路径
     if (this.coverImage && this.coverImage.trim() !== '' && !this.isNetworkUrl(this.coverImage)) {
-      console.info(`Heanup PlayerWidgetSquare: Using local cover image: ${this.coverImage}`);
+      console.info(`Heanup PlayerWidgetSmall: Using local cover image: ${this.coverImage}`);
       return this.coverImage;
     }
 
     // 默认使用内置图片
-    console.info(`Heanup PlayerWidgetSquare: Using default cover image`);
+    console.info(`Heanup PlayerWidgetSmall: Using default cover image`);
     return $r('app.media.ic_avatar4'); // 使用默认专辑封面
   }
 
@@ -96,8 +121,7 @@ struct PlayerWidgetSmall {
             'abilityName': 'EntryAbility',
             'params': {
               'formId': this.formId,
-              'method': 'playPause',
-              'widgetIsPlaying': this.isPlaying // 传递卡片当前显示的播放状态
+              'method': 'playPause'
             }
           });
         }

+ 33 - 26
entry/src/main/ets/widget/pages/PlayerWidgetSquare.ets

@@ -24,35 +24,42 @@ const PlayAlignRules: Record<string, Record<string, string | VerticalAlign | Hor
 @Entry
 @Component
 struct PlayerWidgetSquare {
-  // 卡片数据属性 - 使用LocalStorageProp与FormExtensionAbility通信
+  // 卡片数据属性 - 适配新的数据结构
   @LocalStorageProp('formId') formId: string = '202503';
+
+  // VideoItem 主要数据
+  @LocalStorageProp('id') songId: string = '';
+  @LocalStorageProp('name') songTitle: string = 'Dream It Possible';
+  @LocalStorageProp('artist') songArtist: string = 'Delacey';
+  @LocalStorageProp('album') songAlbum: string = '未知专辑';
+  @LocalStorageProp('pixelMapPath') coverImage: string = '';
+  @LocalStorageProp('duration') songDuration: number = 0;
+  @LocalStorageProp('filePath') songFilePath: string = '';
+
+  // PlayerState 播放状态数据
+  @LocalStorageProp('playerState') playerState: Record<string, Object> = {};
+
+  // 平铺状态数据(优先使用)
   @LocalStorageProp('isPlaying') isPlaying: boolean = false;
-  @LocalStorageProp('songTitle') songTitle: string = 'Dream It Possible';
-  @LocalStorageProp('songArtist') songArtist: string = 'Delacey';
-  @LocalStorageProp('coverImage') coverImage: string = '';
+  @LocalStorageProp('isPaused') isPaused: boolean = true;
   @LocalStorageProp('isLoading') isLoading: boolean = false;
-  @LocalStorageProp('imageColorHex') imageColorHex: string = '2A2A2A';
-  @LocalStorageProp('imgName') imgName: string = ''; // 图片文件名,用于memory://协议
+  @LocalStorageProp('hasNext') hasNext: boolean = false;
+  @LocalStorageProp('hasPrevious') hasPrevious: boolean = false;
 
-  /**
-   * 格式化歌曲标题显示
-   */
-  private getDisplayTitle(): string {
-    if (!this.songTitle || this.songTitle.trim() === '') {
-      return '暂无播放';
-    }
-    return this.songTitle;
-  }
+  // 播放列表信息
+  @LocalStorageProp('playlistInfo') playlistInfo: Record<string, Object> = {};
+  @LocalStorageProp('currentIndex') currentIndex: number = 0;
+  @LocalStorageProp('totalCount') totalCount: number = 0;
 
-  /**
-   * 格式化艺术家显示
-   */
-  private getDisplayArtist(): string {
-    if (!this.songArtist || this.songArtist.trim() === '') {
-      return '未知艺术家';
-    }
-    return this.songArtist;
-  }
+  // 时间信息
+  @LocalStorageProp('timeInfo') timeInfo: Record<string, Object> = {};
+  @LocalStorageProp('currentTimeText') currentTimeText: string = '00:00';
+  @LocalStorageProp('totalTimeText') totalTimeText: string = '00:00';
+  @LocalStorageProp('progressPercentage') progressPercentage: number = 0;
+
+  // 兼容性属性(用于获取复合状态)
+  @LocalStorageProp('imgName') imgName: string = ''; // 图片文件名,用于memory://协议
+  @LocalStorageProp('isFavorite') isFavorite: boolean = false;
 
   /**
    * 获取专辑封面
@@ -88,7 +95,7 @@ struct PlayerWidgetSquare {
   build() {
     RelativeContainer() {
       // 歌曲标题 - 参考华为官方样式
-      Text(this.getDisplayTitle())
+      Text(this.songTitle)
         .fontSize(14)
         .fontWeight(700)
         .width('85%')
@@ -98,7 +105,7 @@ struct PlayerWidgetSquare {
         .id('musicTitle')
 
       // 艺术家名称 - 参考华为官方样式
-      Text(this.getDisplayArtist())
+      Text(this.songArtist)
         .fontSize(12)
         .fontColor('#CCFFFFFF')
         .fontWeight(500)