tuikit-atomicx-vue3 3.3.0 → 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 (46) 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/LiveAudienceList/LiveAudienceListH5.js +1 -1
  8. package/dist/components/LiveCoreView/PlayerControl/AudioControl.js +251 -0
  9. package/dist/components/LiveCoreView/PlayerControl/AudioControl.vue.d.ts +38 -0
  10. package/dist/components/LiveCoreView/PlayerControl/PlayerControl.js +271 -0
  11. package/dist/components/LiveCoreView/PlayerControl/PlayerControl.vue.d.ts +2 -0
  12. package/dist/components/LiveCoreView/PlayerControl/PlayerControlState.d.ts +27 -0
  13. package/dist/components/LiveCoreView/PlayerControl/PlayerControlState.js +407 -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 +23 -3
  27. package/dist/styles/index.css +321 -49
  28. package/package.json +3 -3
  29. package/src/components/ChatSetting/GroupChatSetting/GroupActions/GroupActions.vue +0 -3
  30. package/src/components/ChatSetting/GroupChatSetting/GroupChatSetting.vue +0 -1
  31. package/src/components/ChatSetting/GroupChatSetting/GroupManagement/GroupManagement.vue +0 -1
  32. package/src/components/ContactList/ContactInfo/GroupInfo/GroupInfo.vue +0 -1
  33. package/src/components/ConversationList/ConversationCreate/ConversationCreate.vue +0 -1
  34. package/src/components/ConversationList/ConversationSearch/ConversationSearch.vue +0 -1
  35. package/src/components/LiveAudienceList/LiveAudienceListH5.vue +2 -2
  36. package/src/components/LiveCoreView/PlayerControl/AudioControl.vue +456 -0
  37. package/src/components/LiveCoreView/PlayerControl/PlayerControl.module.scss +52 -0
  38. package/src/components/LiveCoreView/PlayerControl/PlayerControl.vue +429 -0
  39. package/src/components/LiveCoreView/PlayerControl/PlayerControlState.ts +599 -0
  40. package/src/components/LiveCoreView/PlayerControl/index.ts +3 -0
  41. package/src/components/LiveCoreView/PlayerControl/utils/deviceDetection.ts +234 -0
  42. package/src/components/LiveCoreView/PlayerControl/utils/domHelpers.ts +145 -0
  43. package/src/components/LiveCoreView/PlayerControl/utils/fullscreenManager.ts +417 -0
  44. package/src/components/LiveCoreView/i18n/en-US/index.ts +9 -0
  45. package/src/components/LiveCoreView/i18n/zh-CN/index.ts +9 -0
  46. package/src/components/LiveCoreView/index.vue +14 -3
@@ -0,0 +1,27 @@
1
+ import { Ref } from 'vue';
2
+
3
+ export declare enum FillMode {
4
+ CONTAIN = "contain",
5
+ COVER = "cover",
6
+ FILL = "fill"
7
+ }
8
+ export interface PlayerControlState {
9
+ isPlaying: Ref<boolean>;
10
+ currentFillMode: Ref<FillMode>;
11
+ isFullscreen: Ref<boolean>;
12
+ isPictureInPicture: Ref<boolean>;
13
+ currentVolume: Ref<number>;
14
+ resume: () => Promise<boolean>;
15
+ pause: () => Promise<boolean>;
16
+ requestFullscreen: () => Promise<boolean>;
17
+ exitFullscreen: () => Promise<boolean>;
18
+ requestPictureInPicture: () => Promise<boolean>;
19
+ exitPictureInPicture: () => Promise<boolean>;
20
+ setVolume: (volume: number) => Promise<boolean>;
21
+ changeFillMode: (fillMode: FillMode) => Promise<boolean>;
22
+ cleanup: () => void;
23
+ }
24
+ /**
25
+ * Player control state management hook
26
+ */
27
+ export declare function usePlayerControlState(): PlayerControlState;
@@ -0,0 +1,407 @@
1
+ import { ref, computed, watch } from "vue";
2
+ import { useRoomEngine } from "../../../hooks/useRoomEngine.js";
3
+ import { useLiveState } from "../../../states/LiveState/index.js";
4
+ import { useLiveSeatState } from "../../../states/LiveSeatState/index.js";
5
+ import { TRTCCloud } from "@tencentcloud/tuiroom-engine-js";
6
+ import { getDeviceType, getCurrentOrientation, shouldRotateToLandscapeForFullscreen, hadLandscapeRotationToUndo } from "./utils/deviceDetection.js";
7
+ import { OrientationManager, StyleManager, FullscreenManager, FullscreenMode } from "./utils/fullscreenManager.js";
8
+ import { EventListenerManager, DOMElementGetter } from "./utils/domHelpers.js";
9
+ import { LiveStatus } from "../../../types/live.js";
10
+ var FillMode = /* @__PURE__ */ ((FillMode2) => {
11
+ FillMode2["CONTAIN"] = "contain";
12
+ FillMode2["COVER"] = "cover";
13
+ FillMode2["FILL"] = "fill";
14
+ return FillMode2;
15
+ })(FillMode || {});
16
+ const isPlaying = ref(true);
17
+ const currentFillMode = ref(
18
+ "contain"
19
+ /* CONTAIN */
20
+ );
21
+ const isFullscreen = ref(false);
22
+ const isPictureInPicture = ref(false);
23
+ const currentVolume = ref(1);
24
+ function usePlayerControlState() {
25
+ const { localLiveStatus } = useLiveState();
26
+ const { canvas } = useLiveSeatState();
27
+ const roomEngine = useRoomEngine();
28
+ const eventManager = new EventListenerManager();
29
+ const orientationListenerId = `player-control-${Date.now()}`;
30
+ const isLandscapeStream = computed(
31
+ () => canvas.value ? canvas.value.width > canvas.value.height : false
32
+ );
33
+ const isPortraitStream = computed(
34
+ () => canvas.value ? canvas.value.width < canvas.value.height : false
35
+ );
36
+ const deviceType = getDeviceType();
37
+ const withErrorHandling = async (operation, operationName, fallbackValue) => {
38
+ try {
39
+ return await operation();
40
+ } catch (error) {
41
+ console.error(`${operationName} operation failed:`, error);
42
+ return fallbackValue;
43
+ }
44
+ };
45
+ const syncVolumeState = () => {
46
+ const video = DOMElementGetter.getVideoElement();
47
+ if (video) {
48
+ const actualVolume = video.volume;
49
+ if (Math.abs(currentVolume.value - actualVolume) > 0.01) {
50
+ console.log(`Syncing volume state: ${currentVolume.value} -> ${actualVolume}`);
51
+ currentVolume.value = actualVolume;
52
+ }
53
+ }
54
+ };
55
+ const syncPlayingState = () => {
56
+ const video = DOMElementGetter.getVideoElement();
57
+ if (video) {
58
+ const actualPlayingState = !video.paused && !video.ended;
59
+ if (isPlaying.value !== actualPlayingState) {
60
+ console.log(`Syncing playing state: ${isPlaying.value} -> ${actualPlayingState}`);
61
+ isPlaying.value = actualPlayingState;
62
+ }
63
+ }
64
+ };
65
+ const resume = async () => {
66
+ return withErrorHandling(async () => {
67
+ const video = DOMElementGetter.getVideoElement();
68
+ if (!video) {
69
+ throw new Error("Video element not found");
70
+ }
71
+ if (DOMElementGetter.hasTcPlayerElement()) {
72
+ await video.play();
73
+ } else {
74
+ const trtcCloudMap = TRTCCloud.subCloudMap;
75
+ trtcCloudMap.forEach((trtcCloud) => {
76
+ const trtc = trtcCloud == null ? void 0 : trtcCloud._trtc;
77
+ trtc == null ? void 0 : trtc.callExperimentalAPI("resumeRemotePlayer", { userId: "*" });
78
+ });
79
+ }
80
+ isPlaying.value = true;
81
+ console.log("Video playback resumed");
82
+ return true;
83
+ }, "Resume playback", false);
84
+ };
85
+ const pause = async () => {
86
+ return withErrorHandling(async () => {
87
+ const video = DOMElementGetter.getVideoElement();
88
+ if (!video) {
89
+ throw new Error("Video element not found");
90
+ }
91
+ if (DOMElementGetter.hasTcPlayerElement()) {
92
+ await video.pause();
93
+ } else {
94
+ const trtcCloudMap = TRTCCloud.subCloudMap;
95
+ trtcCloudMap.forEach((trtcCloud) => {
96
+ const trtc = trtcCloud == null ? void 0 : trtcCloud._trtc;
97
+ trtc == null ? void 0 : trtc.callExperimentalAPI("pauseRemotePlayer", { userId: "*" });
98
+ });
99
+ }
100
+ isPlaying.value = false;
101
+ console.log("Video playback paused");
102
+ return true;
103
+ }, "Pause playback", false);
104
+ };
105
+ const handleOrientationChange = (newOrientation) => {
106
+ if (!isFullscreen.value) {
107
+ return;
108
+ }
109
+ if (!isLandscapeStream.value) {
110
+ return;
111
+ }
112
+ const elements = DOMElementGetter.getAllElements();
113
+ if (!elements.view) {
114
+ console.warn("live-core-view element not found for orientation change handling");
115
+ return;
116
+ }
117
+ console.log("Handling orientation change:", {
118
+ newOrientation,
119
+ deviceType,
120
+ isLandscapeStream: isLandscapeStream.value,
121
+ isFullscreen: isFullscreen.value
122
+ });
123
+ StyleManager.smartApplyLandscapeStyles(elements.view, newOrientation);
124
+ };
125
+ const requestFullscreen = async () => {
126
+ return withErrorHandling(async () => {
127
+ const elements = DOMElementGetter.getAllElements();
128
+ const validation = DOMElementGetter.validateElements(elements);
129
+ if (!validation.isValid) {
130
+ throw new Error(`Missing required DOM elements: ${validation.missingElements.join(", ")}`);
131
+ }
132
+ const currentOrientation = getCurrentOrientation();
133
+ const shouldRotateToLandscape = shouldRotateToLandscapeForFullscreen(deviceType, isLandscapeStream.value);
134
+ console.log("Request fullscreen:", {
135
+ deviceType,
136
+ currentOrientation,
137
+ streamType: isLandscapeStream.value ? "landscape stream" : isPortraitStream.value ? "portrait stream" : "unknown",
138
+ shouldRotate: shouldRotateToLandscape,
139
+ reason: shouldRotateToLandscape ? "Mobile device in portrait with landscape stream" : currentOrientation === "landscape" ? "Already in landscape orientation" : !isLandscapeStream.value ? "Not a landscape stream" : "Desktop device or other reason"
140
+ });
141
+ const result = await FullscreenManager.requestFullscreen(
142
+ elements.container,
143
+ elements.view,
144
+ deviceType,
145
+ isPortraitStream.value,
146
+ shouldRotateToLandscape
147
+ );
148
+ if (result.success) {
149
+ isFullscreen.value = true;
150
+ console.log(`Fullscreen request successful (${result.mode})`);
151
+ } else {
152
+ console.error("Fullscreen request failed:", result.error);
153
+ }
154
+ return result.success;
155
+ }, "Request fullscreen", false);
156
+ };
157
+ const exitFullscreen = async () => {
158
+ return withErrorHandling(async () => {
159
+ const elements = DOMElementGetter.getAllElements();
160
+ if (!elements.view) {
161
+ throw new Error("live-core-view element not found");
162
+ }
163
+ const currentOrientation = getCurrentOrientation();
164
+ const hadLandscapeRotation = hadLandscapeRotationToUndo(deviceType, isLandscapeStream.value);
165
+ console.log("Exit fullscreen:", {
166
+ deviceType,
167
+ currentOrientation,
168
+ streamType: isLandscapeStream.value ? "landscape stream" : isPortraitStream.value ? "portrait stream" : "unknown",
169
+ hadLandscapeRotation,
170
+ reason: hadLandscapeRotation ? "Mobile device with landscape stream in landscape mode" : currentOrientation === "portrait" ? "Already in portrait orientation" : !isLandscapeStream.value ? "Not a landscape stream" : "Desktop device or other reason"
171
+ });
172
+ const result = await FullscreenManager.exitFullscreen(
173
+ elements.view,
174
+ deviceType,
175
+ hadLandscapeRotation
176
+ );
177
+ if (result.mode === FullscreenMode.CSS_SIMULATED) {
178
+ isFullscreen.value = false;
179
+ }
180
+ if (result.success) {
181
+ console.log(`Fullscreen exit successful (${result.mode})`);
182
+ } else {
183
+ console.error("Fullscreen exit failed:", result.error);
184
+ }
185
+ return result.success;
186
+ }, "Exit fullscreen", false);
187
+ };
188
+ const requestPictureInPicture = async () => {
189
+ return withErrorHandling(async () => {
190
+ const video = DOMElementGetter.getVideoElement();
191
+ if (!video) {
192
+ throw new Error("Video element not found");
193
+ }
194
+ if (!video.requestPictureInPicture) {
195
+ throw new Error("Picture-in-picture not supported in current environment");
196
+ }
197
+ setupVideoEventListeners();
198
+ await video.requestPictureInPicture();
199
+ console.log("Picture-in-picture request successful");
200
+ return true;
201
+ }, "Request picture-in-picture", false);
202
+ };
203
+ const exitPictureInPicture = async () => {
204
+ return withErrorHandling(async () => {
205
+ if (!document.exitPictureInPicture) {
206
+ throw new Error("Exit picture-in-picture not supported in current environment");
207
+ }
208
+ await document.exitPictureInPicture();
209
+ console.log("Picture-in-picture exit successful");
210
+ return true;
211
+ }, "Exit picture-in-picture", false);
212
+ };
213
+ const setVolume = async (volume) => {
214
+ return withErrorHandling(async () => {
215
+ var _a;
216
+ if (volume < 0 || volume > 1) {
217
+ throw new Error("Volume value must be between 0-1");
218
+ }
219
+ const trtcCloudMap = TRTCCloud.subCloudMap;
220
+ if (volume === 0) {
221
+ trtcCloudMap.forEach((trtcCloud) => {
222
+ const trtc = trtcCloud == null ? void 0 : trtcCloud._trtc;
223
+ trtc == null ? void 0 : trtc.muteRemoteAudio("*", true);
224
+ });
225
+ } else {
226
+ trtcCloudMap.forEach((trtcCloud) => {
227
+ const trtc = trtcCloud == null ? void 0 : trtcCloud._trtc;
228
+ trtc == null ? void 0 : trtc.muteRemoteAudio("*", false);
229
+ });
230
+ }
231
+ (_a = roomEngine.instance) == null ? void 0 : _a.setAudioPlayoutVolume({ volume: volume * 100 });
232
+ currentVolume.value = volume;
233
+ console.log(`Video volume set to: ${volume}`);
234
+ return true;
235
+ }, "Set volume", false);
236
+ };
237
+ const changeFillMode = async (fillMode) => {
238
+ return withErrorHandling(async () => {
239
+ currentFillMode.value = fillMode;
240
+ console.log(`Fill mode changed to: ${fillMode}`);
241
+ return true;
242
+ }, "Change fill mode", false);
243
+ };
244
+ const handleFullscreenExit = () => {
245
+ try {
246
+ const elements = DOMElementGetter.getAllElements();
247
+ if (!elements.view) {
248
+ console.warn("live-core-view element not found for fullscreen exit cleanup");
249
+ return;
250
+ }
251
+ console.log("Executing fullscreen exit style cleanup:", {
252
+ deviceType,
253
+ hasLandscapeStream: isLandscapeStream.value,
254
+ currentOrientation: getCurrentOrientation()
255
+ });
256
+ StyleManager.removeFullscreenStyles(elements.view);
257
+ StyleManager.removeLandscapeStyles(elements.view);
258
+ if (deviceType !== "desktop") {
259
+ OrientationManager.unlockOrientation().catch((error) => {
260
+ console.warn("Failed to unlock orientation during cleanup:", error);
261
+ });
262
+ }
263
+ console.log("Fullscreen exit style cleanup completed");
264
+ } catch (error) {
265
+ console.error("Fullscreen exit style cleanup failed:", error);
266
+ }
267
+ };
268
+ const setupFullscreenEventListeners = () => {
269
+ const handleFullscreenChange = () => {
270
+ const isCurrentlyFullscreen = !!document.fullscreenElement;
271
+ const wasFullscreen = isFullscreen.value;
272
+ console.log("Fullscreen state change:", {
273
+ fullscreenElement: document.fullscreenElement,
274
+ isCurrentlyFullscreen,
275
+ previousValue: wasFullscreen,
276
+ deviceType,
277
+ changeType: isCurrentlyFullscreen ? "entered" : "exited"
278
+ });
279
+ isFullscreen.value = isCurrentlyFullscreen;
280
+ if (wasFullscreen && !isCurrentlyFullscreen) {
281
+ console.log("Detected passive fullscreen exit, executing style cleanup");
282
+ handleFullscreenExit();
283
+ }
284
+ };
285
+ eventManager.addListener("fullscreenchange", document, "fullscreenchange", handleFullscreenChange);
286
+ eventManager.addListener("webkitfullscreenchange", document, "webkitfullscreenchange", handleFullscreenChange);
287
+ eventManager.addListener("mozfullscreenchange", document, "mozfullscreenchange", handleFullscreenChange);
288
+ eventManager.addListener("MSFullscreenChange", document, "MSFullscreenChange", handleFullscreenChange);
289
+ const handleVisibilityChange = () => {
290
+ if (document.visibilityState === "visible" && isFullscreen.value) {
291
+ const actuallyFullscreen = !!document.fullscreenElement;
292
+ if (!actuallyFullscreen) {
293
+ console.log("Detected fullscreen state inconsistency via visibilitychange, executing cleanup");
294
+ isFullscreen.value = false;
295
+ handleFullscreenExit();
296
+ }
297
+ }
298
+ };
299
+ eventManager.addListener("visibilitychange", document, "visibilitychange", handleVisibilityChange);
300
+ };
301
+ const setupVideoEventListeners = () => {
302
+ const video = DOMElementGetter.getVideoElement();
303
+ if (!video) return;
304
+ const handleEnterPictureInPicture = () => {
305
+ console.log("Entered picture-in-picture mode");
306
+ isPictureInPicture.value = true;
307
+ };
308
+ const handleLeavePictureInPicture = () => {
309
+ console.log("Left picture-in-picture mode");
310
+ isPictureInPicture.value = false;
311
+ resume();
312
+ };
313
+ const handlePlay = () => {
314
+ console.log("Video play event detected");
315
+ isPlaying.value = true;
316
+ };
317
+ const handlePause = () => {
318
+ console.log("Video pause event detected");
319
+ isPlaying.value = false;
320
+ };
321
+ const handleEnded = () => {
322
+ console.log("Video ended event detected");
323
+ isPlaying.value = false;
324
+ };
325
+ const handleLoadStart = () => {
326
+ console.log("Video load start event detected");
327
+ isPlaying.value = !video.paused;
328
+ };
329
+ const handleCanPlay = () => {
330
+ console.log("Video can play event detected");
331
+ isPlaying.value = !video.paused;
332
+ };
333
+ const handleSeeking = () => {
334
+ console.log("Video seeking event detected");
335
+ syncPlayingState();
336
+ };
337
+ const handleSeeked = () => {
338
+ console.log("Video seeked event detected");
339
+ syncPlayingState();
340
+ };
341
+ const handleTimeUpdate = () => {
342
+ if (Math.random() < 0.1) {
343
+ syncPlayingState();
344
+ }
345
+ };
346
+ const handleVolumeChange = () => {
347
+ console.log("Video volume change event detected");
348
+ syncPlayingState();
349
+ syncVolumeState();
350
+ };
351
+ eventManager.addListener("enterpictureinpicture", video, "enterpictureinpicture", handleEnterPictureInPicture);
352
+ eventManager.addListener("leavepictureinpicture", video, "leavepictureinpicture", handleLeavePictureInPicture);
353
+ eventManager.addListener("play", video, "play", handlePlay);
354
+ eventManager.addListener("pause", video, "pause", handlePause);
355
+ eventManager.addListener("ended", video, "ended", handleEnded);
356
+ eventManager.addListener("loadstart", video, "loadstart", handleLoadStart);
357
+ eventManager.addListener("canplay", video, "canplay", handleCanPlay);
358
+ eventManager.addListener("seeking", video, "seeking", handleSeeking);
359
+ eventManager.addListener("seeked", video, "seeked", handleSeeked);
360
+ eventManager.addListener("timeupdate", video, "timeupdate", handleTimeUpdate);
361
+ eventManager.addListener("volumechange", video, "volumechange", handleVolumeChange);
362
+ };
363
+ const cleanup = () => {
364
+ console.log("Cleaning up player control state...");
365
+ OrientationManager.removeOrientationListener(orientationListenerId);
366
+ eventManager.removeAllListeners();
367
+ console.log(`Cleaned up ${eventManager.getListenerCount()} event listeners`);
368
+ };
369
+ setupFullscreenEventListeners();
370
+ setupVideoEventListeners();
371
+ OrientationManager.addOrientationListener(orientationListenerId, handleOrientationChange);
372
+ syncPlayingState();
373
+ syncVolumeState();
374
+ watch(localLiveStatus, (newStatus) => {
375
+ if (newStatus === LiveStatus.Ended) {
376
+ exitFullscreen();
377
+ exitPictureInPicture();
378
+ isPlaying.value = true;
379
+ isFullscreen.value = false;
380
+ isPictureInPicture.value = false;
381
+ currentVolume.value = 1;
382
+ }
383
+ });
384
+ return {
385
+ // State
386
+ isPlaying,
387
+ currentFillMode,
388
+ isFullscreen,
389
+ isPictureInPicture,
390
+ currentVolume,
391
+ // Methods
392
+ resume,
393
+ pause,
394
+ requestFullscreen,
395
+ exitFullscreen,
396
+ requestPictureInPicture,
397
+ exitPictureInPicture,
398
+ setVolume,
399
+ changeFillMode,
400
+ // Cleanup
401
+ cleanup
402
+ };
403
+ }
404
+ export {
405
+ FillMode,
406
+ usePlayerControlState
407
+ };
@@ -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';
@@ -0,0 +1,8 @@
1
+ import { default as default2 } from "./AudioControl.js";
2
+ import { default as default3 } from "./PlayerControl.js";
3
+ import { usePlayerControlState } from "./PlayerControlState.js";
4
+ export {
5
+ default2 as AudioControl,
6
+ default3 as PlayerControl,
7
+ usePlayerControlState
8
+ };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Device detection utility module
3
+ */
4
+ export declare enum DeviceType {
5
+ DESKTOP = "desktop",
6
+ MOBILE = "mobile",
7
+ IOS = "ios",
8
+ ANDROID = "android"
9
+ }
10
+ export interface DeviceCapabilities {
11
+ supportsFullscreen: boolean;
12
+ supportsOrientation: boolean;
13
+ supportsPictureInPicture: boolean;
14
+ deviceType: DeviceType;
15
+ }
16
+ /**
17
+ * Detect if it's a mobile device
18
+ */
19
+ export declare const isMobileDevice: () => boolean;
20
+ /**
21
+ * Detect if it's an iOS device
22
+ */
23
+ export declare const isIOSDevice: () => boolean;
24
+ /**
25
+ * Detect if it's an Android device
26
+ */
27
+ export declare const isAndroidDevice: () => boolean;
28
+ /**
29
+ * Detect if it's a Safari browser
30
+ */
31
+ export declare const isSafariBrowser: () => boolean;
32
+ /**
33
+ * Detect if it's a Firefox browser
34
+ */
35
+ export declare const isFirefoxBrowser: () => boolean;
36
+ /**
37
+ * Get device type
38
+ */
39
+ export declare const getDeviceType: () => DeviceType;
40
+ /**
41
+ * Detect if device supports screen orientation control
42
+ */
43
+ export declare const isOrientationSupported: () => boolean;
44
+ /**
45
+ * Get current screen orientation
46
+ */
47
+ export declare const getCurrentOrientation: () => "portrait" | "landscape" | "unknown";
48
+ /**
49
+ * Check if device is currently in portrait orientation
50
+ */
51
+ export declare const isCurrentlyPortrait: () => boolean;
52
+ /**
53
+ * Check if device is currently in landscape orientation
54
+ */
55
+ export declare const isCurrentlyLandscape: () => boolean;
56
+ /**
57
+ * Determine if device should rotate to landscape for fullscreen
58
+ * @param deviceType - The type of device
59
+ * @param isLandscapeStream - Whether the stream is landscape oriented
60
+ * @returns Whether the device should rotate to landscape
61
+ */
62
+ export declare const shouldRotateToLandscapeForFullscreen: (deviceType: DeviceType, isLandscapeStream: boolean) => boolean;
63
+ /**
64
+ * Determine if device had landscape rotation that should be undone
65
+ * @param deviceType - The type of device
66
+ * @param isLandscapeStream - Whether the stream is landscape oriented
67
+ * @returns Whether the device had landscape rotation that should be undone
68
+ */
69
+ export declare const hadLandscapeRotationToUndo: (deviceType: DeviceType, isLandscapeStream: boolean) => boolean;
70
+ /**
71
+ * Detect if fullscreen API is supported
72
+ */
73
+ export declare const isFullscreenSupported: (element: HTMLElement) => boolean;
74
+ /**
75
+ * Detect if exit fullscreen API is supported
76
+ */
77
+ export declare const isExitFullscreenSupported: () => boolean;
78
+ /**
79
+ * Detect if picture-in-picture is supported
80
+ */
81
+ export declare const isPictureInPictureSupported: (video?: HTMLVideoElement) => boolean;
82
+ /**
83
+ * Get complete device capabilities information
84
+ */
85
+ export declare const getDeviceCapabilities: (element?: HTMLElement) => DeviceCapabilities;
@@ -0,0 +1,129 @@
1
+ var DeviceType = /* @__PURE__ */ ((DeviceType2) => {
2
+ DeviceType2["DESKTOP"] = "desktop";
3
+ DeviceType2["MOBILE"] = "mobile";
4
+ DeviceType2["IOS"] = "ios";
5
+ DeviceType2["ANDROID"] = "android";
6
+ return DeviceType2;
7
+ })(DeviceType || {});
8
+ const isMobileDevice = () => {
9
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
10
+ };
11
+ const isIOSDevice = () => {
12
+ return /iPad|iPhone|iPod/.test(navigator.userAgent);
13
+ };
14
+ const isAndroidDevice = () => {
15
+ return /Android/i.test(navigator.userAgent);
16
+ };
17
+ const isSafariBrowser = () => {
18
+ const isSafari = (
19
+ // Check the vendor feature
20
+ navigator.vendor && navigator.vendor.includes("Apple") && // Check specific apis or behaviors unique to Safari
21
+ !navigator.userAgent.includes("CriOS") && // Exclude Chrome iOS
22
+ !navigator.userAgent.includes("FxiOS") && // Exclude Firefox iOS
23
+ // Additional feature checks
24
+ (typeof safari !== "undefined" || !("netscape" in window) || // Firefox has netscape objects
25
+ document.documentMode === void 0)
26
+ );
27
+ return !!isSafari;
28
+ };
29
+ const isFirefoxBrowser = () => {
30
+ return typeof InstallTrigger !== "undefined" || navigator.userAgent.includes("Firefox") || navigator.userAgent.includes("Gecko/");
31
+ };
32
+ const getDeviceType = () => {
33
+ if (isIOSDevice()) return "ios";
34
+ if (isAndroidDevice()) return "android";
35
+ if (isMobileDevice()) return "mobile";
36
+ return "desktop";
37
+ };
38
+ const isOrientationSupported = () => {
39
+ return !!(screen.orientation && screen.orientation.lock);
40
+ };
41
+ const getCurrentOrientation = () => {
42
+ if (screen.orientation) {
43
+ const angle = screen.orientation.angle;
44
+ const type = screen.orientation.type;
45
+ if (type.includes("portrait")) {
46
+ return "portrait";
47
+ } else if (type.includes("landscape")) {
48
+ return "landscape";
49
+ }
50
+ if (angle === 0 || angle === 180) {
51
+ return "portrait";
52
+ } else if (angle === 90 || angle === 270) {
53
+ return "landscape";
54
+ }
55
+ }
56
+ if (window.innerHeight > window.innerWidth) {
57
+ return "portrait";
58
+ } else if (window.innerWidth > window.innerHeight) {
59
+ return "landscape";
60
+ }
61
+ return "unknown";
62
+ };
63
+ const isCurrentlyPortrait = () => {
64
+ return getCurrentOrientation() === "portrait";
65
+ };
66
+ const isCurrentlyLandscape = () => {
67
+ return getCurrentOrientation() === "landscape";
68
+ };
69
+ const shouldRotateToLandscapeForFullscreen = (deviceType, isLandscapeStream) => {
70
+ if (deviceType === "desktop") {
71
+ return false;
72
+ }
73
+ if (!isLandscapeStream) {
74
+ return false;
75
+ }
76
+ if (!isCurrentlyPortrait()) {
77
+ return false;
78
+ }
79
+ return true;
80
+ };
81
+ const hadLandscapeRotationToUndo = (deviceType, isLandscapeStream) => {
82
+ if (deviceType === "desktop") {
83
+ return false;
84
+ }
85
+ if (!isLandscapeStream) {
86
+ return false;
87
+ }
88
+ if (!isCurrentlyLandscape()) {
89
+ return false;
90
+ }
91
+ return true;
92
+ };
93
+ const isFullscreenSupported = (element) => {
94
+ return !!(element.requestFullscreen || element.webkitRequestFullscreen || element.mozRequestFullScreen || element.msRequestFullscreen);
95
+ };
96
+ const isExitFullscreenSupported = () => {
97
+ return !!(document.exitFullscreen || document.webkitExitFullscreen || document.mozCancelFullScreen || document.msExitFullscreen);
98
+ };
99
+ const isPictureInPictureSupported = (video) => {
100
+ if (!video) return false;
101
+ return typeof video.requestPictureInPicture === "function" && typeof document.exitPictureInPicture === "function";
102
+ };
103
+ const getDeviceCapabilities = (element) => {
104
+ return {
105
+ supportsFullscreen: element ? isFullscreenSupported(element) : false,
106
+ supportsOrientation: isOrientationSupported(),
107
+ supportsPictureInPicture: isPictureInPictureSupported(),
108
+ deviceType: getDeviceType()
109
+ };
110
+ };
111
+ export {
112
+ DeviceType,
113
+ getCurrentOrientation,
114
+ getDeviceCapabilities,
115
+ getDeviceType,
116
+ hadLandscapeRotationToUndo,
117
+ isAndroidDevice,
118
+ isCurrentlyLandscape,
119
+ isCurrentlyPortrait,
120
+ isExitFullscreenSupported,
121
+ isFirefoxBrowser,
122
+ isFullscreenSupported,
123
+ isIOSDevice,
124
+ isMobileDevice,
125
+ isOrientationSupported,
126
+ isPictureInPictureSupported,
127
+ isSafariBrowser,
128
+ shouldRotateToLandscapeForFullscreen
129
+ };