committed by
Gitee
40 changed files with 1083 additions and 172 deletions
@ -1,16 +1,18 @@ |
|||
package com.bx.imcommon.enums; |
|||
|
|||
|
|||
public enum IMSendStatus { |
|||
public enum IMSendCode { |
|||
|
|||
SUCCESS(0,"发送成功"), |
|||
FAIL(1,"发送失败"); |
|||
NOT_ONLINE(1,"对方当前不在线"), |
|||
NOT_FIND_CHANNEL(2,"未找到对方的channel"), |
|||
UNKONW_ERROR(9999,"未知异常"); |
|||
|
|||
private int code; |
|||
private String desc; |
|||
|
|||
// 构造方法
|
|||
IMSendStatus(int code, String desc) { |
|||
IMSendCode(int code, String desc) { |
|||
this.code = code; |
|||
this.desc = desc; |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
package com.bx.implatform.config; |
|||
|
|||
|
|||
import lombok.Data; |
|||
|
|||
|
|||
@Data |
|||
public class ICEServer { |
|||
|
|||
|
|||
private String urls; |
|||
|
|||
|
|||
private String username; |
|||
|
|||
|
|||
private String credential; |
|||
|
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
package com.bx.implatform.config; |
|||
|
|||
import lombok.Data; |
|||
import org.springframework.boot.context.properties.ConfigurationProperties; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
@Data |
|||
@Component |
|||
@ConfigurationProperties(prefix="webrtc") |
|||
public class ICEServerConfig { |
|||
|
|||
private List<ICEServer> iceServers = new ArrayList<>(); |
|||
|
|||
} |
|||
@ -0,0 +1,126 @@ |
|||
package com.bx.implatform.controller; |
|||
|
|||
|
|||
import com.bx.imclient.IMClient; |
|||
import com.bx.imcommon.model.PrivateMessageInfo; |
|||
import com.bx.implatform.config.ICEServerConfig; |
|||
import com.bx.implatform.enums.MessageType; |
|||
import com.bx.implatform.result.Result; |
|||
import com.bx.implatform.result.ResultUtils; |
|||
import com.bx.implatform.session.SessionContext; |
|||
import io.swagger.annotations.Api; |
|||
import io.swagger.annotations.ApiOperation; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.web.bind.annotation.*; |
|||
|
|||
@Api(tags = "webrtc视频单人通话") |
|||
@RestController |
|||
@RequestMapping("/webrtc/private") |
|||
public class WebrtcController { |
|||
|
|||
@Autowired |
|||
private IMClient imClient; |
|||
|
|||
@Autowired |
|||
private ICEServerConfig iceServerConfig; |
|||
|
|||
@ApiOperation(httpMethod = "POST", value = "呼叫视频通话") |
|||
@PostMapping("/call") |
|||
public Result call(@RequestParam Long uid, @RequestBody String offer) { |
|||
Long userId = SessionContext.getSession().getId(); |
|||
|
|||
PrivateMessageInfo message = new PrivateMessageInfo(); |
|||
message.setType(MessageType.RTC_CALL.code()); |
|||
message.setRecvId(uid); |
|||
message.setSendId(userId); |
|||
message.setContent(offer); |
|||
imClient.sendPrivateMessage(uid,message); |
|||
return ResultUtils.success(); |
|||
} |
|||
|
|||
@ApiOperation(httpMethod = "POST", value = "接受视频通话") |
|||
@PostMapping("/accept") |
|||
public Result accept(@RequestParam Long uid,@RequestBody String answer) { |
|||
Long userId = SessionContext.getSession().getId(); |
|||
|
|||
PrivateMessageInfo message = new PrivateMessageInfo(); |
|||
message.setType(MessageType.RTC_ACCEPT.code()); |
|||
message.setRecvId(uid); |
|||
message.setSendId(userId); |
|||
message.setContent(answer); |
|||
imClient.sendPrivateMessage(uid,message); |
|||
return ResultUtils.success(); |
|||
} |
|||
|
|||
|
|||
@ApiOperation(httpMethod = "POST", value = "拒绝视频通话") |
|||
@PostMapping("/reject") |
|||
public Result reject(@RequestParam Long uid) { |
|||
Long userId = SessionContext.getSession().getId(); |
|||
PrivateMessageInfo message = new PrivateMessageInfo(); |
|||
message.setType(MessageType.RTC_REJECT.code()); |
|||
message.setRecvId(uid); |
|||
message.setSendId(userId); |
|||
imClient.sendPrivateMessage(uid,message); |
|||
return ResultUtils.success(); |
|||
} |
|||
|
|||
@ApiOperation(httpMethod = "POST", value = "取消呼叫") |
|||
@PostMapping("/cancel") |
|||
public Result cancel(@RequestParam Long uid) { |
|||
Long userId = SessionContext.getSession().getId(); |
|||
PrivateMessageInfo message = new PrivateMessageInfo(); |
|||
message.setType(MessageType.RTC_CANCEL.code()); |
|||
message.setRecvId(uid); |
|||
message.setSendId(userId); |
|||
imClient.sendPrivateMessage(uid,message); |
|||
return ResultUtils.success(); |
|||
} |
|||
|
|||
@ApiOperation(httpMethod = "POST", value = "呼叫失败") |
|||
@PostMapping("/failed") |
|||
public Result failed(@RequestParam Long uid,@RequestParam String reason) { |
|||
Long userId = SessionContext.getSession().getId(); |
|||
|
|||
PrivateMessageInfo message = new PrivateMessageInfo(); |
|||
message.setType(MessageType.RTC_FAILED.code()); |
|||
message.setRecvId(uid); |
|||
message.setSendId(userId); |
|||
message.setContent(reason); |
|||
imClient.sendPrivateMessage(uid,message); |
|||
return ResultUtils.success(); |
|||
} |
|||
|
|||
@ApiOperation(httpMethod = "POST", value = "挂断") |
|||
@PostMapping("/handup") |
|||
public Result leave(@RequestParam Long uid) { |
|||
Long userId = SessionContext.getSession().getId(); |
|||
|
|||
PrivateMessageInfo message = new PrivateMessageInfo(); |
|||
message.setType(MessageType.RTC_HANDUP.code()); |
|||
message.setRecvId(uid); |
|||
message.setSendId(userId); |
|||
imClient.sendPrivateMessage(uid,message); |
|||
return ResultUtils.success(); |
|||
} |
|||
|
|||
|
|||
@PostMapping("/candidate") |
|||
@ApiOperation(httpMethod = "POST", value = "同步candidate") |
|||
public Result candidate(@RequestParam Long uid,@RequestBody String candidate ) { |
|||
Long userId = SessionContext.getSession().getId(); |
|||
PrivateMessageInfo message = new PrivateMessageInfo(); |
|||
message.setType(MessageType.RTC_CANDIDATE.code()); |
|||
message.setRecvId(uid); |
|||
message.setSendId(userId); |
|||
message.setContent(candidate); |
|||
imClient.sendPrivateMessage(uid,message); |
|||
return ResultUtils.success(); |
|||
} |
|||
|
|||
@GetMapping("/iceservers") |
|||
@ApiOperation(httpMethod = "GET", value = "获取iceservers") |
|||
public Result iceservers() { |
|||
return ResultUtils.success(iceServerConfig.getIceServers()); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
|
|||
const MESSAGE_TYPE = { |
|||
RTC_CALL: 101, |
|||
RTC_ACCEPT: 102, |
|||
RTC_REJECT: 103, |
|||
RTC_CANCEL: 104, |
|||
RTC_FAILED: 105, |
|||
RTC_HANDUP: 106, |
|||
RTC_CANDIDATE: 107 |
|||
} |
|||
|
|||
const USER_STATE = { |
|||
OFFLINE: 0, |
|||
FREE: 1, |
|||
BUSY: 2 |
|||
} |
|||
|
|||
export { |
|||
MESSAGE_TYPE, |
|||
USER_STATE |
|||
} |
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,383 @@ |
|||
<template> |
|||
<el-dialog v-dialogDrag :title="title" top="5vh" |
|||
:close-on-click-modal="false" |
|||
:close-on-press-escape="false" |
|||
:visible.sync="visible" width="50%" height="70%" :before-close="handleClose"> |
|||
<div class="chat-video"> |
|||
<div class="chat-video-box"> |
|||
<div class="chat-video-friend" v-loading="loading" element-loading-text="等待对方接听..." element-loading-spinner="el-icon-loading" |
|||
element-loading-background="rgba(0, 0, 0, 0.5)"> |
|||
<head-image class="friend-head-image" :id="this.friend.id" :size="80" :url="this.friend.headImage"></head-image> |
|||
<video ref="friendVideo" autoplay=""></video> |
|||
</div> |
|||
<div class="chat-video-mine"> |
|||
<video ref="mineVideo" autoplay=""></video> |
|||
</div> |
|||
</div> |
|||
<div class="chat-video-controllbar"> |
|||
<div v-show="state=='CONNECTING'" title="取消呼叫" class="icon iconfont icon-phone-reject reject" style="color: red;" |
|||
@click="cancel()"></div> |
|||
<div v-show="state=='CONNECTED'" title="挂断" class="icon iconfont icon-phone-reject reject" style="color: red;" |
|||
@click="handup()"></div> |
|||
|
|||
</div> |
|||
</div> |
|||
</el-dialog> |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
import HeadImage from '../common/HeadImage.vue'; |
|||
|
|||
export default { |
|||
name: 'chatVideo', |
|||
components: {HeadImage}, |
|||
props: { |
|||
visible: { |
|||
type: Boolean |
|||
}, |
|||
friend: { |
|||
type: Object |
|||
}, |
|||
master: { |
|||
type: Boolean |
|||
}, |
|||
offer: { |
|||
type: Object |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
stream: null, |
|||
audio: new Audio(), |
|||
loading: false, |
|||
peerConnection: null, |
|||
videoTime: 0, |
|||
videoTimer: null, |
|||
state: 'NOT_CONNECTED', |
|||
candidates: [], |
|||
configuration: { |
|||
iceServers: [] |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
init() { |
|||
if (!this.hasUserMedia() || !this.hasRTCPeerConnection()) { |
|||
this.$message.error("您的浏览器不支持WebRTC"); |
|||
if (!this.master) { |
|||
this.sendFailed("对方浏览器不支持WebRTC") |
|||
} |
|||
return; |
|||
} |
|||
// 打开摄像头 |
|||
this.openCamera((stream) => { |
|||
// 初始化webrtc连接 |
|||
this.setupPeerConnection(stream); |
|||
if (this.master) { |
|||
// 发起呼叫 |
|||
this.call(); |
|||
} else { |
|||
// 接受呼叫 |
|||
this.accept(this.offer); |
|||
} |
|||
}); |
|||
}, |
|||
openCamera(callback) { |
|||
navigator.getUserMedia({ |
|||
video: true, |
|||
audio: true |
|||
}, |
|||
(stream) => { |
|||
this.stream = stream; |
|||
console.log(this.stream) |
|||
this.$refs.mineVideo.srcObject = stream; |
|||
this.$refs.mineVideo.muted = true; |
|||
callback(stream) |
|||
}, |
|||
(error) => { |
|||
this.$message.error("打开摄像头失败:" + error); |
|||
callback() |
|||
}); |
|||
}, |
|||
closeCamera() { |
|||
if (this.stream) { |
|||
this.stream.getTracks().forEach((track) => { |
|||
track.stop(); |
|||
}); |
|||
this.$refs.mineVideo.srcObject = null; |
|||
this.stream = null; |
|||
} |
|||
|
|||
}, |
|||
setupPeerConnection(stream) { |
|||
this.peerConnection = new RTCPeerConnection(this.configuration); |
|||
this.peerConnection.ontrack = (e) => { |
|||
this.$refs.friendVideo.srcObject = e.streams[0]; |
|||
}; |
|||
this.peerConnection.onicecandidate = (event) => { |
|||
if (event.candidate) { |
|||
if (this.state == 'CONNECTED') { |
|||
// 已连接,直接发送 |
|||
this.sendCandidate(event.candidate); |
|||
} else { |
|||
// 未连接,缓存起来,连接后再发送 |
|||
this.candidates.push(event.candidate) |
|||
} |
|||
} |
|||
} |
|||
if (stream) { |
|||
stream.getTracks().forEach((track) => { |
|||
this.peerConnection.addTrack(track, stream); |
|||
}); |
|||
} |
|||
this.peerConnection.oniceconnectionstatechange = (event) => { |
|||
let state = event.target.iceConnectionState; |
|||
console.log("ICE connection status changed : " + state) |
|||
if(state == 'connected'){ |
|||
this.resetTime(); |
|||
} |
|||
}; |
|||
|
|||
}, |
|||
handleMessage(msg) { |
|||
if (msg.type == this.$enums.MESSAGE_TYPE.RTC_ACCEPT) { |
|||
this.peerConnection.setRemoteDescription(new RTCSessionDescription(JSON.parse(msg.content))); |
|||
// 关闭等待提示 |
|||
this.loading = false; |
|||
// 状态为连接中 |
|||
this.state = 'CONNECTED'; |
|||
// 停止播放语音 |
|||
this.audio.pause(); |
|||
// 发送candidate |
|||
this.candidates.forEach((candidate) => { |
|||
this.sendCandidate(candidate); |
|||
}) |
|||
} |
|||
else if (msg.type == this.$enums.MESSAGE_TYPE.RTC_REJECT) { |
|||
this.$message.error("对方拒绝了您的视频请求"); |
|||
this.close(); |
|||
} |
|||
else if (msg.type == this.$enums.MESSAGE_TYPE.RTC_FAILED) { |
|||
this.$message.error(msg.content) |
|||
this.close(); |
|||
} |
|||
else if (msg.type == this.$enums.MESSAGE_TYPE.RTC_CANDIDATE) { |
|||
this.peerConnection.addIceCandidate(new RTCIceCandidate(JSON.parse(msg.content))); |
|||
} |
|||
else if (msg.type == this.$enums.MESSAGE_TYPE.RTC_HANDUP) { |
|||
this.$message.success("对方挂断了视频通话"); |
|||
this.close(); |
|||
} |
|||
}, |
|||
call() { |
|||
this.peerConnection.createOffer((offer) => { |
|||
this.peerConnection.setLocalDescription(offer); |
|||
this.$http({ |
|||
url: `/webrtc/private/call?uid=${this.friend.id}`, |
|||
method: 'post', |
|||
data: JSON.stringify(offer) |
|||
}).then(() => { |
|||
this.loading = true; |
|||
this.state = 'CONNECTING'; |
|||
this.audio.play(); |
|||
}); |
|||
}, |
|||
(error) => { |
|||
this.$message.error(error); |
|||
}); |
|||
|
|||
}, |
|||
accept(offer) { |
|||
this.peerConnection.setRemoteDescription(new RTCSessionDescription(offer)); |
|||
this.peerConnection.createAnswer((answer) => { |
|||
this.peerConnection.setLocalDescription(answer); |
|||
this.$http({ |
|||
url: `/webrtc/private/accept?uid=${this.friend.id}`, |
|||
method: 'post', |
|||
data: JSON.stringify(answer) |
|||
}) |
|||
this.state = 'CONNECTED'; |
|||
}, |
|||
(error) => { |
|||
this.$message.error(error); |
|||
}); |
|||
|
|||
}, |
|||
handup() { |
|||
this.$http({ |
|||
url: `/webrtc/private/handup?uid=${this.friend.id}`, |
|||
method: 'post' |
|||
}) |
|||
this.close(); |
|||
this.$message.success("已挂断视频通话") |
|||
}, |
|||
cancel() { |
|||
this.$http({ |
|||
url: `/webrtc/private/cancel?uid=${this.friend.id}`, |
|||
method: 'post' |
|||
}) |
|||
this.close(); |
|||
this.$message.success("已停止呼叫视频通话") |
|||
}, |
|||
sendFailed(reason) { |
|||
this.$http({ |
|||
url: `/webrtc/private/failed?uid=${this.friend.id}&reason=${reason}`, |
|||
method: 'post' |
|||
}) |
|||
}, |
|||
sendCandidate(candidate) { |
|||
this.$http({ |
|||
url: `/webrtc/private/candidate?uid=${this.friend.id}`, |
|||
method: 'post', |
|||
data: JSON.stringify(candidate) |
|||
}) |
|||
}, |
|||
close() { |
|||
this.$emit("close"); |
|||
this.closeCamera(); |
|||
this.loading = false; |
|||
this.state = 'NOT_CONNECTED'; |
|||
this.videoTime = 0; |
|||
this.videoTimer && clearInterval(this.videoTimer); |
|||
this.audio.pause(); |
|||
this.candidates = []; |
|||
this.$store.commit("setUserState", this.$enums.USER_STATE.FREE); |
|||
this.$refs.friendVideo.srcObject = null; |
|||
this.peerConnection.close(); |
|||
this.peerConnection.onicecandidate = null; |
|||
this.peerConnection.onaddstream = null; |
|||
}, |
|||
resetTime(){ |
|||
this.videoTime = 0; |
|||
this.videoTimer && clearInterval(this.videoTimer); |
|||
this.videoTimer = setInterval(()=>{ |
|||
this.videoTime++; |
|||
},1000) |
|||
}, |
|||
handleClose() { |
|||
if (this.state == 'CONNECTED') { |
|||
this.handup() |
|||
} else if (this.state == 'CONNECTING') { |
|||
this.cancel(); |
|||
} else { |
|||
this.close(); |
|||
} |
|||
}, |
|||
hasUserMedia() { |
|||
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || |
|||
navigator.msGetUserMedia; |
|||
return !!navigator.getUserMedia; |
|||
}, |
|||
hasRTCPeerConnection() { |
|||
window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection; |
|||
window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription; |
|||
window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate; |
|||
return !!window.RTCPeerConnection; |
|||
}, |
|||
initAudio(){ |
|||
let url = require(`@/assets/audio/call.wav`); |
|||
this.audio.src = url; |
|||
this.audio.loop = true; |
|||
}, |
|||
initICEServers(){ |
|||
this.$http({ |
|||
url: '/webrtc/private/iceservers', |
|||
method: 'get' |
|||
}).then((servers) => { |
|||
this.configuration.iceServers = servers; |
|||
}) |
|||
} |
|||
|
|||
}, |
|||
watch: { |
|||
visible: { |
|||
handler(newValue, oldValue) { |
|||
if (newValue) { |
|||
this.init(); |
|||
// 用户忙状态 |
|||
this.$store.commit("setUserState", this.$enums.USER_STATE.BUSY); |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
title() { |
|||
let strTitle = `视频聊天-${this.friend.nickName}`; |
|||
if(this.state == 'CONNECTED'){ |
|||
strTitle += `(${this.currentTime})`; |
|||
}else if(this.state == 'CONNECTING'){ |
|||
strTitle += `(呼叫中)`; |
|||
} |
|||
return strTitle; |
|||
}, |
|||
currentTime(){ |
|||
let currentTime = 0; |
|||
if(this.state == 'CONNECTED' && this.videoTime){ |
|||
currentTime = Math.floor(this.videoTime); |
|||
} |
|||
let min = Math.floor(currentTime/60); |
|||
let sec = currentTime%60; |
|||
let strTime = min<10?"0":""; |
|||
strTime += min; |
|||
strTime += ":" |
|||
strTime += sec<10?"0":""; |
|||
strTime += sec; |
|||
return strTime; |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.initAudio(); |
|||
this.initICEServers(); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.chat-video { |
|||
position: relative; |
|||
|
|||
.chat-video-box { |
|||
position: relative; |
|||
border: #4880b9 solid 1px; |
|||
background-color: #eeeeee; |
|||
|
|||
.chat-video-friend { |
|||
height: 70vh; |
|||
.friend-head-image { |
|||
position: absolute; |
|||
} |
|||
|
|||
video { |
|||
width: 100%; |
|||
height: 100%; |
|||
object-fit: fill; |
|||
} |
|||
} |
|||
|
|||
.chat-video-mine { |
|||
position: absolute; |
|||
z-index: 99999; |
|||
width: 25vh; |
|||
right: 0; |
|||
bottom: 0; |
|||
box-shadow: 0px 0px 5px #ccc; |
|||
background-color: #cccccc; |
|||
video { |
|||
width: 100%; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.chat-video-controllbar { |
|||
display: flex; |
|||
justify-content: space-around; |
|||
padding: 10px; |
|||
|
|||
.icon { |
|||
font-size: 50px; |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,131 @@ |
|||
<template> |
|||
<div class="chat-video-acceptor"> |
|||
<div> |
|||
<head-image :size="120" :url="this.friend.headImage" :id="this.friend.id"></head-image> |
|||
</div> |
|||
<div> |
|||
{{friend.nickName}} 请求和您进行视频通话... |
|||
</div> |
|||
<div class="acceptor-btn-group"> |
|||
<div class="icon iconfont icon-phone-accept accept" @click="accpet()"></div> |
|||
<div class="icon iconfont icon-phone-reject reject" @click="reject()"></div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import HeadImage from '../common/HeadImage.vue'; |
|||
|
|||
export default { |
|||
name: "videoAcceptor", |
|||
components:{HeadImage}, |
|||
props: { |
|||
friend:{ |
|||
type: Object |
|||
} |
|||
}, |
|||
data(){ |
|||
return { |
|||
offer:{}, |
|||
audio: new Audio() |
|||
} |
|||
}, |
|||
methods:{ |
|||
accpet(){ |
|||
let info ={ |
|||
friend: this.friend, |
|||
master: false, |
|||
offer: this.offer |
|||
} |
|||
this.$store.commit("showChatPrivateVideoBox",info); |
|||
this.close(); |
|||
}, |
|||
reject(){ |
|||
this.$http({ |
|||
url: `/webrtc/private/reject?uid=${this.friend.id}`, |
|||
method: 'post' |
|||
}) |
|||
this.close(); |
|||
}, |
|||
failed(reason){ |
|||
this.$http({ |
|||
url: `/webrtc/private/failed?uid=${this.friend.id}&reason=${reason}`, |
|||
method: 'post' |
|||
}) |
|||
this.close(); |
|||
}, |
|||
onCall(msgInfo){ |
|||
console.log("onCall") |
|||
this.offer = JSON.parse(msgInfo.content); |
|||
if(this.$store.state.userStore.state == this.$enums.USER_STATE.BUSY){ |
|||
this.failed("对方正忙,暂时无法接听"); |
|||
return; |
|||
} |
|||
// 超时未接听 |
|||
this.timer && clearTimeout(this.timer); |
|||
this.timer = setTimeout(()=>{ |
|||
this.failed("对方未接听"); |
|||
},30000) |
|||
this.audio.play(); |
|||
}, |
|||
onCancel(){ |
|||
this.$message.success("对方取消了呼叫"); |
|||
this.close(); |
|||
}, |
|||
handleMessage(msgInfo){ |
|||
if(msgInfo.type == this.$enums.MESSAGE_TYPE.RTC_CALL){ |
|||
this.onCall(msgInfo); |
|||
}else if(msgInfo.type == this.$enums.MESSAGE_TYPE.RTC_CANCEL){ |
|||
this.onCancel(); |
|||
} |
|||
}, |
|||
close(){ |
|||
this.timer && clearTimeout(this.timer); |
|||
this.audio.pause(); |
|||
this.$emit("close"); |
|||
}, |
|||
initAudio(){ |
|||
let url = require(`@/assets/audio/call.wav`); |
|||
this.audio.src = url; |
|||
this.audio.loop = true; |
|||
} |
|||
}, |
|||
mounted() { |
|||
// 初始化语音 |
|||
this.initAudio(); |
|||
|
|||
} |
|||
} |
|||
|
|||
</script> |
|||
|
|||
<style scoped lang="scss"> |
|||
.chat-video-acceptor { |
|||
position: absolute; |
|||
right: 1px; |
|||
bottom: 1px; |
|||
width: 250px; |
|||
height: 250px; |
|||
padding: 20px; |
|||
text-align: center; |
|||
background-color: #eeeeee; |
|||
border: #dddddd solid 1px; |
|||
|
|||
.acceptor-btn-group { |
|||
display: flex; |
|||
justify-content: space-around; |
|||
margin-top: 20px; |
|||
|
|||
.icon { |
|||
font-size: 50px; |
|||
cursor: pointer; |
|||
&.accept { |
|||
color: green; |
|||
} |
|||
&.reject { |
|||
color: red; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,72 @@ |
|||
import Vue from 'vue' |
|||
|
|||
// v-dialogDrag: 弹窗拖拽
|
|||
Vue.directive('dialogDrag', { |
|||
bind (el, binding, vnode, oldVnode) { |
|||
const dialogHeaderEl = el.querySelector('.el-dialog__header') |
|||
const dragDom = el.querySelector('.el-dialog') |
|||
dialogHeaderEl.style.cursor = 'move' |
|||
|
|||
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
|
|||
const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null) |
|||
|
|||
dialogHeaderEl.onmousedown = (e) => { |
|||
// 鼠标按下,计算当前元素距离可视区的距离
|
|||
const disX = e.clientX - dialogHeaderEl.offsetLeft |
|||
const disY = e.clientY - dialogHeaderEl.offsetTop |
|||
const screenWidth = document.body.clientWidth; // body当前宽度
|
|||
const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取)
|
|||
const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
|
|||
const dragDomheight = dragDom.offsetHeight; // 对话框高度
|
|||
const minDragDomLeft = dragDom.offsetLeft; |
|||
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth; |
|||
const minDragDomTop = dragDom.offsetTop; |
|||
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight; |
|||
|
|||
// 获取到的值带px 正则匹配替换
|
|||
let styL, styT |
|||
|
|||
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
|
|||
if (sty.left.includes('%')) { |
|||
styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100) |
|||
styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100) |
|||
} else { |
|||
styL = +sty.left.replace(/\px/g, '') |
|||
styT = +sty.top.replace(/\px/g, '') |
|||
} |
|||
|
|||
document.onmousemove = function (e) { |
|||
// 获取body的页面可视宽高
|
|||
// var clientHeight = document.documentElement.clientHeight || document.body.clientHeight
|
|||
// var clientWidth = document.documentElement.clientWidth || document.body.clientWidth
|
|||
|
|||
// 通过事件委托,计算移动的距离
|
|||
var l = e.clientX - disX |
|||
var t = e.clientY - disY |
|||
|
|||
// 边界处理
|
|||
if (-l > minDragDomLeft) { |
|||
l = -minDragDomLeft; |
|||
} else if (l > maxDragDomLeft) { |
|||
l = maxDragDomLeft; |
|||
} |
|||
if (-t > minDragDomTop) { |
|||
t = -minDragDomTop; |
|||
} else if (t > maxDragDomTop) { |
|||
t = maxDragDomTop; |
|||
} |
|||
// 移动当前元素
|
|||
dragDom.style.left = `${l + styL}px` |
|||
dragDom.style.top = `${t + styT}px` |
|||
|
|||
// 将此时的位置传出去
|
|||
// binding.value({x:e.pageX,y:e.pageY})
|
|||
} |
|||
|
|||
document.onmouseup = function (e) { |
|||
document.onmousemove = null |
|||
document.onmouseup = null |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
Loading…
Reference in new issue