import commonEventManager from '@ohos.commonEventManager'; import Want from '@ohos.app.ability.Want'; import common from '@ohos.app.ability.common'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { WidgetData, WidgetCommand, EventData, RequestData, WidgetControlParams } from './WidgetTypes'; import { WidgetTypeHelpers } from './WidgetTypeHelpers'; import { WIDGET_CONTROL_EVENT, WIDGET_REQUEST_STATE_EVENT, PLAYER_STATE_CHANGED_EVENT, PLAYER_SONG_CHANGED_EVENT, PLAYER_PROGRESS_CHANGED_EVENT, APP_BUNDLE_NAME, APP_ABILITY_NAME } from './WidgetEventConstants'; import { AvSessionWidgetListener } from './AvSessionWidgetListener'; const TAG = 'Heanup PlayerControlService'; /** * 启动参数接口 */ interface LaunchParameters { page: string; source: string; timestamp: string; } /** * 播放器控制服务 * 负责与主应用的播放器进行通信和状态同步 */ export class PlayerControlService { private stateListeners: Array<(data: WidgetData) => void> = []; private isListenerRegistered: boolean = false; private avSessionListener: AvSessionWidgetListener; constructor() { this.avSessionListener = AvSessionWidgetListener.getInstance(); this.initializeEventListener(); this.initializeAvSessionListener(); } /** * 初始化事件监听器 */ private async initializeEventListener(): Promise { if (this.isListenerRegistered) { hilog.info(0x0000, TAG, 'Event listener already registered'); return; } try { // 监听播放状态变化事件 const subscribeInfo: commonEventManager.CommonEventSubscribeInfo = { events: [ PLAYER_STATE_CHANGED_EVENT, PLAYER_SONG_CHANGED_EVENT, PLAYER_PROGRESS_CHANGED_EVENT ] }; hilog.info(0x0000, TAG, `Subscribing to events: ${subscribeInfo.events.join(', ')}`); const subscriber = await commonEventManager.createSubscriber(subscribeInfo); await commonEventManager.subscribe(subscriber, (err, data: commonEventManager.CommonEventData) => { if (!err) { hilog.info(0x0000, TAG, `📡 CommonEvent received in Form process: ${data.event}`); hilog.info(0x0000, TAG, `📡 Event data length: ${data.data?.length || 0} characters`); this.handlePlayerStateChange(data); } else { hilog.error(0x0000, TAG, `❌ CommonEvent error: ${JSON.stringify(err)}`); } }); this.isListenerRegistered = true; hilog.info(0x0000, TAG, 'Event listener initialized successfully'); // 立即请求当前状态,确保新进程能获取到最新数据 setTimeout(() => { this.requestCurrentState(); }, 1000); } catch (error) { hilog.error(0x0000, TAG, `Failed to initialize event listener: ${error}`); } } /** * 请求当前播放状态 */ private async requestCurrentState(): Promise { try { const requestData: RequestData = { timestamp: Date.now(), source: 'widget_form_process_recovery' }; const requestInfo: commonEventManager.CommonEventPublishData = { data: JSON.stringify(requestData) }; await commonEventManager.publish(WIDGET_REQUEST_STATE_EVENT, requestInfo, (err) => { if (err) { hilog.error(0x0000, TAG, `Failed to request current state: ${err}`); } else { hilog.info(0x0000, TAG, 'Current state requested from main app for recovery'); } }); } catch (error) { hilog.error(0x0000, TAG, `Failed to request current state: ${error}`); } } /** * 强制重新连接和同步状态 */ async forceReconnect(): Promise { try { hilog.info(0x0000, TAG, 'Force reconnecting to main app...'); // 重新请求当前状态 await this.requestCurrentState(); // 等待一段时间后再次请求,确保能收到响应 setTimeout(async () => { await this.requestCurrentState(); }, 2000); hilog.info(0x0000, TAG, 'Force reconnect completed'); } catch (error) { hilog.error(0x0000, TAG, `Force reconnect failed: ${error}`); } } /** * 发送控制命令到主应用 */ async sendControlCommand(command: WidgetCommand, params?: WidgetControlParams): Promise { try { const defaultParams: WidgetControlParams = {}; const eventData: EventData = { command: command, params: params || defaultParams, timestamp: Date.now(), source: 'widget' }; // 发送CommonEvent到主应用 const publishInfo: commonEventManager.CommonEventPublishData = { data: JSON.stringify(eventData) }; await commonEventManager.publish(WIDGET_CONTROL_EVENT, publishInfo, (err) => { if (err) { hilog.error(0x0000, TAG, `Failed to publish event: ${err}`); } }); hilog.info(0x0000, TAG, `Control command sent: ${command}`); return true; } catch (error) { hilog.error(0x0000, TAG, `Failed to send control command: ${error}`); return false; } } /** * 获取当前播放状态 */ async getCurrentPlayState(): Promise { try { // 优先从AvSession获取当前状态 const avSessionData = this.avSessionListener.getCurrentWidgetData(); // 同时请求CommonEvent状态作为备用 const requestData: RequestData = { timestamp: Date.now(), source: 'widget_form_process' }; const requestInfo: commonEventManager.CommonEventPublishData = { data: JSON.stringify(requestData) }; commonEventManager.publish(WIDGET_REQUEST_STATE_EVENT, requestInfo, (err) => { if (err) { hilog.error(0x0000, TAG, `Failed to publish request state event: ${err}`); } }); return avSessionData; } catch (error) { hilog.error(0x0000, TAG, `Failed to get current play state: ${error}`); return this.getDefaultWidgetData(); } } /** * 初始化AvSession监听器 */ private initializeAvSessionListener(): void { try { // 注册AvSession状态监听器,这个监听器主要用于EntryFormAbility的全局监听 // 不要在这里注册,让EntryFormAbility直接注册到AvSessionWidgetListener hilog.info(0x0000, TAG, 'AvSession listener initialized successfully (no direct registration needed)'); } catch (error) { hilog.error(0x0000, TAG, `Failed to initialize AvSession listener: ${error}`); } } /** * 注册状态变化监听器 */ registerStateListener(callback: (data: WidgetData) => void): void { // 检查是否已经注册过相同的监听器,避免重复注册 if (this.stateListeners.indexOf(callback) === -1) { this.stateListeners.push(callback); hilog.info(0x0000, TAG, `State listener registered, total listeners: ${this.stateListeners.length}`); } else { hilog.warn(0x0000, TAG, 'State listener already registered, skipping'); return; } // 延迟获取当前状态,给主应用时间来广播真实状态 setTimeout(() => { try { const currentData = this.avSessionListener.getCurrentWidgetData(); hilog.info(0x0000, TAG, `Sending current data to listener: hasNext=${currentData.playlist.hasNext}, hasPrevious=${currentData.playlist.hasPrevious}`); callback(currentData); } catch (error) { hilog.error(0x0000, TAG, `Error getting current AvSession data: ${error}`); } }, 1500); // 延迟1.5秒,确保主应用已经广播了状态 } /** * 启动主应用 */ async launchMainApp(page?: string, params?: Record): Promise { try { interface LaunchParameters { page: string; source: string; timestamp: string; } const baseParams: LaunchParameters = { page: page || 'main', source: 'widget', // 标识来源是卡片 timestamp: Date.now().toString() }; // 创建Want对象 const want: Want = { bundleName: APP_BUNDLE_NAME, abilityName: APP_ABILITY_NAME, parameters: { page: baseParams.page, source: baseParams.source, timestamp: baseParams.timestamp } }; // 添加额外参数 if (params && want.parameters) { const paramKeys = Object.keys(params); for (let i = 0; i < paramKeys.length; i++) { const key = paramKeys[i]; want.parameters[key] = params[key]; } } const context = getContext() as common.UIAbilityContext; await context.startAbility(want); hilog.info(0x0000, TAG, `Main app launched with page: ${page}, params: ${JSON.stringify(params)}`); return true; } catch (error) { hilog.error(0x0000, TAG, `Failed to launch main app: ${error}`); // 如果启动失败,尝试启动到默认页面 try { const fallbackWant: Want = { bundleName: APP_BUNDLE_NAME, abilityName: APP_ABILITY_NAME }; const context = getContext() as common.UIAbilityContext; await context.startAbility(fallbackWant); hilog.info(0x0000, TAG, 'Main app launched with fallback method'); return true; } catch (fallbackError) { hilog.error(0x0000, TAG, `Fallback launch also failed: ${fallbackError}`); return false; } } } /** * 处理播放器状态变化 */ private handlePlayerStateChange(eventData: commonEventManager.CommonEventData): void { try { hilog.info(0x0000, TAG, `📨 Form process handling CommonEvent: ${eventData.event}`); hilog.info(0x0000, TAG, `📨 Event data: ${eventData.data?.substring(0, 200)}...`); const data = JSON.parse(eventData.data || '{}') as Object; let widgetData: WidgetData; if (eventData.event === PLAYER_PROGRESS_CHANGED_EVENT) { // 处理进度更新事件 widgetData = this.updateProgressData(data); } else { // 处理完整状态更新事件 widgetData = this.convertToWidgetData(data); } hilog.info(0x0000, TAG, `📨 Form process converted data: isPlaying=${widgetData.playState.isPlaying}, title=${widgetData.currentSong.title}`); // 只更新AvSession监听器的数据,避免重复通知 // AvSession监听器会自动通知所有注册的监听器 this.avSessionListener.updateWidgetData(widgetData); hilog.info(0x0000, TAG, `📨 Form process: ${eventData.event} handled, data updated in AvSession`); } catch (error) { hilog.error(0x0000, TAG, `❌ Form process failed to handle player state change: ${error}`); } } /** * 更新进度数据 */ private updateProgressData(progressData: Object): WidgetData { try { const data: Record = progressData as Record; // 获取当前缓存的数据,而不是默认数据 const currentData = this.avSessionListener.getCurrentWidgetData(); // 只更新进度相关数据,保持其他状态不变 currentData.progress = { currentPosition: (data['currentPosition'] as number) || 0, duration: (data['duration'] as number) || 0, percentage: (data['percentage'] as number) || 0, currentTimeText: (data['currentTimeText'] as string) || '00:00', totalTimeText: (data['totalTimeText'] as string) || '00:00' }; // 更新缓存 this.avSessionListener.updateWidgetData(currentData); hilog.info(0x0000, TAG, `Progress updated: ${currentData.progress.percentage.toFixed(1)}%, ${currentData.progress.currentTimeText}/${currentData.progress.totalTimeText}`); return currentData; } catch (error) { hilog.error(0x0000, TAG, `Failed to update progress data: ${error}`); return this.getDefaultWidgetData(); } } /** * 转换播放器数据为卡片数据格式 */ private convertToWidgetData(playerData: Object): WidgetData { try { const data: Record = playerData as Record; // 正确解析PlayerStateBroadcastData结构 const playState = (data['playState'] as Record) || {}; const currentSong = (data['currentSong'] as Record) || {}; const progress = (data['progress'] as Record) || {}; const playlist = (data['playlist'] as Record) || {}; // 检查是否接收到不完整的数据 const isPlaylistDataIncomplete = playlist['hasNext'] === undefined || playlist['hasPrevious'] === undefined || playlist['currentIndex'] === undefined || playlist['totalCount'] === undefined; if (isPlaylistDataIncomplete) { hilog.warn(0x0000, TAG, `Received incomplete playlist data, using cached data`); // 如果接收到不完整的数据,返回当前缓存的数据 const cachedData = this.avSessionListener.getCurrentWidgetData(); // 只更新非playlist的数据,保持playlist数据不变 const updatedData: WidgetData = { playState: { isPlaying: (playState['isPlaying'] as boolean) !== undefined ? (playState['isPlaying'] as boolean) : cachedData.playState.isPlaying, isPaused: (playState['isPaused'] as boolean) !== undefined ? (playState['isPaused'] as boolean) : cachedData.playState.isPaused, isLoading: (playState['isLoading'] as boolean) !== undefined ? (playState['isLoading'] as boolean) : cachedData.playState.isLoading }, currentSong: { id: (currentSong['id'] as string) || cachedData.currentSong.id, title: (currentSong['title'] as string) || cachedData.currentSong.title, artist: (currentSong['artist'] as string) || cachedData.currentSong.artist, album: (currentSong['album'] as string) || cachedData.currentSong.album, coverImagePath: (currentSong['coverImagePath'] as string) || cachedData.currentSong.coverImagePath, duration: (currentSong['duration'] as number) || cachedData.currentSong.duration }, progress: { currentPosition: (progress['currentPosition'] as number) !== undefined ? (progress['currentPosition'] as number) : cachedData.progress.currentPosition, duration: (progress['duration'] as number) !== undefined ? (progress['duration'] as number) : cachedData.progress.duration, percentage: (progress['percentage'] as number) !== undefined ? (progress['percentage'] as number) : cachedData.progress.percentage, currentTimeText: (progress['currentTimeText'] as string) || cachedData.progress.currentTimeText, totalTimeText: (progress['totalTimeText'] as string) || cachedData.progress.totalTimeText }, playlist: cachedData.playlist, // 保持缓存的playlist数据 config: cachedData.config }; hilog.info(0x0000, TAG, `Using cached playlist data: hasNext=${updatedData.playlist.hasNext}, hasPrevious=${updatedData.playlist.hasPrevious}, currentIndex=${updatedData.playlist.currentIndex}, totalCount=${updatedData.playlist.totalCount}`); return updatedData; } const widgetData: WidgetData = { playState: { isPlaying: (playState['isPlaying'] as boolean) || false, isPaused: (playState['isPaused'] as boolean) || true, isLoading: (playState['isLoading'] as boolean) || false }, currentSong: { id: (currentSong['id'] as string) || '', title: (currentSong['title'] as string) || '暂无播放', artist: (currentSong['artist'] as string) || '未知艺术家', album: (currentSong['album'] as string) || '未知专辑', coverImagePath: (currentSong['coverImagePath'] as string) || '', duration: (currentSong['duration'] as number) || 0 }, progress: { currentPosition: (progress['currentPosition'] as number) || 0, duration: (progress['duration'] as number) || 0, percentage: (progress['percentage'] as number) || 0, currentTimeText: (progress['currentTimeText'] as string) || '00:00', totalTimeText: (progress['totalTimeText'] as string) || '00:00' }, playlist: { hasNext: (playlist['hasNext'] as boolean) !== undefined ? (playlist['hasNext'] as boolean) : false, hasPrevious: (playlist['hasPrevious'] as boolean) !== undefined ? (playlist['hasPrevious'] as boolean) : false, currentIndex: (playlist['currentIndex'] as number) !== undefined ? (playlist['currentIndex'] as number) : 0, totalCount: (playlist['totalCount'] as number) !== undefined ? (playlist['totalCount'] as number) : 0 }, config: { size: 'medium', theme: 'auto', showProgress: true, showCover: true } }; // 添加详细的按钮状态调试日志 hilog.info(0x0000, TAG, `Raw playlist data: hasNext=${playlist['hasNext']}, hasPrevious=${playlist['hasPrevious']}, currentIndex=${playlist['currentIndex']}, totalCount=${playlist['totalCount']}`); hilog.info(0x0000, TAG, `Converted widget data: isPlaying=${widgetData.playState.isPlaying}, hasNext=${widgetData.playlist.hasNext}, hasPrevious=${widgetData.playlist.hasPrevious}, currentIndex=${widgetData.playlist.currentIndex}, totalCount=${widgetData.playlist.totalCount}, title=${widgetData.currentSong.title}`); return widgetData; } catch (error) { hilog.error(0x0000, TAG, `Failed to convert player data: ${error}`); return this.getDefaultWidgetData(); } } /** * 获取默认卡片数据 */ private getDefaultWidgetData(): WidgetData { return WidgetTypeHelpers.createDefaultWidgetData(); } /** * 计算播放进度百分比 */ 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')}`; } }