tuikit-atomicx-vue3 3.3.1 → 3.3.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/components/ChatSetting/GroupChatSetting/GroupActions/GroupActions.js +1 -4
  2. package/dist/components/ChatSetting/GroupChatSetting/GroupChatSetting.js +1 -2
  3. package/dist/components/ChatSetting/GroupChatSetting/GroupManagement/GroupManagement.js +1 -2
  4. package/dist/components/ContactList/ContactInfo/GroupInfo/GroupInfo.js +1 -2
  5. package/dist/components/ConversationList/ConversationCreate/ConversationCreate.js +1 -2
  6. package/dist/components/ConversationList/ConversationSearch/ConversationSearch.js +0 -1
  7. package/dist/components/LiveCoreView/PlayerControl/AudioControl.js +251 -0
  8. package/dist/components/LiveCoreView/PlayerControl/AudioControl.vue.d.ts +38 -0
  9. package/dist/components/LiveCoreView/PlayerControl/PlayerControl.js +271 -0
  10. package/dist/components/LiveCoreView/PlayerControl/PlayerControl.vue.d.ts +2 -0
  11. package/dist/components/LiveCoreView/PlayerControl/PlayerControlState.d.ts +27 -0
  12. package/dist/components/LiveCoreView/PlayerControl/PlayerControlState.js +407 -0
  13. package/dist/components/LiveCoreView/PlayerControl/index.d.ts +3 -0
  14. package/dist/components/LiveCoreView/PlayerControl/index.js +8 -0
  15. package/dist/components/LiveCoreView/PlayerControl/utils/deviceDetection.d.ts +85 -0
  16. package/dist/components/LiveCoreView/PlayerControl/utils/deviceDetection.js +129 -0
  17. package/dist/components/LiveCoreView/PlayerControl/utils/domHelpers.d.ts +75 -0
  18. package/dist/components/LiveCoreView/PlayerControl/utils/domHelpers.js +120 -0
  19. package/dist/components/LiveCoreView/PlayerControl/utils/fullscreenManager.d.ts +120 -0
  20. package/dist/components/LiveCoreView/PlayerControl/utils/fullscreenManager.js +311 -0
  21. package/dist/components/LiveCoreView/i18n/en-US/index.d.ts +9 -0
  22. package/dist/components/LiveCoreView/i18n/en-US/index.js +10 -1
  23. package/dist/components/LiveCoreView/i18n/zh-CN/index.d.ts +9 -0
  24. package/dist/components/LiveCoreView/i18n/zh-CN/index.js +10 -1
  25. package/dist/components/LiveCoreView/index.js +23 -3
  26. package/dist/styles/index.css +302 -30
  27. package/package.json +2 -2
  28. package/src/components/ChatSetting/GroupChatSetting/GroupActions/GroupActions.vue +0 -3
  29. package/src/components/ChatSetting/GroupChatSetting/GroupChatSetting.vue +0 -1
  30. package/src/components/ChatSetting/GroupChatSetting/GroupManagement/GroupManagement.vue +0 -1
  31. package/src/components/ContactList/ContactInfo/GroupInfo/GroupInfo.vue +0 -1
  32. package/src/components/ConversationList/ConversationCreate/ConversationCreate.vue +0 -1
  33. package/src/components/ConversationList/ConversationSearch/ConversationSearch.vue +0 -1
  34. package/src/components/LiveCoreView/PlayerControl/AudioControl.vue +456 -0
  35. package/src/components/LiveCoreView/PlayerControl/PlayerControl.module.scss +52 -0
  36. package/src/components/LiveCoreView/PlayerControl/PlayerControl.vue +429 -0
  37. package/src/components/LiveCoreView/PlayerControl/PlayerControlState.ts +599 -0
  38. package/src/components/LiveCoreView/PlayerControl/index.ts +3 -0
  39. package/src/components/LiveCoreView/PlayerControl/utils/deviceDetection.ts +234 -0
  40. package/src/components/LiveCoreView/PlayerControl/utils/domHelpers.ts +145 -0
  41. package/src/components/LiveCoreView/PlayerControl/utils/fullscreenManager.ts +417 -0
  42. package/src/components/LiveCoreView/i18n/en-US/index.ts +9 -0
  43. package/src/components/LiveCoreView/i18n/zh-CN/index.ts +9 -0
  44. package/src/components/LiveCoreView/index.vue +13 -2
@@ -0,0 +1,599 @@
1
+ import type { Ref } from 'vue';
2
+ import { computed, ref, watch } from 'vue';
3
+ import useRoomEngine from '../../../hooks/useRoomEngine';
4
+ import useLiveState from '../../../states/LiveState';
5
+ import { useLiveSeatState } from '../../../states/LiveSeatState';
6
+ import { TRTCCloud } from '@tencentcloud/tuiroom-engine-js';
7
+ // Import utility modules
8
+ import {
9
+ getDeviceType,
10
+ getCurrentOrientation,
11
+ shouldRotateToLandscapeForFullscreen,
12
+ hadLandscapeRotationToUndo,
13
+ } from './utils/deviceDetection';
14
+ import {
15
+ FullscreenManager,
16
+ FullscreenResult,
17
+ FullscreenMode,
18
+ OrientationManager,
19
+ StyleManager
20
+ } from './utils/fullscreenManager';
21
+ import {
22
+ DOMElementGetter,
23
+ EventListenerManager
24
+ } from './utils/domHelpers';
25
+ import { LiveStatus } from '../../../types';
26
+
27
+ // Player fill mode enum
28
+ export enum FillMode {
29
+ CONTAIN = 'contain',
30
+ COVER = 'cover',
31
+ FILL = 'fill',
32
+ }
33
+
34
+ // Player control state interface
35
+ export interface PlayerControlState {
36
+ // State properties
37
+ isPlaying: Ref<boolean>;
38
+ currentFillMode: Ref<FillMode>;
39
+ isFullscreen: Ref<boolean>;
40
+ isPictureInPicture: Ref<boolean>;
41
+ currentVolume: Ref<number>;
42
+
43
+ // Basic control methods
44
+ resume: () => Promise<boolean>;
45
+ pause: () => Promise<boolean>;
46
+
47
+ // Fullscreen control methods
48
+ requestFullscreen: () => Promise<boolean>;
49
+ exitFullscreen: () => Promise<boolean>;
50
+
51
+ // Picture-in-picture control methods
52
+ requestPictureInPicture: () => Promise<boolean>;
53
+ exitPictureInPicture: () => Promise<boolean>;
54
+
55
+ // Other control methods
56
+ setVolume: (volume: number) => Promise<boolean>;
57
+ changeFillMode: (fillMode: FillMode) => Promise<boolean>;
58
+
59
+ // Cleanup method
60
+ cleanup: () => void;
61
+ }
62
+
63
+ // State management
64
+ const isPlaying = ref(true);
65
+ const currentFillMode = ref<FillMode>(FillMode.CONTAIN);
66
+ const isFullscreen = ref(false);
67
+ const isPictureInPicture = ref(false);
68
+ const currentVolume = ref(1.0); // Default volume is 1.0 (100%)
69
+
70
+ /**
71
+ * Player control state management hook
72
+ */
73
+ export function usePlayerControlState(): PlayerControlState {
74
+ // Dependency injection
75
+ const { localLiveStatus } = useLiveState();
76
+ const { canvas } = useLiveSeatState();
77
+ const roomEngine = useRoomEngine();
78
+
79
+ // Event listener management
80
+ const eventManager = new EventListenerManager();
81
+
82
+ // Orientation listener ID for cleanup
83
+ const orientationListenerId = `player-control-${Date.now()}`;
84
+
85
+ // Computed properties
86
+ const isLandscapeStream = computed(() =>
87
+ canvas.value ? canvas.value.width > canvas.value.height : false
88
+ );
89
+ const isPortraitStream = computed(() =>
90
+ canvas.value ? canvas.value.width < canvas.value.height : false
91
+ );
92
+
93
+ // Device information
94
+ const deviceType = getDeviceType();
95
+
96
+ /**
97
+ * Error handling wrapper
98
+ */
99
+ const withErrorHandling = async <T>(
100
+ operation: () => Promise<T>,
101
+ operationName: string,
102
+ fallbackValue: T
103
+ ): Promise<T> => {
104
+ try {
105
+ return await operation();
106
+ } catch (error) {
107
+ console.error(`${operationName} operation failed:`, error);
108
+ return fallbackValue;
109
+ }
110
+ };
111
+
112
+ /**
113
+ * Video control methods
114
+ */
115
+
116
+ const syncVolumeState = (): void => {
117
+ const video = DOMElementGetter.getVideoElement();
118
+ if (video) {
119
+ const actualVolume = video.volume;
120
+ if (Math.abs(currentVolume.value - actualVolume) > 0.01) { // Use small tolerance for floating point comparison
121
+ console.log(`Syncing volume state: ${currentVolume.value} -> ${actualVolume}`);
122
+ currentVolume.value = actualVolume;
123
+ }
124
+ }
125
+ };
126
+
127
+ const syncPlayingState = (): void => {
128
+ const video = DOMElementGetter.getVideoElement();
129
+ if (video) {
130
+ const actualPlayingState = !video.paused && !video.ended;
131
+ if (isPlaying.value !== actualPlayingState) {
132
+ console.log(`Syncing playing state: ${isPlaying.value} -> ${actualPlayingState}`);
133
+ isPlaying.value = actualPlayingState;
134
+ }
135
+ }
136
+ };
137
+
138
+ const resume = async (): Promise<boolean> => {
139
+ return withErrorHandling(async () => {
140
+ const video = DOMElementGetter.getVideoElement();
141
+ if (!video) {
142
+ throw new Error('Video element not found');
143
+ }
144
+ if (DOMElementGetter.hasTcPlayerElement()) {
145
+ await video.play();
146
+ } else {
147
+ const trtcCloudMap = TRTCCloud.subCloudMap;
148
+ trtcCloudMap.forEach((trtcCloud: TRTCCloud) => {
149
+ const trtc = trtcCloud?._trtc;
150
+ trtc?.callExperimentalAPI('resumeRemotePlayer', { userId: '*'})
151
+ })
152
+ }
153
+ isPlaying.value = true;
154
+ console.log('Video playback resumed');
155
+ return true;
156
+ }, 'Resume playback', false);
157
+ };
158
+
159
+ const pause = async (): Promise<boolean> => {
160
+ return withErrorHandling(async () => {
161
+ const video = DOMElementGetter.getVideoElement();
162
+ if (!video) {
163
+ throw new Error('Video element not found');
164
+ }
165
+ if (DOMElementGetter.hasTcPlayerElement()) {
166
+ await video.pause();
167
+ } else {
168
+ const trtcCloudMap = TRTCCloud.subCloudMap;
169
+ trtcCloudMap.forEach((trtcCloud: TRTCCloud) => {
170
+ const trtc = trtcCloud?._trtc;
171
+ trtc?.callExperimentalAPI('pauseRemotePlayer', { userId: '*'})
172
+ })
173
+ }
174
+ isPlaying.value = false;
175
+ console.log('Video playback paused');
176
+ return true;
177
+ }, 'Pause playback', false);
178
+ };
179
+
180
+ /**
181
+ * Handle orientation change
182
+ */
183
+ const handleOrientationChange = (newOrientation: 'portrait' | 'landscape' | 'unknown'): void => {
184
+ // Only handle orientation changes in fullscreen mode
185
+ if (!isFullscreen.value) {
186
+ return;
187
+ }
188
+
189
+ // Only handle for landscape streams
190
+ if (!isLandscapeStream.value) {
191
+ return;
192
+ }
193
+
194
+ const elements = DOMElementGetter.getAllElements();
195
+ if (!elements.view) {
196
+ console.warn('live-core-view element not found for orientation change handling');
197
+ return;
198
+ }
199
+
200
+ console.log('Handling orientation change:', {
201
+ newOrientation,
202
+ deviceType,
203
+ isLandscapeStream: isLandscapeStream.value,
204
+ isFullscreen: isFullscreen.value
205
+ });
206
+
207
+ // Smart adjustment of landscape styles
208
+ StyleManager.smartApplyLandscapeStyles(elements.view, newOrientation);
209
+ };
210
+
211
+ /**
212
+ * Fullscreen control methods
213
+ */
214
+ const requestFullscreen = async (): Promise<boolean> => {
215
+ return withErrorHandling(async () => {
216
+ const elements = DOMElementGetter.getAllElements();
217
+ const validation = DOMElementGetter.validateElements(elements);
218
+
219
+ if (!validation.isValid) {
220
+ throw new Error(`Missing required DOM elements: ${validation.missingElements.join(', ')}`);
221
+ }
222
+
223
+ const currentOrientation = getCurrentOrientation();
224
+ const shouldRotateToLandscape = shouldRotateToLandscapeForFullscreen(deviceType, isLandscapeStream.value);
225
+
226
+ console.log('Request fullscreen:', {
227
+ deviceType,
228
+ currentOrientation,
229
+ streamType: isLandscapeStream.value ? 'landscape stream' : isPortraitStream.value ? 'portrait stream' : 'unknown',
230
+ shouldRotate: shouldRotateToLandscape,
231
+ reason: shouldRotateToLandscape ? 'Mobile device in portrait with landscape stream' :
232
+ currentOrientation === 'landscape' ? 'Already in landscape orientation' :
233
+ !isLandscapeStream.value ? 'Not a landscape stream' :
234
+ 'Desktop device or other reason'
235
+ });
236
+
237
+ const result: FullscreenResult = await FullscreenManager.requestFullscreen(
238
+ elements.container!,
239
+ elements.view!,
240
+ deviceType,
241
+ isPortraitStream.value,
242
+ shouldRotateToLandscape
243
+ );
244
+
245
+ if (result.success) {
246
+ isFullscreen.value = true;
247
+ console.log(`Fullscreen request successful (${result.mode})`);
248
+ } else {
249
+ console.error('Fullscreen request failed:', result.error);
250
+ }
251
+
252
+ return result.success;
253
+ }, 'Request fullscreen', false);
254
+ };
255
+
256
+ const exitFullscreen = async (): Promise<boolean> => {
257
+ return withErrorHandling(async () => {
258
+ const elements = DOMElementGetter.getAllElements();
259
+ if (!elements.view) {
260
+ throw new Error('live-core-view element not found');
261
+ }
262
+
263
+ const currentOrientation = getCurrentOrientation();
264
+ const hadLandscapeRotation = hadLandscapeRotationToUndo(deviceType, isLandscapeStream.value);
265
+
266
+ console.log('Exit fullscreen:', {
267
+ deviceType,
268
+ currentOrientation,
269
+ streamType: isLandscapeStream.value ? 'landscape stream' : isPortraitStream.value ? 'portrait stream' : 'unknown',
270
+ hadLandscapeRotation,
271
+ reason: hadLandscapeRotation ? 'Mobile device with landscape stream in landscape mode' :
272
+ currentOrientation === 'portrait' ? 'Already in portrait orientation' :
273
+ !isLandscapeStream.value ? 'Not a landscape stream' :
274
+ 'Desktop device or other reason'
275
+ });
276
+
277
+ const result: FullscreenResult = await FullscreenManager.exitFullscreen(
278
+ elements.view,
279
+ deviceType,
280
+ hadLandscapeRotation
281
+ );
282
+
283
+ // For standard fullscreen, state is updated by event listeners
284
+ // For CSS simulated fullscreen, update state directly
285
+ if (result.mode === FullscreenMode.CSS_SIMULATED) {
286
+ isFullscreen.value = false;
287
+ }
288
+
289
+ if (result.success) {
290
+ console.log(`Fullscreen exit successful (${result.mode})`);
291
+ } else {
292
+ console.error('Fullscreen exit failed:', result.error);
293
+ }
294
+
295
+ return result.success;
296
+ }, 'Exit fullscreen', false);
297
+ };
298
+
299
+ /**
300
+ * Picture-in-picture control methods
301
+ */
302
+ const requestPictureInPicture = async (): Promise<boolean> => {
303
+ return withErrorHandling(async () => {
304
+ const video = DOMElementGetter.getVideoElement();
305
+ if (!video) {
306
+ throw new Error('Video element not found');
307
+ }
308
+
309
+ if (!video.requestPictureInPicture) {
310
+ throw new Error('Picture-in-picture not supported in current environment');
311
+ }
312
+
313
+ // Ensure event listeners are set
314
+ setupVideoEventListeners();
315
+ await video.requestPictureInPicture();
316
+
317
+ console.log('Picture-in-picture request successful');
318
+ return true;
319
+ }, 'Request picture-in-picture', false);
320
+ };
321
+
322
+ const exitPictureInPicture = async (): Promise<boolean> => {
323
+ return withErrorHandling(async () => {
324
+ if (!document.exitPictureInPicture) {
325
+ throw new Error('Exit picture-in-picture not supported in current environment');
326
+ }
327
+
328
+ await document.exitPictureInPicture();
329
+ console.log('Picture-in-picture exit successful');
330
+ return true;
331
+ }, 'Exit picture-in-picture', false);
332
+ };
333
+
334
+ /**
335
+ * Other control methods
336
+ */
337
+ const setVolume = async (volume: number): Promise<boolean> => {
338
+ return withErrorHandling(async () => {
339
+ if (volume < 0 || volume > 1) {
340
+ throw new Error('Volume value must be between 0-1');
341
+ }
342
+ const trtcCloudMap = TRTCCloud.subCloudMap;
343
+ if(volume === 0) {
344
+ trtcCloudMap.forEach((trtcCloud: TRTCCloud) => {
345
+ const trtc = trtcCloud?._trtc;
346
+ trtc?.muteRemoteAudio('*', true)
347
+ })
348
+ } else {
349
+ trtcCloudMap.forEach((trtcCloud: TRTCCloud) => {
350
+ const trtc = trtcCloud?._trtc;
351
+ trtc?.muteRemoteAudio('*', false)
352
+ })
353
+ }
354
+ roomEngine.instance?.setAudioPlayoutVolume({ volume: volume * 100 });
355
+ currentVolume.value = volume;
356
+ console.log(`Video volume set to: ${volume}`);
357
+ return true;
358
+ }, 'Set volume', false);
359
+ };
360
+
361
+ const changeFillMode = async (fillMode: FillMode): Promise<boolean> => {
362
+ return withErrorHandling(async () => {
363
+ currentFillMode.value = fillMode;
364
+ console.log(`Fill mode changed to: ${fillMode}`);
365
+ // TODO: Implement actual fill mode logic
366
+ return true;
367
+ }, 'Change fill mode', false);
368
+ };
369
+
370
+ /**
371
+ * Handle style cleanup when exiting fullscreen
372
+ */
373
+ const handleFullscreenExit = (): void => {
374
+ try {
375
+ const elements = DOMElementGetter.getAllElements();
376
+ if (!elements.view) {
377
+ console.warn('live-core-view element not found for fullscreen exit cleanup');
378
+ return;
379
+ }
380
+
381
+ console.log('Executing fullscreen exit style cleanup:', {
382
+ deviceType,
383
+ hasLandscapeStream: isLandscapeStream.value,
384
+ currentOrientation: getCurrentOrientation()
385
+ });
386
+
387
+ // Remove all fullscreen related styles
388
+ StyleManager.removeFullscreenStyles(elements.view);
389
+ StyleManager.removeLandscapeStyles(elements.view);
390
+
391
+ // If mobile device, try to unlock screen orientation
392
+ if (deviceType !== 'desktop') {
393
+ OrientationManager.unlockOrientation().catch(error => {
394
+ console.warn('Failed to unlock orientation during cleanup:', error);
395
+ });
396
+ }
397
+
398
+ console.log('Fullscreen exit style cleanup completed');
399
+ } catch (error) {
400
+ console.error('Fullscreen exit style cleanup failed:', error);
401
+ }
402
+ };
403
+
404
+ /**
405
+ * Event listener setup
406
+ */
407
+ const setupFullscreenEventListeners = (): void => {
408
+ const handleFullscreenChange = () => {
409
+ const isCurrentlyFullscreen = !!document.fullscreenElement;
410
+ const wasFullscreen = isFullscreen.value;
411
+
412
+ console.log('Fullscreen state change:', {
413
+ fullscreenElement: document.fullscreenElement,
414
+ isCurrentlyFullscreen,
415
+ previousValue: wasFullscreen,
416
+ deviceType,
417
+ changeType: isCurrentlyFullscreen ? 'entered' : 'exited'
418
+ });
419
+
420
+ // Update state
421
+ isFullscreen.value = isCurrentlyFullscreen;
422
+
423
+ // If exiting fullscreen, need to cleanup styles
424
+ if (wasFullscreen && !isCurrentlyFullscreen) {
425
+ console.log('Detected passive fullscreen exit, executing style cleanup');
426
+ handleFullscreenExit();
427
+ }
428
+ };
429
+
430
+ // Add fullscreen event listeners for various browsers
431
+ eventManager.addListener('fullscreenchange', document, 'fullscreenchange', handleFullscreenChange);
432
+ eventManager.addListener('webkitfullscreenchange', document, 'webkitfullscreenchange', handleFullscreenChange);
433
+ eventManager.addListener('mozfullscreenchange', document, 'mozfullscreenchange', handleFullscreenChange);
434
+ eventManager.addListener('MSFullscreenChange', document, 'MSFullscreenChange', handleFullscreenChange);
435
+
436
+ // Add additional detection mechanisms for non-standard fullscreen exits (like Android back button)
437
+ const handleVisibilityChange = () => {
438
+ // When page visibility changes, check if fullscreen state is consistent
439
+ if (document.visibilityState === 'visible' && isFullscreen.value) {
440
+ const actuallyFullscreen = !!document.fullscreenElement;
441
+ if (!actuallyFullscreen) {
442
+ console.log('Detected fullscreen state inconsistency via visibilitychange, executing cleanup');
443
+ isFullscreen.value = false;
444
+ handleFullscreenExit();
445
+ }
446
+ }
447
+ };
448
+
449
+ // Listen to page visibility changes (Android back button scenarios)
450
+ eventManager.addListener('visibilitychange', document, 'visibilitychange', handleVisibilityChange);
451
+ };
452
+
453
+ const setupVideoEventListeners = (): void => {
454
+ const video = DOMElementGetter.getVideoElement();
455
+ if (!video) return;
456
+
457
+ const handleEnterPictureInPicture = () => {
458
+ console.log('Entered picture-in-picture mode');
459
+ isPictureInPicture.value = true;
460
+ };
461
+
462
+ const handleLeavePictureInPicture = () => {
463
+ console.log('Left picture-in-picture mode');
464
+ isPictureInPicture.value = false;
465
+ resume();
466
+ };
467
+
468
+ // Video playback state change handlers
469
+ const handlePlay = () => {
470
+ console.log('Video play event detected');
471
+ isPlaying.value = true;
472
+ };
473
+
474
+ const handlePause = () => {
475
+ console.log('Video pause event detected');
476
+ isPlaying.value = false;
477
+ };
478
+
479
+ const handleEnded = () => {
480
+ console.log('Video ended event detected');
481
+ isPlaying.value = false;
482
+ };
483
+
484
+ const handleLoadStart = () => {
485
+ console.log('Video load start event detected');
486
+ // Sync initial state when video loads
487
+ isPlaying.value = !video.paused;
488
+ };
489
+
490
+ const handleCanPlay = () => {
491
+ console.log('Video can play event detected');
492
+ // Sync state when video becomes playable
493
+ isPlaying.value = !video.paused;
494
+ };
495
+
496
+ const handleSeeking = () => {
497
+ console.log('Video seeking event detected');
498
+ // Sync state during seeking
499
+ syncPlayingState();
500
+ };
501
+
502
+ const handleSeeked = () => {
503
+ console.log('Video seeked event detected');
504
+ // Sync state after seeking
505
+ syncPlayingState();
506
+ };
507
+
508
+ const handleTimeUpdate = () => {
509
+ // Only sync occasionally during time updates to avoid too many calls
510
+ if (Math.random() < 0.1) { // 10% chance per timeupdate event
511
+ syncPlayingState();
512
+ }
513
+ };
514
+
515
+ const handleVolumeChange = () => {
516
+ console.log('Video volume change event detected');
517
+ // Sync state when volume changes (might indicate user interaction)
518
+ syncPlayingState();
519
+ syncVolumeState();
520
+ };
521
+
522
+ // Add picture-in-picture event listeners
523
+ eventManager.addListener('enterpictureinpicture', video, 'enterpictureinpicture', handleEnterPictureInPicture);
524
+ eventManager.addListener('leavepictureinpicture', video, 'leavepictureinpicture', handleLeavePictureInPicture);
525
+
526
+ // Add video playback state event listeners
527
+ eventManager.addListener('play', video, 'play', handlePlay);
528
+ eventManager.addListener('pause', video, 'pause', handlePause);
529
+ eventManager.addListener('ended', video, 'ended', handleEnded);
530
+ eventManager.addListener('loadstart', video, 'loadstart', handleLoadStart);
531
+ eventManager.addListener('canplay', video, 'canplay', handleCanPlay);
532
+
533
+ // Add additional state sync events
534
+ eventManager.addListener('seeking', video, 'seeking', handleSeeking);
535
+ eventManager.addListener('seeked', video, 'seeked', handleSeeked);
536
+ eventManager.addListener('timeupdate', video, 'timeupdate', handleTimeUpdate);
537
+ eventManager.addListener('volumechange', video, 'volumechange', handleVolumeChange);
538
+
539
+ };
540
+
541
+ /**
542
+ * Cleanup method
543
+ */
544
+ const cleanup = (): void => {
545
+ console.log('Cleaning up player control state...');
546
+
547
+ // Remove orientation listener
548
+ OrientationManager.removeOrientationListener(orientationListenerId);
549
+
550
+ // Remove all event listeners
551
+ eventManager.removeAllListeners();
552
+ console.log(`Cleaned up ${eventManager.getListenerCount()} event listeners`);
553
+ };
554
+
555
+ // Initialize
556
+ setupFullscreenEventListeners();
557
+ setupVideoEventListeners();
558
+
559
+ // Setup orientation listener for dynamic style adjustment
560
+ OrientationManager.addOrientationListener(orientationListenerId, handleOrientationChange);
561
+
562
+ // Initial state sync
563
+ syncPlayingState();
564
+ syncVolumeState();
565
+
566
+ watch(localLiveStatus, (newStatus) => {
567
+ if(newStatus === LiveStatus.Ended) {
568
+ exitFullscreen();
569
+ exitPictureInPicture();
570
+ isPlaying.value = true;
571
+ isFullscreen.value = false;
572
+ isPictureInPicture.value = false;
573
+ currentVolume.value = 1.0;
574
+ }
575
+ })
576
+
577
+ // Return interface implementation
578
+ return {
579
+ // State
580
+ isPlaying,
581
+ currentFillMode,
582
+ isFullscreen,
583
+ isPictureInPicture,
584
+ currentVolume,
585
+
586
+ // Methods
587
+ resume,
588
+ pause,
589
+ requestFullscreen,
590
+ exitFullscreen,
591
+ requestPictureInPicture,
592
+ exitPictureInPicture,
593
+ setVolume,
594
+ changeFillMode,
595
+
596
+ // Cleanup
597
+ cleanup,
598
+ };
599
+ }
@@ -0,0 +1,3 @@
1
+ export { default as AudioControl } from './AudioControl.vue';
2
+ export { default as PlayerControl } from './PlayerControl.vue';
3
+ export { usePlayerControlState } from './PlayerControlState';