StateSyncService.ets 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. import commonEventManager from '@ohos.commonEventManager';
  2. import { hilog } from '@kit.PerformanceAnalysisKit';
  3. import { common } from '@kit.AbilityKit';
  4. import { VideoItem } from '../../viewmodel/VideoItem';
  5. import { PlayerState, PlayMode } from './PlayerStateModel';
  6. import { PlayProgress, WidgetData } from '../widget/WidgetTypes';
  7. import {
  8. PLAYER_STATE_CHANGED_EVENT,
  9. PLAYER_SONG_CHANGED_EVENT,
  10. PLAYER_PROGRESS_CHANGED_EVENT,
  11. WIDGET_CONTROL_EVENT,
  12. WIDGET_REQUEST_STATE_EVENT
  13. } from '../widget/WidgetEventConstants';
  14. const TAG = 'StateSyncService';
  15. /**
  16. * 状态同步服务接口
  17. */
  18. export interface IStateSyncService {
  19. broadcastState(state: PlayerState): Promise<void>;
  20. broadcastSongChange(song: VideoItem): Promise<void>;
  21. broadcastProgress(progress: PlayProgress): Promise<void>;
  22. subscribeToStateChanges(callback: StateChangeCallback): void;
  23. unsubscribeFromStateChanges(callback: StateChangeCallback): void;
  24. subscribeToWidgetControl(callback: WidgetControlCallback): void;
  25. unsubscribeFromWidgetControl(callback: WidgetControlCallback): void;
  26. initialize(context: common.UIAbilityContext): Promise<void>;
  27. release(): void;
  28. }
  29. /**
  30. * 状态变化回调接口
  31. */
  32. export interface StateChangeCallback {
  33. onStateChanged?(state: PlayerState): void;
  34. onSongChanged?(song: VideoItem): void;
  35. onProgressChanged?(progress: PlayProgress): void;
  36. }
  37. /**
  38. * 卡片控制命令回调接口
  39. */
  40. export interface WidgetControlCallback {
  41. onPlayPause?(): Promise<void>;
  42. onNextSong?(): Promise<void>;
  43. onPreviousSong?(): Promise<void>;
  44. onSeekTo?(position: number): Promise<void>;
  45. onStateRequest?(): Promise<void>;
  46. }
  47. /**
  48. * 卡片控制事件数据
  49. */
  50. interface WidgetControlEventData {
  51. command: string;
  52. params?: Record<string, Object>;
  53. timestamp: number;
  54. source: string;
  55. }
  56. /**
  57. * 播放状态数据接口
  58. */
  59. interface PlayStateBroadcast {
  60. isPlaying: boolean;
  61. isPaused: boolean;
  62. isLoading: boolean;
  63. currentPosition: number;
  64. duration: number;
  65. }
  66. /**
  67. * 当前歌曲数据接口
  68. */
  69. interface CurrentSongBroadcast {
  70. id: string;
  71. title: string;
  72. artist: string;
  73. album: string;
  74. filePath: string;
  75. duration: number;
  76. }
  77. /**
  78. * 进度数据接口
  79. */
  80. interface ProgressBroadcast {
  81. currentPosition: number;
  82. duration: number;
  83. percentage: number;
  84. currentTimeText: string;
  85. totalTimeText: string;
  86. }
  87. /**
  88. * 播放列表数据接口
  89. */
  90. interface PlaylistBroadcast {
  91. hasNext: boolean;
  92. hasPrevious: boolean;
  93. currentIndex: number;
  94. totalCount: number;
  95. }
  96. /**
  97. * 播放器状态广播数据
  98. */
  99. interface PlayerStateBroadcastData {
  100. playState: PlayStateBroadcast;
  101. currentSong: CurrentSongBroadcast;
  102. progress: ProgressBroadcast;
  103. playlist: PlaylistBroadcast;
  104. }
  105. /**
  106. * 歌曲变化广播数据
  107. */
  108. interface SongChangeBroadcastData {
  109. currentSong: CurrentSongBroadcast;
  110. playlist: PlaylistBroadcast;
  111. }
  112. /**
  113. * 进度更新广播数据
  114. */
  115. interface ProgressBroadcastData {
  116. currentPosition: number;
  117. duration: number;
  118. percentage: number;
  119. currentTimeText: string;
  120. totalTimeText: string;
  121. }
  122. /**
  123. * 状态同步服务实现
  124. * 负责在不同组件和进程间同步播放状态
  125. */
  126. export class StateSyncService implements IStateSyncService {
  127. private static instance: StateSyncService | null = null;
  128. private context: common.UIAbilityContext | null = null;
  129. private stateChangeCallbacks: StateChangeCallback[] = [];
  130. private widgetControlCallbacks: WidgetControlCallback[] = [];
  131. private isInitialized: boolean = false;
  132. private lastStateBroadcastTime: number = 0;
  133. private lastProgressBroadcastTime: number = 0;
  134. private readonly STATE_BROADCAST_THROTTLE: number = 500; // 状态广播节流间隔(毫秒)
  135. private readonly PROGRESS_BROADCAST_THROTTLE: number = 1000; // 进度广播节流间隔(毫秒)
  136. private readonly COMMAND_EXECUTION_TIMEOUT: number = 5000; // 命令执行超时时间(毫秒)
  137. private constructor() {}
  138. public static getInstance(): StateSyncService {
  139. if (!StateSyncService.instance) {
  140. StateSyncService.instance = new StateSyncService();
  141. }
  142. return StateSyncService.instance;
  143. }
  144. /**
  145. * 初始化状态同步服务
  146. */
  147. async initialize(context: common.UIAbilityContext): Promise<void> {
  148. if (this.isInitialized) {
  149. hilog.info(0x0000, TAG, 'StateSyncService already initialized');
  150. return;
  151. }
  152. try {
  153. this.context = context;
  154. // 注册状态请求事件监听器
  155. await this.registerStateRequestListener();
  156. // 注册卡片控制事件监听器
  157. await this.registerWidgetControlListener();
  158. this.isInitialized = true;
  159. hilog.info(0x0000, TAG, 'StateSyncService initialized successfully');
  160. } catch (error) {
  161. hilog.error(0x0000, TAG, `Failed to initialize StateSyncService: ${error}`);
  162. throw new Error(`Failed to initialize StateSyncService: ${error}`);
  163. }
  164. }
  165. /**
  166. * 广播播放状态变化
  167. */
  168. async broadcastState(state: PlayerState): Promise<void> {
  169. try {
  170. // 节流控制,避免过于频繁的广播
  171. const now = Date.now();
  172. if (now - this.lastStateBroadcastTime < this.STATE_BROADCAST_THROTTLE) {
  173. return;
  174. }
  175. this.lastStateBroadcastTime = now;
  176. const playStateBroadcast: PlayStateBroadcast = {
  177. isPlaying: state.isPlaying,
  178. isPaused: state.isPaused,
  179. isLoading: state.isLoading,
  180. currentPosition: state.currentPosition,
  181. duration: state.duration
  182. };
  183. const currentSongBroadcast: CurrentSongBroadcast = {
  184. id: state.currentSong?.id || '',
  185. title: state.currentSong?.name || '暂无播放',
  186. artist: state.currentSong?.artist || '未知艺术家',
  187. album: state.currentSong?.album || '未知专辑',
  188. filePath: state.currentSong?.filePath || '',
  189. duration: state.currentSong?.duration ? parseInt(state.currentSong.duration) : 0
  190. };
  191. const progressBroadcast: ProgressBroadcast = {
  192. currentPosition: state.currentPosition,
  193. duration: state.duration,
  194. percentage: this.calculatePercentage(state.currentPosition, state.duration),
  195. currentTimeText: this.formatTime(Math.floor(state.currentPosition / 1000)),
  196. totalTimeText: this.formatTime(Math.floor(state.duration / 1000))
  197. };
  198. const broadcastData: PlayerStateBroadcastData = {
  199. playState: playStateBroadcast,
  200. currentSong: currentSongBroadcast,
  201. progress: progressBroadcast,
  202. playlist: {
  203. hasNext: state.hasNext || false,
  204. hasPrevious: state.hasPrevious || false,
  205. currentIndex: state.currentIndex,
  206. totalCount: state.totalCount || 0
  207. } as PlaylistBroadcast
  208. };
  209. const publishInfo: commonEventManager.CommonEventPublishData = {
  210. data: JSON.stringify(broadcastData)
  211. };
  212. await commonEventManager.publish(PLAYER_STATE_CHANGED_EVENT, publishInfo, (err) => {
  213. if (err) {
  214. hilog.error(0x0000, TAG, `Failed to broadcast state: ${err}`);
  215. } else {
  216. hilog.info(0x0000, TAG, `State broadcasted: isPlaying=${state.isPlaying}, song=${state.currentSong?.name || 'none'}`);
  217. }
  218. });
  219. // 通知本地监听器
  220. this.notifyLocalStateListeners(state);
  221. } catch (error) {
  222. hilog.error(0x0000, TAG, `Failed to broadcast state: ${error}`);
  223. }
  224. }
  225. /**
  226. * 广播歌曲切换事件
  227. */
  228. async broadcastSongChange(song: VideoItem): Promise<void> {
  229. try {
  230. const currentSongBroadcast: CurrentSongBroadcast = {
  231. id: song.id || '',
  232. title: song.name || '暂无播放',
  233. artist: song.artist || '未知艺术家',
  234. album: song.album || '未知专辑',
  235. filePath: song.filePath || '',
  236. duration: song.duration ? parseInt(song.duration) : 0
  237. };
  238. const playlistBroadcast: PlaylistBroadcast = {
  239. hasNext: false, // 这些值需要从播放列表服务获取
  240. hasPrevious: false,
  241. currentIndex: 0,
  242. totalCount: 0
  243. };
  244. const broadcastData: SongChangeBroadcastData = {
  245. currentSong: currentSongBroadcast,
  246. playlist: playlistBroadcast
  247. };
  248. const publishInfo: commonEventManager.CommonEventPublishData = {
  249. data: JSON.stringify(broadcastData)
  250. };
  251. commonEventManager.publish(PLAYER_SONG_CHANGED_EVENT, publishInfo, (err) => {
  252. if (err) {
  253. hilog.error(0x0000, TAG, `Failed to broadcast song change: ${err}`);
  254. } else {
  255. hilog.info(0x0000, TAG, `Song change broadcasted: ${song.name} by ${song.artist || '未知艺术家'}`);
  256. }
  257. });
  258. // 通知本地监听器
  259. this.notifyLocalSongChangeListeners(song);
  260. } catch (error) {
  261. hilog.error(0x0000, TAG, `Failed to broadcast song change: ${error}`);
  262. }
  263. }
  264. /**
  265. * 广播播放进度更新
  266. */
  267. async broadcastProgress(progress: PlayProgress): Promise<void> {
  268. try {
  269. // 节流控制,避免过于频繁的进度广播
  270. const now = Date.now();
  271. if (now - this.lastProgressBroadcastTime < this.PROGRESS_BROADCAST_THROTTLE) {
  272. return;
  273. }
  274. this.lastProgressBroadcastTime = now;
  275. const broadcastData: ProgressBroadcastData = {
  276. currentPosition: progress.currentPosition,
  277. duration: progress.duration,
  278. percentage: this.calculatePercentage(progress.currentPosition, progress.duration),
  279. currentTimeText: this.formatTime(Math.floor(progress.currentPosition / 1000)),
  280. totalTimeText: this.formatTime(Math.floor(progress.duration / 1000))
  281. };
  282. const publishInfo: commonEventManager.CommonEventPublishData = {
  283. data: JSON.stringify(broadcastData)
  284. };
  285. await commonEventManager.publish(PLAYER_PROGRESS_CHANGED_EVENT, publishInfo, (err) => {
  286. if (err) {
  287. hilog.error(0x0000, TAG, `Failed to broadcast progress: ${err}`);
  288. } else {
  289. hilog.info(0x0000, TAG, `Progress broadcasted: ${broadcastData.percentage.toFixed(1)}% (${broadcastData.currentTimeText}/${broadcastData.totalTimeText})`);
  290. }
  291. });
  292. // 通知本地监听器
  293. this.notifyLocalProgressListeners(progress);
  294. } catch (error) {
  295. hilog.error(0x0000, TAG, `Failed to broadcast progress: ${error}`);
  296. }
  297. }
  298. /**
  299. * 订阅状态变化
  300. */
  301. subscribeToStateChanges(callback: StateChangeCallback): void {
  302. if (this.stateChangeCallbacks.indexOf(callback) === -1) {
  303. this.stateChangeCallbacks.push(callback);
  304. hilog.info(0x0000, TAG, `State change callback registered, total: ${this.stateChangeCallbacks.length}`);
  305. }
  306. }
  307. /**
  308. * 取消订阅状态变化
  309. */
  310. unsubscribeFromStateChanges(callback: StateChangeCallback): void {
  311. const index = this.stateChangeCallbacks.indexOf(callback);
  312. if (index !== -1) {
  313. this.stateChangeCallbacks.splice(index, 1);
  314. hilog.info(0x0000, TAG, `State change callback unregistered, remaining: ${this.stateChangeCallbacks.length}`);
  315. }
  316. }
  317. /**
  318. * 订阅卡片控制事件
  319. */
  320. subscribeToWidgetControl(callback: WidgetControlCallback): void {
  321. if (this.widgetControlCallbacks.indexOf(callback) === -1) {
  322. this.widgetControlCallbacks.push(callback);
  323. hilog.info(0x0000, TAG, `Widget control callback registered, total: ${this.widgetControlCallbacks.length}`);
  324. }
  325. }
  326. /**
  327. * 取消订阅卡片控制事件
  328. */
  329. unsubscribeFromWidgetControl(callback: WidgetControlCallback): void {
  330. const index = this.widgetControlCallbacks.indexOf(callback);
  331. if (index !== -1) {
  332. this.widgetControlCallbacks.splice(index, 1);
  333. hilog.info(0x0000, TAG, `Widget control callback unregistered, remaining: ${this.widgetControlCallbacks.length}`);
  334. }
  335. }
  336. /**
  337. * 释放资源
  338. */
  339. release(): void {
  340. try {
  341. this.stateChangeCallbacks = [];
  342. this.widgetControlCallbacks = [];
  343. this.isInitialized = false;
  344. hilog.info(0x0000, TAG, 'StateSyncService released');
  345. } catch (error) {
  346. hilog.error(0x0000, TAG, `Failed to release StateSyncService: ${error}`);
  347. }
  348. }
  349. // ==================== 私有辅助方法 ====================
  350. /**
  351. * 注册状态请求事件监听器
  352. */
  353. private async registerStateRequestListener(): Promise<void> {
  354. try {
  355. const subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
  356. events: [WIDGET_REQUEST_STATE_EVENT]
  357. };
  358. const subscriber = await commonEventManager.createSubscriber(subscribeInfo);
  359. await commonEventManager.subscribe(subscriber, (err, data: commonEventManager.CommonEventData) => {
  360. if (!err) {
  361. hilog.info(0x0000, TAG, `Received state request from widget: ${data.event}`);
  362. this.handleStateRequest(data);
  363. } else {
  364. hilog.error(0x0000, TAG, `State request listener error: ${JSON.stringify(err)}`);
  365. }
  366. });
  367. hilog.info(0x0000, TAG, 'State request listener registered successfully');
  368. } catch (error) {
  369. hilog.error(0x0000, TAG, `Failed to register state request listener: ${error}`);
  370. }
  371. }
  372. /**
  373. * 注册卡片控制事件监听器
  374. */
  375. private async registerWidgetControlListener(): Promise<void> {
  376. try {
  377. const subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
  378. events: [WIDGET_CONTROL_EVENT]
  379. };
  380. const subscriber = await commonEventManager.createSubscriber(subscribeInfo);
  381. commonEventManager.subscribe(subscriber, (err, data: commonEventManager.CommonEventData) => {
  382. if (!err) {
  383. hilog.info(0x0000, TAG, `Received widget control command: ${data.event}`);
  384. this.handleWidgetControlCommand(data);
  385. } else {
  386. hilog.error(0x0000, TAG, `Widget control listener error: ${JSON.stringify(err)}`);
  387. }
  388. });
  389. hilog.info(0x0000, TAG, 'Widget control listener registered successfully');
  390. } catch (error) {
  391. hilog.error(0x0000, TAG, `Failed to register widget control listener: ${error}`);
  392. }
  393. }
  394. /**
  395. * 处理状态请求
  396. */
  397. private handleStateRequest(eventData: commonEventManager.CommonEventData): void {
  398. try {
  399. const requestData: Record<string, string> = JSON.parse(eventData.data || '{}');
  400. const source: string = requestData.source || 'unknown';
  401. hilog.info(0x0000, TAG, `Handling state request from: ${source}`);
  402. // 通知控制回调处理状态请求
  403. this.notifyWidgetControlCallbacks('onStateRequest');
  404. } catch (error) {
  405. hilog.error(0x0000, TAG, `Failed to handle state request: ${error}`);
  406. }
  407. }
  408. /**
  409. * 处理卡片控制命令
  410. */
  411. private async handleWidgetControlCommand(eventData: commonEventManager.CommonEventData): Promise<void> {
  412. try {
  413. const controlData: WidgetControlEventData = JSON.parse(eventData.data || '{}');
  414. const command = controlData.command;
  415. const params = controlData.params;
  416. const source = controlData.source;
  417. const timestamp = controlData.timestamp;
  418. hilog.info(0x0000, TAG, `Processing widget control command: ${command} from ${source}`);
  419. // 验证命令时效性(防止过期命令执行)
  420. const now = Date.now();
  421. if (now - timestamp > this.COMMAND_EXECUTION_TIMEOUT) {
  422. hilog.warn(0x0000, TAG, `Command ${command} expired, ignoring (age: ${now - timestamp}ms)`);
  423. return;
  424. }
  425. // 验证命令来源
  426. if (!this.isValidCommandSource(source)) {
  427. hilog.warn(0x0000, TAG, `Invalid command source: ${source}, ignoring command ${command}`);
  428. return;
  429. }
  430. // 执行命令
  431. await this.executeWidgetCommand(command, params);
  432. hilog.info(0x0000, TAG, `Widget control command ${command} executed successfully`);
  433. } catch (error) {
  434. hilog.error(0x0000, TAG, `Failed to handle widget control command: ${error}`);
  435. }
  436. }
  437. /**
  438. * 通知本地状态监听器
  439. */
  440. private notifyLocalStateListeners(state: PlayerState): void {
  441. this.stateChangeCallbacks.forEach(callback => {
  442. try {
  443. if (callback.onStateChanged) {
  444. callback.onStateChanged(state);
  445. }
  446. } catch (error) {
  447. hilog.error(0x0000, TAG, `Error in state change callback: ${error}`);
  448. }
  449. });
  450. }
  451. /**
  452. * 通知本地歌曲变化监听器
  453. */
  454. private notifyLocalSongChangeListeners(song: VideoItem): void {
  455. this.stateChangeCallbacks.forEach(callback => {
  456. try {
  457. if (callback.onSongChanged) {
  458. callback.onSongChanged(song);
  459. }
  460. } catch (error) {
  461. hilog.error(0x0000, TAG, `Error in song change callback: ${error}`);
  462. }
  463. });
  464. }
  465. /**
  466. * 通知本地进度监听器
  467. */
  468. private notifyLocalProgressListeners(progress: PlayProgress): void {
  469. this.stateChangeCallbacks.forEach(callback => {
  470. try {
  471. if (callback.onProgressChanged) {
  472. callback.onProgressChanged(progress);
  473. }
  474. } catch (error) {
  475. hilog.error(0x0000, TAG, `Error in progress change callback: ${error}`);
  476. }
  477. });
  478. }
  479. /**
  480. * 执行卡片控制命令
  481. */
  482. private async executeWidgetCommand(command: string, params?: Record<string, Object>): Promise<void> {
  483. try {
  484. switch (command) {
  485. case 'PLAY_PAUSE':
  486. await this.notifyWidgetControlCallbacks('onPlayPause');
  487. break;
  488. case 'NEXT_SONG':
  489. await this.notifyWidgetControlCallbacks('onNextSong');
  490. break;
  491. case 'PREV_SONG':
  492. await this.notifyWidgetControlCallbacks('onPreviousSong');
  493. break;
  494. case 'SEEK_TO':
  495. const position = params?.position as number || 0;
  496. await this.notifyWidgetControlCallbacks('onSeekTo', position);
  497. break;
  498. default:
  499. hilog.warn(0x0000, TAG, `Unknown widget command: ${command}`);
  500. }
  501. } catch (error) {
  502. hilog.error(0x0000, TAG, `Failed to execute widget command ${command}: ${error}`);
  503. throw new Error(`Failed to execute widget command ${command}: ${error}`);
  504. }
  505. }
  506. /**
  507. * 验证命令来源是否有效
  508. */
  509. private isValidCommandSource(source: string): boolean {
  510. const validSources = ['widget', 'form', 'card', 'desktop_widget'];
  511. return validSources.includes(source);
  512. }
  513. /**
  514. * 通知卡片控制回调
  515. */
  516. private async notifyWidgetControlCallbacks(method: string, ...args: Object[]): Promise<void> {
  517. const promises: Promise<void>[] = [];
  518. this.widgetControlCallbacks.forEach(callback => {
  519. try {
  520. let promise: Promise<void> | undefined;
  521. switch (method) {
  522. case 'onPlayPause':
  523. promise = callback.onPlayPause?.();
  524. break;
  525. case 'onNextSong':
  526. promise = callback.onNextSong?.();
  527. break;
  528. case 'onPreviousSong':
  529. promise = callback.onPreviousSong?.();
  530. break;
  531. case 'onSeekTo':
  532. promise = callback.onSeekTo?.(args[0] as number);
  533. break;
  534. case 'onStateRequest':
  535. promise = callback.onStateRequest?.();
  536. break;
  537. }
  538. if (promise) {
  539. promises.push(promise);
  540. }
  541. } catch (error) {
  542. hilog.error(0x0000, TAG, `Error in widget control callback ${method}: ${error}`);
  543. }
  544. });
  545. // 等待所有回调执行完成
  546. if (promises.length > 0) {
  547. try {
  548. await Promise.all(promises);
  549. hilog.info(0x0000, TAG, `All widget control callbacks for ${method} completed`);
  550. } catch (error) {
  551. hilog.error(0x0000, TAG, `Some widget control callbacks for ${method} failed: ${error}`);
  552. }
  553. }
  554. }
  555. /**
  556. * 计算播放进度百分比
  557. */
  558. private calculatePercentage(current: number, total: number): number {
  559. if (total <= 0) return 0;
  560. return Math.min(100, Math.max(0, (current / total) * 100));
  561. }
  562. /**
  563. * 格式化时间显示
  564. */
  565. private formatTime(seconds: number): string {
  566. const mins = Math.floor(seconds / 60);
  567. const secs = Math.floor(seconds % 60);
  568. return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  569. }
  570. }