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.
- package/dist/components/ChatSetting/GroupChatSetting/GroupActions/GroupActions.js +1 -4
- package/dist/components/ChatSetting/GroupChatSetting/GroupChatSetting.js +1 -2
- package/dist/components/ChatSetting/GroupChatSetting/GroupManagement/GroupManagement.js +1 -2
- package/dist/components/ContactList/ContactInfo/GroupInfo/GroupInfo.js +1 -2
- package/dist/components/ConversationList/ConversationCreate/ConversationCreate.js +1 -2
- package/dist/components/ConversationList/ConversationSearch/ConversationSearch.js +0 -1
- package/dist/components/LiveAudienceList/LiveAudienceListH5.js +1 -1
- package/dist/components/LiveCoreView/PlayerControl/AudioControl.js +251 -0
- package/dist/components/LiveCoreView/PlayerControl/AudioControl.vue.d.ts +38 -0
- package/dist/components/LiveCoreView/PlayerControl/PlayerControl.js +271 -0
- package/dist/components/LiveCoreView/PlayerControl/PlayerControl.vue.d.ts +2 -0
- package/dist/components/LiveCoreView/PlayerControl/PlayerControlState.d.ts +27 -0
- package/dist/components/LiveCoreView/PlayerControl/PlayerControlState.js +407 -0
- package/dist/components/LiveCoreView/PlayerControl/index.d.ts +3 -0
- package/dist/components/LiveCoreView/PlayerControl/index.js +8 -0
- package/dist/components/LiveCoreView/PlayerControl/utils/deviceDetection.d.ts +85 -0
- package/dist/components/LiveCoreView/PlayerControl/utils/deviceDetection.js +129 -0
- package/dist/components/LiveCoreView/PlayerControl/utils/domHelpers.d.ts +75 -0
- package/dist/components/LiveCoreView/PlayerControl/utils/domHelpers.js +120 -0
- package/dist/components/LiveCoreView/PlayerControl/utils/fullscreenManager.d.ts +120 -0
- package/dist/components/LiveCoreView/PlayerControl/utils/fullscreenManager.js +311 -0
- package/dist/components/LiveCoreView/i18n/en-US/index.d.ts +9 -0
- package/dist/components/LiveCoreView/i18n/en-US/index.js +10 -1
- package/dist/components/LiveCoreView/i18n/zh-CN/index.d.ts +9 -0
- package/dist/components/LiveCoreView/i18n/zh-CN/index.js +10 -1
- package/dist/components/LiveCoreView/index.js +23 -3
- package/dist/styles/index.css +321 -49
- package/package.json +3 -3
- package/src/components/ChatSetting/GroupChatSetting/GroupActions/GroupActions.vue +0 -3
- package/src/components/ChatSetting/GroupChatSetting/GroupChatSetting.vue +0 -1
- package/src/components/ChatSetting/GroupChatSetting/GroupManagement/GroupManagement.vue +0 -1
- package/src/components/ContactList/ContactInfo/GroupInfo/GroupInfo.vue +0 -1
- package/src/components/ConversationList/ConversationCreate/ConversationCreate.vue +0 -1
- package/src/components/ConversationList/ConversationSearch/ConversationSearch.vue +0 -1
- package/src/components/LiveAudienceList/LiveAudienceListH5.vue +2 -2
- package/src/components/LiveCoreView/PlayerControl/AudioControl.vue +456 -0
- package/src/components/LiveCoreView/PlayerControl/PlayerControl.module.scss +52 -0
- package/src/components/LiveCoreView/PlayerControl/PlayerControl.vue +429 -0
- package/src/components/LiveCoreView/PlayerControl/PlayerControlState.ts +599 -0
- package/src/components/LiveCoreView/PlayerControl/index.ts +3 -0
- package/src/components/LiveCoreView/PlayerControl/utils/deviceDetection.ts +234 -0
- package/src/components/LiveCoreView/PlayerControl/utils/domHelpers.ts +145 -0
- package/src/components/LiveCoreView/PlayerControl/utils/fullscreenManager.ts +417 -0
- package/src/components/LiveCoreView/i18n/en-US/index.ts +9 -0
- package/src/components/LiveCoreView/i18n/zh-CN/index.ts +9 -0
- 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,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
|
+
};
|