Browse Source

集成播放卡片

chendeben 1 năm trước cách đây
mục cha
commit
5342bf1527

+ 104 - 0
entry/src/main/ets/common/widget/GlobalWidgetService.ets

@@ -0,0 +1,104 @@
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { WidgetData } from './WidgetTypes';
+import { AvSessionWidgetListener } from './AvSessionWidgetListener';
+
+const TAG = 'GlobalWidgetService';
+
+/**
+ * 全局卡片服务
+ * 用于主应用直接更新卡片数据,绕过跨进程通信限制
+ */
+export class GlobalWidgetService {
+  private static instance: GlobalWidgetService | null = null;
+  private avSessionListener: AvSessionWidgetListener;
+
+  private constructor() {
+    this.avSessionListener = AvSessionWidgetListener.getInstance();
+  }
+
+  public static getInstance(): GlobalWidgetService {
+    if (!GlobalWidgetService.instance) {
+      GlobalWidgetService.instance = new GlobalWidgetService();
+    }
+    return GlobalWidgetService.instance;
+  }
+
+  /**
+   * 更新卡片数据(主应用调用)
+   */
+  public updateWidgetData(data: WidgetData): void {
+    try {
+      // 在全局服务层面也进行按钮状态验证和修复
+      const correctedData = this.validateAndFixButtonStates(data);
+      
+      // 更新AvSession监听器的数据
+      this.avSessionListener.updateWidgetData(correctedData);
+      
+      hilog.info(0x0000, TAG, `Widget data updated globally: isPlaying=${correctedData.playState.isPlaying}, title=${correctedData.currentSong.title}, hasNext=${correctedData.playlist.hasNext}, hasPrevious=${correctedData.playlist.hasPrevious}, currentIndex=${correctedData.playlist.currentIndex}, totalCount=${correctedData.playlist.totalCount}`);
+    } catch (error) {
+      hilog.error(0x0000, TAG, `Failed to update widget data globally: ${error}`);
+    }
+  }
+
+  /**
+   * 验证和修复按钮状态
+   */
+  private validateAndFixButtonStates(data: WidgetData): WidgetData {
+    hilog.info(0x0000, TAG, `Validating button states: original hasNext=${data.playlist.hasNext}, hasPrevious=${data.playlist.hasPrevious}, currentIndex=${data.playlist.currentIndex}, totalCount=${data.playlist.totalCount}`);
+    
+    // 创建新的 WidgetData 对象
+    const correctedData: WidgetData = {
+      playState: {
+        isPlaying: data.playState.isPlaying,
+        isPaused: data.playState.isPaused,
+        isLoading: data.playState.isLoading
+      },
+      currentSong: {
+        id: data.currentSong.id,
+        title: data.currentSong.title,
+        artist: data.currentSong.artist,
+        album: data.currentSong.album,
+        coverImagePath: data.currentSong.coverImagePath,
+        duration: data.currentSong.duration
+      },
+      progress: {
+        currentPosition: data.progress.currentPosition,
+        duration: data.progress.duration,
+        percentage: data.progress.percentage,
+        currentTimeText: data.progress.currentTimeText,
+        totalTimeText: data.progress.totalTimeText
+      },
+      playlist: {
+        hasNext: data.playlist.hasNext,
+        hasPrevious: data.playlist.hasPrevious,
+        currentIndex: data.playlist.currentIndex,
+        totalCount: data.playlist.totalCount
+      },
+      config: {
+        size: data.config.size,
+        theme: data.config.theme,
+        showProgress: data.config.showProgress,
+        showCover: data.config.showCover
+      }
+    };
+    
+    if (data.playlist.totalCount > 1) {
+      const correctHasNext = data.playlist.currentIndex < data.playlist.totalCount - 1;
+      const correctHasPrevious = data.playlist.currentIndex > 0;
+      
+      // 更新按钮状态
+      correctedData.playlist.hasNext = correctHasNext;
+      correctedData.playlist.hasPrevious = correctHasPrevious;
+      
+      if (correctHasNext !== data.playlist.hasNext || correctHasPrevious !== data.playlist.hasPrevious) {
+        hilog.info(0x0000, TAG, `Fixed button states in GlobalWidgetService: hasNext=${correctHasNext}, hasPrevious=${correctHasPrevious}`);
+      }
+    } else {
+      // 单首歌或无歌曲时,禁用所有按钮
+      correctedData.playlist.hasNext = false;
+      correctedData.playlist.hasPrevious = false;
+    }
+    
+    return correctedData;
+  }
+}

+ 203 - 0
entry/src/main/ets/common/widget/WidgetDataFlowTest.ets

@@ -0,0 +1,203 @@
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { PlayerControlService } from './PlayerControlService';
+import { AvSessionWidgetListener } from './AvSessionWidgetListener';
+import { WidgetDataManager } from './WidgetDataManager';
+import { WidgetData, WidgetSize, WidgetTheme } from './WidgetTypes';
+
+const TAG = 'WidgetDataFlowTest';
+
+/**
+ * 卡片数据流测试工具
+ * 用于调试和验证数据流是否正常工作
+ */
+export class WidgetDataFlowTest {
+  private playerControlService: PlayerControlService;
+  private avSessionListener: AvSessionWidgetListener;
+  private widgetDataManager: WidgetDataManager;
+
+  constructor() {
+    this.playerControlService = new PlayerControlService();
+    this.avSessionListener = AvSessionWidgetListener.getInstance();
+    this.widgetDataManager = new WidgetDataManager();
+  }
+
+  /**
+   * 测试数据流
+   */
+  public async testDataFlow(): Promise<void> {
+    hilog.info(0x0000, TAG, '🔍 Starting widget data flow test...');
+
+    // 1. 测试 AvSession 监听器
+    await this.testAvSessionListener();
+
+    // 2. 测试 PlayerControlService
+    await this.testPlayerControlService();
+
+    // 3. 测试 WidgetDataManager
+    await this.testWidgetDataManager();
+
+    // 4. 测试端到端数据流
+    await this.testEndToEndFlow();
+
+    hilog.info(0x0000, TAG, '✅ Widget data flow test completed');
+  }
+
+  /**
+   * 测试 AvSession 监听器
+   */
+  private async testAvSessionListener(): Promise<void> {
+    hilog.info(0x0000, TAG, '📡 Testing AvSession listener...');
+
+    const testData = this.createTestWidgetData();
+
+    // 注册监听器
+    this.avSessionListener.addStateListener((data: WidgetData) => {
+      hilog.info(0x0000, TAG, `✅ AvSession listener received data: isPlaying=${data.playState.isPlaying}, title=${data.currentSong.title}`);
+    });
+
+    // 更新数据
+    this.avSessionListener.updateWidgetData(testData);
+
+    // 获取当前数据
+    const currentData = this.avSessionListener.getCurrentWidgetData();
+    hilog.info(0x0000, TAG, `📊 AvSession current data: hasNext=${currentData.playlist.hasNext}, hasPrevious=${currentData.playlist.hasPrevious}`);
+  }
+
+  /**
+   * 测试 PlayerControlService
+   */
+  private async testPlayerControlService(): Promise<void> {
+    hilog.info(0x0000, TAG, '🎮 Testing PlayerControlService...');
+
+    // 注册监听器
+    this.playerControlService.registerStateListener((data: WidgetData) => {
+      hilog.info(0x0000, TAG, `✅ PlayerControlService listener received data: isPlaying=${data.playState.isPlaying}, title=${data.currentSong.title}`);
+    });
+
+    // 获取当前状态
+    const currentState = await this.playerControlService.getCurrentPlayState();
+    hilog.info(0x0000, TAG, `📊 PlayerControlService current state: hasNext=${currentState.playlist.hasNext}, hasPrevious=${currentState.playlist.hasPrevious}`);
+  }
+
+  /**
+   * 测试 WidgetDataManager
+   */
+  private async testWidgetDataManager(): Promise<void> {
+    hilog.info(0x0000, TAG, '📋 Testing WidgetDataManager...');
+
+    const testFormId = 'test_form_123';
+    const testData = this.createTestWidgetData();
+
+    // 保存数据
+    await this.widgetDataManager.saveWidgetData(testFormId, testData);
+    hilog.info(0x0000, TAG, `💾 Test data saved for form: ${testFormId}`);
+
+    // 读取数据
+    const retrievedData = await this.widgetDataManager.getWidgetData(testFormId);
+    hilog.info(0x0000, TAG, `📖 Test data retrieved: title=${retrievedData.currentSong.title}, hasNext=${retrievedData.playlist.hasNext}`);
+
+    // 模拟更新卡片(不会真正更新,因为formId是测试用的)
+    try {
+      await this.widgetDataManager.updateWidget(testFormId, testData);
+      hilog.info(0x0000, TAG, `🔄 Mock widget update completed for: ${testFormId}`);
+    } catch (error) {
+      hilog.error(0x0000, TAG, `❌ Widget update failed (expected for test): ${error}`);
+    }
+
+    // 清理测试数据
+    await this.widgetDataManager.removeWidgetData(testFormId);
+    hilog.info(0x0000, TAG, `🗑️ Test data cleaned up for form: ${testFormId}`);
+  }
+
+  /**
+   * 测试端到端数据流
+   */
+  private async testEndToEndFlow(): Promise<void> {
+    hilog.info(0x0000, TAG, '🔄 Testing end-to-end data flow...');
+
+    let receivedByPlayerControl = false;
+    let receivedByAvSession = false;
+
+    // 注册 PlayerControlService 监听器
+    this.playerControlService.registerStateListener((data: WidgetData) => {
+      receivedByPlayerControl = true;
+      hilog.info(0x0000, TAG, `✅ End-to-end: PlayerControlService received data`);
+    });
+
+    // 注册 AvSession 监听器
+    this.avSessionListener.addStateListener((data: WidgetData) => {
+      receivedByAvSession = true;
+      hilog.info(0x0000, TAG, `✅ End-to-end: AvSession received data`);
+    });
+
+    // 模拟数据更新
+    const testData = this.createTestWidgetData();
+    this.avSessionListener.updateWidgetData(testData);
+
+    // 检查结果
+    setTimeout(() => {
+      hilog.info(0x0000, TAG, `📊 End-to-end test results:`);
+      hilog.info(0x0000, TAG, `   PlayerControlService received: ${receivedByPlayerControl}`);
+      hilog.info(0x0000, TAG, `   AvSession received: ${receivedByAvSession}`);
+      
+      if (receivedByPlayerControl && receivedByAvSession) {
+        hilog.info(0x0000, TAG, `✅ End-to-end data flow is working correctly!`);
+      } else {
+        hilog.warn(0x0000, TAG, `⚠️ End-to-end data flow has issues!`);
+      }
+    }, 1000);
+  }
+
+  /**
+   * 创建测试用的 WidgetData
+   */
+  private createTestWidgetData(): WidgetData {
+    return {
+      playState: {
+        isPlaying: true,
+        isPaused: false,
+        isLoading: false
+      },
+      currentSong: {
+        id: 'test_song_123',
+        title: '测试歌曲',
+        artist: '测试艺术家',
+        album: '测试专辑',
+        coverImagePath: '/test/cover.jpg',
+        duration: 240
+      },
+      progress: {
+        currentPosition: 60,
+        duration: 240,
+        percentage: 25,
+        currentTimeText: '01:00',
+        totalTimeText: '04:00'
+      },
+      playlist: {
+        hasNext: true,
+        hasPrevious: true,
+        currentIndex: 1,
+        totalCount: 5
+      },
+      config: {
+        size: WidgetSize.MEDIUM,
+        theme: WidgetTheme.AUTO,
+        showProgress: true,
+        showCover: true
+      }
+    };
+  }
+
+  /**
+   * 打印当前所有监听器状态
+   */
+  public printListenerStatus(): void {
+    hilog.info(0x0000, TAG, '📊 Current listener status:');
+    hilog.info(0x0000, TAG, `   PlayerControlService listeners: Available`);
+    hilog.info(0x0000, TAG, `   AvSession listeners: Available`);
+    
+    // 获取当前数据
+    const avSessionData = this.avSessionListener.getCurrentWidgetData();
+    hilog.info(0x0000, TAG, `   Current AvSession data: isPlaying=${avSessionData.playState.isPlaying}, title=${avSessionData.currentSong.title}`);
+  }
+}

+ 93 - 0
entry/src/main/ets/common/widget/WidgetDebugConfig.ets

@@ -0,0 +1,93 @@
+/**
+ * 卡片调试配置
+ * 用于控制日志输出和调试功能
+ */
+
+export const WidgetDebugConfig = {
+  // 是否启用详细日志
+  enableVerboseLogging: true,
+  
+  // 是否启用数据流测试
+  enableDataFlowTest: true,
+  
+  // 是否启用跨进程通信调试
+  enableCrossProcessDebug: true,
+  
+  // 是否启用监听器调试
+  enableListenerDebug: true,
+  
+  // 是否启用性能监控
+  enablePerformanceMonitoring: true,
+  
+  // 日志标签前缀
+  logTagPrefix: 'TTMusic-Widget',
+  
+  // 测试延迟时间(毫秒)
+  testDelays: {
+    initialUpdate: 500,
+    retryUpdate: 2000,
+    finalUpdate: 5000,
+    dataFlowTest: 3000
+  },
+  
+  // 错误重试次数
+  maxRetryCount: 3,
+  
+  // 缓存配置
+  cache: {
+    enabled: true,
+    expiryTime: 30000, // 30秒
+    maxSize: 100
+  }
+};
+
+/**
+ * 调试日志工具
+ */
+export class WidgetLogger {
+  private static logTagPrefix = WidgetDebugConfig.logTagPrefix;
+  
+  static info(tag: string, message: string): void {
+    if (WidgetDebugConfig.enableVerboseLogging) {
+      console.info(`[${this.logTagPrefix}-${tag}] ${message}`);
+    }
+  }
+  
+  static warn(tag: string, message: string): void {
+    console.warn(`[${this.logTagPrefix}-${tag}] ⚠️ ${message}`);
+  }
+  
+  static error(tag: string, message: string): void {
+    console.error(`[${this.logTagPrefix}-${tag}] ❌ ${message}`);
+  }
+  
+  static debug(tag: string, message: string): void {
+    if (WidgetDebugConfig.enableVerboseLogging) {
+      console.debug(`[${this.logTagPrefix}-${tag}] 🐛 ${message}`);
+    }
+  }
+  
+  static dataFlow(tag: string, message: string): void {
+    if (WidgetDebugConfig.enableDataFlowTest) {
+      console.info(`[${this.logTagPrefix}-DataFlow-${tag}] 🔄 ${message}`);
+    }
+  }
+  
+  static crossProcess(tag: string, message: string): void {
+    if (WidgetDebugConfig.enableCrossProcessDebug) {
+      console.info(`[${this.logTagPrefix}-CrossProcess-${tag}] 🌐 ${message}`);
+    }
+  }
+  
+  static listener(tag: string, message: string): void {
+    if (WidgetDebugConfig.enableListenerDebug) {
+      console.info(`[${this.logTagPrefix}-Listener-${tag}] 👂 ${message}`);
+    }
+  }
+  
+  static performance(tag: string, message: string): void {
+    if (WidgetDebugConfig.enablePerformanceMonitoring) {
+      console.info(`[${this.logTagPrefix}-Perf-${tag}] ⚡ ${message}`);
+    }
+  }
+}

+ 62 - 0
entry/src/main/ets/common/widget/WidgetIconManager.ets

@@ -0,0 +1,62 @@
+/**
+ * 卡片图标资源管理器
+ * 用于统一管理和验证卡片中使用的图标资源
+ */
+
+export class WidgetIconManager {
+  
+  /**
+   * 获取播放按钮图标
+   */
+  static getPlayIcon(): Resource {
+    return $r('app.media.hm_play');
+  }
+  
+  /**
+   * 获取暂停按钮图标
+   */
+  static getPauseIcon(): Resource {
+    return $r('app.media.hm_pause');
+  }
+  
+  /**
+   * 获取上一首按钮图标
+   */
+  static getPreviousIcon(): Resource {
+    return $r('app.media.ic_previous');
+  }
+  
+  /**
+   * 获取下一首按钮图标
+   */
+  static getNextIcon(): Resource {
+    return $r('app.media.ic_next');
+  }
+  
+  /**
+   * 获取加载图标
+   */
+  static getLoadingIcon(): Resource {
+    return $r('app.media.icon_load');
+  }
+  
+  /**
+   * 获取默认专辑封面
+   */
+  static getDefaultCover(): Resource {
+    return $r('app.media.bg_music');
+  }
+  
+  /**
+   * 验证图标资源是否可用
+   */
+  static validateIcons(): boolean {
+    try {
+      // 这里可以添加图标验证逻辑
+      return true;
+    } catch (error) {
+      console.error('Widget icon validation failed:', error);
+      return false;
+    }
+  }
+}