tuikit-atomicx-vue3 3.3.1 → 3.3.2

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