mdm-client 1.0.3 → 1.0.5
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 +72 -67
- package/src/assets/image/common/layout-active16.png +0 -0
- package/src/assets/image/common/layout-active25.png +0 -0
- package/src/assets/image/common/layout-active3.png +0 -0
- package/src/assets/image/common/layout-active9.png +0 -0
- package/src/assets/image/common/layout16.png +0 -0
- package/src/assets/image/common/layout25.png +0 -0
- package/src/assets/image/common/layout3.png +0 -0
- package/src/assets/image/common/layout9.png +0 -0
- package/src/assets/image/common/mirror.png +0 -0
- package/src/assets/image/common/rotate_icon1.png +0 -0
- package/src/assets/image/common/rotate_icon2.png +0 -0
- package/src/assets/image/common/rotate_icon3.png +0 -0
- package/src/assets/image/common/rotate_icon4.png +0 -0
- package/src/assets/style/base.scss +5 -0
- package/src/components/LiveMulti/LiveMulti.vue +27 -6
- package/src/components/LiveMultipleMeeting/LiveMultipleMeeting.vue +1163 -99
- package/src/components/LiveMultipleMeeting/style/index.scss +145 -14
- package/src/components/LivePoint/LivePoint.vue +71 -208
- package/src/components/LivePointMeeting/LivePointMeeting.vue +223 -13
- package/src/components/LivePointMeeting/style/index.scss +35 -0
- package/src/components/MeetingReadyDialog/MeetingReadyDialog.vue +96 -14
- package/src/components/MiniumVideoDialog/MiniumVideoDialog.vue +185 -50
- package/src/components/other/addressBook.vue +137 -20
- package/src/components/other/appointDialog.vue +1 -1
- package/src/components/other/customLayout.vue +368 -202
- package/src/components/other/layoutSwitch.vue +253 -37
- package/src/components/other/leadershipFocus.vue +422 -0
- package/src/components/other/leaveOptionDialog.vue +1 -1
- package/src/components/other/moreOptionDialog.vue +17 -1
- package/src/components/other/screenShareBoard.vue +2 -2
- package/src/components/other/selectDialog.vue +1 -1
- package/src/components/other/selectSpecialDialog.vue +1 -1
- package/src/utils/api.js +19 -0
- package/src/utils/livekit/live-client-esm.js +1 -1
|
@@ -43,10 +43,12 @@
|
|
|
43
43
|
<LayoutSwitch
|
|
44
44
|
v-if="layoutSwitchShow"
|
|
45
45
|
:currentLayout="currentLayout"
|
|
46
|
+
:current-grid-size="currentGridSize"
|
|
46
47
|
:layoutList="layoutList"
|
|
47
48
|
:layoutLabels="layoutLabels"
|
|
48
49
|
:currentRoomMode="currentRoomMode"
|
|
49
50
|
@setLayout="manualSetLayout"
|
|
51
|
+
@setGridSize="setGridSize"
|
|
50
52
|
@mouseenter.native="layoutSwitchShow = true"
|
|
51
53
|
@mouseleave.native="layoutSwitchShow = false"
|
|
52
54
|
></LayoutSwitch>
|
|
@@ -86,6 +88,12 @@
|
|
|
86
88
|
@screenShareOptionChange="screenShareOptionChange"
|
|
87
89
|
@stopScreenShare="stopScreenShare"
|
|
88
90
|
></ScreenShareBoard>
|
|
91
|
+
<LeadershipFocus
|
|
92
|
+
v-if="isLeaderShipFocusShow"
|
|
93
|
+
:participant="curLeadershipFocus"
|
|
94
|
+
:local-metadata="localMetadata"
|
|
95
|
+
@moreClick="leaderShipMoreClick"
|
|
96
|
+
@microphoneClick="leaderShipMicroClick"></LeadershipFocus>
|
|
89
97
|
</div>
|
|
90
98
|
|
|
91
99
|
<!-- 会议控制栏 -->
|
|
@@ -321,6 +329,7 @@
|
|
|
321
329
|
:participants="participants"
|
|
322
330
|
:meetingHost="meetingHost"
|
|
323
331
|
:meetingCoHost="meetingCoHost"
|
|
332
|
+
:leaderShipFocus="leaderShipFocus"
|
|
324
333
|
@close="moreDialogShow = false"
|
|
325
334
|
@setToBlur="setMemberBlur"
|
|
326
335
|
@removeFromBlur="removeCurBlur"
|
|
@@ -335,14 +344,17 @@
|
|
|
335
344
|
@remove="removeMember"
|
|
336
345
|
@setToCoHost="setToCoHost"
|
|
337
346
|
@removeFromCoHost="removeFromCoHost"
|
|
347
|
+
@openLeadershipFocus="openLeadershipFocus"
|
|
348
|
+
@closeLeadershipFocus="closeLeadershipFocus"
|
|
338
349
|
>
|
|
339
350
|
</MoreOptionDialog>
|
|
340
351
|
<AddressBook
|
|
341
|
-
:inviteList="inviteList"
|
|
342
352
|
:baseUrl="baseUrl"
|
|
343
353
|
:token="token"
|
|
354
|
+
:tab-list="addressBookTabList"
|
|
344
355
|
@appendInvitePeople="appendInvitePeople"
|
|
345
356
|
@appendInviteDevice="appendInviteDevice"
|
|
357
|
+
@appendInviteTerminal="appendInviteTerminal"
|
|
346
358
|
></AddressBook>
|
|
347
359
|
<UpdateNameDialog @updateNameConfirm="updateNameConfirm"></UpdateNameDialog>
|
|
348
360
|
<SettingDialog
|
|
@@ -375,7 +387,9 @@ import SelectSpecialDialog from "../other/selectSpecialDialog.vue";
|
|
|
375
387
|
import ScreenShareBoard from "../other/screenShareBoard.vue";
|
|
376
388
|
import SettingDialog from "../other/settingDialog.vue";
|
|
377
389
|
import CustomLayout from "../other/customLayout.vue";
|
|
390
|
+
import LeadershipFocus from "../other/leadershipFocus.vue";
|
|
378
391
|
|
|
392
|
+
let ringResizeObserver = null;
|
|
379
393
|
export default {
|
|
380
394
|
name: "LiveMultipleMeeting",
|
|
381
395
|
components: {
|
|
@@ -394,6 +408,7 @@ export default {
|
|
|
394
408
|
ScreenShareBoard,
|
|
395
409
|
SettingDialog,
|
|
396
410
|
CustomLayout,
|
|
411
|
+
LeadershipFocus
|
|
397
412
|
},
|
|
398
413
|
props: {
|
|
399
414
|
tempMeetingName: {
|
|
@@ -425,6 +440,10 @@ export default {
|
|
|
425
440
|
type: Array,
|
|
426
441
|
default: () => [],
|
|
427
442
|
},
|
|
443
|
+
meetingTerminals: {
|
|
444
|
+
type: Array,
|
|
445
|
+
default: () => [],
|
|
446
|
+
},
|
|
428
447
|
userData: {
|
|
429
448
|
type: Object,
|
|
430
449
|
default: () => ({}),
|
|
@@ -453,6 +472,21 @@ export default {
|
|
|
453
472
|
type: String,
|
|
454
473
|
default: "",
|
|
455
474
|
},
|
|
475
|
+
// 通讯录可见的标签页配置
|
|
476
|
+
addressBookTabList: {
|
|
477
|
+
type: Array,
|
|
478
|
+
default: () => ["组织架构", "人员", "会议终端", "volte", "设备", "监控", "常用分组"],
|
|
479
|
+
},
|
|
480
|
+
// 语音激励开关
|
|
481
|
+
isVoiceMotivationOpen: {
|
|
482
|
+
type: Boolean,
|
|
483
|
+
default: false,
|
|
484
|
+
},
|
|
485
|
+
// 镜像开关
|
|
486
|
+
isMirror: {
|
|
487
|
+
type: Boolean,
|
|
488
|
+
default: false,
|
|
489
|
+
},
|
|
456
490
|
},
|
|
457
491
|
data() {
|
|
458
492
|
return {
|
|
@@ -496,9 +530,9 @@ export default {
|
|
|
496
530
|
// 布局数据
|
|
497
531
|
currentLayout: "grid",
|
|
498
532
|
currentRoomMode: "normal",
|
|
499
|
-
layoutList: ["grid", "rightSide"],
|
|
500
|
-
layoutLabels: ["宫格", "右侧边栏"],
|
|
501
|
-
|
|
533
|
+
layoutList: ["grid", "rightSide", "ring"],
|
|
534
|
+
layoutLabels: ["宫格", "右侧边栏", "环形"],
|
|
535
|
+
currentGridSize: 9,
|
|
502
536
|
// 状态数据
|
|
503
537
|
duration: 0,
|
|
504
538
|
memberManageShow: false,
|
|
@@ -582,6 +616,15 @@ export default {
|
|
|
582
616
|
durationInterval: null,
|
|
583
617
|
footerInterval: null,
|
|
584
618
|
unjoinParticipantInterval: null,
|
|
619
|
+
// 保存与会者画面旋转状态
|
|
620
|
+
rotateDegreeMap: new Map(),
|
|
621
|
+
// 当前聚焦画面
|
|
622
|
+
leaderShipFocus: ""
|
|
623
|
+
};
|
|
624
|
+
},
|
|
625
|
+
provide() {
|
|
626
|
+
return {
|
|
627
|
+
rotateDegreeMap: this.rotateDegreeMap,
|
|
585
628
|
};
|
|
586
629
|
},
|
|
587
630
|
computed: {
|
|
@@ -594,12 +637,33 @@ export default {
|
|
|
594
637
|
participantIdentities() {
|
|
595
638
|
return this.participantNum > 0 ? this.participants.map((item) => item.identity) : [];
|
|
596
639
|
},
|
|
640
|
+
isLeaderShipFocusShow() {
|
|
641
|
+
if (!this.leaderShipFocus) {
|
|
642
|
+
return false
|
|
643
|
+
}
|
|
644
|
+
if (this.participantNum <= 0) {
|
|
645
|
+
return false
|
|
646
|
+
}
|
|
647
|
+
if (this.participantIdentities.includes(this.leaderShipFocus)) {
|
|
648
|
+
return true
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
curLeadershipFocus() {
|
|
652
|
+
if (this.participantNum <= 0 || !this.leaderShipFocus) {
|
|
653
|
+
return {}
|
|
654
|
+
} else {
|
|
655
|
+
return this.participants.find(item => item.identity === this.leaderShipFocus)
|
|
656
|
+
}
|
|
657
|
+
},
|
|
597
658
|
inviteNum() {
|
|
598
659
|
return this.inviteList.length;
|
|
599
660
|
},
|
|
600
661
|
deviceNum() {
|
|
601
662
|
return this.deviceList.length;
|
|
602
663
|
},
|
|
664
|
+
terminalNum() {
|
|
665
|
+
return this.meetingTerminals.length;
|
|
666
|
+
},
|
|
603
667
|
invitedNum() {
|
|
604
668
|
return this.tempInvitedList?.length || 0;
|
|
605
669
|
},
|
|
@@ -621,22 +685,55 @@ export default {
|
|
|
621
685
|
return "point-turn";
|
|
622
686
|
} else {
|
|
623
687
|
if (this.currentLayout === "grid") {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
688
|
+
// 当前为宫格布局时, 不同与会者数量对应不同样式
|
|
689
|
+
if (this.currentGridSize === 9) {
|
|
690
|
+
if (this.participantNum <= 1) {
|
|
691
|
+
return 'grid1'
|
|
692
|
+
} else if (this.participantNum <= 2) {
|
|
693
|
+
return 'grid2'
|
|
694
|
+
} else if (this.participantNum <= 4) {
|
|
695
|
+
return 'grid3'
|
|
696
|
+
} else if (this.participantNum <= 6) {
|
|
697
|
+
return 'grid4'
|
|
698
|
+
} else if (this.participantNum <= 9) {
|
|
699
|
+
return 'grid5'
|
|
700
|
+
} else {
|
|
701
|
+
return 'grid5'
|
|
702
|
+
}
|
|
703
|
+
} else if (this.currentGridSize === 16) {
|
|
704
|
+
if (this.participantNum <= 1) {
|
|
705
|
+
return 'grid1'
|
|
706
|
+
} else if (this.participantNum <= 2) {
|
|
707
|
+
return 'grid2'
|
|
708
|
+
} else if (this.participantNum <= 4) {
|
|
709
|
+
return 'grid3'
|
|
710
|
+
} else if (this.participantNum <= 6) {
|
|
711
|
+
return 'grid4'
|
|
712
|
+
} else if (this.participantNum <= 9) {
|
|
713
|
+
return 'grid5'
|
|
714
|
+
} else if (this.participantNum <= 16) {
|
|
715
|
+
return 'grid6'
|
|
716
|
+
} else {
|
|
717
|
+
return 'grid6'
|
|
718
|
+
}
|
|
719
|
+
} else if (this.currentGridSize === 25) {
|
|
720
|
+
if (this.participantNum <= 1) {
|
|
721
|
+
return 'grid1'
|
|
722
|
+
} else if (this.participantNum <= 2) {
|
|
723
|
+
return 'grid2'
|
|
724
|
+
} else if (this.participantNum <= 4) {
|
|
725
|
+
return 'grid3'
|
|
726
|
+
} else if (this.participantNum <= 6) {
|
|
727
|
+
return 'grid4'
|
|
728
|
+
} else if (this.participantNum <= 9) {
|
|
729
|
+
return 'grid5'
|
|
730
|
+
} else if (this.participantNum <= 16) {
|
|
731
|
+
return 'grid6'
|
|
732
|
+
} else if (this.participantNum <= 25) {
|
|
733
|
+
return 'grid7'
|
|
734
|
+
} else {
|
|
735
|
+
return 'grid7'
|
|
736
|
+
}
|
|
640
737
|
}
|
|
641
738
|
} else {
|
|
642
739
|
return this.currentLayout;
|
|
@@ -695,6 +792,33 @@ export default {
|
|
|
695
792
|
});
|
|
696
793
|
},
|
|
697
794
|
},
|
|
795
|
+
meetingNum: {
|
|
796
|
+
handler(newVal) {
|
|
797
|
+
if (!newVal) return
|
|
798
|
+
try {
|
|
799
|
+
const key = `lk:ss_prefs:${newVal}`
|
|
800
|
+
const raw = sessionStorage.getItem(key)
|
|
801
|
+
if (!raw) return
|
|
802
|
+
const saved = JSON.parse(raw)
|
|
803
|
+
if (saved && typeof saved === 'object') {
|
|
804
|
+
if (saved.systemAudio === 'include' || saved.systemAudio === 'exclude') {
|
|
805
|
+
this.screenShareCaptureOption.systemAudio = saved.systemAudio
|
|
806
|
+
}
|
|
807
|
+
if (saved.resolution && typeof saved.resolution === 'object') {
|
|
808
|
+
const { width, height, frameRate } = saved.resolution
|
|
809
|
+
this.screenShareCaptureOption.resolution = {
|
|
810
|
+
width: width ?? 1920,
|
|
811
|
+
height: height ?? 1080,
|
|
812
|
+
frameRate: typeof frameRate === 'number' ? frameRate : 60,
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
} catch (err) {
|
|
817
|
+
// 忽略解析异常
|
|
818
|
+
}
|
|
819
|
+
},
|
|
820
|
+
immediate: true,
|
|
821
|
+
}
|
|
698
822
|
},
|
|
699
823
|
created() {
|
|
700
824
|
this.showMessage = new ShowMessage();
|
|
@@ -714,14 +838,148 @@ export default {
|
|
|
714
838
|
}
|
|
715
839
|
this.setPageFooterVisible(5);
|
|
716
840
|
this.initGlobleEvent();
|
|
841
|
+
// 观察#room尺寸变化,动态同步环形布局顶部高度
|
|
842
|
+
this.observeRoomResizeForRing();
|
|
843
|
+
// 首次挂载后尝试同步一次高度
|
|
844
|
+
requestAnimationFrame(() => this.updateRingTopHeight());
|
|
717
845
|
},
|
|
718
846
|
beforeDestroy() {
|
|
719
847
|
this.stopUnjoinParticipantPolling();
|
|
720
848
|
this.dispatchLiveClientEvent();
|
|
721
849
|
this.removeGlobleEvent();
|
|
850
|
+
// 清理observer或事件监听
|
|
851
|
+
if (ringResizeObserver) {
|
|
852
|
+
try { ringResizeObserver.disconnect(); } catch (e) {}
|
|
853
|
+
ringResizeObserver = null;
|
|
854
|
+
} else {
|
|
855
|
+
window.removeEventListener('resize', this.updateRingTopHeight);
|
|
856
|
+
}
|
|
722
857
|
this.liveClient = null;
|
|
723
858
|
},
|
|
724
859
|
methods: {
|
|
860
|
+
handleInviteType(integrationType = 0) {
|
|
861
|
+
// 根据integrationType返回对应的makeCall的type参数
|
|
862
|
+
switch (Number(integrationType)) {
|
|
863
|
+
case 0:
|
|
864
|
+
return 1
|
|
865
|
+
case 1:
|
|
866
|
+
return 2
|
|
867
|
+
case 2:
|
|
868
|
+
return 3
|
|
869
|
+
default:
|
|
870
|
+
return 1
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
leaderShipMoreClick(e) {
|
|
874
|
+
// 动态计算选项数量以获得精确的弹窗高度
|
|
875
|
+
const optionCount = this.getOptionCount(e.identity)
|
|
876
|
+
|
|
877
|
+
// 使用工具函数计算弹窗位置(使用真实的MoreOptionDialog尺寸)
|
|
878
|
+
this.optionDialogOffset = this.calculateDialogPosition(e.event, {
|
|
879
|
+
dialogWidth: 130,
|
|
880
|
+
dialogHeight: 350, // 备用高度
|
|
881
|
+
optionCount: optionCount, // 动态计算精确高度
|
|
882
|
+
preferredPosition: 'bottom-left',
|
|
883
|
+
})
|
|
884
|
+
|
|
885
|
+
if (this.optionIdentity === e.identity && this.moreDialogShow) {
|
|
886
|
+
this.moreDialogShow = false
|
|
887
|
+
} else {
|
|
888
|
+
this.moreDialogShow = false
|
|
889
|
+
this.optionIdentity = e.identity
|
|
890
|
+
this.moreDialogShow = true
|
|
891
|
+
}
|
|
892
|
+
},
|
|
893
|
+
async leaderShipMicroClick() {
|
|
894
|
+
await this.liveClient.changeParticipantMicrophoneStatus(e.identity, e.isMuted)
|
|
895
|
+
},
|
|
896
|
+
// 聚焦画面
|
|
897
|
+
async openLeadershipFocus(identity) {
|
|
898
|
+
await this.liveClient.openLeadershipFocus(identity)
|
|
899
|
+
},
|
|
900
|
+
// 取消聚焦画面
|
|
901
|
+
async closeLeadershipFocus(identity) {
|
|
902
|
+
await this.liveClient.closeLeadershipFocus(identity)
|
|
903
|
+
},
|
|
904
|
+
updateRingTopHeight() {
|
|
905
|
+
try {
|
|
906
|
+
const room = document.getElementById('room');
|
|
907
|
+
if (!room) return;
|
|
908
|
+
const ringTop = room.querySelector('.layout-ring-top');
|
|
909
|
+
if (!ringTop) return;
|
|
910
|
+
// 使用clientHeight以获得包含内边距的可见高度
|
|
911
|
+
const roomHeight = room.clientHeight;
|
|
912
|
+
// 设置明确像素高度,覆盖CSS百分比或flex计算值
|
|
913
|
+
ringTop.style.height = roomHeight + 'px';
|
|
914
|
+
} catch (e) {
|
|
915
|
+
console.warn('updateRingTopHeight error:', e);
|
|
916
|
+
}
|
|
917
|
+
},
|
|
918
|
+
observeRoomResizeForRing() {
|
|
919
|
+
const room = document.getElementById('room');
|
|
920
|
+
if (!room) return;
|
|
921
|
+
// 优先使用ResizeObserver,实时响应容器尺寸变化
|
|
922
|
+
if ('ResizeObserver' in window) {
|
|
923
|
+
ringResizeObserver = new ResizeObserver(() => {
|
|
924
|
+
this.updateRingTopHeight();
|
|
925
|
+
});
|
|
926
|
+
ringResizeObserver.observe(room);
|
|
927
|
+
} else {
|
|
928
|
+
// 回退到window resize
|
|
929
|
+
window.addEventListener('resize', this.updateRingTopHeight);
|
|
930
|
+
}
|
|
931
|
+
},
|
|
932
|
+
setGridSize(size) {
|
|
933
|
+
this.currentGridSize = size;
|
|
934
|
+
},
|
|
935
|
+
// 根据identity和度数应用到video元素和旋转按钮的样式/属性
|
|
936
|
+
applyRotation(identity, degree) {
|
|
937
|
+
try {
|
|
938
|
+
const norm = ((degree % 360) + 360) % 360; // 归一化到0-359
|
|
939
|
+
// 更新video元素的transform和objectFit
|
|
940
|
+
const videoElm = document.getElementById(`video-${identity}`);
|
|
941
|
+
if (videoElm) {
|
|
942
|
+
videoElm.style.transformOrigin = 'center center';
|
|
943
|
+
// 针对90/270度,居中并以高度为基准适配,避免布局“怪异”
|
|
944
|
+
if (norm === 90 || norm === 270) {
|
|
945
|
+
const parent = videoElm.parentElement;
|
|
946
|
+
if (parent) {
|
|
947
|
+
const rect = parent.getBoundingClientRect();
|
|
948
|
+
// 旋转后使可视区域与父容器宽高贴合:宽高对调为父容器的高/宽
|
|
949
|
+
videoElm.style.top = '50%';
|
|
950
|
+
videoElm.style.left = '50%';
|
|
951
|
+
videoElm.style.width = `${rect.height}px`;
|
|
952
|
+
videoElm.style.height = `${rect.width}px`;
|
|
953
|
+
videoElm.style.transform = `translate(-50%, -50%) rotate(${norm}deg)`;
|
|
954
|
+
// 使用cover确保充满父容器(必要时会稍作裁剪)
|
|
955
|
+
videoElm.style.objectFit = 'cover';
|
|
956
|
+
}
|
|
957
|
+
} else {
|
|
958
|
+
// 0/180度直接占满容器
|
|
959
|
+
videoElm.style.top = '0';
|
|
960
|
+
videoElm.style.left = '0';
|
|
961
|
+
videoElm.style.width = '100%';
|
|
962
|
+
videoElm.style.height = '100%';
|
|
963
|
+
videoElm.style.transform = `rotate(${norm}deg)`;
|
|
964
|
+
videoElm.style.objectFit = 'contain';
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// 更新旋转按钮的class以反映当前角度(1..4)
|
|
969
|
+
const rotateElm = document.getElementById(`rotate-${identity}`);
|
|
970
|
+
if (rotateElm) {
|
|
971
|
+
let clsIndex = 1;
|
|
972
|
+
if (norm === 0) clsIndex = 1;
|
|
973
|
+
else if (norm === 90) clsIndex = 2;
|
|
974
|
+
else if (norm === 180) clsIndex = 3;
|
|
975
|
+
else if (norm === 270) clsIndex = 4;
|
|
976
|
+
rotateElm.className = `rotate-icon rotate-icon${clsIndex}`;
|
|
977
|
+
}
|
|
978
|
+
} catch (err) {
|
|
979
|
+
// 忽略DOM异常
|
|
980
|
+
console.error('applyRotation error', err);
|
|
981
|
+
}
|
|
982
|
+
},
|
|
725
983
|
// DOM操作管理函数
|
|
726
984
|
async batchUpdateStates(updateFn) {
|
|
727
985
|
this.isBatchUpdating = true;
|
|
@@ -868,12 +1126,12 @@ export default {
|
|
|
868
1126
|
// 计算MoreOptionDialog选项数量的辅助函数
|
|
869
1127
|
getOptionCount(identity) {
|
|
870
1128
|
const participantItem = this.getUserItemByIdentity(identity);
|
|
871
|
-
if (!participantItem || !participantItem.metadata) return
|
|
1129
|
+
if (!participantItem || !participantItem.metadata) return 9; // 默认最大选项数
|
|
872
1130
|
|
|
873
1131
|
const metadata = participantItem.metadata;
|
|
874
1132
|
const isLocalParticipant = identity === this.localIdentity;
|
|
875
1133
|
|
|
876
|
-
let optionCount =
|
|
1134
|
+
let optionCount = 4; // 基础选项:设为主屏、麦克风、摄像头、聚焦画面
|
|
877
1135
|
|
|
878
1136
|
if (this.judgeParticipantIsHost(this.localIdentity)) {
|
|
879
1137
|
// 本地为主持人
|
|
@@ -898,7 +1156,7 @@ export default {
|
|
|
898
1156
|
}
|
|
899
1157
|
}
|
|
900
1158
|
|
|
901
|
-
return Math.min(optionCount,
|
|
1159
|
+
return Math.min(optionCount, 9); // 限制最大选项数
|
|
902
1160
|
},
|
|
903
1161
|
// 弹窗定位工具函数
|
|
904
1162
|
calculateDialogPosition(event, options = {}) {
|
|
@@ -1366,12 +1624,12 @@ export default {
|
|
|
1366
1624
|
const deviceCalls = this.deviceList
|
|
1367
1625
|
.map(item => {
|
|
1368
1626
|
const id = item?.source === '监控' ? item?.monitorID : item?.equipmentID;
|
|
1369
|
-
return id ? { dnis: id, name: item.label } : null;
|
|
1627
|
+
return id ? { dnis: id, name: item.label, type: this.handleInviteType(item?.integrationType) } : null;
|
|
1370
1628
|
})
|
|
1371
1629
|
.filter(Boolean);
|
|
1372
1630
|
|
|
1373
1631
|
if (deviceCalls.length > 0) {
|
|
1374
|
-
this.liveClient.makeBatchCall(deviceCalls
|
|
1632
|
+
this.liveClient.makeBatchCall(deviceCalls)
|
|
1375
1633
|
.then(() => {
|
|
1376
1634
|
this.showMessage.message('success', '监控设备批量外呼已发起');
|
|
1377
1635
|
})
|
|
@@ -1380,6 +1638,24 @@ export default {
|
|
|
1380
1638
|
});
|
|
1381
1639
|
}
|
|
1382
1640
|
}
|
|
1641
|
+
// 拉取会议终端
|
|
1642
|
+
if (this.terminalNum > 0 && this.meetingTerminals) {
|
|
1643
|
+
const terminalCalls = this.meetingTerminals
|
|
1644
|
+
.map(item => {
|
|
1645
|
+
return item?.id ? { dnis: item.id, name: item.label, type: 4 } : null
|
|
1646
|
+
})
|
|
1647
|
+
.filter(Boolean)
|
|
1648
|
+
if (terminalCalls.length > 0) {
|
|
1649
|
+
this.liveClient
|
|
1650
|
+
.makeBatchCall(terminalCalls)
|
|
1651
|
+
.then(() => {
|
|
1652
|
+
this.showMessage.message('success', '会议终端批量外呼已发起')
|
|
1653
|
+
})
|
|
1654
|
+
.catch(err => {
|
|
1655
|
+
this.showMessage.message('error', `批量拉取会议终端失败: ${err?.message || err}`)
|
|
1656
|
+
})
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1383
1659
|
}
|
|
1384
1660
|
// 启动获取未入会和邀请人员轮询
|
|
1385
1661
|
this.startUnjoinParticipantPolling();
|
|
@@ -1475,18 +1751,44 @@ export default {
|
|
|
1475
1751
|
}
|
|
1476
1752
|
});
|
|
1477
1753
|
if (maxAudioLevelIndex >= 0) {
|
|
1478
|
-
this.
|
|
1754
|
+
this.highlightCurrentSpeaker(e[maxAudioLevelIndex])
|
|
1755
|
+
if (this.isVoiceMotivationOpen) {
|
|
1756
|
+
this.switchActiveSpeakerToFirst(e[maxAudioLevelIndex])
|
|
1757
|
+
}
|
|
1758
|
+
} else {
|
|
1759
|
+
this.highlightCurrentSpeaker(-1)
|
|
1479
1760
|
}
|
|
1761
|
+
} else {
|
|
1762
|
+
this.highlightCurrentSpeaker(-1)
|
|
1480
1763
|
}
|
|
1481
1764
|
});
|
|
1482
1765
|
this.liveClient.on("resMeetingRefresh", this.handleResMeetingRefresh);
|
|
1483
1766
|
},
|
|
1484
|
-
|
|
1767
|
+
// 高亮当前与会者
|
|
1768
|
+
highlightCurrentSpeaker(speaker) {
|
|
1769
|
+
const participantElms = document.querySelectorAll('#room .participant')
|
|
1770
|
+
if (speaker === -1) {
|
|
1771
|
+
if (participantElms.length > 0) {
|
|
1772
|
+
participantElms.forEach(elm => {
|
|
1773
|
+
elm.style.border = 'none'
|
|
1774
|
+
})
|
|
1775
|
+
}
|
|
1776
|
+
} else if (speaker && speaker.identity) {
|
|
1777
|
+
const speakerElm = document.getElementById(`participant-${speaker.identity}`)
|
|
1778
|
+
if (participantElms.length > 0) {
|
|
1779
|
+
participantElms.forEach(elm => {
|
|
1780
|
+
elm.style.border = 'none'
|
|
1781
|
+
})
|
|
1782
|
+
}
|
|
1783
|
+
speakerElm && (speakerElm.style.border = '2px solid #95ec69')
|
|
1784
|
+
}
|
|
1785
|
+
},
|
|
1786
|
+
handleResMeetingRefresh(e) {
|
|
1485
1787
|
console.log('resMeetingRefresh事件触发', e);
|
|
1486
|
-
|
|
1487
|
-
if(e.includes("queryAllInvite") || e.includes("queryUnjoined")) {
|
|
1788
|
+
|
|
1789
|
+
if (e.includes("queryAllInvite") || e.includes("queryUnjoined")) {
|
|
1488
1790
|
console.log('this', this, this.getUnjoinParticipant);
|
|
1489
|
-
|
|
1791
|
+
|
|
1490
1792
|
this.getUnjoinParticipant();
|
|
1491
1793
|
this.queryAllInviteParticipant();
|
|
1492
1794
|
this.startUnjoinParticipantPolling();
|
|
@@ -1632,7 +1934,7 @@ export default {
|
|
|
1632
1934
|
} else {
|
|
1633
1935
|
this.meetingCoHost = [];
|
|
1634
1936
|
}
|
|
1635
|
-
|
|
1937
|
+
this.leaderShipFocus = metadata.leaderShipFocus;
|
|
1636
1938
|
this.roomMetadata = metadata;
|
|
1637
1939
|
|
|
1638
1940
|
// 批量设置关键状态
|
|
@@ -1708,6 +2010,7 @@ export default {
|
|
|
1708
2010
|
this.meetingCoHost = [];
|
|
1709
2011
|
}
|
|
1710
2012
|
|
|
2013
|
+
this.leaderShipFocus = metadata.leaderShipFocus;
|
|
1711
2014
|
this.roomMetadata = metadata;
|
|
1712
2015
|
|
|
1713
2016
|
// 批量设置关键状态
|
|
@@ -1955,7 +2258,8 @@ export default {
|
|
|
1955
2258
|
// 构建批量外呼数据
|
|
1956
2259
|
const volteCalls = volteList.map(item => ({
|
|
1957
2260
|
dnis: item.phone,
|
|
1958
|
-
name: item.label
|
|
2261
|
+
name: item.label,
|
|
2262
|
+
type: 0
|
|
1959
2263
|
}));
|
|
1960
2264
|
|
|
1961
2265
|
promises.push(
|
|
@@ -1997,9 +2301,9 @@ export default {
|
|
|
1997
2301
|
roomNum: this.meetingNum,
|
|
1998
2302
|
cameraStatus: this.isCameraEnabled,
|
|
1999
2303
|
microphoneStatus: this.isMicrophoneEnabled,
|
|
2000
|
-
audioDeviceId: this.
|
|
2001
|
-
videoDeviceId: this.
|
|
2002
|
-
outputDeviceId: this.
|
|
2304
|
+
audioDeviceId: this.tempActiveDevice.audioInputDevice,
|
|
2305
|
+
videoDeviceId: this.tempActiveDevice.videoDevice,
|
|
2306
|
+
outputDeviceId: this.tempActiveDevice.audioOutputDevice,
|
|
2003
2307
|
};
|
|
2004
2308
|
let tempActiveDevice = null;
|
|
2005
2309
|
try {
|
|
@@ -2133,36 +2437,83 @@ export default {
|
|
|
2133
2437
|
</div>
|
|
2134
2438
|
`;
|
|
2135
2439
|
} else if (videoItem.isCameraEnabled) {
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2440
|
+
if (videoItem.metadata?.platformID == 5) {
|
|
2441
|
+
videoDom.innerHTML = `
|
|
2442
|
+
<video id="video-${
|
|
2443
|
+
videoItem.identity
|
|
2444
|
+
}" class="p-video" autoplay webkit-playsinline playsinline x5-video-player-type="h5"></video>
|
|
2445
|
+
<div id="loadingIndicator-${videoItem.identity}" class="loadingIndicator">
|
|
2446
|
+
<div class="loading-icon"></div>
|
|
2447
|
+
<div class="loading-text">视频流加载中...</div>
|
|
2448
|
+
</div>
|
|
2449
|
+
<div id="signal-${videoItem.identity}" class="signal-icon signal-icon-good"></div>
|
|
2450
|
+
<div id="more-${videoItem.identity}" class="more-icon"></div>
|
|
2451
|
+
<div id="bitrate-${videoItem.identity}" class="bitrate-indicator"></div>
|
|
2452
|
+
<div class="describe">
|
|
2453
|
+
<div id="microphone-${videoItem.identity}" class="microphone"></div>
|
|
2454
|
+
<div id="${videoItem.identity}" class="identity">${
|
|
2455
|
+
videoItem.isLocal ? videoItem.name + "(我)" : videoItem.name
|
|
2456
|
+
}</div>
|
|
2457
|
+
</div>
|
|
2458
|
+
`;
|
|
2459
|
+
} else {
|
|
2460
|
+
videoDom.innerHTML = `
|
|
2461
|
+
<video id="video-${
|
|
2462
|
+
videoItem.identity
|
|
2463
|
+
}" class="p-video" autoplay webkit-playsinline playsinline x5-video-player-type="h5"></video>
|
|
2464
|
+
<div id="signal-${videoItem.identity}" class="signal-icon signal-icon-good"></div>
|
|
2465
|
+
<div id="more-${videoItem.identity}" class="more-icon"></div>
|
|
2466
|
+
<div id="bitrate-${videoItem.identity}" class="bitrate-indicator"></div>
|
|
2467
|
+
<div class="describe">
|
|
2468
|
+
<div id="microphone-${videoItem.identity}" class="microphone"></div>
|
|
2469
|
+
<div id="${videoItem.identity}" class="identity">${
|
|
2470
|
+
videoItem.isLocal ? videoItem.name + '(我)' : videoItem.name
|
|
2471
|
+
}</div>
|
|
2472
|
+
</div>
|
|
2473
|
+
`;
|
|
2474
|
+
}
|
|
2149
2475
|
} else {
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
<div class="
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2476
|
+
if (videoItem.metadata?.platformID == 5) {
|
|
2477
|
+
videoDom.innerHTML = `
|
|
2478
|
+
<video id="video-${
|
|
2479
|
+
videoItem.identity
|
|
2480
|
+
}" class="p-video" autoplay webkit-playsinline playsinline x5-video-player-type="h5"></video>
|
|
2481
|
+
<div id="loadingIndicator-${videoItem.identity}" class="loadingIndicator">
|
|
2482
|
+
<div class="loading-icon"></div>
|
|
2483
|
+
<div class="loading-text">视频流加载中...</div>
|
|
2484
|
+
</div>
|
|
2485
|
+
<div id="board-${videoItem.identity}" class="board">
|
|
2486
|
+
<div class="board-icon"></div>
|
|
2487
|
+
</div>
|
|
2488
|
+
<div id="signal-${videoItem.identity}" class="signal-icon signal-icon-good"></div>
|
|
2489
|
+
<div id="more-${videoItem.identity}" class="more-icon"></div>
|
|
2490
|
+
<div id="bitrate-${videoItem.identity}" class="bitrate-indicator"></div>
|
|
2491
|
+
<div class="describe">
|
|
2492
|
+
<div id="microphone-${videoItem.identity}" class="microphone"></div>
|
|
2493
|
+
<div id="${videoItem.identity}" class="identity">${
|
|
2494
|
+
videoItem.isLocal ? videoItem.name + '(我)' : videoItem.name
|
|
2495
|
+
}</div>
|
|
2496
|
+
</div>
|
|
2497
|
+
`
|
|
2498
|
+
} else {
|
|
2499
|
+
videoDom.innerHTML = `
|
|
2500
|
+
<video id="video-${
|
|
2501
|
+
videoItem.identity
|
|
2502
|
+
}" class="p-video" autoplay webkit-playsinline playsinline x5-video-player-type="h5"></video>
|
|
2503
|
+
<div id="board-${videoItem.identity}" class="board">
|
|
2504
|
+
<div class="board-icon"></div>
|
|
2505
|
+
</div>
|
|
2506
|
+
<div id="signal-${videoItem.identity}" class="signal-icon signal-icon-good"></div>
|
|
2507
|
+
<div id="more-${videoItem.identity}" class="more-icon"></div>
|
|
2508
|
+
<div id="bitrate-${videoItem.identity}" class="bitrate-indicator"></div>
|
|
2509
|
+
<div class="describe">
|
|
2510
|
+
<div id="microphone-${videoItem.identity}" class="microphone"></div>
|
|
2511
|
+
<div id="${videoItem.identity}" class="identity">${
|
|
2512
|
+
videoItem.isLocal ? videoItem.name + '(我)' : videoItem.name
|
|
2513
|
+
}</div>
|
|
2514
|
+
</div>
|
|
2515
|
+
`
|
|
2516
|
+
}
|
|
2166
2517
|
}
|
|
2167
2518
|
return videoDom;
|
|
2168
2519
|
},
|
|
@@ -2311,6 +2662,57 @@ export default {
|
|
|
2311
2662
|
layoutRightSideEle.appendChild(videoDiv);
|
|
2312
2663
|
}
|
|
2313
2664
|
}
|
|
2665
|
+
} else if (this.currentLayout === 'ring') {
|
|
2666
|
+
// 环形布局:确保容器存在并将与会者放入合适位置
|
|
2667
|
+
let layoutRing = document.querySelector('#room .layout-ring');
|
|
2668
|
+
let layoutRingTop = layoutRing?.querySelector('.layout-ring-top');
|
|
2669
|
+
let layoutRingBottom = layoutRing?.querySelector('.layout-ring-bottom');
|
|
2670
|
+
let ringCenter = layoutRing?.querySelector('.ring-center');
|
|
2671
|
+
|
|
2672
|
+
// 如果容器不存在(可能由于时序问题),创建它
|
|
2673
|
+
if (!layoutRing || !layoutRingTop || !layoutRingBottom || !ringCenter) {
|
|
2674
|
+
layoutRing = document.createElement('div');
|
|
2675
|
+
layoutRing.className = 'layout-ring';
|
|
2676
|
+
layoutRingTop = document.createElement('div');
|
|
2677
|
+
layoutRingTop.className = 'layout-ring-top';
|
|
2678
|
+
layoutRingBottom = document.createElement('div');
|
|
2679
|
+
layoutRingBottom.className = 'layout-ring-bottom';
|
|
2680
|
+
// 创建12个环槽位
|
|
2681
|
+
for (let i = 0; i < 12; i++) {
|
|
2682
|
+
const slot = document.createElement('div');
|
|
2683
|
+
slot.className = 'ring-slot';
|
|
2684
|
+
layoutRingTop.appendChild(slot);
|
|
2685
|
+
}
|
|
2686
|
+
ringCenter = document.createElement('div');
|
|
2687
|
+
ringCenter.className = 'ring-center';
|
|
2688
|
+
layoutRingTop.appendChild(ringCenter);
|
|
2689
|
+
layoutRing.appendChild(layoutRingTop);
|
|
2690
|
+
layoutRing.appendChild(layoutRingBottom);
|
|
2691
|
+
container.insertBefore(layoutRing, container.firstChild ?? null);
|
|
2692
|
+
// 确保环形顶部高度与#room一致
|
|
2693
|
+
requestAnimationFrame(() => this.updateRingTopHeight());
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
const appendToFirstEmptySlot = (el) => {
|
|
2697
|
+
const slots = layoutRingTop.querySelectorAll('.ring-slot');
|
|
2698
|
+
for (let i = 0; i < slots.length; i++) {
|
|
2699
|
+
if (!slots[i].firstChild) {
|
|
2700
|
+
slots[i].appendChild(el);
|
|
2701
|
+
return true;
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
return false;
|
|
2705
|
+
};
|
|
2706
|
+
|
|
2707
|
+
// 主持人优先放置在中心;否则进入第一个空槽;都满了则放到底部
|
|
2708
|
+
if (this.curHostIdentity && videoItem.identity === this.curHostIdentity) {
|
|
2709
|
+
ringCenter.appendChild(videoDiv);
|
|
2710
|
+
} else {
|
|
2711
|
+
const placed = appendToFirstEmptySlot(videoDiv);
|
|
2712
|
+
if (!placed) {
|
|
2713
|
+
layoutRingBottom.appendChild(videoDiv);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2314
2716
|
}
|
|
2315
2717
|
}
|
|
2316
2718
|
return container;
|
|
@@ -2338,6 +2740,25 @@ export default {
|
|
|
2338
2740
|
videoDiv = this.constructParticipantDom(videoItem);
|
|
2339
2741
|
// 构建会议室布局
|
|
2340
2742
|
container = this.constructRoomLayout(container, videoDiv, videoItem);
|
|
2743
|
+
|
|
2744
|
+
// 显示与会者画面加载中状态
|
|
2745
|
+
let videoElm = document.getElementById(`video-${videoItem.identity}`)
|
|
2746
|
+
let loadingIndicatorElm = document.getElementById(`loadingIndicator-${videoItem.identity}`)
|
|
2747
|
+
if (videoItem.metadata?.platformID == 5 && videoElm && loadingIndicatorElm) {
|
|
2748
|
+
setTimeout(() => {
|
|
2749
|
+
loadingIndicatorElm.style.display = 'none'
|
|
2750
|
+
}, 2000)
|
|
2751
|
+
}
|
|
2752
|
+
if (videoItem.metadata?.platformID == 4 && videoElm && loadingIndicatorElm) {
|
|
2753
|
+
setTimeout(() => {
|
|
2754
|
+
loadingIndicatorElm.style.display = 'none'
|
|
2755
|
+
}, 4000)
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
// 本地与会者镜像处理
|
|
2759
|
+
if (videoItem.isLocal) {
|
|
2760
|
+
videoElm && this.isMirror && (videoElm.style.transform = 'rotateY(180deg)')
|
|
2761
|
+
}
|
|
2341
2762
|
// 添加到participant数组
|
|
2342
2763
|
this.addToParticipantList(videoItem);
|
|
2343
2764
|
}
|
|
@@ -2352,6 +2773,8 @@ export default {
|
|
|
2352
2773
|
let signalElm = document.getElementById(`signal-${videoItem.identity}`);
|
|
2353
2774
|
// 与会者更多操作按钮元素
|
|
2354
2775
|
let moreElm = document.getElementById(`more-${videoItem.identity}`);
|
|
2776
|
+
// 与会者画面旋转按钮元素
|
|
2777
|
+
let rotateElm = document.getElementById(`rotate-${videoItem.identity}`)
|
|
2355
2778
|
// 声明麦克风按钮点击事件回调
|
|
2356
2779
|
const unableMicrophone = () => {
|
|
2357
2780
|
this.liveClient.changeParticipantMicrophoneStatus(videoItem.identity, true);
|
|
@@ -2366,6 +2789,8 @@ export default {
|
|
|
2366
2789
|
const currentMoreElm = document.getElementById(`more-${videoItem.identity}`);
|
|
2367
2790
|
currentSignalElm && (currentSignalElm.style.visibility = "visible");
|
|
2368
2791
|
currentMoreElm && (currentMoreElm.style.visibility = "visible");
|
|
2792
|
+
const currentRotateElm = document.getElementById(`rotate-${videoItem.identity}`);
|
|
2793
|
+
currentRotateElm && (currentRotateElm.style.visibility = "visible");
|
|
2369
2794
|
};
|
|
2370
2795
|
const signalAndMoreHide = () => {
|
|
2371
2796
|
// 每次执行时重新获取最新的元素
|
|
@@ -2373,6 +2798,8 @@ export default {
|
|
|
2373
2798
|
const currentMoreElm = document.getElementById(`more-${videoItem.identity}`);
|
|
2374
2799
|
currentSignalElm && (currentSignalElm.style.visibility = "hidden");
|
|
2375
2800
|
currentMoreElm && (currentMoreElm.style.visibility = "hidden");
|
|
2801
|
+
const currentRotateElm = document.getElementById(`rotate-${videoItem.identity}`);
|
|
2802
|
+
currentRotateElm && (currentRotateElm.style.visibility = "hidden");
|
|
2376
2803
|
};
|
|
2377
2804
|
// 声明操作按钮元素点击事件回调
|
|
2378
2805
|
const moreElmClick = (event) => {
|
|
@@ -2395,6 +2822,13 @@ export default {
|
|
|
2395
2822
|
this.moreDialogShow = true;
|
|
2396
2823
|
}
|
|
2397
2824
|
};
|
|
2825
|
+
// 旋转按钮点击事件:顺时针旋转90度(并居中适配)
|
|
2826
|
+
const rotateElmClick = () => {
|
|
2827
|
+
const current = this.rotateDegreeMap.get(videoItem.identity) || 0;
|
|
2828
|
+
const next = (current + 90) % 360;
|
|
2829
|
+
this.rotateDegreeMap.set(videoItem.identity, next);
|
|
2830
|
+
this.applyRotation(videoItem.identity, next);
|
|
2831
|
+
};
|
|
2398
2832
|
// 为麦克风元素绑定事件
|
|
2399
2833
|
if (microElm && videoItem.isMicrophoneEnabled) {
|
|
2400
2834
|
microElm.className = "microphone microphone-active";
|
|
@@ -2422,12 +2856,47 @@ export default {
|
|
|
2422
2856
|
if (moreElm) {
|
|
2423
2857
|
moreElm.onclick = moreElmClick;
|
|
2424
2858
|
}
|
|
2859
|
+
if (rotateElm) {
|
|
2860
|
+
rotateElm.onclick = rotateElmClick;
|
|
2861
|
+
}
|
|
2425
2862
|
if (signalElm) {
|
|
2426
2863
|
if (videoItem.connectionQuality === "excellent" || videoItem.connectionQuality === "good") {
|
|
2427
2864
|
signalElm.className = "signal-icon signal-icon-good";
|
|
2428
2865
|
} else {
|
|
2429
2866
|
signalElm.className = "signal-icon signal-icon-poor";
|
|
2430
2867
|
}
|
|
2868
|
+
// 为信号图标绑定鼠标悬停事件,显示码率信息
|
|
2869
|
+
signalElm.onmouseenter = () => {
|
|
2870
|
+
const bitrateElm = document.getElementById(`bitrate-${videoItem.identity}`)
|
|
2871
|
+
if (bitrateElm) {
|
|
2872
|
+
const bitrate = this.liveClient.getParticipantBitrate(videoItem.identity)
|
|
2873
|
+
console.log('bitrate', bitrate)
|
|
2874
|
+
if (bitrate !== null) {
|
|
2875
|
+
bitrateElm.innerText = `${bitrate} kbps`
|
|
2876
|
+
bitrateElm.style.display = 'block'
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
signalElm.onmouseleave = () => {
|
|
2881
|
+
const bitrateElm = document.getElementById(`bitrate-${videoItem.identity}`)
|
|
2882
|
+
if (bitrateElm) {
|
|
2883
|
+
bitrateElm.style.display = 'none'
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
// 根据platformID设定元素样式
|
|
2888
|
+
if (
|
|
2889
|
+
videoItem.metadata?.platformID == 1 ||
|
|
2890
|
+
videoItem.metadata?.platformID == 4 ||
|
|
2891
|
+
videoItem.metadata?.platformID == 7
|
|
2892
|
+
) {
|
|
2893
|
+
videoElm && (videoElm.style.objectFit = 'contain')
|
|
2894
|
+
} else {
|
|
2895
|
+
if (videoItem?.source == 'camera') {
|
|
2896
|
+
videoElm && (videoElm.style.objectFit = 'cover')
|
|
2897
|
+
} else {
|
|
2898
|
+
videoElm && (videoElm.style.objectFit = 'contain')
|
|
2899
|
+
}
|
|
2431
2900
|
}
|
|
2432
2901
|
let screenShareElm = document.getElementById(`screenshare-${videoItem.identity}`);
|
|
2433
2902
|
// 与会者dom元素内部样式重新渲染(摄像头或屏幕共享切换后)
|
|
@@ -2477,6 +2946,15 @@ export default {
|
|
|
2477
2946
|
signalElm.className = "signal-icon signal-icon-poor";
|
|
2478
2947
|
}
|
|
2479
2948
|
videoDiv.appendChild(signalElm);
|
|
2949
|
+
|
|
2950
|
+
// 构建码率显示元素
|
|
2951
|
+
let bitrateElm = document.getElementById(`bitrate-${videoItem.identity}`)
|
|
2952
|
+
if (!bitrateElm) {
|
|
2953
|
+
bitrateElm = document.createElement('div')
|
|
2954
|
+
bitrateElm.id = `bitrate-${videoItem.identity}`
|
|
2955
|
+
bitrateElm.className = 'bitrate-indicator'
|
|
2956
|
+
videoDiv.appendChild(bitrateElm)
|
|
2957
|
+
}
|
|
2480
2958
|
}
|
|
2481
2959
|
// if (!moreElm) {
|
|
2482
2960
|
// // 构建更多操作按钮元素
|
|
@@ -2499,6 +2977,28 @@ export default {
|
|
|
2499
2977
|
moreElm = null;
|
|
2500
2978
|
}
|
|
2501
2979
|
}
|
|
2980
|
+
// 仅在platformID为4展示旋转按钮(对所有与会者可见)
|
|
2981
|
+
if (videoItem.metadata?.platformID === 4) {
|
|
2982
|
+
if (!rotateElm) {
|
|
2983
|
+
rotateElm = document.createElement('div');
|
|
2984
|
+
rotateElm.id = `rotate-${videoItem.identity}`;
|
|
2985
|
+
rotateElm.className = 'rotate-icon rotate-icon1';
|
|
2986
|
+
videoDiv && videoDiv.appendChild(rotateElm);
|
|
2987
|
+
rotateElm.onclick = rotateElmClick;
|
|
2988
|
+
}
|
|
2989
|
+
// 初始化map中度数(如果不存在)并应用到元素
|
|
2990
|
+
if (!this.rotateDegreeMap.has(videoItem.identity)) {
|
|
2991
|
+
this.rotateDegreeMap.set(videoItem.identity, 0);
|
|
2992
|
+
}
|
|
2993
|
+
this.applyRotation(videoItem.identity, this.rotateDegreeMap.get(videoItem.identity));
|
|
2994
|
+
} else {
|
|
2995
|
+
// 如果不是platformID 4,移除旋转按钮及map记录
|
|
2996
|
+
if (rotateElm) {
|
|
2997
|
+
try { videoDiv.removeChild(rotateElm); } catch(e) {}
|
|
2998
|
+
rotateElm = null;
|
|
2999
|
+
}
|
|
3000
|
+
this.rotateDegreeMap.delete(videoItem.identity);
|
|
3001
|
+
}
|
|
2502
3002
|
} else {
|
|
2503
3003
|
if (!boardElm) {
|
|
2504
3004
|
// 之前为屏幕共享状态或摄像头开启状态
|
|
@@ -2532,6 +3032,11 @@ export default {
|
|
|
2532
3032
|
this.removeFromParticipantList(videoItem.identity);
|
|
2533
3033
|
this.filterParticipantList();
|
|
2534
3034
|
|
|
3035
|
+
// 清理可能存在的旋转状态
|
|
3036
|
+
if (this.rotateDegreeMap.has(videoItem.identity)) {
|
|
3037
|
+
this.rotateDegreeMap.delete(videoItem.identity);
|
|
3038
|
+
}
|
|
3039
|
+
|
|
2535
3040
|
// 处理焦点用户离开的情况
|
|
2536
3041
|
if (this.curBlurIdentity === videoItem.identity) {
|
|
2537
3042
|
console.log("焦点用户离开会议:", videoItem.identity);
|
|
@@ -2559,18 +3064,6 @@ export default {
|
|
|
2559
3064
|
this.curHostIdentity = null;
|
|
2560
3065
|
// 主持人离开后具体的逻辑处理放到watch中处理
|
|
2561
3066
|
}
|
|
2562
|
-
// pointTurn模式下,主持人离开处理
|
|
2563
|
-
// if (currentRoomMode.value === "pointTurn" && videoItem.identity === curHostIdentity.value) {
|
|
2564
|
-
// 当前离开与会者为会议主持人
|
|
2565
|
-
// const pointTurnCenter = document.querySelector("#room .point-turn-center");
|
|
2566
|
-
// if (pointTurnCenter) {
|
|
2567
|
-
// // 会议中没有其他主持人,显示默认占位元素
|
|
2568
|
-
// const placeHolderElm = document.createElement("div");
|
|
2569
|
-
// placeHolderElm.className = "participant";
|
|
2570
|
-
// placeHolderElm.innerHTML = placeholderTemplate;
|
|
2571
|
-
// pointTurnCenter.insertBefore(placeHolderElm, pointTurnCenter.firstChild ?? null);
|
|
2572
|
-
// }
|
|
2573
|
-
// }
|
|
2574
3067
|
// pointTurn模式下的其他用户离开处理
|
|
2575
3068
|
if (
|
|
2576
3069
|
this.currentRoomMode === "pointTurn" &&
|
|
@@ -2608,6 +3101,11 @@ export default {
|
|
|
2608
3101
|
if (videoItem?.audioTrack) {
|
|
2609
3102
|
videoItem.audioTrack.detach();
|
|
2610
3103
|
}
|
|
3104
|
+
if (videoItem?.screenShareAudioTrack) {
|
|
3105
|
+
try {
|
|
3106
|
+
videoItem.screenShareAudioTrack.detach()
|
|
3107
|
+
} catch (e) {}
|
|
3108
|
+
}
|
|
2611
3109
|
return;
|
|
2612
3110
|
}
|
|
2613
3111
|
// 与会者屏幕共享布局切换
|
|
@@ -2670,6 +3168,32 @@ export default {
|
|
|
2670
3168
|
if (videoItem?.audioTrack) {
|
|
2671
3169
|
videoItem.audioTrack.attach(videoElm);
|
|
2672
3170
|
}
|
|
3171
|
+
// 屏幕共享系统音频:使用独立的隐藏 audio 元素,避免某些浏览器在多音轨时替换 video 的音频源
|
|
3172
|
+
if (videoItem?.isScreenShareEnabled && videoItem?.screenShareAudioTrack) {
|
|
3173
|
+
const audioHolderId = `screenshare-audio-${videoItem.identity}`
|
|
3174
|
+
let audioHolder = document.getElementById(audioHolderId)
|
|
3175
|
+
if (!audioHolder) {
|
|
3176
|
+
audioHolder = document.createElement('audio')
|
|
3177
|
+
audioHolder.id = audioHolderId
|
|
3178
|
+
audioHolder.style.display = 'none'
|
|
3179
|
+
audioHolder.setAttribute('playsinline', '')
|
|
3180
|
+
audioHolder.autoplay = true
|
|
3181
|
+
// 将隐藏audio元素插入到与会者容器,便于随与会者一起清理
|
|
3182
|
+
videoDiv && videoDiv.appendChild(audioHolder)
|
|
3183
|
+
}
|
|
3184
|
+
try {
|
|
3185
|
+
videoItem.screenShareAudioTrack.attach(audioHolder)
|
|
3186
|
+
} catch (e) {
|
|
3187
|
+
console.warn('屏幕共享系统音频附加失败', videoItem.identity, e)
|
|
3188
|
+
}
|
|
3189
|
+
} else {
|
|
3190
|
+
// 如果当前不再共享屏幕,清理残留的隐藏 audio 元素
|
|
3191
|
+
const orphanAudio = document.getElementById(`screenshare-audio-${videoItem.identity}`)
|
|
3192
|
+
if (orphanAudio) {
|
|
3193
|
+
try { videoItem.screenShareAudioTrack?.detach(orphanAudio) } catch (_) {}
|
|
3194
|
+
orphanAudio.remove()
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
2673
3197
|
}
|
|
2674
3198
|
// 更新与会者名称
|
|
2675
3199
|
let nameDom = document.getElementById(videoItem.identity);
|
|
@@ -2679,6 +3203,10 @@ export default {
|
|
|
2679
3203
|
// 更新与会者数组
|
|
2680
3204
|
this.addToParticipantList(videoItem);
|
|
2681
3205
|
this.filterParticipantList();
|
|
3206
|
+
// 每次render时,如果该用户在rotateDegreeMap中,则应用其旋转到video元素(确保DOM更新后生效)
|
|
3207
|
+
if (this.rotateDegreeMap.has(videoItem.identity)) {
|
|
3208
|
+
this.applyRotation(videoItem.identity, this.rotateDegreeMap.get(videoItem.identity));
|
|
3209
|
+
}
|
|
2682
3210
|
// 与会者结束共享,切换回到之前布局(仅主持人执行)
|
|
2683
3211
|
if (this.currentRoomMode === "normal" && this.judgeParticipantIsHost(this.localIdentity)) {
|
|
2684
3212
|
console.log(
|
|
@@ -2891,7 +3419,73 @@ export default {
|
|
|
2891
3419
|
|
|
2892
3420
|
if (oldLayout === "grid" && newLayout === "ring") {
|
|
2893
3421
|
// 从宫格布局切换到环状布局
|
|
2894
|
-
//
|
|
3422
|
+
// 实现环形布局:上半部分为4x4网格(中心2x2为主持人独占),其余12个位置按从上到下、从左到右顺序填充;
|
|
3423
|
+
// 超过13人的用户放到下半部分按4x4顺序排列
|
|
3424
|
+
|
|
3425
|
+
// 清理可能残留的环形容器
|
|
3426
|
+
const existingRing = document.querySelector('#room .layout-ring');
|
|
3427
|
+
if (existingRing) {
|
|
3428
|
+
console.warn('发现残留的环形布局容器,正在清理');
|
|
3429
|
+
existingRing.remove();
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
// 创建环形布局容器
|
|
3433
|
+
const layoutRing = document.createElement('div');
|
|
3434
|
+
layoutRing.className = 'layout-ring';
|
|
3435
|
+
|
|
3436
|
+
const layoutRingTop = document.createElement('div');
|
|
3437
|
+
layoutRingTop.className = 'layout-ring-top';
|
|
3438
|
+
|
|
3439
|
+
const layoutRingBottom = document.createElement('div');
|
|
3440
|
+
layoutRingBottom.className = 'layout-ring-bottom';
|
|
3441
|
+
|
|
3442
|
+
// 按照4x4网格的顺序,但排除中心2x2的位置(索引 5,6,9,10),插入12个slot
|
|
3443
|
+
const slotOrder = [0,1,2,3,4,7,8,11,12,13,14,15];
|
|
3444
|
+
slotOrder.forEach((pos, idx) => {
|
|
3445
|
+
const slot = document.createElement('div');
|
|
3446
|
+
slot.className = 'ring-slot';
|
|
3447
|
+
slot.dataset.pos = String(pos);
|
|
3448
|
+
layoutRingTop.appendChild(slot);
|
|
3449
|
+
});
|
|
3450
|
+
|
|
3451
|
+
// 中央占位,用于主持人占据2x2
|
|
3452
|
+
const ringCenter = document.createElement('div');
|
|
3453
|
+
ringCenter.className = 'ring-center';
|
|
3454
|
+
layoutRingTop.appendChild(ringCenter);
|
|
3455
|
+
|
|
3456
|
+
// 将元素从主容器中收集并按规则分配到环形布局
|
|
3457
|
+
const participantElements = Array.from(container.children).filter(child => child.classList && child.classList.contains('participant') && child.id && child.id.startsWith('participant-'));
|
|
3458
|
+
|
|
3459
|
+
// 将主持人放到center,其余依序填充top slots,溢出进入bottom
|
|
3460
|
+
let filledCount = 0;
|
|
3461
|
+
participantElements.forEach((child) => {
|
|
3462
|
+
try {
|
|
3463
|
+
const id = child.id.replace('participant-', '');
|
|
3464
|
+
if (this.curHostIdentity && id === this.curHostIdentity) {
|
|
3465
|
+
ringCenter.appendChild(child);
|
|
3466
|
+
} else {
|
|
3467
|
+
if (filledCount < 12) {
|
|
3468
|
+
const slot = layoutRingTop.querySelectorAll('.ring-slot')[filledCount];
|
|
3469
|
+
slot && slot.appendChild(child);
|
|
3470
|
+
filledCount++;
|
|
3471
|
+
} else {
|
|
3472
|
+
layoutRingBottom.appendChild(child);
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
} catch (err) {
|
|
3476
|
+
console.error('分配参与者到环形布局时出错', err);
|
|
3477
|
+
}
|
|
3478
|
+
});
|
|
3479
|
+
|
|
3480
|
+
layoutRing.appendChild(layoutRingTop);
|
|
3481
|
+
layoutRing.appendChild(layoutRingBottom);
|
|
3482
|
+
|
|
3483
|
+
// 清理主容器中可能存在的非participant占位元素(避免重复)
|
|
3484
|
+
// 移除原有非布局容器元素
|
|
3485
|
+
// 插入新的环形布局
|
|
3486
|
+
container.insertBefore(layoutRing, container.firstChild ?? null);
|
|
3487
|
+
// 切换到环形后,同步一次顶部高度
|
|
3488
|
+
requestAnimationFrame(() => this.updateRingTopHeight());
|
|
2895
3489
|
}
|
|
2896
3490
|
|
|
2897
3491
|
if (oldLayout === "grid" && newLayout === "downLSide") {
|
|
@@ -2939,6 +3533,178 @@ export default {
|
|
|
2939
3533
|
// 添加其他元素
|
|
2940
3534
|
container.appendChild(fragment);
|
|
2941
3535
|
}
|
|
3536
|
+
// 从焦点布局切换到环形布局
|
|
3537
|
+
if (oldLayout === 'rightSide' && newLayout === 'ring') {
|
|
3538
|
+
// 将右侧/左侧容器拆解回主容器,然后重用 grid->ring 的逻辑
|
|
3539
|
+
let layoutRightSideEle = document.querySelector('#room .layout-rightside');
|
|
3540
|
+
let layoutLeftSideEle = document.querySelector('#room .layout-leftside');
|
|
3541
|
+
|
|
3542
|
+
// 把左右容器内的元素移动回主容器顺序(左侧优先)
|
|
3543
|
+
const moveChildrenToContainer = (ele) => {
|
|
3544
|
+
if (!ele) return;
|
|
3545
|
+
let child = null;
|
|
3546
|
+
while ((child = ele.firstChild)) {
|
|
3547
|
+
container.appendChild(child);
|
|
3548
|
+
}
|
|
3549
|
+
if (container.contains(ele)) container.removeChild(ele);
|
|
3550
|
+
};
|
|
3551
|
+
moveChildrenToContainer(layoutLeftSideEle);
|
|
3552
|
+
moveChildrenToContainer(layoutRightSideEle);
|
|
3553
|
+
|
|
3554
|
+
// 触发自身来走 grid->ring 分支,通过重新调用 executeLayoutChange 从 container 状态转换
|
|
3555
|
+
// 为避免递归/竞态,我们直接调用 the grid->ring block by setting variables here.
|
|
3556
|
+
// 简化做法:复制 grid->ring behavior here
|
|
3557
|
+
const existingRing = document.querySelector('#room .layout-ring');
|
|
3558
|
+
if (existingRing) existingRing.remove();
|
|
3559
|
+
const layoutRing = document.createElement('div');
|
|
3560
|
+
layoutRing.className = 'layout-ring';
|
|
3561
|
+
const layoutRingTop = document.createElement('div');
|
|
3562
|
+
layoutRingTop.className = 'layout-ring-top';
|
|
3563
|
+
const layoutRingBottom = document.createElement('div');
|
|
3564
|
+
layoutRingBottom.className = 'layout-ring-bottom';
|
|
3565
|
+
const slotOrder = [0,1,2,3,4,7,8,11,12,13,14,15];
|
|
3566
|
+
slotOrder.forEach((pos) => {
|
|
3567
|
+
const slot = document.createElement('div');
|
|
3568
|
+
slot.className = 'ring-slot';
|
|
3569
|
+
slot.dataset.pos = String(pos);
|
|
3570
|
+
layoutRingTop.appendChild(slot);
|
|
3571
|
+
});
|
|
3572
|
+
const ringCenter = document.createElement('div');
|
|
3573
|
+
ringCenter.className = 'ring-center';
|
|
3574
|
+
layoutRingTop.appendChild(ringCenter);
|
|
3575
|
+
const participantElements = Array.from(container.children).filter(child => child.classList && child.classList.contains('participant') && child.id && child.id.startsWith('participant-'));
|
|
3576
|
+
let filledCount = 0;
|
|
3577
|
+
participantElements.forEach((child) => {
|
|
3578
|
+
const id = child.id.replace('participant-', '');
|
|
3579
|
+
if (this.curHostIdentity && id === this.curHostIdentity) {
|
|
3580
|
+
ringCenter.appendChild(child);
|
|
3581
|
+
} else {
|
|
3582
|
+
if (filledCount < 12) {
|
|
3583
|
+
const slot = layoutRingTop.querySelectorAll('.ring-slot')[filledCount];
|
|
3584
|
+
slot && slot.appendChild(child);
|
|
3585
|
+
filledCount++;
|
|
3586
|
+
} else {
|
|
3587
|
+
layoutRingBottom.appendChild(child);
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
});
|
|
3591
|
+
layoutRing.appendChild(layoutRingTop);
|
|
3592
|
+
layoutRing.appendChild(layoutRingBottom);
|
|
3593
|
+
container.insertBefore(layoutRing, container.firstChild ?? null);
|
|
3594
|
+
// 右侧边栏 -> 环形:创建后同步高度
|
|
3595
|
+
requestAnimationFrame(() => this.updateRingTopHeight());
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
// 从环形布局切换到宫格布局
|
|
3599
|
+
if (oldLayout === 'ring' && newLayout === 'grid') {
|
|
3600
|
+
const layoutRing = document.querySelector('#room .layout-ring');
|
|
3601
|
+
if (layoutRing) {
|
|
3602
|
+
const top = layoutRing.querySelector('.layout-ring-top');
|
|
3603
|
+
const bottom = layoutRing.querySelector('.layout-ring-bottom');
|
|
3604
|
+
// 将top中的slot里的参与者按slot顺序移动回主容器
|
|
3605
|
+
if (top) {
|
|
3606
|
+
const slots = top.querySelectorAll('.ring-slot');
|
|
3607
|
+
slots.forEach(slot => {
|
|
3608
|
+
while (slot.firstChild) {
|
|
3609
|
+
container.appendChild(slot.firstChild);
|
|
3610
|
+
}
|
|
3611
|
+
slot.remove();
|
|
3612
|
+
});
|
|
3613
|
+
const center = top.querySelector('.ring-center');
|
|
3614
|
+
if (center) {
|
|
3615
|
+
while (center.firstChild) {
|
|
3616
|
+
container.appendChild(center.firstChild);
|
|
3617
|
+
}
|
|
3618
|
+
center.remove();
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
if (bottom) {
|
|
3622
|
+
while (bottom.firstChild) {
|
|
3623
|
+
container.appendChild(bottom.firstChild);
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
layoutRing.remove();
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
// 从环形布局切换到焦点布局
|
|
3631
|
+
if (oldLayout === 'ring' && newLayout === 'rightSide') {
|
|
3632
|
+
// 先把ring拆平回主容器,再复用 rightSide creation logic
|
|
3633
|
+
const layoutRing = document.querySelector('#room .layout-ring');
|
|
3634
|
+
if (layoutRing) {
|
|
3635
|
+
const top = layoutRing.querySelector('.layout-ring-top');
|
|
3636
|
+
const bottom = layoutRing.querySelector('.layout-ring-bottom');
|
|
3637
|
+
if (top) {
|
|
3638
|
+
const slots = top.querySelectorAll('.ring-slot');
|
|
3639
|
+
slots.forEach(slot => {
|
|
3640
|
+
while (slot.firstChild) {
|
|
3641
|
+
container.appendChild(slot.firstChild);
|
|
3642
|
+
}
|
|
3643
|
+
});
|
|
3644
|
+
const center = top.querySelector('.ring-center');
|
|
3645
|
+
if (center) {
|
|
3646
|
+
while (center.firstChild) {
|
|
3647
|
+
container.appendChild(center.firstChild);
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3651
|
+
if (bottom) {
|
|
3652
|
+
while (bottom.firstChild) {
|
|
3653
|
+
container.appendChild(bottom.firstChild);
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
layoutRing.remove();
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
// 现在容器已被平铺,复用已有 grid->rightSide 代码 path by creating left/right containers
|
|
3660
|
+
let layoutRightSideEle = document.createElement('div');
|
|
3661
|
+
layoutRightSideEle.className = 'layout-rightside';
|
|
3662
|
+
let layoutLeftSideEle = document.createElement('div');
|
|
3663
|
+
layoutLeftSideEle.className = 'layout-leftside';
|
|
3664
|
+
|
|
3665
|
+
// 确定焦点用户 - 优先级:当前焦点用户 > 主持人 > 本地用户
|
|
3666
|
+
let focusVideoItem = null;
|
|
3667
|
+
let blurDom = null;
|
|
3668
|
+
if (this.curBlurIdentity) {
|
|
3669
|
+
focusVideoItem = getUserItemByIdentity(this.curBlurIdentity);
|
|
3670
|
+
if (focusVideoItem) blurDom = document.getElementById(`participant-${focusVideoItem.identity}`);
|
|
3671
|
+
}
|
|
3672
|
+
if (!blurDom && this.curHostIdentity) {
|
|
3673
|
+
focusVideoItem = getUserItemByIdentity(this.curHostIdentity);
|
|
3674
|
+
if (focusVideoItem) blurDom = document.getElementById(`participant-${focusVideoItem.identity}`);
|
|
3675
|
+
}
|
|
3676
|
+
if (!blurDom) {
|
|
3677
|
+
focusVideoItem = getLocalParticipant();
|
|
3678
|
+
if (focusVideoItem) blurDom = document.getElementById(`participant-${focusVideoItem.identity}`);
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3681
|
+
if (blurDom) {
|
|
3682
|
+
if (layoutLeftSideEle.hasChildNodes()) {
|
|
3683
|
+
let child;
|
|
3684
|
+
while ((child = layoutLeftSideEle.firstChild)) {
|
|
3685
|
+
layoutRightSideEle.appendChild(child);
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
layoutLeftSideEle.appendChild(blurDom);
|
|
3689
|
+
|
|
3690
|
+
// 将剩余元素移动到右侧
|
|
3691
|
+
if (container.hasChildNodes()) {
|
|
3692
|
+
let child;
|
|
3693
|
+
while ((child = container.firstElementChild)) {
|
|
3694
|
+
if (child && child !== layoutLeftSideEle && child !== layoutRightSideEle) {
|
|
3695
|
+
layoutRightSideEle.appendChild(child);
|
|
3696
|
+
} else {
|
|
3697
|
+
if (child) container.removeChild(child);
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
container.insertBefore(layoutLeftSideEle, container.firstChild ?? null);
|
|
3703
|
+
container.appendChild(layoutRightSideEle);
|
|
3704
|
+
} else {
|
|
3705
|
+
this.showMessage.warning('无法找到有效的焦点用户');
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
2942
3708
|
}
|
|
2943
3709
|
});
|
|
2944
3710
|
},
|
|
@@ -3038,7 +3804,6 @@ export default {
|
|
|
3038
3804
|
this.liveClient.deleteUnjoinParticipant(this.meetingNum, item.identity).then((res) => {
|
|
3039
3805
|
if (res.code == 200) {
|
|
3040
3806
|
this.showMessage.message("success", "成功删除未入会人员");
|
|
3041
|
-
// this.getUnjoinParticipant();
|
|
3042
3807
|
this.removeFromInviteList(item.identity);
|
|
3043
3808
|
} else {
|
|
3044
3809
|
this.showMessage.message("error", res?.msg);
|
|
@@ -3278,12 +4043,12 @@ export default {
|
|
|
3278
4043
|
},
|
|
3279
4044
|
async openCamera(e) {
|
|
3280
4045
|
if (this.liveClient) {
|
|
3281
|
-
await this.liveClient.changeParticipantCameraStatus(e,
|
|
4046
|
+
await this.liveClient.changeParticipantCameraStatus(e, false);
|
|
3282
4047
|
}
|
|
3283
4048
|
},
|
|
3284
4049
|
async closeCamera(e) {
|
|
3285
4050
|
if (this.liveClient) {
|
|
3286
|
-
await this.liveClient.changeParticipantCameraStatus(e,
|
|
4051
|
+
await this.liveClient.changeParticipantCameraStatus(e, true);
|
|
3287
4052
|
}
|
|
3288
4053
|
},
|
|
3289
4054
|
async setToHost(e) {
|
|
@@ -3335,9 +4100,43 @@ export default {
|
|
|
3335
4100
|
async appendInviteDevice(e) {
|
|
3336
4101
|
console.log("通讯录邀请设备", e);
|
|
3337
4102
|
if (e && e.length > 0) {
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
4103
|
+
// 构建批量外呼参数并一次性发起
|
|
4104
|
+
const deviceCalls = e
|
|
4105
|
+
.map(item => {
|
|
4106
|
+
const id = item?.equipmentID || item?.monitorID
|
|
4107
|
+
return id ? { dnis: id, name: item.label, type: this.handleInviteType(item?.integrationType) } : null
|
|
4108
|
+
})
|
|
4109
|
+
.filter(Boolean)
|
|
4110
|
+
|
|
4111
|
+
if (deviceCalls.length > 0) {
|
|
4112
|
+
try {
|
|
4113
|
+
await this.liveClient.makeBatchCall(deviceCalls)
|
|
4114
|
+
this.showMessage.message('success', '已发起通讯录设备批量外呼')
|
|
4115
|
+
} catch (err) {
|
|
4116
|
+
this.showMessage.message('error', `通讯录设备批量外呼失败: ${err?.message || err}`)
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
},
|
|
4121
|
+
async appendInviteTerminal(e) {
|
|
4122
|
+
console.log('通讯录邀请会议终端', e)
|
|
4123
|
+
if (e && e.length > 0) {
|
|
4124
|
+
// 构建批量外呼参数并一次性发起
|
|
4125
|
+
const terminalCalls = e
|
|
4126
|
+
.map(item => {
|
|
4127
|
+
const id = item?.id
|
|
4128
|
+
return id ? { dnis: id, name: item.label, type: 4 } : null
|
|
4129
|
+
})
|
|
4130
|
+
.filter(Boolean)
|
|
4131
|
+
|
|
4132
|
+
if (terminalCalls.length > 0) {
|
|
4133
|
+
try {
|
|
4134
|
+
await this.liveClient.makeBatchCall(terminalCalls)
|
|
4135
|
+
this.showMessage.message('success', '已发起会议终端批量外呼')
|
|
4136
|
+
} catch (err) {
|
|
4137
|
+
this.showMessage.message('error', `会议终端批量外呼失败: ${err?.message || err}`)
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
3341
4140
|
}
|
|
3342
4141
|
},
|
|
3343
4142
|
async updateNameConfirm(e) {
|
|
@@ -3369,6 +4168,27 @@ export default {
|
|
|
3369
4168
|
type,
|
|
3370
4169
|
});
|
|
3371
4170
|
},
|
|
4171
|
+
dispatchLocalTrack(kind = '') {
|
|
4172
|
+
if (this.participantNum > 0) {
|
|
4173
|
+
let index = this.participants.findIndex(item => item.isLocal)
|
|
4174
|
+
if (index !== -1) {
|
|
4175
|
+
let localVideoTrack = this.participants[index]?.videoTrack
|
|
4176
|
+
let localAudioTrack = this.participants[index]?.audioTrack
|
|
4177
|
+
if (!kind) {
|
|
4178
|
+
localVideoTrack?.detach()
|
|
4179
|
+
localVideoTrack?.stop()
|
|
4180
|
+
localAudioTrack?.detach()
|
|
4181
|
+
localAudioTrack?.stop()
|
|
4182
|
+
} else if (kind === 'video') {
|
|
4183
|
+
localVideoTrack?.detach()
|
|
4184
|
+
localVideoTrack?.stop()
|
|
4185
|
+
} else if (kind === 'audio') {
|
|
4186
|
+
localAudioTrack?.detach()
|
|
4187
|
+
localAudioTrack?.stop()
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
},
|
|
3372
4192
|
async chooseVideoDevice(e) {
|
|
3373
4193
|
await this.changeActiveDevice("videoinput", e);
|
|
3374
4194
|
this.videoSelectShow = false;
|
|
@@ -3393,7 +4213,50 @@ export default {
|
|
|
3393
4213
|
await this.liveClient.changeScreenShareStatus(this.screenShareCaptureOption);
|
|
3394
4214
|
},
|
|
3395
4215
|
screenShareOptionChange(e) {
|
|
3396
|
-
|
|
4216
|
+
if (!e) return
|
|
4217
|
+
const { isFluencyPriority, systemAudioInclude } = e
|
|
4218
|
+
|
|
4219
|
+
// 1) 生效到当前响应式配置(将于下一次开始共享时使用;不对正在共享做自动重启)
|
|
4220
|
+
this.screenShareCaptureOption.systemAudio = systemAudioInclude ? 'include' : 'exclude'
|
|
4221
|
+
const targetFrameRate = isFluencyPriority ? 60 : 30
|
|
4222
|
+
if (
|
|
4223
|
+
!this.screenShareCaptureOption.resolution ||
|
|
4224
|
+
typeof this.screenShareCaptureOption.resolution !== 'object'
|
|
4225
|
+
) {
|
|
4226
|
+
this.screenShareCaptureOption.resolution = { width: 1920, height: 1080, frameRate: targetFrameRate }
|
|
4227
|
+
} else {
|
|
4228
|
+
this.screenShareCaptureOption.resolution.frameRate = targetFrameRate
|
|
4229
|
+
}
|
|
4230
|
+
|
|
4231
|
+
// 2) 正在共享时尽量实时生效:
|
|
4232
|
+
// - 帧率:对现有轨道调用 applyConstraints(frameRate)
|
|
4233
|
+
// - 系统音频:单独发布/取消发布 ScreenShareAudio 轨道,避免影响视频轨道
|
|
4234
|
+
// if (isScreenShareEnabled?.value) {
|
|
4235
|
+
// try {
|
|
4236
|
+
// const fr = screenShareCaptureOption?.resolution?.frameRate
|
|
4237
|
+
// if (typeof fr === 'number') {
|
|
4238
|
+
// await liveClient.updateScreenShareFrameRate(fr)
|
|
4239
|
+
// }
|
|
4240
|
+
// } catch (_) {}
|
|
4241
|
+
// try {
|
|
4242
|
+
// await liveClient.setScreenShareSystemAudio(systemAudioInclude)
|
|
4243
|
+
// } catch (_) {}
|
|
4244
|
+
// }
|
|
4245
|
+
|
|
4246
|
+
// 3) 按会议号持久化(当前会话内保留,退出会议后不跨会保留)
|
|
4247
|
+
const roomId = this.meetingNum
|
|
4248
|
+
if (roomId) {
|
|
4249
|
+
try {
|
|
4250
|
+
const key = `lk:ss_prefs:${roomId}`
|
|
4251
|
+
const toSave = {
|
|
4252
|
+
systemAudio: this.screenShareCaptureOption.systemAudio,
|
|
4253
|
+
resolution: { ...this.screenShareCaptureOption.resolution },
|
|
4254
|
+
}
|
|
4255
|
+
sessionStorage.setItem(key, JSON.stringify(toSave))
|
|
4256
|
+
} catch (err) {
|
|
4257
|
+
// 忽略持久化异常
|
|
4258
|
+
}
|
|
4259
|
+
}
|
|
3397
4260
|
},
|
|
3398
4261
|
stopScreenShare() {
|
|
3399
4262
|
this.changeScreenShareStatus(this.screenShareCaptureOption);
|
|
@@ -3431,6 +4294,7 @@ export default {
|
|
|
3431
4294
|
appendInvite(e, inviteWay) {
|
|
3432
4295
|
let tempList = [];
|
|
3433
4296
|
let tempDeviceList = []
|
|
4297
|
+
let tempTerminalList = []
|
|
3434
4298
|
let index = -1;
|
|
3435
4299
|
if (e && e.length > 0) {
|
|
3436
4300
|
e.forEach((item) => {
|
|
@@ -3451,12 +4315,15 @@ export default {
|
|
|
3451
4315
|
tempList.push(item);
|
|
3452
4316
|
}
|
|
3453
4317
|
this.addToInviteList(item);
|
|
3454
|
-
} else {
|
|
4318
|
+
} else if (item.source == '设备' || item.source == '监控') {
|
|
3455
4319
|
tempDeviceList.push(item)
|
|
4320
|
+
} else if (item.source == '会议终端') {
|
|
4321
|
+
tempTerminalList.push(item)
|
|
3456
4322
|
}
|
|
3457
4323
|
});
|
|
3458
4324
|
console.log("本次追加邀请人员", tempList);
|
|
3459
4325
|
console.log("本次追加邀请设备", tempDeviceList);
|
|
4326
|
+
console.log("本次追加邀请会议终端", tempTerminalList);
|
|
3460
4327
|
// 追加邀请人员
|
|
3461
4328
|
if(tempList.length > 0) {
|
|
3462
4329
|
const promises = [];
|
|
@@ -3485,7 +4352,8 @@ export default {
|
|
|
3485
4352
|
const volteCalls = tempList.map(item => {
|
|
3486
4353
|
return {
|
|
3487
4354
|
dnis: item.phone,
|
|
3488
|
-
name: item?.label || item?.userName || '未知用户'
|
|
4355
|
+
name: item?.label || item?.userName || '未知用户',
|
|
4356
|
+
type: 0,
|
|
3489
4357
|
}
|
|
3490
4358
|
})
|
|
3491
4359
|
promises.push(
|
|
@@ -3524,25 +4392,52 @@ export default {
|
|
|
3524
4392
|
});
|
|
3525
4393
|
}
|
|
3526
4394
|
// 追加邀请设备
|
|
3527
|
-
if(tempDeviceList.length > 0) {
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
4395
|
+
if (tempDeviceList && tempDeviceList.length > 0) {
|
|
4396
|
+
const deviceCalls = tempDeviceList
|
|
4397
|
+
.map(item => {
|
|
4398
|
+
const id = item?.source === '监控' ? item?.monitorID : item?.equipmentID;
|
|
4399
|
+
return id ? { dnis: id, name: item.label, type: this.handleInviteType(item?.integrationType) } : null;
|
|
4400
|
+
})
|
|
4401
|
+
.filter(Boolean);
|
|
4402
|
+
|
|
4403
|
+
if (deviceCalls.length > 0) {
|
|
4404
|
+
this.liveClient.makeBatchCall(deviceCalls)
|
|
4405
|
+
.then(() => {
|
|
4406
|
+
this.showMessage.message('success', '监控设备批量外呼已发起');
|
|
4407
|
+
})
|
|
4408
|
+
.catch(err => {
|
|
4409
|
+
this.showMessage.message('error', `批量拉取监控设备失败: ${err?.message || err}`);
|
|
4410
|
+
});
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
// 追加邀请终端
|
|
4414
|
+
if (tempTerminalList.length > 0) {
|
|
4415
|
+
const terminalCalls = tempTerminalList
|
|
4416
|
+
.map(item => {
|
|
4417
|
+
return item?.id ? { dnis: item.id, name: item.label, type: 4 } : null
|
|
4418
|
+
})
|
|
4419
|
+
.filter(Boolean)
|
|
4420
|
+
if (terminalCalls.length > 0) {
|
|
4421
|
+
this.liveClient
|
|
4422
|
+
.makeBatchCall(terminalCalls)
|
|
4423
|
+
.then(() => {
|
|
4424
|
+
this.showMessage.message('success', '会议终端批量外呼已发起')
|
|
4425
|
+
})
|
|
4426
|
+
.catch(err => {
|
|
4427
|
+
this.showMessage.message('error', `批量拉取会议终端失败: ${err?.message || err}`)
|
|
4428
|
+
})
|
|
4429
|
+
}
|
|
3535
4430
|
}
|
|
3536
4431
|
}
|
|
3537
4432
|
},
|
|
3538
|
-
pullMonitorDevice(monitorID, monitorName) {
|
|
4433
|
+
pullMonitorDevice(monitorID, monitorName, integrationType = 0) {
|
|
3539
4434
|
this.liveClient.judgeUserInMeeting(this.meetingNum, monitorID).then((res) => {
|
|
3540
4435
|
if (res && res?.code == 200) {
|
|
3541
4436
|
if (res.data == 1) {
|
|
3542
4437
|
this.showMessage.message("error", "该监控设备已进入会议");
|
|
3543
4438
|
return;
|
|
3544
4439
|
} else {
|
|
3545
|
-
this.liveClient.makeCall(monitorName, monitorID,
|
|
4440
|
+
this.liveClient.makeCall(monitorName, monitorID, this.handleInviteType(integrationType));
|
|
3546
4441
|
}
|
|
3547
4442
|
} else {
|
|
3548
4443
|
this.showMessage.message("error", "获取监控设备进会状态失败");
|
|
@@ -3557,7 +4452,8 @@ export default {
|
|
|
3557
4452
|
const platformGroup = {
|
|
3558
4453
|
'1_2': [], // platformID 为 1 或 2
|
|
3559
4454
|
'7': [], // platformID 为 7
|
|
3560
|
-
'4': [] // platformID 为 4
|
|
4455
|
+
'4': [], // platformID 为 4
|
|
4456
|
+
'8': [] // platformID 为 8
|
|
3561
4457
|
};
|
|
3562
4458
|
|
|
3563
4459
|
uninviteList.forEach(item => {
|
|
@@ -3569,6 +4465,8 @@ export default {
|
|
|
3569
4465
|
platformGroup['7'].push({ userName, identity, phone });
|
|
3570
4466
|
} else if (platformID === 4) {
|
|
3571
4467
|
platformGroup['4'].push({ userName, identity, phone });
|
|
4468
|
+
} else if (platformID === 8) {
|
|
4469
|
+
platformGroup['8'].push({ userName, identity, phone });
|
|
3572
4470
|
}
|
|
3573
4471
|
});
|
|
3574
4472
|
|
|
@@ -3619,7 +4517,8 @@ export default {
|
|
|
3619
4517
|
// 构建批量外呼数据
|
|
3620
4518
|
const volteCalls = platformGroup['4'].map(item => ({
|
|
3621
4519
|
dnis: item.phone,
|
|
3622
|
-
name: item.userName
|
|
4520
|
+
name: item.userName,
|
|
4521
|
+
type: 0
|
|
3623
4522
|
}));
|
|
3624
4523
|
|
|
3625
4524
|
promises.push(
|
|
@@ -3636,6 +4535,27 @@ export default {
|
|
|
3636
4535
|
);
|
|
3637
4536
|
}
|
|
3638
4537
|
|
|
4538
|
+
// 处理 platformID 为 8 的呼叫
|
|
4539
|
+
if (platformGroup['8'].length > 0) {
|
|
4540
|
+
// 构建批量外呼数据
|
|
4541
|
+
const terminalCalls = platformGroup['8'].map(item => ({
|
|
4542
|
+
dnis: item.phone,
|
|
4543
|
+
name: item.userName,
|
|
4544
|
+
type: 4
|
|
4545
|
+
}))
|
|
4546
|
+
|
|
4547
|
+
promises.push(
|
|
4548
|
+
this.liveClient
|
|
4549
|
+
.makeBatchCall(terminalCalls)
|
|
4550
|
+
.then(() => {
|
|
4551
|
+
this.showMessage.message('success', '已发起终端批量外呼')
|
|
4552
|
+
})
|
|
4553
|
+
.catch(err => {
|
|
4554
|
+
this.showMessage.message('error', `批量呼叫失败 (platformID 8): ${err.message}`)
|
|
4555
|
+
})
|
|
4556
|
+
)
|
|
4557
|
+
}
|
|
4558
|
+
|
|
3639
4559
|
// 等待所有邀请完成后更新列表
|
|
3640
4560
|
Promise.allSettled(promises).then(() => {
|
|
3641
4561
|
// 可以在这里添加统一的后续处理,如刷新邀请列表
|
|
@@ -4159,6 +5079,64 @@ export default {
|
|
|
4159
5079
|
}
|
|
4160
5080
|
}
|
|
4161
5081
|
|
|
5082
|
+
if (this.currentLayout === 'ring') {
|
|
5083
|
+
// 从环形布局到点调模式:先拆解环形容器,将参与者恢复到主容器,然后按照点调规则分配
|
|
5084
|
+
const layoutRing = document.querySelector('#room .layout-ring');
|
|
5085
|
+
if (layoutRing) {
|
|
5086
|
+
const ringTop = layoutRing.querySelector('.layout-ring-top');
|
|
5087
|
+
const ringBottom = layoutRing.querySelector('.layout-ring-bottom');
|
|
5088
|
+
const ringCenter = layoutRing.querySelector('.ring-center');
|
|
5089
|
+
// 先将center、slots、bottom中的参与者移动回主容器
|
|
5090
|
+
if (ringTop) {
|
|
5091
|
+
const slots = ringTop.querySelectorAll('.ring-slot');
|
|
5092
|
+
slots.forEach(slot => {
|
|
5093
|
+
while (slot.firstChild) {
|
|
5094
|
+
container.appendChild(slot.firstChild);
|
|
5095
|
+
}
|
|
5096
|
+
});
|
|
5097
|
+
if (ringCenter) {
|
|
5098
|
+
while (ringCenter.firstChild) {
|
|
5099
|
+
container.appendChild(ringCenter.firstChild);
|
|
5100
|
+
}
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
if (ringBottom) {
|
|
5104
|
+
while (ringBottom.firstChild) {
|
|
5105
|
+
container.appendChild(ringBottom.firstChild);
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5108
|
+
layoutRing.remove();
|
|
5109
|
+
}
|
|
5110
|
+
|
|
5111
|
+
// 收集参与者元素
|
|
5112
|
+
const participantElements = Array.from(container.children).filter(child =>
|
|
5113
|
+
child.classList && child.classList.contains('participant') && child.id && child.id.startsWith('participant-')
|
|
5114
|
+
);
|
|
5115
|
+
|
|
5116
|
+
// 分配到点调容器:主持人优先到center[0],焦点到center[1](如果存在且不同),其余进入top/bottom/other
|
|
5117
|
+
// 注意:pointTurnCenter已有两个占位符 placeHolderElm1, placeHolderElm2
|
|
5118
|
+
participantElements.forEach(child => {
|
|
5119
|
+
if (this.curHostIdentity && child.id === `participant-${this.curHostIdentity}`) {
|
|
5120
|
+
// 主持人放在中心第一个位置
|
|
5121
|
+
pointTurnCenter.removeChild(placeHolderElm1);
|
|
5122
|
+
pointTurnCenter.insertBefore(child, pointTurnCenter.firstChild ?? null);
|
|
5123
|
+
} else if (this.curBlurIdentity && child.id === `participant-${this.curBlurIdentity}` && this.curBlurIdentity !== this.curHostIdentity) {
|
|
5124
|
+
// 焦点用户放在中心第二个位置(若不同于主持人)
|
|
5125
|
+
pointTurnCenter.removeChild(placeHolderElm2);
|
|
5126
|
+
pointTurnCenter.appendChild(child);
|
|
5127
|
+
} else {
|
|
5128
|
+
if (pointTurnTop.children.length >= 5) {
|
|
5129
|
+
if (pointTurnBottom.children.length >= 5) {
|
|
5130
|
+
pointTurnOther.appendChild(child);
|
|
5131
|
+
} else {
|
|
5132
|
+
pointTurnBottom.appendChild(child);
|
|
5133
|
+
}
|
|
5134
|
+
} else {
|
|
5135
|
+
pointTurnTop.appendChild(child);
|
|
5136
|
+
}
|
|
5137
|
+
}
|
|
5138
|
+
});
|
|
5139
|
+
}
|
|
4162
5140
|
// 添加点调容器之前的最终检查
|
|
4163
5141
|
const remainingLayoutElements = container.querySelectorAll(
|
|
4164
5142
|
".layout-leftside, .layout-rightside, .point-turn-top, .point-turn-center, .point-turn-bottom, .point-turn-other"
|
|
@@ -4327,6 +5305,92 @@ export default {
|
|
|
4327
5305
|
container.insertBefore(layoutLeftSideEle, container.firstChild ?? null);
|
|
4328
5306
|
container.appendChild(layoutRightSideEle);
|
|
4329
5307
|
}
|
|
5308
|
+
|
|
5309
|
+
if (this.currentLayout === 'ring') {
|
|
5310
|
+
// 从点调模式到环形布局:创建环形容器,将与会者分配到center/slots/bottom
|
|
5311
|
+
const existingRing = document.querySelector('#room .layout-ring');
|
|
5312
|
+
if (existingRing) {
|
|
5313
|
+
existingRing.remove();
|
|
5314
|
+
}
|
|
5315
|
+
const layoutRing = document.createElement('div');
|
|
5316
|
+
layoutRing.className = 'layout-ring';
|
|
5317
|
+
const layoutRingTop = document.createElement('div');
|
|
5318
|
+
layoutRingTop.className = 'layout-ring-top';
|
|
5319
|
+
const layoutRingBottom = document.createElement('div');
|
|
5320
|
+
layoutRingBottom.className = 'layout-ring-bottom';
|
|
5321
|
+
// 创建12个槽位
|
|
5322
|
+
for (let i = 0; i < 12; i++) {
|
|
5323
|
+
const slot = document.createElement('div');
|
|
5324
|
+
slot.className = 'ring-slot';
|
|
5325
|
+
layoutRingTop.appendChild(slot);
|
|
5326
|
+
}
|
|
5327
|
+
const ringCenter = document.createElement('div');
|
|
5328
|
+
ringCenter.className = 'ring-center';
|
|
5329
|
+
layoutRingTop.appendChild(ringCenter);
|
|
5330
|
+
|
|
5331
|
+
// 将点调容器中的参与者按顺序分配:center(主持人 -> 焦点)、再从top、bottom、other依序填充slots;超出放到bottom
|
|
5332
|
+
const collect = (ele) => (ele ? Array.from(ele.children).filter(c => c.id && c.classList.contains('participant')) : []);
|
|
5333
|
+
const topList = collect(pointTurnTop);
|
|
5334
|
+
const centerList = collect(pointTurnCenter);
|
|
5335
|
+
const bottomList = collect(pointTurnBottom);
|
|
5336
|
+
const otherList = collect(pointTurnOther);
|
|
5337
|
+
|
|
5338
|
+
// 先处理中心:主持人在center首位,若有焦点且不同于主持人,作为第二位
|
|
5339
|
+
let hostEl = centerList.find(c => this.curHostIdentity && c.id === `participant-${this.curHostIdentity}`);
|
|
5340
|
+
if (!hostEl) {
|
|
5341
|
+
// 尝试从其它列表找到主持人
|
|
5342
|
+
hostEl = [...topList, ...bottomList, ...otherList].find(c => this.curHostIdentity && c.id === `participant-${this.curHostIdentity}`);
|
|
5343
|
+
if (hostEl && hostEl.parentElement) hostEl.parentElement.removeChild(hostEl);
|
|
5344
|
+
}
|
|
5345
|
+
if (hostEl) {
|
|
5346
|
+
ringCenter.appendChild(hostEl);
|
|
5347
|
+
}
|
|
5348
|
+
let blurEl = null;
|
|
5349
|
+
if (this.curBlurIdentity && this.curBlurIdentity !== this.curHostIdentity) {
|
|
5350
|
+
blurEl = centerList.find(c => c.id === `participant-${this.curBlurIdentity}`)
|
|
5351
|
+
|| topList.find(c => c.id === `participant-${this.curBlurIdentity}`)
|
|
5352
|
+
|| bottomList.find(c => c.id === `participant-${this.curBlurIdentity}`)
|
|
5353
|
+
|| otherList.find(c => c.id === `participant-${this.curBlurIdentity}`);
|
|
5354
|
+
if (blurEl && blurEl.parentElement) {
|
|
5355
|
+
blurEl.parentElement.removeChild(blurEl);
|
|
5356
|
+
// 注意:ring 只在中心留主持人,焦点不会占用中心,因此将其回到 slots 序列,不放中心
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
5359
|
+
|
|
5360
|
+
// 构建顺序序列:top -> bottom -> other(去除已被拿走的host/blur)
|
|
5361
|
+
const rest = [...topList, ...bottomList, ...otherList].filter(el => el !== hostEl && el !== blurEl);
|
|
5362
|
+
const slots = layoutRingTop.querySelectorAll('.ring-slot');
|
|
5363
|
+
let filled = 0;
|
|
5364
|
+
// 先将可能存在的焦点用户放在第一个可用槽(如果存在)
|
|
5365
|
+
if (blurEl) {
|
|
5366
|
+
if (filled < slots.length) {
|
|
5367
|
+
slots[filled++].appendChild(blurEl);
|
|
5368
|
+
} else {
|
|
5369
|
+
layoutRingBottom.appendChild(blurEl);
|
|
5370
|
+
}
|
|
5371
|
+
}
|
|
5372
|
+
// 填充剩余slots
|
|
5373
|
+
for (const el of rest) {
|
|
5374
|
+
if (filled < slots.length) {
|
|
5375
|
+
slots[filled++].appendChild(el);
|
|
5376
|
+
} else {
|
|
5377
|
+
layoutRingBottom.appendChild(el);
|
|
5378
|
+
}
|
|
5379
|
+
}
|
|
5380
|
+
|
|
5381
|
+
// 清理点调容器并挂载环容器
|
|
5382
|
+
const removeIfChild = (ele) => { if (ele && container.contains(ele)) container.removeChild(ele); };
|
|
5383
|
+
removeIfChild(pointTurnTop);
|
|
5384
|
+
removeIfChild(pointTurnCenter);
|
|
5385
|
+
removeIfChild(pointTurnBottom);
|
|
5386
|
+
removeIfChild(pointTurnOther);
|
|
5387
|
+
|
|
5388
|
+
layoutRing.appendChild(layoutRingTop);
|
|
5389
|
+
layoutRing.appendChild(layoutRingBottom);
|
|
5390
|
+
container.insertBefore(layoutRing, container.firstChild ?? null);
|
|
5391
|
+
// 点调 -> 环形:创建后同步高度
|
|
5392
|
+
requestAnimationFrame(() => this.updateRingTopHeight());
|
|
5393
|
+
}
|
|
4330
5394
|
}
|
|
4331
5395
|
});
|
|
4332
5396
|
},
|
|
@@ -4550,7 +5614,7 @@ export default {
|
|
|
4550
5614
|
align-items: center;
|
|
4551
5615
|
justify-content: space-between;
|
|
4552
5616
|
position: absolute;
|
|
4553
|
-
z-index:
|
|
5617
|
+
z-index: 4100;
|
|
4554
5618
|
bottom: -66px;
|
|
4555
5619
|
left: 0;
|
|
4556
5620
|
transition: all 0.2s ease;
|