mdm-client 1.0.4 → 1.0.6
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/package.json +1 -1
- package/src/App.vue +1 -1
- package/src/components/LiveMultipleMeeting/LiveMultipleMeeting.vue +104 -5
- package/src/components/LivePoint/LivePoint.vue +25 -0
- package/src/components/LivePointMeeting/LivePointMeeting.vue +63 -2
- package/src/components/MiniumVideoDialog/MiniumVideoDialog.vue +185 -50
- package/src/components/other/appointDialog.vue +1 -1
- package/src/components/other/layoutSwitch.vue +1 -1
- package/src/components/other/leaveOptionDialog.vue +1 -1
- package/src/components/other/moreOptionDialog.vue +1 -1
- package/src/components/other/screenShareBoard.vue +2 -2
- package/src/utils/livekit/live-client-esm.js +1 -1
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
></div>
|
|
20
20
|
<div class="identity">{{ curSpeaker.name }}</div>
|
|
21
21
|
</div>
|
|
22
|
+
<!-- 当当前讲话者未开启摄像头时,显示占位板(与 liveMultipleMeeting 的 board 样式一致) -->
|
|
23
|
+
<div v-if="!curSpeakerCameraStatus" class="board">
|
|
24
|
+
<div class="board-icon"></div>
|
|
25
|
+
</div>
|
|
22
26
|
</div>
|
|
23
27
|
<div class="speaking-wrapper" v-show="!isExpand">
|
|
24
28
|
<div :class="['cur-speaking-mic', curSpeakerMicrophoneStatus ? 'mic-on' : 'mic-off']"></div>
|
|
@@ -34,8 +38,10 @@
|
|
|
34
38
|
>
|
|
35
39
|
<div class="minium-video-dialog-footer-btn mic-on" v-if="localMicrophoneStatus" @click="changeLocalMicrophoneStatus"></div>
|
|
36
40
|
<div class="minium-video-dialog-footer-btn mic-off" v-else @click="changeLocalMicrophoneStatus"></div>
|
|
37
|
-
<
|
|
38
|
-
|
|
41
|
+
<template v-if="roomMode === 'auto'">
|
|
42
|
+
<div class="minium-video-dialog-footer-btn camera-on" v-if="localCameraStatus" @click="changeLocalCameraStatus"></div>
|
|
43
|
+
<div class="minium-video-dialog-footer-btn camera-off" v-else @click="changeLocalCameraStatus"></div>
|
|
44
|
+
</template>
|
|
39
45
|
<div class="minium-video-dialog-footer-btn reset" @click="reset"></div>
|
|
40
46
|
<div :class="['minium-video-dialog-footer-btn', isExpand ? 'slide' : 'expand']" @click="changeExpand"></div>
|
|
41
47
|
</div>
|
|
@@ -59,6 +65,8 @@ export default {
|
|
|
59
65
|
isExpand: true,
|
|
60
66
|
curSpeakers: [],
|
|
61
67
|
curSpeaker: null,
|
|
68
|
+
// 统一维护的 liveClient 实例,避免在各处重复通过 window 访问
|
|
69
|
+
liveClient: null,
|
|
62
70
|
localCameraStatus: false,
|
|
63
71
|
localMicrophoneStatus: false,
|
|
64
72
|
curSpeakerMicrophoneStatus: false,
|
|
@@ -66,6 +74,8 @@ export default {
|
|
|
66
74
|
curSpeakerTrack: null,
|
|
67
75
|
showFooter: false,
|
|
68
76
|
footerTimer: null,
|
|
77
|
+
// room模式,auto-音视频,audio-仅音频
|
|
78
|
+
roomMode: 'auto',
|
|
69
79
|
};
|
|
70
80
|
},
|
|
71
81
|
computed: {
|
|
@@ -84,7 +94,10 @@ export default {
|
|
|
84
94
|
},
|
|
85
95
|
beforeDestroy() {
|
|
86
96
|
this.clearFooterTimer();
|
|
97
|
+
this.detachCurrentTrack();
|
|
87
98
|
this.dispatchLiveClientEvent();
|
|
99
|
+
// 组件销毁时清理引用
|
|
100
|
+
this.liveClient = null;
|
|
88
101
|
},
|
|
89
102
|
methods: {
|
|
90
103
|
changeExpand() {
|
|
@@ -131,19 +144,21 @@ export default {
|
|
|
131
144
|
|
|
132
145
|
// 初始化liveClient
|
|
133
146
|
initLiveClient() {
|
|
134
|
-
|
|
135
|
-
if(!liveClient) {
|
|
147
|
+
this.liveClient = window['liveClient'] || null;
|
|
148
|
+
if(!this.liveClient) {
|
|
136
149
|
return;
|
|
137
150
|
}
|
|
151
|
+
this.roomMode = this.liveClient?.roomMode || 'auto';
|
|
138
152
|
this.curSpeaker = {
|
|
139
|
-
identity: liveClient.room.localParticipant.identity,
|
|
140
|
-
name: liveClient.room.localParticipant.name,
|
|
153
|
+
identity: this.liveClient.room.localParticipant.identity,
|
|
154
|
+
name: this.liveClient.room.localParticipant.name,
|
|
141
155
|
};
|
|
142
|
-
this.localCameraStatus = liveClient.room.localParticipant.isCameraEnabled;
|
|
143
|
-
this.localMicrophoneStatus = liveClient.room.localParticipant.isMicrophoneEnabled;
|
|
144
|
-
this.curSpeakerMicrophoneStatus = liveClient.room.localParticipant.isMicrophoneEnabled;
|
|
145
|
-
this.curSpeakerCameraStatus = liveClient.room.localParticipant.isCameraEnabled;
|
|
146
|
-
|
|
156
|
+
this.localCameraStatus = this.liveClient.room.localParticipant.isCameraEnabled;
|
|
157
|
+
this.localMicrophoneStatus = this.liveClient.room.localParticipant.isMicrophoneEnabled;
|
|
158
|
+
this.curSpeakerMicrophoneStatus = this.liveClient.room.localParticipant.isMicrophoneEnabled;
|
|
159
|
+
this.curSpeakerCameraStatus = this.liveClient.room.localParticipant.isCameraEnabled;
|
|
160
|
+
// 仅在视频会议模式下记录并绑定视频轨道
|
|
161
|
+
this.curSpeakerTrack = this.roomMode === 'auto' ? this.trackData.videoTrack : null;
|
|
147
162
|
// 初始化视频轨道绑定
|
|
148
163
|
this.initVideoAttach();
|
|
149
164
|
// 监听并处理liveclient事件
|
|
@@ -194,22 +209,27 @@ export default {
|
|
|
194
209
|
},
|
|
195
210
|
|
|
196
211
|
initVideoAttach() {
|
|
197
|
-
if(this.
|
|
198
|
-
|
|
199
|
-
videoElm
|
|
212
|
+
if (this.roomMode !== 'auto') return
|
|
213
|
+
if (this.curSpeakerTrack) {
|
|
214
|
+
const videoElm = document.getElementById('video-contain')
|
|
215
|
+
if (videoElm) {
|
|
216
|
+
// 先解绑,确保不会重复附加
|
|
217
|
+
try { this.curSpeakerTrack.detach(videoElm) } catch (e) {}
|
|
218
|
+
this.curSpeakerTrack.attach(videoElm)
|
|
219
|
+
}
|
|
200
220
|
}
|
|
201
221
|
},
|
|
202
222
|
|
|
203
223
|
initLiveClientEvent() {
|
|
204
|
-
|
|
205
|
-
if(!liveClient) {
|
|
224
|
+
if(!this.liveClient) {
|
|
206
225
|
return;
|
|
207
226
|
}
|
|
208
|
-
liveClient.on("localCameraChange", this.handleLocalCameraChange);
|
|
209
|
-
liveClient.on("localMicrophoneChange", this.handleLocalMicrophoneChange);
|
|
210
|
-
liveClient.on("activeSpeakerChange", this.handleActiveSpeakerChange);
|
|
211
|
-
liveClient.on("videoCallRender", this.handleVideoCallRender);
|
|
212
|
-
liveClient.on("
|
|
227
|
+
this.liveClient.on("localCameraChange", this.handleLocalCameraChange);
|
|
228
|
+
this.liveClient.on("localMicrophoneChange", this.handleLocalMicrophoneChange);
|
|
229
|
+
this.liveClient.on("activeSpeakerChange", this.handleActiveSpeakerChange);
|
|
230
|
+
this.liveClient.on("videoCallRender", this.handleVideoCallRender);
|
|
231
|
+
this.liveClient.on("audioCallRender", this.handleAudioCallRender);
|
|
232
|
+
this.liveClient.on("roomDisconnected", this.handleRoomDisconnected);
|
|
213
233
|
},
|
|
214
234
|
|
|
215
235
|
handleLocalCameraChange(e) {
|
|
@@ -221,19 +241,35 @@ export default {
|
|
|
221
241
|
},
|
|
222
242
|
|
|
223
243
|
handleActiveSpeakerChange(e) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
244
|
+
this.curSpeakers = Array.isArray(e) ? e : []
|
|
245
|
+
|
|
246
|
+
if(this.curSpeakers.length > 0) {
|
|
247
|
+
// 找到音量最大的讲话者
|
|
248
|
+
let maxAudioLevel = -1
|
|
249
|
+
let winner = null
|
|
250
|
+
this.curSpeakers.forEach(speaker => {
|
|
251
|
+
const level = typeof speaker.audioLevel === 'number' ? speaker.audioLevel : 0
|
|
252
|
+
if (level > maxAudioLevel) {
|
|
253
|
+
maxAudioLevel = level
|
|
254
|
+
winner = speaker
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
if (!winner) return
|
|
259
|
+
|
|
260
|
+
// 仅当 identity 变化时才切换,以减少抖动
|
|
261
|
+
if (winner.identity !== this.curSpeaker?.identity) {
|
|
262
|
+
// 解绑旧轨道,等待 videoCallRender 到来后再绑定新轨道
|
|
263
|
+
// detachCurrentTrack()
|
|
264
|
+
this.curSpeaker = {
|
|
265
|
+
identity: winner.identity,
|
|
266
|
+
name: winner.name || winner.identity,
|
|
267
|
+
isLocal: !!winner.isLocal,
|
|
231
268
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
269
|
+
// 在未收到渲染事件前,暂时认为摄像头状态未知/关闭,用以显示占位
|
|
270
|
+
// curSpeakerCameraStatus.value = false
|
|
271
|
+
// curSpeakerMicrophoneStatus.value = false
|
|
235
272
|
}
|
|
236
|
-
this.curSpeakers = e;
|
|
237
273
|
}
|
|
238
274
|
},
|
|
239
275
|
|
|
@@ -242,15 +278,61 @@ export default {
|
|
|
242
278
|
this.localCameraStatus = e.isCameraEnabled;
|
|
243
279
|
this.localMicrophoneStatus = e.isMicrophoneEnabled;
|
|
244
280
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
this.
|
|
248
|
-
|
|
249
|
-
|
|
281
|
+
// 利用 videoCallRender 的 remove 标记判断离会
|
|
282
|
+
if (e?.remove === true && e.identity === this.curSpeaker?.identity) {
|
|
283
|
+
this.switchToLocalAsCurrentSpeaker()
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
if (e.identity === this.curSpeaker?.identity) {
|
|
287
|
+
// 更新当前讲话者音视频状态
|
|
288
|
+
this.curSpeakerCameraStatus = this.roomMode === 'auto' ? !!e.isCameraEnabled : false
|
|
289
|
+
this.curSpeakerMicrophoneStatus = !!e.isMicrophoneEnabled
|
|
290
|
+
|
|
291
|
+
const videoElm = document.getElementById('video-contain')
|
|
292
|
+
|
|
293
|
+
if (this.roomMode === 'auto' && e.videoTrack && videoElm) {
|
|
294
|
+
// 使用 sid 作为更稳定的身份标识;sid 发布后会保持不变
|
|
295
|
+
const oldTrack = this.curSpeakerTrack
|
|
296
|
+
const newTrack = e.videoTrack
|
|
297
|
+
if (oldTrack) {
|
|
298
|
+
const sameBySid = oldTrack.sid && newTrack?.sid && oldTrack.sid === newTrack.sid
|
|
299
|
+
// 当 sid 不同或对象引用不同(无 sid 时),说明发生实际轨道切换,需要解绑旧轨道
|
|
300
|
+
if (!sameBySid) {
|
|
301
|
+
try { oldTrack.detach(videoElm) } catch (err) {}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
this.curSpeakerTrack = newTrack
|
|
305
|
+
// 重新绑定(同轨道重复 attach 时内部已处理,不影响稳定性)
|
|
306
|
+
try { this.curSpeakerTrack.attach(videoElm) } catch (err) {}
|
|
307
|
+
} else {
|
|
308
|
+
// 摄像头关闭或无轨道,解绑任何已绑定轨道
|
|
309
|
+
if (this.curSpeakerTrack && videoElm) {
|
|
310
|
+
try { this.curSpeakerTrack.detach(videoElm) } catch (err) {}
|
|
311
|
+
}
|
|
312
|
+
this.curSpeakerTrack = null
|
|
250
313
|
}
|
|
251
314
|
}
|
|
252
315
|
},
|
|
253
316
|
|
|
317
|
+
// 音频会议渲染事件:不进行任何视频轨道的绑定,仅更新状态并显示占位
|
|
318
|
+
handleAudioCallRender(e) {
|
|
319
|
+
if (e?.remove === true && e.identity === this.curSpeaker?.identity) {
|
|
320
|
+
this.switchToLocalAsCurrentSpeaker()
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
if (e.local) {
|
|
324
|
+
this.localMicrophoneStatus = !!e.isMicrophoneEnabled
|
|
325
|
+
}
|
|
326
|
+
if (e.identity === this.curSpeaker?.identity) {
|
|
327
|
+
this.curSpeakerMicrophoneStatus = !!e.isMicrophoneEnabled
|
|
328
|
+
// 音频模式下不显示摄像头画面
|
|
329
|
+
this.curSpeakerCameraStatus = false
|
|
330
|
+
// 确保解绑任何已绑定的视频轨道
|
|
331
|
+
this.detachCurrentTrack()
|
|
332
|
+
this.curSpeakerTrack = null
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
|
|
254
336
|
handleRoomDisconnected() {
|
|
255
337
|
this.$emit('miniVideoDialogClose');
|
|
256
338
|
},
|
|
@@ -260,27 +342,25 @@ export default {
|
|
|
260
342
|
},
|
|
261
343
|
|
|
262
344
|
dispatchLiveClientEvent() {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
liveClient.off("
|
|
266
|
-
liveClient.off("
|
|
267
|
-
liveClient.off("
|
|
268
|
-
liveClient.off("
|
|
269
|
-
liveClient.off("roomDisconnected", this.handleRoomDisconnected);
|
|
345
|
+
if(this.liveClient) {
|
|
346
|
+
this.liveClient.off("localCameraChange", this.handleLocalCameraChange);
|
|
347
|
+
this.liveClient.off("localMicrophoneChange", this.handleLocalMicrophoneChange);
|
|
348
|
+
this.liveClient.off("activeSpeakerChange", this.handleActiveSpeakerChange);
|
|
349
|
+
this.liveClient.off("videoCallRender", this.handleVideoCallRender);
|
|
350
|
+
this.liveClient.off("audioCallRender", this.handleAudioCallRender)
|
|
351
|
+
this.liveClient.off("roomDisconnected", this.handleRoomDisconnected);
|
|
270
352
|
}
|
|
271
353
|
},
|
|
272
354
|
|
|
273
355
|
async changeLocalMicrophoneStatus() {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
await liveClient.changeMicrophoneStatus();
|
|
356
|
+
if(this.liveClient) {
|
|
357
|
+
await this.liveClient.changeMicrophoneStatus();
|
|
277
358
|
}
|
|
278
359
|
},
|
|
279
360
|
|
|
280
361
|
async changeLocalCameraStatus() {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
await liveClient.changeCameraStatus();
|
|
362
|
+
if(this.liveClient) {
|
|
363
|
+
await this.liveClient.changeCameraStatus();
|
|
284
364
|
}
|
|
285
365
|
},
|
|
286
366
|
|
|
@@ -290,6 +370,42 @@ export default {
|
|
|
290
370
|
this.footerTimer = null;
|
|
291
371
|
}
|
|
292
372
|
},
|
|
373
|
+
|
|
374
|
+
// 工具:解绑当前视频轨道
|
|
375
|
+
detachCurrentTrack() {
|
|
376
|
+
try {
|
|
377
|
+
const videoElm = document.getElementById('video-contain')
|
|
378
|
+
if (this.curSpeakerTrack && videoElm) {
|
|
379
|
+
this.curSpeakerTrack.detach(videoElm)
|
|
380
|
+
}
|
|
381
|
+
} catch (e) {}
|
|
382
|
+
},
|
|
383
|
+
// 工具:切换当前讲话者为本地参会者
|
|
384
|
+
switchToLocalAsCurrentSpeaker() {
|
|
385
|
+
try {
|
|
386
|
+
const lp = this.liveClient?.room?.localParticipant
|
|
387
|
+
this.detachCurrentTrack()
|
|
388
|
+
if (lp) {
|
|
389
|
+
this.curSpeaker = {
|
|
390
|
+
identity: lp.identity,
|
|
391
|
+
name: lp.name,
|
|
392
|
+
isLocal: true,
|
|
393
|
+
}
|
|
394
|
+
this.curSpeakerCameraStatus = this.roomMode === 'auto' ? !!lp.isCameraEnabled : false
|
|
395
|
+
this.curSpeakerMicrophoneStatus = !!lp.isMicrophoneEnabled
|
|
396
|
+
// 立即尝试绑定已知的本地轨道(如存在),后续仍会由 videoCallRender 纠正
|
|
397
|
+
if (this.roomMode === 'auto') {
|
|
398
|
+
const track = this.trackData?.videoTrack
|
|
399
|
+
if (track && this.curSpeakerCameraStatus) {
|
|
400
|
+
this.curSpeakerTrack = track
|
|
401
|
+
this.initVideoAttach()
|
|
402
|
+
}
|
|
403
|
+
} else {
|
|
404
|
+
this.curSpeakerTrack = null
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
} catch (e) {}
|
|
408
|
+
}
|
|
293
409
|
},
|
|
294
410
|
}
|
|
295
411
|
</script>
|
|
@@ -318,6 +434,25 @@ export default {
|
|
|
318
434
|
border-radius: 4px;
|
|
319
435
|
object-fit: contain;
|
|
320
436
|
}
|
|
437
|
+
// 与 liveMultipleMeeting 保持一致的缺省板样式
|
|
438
|
+
.board {
|
|
439
|
+
position: absolute;
|
|
440
|
+
width: 100%;
|
|
441
|
+
height: 100%;
|
|
442
|
+
left: 0;
|
|
443
|
+
top: 0;
|
|
444
|
+
z-index: 30;
|
|
445
|
+
display: flex;
|
|
446
|
+
align-items: center;
|
|
447
|
+
justify-content: center;
|
|
448
|
+
background: var(--meeting-board-bg) no-repeat center / 100% 100%;
|
|
449
|
+
.board-icon {
|
|
450
|
+
width: 20%;
|
|
451
|
+
height: auto;
|
|
452
|
+
aspect-ratio: 1/1;
|
|
453
|
+
background: url("../../assets/image/common/default_avatar.png") no-repeat center / 100% 100%;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
321
456
|
.describe {
|
|
322
457
|
position: absolute;
|
|
323
458
|
bottom: 6px;
|
|
@@ -49,7 +49,7 @@ export default {
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
mounted() {
|
|
52
|
-
this.isFluencyPriority = (this.screenShareCaptureOption
|
|
52
|
+
this.isFluencyPriority = (this.screenShareCaptureOption?.resolution?.frameRate === 60);
|
|
53
53
|
this.systemAudioInclude = this.screenShareCaptureOption.systemAudio === "include";
|
|
54
54
|
},
|
|
55
55
|
methods: {
|
|
@@ -64,7 +64,7 @@ export default {
|
|
|
64
64
|
@import "../../assets/style/mixin.scss";
|
|
65
65
|
.screen-share-board {
|
|
66
66
|
position: absolute;
|
|
67
|
-
z-index:
|
|
67
|
+
z-index: 2050;
|
|
68
68
|
left: 0;
|
|
69
69
|
top: 0;
|
|
70
70
|
width: 100%;
|