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,429 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Transition name="player-control">
|
|
3
|
+
<div
|
|
4
|
+
v-show="showControls"
|
|
5
|
+
ref="playerControlRef"
|
|
6
|
+
:class="['playback-controls', isMobile ? 'mobile-mode' : 'pc-mode']"
|
|
7
|
+
>
|
|
8
|
+
<div class="control-buttons">
|
|
9
|
+
<span class="control-btn play-pause-btn" :title="isPlaying ? t('Pause') : t('Play')" @click="handlePlayPause">
|
|
10
|
+
<IconPause size="20" v-if="isPlaying" />
|
|
11
|
+
<IconPlay size="20" v-else />
|
|
12
|
+
</span>
|
|
13
|
+
<div class="center-controls"></div>
|
|
14
|
+
<div class="right-controls">
|
|
15
|
+
<span class="control-btn audio-control-btn">
|
|
16
|
+
<AudioControl
|
|
17
|
+
class="audio-control-icon"
|
|
18
|
+
:icon-size="20"
|
|
19
|
+
:enable-volume-control="isEnableVolumeControl()"
|
|
20
|
+
@volume-change="handleVolumeChange"
|
|
21
|
+
@muted-change="handleMutedChange"
|
|
22
|
+
/>
|
|
23
|
+
</span>
|
|
24
|
+
<span
|
|
25
|
+
class="control-btn"
|
|
26
|
+
:title="isPictureInPicture ? t('Exit Picture in Picture') : t('Picture in Picture')"
|
|
27
|
+
@click="handlePictureInPicture"
|
|
28
|
+
>
|
|
29
|
+
<IconPictureInPicture size="20" />
|
|
30
|
+
</span>
|
|
31
|
+
<span
|
|
32
|
+
class="control-btn fullscreen-btn"
|
|
33
|
+
:title="isFullscreen ? t('Exit Fullscreen') : t('Fullscreen')"
|
|
34
|
+
@click="handleFullscreen"
|
|
35
|
+
>
|
|
36
|
+
<IconFullScreen size="20" />
|
|
37
|
+
</span>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</Transition>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<script setup lang="ts">
|
|
45
|
+
import { onMounted, ref, onBeforeUnmount } from 'vue';
|
|
46
|
+
import { IconFullScreen, IconPictureInPicture, IconPause, IconPlay, useUIKit, TUIToast, TOAST_TYPE } from '@tencentcloud/uikit-base-component-vue3';
|
|
47
|
+
import { usePlayerControlState } from './PlayerControlState';
|
|
48
|
+
import AudioControl from './AudioControl.vue';
|
|
49
|
+
import { isMobile } from '../../../utils';
|
|
50
|
+
import { isFirefoxBrowser, isSafariBrowser } from './utils/deviceDetection';
|
|
51
|
+
|
|
52
|
+
const {
|
|
53
|
+
isPlaying,
|
|
54
|
+
isFullscreen,
|
|
55
|
+
isPictureInPicture,
|
|
56
|
+
pause,
|
|
57
|
+
resume,
|
|
58
|
+
requestPictureInPicture,
|
|
59
|
+
exitPictureInPicture,
|
|
60
|
+
requestFullscreen,
|
|
61
|
+
exitFullscreen,
|
|
62
|
+
setVolume,
|
|
63
|
+
cleanup,
|
|
64
|
+
} = usePlayerControlState();
|
|
65
|
+
|
|
66
|
+
const { t } = useUIKit();
|
|
67
|
+
const currentVolume = ref(1);
|
|
68
|
+
const isMuted = ref(false);
|
|
69
|
+
|
|
70
|
+
const playerControlRef = ref<HTMLElement>();
|
|
71
|
+
|
|
72
|
+
const showControls = ref(false);
|
|
73
|
+
const hideTimeout = ref<number | null>(null);
|
|
74
|
+
|
|
75
|
+
const AUTO_HIDE_DELAY = 3000; // ms
|
|
76
|
+
|
|
77
|
+
const isEnableVolumeControl = () => {
|
|
78
|
+
// PC
|
|
79
|
+
if (!isMobile) return true;
|
|
80
|
+
|
|
81
|
+
// Safari is not supported
|
|
82
|
+
if (isSafariBrowser()) return false;
|
|
83
|
+
|
|
84
|
+
// Firefox is not supported
|
|
85
|
+
if (isFirefoxBrowser()) return false;
|
|
86
|
+
|
|
87
|
+
// Other Mobile browsers is supported
|
|
88
|
+
return true;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handlePlayPause = () => {
|
|
92
|
+
if (isPlaying.value) {
|
|
93
|
+
pause();
|
|
94
|
+
} else {
|
|
95
|
+
resume();
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const handlePictureInPicture = async () => {
|
|
100
|
+
let flag = false;
|
|
101
|
+
if (isPictureInPicture.value) {
|
|
102
|
+
flag = await exitPictureInPicture();
|
|
103
|
+
} else {
|
|
104
|
+
flag = await requestPictureInPicture();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!flag) {
|
|
108
|
+
TUIToast({
|
|
109
|
+
type: TOAST_TYPE.ERROR,
|
|
110
|
+
message: t('The system does not support picture-in-picture mode'),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const handleFullscreen = () => {
|
|
116
|
+
console.log('handleFullscreen');
|
|
117
|
+
if (isFullscreen.value) {
|
|
118
|
+
exitFullscreen();
|
|
119
|
+
} else {
|
|
120
|
+
requestFullscreen();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const handleVolumeChange = async (volume: number) => {
|
|
125
|
+
currentVolume.value = volume;
|
|
126
|
+
// When the mouse is placed in the liveCoreView area on a pc, playerControls will always be displayed
|
|
127
|
+
if (isMobile) {
|
|
128
|
+
startAutoHideControl();
|
|
129
|
+
}
|
|
130
|
+
await setVolume(volume);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const startAutoHideControl = () => {
|
|
134
|
+
stopAutoHideControl();
|
|
135
|
+
hideTimeout.value = window.setTimeout(() => {
|
|
136
|
+
showControls.value = false;
|
|
137
|
+
hideTimeout.value = null;
|
|
138
|
+
}, AUTO_HIDE_DELAY);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const stopAutoHideControl = () => {
|
|
142
|
+
if (hideTimeout.value) {
|
|
143
|
+
clearTimeout(hideTimeout.value);
|
|
144
|
+
hideTimeout.value = null;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const handleMouseEnter = () => {
|
|
149
|
+
stopAutoHideControl();
|
|
150
|
+
showControls.value = true;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const handleMouseLeave = () => {
|
|
154
|
+
startAutoHideControl();
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const setupParentMouseListener = () => {
|
|
158
|
+
if (!isMobile && playerControlRef.value) {
|
|
159
|
+
const parentElement = playerControlRef.value.parentElement;
|
|
160
|
+
if (parentElement) {
|
|
161
|
+
parentElement.addEventListener('mouseenter', handleMouseEnter);
|
|
162
|
+
parentElement.addEventListener('mouseleave', handleMouseLeave);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const removeParentMouseListener = () => {
|
|
168
|
+
if (!isMobile && playerControlRef.value) {
|
|
169
|
+
const parentElement = playerControlRef.value.parentElement;
|
|
170
|
+
if (parentElement) {
|
|
171
|
+
parentElement.removeEventListener('mouseenter', handleMouseEnter);
|
|
172
|
+
parentElement.removeEventListener('mouseleave', handleMouseLeave);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const touchStartCoords = ref<{ x: number; y: number } | null>(null);
|
|
178
|
+
|
|
179
|
+
// Touch distance calculation
|
|
180
|
+
const calculateTouchDistance = (start: { x: number; y: number }, end: Touch) => {
|
|
181
|
+
return Math.sqrt(Math.pow(end.clientX - start.x, 2) + Math.pow(end.clientY - start.y, 2));
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const isPlayerControlTarget = (target: Node) => {
|
|
185
|
+
return playerControlRef.value?.contains(target) || false;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const isLiveCoreViewTarget = (target: Node) => {
|
|
189
|
+
const container = document.getElementById('live-core-view-container');
|
|
190
|
+
return container?.contains(target) || false;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// Handle the touch in the player control area
|
|
194
|
+
const handlePlayerControlTouch = () => {
|
|
195
|
+
stopAutoHideControl();
|
|
196
|
+
startAutoHideControl();
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Handle the touch in the core view area of the live broadcast
|
|
200
|
+
const handleLiveCoreViewTouch = () => {
|
|
201
|
+
showControls.value = !showControls.value;
|
|
202
|
+
if (showControls.value) {
|
|
203
|
+
startAutoHideControl();
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const handleScreenTouchStart = (event: TouchEvent) => {
|
|
208
|
+
if (event.touches.length === 1) {
|
|
209
|
+
const touch = event.touches[0];
|
|
210
|
+
touchStartCoords.value = {
|
|
211
|
+
x: touch.clientX,
|
|
212
|
+
y: touch.clientY,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const handleScreenTouchMove = (event: TouchEvent) => {
|
|
218
|
+
if (playerControlRef.value && playerControlRef.value.contains(event.target as Node)) {
|
|
219
|
+
stopAutoHideControl();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const handleScreenTouchEnd = (event: TouchEvent) => {
|
|
225
|
+
if (!touchStartCoords.value) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const touchEnd = event.changedTouches[0];
|
|
230
|
+
const distance = calculateTouchDistance(touchStartCoords.value, touchEnd);
|
|
231
|
+
|
|
232
|
+
const MAX_CLICK_DISTANCE = 20;
|
|
233
|
+
if (distance > MAX_CLICK_DISTANCE) {
|
|
234
|
+
touchStartCoords.value = null;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const target = event.target as Node;
|
|
239
|
+
|
|
240
|
+
if (isPlayerControlTarget(target)) {
|
|
241
|
+
handlePlayerControlTouch();
|
|
242
|
+
} else if (isLiveCoreViewTarget(target)) {
|
|
243
|
+
handleLiveCoreViewTouch();
|
|
244
|
+
} else {
|
|
245
|
+
showControls.value = false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
touchStartCoords.value = null;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const setupTouchEventListeners = () => {
|
|
252
|
+
if (isMobile) {
|
|
253
|
+
document.addEventListener('touchstart', handleScreenTouchStart, true);
|
|
254
|
+
document.addEventListener('touchmove', handleScreenTouchMove, true);
|
|
255
|
+
document.addEventListener('touchend', handleScreenTouchEnd, true);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const removeTouchEventListeners = () => {
|
|
260
|
+
if (isMobile) {
|
|
261
|
+
document.removeEventListener('touchstart', handleScreenTouchStart, true);
|
|
262
|
+
document.removeEventListener('touchmove', handleScreenTouchMove, true);
|
|
263
|
+
document.removeEventListener('touchend', handleScreenTouchEnd, true);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const handleMutedChange = async (muted: boolean) => {
|
|
268
|
+
isMuted.value = muted;
|
|
269
|
+
if (muted) {
|
|
270
|
+
await setVolume(0);
|
|
271
|
+
} else {
|
|
272
|
+
await setVolume(currentVolume.value);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const cleanupEventListeners = () => {
|
|
277
|
+
removeTouchEventListeners();
|
|
278
|
+
removeParentMouseListener();
|
|
279
|
+
stopAutoHideControl();
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
onMounted(() => {
|
|
283
|
+
setupTouchEventListeners();
|
|
284
|
+
setupParentMouseListener();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
onBeforeUnmount(() => {
|
|
288
|
+
cleanupEventListeners();
|
|
289
|
+
cleanup();
|
|
290
|
+
});
|
|
291
|
+
</script>
|
|
292
|
+
|
|
293
|
+
<style scoped lang="scss">
|
|
294
|
+
.pc-mode {
|
|
295
|
+
position: absolute;
|
|
296
|
+
bottom: 0;
|
|
297
|
+
left: 0;
|
|
298
|
+
right: 0;
|
|
299
|
+
z-index: 10;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.mobile-mode {
|
|
303
|
+
position: fixed !important;
|
|
304
|
+
bottom: 0;
|
|
305
|
+
left: 0;
|
|
306
|
+
right: 0;
|
|
307
|
+
z-index: 999999;
|
|
308
|
+
pointer-events: auto;
|
|
309
|
+
}
|
|
310
|
+
.playback-controls {
|
|
311
|
+
background: #000000;
|
|
312
|
+
padding: 12px 0;
|
|
313
|
+
display: flex;
|
|
314
|
+
width: calc(100% + 1px); // Solve the problem of 1px deviation during absolute positioning
|
|
315
|
+
align-items: center;
|
|
316
|
+
box-sizing: border-box;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.control-buttons {
|
|
320
|
+
display: flex;
|
|
321
|
+
align-items: center;
|
|
322
|
+
justify-content: space-around;
|
|
323
|
+
width: 100%;
|
|
324
|
+
pointer-events: all;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.center-controls {
|
|
328
|
+
display: flex;
|
|
329
|
+
align-items: center;
|
|
330
|
+
gap: 16px;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.control-btn {
|
|
334
|
+
background: transparent;
|
|
335
|
+
border: none;
|
|
336
|
+
border-radius: 50%;
|
|
337
|
+
width: 36px;
|
|
338
|
+
height: 36px;
|
|
339
|
+
display: flex;
|
|
340
|
+
align-items: center;
|
|
341
|
+
justify-content: center;
|
|
342
|
+
cursor: pointer;
|
|
343
|
+
transition: all 0.2s ease;
|
|
344
|
+
color: white;
|
|
345
|
+
|
|
346
|
+
&:hover {
|
|
347
|
+
background: rgba(255, 255, 255, 0.1);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
&:active {
|
|
351
|
+
transform: scale(0.95);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.btn-icon {
|
|
355
|
+
width: 20px;
|
|
356
|
+
height: 20px;
|
|
357
|
+
fill: currentColor;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.play-pause-btn {
|
|
362
|
+
.tui-icon {
|
|
363
|
+
transform: scale(1.5);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.audio-control-btn {
|
|
368
|
+
&:active {
|
|
369
|
+
transform: unset;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.playback-time {
|
|
374
|
+
color: white;
|
|
375
|
+
font-size: 14px;
|
|
376
|
+
font-weight: 500;
|
|
377
|
+
margin-left: 16px;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.right-controls {
|
|
381
|
+
display: flex;
|
|
382
|
+
align-items: center;
|
|
383
|
+
gap: 16px;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.fullscreen-btn {
|
|
387
|
+
.btn-icon {
|
|
388
|
+
width: 18px;
|
|
389
|
+
height: 18px;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.more-btn {
|
|
394
|
+
.btn-icon {
|
|
395
|
+
width: 18px;
|
|
396
|
+
height: 18px;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.player-control-enter-active,
|
|
401
|
+
.player-control-leave-active {
|
|
402
|
+
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
403
|
+
will-change: transform, opacity;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.player-control-enter-from {
|
|
407
|
+
opacity: 0;
|
|
408
|
+
transform: translateY(100%);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.player-control-enter-to {
|
|
412
|
+
opacity: 1;
|
|
413
|
+
transform: translateY(0);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.player-control-leave-from {
|
|
417
|
+
opacity: 1;
|
|
418
|
+
transform: translateY(0);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.player-control-leave-to {
|
|
422
|
+
opacity: 0;
|
|
423
|
+
transform: translateY(100%);
|
|
424
|
+
}
|
|
425
|
+
</style>
|
|
426
|
+
|
|
427
|
+
<style lang="scss">
|
|
428
|
+
@import './PlayerControl.module.scss';
|
|
429
|
+
</style>
|