@zhangxuejing123./sip-phone-sdk 0.0.0

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.
Files changed (49) hide show
  1. package/README.md +24 -0
  2. package/babel.config.js +5 -0
  3. package/dist/demo.html +21 -0
  4. package/dist/media/outgoing-call2.b8558579.mp3 +0 -0
  5. package/dist/sip-phone-sdk.common.js +42439 -0
  6. package/dist/sip-phone-sdk.css +5 -0
  7. package/dist/sip-phone-sdk.umd.js +42449 -0
  8. package/dist/sip-phone-sdk.umd.min.js +14 -0
  9. package/jsconfig.json +19 -0
  10. package/package-lock.json +15671 -0
  11. package/package.json +99 -0
  12. package/public/SIP_MIX_WEB.js +50 -0
  13. package/public/en.js +24 -0
  14. package/public/favicon.ico +0 -0
  15. package/public/index.html +32 -0
  16. package/public/index.test.html +31 -0
  17. package/public/zh.js +24 -0
  18. package/src/.DS_Store +0 -0
  19. package/src/App.vue +136 -0
  20. package/src/assets/.DS_Store +0 -0
  21. package/src/assets/adudio-open.png +0 -0
  22. package/src/assets/audio-close.png +0 -0
  23. package/src/assets/call-down.png +0 -0
  24. package/src/assets/camera-close.png +0 -0
  25. package/src/assets/camera-open.png +0 -0
  26. package/src/assets/icon_Recording_Fill_Red_Active.svg +15 -0
  27. package/src/assets/icon_Recording_Fill_Red_Inactive.svg +15 -0
  28. package/src/assets/img_Avatar_User.png +0 -0
  29. package/src/assets/normal-logo.png +0 -0
  30. package/src/assets/outgoing-call2.mp3 +0 -0
  31. package/src/components/DialPanelMini.vue +179 -0
  32. package/src/components/MobilePhone copy 2.vue +1173 -0
  33. package/src/components/MobilePhone copy.vue +1046 -0
  34. package/src/components/MobilePhone.vue +1157 -0
  35. package/src/components/index.js +36 -0
  36. package/src/components/vuetify.css +29663 -0
  37. package/src/index.js +100 -0
  38. package/src/lang/en.js +24 -0
  39. package/src/lang/zh.js +24 -0
  40. package/src/libs/SIP_MIX_WEB.js +50 -0
  41. package/src/libs/en.js +24 -0
  42. package/src/libs/tool.js +312 -0
  43. package/src/libs/zh.js +24 -0
  44. package/src/main.js +24 -0
  45. package/src/plugins/vuetify.js +11 -0
  46. package/src/utils/rem.js +22 -0
  47. package/vue.config copy 2.js +39 -0
  48. package/vue.config copy.js +11 -0
  49. package/vue.config.js +39 -0
@@ -0,0 +1,1173 @@
1
+ <!-- 主模板:视频通话界面 -->
2
+ <template>
3
+ <!-- 主容器:使用flex垂直布局,内容居中对齐 -->
4
+ <div class="d-flex flex-column align-center justify-space-between sip-call-main">
5
+ <!-- 顶部提示信息 -->
6
+ <div style="position: absolute; top: 0; left: 0; background: transparent; color: #FFFFFF; width: 100%; z-index: 9999; height: 0; padding-top: 10px; padding-left: 10px;">
7
+ <!-- {{ prompt }} -->
8
+ </div>
9
+ <!-- 位置信息显示区域 -->
10
+ <div class="location-wrapper" v-if="showLocation">
11
+ <!-- 显示经纬度和海拔信息 -->
12
+ <p>LAT: {{latitude}}</p>
13
+ <p>LNG: {{longitude}}</p>
14
+ <p>ALT: {{altitude}}</p>
15
+ </div>
16
+ <!-- 通话信息显示区域 -->
17
+ <div class="d-flex flex-column align-center" v-if="!miniMode && !showDialPad" style="margin-top:160px;">
18
+ <!-- 应用logo -->
19
+ <!-- <img class="elevation-4" alt="logo" :src="require('@/assets/normal-logo.png')" width="72" height="72" style="border-radius: 50%;"/> -->
20
+ <img class="elevation-4" alt="logo" src="https://kefu-gm-jc-dat.boc-samsunglife.cn/static/normal-logo.png" width="72" height="72" style="border-radius: 50%;"/>
21
+ <!-- 通话状态提示 -->
22
+ <p class="mt-6 mb-0" style="font-size: 16px; color: #FFFFFF;">{{callPrompt}}</p>
23
+ </div>
24
+
25
+ <!-- 功能按钮区域 -->
26
+ <div class="pr-10 pl-10 btn-group" v-if="!miniMode && !showDialPad">
27
+ <v-row>
28
+ <v-col cols="12" class="text-center">
29
+ <!-- 通话计时器 -->
30
+ <div
31
+ v-show="isCalling && showHangup" style="font-size: 14px;margin-top: -50px; color: #FFFFFF;">
32
+ 视频时长 {{duration}}
33
+ </div>
34
+ </v-col>
35
+ </v-row>
36
+ <!-- 第一行按钮组 -->
37
+ <v-row class="d-flex justify-space-around mb-10">
38
+ <!-- 取消按钮 - 用于微信公众号调用的场景 -->
39
+ <v-btn
40
+ style="opacity: 1; background: transparent; box-shadow: none; position: relative; margin-top: -200px;"
41
+ v-show="!isCalling && !needDial"
42
+ @click="onHangupBtnClick"
43
+ >
44
+ <!-- <img :src="require('@/assets/call-down.png')" alt="hangup" width="80" height="80"> -->
45
+ <img src="https://kefu-gm-jc-dat.boc-samsunglife.cn/static/call-down.png" alt="hangup" width="80" height="80">
46
+
47
+ <span style="position: absolute; top: 78px; color: #FFFFFF;">取消</span>
48
+ </v-btn>
49
+
50
+ <!-- 原拨号按钮已注释,改为由微信公众号调用 -->
51
+ <!-- <v-btn
52
+ large
53
+ fab
54
+ color="#FFFFFF"
55
+ v-show="!isCalling && !needDial"
56
+ @click="onMakeCallBtnClick"
57
+ >
58
+ <v-icon>mdi-phone</v-icon>
59
+ <span class="btn-label">拨号</span>
60
+ </v-btn> -->
61
+
62
+ <!-- 麦克风静音/取消静音按钮 -->
63
+ <!-- 当正在通话且显示挂断按钮时显示 -->
64
+ <!-- 点击时触发onMuteBtnClick方法,传入'audio'参数 -->
65
+ <v-btn
66
+ large
67
+ v-show="isCalling && showHangup"
68
+ @click="onMuteBtnClick('audio')"
69
+ class="custom-mute-btn"
70
+ >
71
+ <!-- 静音状态显示开启的麦克风图标 -->
72
+ <!-- <img v-if="!isAudioMute" :src="require('@/assets/adudio-open.png')" width="55" height="80">
73
+ <img v-else :src="require('@/assets/audio-close.png')" width="55" height="80"> -->
74
+ <img v-if="!isAudioMute" src="https://kefu-gm-jc-dat.boc-samsunglife.cn/static/adudio-open.png" width="55" height="80">
75
+ <img v-else src="https://kefu-gm-jc-dat.boc-samsunglife.cn/static/adudio-close.png" width="55" height="80">
76
+ <!-- 显示当前状态的文字提示 -->
77
+ <!-- <span class="btn-label">{{ isAudioMute ? '取消静音' : '静音' }}</span> -->
78
+ </v-btn>
79
+
80
+ <!-- 摄像头开关按钮 -->
81
+ <v-btn
82
+ large
83
+ v-show="isCalling && showHangup"
84
+ @click="onMuteBtnClick('video')"
85
+ class="custom-mute-btn"
86
+ >
87
+ <!-- <img v-if="!isVideoMute" :src="require('@/assets/camera-open.png')" alt="mic-on" width="55" height="80">
88
+ <img v-else :src="require('@/assets/camera-close.png')" alt="mic-off" width="55" height="80"> -->
89
+ <img v-if="!isVideoMute" src="https://kefu-gm-jc-dat.boc-samsunglife.cn/static/camera-open.png" alt="mic-on" width="55" height="80">
90
+ <img v-else src="https://kefu-gm-jc-dat.boc-samsunglife.cn/static/camera-close.png" alt="mic-off" width="55" height="80">
91
+ </v-btn>
92
+
93
+ <!-- 显示/隐藏本地视频按钮 -->
94
+ <!-- <v-btn
95
+ style="opacity: .6"
96
+ color="blue-grey"
97
+ fab
98
+ large
99
+ dark
100
+ v-show="isCalling && showHangup"
101
+ @click="onShowLocalVideoBtnClick"
102
+ >
103
+ <v-icon v-if="localVideo.show">mdi-monitor</v-icon>
104
+ <v-icon v-else>mdi-monitor-multiple</v-icon>
105
+ <span class="btn-label">{{ localVideo.show ? '隐藏本地' : '显示本地' }}</span>
106
+ </v-btn> -->
107
+ </v-row>
108
+
109
+ <!-- 第二行按钮组 -->
110
+ <v-row class="d-flex justify-center mt-10" style="position: relative; width: 100%;">
111
+ <!-- 挂断按钮 -->
112
+ <v-btn
113
+ style="opacity: 1; background: transparent; box-shadow: none; position: relative;"
114
+ v-show="isCalling && showHangup"
115
+ @click="onHangupBtnClick"
116
+ >
117
+ <!-- <img :src="require('@/assets/call-down.png')" alt="hangup" width="80" height="80"> -->
118
+ <img src="https://kefu-gm-jc-dat.boc-samsunglife.cn/static/call-down.png" alt="hangup" width="80" height="80">
119
+ <span style="position: absolute; top: 73px; color: #FFFFFF;">挂断</span>
120
+ </v-btn>
121
+
122
+ <!-- 切换摄像头按钮 -->
123
+ <v-btn
124
+ style="opacity: .6; position: absolute; left: calc(50% + 80px);"
125
+ v-show="isCalling && showHangup"
126
+ @click="onSwitchCameraBtnClick"
127
+ class="transparent-btn"
128
+ >
129
+ <v-icon size="30">mdi-camera-flip</v-icon>
130
+ </v-btn>
131
+ </v-row>
132
+ </div>
133
+
134
+ <!-- 视频容器区域 -->
135
+ <v-expand-transition>
136
+ <div class="sip-call-wrapper" v-show="showVideoCt" @click="onSipCallClick">
137
+ <!-- 远程视频窗口 -->
138
+ <vue-draggable-resizable
139
+ v-show="remoteVideo.show"
140
+ :w="remoteVideo.w"
141
+ :h="remoteVideo.h"
142
+ :x="remoteVideo.x"
143
+ :y="remoteVideo.y"
144
+ :z="remoteVideo.z"
145
+ :draggable="remoteVideo.draggable"
146
+ :handles="[]"
147
+ :parent="true"
148
+ :active="false"
149
+ class-name="elevation-6 video-panel"
150
+ ref="remoteVideoWrapper"
151
+ @mousedown.native="onLocalVideoDragCallback($event, 'remoteVideo')"
152
+ >
153
+ <v-btn v-if="miniMode" class="video-normal-mode" dark text x-small width="24" height="24" min-width="24" @click="onChangeVideoNormalModeBtnClick">
154
+ <v-icon>mdi-arrow-bottom-right-bold-box</v-icon>
155
+ </v-btn>
156
+ <video
157
+ v-show="remoteVideo.show"
158
+ autoplay="autoplay"
159
+ webkit-playsinline="true"
160
+ playsinline="true"
161
+ x-webkit-airplay="true"
162
+ x5-video-player-type="h5"
163
+ x5-video-orientation="h5"
164
+ ref="remoteVideo"
165
+ class="sip-call-video"
166
+ style="object-fit: contain;"
167
+ controls
168
+ ></video>
169
+ </vue-draggable-resizable>
170
+ <!-- 本地视频窗口 -->
171
+ <vue-draggable-resizable
172
+ v-show="localVideo.show"
173
+ :w="localVideo.w"
174
+ :h="localVideo.h"
175
+ :x="localVideo.x"
176
+ :y="localVideo.y"
177
+ :z="localVideo.z"
178
+ :draggable="localVideo.draggable"
179
+ :handles="[]"
180
+ :parent="true"
181
+ :active="false"
182
+ class-name="elevation-6 video-panel"
183
+ ref="localVideoWrapper"
184
+ @mousedown.native="onLocalVideoDragCallback($event, 'localVideo')"
185
+ >
186
+ <video
187
+ v-show="localVideo.show"
188
+ autoplay="autoplay"
189
+ muted
190
+ webkit-playsinline="true"
191
+ playsinline="true"
192
+ x-webkit-airplay="true"
193
+ x5-video-player-type="h5"
194
+ x5-video-orientation="h5"
195
+ ref="localVideo"
196
+ class="sip-call-video"
197
+ controls
198
+ ></video>
199
+ </vue-draggable-resizable>
200
+
201
+ <!-- 视频控制区域 -->
202
+ <!-- <template v-if="!miniMode">
203
+ 通话计时器
204
+ <span class="video-timer">
205
+ <img :src="require('@/assets/icon_Recording_Fill_Red_Active.svg')" alt="icon" width="24" height="24" class="mr-2">
206
+ {{duration}}
207
+ </span>
208
+
209
+ 全屏切换按钮
210
+ <v-btn class="camera-switch-btn" dark text x-small width="24" height="24" min-width="24" @click="onChangeVideoFullScreenBtnClick">
211
+ <v-icon>mdi-box-shadow</v-icon>
212
+ <span class="btn-label">全屏</span>
213
+ </v-btn>
214
+ </template> -->
215
+
216
+ </div>
217
+ </v-expand-transition>
218
+
219
+ <!-- 拨号盘组件 -->
220
+ <v-expand-transition>
221
+ <dial-panel-mini
222
+ :isCalling="isCalling"
223
+ v-show="showDialPad"
224
+ @makeCall="onDialPadMakeCallBtnClick"
225
+ @close="showDialPad=false"
226
+ @dtmf="onDialPadDtmfBtnClick"
227
+ @hangup="onHangupBtnClick"
228
+ ref="dialPanelMini"
229
+ />
230
+ </v-expand-transition>
231
+
232
+ <!-- 音频播放器(用于播放提示音) -->
233
+ <v-bottom-sheet v-model="showAudioPlayer" hide-overlay>
234
+ <audio :src="require('@/assets/outgoing-call2.mp3')" loop ref="audioPlayer"></audio>
235
+ </v-bottom-sheet>
236
+
237
+ <!-- 提示信息弹窗 -->
238
+ <v-snackbar v-model="showSnackbar" :timeout="2000">
239
+ {{snackbarText}}
240
+ </v-snackbar>
241
+ </div>
242
+ </template>
243
+
244
+ <script>
245
+ import 'webrtc-adapter'
246
+ import SipMix from '@/libs/SIP_MIX_WEB'
247
+ import DialPanelMini from '@/components/DialPanelMini'
248
+ import VueDraggableResizable from 'vue-draggable-resizable'
249
+ import { closeStream } from '@/libs/tool'
250
+
251
+
252
+ export default {
253
+ name: 'MobilePhone',
254
+ api: null,
255
+ locateHandler: -1,
256
+ sendPositionHandler: -1,
257
+ clientHeight: 0,
258
+ clientWidth: 0,
259
+
260
+ components: {
261
+ VueDraggableResizable,
262
+ DialPanelMini
263
+ },
264
+
265
+ props: {
266
+ calledNum: { // 被叫
267
+ type: String,
268
+ default: ''
269
+ },
270
+
271
+ callingNum: { // 主叫
272
+ type: String,
273
+ default: 'H5User',
274
+ validator: (v) => !!v
275
+ },
276
+
277
+ callType: {
278
+ type: String,
279
+ default: 'videoOnly',
280
+ validator: v => ['audio', 'video', 'videoOnly'].indexOf(v) > -1
281
+ },
282
+
283
+ debug: {
284
+ type: Boolean,
285
+ default: false
286
+ },
287
+
288
+ needDial: {
289
+ type: Boolean,
290
+ default: false
291
+ },
292
+
293
+ autoHideLocalVideo: {
294
+ type: Boolean,
295
+ default: false
296
+ },
297
+
298
+ showLocation: {
299
+ type: Boolean,
300
+ default: true
301
+ },
302
+
303
+ callData: {
304
+ type: String,
305
+ default: ''
306
+ }
307
+ },
308
+
309
+ data: function () {
310
+ return {
311
+ prompt: `${this.$t('serverConnecting')}...`,
312
+ callPrompt: this.$t('pleaseBePatient'),
313
+
314
+ isCalling: false,
315
+ showHangup: false,
316
+ showAudioPlayer: true,
317
+ showVideoCt: false,
318
+ showDialPad: false,
319
+ showSnackbar: false,
320
+
321
+ miniMode: false,
322
+ dialNum: '',
323
+
324
+ controls: '',
325
+
326
+ isAudioMute: false,
327
+ isVideoMute: false,
328
+ cameraType: 'front',
329
+ videoInputList: [],
330
+ videoInputIndex: 0,
331
+
332
+ localVideo: {
333
+ w: 1,
334
+ h: 1,
335
+ x: 0,
336
+ y: 0,
337
+ z: 1000,
338
+ show: false,
339
+ draggable: true
340
+ },
341
+
342
+ remoteVideo: {
343
+ w: 1,
344
+ h: 1,
345
+ x: 0,
346
+ y: 0,
347
+ z: 1000,
348
+ show: false,
349
+ draggable: true
350
+ },
351
+
352
+ connected: false,
353
+ callDuration: 0,
354
+
355
+ durationHandler: -1,
356
+ snackbarText: '',
357
+
358
+ callId: '',
359
+ lastCallId: '',
360
+ queueCount: 0,
361
+
362
+ latitude: 0,
363
+ longitude: 0,
364
+ altitude: 0
365
+ }
366
+ },
367
+
368
+ watch: {
369
+ isCalling (is) {
370
+ if (is) {
371
+ this.startDurationCount()
372
+ } else {
373
+ this.stopDurationCount()
374
+ }
375
+ },
376
+ callId (id) {
377
+ this.lastCallId = id || this.lastCallId
378
+ },
379
+ prompt (v) {
380
+ console.info('Prompt', v)
381
+ }
382
+ },
383
+
384
+ computed: {
385
+ duration () {
386
+ let s = this.callDuration % 60
387
+ s = s < 10 ? `0${s}` : `${s}`
388
+ let m = parseInt(this.callDuration / 60)
389
+ m = m < 10 ? `0${m}` : `${m}`
390
+ return `${m}:${s}`
391
+ }
392
+ },
393
+
394
+ created() {
395
+ setTimeout(() => {
396
+ // this.onMakeCallBtnClick();
397
+ // 处理微信公众号传递的参数
398
+ // this.handleWechatParams();
399
+ }, 2000);
400
+ },
401
+
402
+ methods: {
403
+ // 处理微信公众号参数
404
+ handleWechatParams(val) {
405
+ // ===== 测试代码开始 =====
406
+ // 测试用的URL参数字符串 - 模拟微信公众号传递的参数
407
+ // const testParams = 'debug=true&calledNum=0000021000021001&av=video&callData={"calledNum":"0000021000021001","videoRecordId":"ae756872ff3a122817848719605aa5cb"}';
408
+ // // 解析测试参数
409
+ // const urlParams = new URLSearchParams(testParams);
410
+ // // 获取参数值
411
+ // const calledNum = urlParams.get('calledNum');
412
+ // const callDataStr = urlParams.get('callData');
413
+ console.log('测试模式:使用模拟参数');
414
+
415
+ // ===== 原始代码(已注释) ===== // 用于微信公众号 此方式是获取url参数;父子组件传值可以使用props
416
+ // const urlParams = new URLSearchParams(window.location.search);
417
+ // const calledNum = urlParams.get('calledNum');
418
+ // const callDataStr = urlParams.get('callData');
419
+ // const urlParams = val;
420
+ const calledNum = val.calledNum;
421
+ const callDataStr = val.callData;
422
+
423
+ console.log('测试模式:使用模拟参数');
424
+
425
+ if (calledNum) {
426
+ // 存储被叫号码
427
+ this.setDialNum(calledNum);
428
+
429
+ // 如果有callData,解析并存储
430
+ let callData = null;
431
+ try {
432
+ callData = callDataStr ? JSON.parse(decodeURIComponent(callDataStr)) : null;
433
+ // 这里可以将callData存储到Vuex或组件的data中
434
+ if (callData) {
435
+ // 例如:this.$store.commit('setCallData', callData);
436
+ console.log('Call data received:', callData);
437
+ }
438
+ } catch (e) {
439
+ console.error('Failed to parse callData:', e);
440
+ }
441
+
442
+ // 自动发起呼叫
443
+ this.$nextTick(() => {
444
+ this.onMakeCallBtnClick();
445
+ });
446
+ }
447
+ },
448
+
449
+ _resetCmpWHLRTB ({rl =0, rt = 0, ll = 0, lt = 0} = {}) {
450
+ this.$refs.remoteVideoWrapper.left = rl
451
+ this.$refs.remoteVideoWrapper.top = rt
452
+
453
+ this.$refs.localVideoWrapper.left = ll
454
+ this.$refs.localVideoWrapper.top = lt
455
+
456
+ this.$refs.localVideoWrapper.parentWidth = this.clientWidth
457
+ this.$refs.localVideoWrapper.parentHeight = this.clientHeight
458
+ this.$refs.localVideoWrapper.right = this.clientWidth - this.localVideo.w
459
+ this.$refs.localVideoWrapper.bottom = this.clientHeight - this.localVideo.h
460
+
461
+ this.$refs.remoteVideoWrapper.parentWidth = this.clientWidth
462
+ this.$refs.remoteVideoWrapper.parentHeight = this.clientHeight
463
+ this.$refs.remoteVideoWrapper.right = this.clientWidth - this.remoteVideo.w
464
+ this.$refs.remoteVideoWrapper.bottom = this.clientHeight - this.remoteVideo.h
465
+ },
466
+
467
+ toMiniMode () {
468
+ this._resetCmpWHLRTB()
469
+ Object.assign(this.localVideo, {
470
+ show: false,
471
+ draggable: false
472
+ })
473
+ this.miniMode = true
474
+
475
+ setTimeout(() => {
476
+ Object.assign(this.remoteVideo, {
477
+ show: true,
478
+ w: parseInt(this.clientWidth / 3),
479
+ h: parseInt(this.clientHeight / 3),
480
+ x: 0,
481
+ y: 0,
482
+ z: 1100,
483
+ draggable: true
484
+ })
485
+ }, 500)
486
+ },
487
+
488
+ toNormalMode () {
489
+ this._resetCmpWHLRTB()
490
+ Object.assign(this.localVideo, {
491
+ show: true,
492
+ w: parseInt(this.clientWidth / 3),
493
+ h: parseInt(this.clientHeight / 3),
494
+ x: 0,
495
+ y: 0,
496
+ z: 1100,
497
+ draggable: false
498
+ })
499
+ this.miniMode = false
500
+
501
+ setTimeout(() => {
502
+ // this._resetCmpWHLRTB()
503
+ Object.assign(this.remoteVideo, {
504
+ show: true,
505
+ w: this.clientWidth,
506
+ h: this.clientHeight,
507
+ x: 0,
508
+ y: 0,
509
+ z: 1000,
510
+ draggable: true
511
+ })
512
+ }, 500)
513
+ },
514
+
515
+ onDialPadBtnClick () {
516
+ this.showDialPad = true
517
+ },
518
+
519
+ onDialPadMakeCallBtnClick ({dialNum}) {
520
+ this.dialNum = dialNum
521
+ this.onMakeCallBtnClick()
522
+ },
523
+
524
+ onDialPadDtmfBtnClick (v) {
525
+ this.sipMix.sendDTMF({tones: v})
526
+ },
527
+
528
+ onChangeVideoMiniModeBtnClick () {
529
+ this.toMiniMode()
530
+ },
531
+
532
+ onChangeVideoNormalModeBtnClick () {
533
+ this.toNormalMode()
534
+ },
535
+
536
+ onChangeVideoFullScreenBtnClick () {
537
+ if (this.remoteVideo.show) {
538
+ this.$refs.remoteVideoWrapper.left = 0
539
+ this.$refs.remoteVideoWrapper.top = 0
540
+
541
+ this.$refs.localVideoWrapper.left = 0
542
+ this.$refs.localVideoWrapper.top = 0
543
+
544
+ this.$nextTick(() => {
545
+ if (this.localVideo.w === this.clientWidth) {
546
+ Object.assign(this.localVideo, {
547
+ w: parseInt(this.clientWidth / 3),
548
+ h: parseInt(this.clientHeight / 3),
549
+ z: 1100,
550
+ draggable: true
551
+ })
552
+ Object.assign(this.remoteVideo, {
553
+ w: this.clientWidth,
554
+ h: this.clientHeight,
555
+ z: 1000,
556
+ draggable: false
557
+ })
558
+ } else {
559
+ Object.assign(this.localVideo, {
560
+ w: this.clientWidth,
561
+ h: this.clientHeight,
562
+ z: 1000,
563
+ draggable: false
564
+ })
565
+ Object.assign(this.remoteVideo, {
566
+ w: parseInt(this.clientWidth / 3),
567
+ h: parseInt(this.clientHeight / 3),
568
+ z: 1100,
569
+ draggable: true
570
+ })
571
+ }
572
+ })
573
+ }
574
+ },
575
+
576
+ onLocalVideoDragCallback (e, target) {
577
+ if (target === 'remoteVideo' && this.remoteVideo.w !== this.clientWidth) {
578
+ e.stopPropagation()
579
+ } else if (target === 'localVideo' && this.localVideo.w !== this.clientWidth) {
580
+ e.stopPropagation()
581
+ }
582
+ },
583
+
584
+ onSipCallClick (){
585
+ this.autoShowHangupBtn()
586
+ },
587
+
588
+ async onMakeCallBtnClick() {
589
+ if (!this.needDial) {
590
+ this.dialNum = this.calledNum
591
+ }
592
+
593
+ const params = { target: this.dialNum }
594
+ console.log('paramsparams Data', params)
595
+ console.log('this.callData Data', this.callData)
596
+ console.log('window.config.videoParams', window.config.videoParams)
597
+ if (this.callType === 'videoOnly') {
598
+ console.log('videoOnly')
599
+ params.video = Object.assign({
600
+ facingMode: 'user'
601
+ }, window.config.videoParams)
602
+ params.audio = false
603
+ } else if (this.callType === 'video') {
604
+ console.log('video')
605
+ params.video = Object.assign({
606
+ facingMode: 'user'
607
+ }, window.config.videoParams)
608
+ params.audio = true
609
+ } else {
610
+ params.video = false
611
+ params.audio = true
612
+ }
613
+
614
+ if (this.callData) {
615
+ params.callData = this.callData
616
+ console.log('Call Da1ta', this.callData)
617
+ }
618
+
619
+ // console.info('Call Params', params)
620
+ try {
621
+ await this.sipMix.call(params)
622
+ } catch (e) {
623
+ this.snackbarText = this.$t('gotCameraStreamFailed')
624
+ this.showSnackbar = true
625
+ }
626
+
627
+ this.isCalling = true
628
+ this.showHangup = true
629
+ if (this.needDial) {
630
+ this.callPrompt = `${this.$t('calling')} ${this.dialNum}`
631
+ this.showDialPad = false
632
+ } else {
633
+ // this.callPrompt = `${this.$t('calling')}...${this.$t('pleaseBePatient')}`
634
+ this.callPrompt = `${this.$t('pleaseBePatient')}`
635
+ }
636
+
637
+ },
638
+ onHangupBtnClick () {
639
+ this.sipMix.hangup();
640
+ // this.$emit('hangup-event', { imType: 0 });// 通知父组件
641
+ // 触发 hangup 事件,并传递参数给父组件
642
+ this.$emit('hangup', {
643
+ // callId: this.callId,
644
+ // duration: this.duration,
645
+ // timestamp: new Date().getTime(),
646
+ imType: 0,
647
+ });
648
+ },
649
+
650
+ onMuteBtnClick (type) {
651
+ if (type === 'audio') {
652
+ if (this.isAudioMute) {
653
+ this.sipMix.unmute({audio: true})
654
+ } else {
655
+ this.sipMix.mute({audio: true})
656
+ }
657
+ this.isAudioMute = !this.isAudioMute
658
+ } else {
659
+ if (this.isVideoMute) {
660
+ this.sipMix.unmute({video: true})
661
+ } else {
662
+ this.sipMix.mute({video: true})
663
+ }
664
+ this.isVideoMute = !this.isVideoMute
665
+ }
666
+ },
667
+
668
+
669
+ onShowLocalVideoBtnClick () {
670
+ // this.localVideo.show = !this.localVideo.show
671
+ this.localVideo.z = (this.localVideo.z === 1 ? 1100 : 1)
672
+ },
673
+
674
+ dealCallEndOrFail () {
675
+ // this.$refs.audioPlayer.currentTime = 0
676
+ this.$refs.audioPlayer.pause()
677
+ this.showVideoCt = false
678
+ this.remoteVideo.show = false
679
+ this.localVideo.show = false
680
+ this.localVideo.w = this.clientWidth
681
+ this.localVideo.h = this.clientHeight
682
+ this.isCalling = false
683
+ this.callId = ''
684
+ this.prompt = this.$t('serverConnected')
685
+
686
+ if (this.needDial) {
687
+ this.showDialPad = true
688
+ }
689
+
690
+ this.stopDurationCount()
691
+ this.toNormalMode()
692
+ this.stopSendPosition()
693
+ },
694
+
695
+ autoShowHangupBtn () {
696
+ this.showHangup = true
697
+ /* setTimeout(() => {
698
+ this.showHangup = false
699
+ }, 3000) */
700
+ },
701
+
702
+ stopDurationCount () {
703
+ this.callDuration = 0
704
+ clearInterval(this.durationHandler)
705
+ },
706
+
707
+ startDurationCount () {
708
+ this.stopDurationCount()
709
+ this.durationHandler = setInterval(() => {
710
+ this.callDuration++
711
+ }, 1000)
712
+ },
713
+
714
+ async onSwitchCameraBtnClick() {
715
+ const list = await navigator.mediaDevices.enumerateDevices()
716
+ console.warn(list)
717
+ let videoOption = {
718
+ facingMode: 'user',
719
+ }
720
+ if (this.cameraType === 'front') {
721
+ videoOption = {
722
+ facingMode: { exact: 'environment' }
723
+ }
724
+ }
725
+ Object.assign(videoOption, window.config.videoParams)
726
+
727
+ const stream = await navigator.mediaDevices.getUserMedia({
728
+ video: videoOption,
729
+ audio: false
730
+ })
731
+ if (stream) {
732
+ this.cameraType = this.cameraType === 'front' ? 'back' : 'front'
733
+ this.$refs.localVideo.srcObject = stream
734
+ this.sipMix.changeLocalVideoTrack({stream})
735
+ }
736
+ },
737
+
738
+ setDialNum (dialNum) {
739
+ this.$refs.dialPanelMini.setDialNum(dialNum)
740
+ },
741
+
742
+ async checkBrowser() {
743
+ const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true})
744
+ if (stream) {
745
+ closeStream(stream)
746
+ } else {
747
+ return Promise.reject(this.$t('cameraOrMicUnsupported'))
748
+ }
749
+
750
+
751
+ if (!window.RTCPeerConnection) {
752
+ return Promise.reject(this.$t('WebRTCUnsupported'))
753
+ }
754
+
755
+ const list = await navigator.mediaDevices.enumerateDevices()
756
+ const vList = list.filter(e => e.kind === 'videoinput')
757
+ vList.forEach(device => {
758
+ console.warn({
759
+ label: device.label,
760
+ deviceId: device.deviceId,
761
+ groupId: device.groupId
762
+ })
763
+ })
764
+
765
+ this.videoInputList = vList
766
+
767
+ return Promise.resolve(true)
768
+ },
769
+
770
+ startSip ({debug, password, register, autoDial = false}) {
771
+ const {domain, port, path} = window.config
772
+
773
+ this.sipMix = SipMix.getInstance({
774
+ domain, port,
775
+ path
776
+ })
777
+ this.sipMix.on('socketError', () => this.prompt = this.$t('connectionFailedPleaseCheckYourNetwork'))
778
+
779
+ this.sipMix.on('connecting', () => this.prompt = this.$t('serverConnected') + '...')
780
+ this.sipMix.on('connected', () => {
781
+ this.prompt = this.$t('serverConnected')
782
+ if (!register && autoDial) {
783
+ this.onMakeCallBtnClick()
784
+ }
785
+ })
786
+ this.sipMix.on('disconnected', () => this.prompt = this.$t('connectionClosed'))
787
+
788
+ // 非注册模式不会触发以下3个事件
789
+ this.sipMix.on('registered', () => {
790
+ this.prompt = this.$t('registered')
791
+ if (register && autoDial) {
792
+ this.onMakeCallBtnClick()
793
+ }
794
+ })
795
+ this.sipMix.on('unregistered', () => this.prompt = this.$t('unregistered'))
796
+ this.sipMix.on('registrationFailed', () => this.prompt = this.$t('registerFailed'))
797
+
798
+
799
+ this.sipMix.on('newSession', ({sessionId}) => {
800
+ console.info('New session, session id: ', sessionId)
801
+ })
802
+
803
+ // 处理通话失败事件
804
+ this.sipMix.on('failed', (e) => {
805
+ console.warn('通话失败', e)
806
+ this.dealCallEndOrFail() // 清理通话相关状态
807
+ this.callPrompt = this.$t('cancelled') // 显示通话已取消提示
808
+
809
+ // 触发挂断事件,通知父组件
810
+ this.$emit('hangup', {
811
+ imType: 0, // 即时通讯类型
812
+ reason: 'call_failed', // 失败原因
813
+ error: e // 错误信息
814
+ });
815
+
816
+ // 2秒后恢复默认提示
817
+ setTimeout(() => {
818
+ this.callPrompt = this.$t('pleaseBePatient')
819
+ }, 2000)
820
+ })
821
+
822
+ // 处理通话结束事件(对方挂断或通话正常结束)
823
+ this.sipMix.on('ended', () => {
824
+ console.warn('通话结束')
825
+ this.dealCallEndOrFail() // 清理通话相关状态
826
+ this.callPrompt = this.$t('hangedUp') // 显示通话已挂断提示
827
+
828
+ // 触发挂断事件,通知父组件
829
+ this.$emit('hangup', {
830
+ imType: 0, // 即时通讯类型
831
+ reason: 'call_ended' // 通话结束原因
832
+ });
833
+
834
+ // 2秒后恢复默认提示
835
+ setTimeout(() => {
836
+ this.callPrompt = this.$t('pleaseBePatient')
837
+ }, 2000)
838
+ })
839
+
840
+ this.sipMix.on('localStream', async (stream, info) => {
841
+ console.info('localStream', stream, info)
842
+ this.localVideo.w = this.clientWidth
843
+ this.localVideo.h = this.clientHeight
844
+ this.localVideo.draggable = false
845
+ this.showVideoCt = true
846
+ this.localVideo.show = true
847
+
848
+ await this.$nextTick()
849
+ this.$refs.localVideo.srcObject = stream
850
+ })
851
+
852
+ this.sipMix.on('remoteStream', (stream, info) => {
853
+ console.info('remoteStream', stream, info)
854
+ this._resetCmpWHLRTB()
855
+
856
+ Object.assign(this.localVideo, {
857
+ w: parseInt(this.clientWidth / 3),
858
+ h: parseInt(this.clientHeight / 3),
859
+ x: 0,
860
+ y: 80,
861
+ z: 1100,
862
+ draggable: true
863
+ })
864
+ this.$nextTick(() => {
865
+ this.$refs.localVideoWrapper.moveVertically(80)
866
+ })
867
+
868
+
869
+ setTimeout(async () => {
870
+ Object.assign(this.remoteVideo, {
871
+ show: true,
872
+ w: this.clientWidth,
873
+ h: this.clientHeight,
874
+ x: 0,
875
+ y: 0,
876
+ z: 1000,
877
+ draggable: false
878
+ })
879
+
880
+ await this.$nextTick()
881
+ if (this.autoHideLocalVideo) {
882
+ this.localVideo.show = false
883
+ }
884
+ this.$refs.remoteVideo.srcObject = stream
885
+ }, 500)
886
+
887
+ })
888
+
889
+ this.sipMix.on('remoteRing', () => {
890
+ this.callPrompt = this.$t('remoteRinging')
891
+ // this.$refs.audioPlayer.currentTime = 0
892
+ const promise = this.$refs.audioPlayer.play()
893
+ if (promise !== undefined) {
894
+ promise.catch(error => {
895
+ console.info(error)
896
+ if (error.name === 'NotAllowedError') {
897
+ this.showAudioPlayer = true
898
+ }
899
+ })
900
+ }
901
+ })
902
+
903
+ this.sipMix.on('accepted', (e) => {
904
+ this.callPrompt = this.$t('remoteAccept')
905
+ const {callId} = e
906
+ this.callId = callId
907
+ this.$refs.audioPlayer.pause()
908
+ this.autoShowHangupBtn()
909
+ })
910
+
911
+ this.sipMix.on('confirmed', () => {
912
+ this.startSendPosition()
913
+ })
914
+
915
+ this.sipMix.on('newInfo', ({content, contentType}) => {
916
+ console.info('New Info', contentType, content)
917
+ })
918
+
919
+ this.sipMix.start({
920
+ deviceId: this.callingNum,
921
+ password,
922
+ register,
923
+ debug
924
+ })
925
+ },
926
+
927
+ startLocate () {
928
+ if (navigator.geolocation) {
929
+ navigator.geolocation.getCurrentPosition(position => {
930
+ console.info('Position', position)
931
+ const {latitude, longitude, altitude } = position?.coords || {}
932
+ console.info(`获取地里位置信息: ${latitude}, ${longitude}, ${altitude}`)
933
+ /*this.sendCustomEvent({
934
+ act: 'position',
935
+ params: {latitude, longitude, accuracy}
936
+ })*/
937
+ this.latitude = latitude
938
+ this.longitude = longitude
939
+ this.altitude = altitude
940
+ }, () => {
941
+ console.info('获取地里位置信息失败')
942
+ })
943
+
944
+ this.locateHandler = setTimeout (() => {
945
+ this.startLocate()
946
+ }, 15000)
947
+ }
948
+ },
949
+
950
+ stopLocate () {
951
+ clearTimeout(this.locateHandler)
952
+ },
953
+
954
+ startSendPosition () {
955
+ if (this.showLocation && this.locateHandler) {
956
+ this.sipMix.sendInfo({
957
+ encode: false,
958
+ content: {
959
+ type: 'Position',
960
+ content: {
961
+ latitude: this.latitude,
962
+ longitude: this.longitude,
963
+ altitude: this.altitude
964
+ }
965
+ }
966
+ })
967
+ /*this.sipMix.sendMessage({
968
+ target: this.calledNum,
969
+ content: {
970
+ type: 'Position',
971
+ content: {
972
+ latitude: this.latitude,
973
+ longitude: this.longitude,
974
+ altitude: this.altitude
975
+ }
976
+ }
977
+ })*/
978
+ }
979
+ this.sendPositionHandler = setTimeout(() => {
980
+ this.startSendPosition()
981
+ }, 15000)
982
+ },
983
+
984
+ stopSendPosition () {
985
+ clearTimeout(this.sendPositionHandler)
986
+ }
987
+ },
988
+
989
+ async mounted() {
990
+ console.info('MobilePhone mounted')
991
+ await this.checkBrowser().catch(e => {
992
+ this.showSnackbar = true
993
+ this.snackbarText = e
994
+ })
995
+
996
+ setTimeout(() => {
997
+ const {clientHeight, clientWidth} = window.document.body
998
+ this.clientHeight = clientHeight
999
+ this.clientWidth = clientWidth
1000
+ this.localVideo.w = clientWidth
1001
+ this.localVideo.h = clientHeight
1002
+
1003
+ if (this.showLocation) {
1004
+ this.startLocate()
1005
+ }
1006
+ }, 200)
1007
+
1008
+ this.$nextTick(() => {
1009
+ this.showAudioPlayer = false
1010
+ })
1011
+ }
1012
+ }
1013
+ </script>
1014
+ <!-- <style>
1015
+ @import './vuetify.css';
1016
+ </style> -->
1017
+ <style lang="scss">
1018
+ .sip-call-main {
1019
+ background-color: #737778;
1020
+ padding: 25% 10px 20% !important;
1021
+ width: 100vw;
1022
+ height: 100vh;
1023
+ overflow: hidden;
1024
+ }
1025
+
1026
+ .sip-call-wrapper {
1027
+ position: absolute;
1028
+ left: 0;
1029
+ top: 0;
1030
+ width: 100vw;
1031
+ height: 100vh;
1032
+ z-index: 100;
1033
+ }
1034
+
1035
+ .btn-group {
1036
+ width: 100%;
1037
+ position: absolute;
1038
+ bottom: 150px;
1039
+ z-index: 4000;
1040
+ }
1041
+
1042
+ @media screen and (min-width: 1024px) {
1043
+ .btn-group {
1044
+ position: absolute;
1045
+ bottom: 80px;
1046
+ width: 640px;
1047
+ z-index: 4000;
1048
+ }
1049
+ }
1050
+
1051
+ .sip-call-video {
1052
+ width: 100%;
1053
+ height: 100%;
1054
+ object-fit: cover;
1055
+ background-color: #000000;
1056
+ }
1057
+
1058
+ .video-panel {
1059
+ // touch-action: none;
1060
+ position: absolute;
1061
+ display: flex;
1062
+ flex-direction: column;
1063
+ justify-content: space-around;
1064
+ align-items: center;
1065
+ }
1066
+
1067
+ .video-timer {
1068
+ background-color: rgba(255,255,255,0.80);
1069
+ position: absolute;
1070
+ top: 40px;
1071
+ left: 16px;
1072
+ font-size: 14px;
1073
+ font-weight: bold;
1074
+ padding: 2px 8px 2px 6px;
1075
+ border-radius: 12px;
1076
+ z-index: 2000;
1077
+ display: flex;
1078
+ justify-content: space-around;
1079
+ align-items: center;
1080
+ }
1081
+
1082
+ .video-switch-btn {
1083
+ position: absolute !important;
1084
+ top: 40px;
1085
+ right: 80px;
1086
+ z-index: 2000;
1087
+ img {
1088
+ background-color: #000000;
1089
+ border-radius: 6px;
1090
+ border: 1px solid #000000;
1091
+ }
1092
+ }
1093
+
1094
+ .video-mini-mode {
1095
+ position: absolute !important;
1096
+ top: 40px;
1097
+ right: 48px;
1098
+ z-index: 2000;
1099
+ }
1100
+
1101
+ .camera-switch-btn {
1102
+ position: absolute !important;
1103
+ top: 40px;
1104
+ right: 16px;
1105
+ z-index: 2000;
1106
+ }
1107
+
1108
+ .video-normal-mode {
1109
+ position: absolute !important;
1110
+ top: 16px;
1111
+ right: 16px;
1112
+ z-index: 2000;
1113
+ }
1114
+
1115
+ .location-wrapper {
1116
+ position: absolute;
1117
+ top: 0;
1118
+ right: 0;
1119
+ width: 150px;
1120
+ background-color: #00000055;
1121
+ color: #FFFFFF;
1122
+ padding: 6px;
1123
+ p {
1124
+ font-size: 12px;
1125
+ margin: 0;
1126
+ padding: 0;
1127
+ width: 100%;
1128
+ white-space: nowrap;
1129
+ text-overflow: ellipsis;
1130
+ }
1131
+ }
1132
+ </style>
1133
+ <style scoped>
1134
+ .custom-mute-btn {
1135
+ background: transparent !important;
1136
+ box-shadow: none !important;
1137
+ z-index: 9999 !important;
1138
+ min-width: 0 !important;
1139
+ width: auto !important;
1140
+ height: auto !important;
1141
+ padding: 0 !important;
1142
+ margin: 0 !important;
1143
+ }
1144
+
1145
+ /* 移除按钮悬停和激活状态的背景色 */
1146
+ .custom-mute-btn::before {
1147
+ background-color: transparent !important;
1148
+ opacity: 0 !important;
1149
+ }
1150
+
1151
+ /* 移除按钮的点击效果 */
1152
+ .custom-mute-btn:hover::before,
1153
+ .custom-mute-btn:focus::before,
1154
+ .custom-mute-btn:active::before {
1155
+ opacity: 0 !important;
1156
+ }
1157
+
1158
+ .transparent-btn {
1159
+ background: transparent !important;
1160
+ box-shadow: none !important;
1161
+ min-width: 0 !important;
1162
+ width: auto !important;
1163
+ height: auto !important;
1164
+ padding: 0 !important;
1165
+ margin: 0 !important;
1166
+ }
1167
+ .transparent-btn::before {
1168
+ background-color: transparent !important;
1169
+ opacity: 0 !important;
1170
+ }
1171
+
1172
+
1173
+ </style>