Browse Source

视频聊天功能-完成

master
xie.bx 3 years ago
parent
commit
54d43ec1f1
  1. 19
      im-platform/src/main/java/com/bx/implatform/config/ICEServer.java
  2. 17
      im-platform/src/main/java/com/bx/implatform/config/ICEServerConfig.java
  3. 10
      im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java
  4. 5
      im-platform/src/main/resources/application.yml
  5. BIN
      im-ui/src/assets/audio/call.wav
  6. BIN
      im-ui/src/assets/audio/tip.wav
  7. 8
      im-ui/src/components/chat/ChatBox.vue
  8. 8
      im-ui/src/components/chat/ChatHistory.vue
  9. 70
      im-ui/src/components/chat/ChatPrivateVideo.vue
  10. 369
      im-ui/src/components/chat/MessageItem.vue
  11. 124
      im-ui/src/components/chat/VideoAcceptor.vue
  12. 72
      im-ui/src/utils/directive/dialogDrag.js
  13. 18
      im-ui/src/view/Home.vue

19
im-platform/src/main/java/com/bx/implatform/config/ICEServer.java

@ -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;
}

17
im-platform/src/main/java/com/bx/implatform/config/ICEServerConfig.java

@ -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<>();
}

10
im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java

@ -3,13 +3,13 @@ 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 io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@ -21,6 +21,8 @@ public class WebrtcController {
@Autowired
private IMClient imClient;
@Autowired
private ICEServerConfig iceServerConfig;
@ApiOperation(httpMethod = "POST", value = "呼叫视频通话")
@PostMapping("/call")
@ -115,4 +117,10 @@ public class WebrtcController {
imClient.sendPrivateMessage(uid,message);
return ResultUtils.success();
}
@GetMapping("/iceservers")
@ApiOperation(httpMethod = "GET", value = "获取iceservers")
public Result iceservers() {
return ResultUtils.success(iceServerConfig.getIceServers());
}
}

5
im-platform/src/main/resources/application.yml

@ -36,3 +36,8 @@ minio:
bucketName: box-im
imagePath: image
filePath: file
webrtc:
iceServers:
- urls: stun:stun.l.google.com:19302

BIN
im-ui/src/assets/audio/call.wav

Binary file not shown.

BIN
im-ui/src/assets/audio/tip.wav

Binary file not shown.

8
im-ui/src/components/chat/ChatBox.vue

@ -11,9 +11,9 @@
<div class="im-chat-box">
<ul>
<li v-for="(msgInfo,idx) in chat.messages" :key="idx">
<message-item :mine="msgInfo.sendId == mine.id" :headImage="headImage(msgInfo)" :showName="showName(msgInfo)"
<chat-message-item :mine="msgInfo.sendId == mine.id" :headImage="headImage(msgInfo)" :showName="showName(msgInfo)"
:msgInfo="msgInfo" @delete="deleteMessage" @recall="recallMessage">
</message-item>
</chat-message-item>
</li>
</ul>
</div>
@ -60,7 +60,7 @@
<script>
import ChatGroupSide from "./ChatGroupSide.vue";
import MessageItem from "./MessageItem.vue";
import ChatMessageItem from "./ChatMessageItem.vue";
import FileUpload from "../common/FileUpload.vue";
import Emotion from "../common/Emotion.vue";
import ChatVoice from "./ChatVoice.vue";
@ -69,7 +69,7 @@
export default {
name: "chatPrivate",
components: {
MessageItem,
ChatMessageItem,
FileUpload,
ChatGroupSide,
Emotion,

8
im-ui/src/components/chat/ChatHistory.vue

@ -5,9 +5,9 @@
<el-scrollbar class="chat-history-scrollbar" ref="scrollbar" id="historyScrollbar" >
<ul>
<li v-for="(msgInfo,idx) in messages" :key="idx">
<message-item :mine="msgInfo.sendId == mine.id" :headImage="headImage(msgInfo)" :showName="showName(msgInfo)"
<chat-message-item :mine="msgInfo.sendId == mine.id" :headImage="headImage(msgInfo)" :showName="showName(msgInfo)"
:msgInfo="msgInfo" :menu="false">
</message-item>
</chat-message-item>
</li>
</ul>
</el-scrollbar>
@ -16,12 +16,12 @@
</template>
<script>
import MessageItem from './MessageItem.vue';
import ChatMessageItem from './ChatMessageItem.vue';
export default {
name: 'chatHistory',
components: {
MessageItem
ChatMessageItem
},
props: {
visible: {

70
im-ui/src/components/chat/ChatPrivateVideo.vue

@ -49,6 +49,7 @@
data() {
return {
stream: null,
audio: new Audio(),
loading: false,
peerConnection: null,
videoTime: 0,
@ -56,19 +57,7 @@
state: 'NOT_CONNECTED',
candidates: [],
configuration: {
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
},
{
urls: 'turn:8.134.92.70:3478',
credential: 'admin123',
username: 'admin'
},
{
urls: 'stun:8.134.92.70:3478'
}
]
iceServers: []
}
}
},
@ -101,6 +90,7 @@
},
(stream) => {
this.stream = stream;
console.log(this.stream)
this.$refs.mineVideo.srcObject = stream;
this.$refs.mineVideo.muted = true;
callback(stream)
@ -123,7 +113,6 @@
setupPeerConnection(stream) {
this.peerConnection = new RTCPeerConnection(this.configuration);
this.peerConnection.ontrack = (e) => {
console.log("onaddstream")
this.$refs.friendVideo.srcObject = e.streams[0];
};
this.peerConnection.onicecandidate = (event) => {
@ -153,46 +142,37 @@
},
handleMessage(msg) {
if (msg.type == this.$enums.MESSAGE_TYPE.RTC_ACCEPT) {
console.log("接收answer")
console.log(msg.content)
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);
})
}
if (msg.type == this.$enums.MESSAGE_TYPE.RTC_REJECT) {
else if (msg.type == this.$enums.MESSAGE_TYPE.RTC_REJECT) {
this.$message.error("对方拒绝了您的视频请求");
this.peerConnection.close();
//
this.loading = false;
//
this.state = 'NOT_CONNECTED';
this.close();
}
if (msg.type == this.$enums.MESSAGE_TYPE.RTC_FAILED) {
else if (msg.type == this.$enums.MESSAGE_TYPE.RTC_FAILED) {
this.$message.error(msg.content)
//
this.loading = false;
//
this.state = 'NOT_CONNECTED';
this.close();
}
if (msg.type == this.$enums.MESSAGE_TYPE.RTC_CANDIDATE) {
else if (msg.type == this.$enums.MESSAGE_TYPE.RTC_CANDIDATE) {
this.peerConnection.addIceCandidate(new RTCIceCandidate(JSON.parse(msg.content)));
}
if (msg.type == this.$enums.MESSAGE_TYPE.RTC_HANDUP) {
this.$message.success("对方挂断");
else if (msg.type == this.$enums.MESSAGE_TYPE.RTC_HANDUP) {
this.$message.success("对方挂断了视频通话");
this.close();
}
},
call() {
this.peerConnection.createOffer((offer) => {
this.peerConnection.setLocalDescription(offer);
console.log("发送offer")
console.log(JSON.stringify(offer))
this.$http({
url: `/webrtc/private/call?uid=${this.friend.id}`,
method: 'post',
@ -200,6 +180,7 @@
}).then(() => {
this.loading = true;
this.state = 'CONNECTING';
this.audio.play();
});
},
(error) => {
@ -208,12 +189,8 @@
},
accept(offer) {
console.log("接收到offer")
console.log(offer)
this.peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
this.peerConnection.createAnswer((answer) => {
console.log("发送answer")
console.log(JSON.stringify(answer))
this.peerConnection.setLocalDescription(answer);
this.$http({
url: `/webrtc/private/accept?uid=${this.friend.id}`,
@ -263,6 +240,7 @@
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;
@ -296,6 +274,19 @@
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;
})
}
},
@ -315,6 +306,8 @@
let strTitle = `视频聊天-${this.friend.nickName}`;
if(this.state == 'CONNECTED'){
strTitle += `(${this.currentTime})`;
}else if(this.state == 'CONNECTING'){
strTitle += `(呼叫中)`;
}
return strTitle;
},
@ -332,7 +325,10 @@
strTime += sec;
return strTime;
}
},
mounted() {
this.initAudio();
this.initICEServers();
}
}
</script>

369
im-ui/src/components/chat/MessageItem.vue

@ -1,369 +0,0 @@
<template>
<div class="im-msg-item">
<div class="im-msg-tip" v-show="msgInfo.type==10">{{msgInfo.content}}</div>
<div class="im-msg-normal" v-show="msgInfo.type!=10" :class="{'im-chat-mine':mine}">
<div class="head-image">
<head-image :url="headImage" :id="msgInfo.sendId"></head-image>
</div>
<div class="im-msg-content">
<div class="im-msg-top">
<span>{{showName}}</span>
<chat-time :time="msgInfo.sendTime"></chat-time>
</div>
<div class="im-msg-bottom" @contextmenu.prevent="showRightMenu($event)">
<span class="im-msg-text" v-if="msgInfo.type==0" v-html="$emo.transform(msgInfo.content)"></span>
<div class="im-msg-image" v-if="msgInfo.type==1">
<div class="img-load-box" v-loading="loading" element-loading-text="上传中.." element-loading-background="rgba(0, 0, 0, 0.4)">
<img class="send-image" :src="JSON.parse(msgInfo.content).thumbUrl" @click="showFullImageBox()" />
</div>
<span title="发送失败" v-show="loadFail" @click="handleSendFail" class="send-fail el-icon-warning"></span>
</div>
<div class="im-msg-file" v-if="msgInfo.type==2">
<div class="im-file-box" v-loading="loading">
<div class="im-file-info">
<el-link class="im-file-name" :underline="true" target="_blank" type="primary" :href="data.url">{{data.name}}</el-link>
<div class="im-file-size">{{fileSize}}</div>
</div>
<div class="im-file-icon">
<span type="primary" class="el-icon-document"></span>
</div>
</div>
<span title="发送失败" v-show="loadFail" @click="handleSendFail" class="send-fail el-icon-warning"></span>
</div>
<div class="im-msg-voice" v-if="msgInfo.type==3" @click="handlePlayVoice()">
<audio controls :src="JSON.parse(msgInfo.content).url"></audio>
</div>
</div>
</div>
</div>
<right-menu v-show="menu && rightMenu.show" :pos="rightMenu.pos" :items="menuItems" @close="rightMenu.show=false"
@select="handleSelectMenu"></right-menu>
</div>
</template>
<script>
import ChatTime from "./ChatTime.vue";
import HeadImage from "../common/HeadImage.vue";
import RightMenu from '../common/RightMenu.vue';
export default {
name: "messageItem",
components: {
ChatTime,
HeadImage,
RightMenu
},
props: {
mine: {
type: Boolean,
required: true
},
headImage: {
type: String,
required: true
},
showName: {
type: String,
required: true
},
msgInfo: {
type: Object,
required: true
},
menu:{
type: Boolean,
default: true
}
},
data() {
return {
audioPlayState: 'STOP',
rightMenu: {
show: false,
pos: {
x: 0,
y: 0
}
}
}
},
methods: {
handleSendFail() {
this.$message.error("该文件已发送失败,目前不支持自动重新发送,建议手动重新发送")
},
showFullImageBox() {
let imageUrl = JSON.parse(this.msgInfo.content).originUrl;
if (imageUrl) {
this.$store.commit('showFullImageBox', imageUrl);
}
},
handlePlayVoice() {
if (!this.audio) {
this.audio = new Audio();
}
this.audio.src = JSON.parse(this.msgInfo.content).url;
this.audio.play();
this.handlePlayVoice = 'RUNNING';
},
showRightMenu(e) {
this.rightMenu.pos = {
x: e.x,
y: e.y
};
this.rightMenu.show = "true";
},
handleSelectMenu(item) {
this.$emit(item.key.toLowerCase(), this.msgInfo);
}
},
computed: {
loading() {
return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "loading";
},
loadFail() {
return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "fail";
},
data() {
return JSON.parse(this.msgInfo.content)
},
fileSize() {
let size = this.data.size;
if (size > 1024 * 1024) {
return Math.round(size / 1024 / 1024) + "M";
}
if (size > 1024) {
return Math.round(size / 1024) + "KB";
}
return size + "B";
},
menuItems() {
let items = [];
items.push({
key: 'DELETE',
name: '删除',
icon: 'el-icon-delete'
});
if (this.msgInfo.selfSend && this.msgInfo.id > 0) {
items.push({
key: 'RECALL',
name: '撤回',
icon: 'el-icon-refresh-left'
});
}
return items;
}
},
mounted() {
//console.log(this.msgInfo);
}
}
</script>
<style lang="scss">
.im-msg-item {
.im-msg-tip {
line-height: 50px;
}
.im-msg-normal {
position: relative;
font-size: 0;
margin-bottom: 10px;
padding-left: 60px;
min-height: 68px;
.head-image {
position: absolute;
width: 40px;
height: 40px;
top: 0;
left: 0;
}
.im-msg-content {
display: flex;
flex-direction: column;
.im-msg-top {
display: flex;
flex-wrap: nowrap;
color: #333;
font-size: 14px;
line-height: 20px;
span {
margin-right: 12px;
}
}
.im-msg-bottom {
text-align: left;
.im-msg-text {
position: relative;
line-height: 22px;
margin-top: 10px;
padding: 10px;
background-color: #eeeeee;
border-radius: 3px;
color: #333;
display: inline-block;
font-size: 14px;
&:after {
content: "";
position: absolute;
left: -10px;
top: 13px;
width: 0;
height: 0;
border-style: solid dashed dashed;
border-color: #eeeeee transparent transparent;
overflow: hidden;
border-width: 10px;
}
}
.im-msg-image {
display: flex;
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
.send-image {
min-width: 300px;
min-height: 200px;
max-width: 600px;
max-height: 400px;
border: #dddddd solid 1px;
cursor: pointer;
}
.send-fail {
color: #e60c0c;
font-size: 30px;
cursor: pointer;
margin: 0 20px;
}
}
.im-msg-file {
display: flex;
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
cursor: pointer;
.im-file-box {
display: flex;
flex-wrap: nowrap;
align-items: center;
width: 20%;
min-height: 80px;
border: #dddddd solid 1px;
border-radius: 3px;
background-color: #eeeeee;
padding: 10px 15px;
.im-file-info {
flex: 1;
height: 100%;
text-align: left;
font-size: 14px;
.im-file-name {
font-size: 16px;
font-weight: 600;
margin-bottom: 15px;
}
}
.im-file-icon {
font-size: 50px;
color: #d42e07;
}
}
.send-fail {
color: #e60c0c;
font-size: 30px;
cursor: pointer;
margin: 0 20px;
}
}
.im-msg-voice {
font-size: 14px;
cursor: pointer;
audio {
height: 45px;
padding: 5px 0;
}
}
}
}
&.im-chat-mine {
text-align: right;
padding-left: 0;
padding-right: 60px;
.head-image {
left: auto;
right: 0;
}
.im-msg-content {
.im-msg-top {
flex-direction: row-reverse;
span {
margin-left: 12px;
margin-right: 0;
}
}
.im-msg-bottom {
text-align: right;
.im-msg-text {
margin-left: 10px;
background-color: #5fb878;
color: #fff;
display: inline-block;
vertical-align: top;
font-size: 14px;
&:after {
left: auto;
right: -10px;
border-top-color: #5fb878;
}
}
.im-msg-image {
flex-direction: row-reverse;
}
.im-msg-file {
flex-direction: row-reverse;
}
}
}
.message-info {
right: 60px !important;
display: inline-block;
}
}
}
}
</style>

124
im-ui/src/components/chat/VideoAcceptor.vue

@ -1,124 +0,0 @@
<template>
<div class="video-acceptor">
<div>
<head-image :size="120" :url="this.friend.headImage" :id="this.friend.id"></head-image>
</div>
<div>
{{friend.nickName}} 请求和您进行视频通话...
</div>
<div class="video-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:{}
}
},
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)
},
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.$emit("close");
}
}
}
</script>
<style scoped lang="scss">
.video-acceptor {
position: absolute;
right: 1px;
bottom: 1px;
width: 250px;
height: 250px;
padding: 20px;
text-align: center;
background-color: #eeeeee;
border: #dddddd solid 1px;
.video-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>

72
im-ui/src/utils/directive/dialogDrag.js

@ -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
      }
    }
  }
})

18
im-ui/src/view/Home.vue

@ -44,11 +44,11 @@
:offer="uiStore.chatPrivateVideo.offer"
@close="$store.commit('closeChatPrivateVideoBox')" >
</chat-private-video>
<video-acceptor ref="videoAcceptor"
<chat-video-acceptor ref="videoAcceptor"
v-show="uiStore.videoAcceptor.show"
:friend="uiStore.videoAcceptor.friend"
@close="$store.commit('closeVideoAcceptorBox')" >
</video-acceptor>
</chat-video-acceptor>
</el-container>
</template>
@ -58,7 +58,7 @@
import UserInfo from '../components/common/UserInfo.vue';
import FullImage from '../components/common/FullImage.vue';
import ChatPrivateVideo from '../components/chat/ChatPrivateVideo.vue';
import VideoAcceptor from '../components/chat/VideoAcceptor.vue';
import ChatVideoAcceptor from '../components/chat/ChatVideoAcceptor.vue';
export default {
@ -68,7 +68,7 @@
UserInfo,
FullImage,
ChatPrivateVideo,
VideoAcceptor
ChatVideoAcceptor
},
data() {
return {
@ -156,6 +156,8 @@
this.$store.commit("openChat", chatInfo);
//
this.$store.commit("insertMessage", msg);
//
this.playAudioTip();
},
handleGroupMessage(msg) {
//
@ -184,6 +186,8 @@
this.$store.commit("openChat", chatInfo);
//
this.$store.commit("insertMessage", msg);
//
this.playAudioTip();
},
handleExit() {
this.$http({
@ -194,6 +198,12 @@
location.href = "/";
})
},
playAudioTip(){
let audio = new Audio();
let url = require(`@/assets/audio/tip.wav`);
audio.src = url;
audio.play();
},
showSetting() {
this.showSettingDialog = true;
},

Loading…
Cancel
Save