PlayerControlService.ets 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. import commonEventManager from '@ohos.commonEventManager';
  2. import Want from '@ohos.app.ability.Want';
  3. import common from '@ohos.app.ability.common';
  4. import { hilog } from '@kit.PerformanceAnalysisKit';
  5. import { WidgetData, WidgetCommand, EventData, RequestData, WidgetControlParams } from './WidgetTypes';
  6. import { WidgetTypeHelpers } from './WidgetTypeHelpers';
  7. import {
  8. WIDGET_CONTROL_EVENT,
  9. WIDGET_REQUEST_STATE_EVENT,
  10. PLAYER_STATE_CHANGED_EVENT,
  11. PLAYER_SONG_CHANGED_EVENT,
  12. PLAYER_PROGRESS_CHANGED_EVENT,
  13. APP_BUNDLE_NAME,
  14. APP_ABILITY_NAME
  15. } from './WidgetEventConstants';
  16. import { AvSessionWidgetListener } from './AvSessionWidgetListener';
  17. const TAG = 'Heanup PlayerControlService';
  18. /**
  19. * 启动参数接口
  20. */
  21. interface LaunchParameters {
  22. page: string;
  23. source: string;
  24. timestamp: string;
  25. }
  26. /**
  27. * 播放器控制服务
  28. * 负责与主应用的播放器进行通信和状态同步
  29. */
  30. export class PlayerControlService {
  31. private stateListeners: Array<(data: WidgetData) => void> = [];
  32. private isListenerRegistered: boolean = false;
  33. private avSessionListener: AvSessionWidgetListener;
  34. constructor() {
  35. this.avSessionListener = AvSessionWidgetListener.getInstance();
  36. this.initializeEventListener();
  37. this.initializeAvSessionListener();
  38. }
  39. /**
  40. * 初始化事件监听器
  41. */
  42. private async initializeEventListener(): Promise<void> {
  43. if (this.isListenerRegistered) {
  44. hilog.info(0x0000, TAG, 'Event listener already registered');
  45. return;
  46. }
  47. try {
  48. // 监听播放状态变化事件
  49. const subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
  50. events: [
  51. PLAYER_STATE_CHANGED_EVENT,
  52. PLAYER_SONG_CHANGED_EVENT,
  53. PLAYER_PROGRESS_CHANGED_EVENT
  54. ]
  55. };
  56. hilog.info(0x0000, TAG, `Subscribing to events: ${subscribeInfo.events.join(', ')}`);
  57. const subscriber = await commonEventManager.createSubscriber(subscribeInfo);
  58. await commonEventManager.subscribe(subscriber, (err, data: commonEventManager.CommonEventData) => {
  59. if (!err) {
  60. hilog.info(0x0000, TAG, `📡 CommonEvent received in Form process: ${data.event}`);
  61. hilog.info(0x0000, TAG, `📡 Event data length: ${data.data?.length || 0} characters`);
  62. this.handlePlayerStateChange(data);
  63. } else {
  64. hilog.error(0x0000, TAG, `❌ CommonEvent error: ${JSON.stringify(err)}`);
  65. }
  66. });
  67. this.isListenerRegistered = true;
  68. hilog.info(0x0000, TAG, 'Event listener initialized successfully');
  69. // 立即请求当前状态,确保新进程能获取到最新数据
  70. setTimeout(() => {
  71. this.requestCurrentState();
  72. }, 1000);
  73. } catch (error) {
  74. hilog.error(0x0000, TAG, `Failed to initialize event listener: ${error}`);
  75. }
  76. }
  77. /**
  78. * 请求当前播放状态
  79. */
  80. private async requestCurrentState(): Promise<void> {
  81. try {
  82. const requestData: RequestData = {
  83. timestamp: Date.now(),
  84. source: 'widget_form_process_recovery'
  85. };
  86. const requestInfo: commonEventManager.CommonEventPublishData = {
  87. data: JSON.stringify(requestData)
  88. };
  89. await commonEventManager.publish(WIDGET_REQUEST_STATE_EVENT, requestInfo, (err) => {
  90. if (err) {
  91. hilog.error(0x0000, TAG, `Failed to request current state: ${err}`);
  92. } else {
  93. hilog.info(0x0000, TAG, 'Current state requested from main app for recovery');
  94. }
  95. });
  96. } catch (error) {
  97. hilog.error(0x0000, TAG, `Failed to request current state: ${error}`);
  98. }
  99. }
  100. /**
  101. * 强制重新连接和同步状态
  102. */
  103. async forceReconnect(): Promise<void> {
  104. try {
  105. hilog.info(0x0000, TAG, 'Force reconnecting to main app...');
  106. // 重新请求当前状态
  107. await this.requestCurrentState();
  108. // 等待一段时间后再次请求,确保能收到响应
  109. setTimeout(async () => {
  110. await this.requestCurrentState();
  111. }, 2000);
  112. hilog.info(0x0000, TAG, 'Force reconnect completed');
  113. } catch (error) {
  114. hilog.error(0x0000, TAG, `Force reconnect failed: ${error}`);
  115. }
  116. }
  117. /**
  118. * 发送控制命令到主应用
  119. */
  120. async sendControlCommand(command: WidgetCommand, params?: WidgetControlParams): Promise<boolean> {
  121. try {
  122. const defaultParams: WidgetControlParams = {};
  123. const eventData: EventData = {
  124. command: command,
  125. params: params || defaultParams,
  126. timestamp: Date.now(),
  127. source: 'widget'
  128. };
  129. // 发送CommonEvent到主应用
  130. const publishInfo: commonEventManager.CommonEventPublishData = {
  131. data: JSON.stringify(eventData)
  132. };
  133. await commonEventManager.publish(WIDGET_CONTROL_EVENT, publishInfo, (err) => {
  134. if (err) {
  135. hilog.error(0x0000, TAG, `Failed to publish event: ${err}`);
  136. }
  137. });
  138. hilog.info(0x0000, TAG, `Control command sent: ${command}`);
  139. return true;
  140. } catch (error) {
  141. hilog.error(0x0000, TAG, `Failed to send control command: ${error}`);
  142. return false;
  143. }
  144. }
  145. /**
  146. * 获取当前播放状态
  147. */
  148. async getCurrentPlayState(): Promise<WidgetData> {
  149. try {
  150. // 优先从AvSession获取当前状态
  151. const avSessionData = this.avSessionListener.getCurrentWidgetData();
  152. // 同时请求CommonEvent状态作为备用
  153. const requestData: RequestData = {
  154. timestamp: Date.now(),
  155. source: 'widget_form_process'
  156. };
  157. const requestInfo: commonEventManager.CommonEventPublishData = {
  158. data: JSON.stringify(requestData)
  159. };
  160. commonEventManager.publish(WIDGET_REQUEST_STATE_EVENT, requestInfo, (err) => {
  161. if (err) {
  162. hilog.error(0x0000, TAG, `Failed to publish request state event: ${err}`);
  163. }
  164. });
  165. return avSessionData;
  166. } catch (error) {
  167. hilog.error(0x0000, TAG, `Failed to get current play state: ${error}`);
  168. return this.getDefaultWidgetData();
  169. }
  170. }
  171. /**
  172. * 初始化AvSession监听器
  173. */
  174. private initializeAvSessionListener(): void {
  175. try {
  176. // 注册AvSession状态监听器,这个监听器主要用于EntryFormAbility的全局监听
  177. // 不要在这里注册,让EntryFormAbility直接注册到AvSessionWidgetListener
  178. hilog.info(0x0000, TAG, 'AvSession listener initialized successfully (no direct registration needed)');
  179. } catch (error) {
  180. hilog.error(0x0000, TAG, `Failed to initialize AvSession listener: ${error}`);
  181. }
  182. }
  183. /**
  184. * 注册状态变化监听器
  185. */
  186. registerStateListener(callback: (data: WidgetData) => void): void {
  187. // 检查是否已经注册过相同的监听器,避免重复注册
  188. if (this.stateListeners.indexOf(callback) === -1) {
  189. this.stateListeners.push(callback);
  190. hilog.info(0x0000, TAG, `State listener registered, total listeners: ${this.stateListeners.length}`);
  191. } else {
  192. hilog.warn(0x0000, TAG, 'State listener already registered, skipping');
  193. return;
  194. }
  195. // 延迟获取当前状态,给主应用时间来广播真实状态
  196. setTimeout(() => {
  197. try {
  198. const currentData = this.avSessionListener.getCurrentWidgetData();
  199. hilog.info(0x0000, TAG, `Sending current data to listener: hasNext=${currentData.playlist.hasNext}, hasPrevious=${currentData.playlist.hasPrevious}`);
  200. callback(currentData);
  201. } catch (error) {
  202. hilog.error(0x0000, TAG, `Error getting current AvSession data: ${error}`);
  203. }
  204. }, 1500); // 延迟1.5秒,确保主应用已经广播了状态
  205. }
  206. /**
  207. * 启动主应用
  208. */
  209. async launchMainApp(page?: string, params?: Record<string, Object>): Promise<boolean> {
  210. try {
  211. interface LaunchParameters {
  212. page: string;
  213. source: string;
  214. timestamp: string;
  215. }
  216. const baseParams: LaunchParameters = {
  217. page: page || 'main',
  218. source: 'widget', // 标识来源是卡片
  219. timestamp: Date.now().toString()
  220. };
  221. // 创建Want对象
  222. const want: Want = {
  223. bundleName: APP_BUNDLE_NAME,
  224. abilityName: APP_ABILITY_NAME,
  225. parameters: {
  226. page: baseParams.page,
  227. source: baseParams.source,
  228. timestamp: baseParams.timestamp
  229. }
  230. };
  231. // 添加额外参数
  232. if (params && want.parameters) {
  233. const paramKeys = Object.keys(params);
  234. for (let i = 0; i < paramKeys.length; i++) {
  235. const key = paramKeys[i];
  236. want.parameters[key] = params[key];
  237. }
  238. }
  239. const context = getContext() as common.UIAbilityContext;
  240. await context.startAbility(want);
  241. hilog.info(0x0000, TAG, `Main app launched with page: ${page}, params: ${JSON.stringify(params)}`);
  242. return true;
  243. } catch (error) {
  244. hilog.error(0x0000, TAG, `Failed to launch main app: ${error}`);
  245. // 如果启动失败,尝试启动到默认页面
  246. try {
  247. const fallbackWant: Want = {
  248. bundleName: APP_BUNDLE_NAME,
  249. abilityName: APP_ABILITY_NAME
  250. };
  251. const context = getContext() as common.UIAbilityContext;
  252. await context.startAbility(fallbackWant);
  253. hilog.info(0x0000, TAG, 'Main app launched with fallback method');
  254. return true;
  255. } catch (fallbackError) {
  256. hilog.error(0x0000, TAG, `Fallback launch also failed: ${fallbackError}`);
  257. return false;
  258. }
  259. }
  260. }
  261. /**
  262. * 处理播放器状态变化
  263. */
  264. private handlePlayerStateChange(eventData: commonEventManager.CommonEventData): void {
  265. try {
  266. hilog.info(0x0000, TAG, `📨 Form process handling CommonEvent: ${eventData.event}`);
  267. hilog.info(0x0000, TAG, `📨 Event data: ${eventData.data?.substring(0, 200)}...`);
  268. const data = JSON.parse(eventData.data || '{}') as Object;
  269. let widgetData: WidgetData;
  270. if (eventData.event === PLAYER_PROGRESS_CHANGED_EVENT) {
  271. // 处理进度更新事件
  272. widgetData = this.updateProgressData(data);
  273. } else {
  274. // 处理完整状态更新事件
  275. widgetData = this.convertToWidgetData(data);
  276. }
  277. hilog.info(0x0000, TAG, `📨 Form process converted data: isPlaying=${widgetData.playState.isPlaying}, title=${widgetData.currentSong.title}`);
  278. // 只更新AvSession监听器的数据,避免重复通知
  279. // AvSession监听器会自动通知所有注册的监听器
  280. this.avSessionListener.updateWidgetData(widgetData);
  281. hilog.info(0x0000, TAG, `📨 Form process: ${eventData.event} handled, data updated in AvSession`);
  282. } catch (error) {
  283. hilog.error(0x0000, TAG, `❌ Form process failed to handle player state change: ${error}`);
  284. }
  285. }
  286. /**
  287. * 更新进度数据
  288. */
  289. private updateProgressData(progressData: Object): WidgetData {
  290. try {
  291. const data: Record<string, Object> = progressData as Record<string, Object>;
  292. // 获取当前缓存的数据,而不是默认数据
  293. const currentData = this.avSessionListener.getCurrentWidgetData();
  294. // 只更新进度相关数据,保持其他状态不变
  295. currentData.progress = {
  296. currentPosition: (data['currentPosition'] as number) || 0,
  297. duration: (data['duration'] as number) || 0,
  298. percentage: (data['percentage'] as number) || 0,
  299. currentTimeText: (data['currentTimeText'] as string) || '00:00',
  300. totalTimeText: (data['totalTimeText'] as string) || '00:00'
  301. };
  302. // 更新缓存
  303. this.avSessionListener.updateWidgetData(currentData);
  304. hilog.info(0x0000, TAG, `Progress updated: ${currentData.progress.percentage.toFixed(1)}%, ${currentData.progress.currentTimeText}/${currentData.progress.totalTimeText}`);
  305. return currentData;
  306. } catch (error) {
  307. hilog.error(0x0000, TAG, `Failed to update progress data: ${error}`);
  308. return this.getDefaultWidgetData();
  309. }
  310. }
  311. /**
  312. * 转换播放器数据为卡片数据格式
  313. */
  314. private convertToWidgetData(playerData: Object): WidgetData {
  315. try {
  316. const data: Record<string, Object> = playerData as Record<string, Object>;
  317. // 正确解析PlayerStateBroadcastData结构
  318. const playState = (data['playState'] as Record<string, Object>) || {};
  319. const currentSong = (data['currentSong'] as Record<string, Object>) || {};
  320. const progress = (data['progress'] as Record<string, Object>) || {};
  321. const playlist = (data['playlist'] as Record<string, Object>) || {};
  322. // 检查是否接收到不完整的数据
  323. const isPlaylistDataIncomplete = playlist['hasNext'] === undefined ||
  324. playlist['hasPrevious'] === undefined ||
  325. playlist['currentIndex'] === undefined ||
  326. playlist['totalCount'] === undefined;
  327. if (isPlaylistDataIncomplete) {
  328. hilog.warn(0x0000, TAG, `Received incomplete playlist data, using cached data`);
  329. // 如果接收到不完整的数据,返回当前缓存的数据
  330. const cachedData = this.avSessionListener.getCurrentWidgetData();
  331. // 只更新非playlist的数据,保持playlist数据不变
  332. const updatedData: WidgetData = {
  333. playState: {
  334. isPlaying: (playState['isPlaying'] as boolean) !== undefined ? (playState['isPlaying'] as boolean) : cachedData.playState.isPlaying,
  335. isPaused: (playState['isPaused'] as boolean) !== undefined ? (playState['isPaused'] as boolean) : cachedData.playState.isPaused,
  336. isLoading: (playState['isLoading'] as boolean) !== undefined ? (playState['isLoading'] as boolean) : cachedData.playState.isLoading
  337. },
  338. currentSong: {
  339. id: (currentSong['id'] as string) || cachedData.currentSong.id,
  340. title: (currentSong['title'] as string) || cachedData.currentSong.title,
  341. artist: (currentSong['artist'] as string) || cachedData.currentSong.artist,
  342. album: (currentSong['album'] as string) || cachedData.currentSong.album,
  343. coverImagePath: (currentSong['coverImagePath'] as string) || cachedData.currentSong.coverImagePath,
  344. duration: (currentSong['duration'] as number) || cachedData.currentSong.duration
  345. },
  346. progress: {
  347. currentPosition: (progress['currentPosition'] as number) !== undefined ? (progress['currentPosition'] as number) : cachedData.progress.currentPosition,
  348. duration: (progress['duration'] as number) !== undefined ? (progress['duration'] as number) : cachedData.progress.duration,
  349. percentage: (progress['percentage'] as number) !== undefined ? (progress['percentage'] as number) : cachedData.progress.percentage,
  350. currentTimeText: (progress['currentTimeText'] as string) || cachedData.progress.currentTimeText,
  351. totalTimeText: (progress['totalTimeText'] as string) || cachedData.progress.totalTimeText
  352. },
  353. playlist: cachedData.playlist, // 保持缓存的playlist数据
  354. config: cachedData.config
  355. };
  356. hilog.info(0x0000, TAG, `Using cached playlist data: hasNext=${updatedData.playlist.hasNext}, hasPrevious=${updatedData.playlist.hasPrevious}, currentIndex=${updatedData.playlist.currentIndex}, totalCount=${updatedData.playlist.totalCount}`);
  357. return updatedData;
  358. }
  359. const widgetData: WidgetData = {
  360. playState: {
  361. isPlaying: (playState['isPlaying'] as boolean) || false,
  362. isPaused: (playState['isPaused'] as boolean) || true,
  363. isLoading: (playState['isLoading'] as boolean) || false
  364. },
  365. currentSong: {
  366. id: (currentSong['id'] as string) || '',
  367. title: (currentSong['title'] as string) || '暂无播放',
  368. artist: (currentSong['artist'] as string) || '未知艺术家',
  369. album: (currentSong['album'] as string) || '未知专辑',
  370. coverImagePath: (currentSong['coverImagePath'] as string) || '',
  371. duration: (currentSong['duration'] as number) || 0
  372. },
  373. progress: {
  374. currentPosition: (progress['currentPosition'] as number) || 0,
  375. duration: (progress['duration'] as number) || 0,
  376. percentage: (progress['percentage'] as number) || 0,
  377. currentTimeText: (progress['currentTimeText'] as string) || '00:00',
  378. totalTimeText: (progress['totalTimeText'] as string) || '00:00'
  379. },
  380. playlist: {
  381. hasNext: (playlist['hasNext'] as boolean) !== undefined ? (playlist['hasNext'] as boolean) : false,
  382. hasPrevious: (playlist['hasPrevious'] as boolean) !== undefined ? (playlist['hasPrevious'] as boolean) : false,
  383. currentIndex: (playlist['currentIndex'] as number) !== undefined ? (playlist['currentIndex'] as number) : 0,
  384. totalCount: (playlist['totalCount'] as number) !== undefined ? (playlist['totalCount'] as number) : 0
  385. },
  386. config: {
  387. size: 'medium',
  388. theme: 'auto',
  389. showProgress: true,
  390. showCover: true
  391. }
  392. };
  393. // 添加详细的按钮状态调试日志
  394. hilog.info(0x0000, TAG, `Raw playlist data: hasNext=${playlist['hasNext']}, hasPrevious=${playlist['hasPrevious']}, currentIndex=${playlist['currentIndex']}, totalCount=${playlist['totalCount']}`);
  395. 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}`);
  396. return widgetData;
  397. } catch (error) {
  398. hilog.error(0x0000, TAG, `Failed to convert player data: ${error}`);
  399. return this.getDefaultWidgetData();
  400. }
  401. }
  402. /**
  403. * 获取默认卡片数据
  404. */
  405. private getDefaultWidgetData(): WidgetData {
  406. return WidgetTypeHelpers.createDefaultWidgetData();
  407. }
  408. /**
  409. * 计算播放进度百分比
  410. */
  411. private calculatePercentage(current: number, total: number): number {
  412. if (total <= 0) return 0;
  413. return Math.min(100, Math.max(0, (current / total) * 100));
  414. }
  415. /**
  416. * 格式化时间显示
  417. */
  418. private formatTime(seconds: number): string {
  419. const mins = Math.floor(seconds / 60);
  420. const secs = Math.floor(seconds % 60);
  421. return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  422. }
  423. }