import commonEventManager from '@ohos.commonEventManager'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { common } from '@kit.AbilityKit'; import { VideoItem } from '../../viewmodel/VideoItem'; import { PlayerState, PlayMode } from './PlayerStateModel'; import { PlayProgress, WidgetData } from '../widget/WidgetTypes'; import { PLAYER_STATE_CHANGED_EVENT, PLAYER_SONG_CHANGED_EVENT, PLAYER_PROGRESS_CHANGED_EVENT, WIDGET_CONTROL_EVENT, WIDGET_REQUEST_STATE_EVENT } from '../widget/WidgetEventConstants'; const TAG = 'StateSyncService'; /** * 状态同步服务接口 */ export interface IStateSyncService { broadcastState(state: PlayerState): Promise; broadcastSongChange(song: VideoItem): Promise; broadcastProgress(progress: PlayProgress): Promise; subscribeToStateChanges(callback: StateChangeCallback): void; unsubscribeFromStateChanges(callback: StateChangeCallback): void; subscribeToWidgetControl(callback: WidgetControlCallback): void; unsubscribeFromWidgetControl(callback: WidgetControlCallback): void; initialize(context: common.UIAbilityContext): Promise; release(): void; } /** * 状态变化回调接口 */ export interface StateChangeCallback { onStateChanged?(state: PlayerState): void; onSongChanged?(song: VideoItem): void; onProgressChanged?(progress: PlayProgress): void; } /** * 卡片控制命令回调接口 */ export interface WidgetControlCallback { onPlayPause?(): Promise; onNextSong?(): Promise; onPreviousSong?(): Promise; onSeekTo?(position: number): Promise; onStateRequest?(): Promise; } /** * 卡片控制事件数据 */ interface WidgetControlEventData { command: string; params?: Record; timestamp: number; source: string; } /** * 播放状态数据接口 */ interface PlayStateBroadcast { isPlaying: boolean; isPaused: boolean; isLoading: boolean; currentPosition: number; duration: number; } /** * 当前歌曲数据接口 */ interface CurrentSongBroadcast { id: string; title: string; artist: string; album: string; filePath: string; duration: number; } /** * 进度数据接口 */ interface ProgressBroadcast { currentPosition: number; duration: number; percentage: number; currentTimeText: string; totalTimeText: string; } /** * 播放列表数据接口 */ interface PlaylistBroadcast { hasNext: boolean; hasPrevious: boolean; currentIndex: number; totalCount: number; } /** * 播放器状态广播数据 */ interface PlayerStateBroadcastData { playState: PlayStateBroadcast; currentSong: CurrentSongBroadcast; progress: ProgressBroadcast; playlist: PlaylistBroadcast; } /** * 歌曲变化广播数据 */ interface SongChangeBroadcastData { currentSong: CurrentSongBroadcast; playlist: PlaylistBroadcast; } /** * 进度更新广播数据 */ interface ProgressBroadcastData { currentPosition: number; duration: number; percentage: number; currentTimeText: string; totalTimeText: string; } /** * 状态同步服务实现 * 负责在不同组件和进程间同步播放状态 */ export class StateSyncService implements IStateSyncService { private static instance: StateSyncService | null = null; private context: common.UIAbilityContext | null = null; private stateChangeCallbacks: StateChangeCallback[] = []; private widgetControlCallbacks: WidgetControlCallback[] = []; private isInitialized: boolean = false; private lastStateBroadcastTime: number = 0; private lastProgressBroadcastTime: number = 0; private readonly STATE_BROADCAST_THROTTLE: number = 500; // 状态广播节流间隔(毫秒) private readonly PROGRESS_BROADCAST_THROTTLE: number = 1000; // 进度广播节流间隔(毫秒) private readonly COMMAND_EXECUTION_TIMEOUT: number = 5000; // 命令执行超时时间(毫秒) private constructor() {} public static getInstance(): StateSyncService { if (!StateSyncService.instance) { StateSyncService.instance = new StateSyncService(); } return StateSyncService.instance; } /** * 初始化状态同步服务 */ async initialize(context: common.UIAbilityContext): Promise { if (this.isInitialized) { hilog.info(0x0000, TAG, 'StateSyncService already initialized'); return; } try { this.context = context; // 注册状态请求事件监听器 await this.registerStateRequestListener(); // 注册卡片控制事件监听器 await this.registerWidgetControlListener(); this.isInitialized = true; hilog.info(0x0000, TAG, 'StateSyncService initialized successfully'); } catch (error) { hilog.error(0x0000, TAG, `Failed to initialize StateSyncService: ${error}`); throw new Error(`Failed to initialize StateSyncService: ${error}`); } } /** * 广播播放状态变化 */ async broadcastState(state: PlayerState): Promise { try { // 节流控制,避免过于频繁的广播 const now = Date.now(); if (now - this.lastStateBroadcastTime < this.STATE_BROADCAST_THROTTLE) { return; } this.lastStateBroadcastTime = now; const playStateBroadcast: PlayStateBroadcast = { isPlaying: state.isPlaying, isPaused: state.isPaused, isLoading: state.isLoading, currentPosition: state.currentPosition, duration: state.duration }; const currentSongBroadcast: CurrentSongBroadcast = { id: state.currentSong?.id || '', title: state.currentSong?.name || '暂无播放', artist: state.currentSong?.artist || '未知艺术家', album: state.currentSong?.album || '未知专辑', filePath: state.currentSong?.filePath || '', duration: state.currentSong?.duration ? parseInt(state.currentSong.duration) : 0 }; const progressBroadcast: ProgressBroadcast = { currentPosition: state.currentPosition, duration: state.duration, percentage: this.calculatePercentage(state.currentPosition, state.duration), currentTimeText: this.formatTime(Math.floor(state.currentPosition / 1000)), totalTimeText: this.formatTime(Math.floor(state.duration / 1000)) }; const broadcastData: PlayerStateBroadcastData = { playState: playStateBroadcast, currentSong: currentSongBroadcast, progress: progressBroadcast, playlist: { hasNext: state.hasNext || false, hasPrevious: state.hasPrevious || false, currentIndex: state.currentIndex, totalCount: state.totalCount || 0 } as PlaylistBroadcast }; const publishInfo: commonEventManager.CommonEventPublishData = { data: JSON.stringify(broadcastData) }; await commonEventManager.publish(PLAYER_STATE_CHANGED_EVENT, publishInfo, (err) => { if (err) { hilog.error(0x0000, TAG, `Failed to broadcast state: ${err}`); } else { hilog.info(0x0000, TAG, `State broadcasted: isPlaying=${state.isPlaying}, song=${state.currentSong?.name || 'none'}`); } }); // 通知本地监听器 this.notifyLocalStateListeners(state); } catch (error) { hilog.error(0x0000, TAG, `Failed to broadcast state: ${error}`); } } /** * 广播歌曲切换事件 */ async broadcastSongChange(song: VideoItem): Promise { try { const currentSongBroadcast: CurrentSongBroadcast = { id: song.id || '', title: song.name || '暂无播放', artist: song.artist || '未知艺术家', album: song.album || '未知专辑', filePath: song.filePath || '', duration: song.duration ? parseInt(song.duration) : 0 }; const playlistBroadcast: PlaylistBroadcast = { hasNext: false, // 这些值需要从播放列表服务获取 hasPrevious: false, currentIndex: 0, totalCount: 0 }; const broadcastData: SongChangeBroadcastData = { currentSong: currentSongBroadcast, playlist: playlistBroadcast }; const publishInfo: commonEventManager.CommonEventPublishData = { data: JSON.stringify(broadcastData) }; commonEventManager.publish(PLAYER_SONG_CHANGED_EVENT, publishInfo, (err) => { if (err) { hilog.error(0x0000, TAG, `Failed to broadcast song change: ${err}`); } else { hilog.info(0x0000, TAG, `Song change broadcasted: ${song.name} by ${song.artist || '未知艺术家'}`); } }); // 通知本地监听器 this.notifyLocalSongChangeListeners(song); } catch (error) { hilog.error(0x0000, TAG, `Failed to broadcast song change: ${error}`); } } /** * 广播播放进度更新 */ async broadcastProgress(progress: PlayProgress): Promise { try { // 节流控制,避免过于频繁的进度广播 const now = Date.now(); if (now - this.lastProgressBroadcastTime < this.PROGRESS_BROADCAST_THROTTLE) { return; } this.lastProgressBroadcastTime = now; const broadcastData: ProgressBroadcastData = { currentPosition: progress.currentPosition, duration: progress.duration, percentage: this.calculatePercentage(progress.currentPosition, progress.duration), currentTimeText: this.formatTime(Math.floor(progress.currentPosition / 1000)), totalTimeText: this.formatTime(Math.floor(progress.duration / 1000)) }; const publishInfo: commonEventManager.CommonEventPublishData = { data: JSON.stringify(broadcastData) }; await commonEventManager.publish(PLAYER_PROGRESS_CHANGED_EVENT, publishInfo, (err) => { if (err) { hilog.error(0x0000, TAG, `Failed to broadcast progress: ${err}`); } else { hilog.info(0x0000, TAG, `Progress broadcasted: ${broadcastData.percentage.toFixed(1)}% (${broadcastData.currentTimeText}/${broadcastData.totalTimeText})`); } }); // 通知本地监听器 this.notifyLocalProgressListeners(progress); } catch (error) { hilog.error(0x0000, TAG, `Failed to broadcast progress: ${error}`); } } /** * 订阅状态变化 */ subscribeToStateChanges(callback: StateChangeCallback): void { if (this.stateChangeCallbacks.indexOf(callback) === -1) { this.stateChangeCallbacks.push(callback); hilog.info(0x0000, TAG, `State change callback registered, total: ${this.stateChangeCallbacks.length}`); } } /** * 取消订阅状态变化 */ unsubscribeFromStateChanges(callback: StateChangeCallback): void { const index = this.stateChangeCallbacks.indexOf(callback); if (index !== -1) { this.stateChangeCallbacks.splice(index, 1); hilog.info(0x0000, TAG, `State change callback unregistered, remaining: ${this.stateChangeCallbacks.length}`); } } /** * 订阅卡片控制事件 */ subscribeToWidgetControl(callback: WidgetControlCallback): void { if (this.widgetControlCallbacks.indexOf(callback) === -1) { this.widgetControlCallbacks.push(callback); hilog.info(0x0000, TAG, `Widget control callback registered, total: ${this.widgetControlCallbacks.length}`); } } /** * 取消订阅卡片控制事件 */ unsubscribeFromWidgetControl(callback: WidgetControlCallback): void { const index = this.widgetControlCallbacks.indexOf(callback); if (index !== -1) { this.widgetControlCallbacks.splice(index, 1); hilog.info(0x0000, TAG, `Widget control callback unregistered, remaining: ${this.widgetControlCallbacks.length}`); } } /** * 释放资源 */ release(): void { try { this.stateChangeCallbacks = []; this.widgetControlCallbacks = []; this.isInitialized = false; hilog.info(0x0000, TAG, 'StateSyncService released'); } catch (error) { hilog.error(0x0000, TAG, `Failed to release StateSyncService: ${error}`); } } // ==================== 私有辅助方法 ==================== /** * 注册状态请求事件监听器 */ private async registerStateRequestListener(): Promise { try { const subscribeInfo: commonEventManager.CommonEventSubscribeInfo = { events: [WIDGET_REQUEST_STATE_EVENT] }; const subscriber = await commonEventManager.createSubscriber(subscribeInfo); await commonEventManager.subscribe(subscriber, (err, data: commonEventManager.CommonEventData) => { if (!err) { hilog.info(0x0000, TAG, `Received state request from widget: ${data.event}`); this.handleStateRequest(data); } else { hilog.error(0x0000, TAG, `State request listener error: ${JSON.stringify(err)}`); } }); hilog.info(0x0000, TAG, 'State request listener registered successfully'); } catch (error) { hilog.error(0x0000, TAG, `Failed to register state request listener: ${error}`); } } /** * 注册卡片控制事件监听器 */ private async registerWidgetControlListener(): Promise { try { const subscribeInfo: commonEventManager.CommonEventSubscribeInfo = { events: [WIDGET_CONTROL_EVENT] }; const subscriber = await commonEventManager.createSubscriber(subscribeInfo); commonEventManager.subscribe(subscriber, (err, data: commonEventManager.CommonEventData) => { if (!err) { hilog.info(0x0000, TAG, `Received widget control command: ${data.event}`); this.handleWidgetControlCommand(data); } else { hilog.error(0x0000, TAG, `Widget control listener error: ${JSON.stringify(err)}`); } }); hilog.info(0x0000, TAG, 'Widget control listener registered successfully'); } catch (error) { hilog.error(0x0000, TAG, `Failed to register widget control listener: ${error}`); } } /** * 处理状态请求 */ private handleStateRequest(eventData: commonEventManager.CommonEventData): void { try { const requestData: Record = JSON.parse(eventData.data || '{}'); const source: string = requestData.source || 'unknown'; hilog.info(0x0000, TAG, `Handling state request from: ${source}`); // 通知控制回调处理状态请求 this.notifyWidgetControlCallbacks('onStateRequest'); } catch (error) { hilog.error(0x0000, TAG, `Failed to handle state request: ${error}`); } } /** * 处理卡片控制命令 */ private async handleWidgetControlCommand(eventData: commonEventManager.CommonEventData): Promise { try { const controlData: WidgetControlEventData = JSON.parse(eventData.data || '{}'); const command = controlData.command; const params = controlData.params; const source = controlData.source; const timestamp = controlData.timestamp; hilog.info(0x0000, TAG, `Processing widget control command: ${command} from ${source}`); // 验证命令时效性(防止过期命令执行) const now = Date.now(); if (now - timestamp > this.COMMAND_EXECUTION_TIMEOUT) { hilog.warn(0x0000, TAG, `Command ${command} expired, ignoring (age: ${now - timestamp}ms)`); return; } // 验证命令来源 if (!this.isValidCommandSource(source)) { hilog.warn(0x0000, TAG, `Invalid command source: ${source}, ignoring command ${command}`); return; } // 执行命令 await this.executeWidgetCommand(command, params); hilog.info(0x0000, TAG, `Widget control command ${command} executed successfully`); } catch (error) { hilog.error(0x0000, TAG, `Failed to handle widget control command: ${error}`); } } /** * 通知本地状态监听器 */ private notifyLocalStateListeners(state: PlayerState): void { this.stateChangeCallbacks.forEach(callback => { try { if (callback.onStateChanged) { callback.onStateChanged(state); } } catch (error) { hilog.error(0x0000, TAG, `Error in state change callback: ${error}`); } }); } /** * 通知本地歌曲变化监听器 */ private notifyLocalSongChangeListeners(song: VideoItem): void { this.stateChangeCallbacks.forEach(callback => { try { if (callback.onSongChanged) { callback.onSongChanged(song); } } catch (error) { hilog.error(0x0000, TAG, `Error in song change callback: ${error}`); } }); } /** * 通知本地进度监听器 */ private notifyLocalProgressListeners(progress: PlayProgress): void { this.stateChangeCallbacks.forEach(callback => { try { if (callback.onProgressChanged) { callback.onProgressChanged(progress); } } catch (error) { hilog.error(0x0000, TAG, `Error in progress change callback: ${error}`); } }); } /** * 执行卡片控制命令 */ private async executeWidgetCommand(command: string, params?: Record): Promise { try { switch (command) { case 'PLAY_PAUSE': await this.notifyWidgetControlCallbacks('onPlayPause'); break; case 'NEXT_SONG': await this.notifyWidgetControlCallbacks('onNextSong'); break; case 'PREV_SONG': await this.notifyWidgetControlCallbacks('onPreviousSong'); break; case 'SEEK_TO': const position = params?.position as number || 0; await this.notifyWidgetControlCallbacks('onSeekTo', position); break; default: hilog.warn(0x0000, TAG, `Unknown widget command: ${command}`); } } catch (error) { hilog.error(0x0000, TAG, `Failed to execute widget command ${command}: ${error}`); throw new Error(`Failed to execute widget command ${command}: ${error}`); } } /** * 验证命令来源是否有效 */ private isValidCommandSource(source: string): boolean { const validSources = ['widget', 'form', 'card', 'desktop_widget']; return validSources.includes(source); } /** * 通知卡片控制回调 */ private async notifyWidgetControlCallbacks(method: string, ...args: Object[]): Promise { const promises: Promise[] = []; this.widgetControlCallbacks.forEach(callback => { try { let promise: Promise | undefined; switch (method) { case 'onPlayPause': promise = callback.onPlayPause?.(); break; case 'onNextSong': promise = callback.onNextSong?.(); break; case 'onPreviousSong': promise = callback.onPreviousSong?.(); break; case 'onSeekTo': promise = callback.onSeekTo?.(args[0] as number); break; case 'onStateRequest': promise = callback.onStateRequest?.(); break; } if (promise) { promises.push(promise); } } catch (error) { hilog.error(0x0000, TAG, `Error in widget control callback ${method}: ${error}`); } }); // 等待所有回调执行完成 if (promises.length > 0) { try { await Promise.all(promises); hilog.info(0x0000, TAG, `All widget control callbacks for ${method} completed`); } catch (error) { hilog.error(0x0000, TAG, `Some widget control callbacks for ${method} failed: ${error}`); } } } /** * 计算播放进度百分比 */ private calculatePercentage(current: number, total: number): number { if (total <= 0) return 0; return Math.min(100, Math.max(0, (current / total) * 100)); } /** * 格式化时间显示 */ private 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')}`; } }