mdm-client 1.0.4 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdm-client",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "private": false,
5
5
  "main": "index.js",
6
6
  "author": {
package/src/App.vue CHANGED
@@ -66,7 +66,7 @@ export default {
66
66
  baseUrl: "https://10.2.12.103:8443",
67
67
  roomServerUrl: "wss://10.2.12.103:8443",
68
68
  token:
69
- "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJMb2dpbkluZm8iOnsibG9naW5Db2RlIjoibGl1amluYm8iLCJ1c2VyTmFtZSI6IuWImOmUpuazoiIsInRlbmFudElEIjo3OTA2MDE5OTgyNTAxMDAwLCJkZXB0VHJlZSI6Ijc5MDYwNzA4NTA5MDEwMDAvNzkwNzk1MzkyNjcwMTAwMCIsInVzZXJJRCI6NzkwNjI1NzUyOTgwMTAwMCwicm9sZUlEIjo3OTA2MDc0MzkyMDAxMDAwLCJ2ZXJzaW9uSUQiOjc5MDc5NDk3NjQyMDEwMDB9LCJwbGF0Zm9ybUlEIjowLCJwaG9uZSI6IiIsImV4cCI6MTc2MzIxNjUyNSwiaWF0IjoxNzYyNDk2NTI1fQ.FEGXEUhOfOC0nqKr9oaARnbzrqhGW8iE7WixaapiXXE",
69
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJMb2dpbkluZm8iOnsibG9naW5Db2RlIjoibGl1amluYm8iLCJ1c2VyTmFtZSI6IuWImOmUpuazoiIsInRlbmFudElEIjo3OTA2MDE5OTgyNTAxMDAwLCJkZXB0VHJlZSI6Ijc5MDYwNzA4NTA5MDEwMDAvNzkwNzk1MzkyNjcwMTAwMCIsInVzZXJJRCI6NzkwNjI1NzUyOTgwMTAwMCwicm9sZUlEIjo3OTA2MDc0MzkyMDAxMDAwLCJ2ZXJzaW9uSUQiOjc5MDc5NDk3NjQyMDEwMDB9LCJwbGF0Zm9ybUlEIjowLCJwaG9uZSI6IiIsImV4cCI6MTc2NDA5MzM3MCwiaWF0IjoxNzYzMzczMzcwfQ.sWfU8eZPDc05YIgstuI40C-lDvXZq3G30b-j9qAxWGg",
70
70
  liveClient: null,
71
71
  showMessage: null,
72
72
  };
@@ -792,6 +792,33 @@ export default {
792
792
  });
793
793
  },
794
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
+ }
795
822
  },
796
823
  created() {
797
824
  this.showMessage = new ShowMessage();
@@ -2921,7 +2948,7 @@ export default {
2921
2948
  videoDiv.appendChild(signalElm);
2922
2949
 
2923
2950
  // 构建码率显示元素
2924
- let bitrateElm = document.getElementById(`bitrate-${identity}`)
2951
+ let bitrateElm = document.getElementById(`bitrate-${videoItem.identity}`)
2925
2952
  if (!bitrateElm) {
2926
2953
  bitrateElm = document.createElement('div')
2927
2954
  bitrateElm.id = `bitrate-${videoItem.identity}`
@@ -3074,6 +3101,11 @@ export default {
3074
3101
  if (videoItem?.audioTrack) {
3075
3102
  videoItem.audioTrack.detach();
3076
3103
  }
3104
+ if (videoItem?.screenShareAudioTrack) {
3105
+ try {
3106
+ videoItem.screenShareAudioTrack.detach()
3107
+ } catch (e) {}
3108
+ }
3077
3109
  return;
3078
3110
  }
3079
3111
  // 与会者屏幕共享布局切换
@@ -3136,6 +3168,32 @@ export default {
3136
3168
  if (videoItem?.audioTrack) {
3137
3169
  videoItem.audioTrack.attach(videoElm);
3138
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
+ }
3139
3197
  }
3140
3198
  // 更新与会者名称
3141
3199
  let nameDom = document.getElementById(videoItem.identity);
@@ -4132,8 +4190,6 @@ export default {
4132
4190
  }
4133
4191
  },
4134
4192
  async chooseVideoDevice(e) {
4135
- this.dispatchLocalTrack('video')
4136
- await this.sleep(100)
4137
4193
  await this.changeActiveDevice("videoinput", e);
4138
4194
  this.videoSelectShow = false;
4139
4195
  },
@@ -4157,7 +4213,50 @@ export default {
4157
4213
  await this.liveClient.changeScreenShareStatus(this.screenShareCaptureOption);
4158
4214
  },
4159
4215
  screenShareOptionChange(e) {
4160
- console.log("屏幕共享设置变更", e);
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
+ }
4161
4260
  },
4162
4261
  stopScreenShare() {
4163
4262
  this.changeScreenShareStatus(this.screenShareCaptureOption);
@@ -5515,7 +5614,7 @@ export default {
5515
5614
  align-items: center;
5516
5615
  justify-content: space-between;
5517
5616
  position: absolute;
5518
- z-index: 2100;
5617
+ z-index: 4100;
5519
5618
  bottom: -66px;
5520
5619
  left: 0;
5521
5620
  transition: all 0.2s ease;
@@ -23,6 +23,7 @@
23
23
  <teleport to="body">
24
24
  <LivePointMeeting
25
25
  v-if="isMeetingDialogShow"
26
+ ref="livePointMeetingRef"
26
27
  :joinType="pointMeetingData.joinType"
27
28
  :meetingNum="pointMeetingData.meetingNum"
28
29
  :meetingType="pointMeetingData.meetingType"
@@ -36,9 +37,18 @@
36
37
  @miniLinkSend="miniLinkSend"
37
38
  @meetingStarted="isInMeeting = true"
38
39
  @meetingEnded="isInMeeting = false"
40
+ @pointMeetingMinum="openMiniVideoDialog"
39
41
  >
40
42
  </LivePointMeeting>
41
43
  </teleport>
44
+ <teleport to="body">
45
+ <minium-video-dialog
46
+ v-if="isMiniVideoDialogShow"
47
+ :trackData="trackData"
48
+ @resetMultiDialog="resetPointDialog"
49
+ @miniVideoDialogClose="closeMiniVideoDialog"
50
+ ></minium-video-dialog>
51
+ </teleport>
42
52
  </div>
43
53
  </template>
44
54
 
@@ -48,6 +58,7 @@ import { mittBus, ShowMessage } from "../../utils/index.js";
48
58
  import LiveCallBoard from "../LiveCallBoard/LiveCallBoard.vue";
49
59
  import LivePointMeeting from "../LivePointMeeting/LivePointMeeting.vue";
50
60
  import LiveInviteReceive from "../LiveInviteReceive/LiveInviteReceive.vue";
61
+ import MiniumVideoDialog from "../MiniumVideoDialog/MiniumVideoDialog.vue";
51
62
  import Teleport from 'vue2-teleport'
52
63
 
53
64
  export default {
@@ -56,6 +67,7 @@ export default {
56
67
  LiveCallBoard,
57
68
  LivePointMeeting,
58
69
  LiveInviteReceive,
70
+ MiniumVideoDialog,
59
71
  Teleport
60
72
  },
61
73
  props: {
@@ -92,6 +104,8 @@ export default {
92
104
  isInMeeting: false,
93
105
  liveClient: null,
94
106
  showMessage: null,
107
+ isMiniVideoDialogShow: false,
108
+ trackData: {}
95
109
  };
96
110
  },
97
111
  computed: {
@@ -198,6 +212,17 @@ export default {
198
212
  meetingDialogClose() {
199
213
  this.isMeetingDialogShow = false;
200
214
  },
215
+ openMiniVideoDialog(e) {
216
+ this.trackData = e ?? {}
217
+ this.isMiniVideoDialogShow = true
218
+ },
219
+ resetPointDialog() {
220
+ this.isMiniVideoDialogShow = false
221
+ this.$refs.livePointMeetingRef && this.$refs.livePointMeetingRef.resetDialog()
222
+ },
223
+ closeMiniVideoDialog() {
224
+ this.isMiniVideoDialogShow = false
225
+ },
201
226
  handleLiveClientInitSuccess() {
202
227
  this.liveClient = window["liveClient"];
203
228
  }
@@ -1,12 +1,14 @@
1
1
  <template>
2
- <div ref="rootElm" class="point-meeting" id="point-meeting" :style="{ resize: 'both' }">
2
+ <div ref="roomElm" class="point-meeting" id="point-meeting" :style="{ resize: 'both' }">
3
3
  <div class="point-meeting-top" v-drag="'point-meeting'">
4
4
  <div class="top-group">
5
5
  <div class="duration-icon"></div>
6
6
  <span>{{ meetingDuration }}</span>
7
7
  </div>
8
8
  <div class="top-group">
9
+ <div class="minimize-btn" @click="minumDialog"></div>
9
10
  <div class="full-screen-btn" @click="switchComponentFullScreen"></div>
11
+ <div class="close-btn" @click="confirmClose"></div>
10
12
  </div>
11
13
  </div>
12
14
  <div class="point-meeting-wrapper">
@@ -78,6 +80,7 @@
78
80
 
79
81
  <script>
80
82
  import { calculateTime, ShowMessage } from "../../utils/index.js";
83
+ import { MessageBox } from "element-ui";
81
84
 
82
85
  export default {
83
86
  name: "LivePointMeeting",
@@ -591,13 +594,63 @@ export default {
591
594
  closeMeetingDialog() {
592
595
  this.$emit("meetingDialogClose");
593
596
  },
597
+ // 最小化窗口: 复用 multi 会议的最小化逻辑,向父组件抛出 pointMeetingMinum 事件
598
+ minumDialog() {
599
+ if (!this.isInMeeting) {
600
+ this.showMessage.message('error', '请先进入会议')
601
+ return
602
+ }
603
+ if (!this.localIdentity) {
604
+ this.showMessage.message('error', '获取本地与会者信息失败')
605
+ return
606
+ }
607
+ // 查找本地与会者渲染数据(视频或音频)
608
+ let localVideoTrack = null
609
+ if (this.liveClient?.roomMode === 'auto') {
610
+ const idx = this.participants.findIndex(item => item.identity === this.localIdentity)
611
+ if (idx > -1) {
612
+ localVideoTrack = this.participants[idx]?.videoTrack || null
613
+ }
614
+ }
615
+ // 通知父组件打开最小化窗口
616
+ this.$emit('pointMeetingMinum', {
617
+ identity: this.localIdentity,
618
+ name: this.localName,
619
+ videoTrack: this.liveClient?.roomMode === 'auto' ? localVideoTrack : null,
620
+ })
621
+ // 隐藏当前窗口(保持状态以便恢复)
622
+ this.$refs.roomElm && (this.$refs.roomElm.style.visibility = 'hidden')
623
+ },
624
+ // 还原窗口(供父组件调用)
625
+ resetDialog() {
626
+ this.$refs.roomElm && (this.$refs.roomElm.style.visibility = 'visible')
627
+ },
594
628
  async leaveRoom() {
629
+ if (!this.isInMeeting) {
630
+ this.closeMeetingDialog();
631
+ return
632
+ }
595
633
  if (this.liveClient) {
596
634
  await this.liveClient.leaveRoom();
597
635
  }
598
636
  // 重置房间连接处理标志
599
637
  this.isRoomConnectedHandled = false;
600
638
  },
639
+ confirmClose() {
640
+ if(!this.isInMeeting) {
641
+ this.closeMeetingDialog();
642
+ return;
643
+ }
644
+ MessageBox.confirm('点击确定后会议将被解散,您确定要这么做么?', '警告', {
645
+ confirmButtonText: '确定',
646
+ cancelButtonText: '取消',
647
+ type: 'warning',
648
+ }).then(() => {
649
+ this.leaveRoom();
650
+ }).catch(() => {
651
+ // 取消操作
652
+ });
653
+ },
601
654
  // 切换组件全屏状态
602
655
  async switchComponentFullScreen(e) {
603
656
  if (
@@ -1151,6 +1204,13 @@ export default {
1151
1204
  display: flex;
1152
1205
  align-items: center;
1153
1206
 
1207
+ .minimize-btn {
1208
+ cursor: pointer;
1209
+ width: 16px;
1210
+ height: 16px;
1211
+ margin-right: 12px;
1212
+ background: url('../../assets/image/screenBlue/meeting_slide_small_icon.png') no-repeat center / 100% 100%;
1213
+ }
1154
1214
  .duration-icon {
1155
1215
  width: 15px;
1156
1216
  height: 15px;
@@ -1171,8 +1231,9 @@ export default {
1171
1231
 
1172
1232
  .close-btn {
1173
1233
  cursor: pointer;
1174
- width: 20px;
1175
- height: 20px;
1234
+ width: 16px;
1235
+ height: 16px;
1236
+ margin-left: 12px;
1176
1237
  background: var(--close-icon) no-repeat center / 100% 100%;
1177
1238
  }
1178
1239
  }