mdm-client 1.0.3 → 1.0.4
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 +1063 -98
- package/src/components/LiveMultipleMeeting/style/index.scss +145 -14
- package/src/components/LivePoint/LivePoint.vue +49 -211
- package/src/components/LivePointMeeting/LivePointMeeting.vue +159 -10
- package/src/components/LivePointMeeting/style/index.scss +35 -0
- package/src/components/MeetingReadyDialog/MeetingReadyDialog.vue +96 -14
- 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/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
|
@@ -142,7 +142,8 @@ export default {
|
|
|
142
142
|
isDurationCalc: true,
|
|
143
143
|
isFootShow: false,
|
|
144
144
|
footVisibleDuration: 3,
|
|
145
|
-
isRoomConnectedHandled: false
|
|
145
|
+
isRoomConnectedHandled: false,
|
|
146
|
+
rotateDegreeMap: new Map(), // 存储与会者identity对应的旋转角度
|
|
146
147
|
}
|
|
147
148
|
},
|
|
148
149
|
computed: {
|
|
@@ -164,6 +165,47 @@ export default {
|
|
|
164
165
|
}
|
|
165
166
|
},
|
|
166
167
|
methods: {
|
|
168
|
+
// 根据 identity 和度数应用旋转到视频元素,并切换旋转按钮图标
|
|
169
|
+
applyRotation(identity, degree) {
|
|
170
|
+
try {
|
|
171
|
+
const norm = ((degree % 360) + 360) % 360; // 归一化到0-359
|
|
172
|
+
const videoElm = document.getElementById(`video-${identity}`);
|
|
173
|
+
if (videoElm) {
|
|
174
|
+
videoElm.style.transformOrigin = 'center center';
|
|
175
|
+
if (norm === 90 || norm === 270) {
|
|
176
|
+
const parent = videoElm.parentElement;
|
|
177
|
+
if (parent) {
|
|
178
|
+
const rect = parent.getBoundingClientRect();
|
|
179
|
+
videoElm.style.top = '50%';
|
|
180
|
+
videoElm.style.left = '50%';
|
|
181
|
+
videoElm.style.width = `${rect.height}px`;
|
|
182
|
+
videoElm.style.height = `${rect.width}px`;
|
|
183
|
+
videoElm.style.transform = `translate(-50%, -50%) rotate(${norm}deg)`;
|
|
184
|
+
videoElm.style.objectFit = 'cover';
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
videoElm.style.top = '0';
|
|
188
|
+
videoElm.style.left = '0';
|
|
189
|
+
videoElm.style.width = '100%';
|
|
190
|
+
videoElm.style.height = '100%';
|
|
191
|
+
videoElm.style.transform = `rotate(${norm}deg)`;
|
|
192
|
+
videoElm.style.objectFit = 'contain';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const rotateElm = document.getElementById(`rotate-${identity}`);
|
|
197
|
+
if (rotateElm) {
|
|
198
|
+
let clsIndex = 1;
|
|
199
|
+
if (norm === 0) clsIndex = 1;
|
|
200
|
+
else if (norm === 90) clsIndex = 2;
|
|
201
|
+
else if (norm === 180) clsIndex = 3;
|
|
202
|
+
else if (norm === 270) clsIndex = 4;
|
|
203
|
+
rotateElm.className = `rotate-icon rotate-icon${clsIndex}`;
|
|
204
|
+
}
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error('applyRotation error', err);
|
|
207
|
+
}
|
|
208
|
+
},
|
|
167
209
|
async createRoom() {
|
|
168
210
|
if (!this.liveClient) {
|
|
169
211
|
return;
|
|
@@ -719,6 +761,8 @@ export default {
|
|
|
719
761
|
// 与会者状态变更
|
|
720
762
|
// 视频元素
|
|
721
763
|
let videoElm = document.getElementById(`video-${videoItem.identity}`);
|
|
764
|
+
// 旋转按钮元素
|
|
765
|
+
let rotateElm = document.getElementById(`rotate-${videoItem.identity}`);
|
|
722
766
|
// 当与会者断开会议链接即remove为true
|
|
723
767
|
if (videoItem.remove) {
|
|
724
768
|
if (videoElm) {
|
|
@@ -731,6 +775,15 @@ export default {
|
|
|
731
775
|
}
|
|
732
776
|
this.removeFromParticipantList(videoItem);
|
|
733
777
|
|
|
778
|
+
// 清理旋转按钮与状态
|
|
779
|
+
if (rotateElm) {
|
|
780
|
+
try { videoDiv && videoDiv.removeChild(rotateElm); } catch (e) {}
|
|
781
|
+
rotateElm = null;
|
|
782
|
+
}
|
|
783
|
+
if (this.rotateDegreeMap.has(videoItem.identity)) {
|
|
784
|
+
this.rotateDegreeMap.delete(videoItem.identity);
|
|
785
|
+
}
|
|
786
|
+
|
|
734
787
|
// 音视频轨道与video元素解绑
|
|
735
788
|
if (videoItem.videoTrack) {
|
|
736
789
|
videoItem.videoTrack.detach();
|
|
@@ -760,6 +813,22 @@ export default {
|
|
|
760
813
|
let boardElm = document.getElementById(`board-${videoItem.identity}`);
|
|
761
814
|
// 底部麦克风图标元素
|
|
762
815
|
let microElm = document.getElementById(`microphone-${videoItem.identity}`);
|
|
816
|
+
// 根据platformID设定元素样式(与多方会议保持一致)
|
|
817
|
+
if (videoElm) {
|
|
818
|
+
if (
|
|
819
|
+
videoItem.metadata?.platformID == 1 ||
|
|
820
|
+
videoItem.metadata?.platformID == 4 ||
|
|
821
|
+
videoItem.metadata?.platformID == 7
|
|
822
|
+
) {
|
|
823
|
+
videoElm.style.objectFit = 'contain';
|
|
824
|
+
} else {
|
|
825
|
+
if (videoItem?.source == 'camera') {
|
|
826
|
+
videoElm.style.objectFit = 'cover';
|
|
827
|
+
} else {
|
|
828
|
+
videoElm.style.objectFit = 'contain';
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
763
832
|
// 声明麦克风按钮点击事件回调
|
|
764
833
|
const unableMicrophone = () => {
|
|
765
834
|
this.liveClient.changeParticipantMicrophoneStatus(videoItem.identity, true);
|
|
@@ -776,17 +845,43 @@ export default {
|
|
|
776
845
|
let layoutNormalElm = document.querySelector("#point-meeting-contain .layout-normal");
|
|
777
846
|
let localChild = layoutBlurElm.firstElementChild;
|
|
778
847
|
let currentEle = document.getElementById(`participant-${videoItem.identity}`);
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
848
|
+
// 核心逻辑:点击任意非焦点或焦点元素,切换位置
|
|
849
|
+
// 期望行为:layoutBlurElm 始终只保留一个“缩略/小画面”元素(本地),点击其他元素切换焦点并将之前焦点移到小画面
|
|
850
|
+
if (!layoutBlurElm || !layoutNormalElm || !currentEle) return;
|
|
851
|
+
|
|
852
|
+
const isCurrentInBlur = currentEle.parentElement === layoutBlurElm;
|
|
853
|
+
const isCurrentInNormal = currentEle.parentElement === layoutNormalElm;
|
|
854
|
+
const blurFirst = layoutBlurElm.firstElementChild;
|
|
855
|
+
const normalFirst = layoutNormalElm.firstElementChild;
|
|
856
|
+
|
|
857
|
+
// 如果当前元素已经在小画面区域(layoutBlurElm),则尝试与 normalFirst 交换(如果存在)
|
|
858
|
+
if (isCurrentInBlur) {
|
|
859
|
+
if (normalFirst) {
|
|
860
|
+
// 将 normalFirst 移到小画面
|
|
861
|
+
layoutBlurElm.appendChild(normalFirst);
|
|
862
|
+
// 将当前小画面移到大画面顶部
|
|
863
|
+
layoutNormalElm.insertBefore(currentEle, layoutNormalElm.firstChild ?? null);
|
|
782
864
|
} else {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
layoutBlurElm.appendChild(currentEle);
|
|
786
|
-
}
|
|
865
|
+
// 没有大画面,直接保持当前不动
|
|
866
|
+
return;
|
|
787
867
|
}
|
|
788
|
-
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// 当前在大画面区域
|
|
872
|
+
if (isCurrentInNormal) {
|
|
873
|
+
// 如果小画面里有元素,则和它交换
|
|
874
|
+
if (blurFirst) {
|
|
875
|
+
layoutNormalElm.insertBefore(blurFirst, layoutNormalElm.firstChild ?? null);
|
|
876
|
+
}
|
|
877
|
+
// 将点击的大画面元素移入小画面
|
|
789
878
|
layoutBlurElm.appendChild(currentEle);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// 兜底:如果元素不在两者内(理论上不发生),直接放入大画面
|
|
883
|
+
if (!isCurrentInBlur && !isCurrentInNormal) {
|
|
884
|
+
layoutNormalElm.insertBefore(currentEle, layoutNormalElm.firstChild ?? null);
|
|
790
885
|
}
|
|
791
886
|
};
|
|
792
887
|
// 为与会者元素绑定事件
|
|
@@ -810,6 +905,55 @@ export default {
|
|
|
810
905
|
`;
|
|
811
906
|
videoDiv.appendChild(describeElm);
|
|
812
907
|
}
|
|
908
|
+
// 仅在视频通话(roomMode=auto => 此组件中对应 meetingType==='video')、且为摄像头画面、且 platformID 为 4 时显示旋转按钮
|
|
909
|
+
if (
|
|
910
|
+
videoItem.isCameraEnabled &&
|
|
911
|
+
!videoItem.isScreenShareEnabled &&
|
|
912
|
+
videoItem?.metadata?.platformID === 4
|
|
913
|
+
) {
|
|
914
|
+
if (!rotateElm) {
|
|
915
|
+
rotateElm = document.createElement('div');
|
|
916
|
+
rotateElm.id = `rotate-${videoItem.identity}`;
|
|
917
|
+
rotateElm.className = 'rotate-icon rotate-icon1';
|
|
918
|
+
videoDiv && videoDiv.appendChild(rotateElm);
|
|
919
|
+
}
|
|
920
|
+
// 绑定(或重绑)事件处理,阻止冒泡,避免触发父容器的setBlurVideoLeft
|
|
921
|
+
const handleRotateClick = (event) => {
|
|
922
|
+
event?.stopPropagation?.();
|
|
923
|
+
event?.preventDefault?.();
|
|
924
|
+
const current = this.rotateDegreeMap.get(videoItem.identity) || 0;
|
|
925
|
+
const next = (current + 90) % 360;
|
|
926
|
+
this.rotateDegreeMap.set(videoItem.identity, next);
|
|
927
|
+
this.applyRotation(videoItem.identity, next);
|
|
928
|
+
};
|
|
929
|
+
rotateElm.onclick = handleRotateClick;
|
|
930
|
+
// 进一步保险:阻止按下就冒泡(处理某些浏览器差异与长按)
|
|
931
|
+
rotateElm.onmousedown = (e) => { e?.stopPropagation?.(); };
|
|
932
|
+
rotateElm.ontouchstart = (e) => { e?.stopPropagation?.(); };
|
|
933
|
+
// 初始化角度并应用
|
|
934
|
+
if (!this.rotateDegreeMap.has(videoItem.identity)) {
|
|
935
|
+
this.rotateDegreeMap.set(videoItem.identity, 0);
|
|
936
|
+
}
|
|
937
|
+
this.applyRotation(videoItem.identity, this.rotateDegreeMap.get(videoItem.identity));
|
|
938
|
+
} else {
|
|
939
|
+
// 不满足条件则移除旋转按钮并清除状态
|
|
940
|
+
if (rotateElm) {
|
|
941
|
+
try { videoDiv && videoDiv.removeChild(rotateElm); } catch (e) {}
|
|
942
|
+
rotateElm = null;
|
|
943
|
+
}
|
|
944
|
+
if (this.rotateDegreeMap.has(videoItem.identity)) {
|
|
945
|
+
this.rotateDegreeMap.delete(videoItem.identity);
|
|
946
|
+
}
|
|
947
|
+
// 同时复原视频元素的旋转
|
|
948
|
+
if (videoElm) {
|
|
949
|
+
videoElm.style.transform = '';
|
|
950
|
+
videoElm.style.transformOrigin = '';
|
|
951
|
+
videoElm.style.top = '';
|
|
952
|
+
videoElm.style.left = '';
|
|
953
|
+
videoElm.style.width = '';
|
|
954
|
+
videoElm.style.height = '';
|
|
955
|
+
}
|
|
956
|
+
}
|
|
813
957
|
} else {
|
|
814
958
|
if (describeElm) {
|
|
815
959
|
videoDiv.removeChild(describeElm);
|
|
@@ -851,6 +995,10 @@ export default {
|
|
|
851
995
|
}
|
|
852
996
|
}
|
|
853
997
|
this.addToParticipantList(videoItem);
|
|
998
|
+
// 每次渲染时,如存在旋转角度记录,则应用旋转(避免重新渲染后丢失旋转效果)
|
|
999
|
+
if (this.rotateDegreeMap.has(videoItem.identity)) {
|
|
1000
|
+
this.applyRotation(videoItem.identity, this.rotateDegreeMap.get(videoItem.identity));
|
|
1001
|
+
}
|
|
854
1002
|
},
|
|
855
1003
|
renderAudioItem(audioItem) {
|
|
856
1004
|
console.log("语音通话渲染" + audioItem.identity + ":", audioItem);
|
|
@@ -887,7 +1035,8 @@ export default {
|
|
|
887
1035
|
}
|
|
888
1036
|
this.addToParticipantList(audioItem);
|
|
889
1037
|
}
|
|
890
|
-
|
|
1038
|
+
// 修复ID选择器错误:应为 audio-${identity}
|
|
1039
|
+
let audioElm = document.getElementById(`audio-${audioItem.identity}`);
|
|
891
1040
|
if (audioItem.remove) {
|
|
892
1041
|
if (audioElm) {
|
|
893
1042
|
audioElm.srcObject = null;
|
|
@@ -110,6 +110,8 @@
|
|
|
110
110
|
height: 100%;
|
|
111
111
|
position: relative;
|
|
112
112
|
border-radius: 6px;
|
|
113
|
+
background: var(--dialog-bg);
|
|
114
|
+
overflow: hidden; // 旋转后裁剪溢出内容,避免页面出现“怪异”形态
|
|
113
115
|
.p-video {
|
|
114
116
|
position: absolute;
|
|
115
117
|
width: 100%;
|
|
@@ -119,6 +121,39 @@
|
|
|
119
121
|
z-index: 10;
|
|
120
122
|
object-fit: contain;
|
|
121
123
|
border-radius: 6px;
|
|
124
|
+
backface-visibility: hidden; // 避免3D旋转引发的锯齿闪烁
|
|
125
|
+
will-change: transform; // 提示浏览器优化旋转
|
|
126
|
+
}
|
|
127
|
+
// 参会者视频旋转按钮(与 LiveMultipleMeeting 保持一致)
|
|
128
|
+
.rotate-icon {
|
|
129
|
+
visibility: hidden; // 默认隐藏,悬浮显示
|
|
130
|
+
cursor: pointer;
|
|
131
|
+
position: absolute;
|
|
132
|
+
top: 10px;
|
|
133
|
+
left: 10px;
|
|
134
|
+
z-index: 50;
|
|
135
|
+
aspect-ratio: 1/1;
|
|
136
|
+
width: auto;
|
|
137
|
+
height: 3%;
|
|
138
|
+
min-height: 30px;
|
|
139
|
+
&1 {
|
|
140
|
+
background: var(--rotate-icon1) no-repeat center / 100% 100%;
|
|
141
|
+
}
|
|
142
|
+
&2 {
|
|
143
|
+
background: var(--rotate-icon2) no-repeat center / 100% 100%;
|
|
144
|
+
}
|
|
145
|
+
&3 {
|
|
146
|
+
background: var(--rotate-icon3) no-repeat center / 100% 100%;
|
|
147
|
+
}
|
|
148
|
+
&4 {
|
|
149
|
+
background: var(--rotate-icon4) no-repeat center / 100% 100%;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// 悬浮显示旋转按钮
|
|
153
|
+
&:hover {
|
|
154
|
+
.rotate-icon {
|
|
155
|
+
visibility: visible;
|
|
156
|
+
}
|
|
122
157
|
}
|
|
123
158
|
.describe {
|
|
124
159
|
position: absolute;
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
<div class="video-contain">
|
|
20
20
|
<div class="avatar"></div>
|
|
21
21
|
<video id="video-ele" class="video-ele" muted autoplay v-show="cameraStatus"></video>
|
|
22
|
+
<div class="mirror-switch" @click="setMirror">
|
|
23
|
+
<img class="mirror-switch-icon" src="../../assets/image/common/mirror.png" />
|
|
24
|
+
<span>镜像</span>
|
|
25
|
+
</div>
|
|
22
26
|
</div>
|
|
23
27
|
<div class="tool-bar">
|
|
24
28
|
<div class="tool-bar-group">
|
|
@@ -124,6 +128,7 @@ export default {
|
|
|
124
128
|
videoSelectShow: false,
|
|
125
129
|
outputSelectShow: false,
|
|
126
130
|
allTracks: [],
|
|
131
|
+
isMirror: false,
|
|
127
132
|
audioDevices: [],
|
|
128
133
|
outputDevices: [],
|
|
129
134
|
videoDevices: [],
|
|
@@ -150,16 +155,16 @@ export default {
|
|
|
150
155
|
if (newVal) {
|
|
151
156
|
constrict = {
|
|
152
157
|
audio: this.activeDevice?.audioInputDevice
|
|
153
|
-
? { deviceId: this.activeDevice.audioInputDevice }
|
|
158
|
+
? { deviceId: { exact: this.activeDevice.audioInputDevice } }
|
|
154
159
|
: true,
|
|
155
160
|
video: this.activeDevice?.videoDevice
|
|
156
|
-
? { deviceId: this.activeDevice.videoDevice }
|
|
161
|
+
? { deviceId: { exact: this.activeDevice.videoDevice } }
|
|
157
162
|
: { facingMode: this.curFacingMode },
|
|
158
163
|
};
|
|
159
164
|
} else {
|
|
160
165
|
constrict = {
|
|
161
166
|
audio: this.activeDevice?.audioInputDevice
|
|
162
|
-
? { deviceId: this.activeDevice.audioInputDevice }
|
|
167
|
+
? { deviceId: { exact: this.activeDevice.audioInputDevice } }
|
|
163
168
|
: true,
|
|
164
169
|
video: false,
|
|
165
170
|
};
|
|
@@ -167,6 +172,33 @@ export default {
|
|
|
167
172
|
await this.getDefaultDeviceStream(constrict);
|
|
168
173
|
},
|
|
169
174
|
},
|
|
175
|
+
microphoneStatus: {
|
|
176
|
+
handler: async function (newVal) {
|
|
177
|
+
let constrict = null;
|
|
178
|
+
if (newVal) {
|
|
179
|
+
constrict = {
|
|
180
|
+
audio: this.activeDevice?.audioInputDevice
|
|
181
|
+
? { deviceId: { exact: this.activeDevice.audioInputDevice } }
|
|
182
|
+
: true,
|
|
183
|
+
video: this.cameraStatus
|
|
184
|
+
? this.activeDevice?.videoDevice
|
|
185
|
+
? { deviceId: { exact: this.activeDevice.videoDevice } }
|
|
186
|
+
: { facingMode: this.curFacingMode }
|
|
187
|
+
: false,
|
|
188
|
+
};
|
|
189
|
+
} else {
|
|
190
|
+
constrict = {
|
|
191
|
+
audio: false,
|
|
192
|
+
video: this.cameraStatus
|
|
193
|
+
? this.activeDevice?.videoDevice
|
|
194
|
+
? { deviceId: { exact: this.activeDevice.videoDevice } }
|
|
195
|
+
: { facingMode: this.curFacingMode }
|
|
196
|
+
: false,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
await this.getDefaultDeviceStream(constrict);
|
|
200
|
+
},
|
|
201
|
+
},
|
|
170
202
|
},
|
|
171
203
|
mounted() {
|
|
172
204
|
this.init();
|
|
@@ -198,6 +230,7 @@ export default {
|
|
|
198
230
|
this.showMessageInstance.message("error", "会议名称不能超过20个字符");
|
|
199
231
|
return;
|
|
200
232
|
}
|
|
233
|
+
this.streamPause();
|
|
201
234
|
this.$emit("launchMeeting", {
|
|
202
235
|
roomName: this.roomName,
|
|
203
236
|
cameraStatus: this.cameraStatus,
|
|
@@ -206,6 +239,7 @@ export default {
|
|
|
206
239
|
audioInputDevice: this.activeDevice.audioInputDevice,
|
|
207
240
|
audioOutputDevice: this.activeDevice.audioOutputDevice,
|
|
208
241
|
videoDevice: this.activeDevice.videoDevice,
|
|
242
|
+
isMirror: this.isMirror,
|
|
209
243
|
});
|
|
210
244
|
this.$emit("close");
|
|
211
245
|
},
|
|
@@ -215,6 +249,7 @@ export default {
|
|
|
215
249
|
this.showMessageInstance.message("error", "会议ID不能为空");
|
|
216
250
|
return;
|
|
217
251
|
}
|
|
252
|
+
this.streamPause();
|
|
218
253
|
this.$emit("joinMeeting", {
|
|
219
254
|
roomNum: this.roomNum,
|
|
220
255
|
cameraStatus: this.cameraStatus,
|
|
@@ -223,10 +258,23 @@ export default {
|
|
|
223
258
|
audioInputDevice: this.activeDevice.audioInputDevice,
|
|
224
259
|
audioOutputDevice: this.activeDevice.audioOutputDevice,
|
|
225
260
|
videoDevice: this.activeDevice.videoDevice,
|
|
261
|
+
isMirror: this.isMirror,
|
|
226
262
|
});
|
|
227
263
|
this.$emit("close");
|
|
228
264
|
},
|
|
229
265
|
|
|
266
|
+
setMirror() {
|
|
267
|
+
this.isMirror = !this.isMirror;
|
|
268
|
+
this.$nextTick(() => {
|
|
269
|
+
let videoDom = document.getElementById("video-ele");
|
|
270
|
+
if (videoDom) {
|
|
271
|
+
this.isMirror
|
|
272
|
+
? (videoDom.style.transform = "rotateY(180deg)")
|
|
273
|
+
: (videoDom.style.transform = "rotateY(0deg)");
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
},
|
|
277
|
+
|
|
230
278
|
async getDeviceList() {
|
|
231
279
|
try {
|
|
232
280
|
const { audioDevices, videoDevices, outputDevices } = await LiveClient.getDeviceList();
|
|
@@ -312,11 +360,16 @@ export default {
|
|
|
312
360
|
}
|
|
313
361
|
},
|
|
314
362
|
|
|
363
|
+
sleep(ms) {
|
|
364
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
365
|
+
},
|
|
366
|
+
|
|
315
367
|
async getDefaultDeviceStream(constrict) {
|
|
316
368
|
try {
|
|
369
|
+
this.clearAllTrack();
|
|
370
|
+
await this.sleep(500);
|
|
317
371
|
const stream = await navigator.mediaDevices.getUserMedia(constrict);
|
|
318
372
|
let videoEle = document.querySelector("#video-ele");
|
|
319
|
-
this.clearAllTrack();
|
|
320
373
|
if (videoEle) {
|
|
321
374
|
this.allTracks = stream.getTracks();
|
|
322
375
|
this.getActiveDeviceId(stream);
|
|
@@ -325,8 +378,10 @@ export default {
|
|
|
325
378
|
videoEle.play();
|
|
326
379
|
};
|
|
327
380
|
}
|
|
381
|
+
return stream;
|
|
328
382
|
} catch (err) {
|
|
329
|
-
|
|
383
|
+
console.error("Failed to get media stream:", err);
|
|
384
|
+
this.showMessageInstance.message("error", "获取设备媒体流失败,请检查浏览器权限状态或者设备是否可用");
|
|
330
385
|
}
|
|
331
386
|
},
|
|
332
387
|
|
|
@@ -343,12 +398,16 @@ export default {
|
|
|
343
398
|
async initVideoStream() {
|
|
344
399
|
this.curFacingMode = "user";
|
|
345
400
|
const constrict = {
|
|
346
|
-
audio: this.microphoneStatus
|
|
401
|
+
audio: this.microphoneStatus
|
|
402
|
+
? this.activeDevice?.audioInputDevice
|
|
403
|
+
? { deviceId: { exact: this.activeDevice.audioInputDevice } }
|
|
404
|
+
: true
|
|
405
|
+
: false,
|
|
347
406
|
video: this.cameraStatus
|
|
348
|
-
?
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
:
|
|
407
|
+
? this.activeDevice?.videoDevice
|
|
408
|
+
? { deviceId: { exact: this.activeDevice.videoDevice } }
|
|
409
|
+
: { facingMode: this.curFacingMode }
|
|
410
|
+
: false,
|
|
352
411
|
};
|
|
353
412
|
await this.getDefaultDeviceStream(constrict);
|
|
354
413
|
},
|
|
@@ -356,10 +415,10 @@ export default {
|
|
|
356
415
|
async handleAudioInputDeviceChoice(e) {
|
|
357
416
|
if (e && e !== this.activeDevice?.audioInputDevice) {
|
|
358
417
|
const constrict = {
|
|
359
|
-
audio: this.microphoneStatus ? { deviceId: e } : false,
|
|
418
|
+
audio: this.microphoneStatus ? { deviceId: { exact: e } } : false,
|
|
360
419
|
video: this.cameraStatus
|
|
361
420
|
? this.activeDevice?.videoDevice
|
|
362
|
-
? { deviceId: this.activeDevice.videoDevice }
|
|
421
|
+
? { deviceId: { exact: this.activeDevice.videoDevice } }
|
|
363
422
|
: { facingMode: this.curFacingMode }
|
|
364
423
|
: this.cameraStatus,
|
|
365
424
|
};
|
|
@@ -378,10 +437,10 @@ export default {
|
|
|
378
437
|
const constrict = {
|
|
379
438
|
audio: this.microphoneStatus
|
|
380
439
|
? this.activeDevice?.audioInputDevice
|
|
381
|
-
? { deviceId: this.activeDevice.audioInputDevice }
|
|
440
|
+
? { deviceId: { exact: this.activeDevice.audioInputDevice } }
|
|
382
441
|
: this.microphoneStatus
|
|
383
442
|
: this.microphoneStatus,
|
|
384
|
-
video: this.cameraStatus ? { deviceId: e } : this.cameraStatus,
|
|
443
|
+
video: this.cameraStatus ? { deviceId: { exact: e } } : this.cameraStatus,
|
|
385
444
|
};
|
|
386
445
|
await this.getDefaultDeviceStream(constrict);
|
|
387
446
|
}
|
|
@@ -493,6 +552,29 @@ export default {
|
|
|
493
552
|
top: 0;
|
|
494
553
|
object-fit: cover;
|
|
495
554
|
}
|
|
555
|
+
|
|
556
|
+
.mirror-switch {
|
|
557
|
+
cursor: pointer;
|
|
558
|
+
position: absolute;
|
|
559
|
+
top: 23px;
|
|
560
|
+
right: 17px;
|
|
561
|
+
background: rgba(28, 36, 47, 0.6);
|
|
562
|
+
border-radius: 8px 8px 8px 8px;
|
|
563
|
+
display: flex;
|
|
564
|
+
align-items: center;
|
|
565
|
+
height: 40px;
|
|
566
|
+
padding: 0 10px;
|
|
567
|
+
font-weight: 400;
|
|
568
|
+
font-size: 12px;
|
|
569
|
+
z-index: 10;
|
|
570
|
+
color: #fff;
|
|
571
|
+
|
|
572
|
+
&-icon {
|
|
573
|
+
width: 16px;
|
|
574
|
+
height: 14px;
|
|
575
|
+
margin-right: 6px;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
496
578
|
}
|
|
497
579
|
|
|
498
580
|
.tool-bar {
|