Browse Source

视频聊天功能-开发中

master
xie.bx 3 years ago
parent
commit
c427d3c63d
  1. 21
      im-ui/src/api/enums.js
  2. 2
      im-ui/src/components/chat/ChatBox.vue
  3. 322
      im-ui/src/components/chat/ChatPrivateVideo.vue
  4. 124
      im-ui/src/components/chat/VideoAcceptor.vue

21
im-ui/src/api/enums.js

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

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

@ -36,7 +36,7 @@
</div>
<div title="发送语音" class="el-icon-microphone" @click="showVoiceBox()">
</div>
<div title="发起视频" class="el-icon-phone-outline" @click="showVideoBox()">
<div title="视频聊天" v-show="chat.type=='PRIVATE'" class="el-icon-phone-outline" @click="showVideoBox()">
</div>
<div title="聊天记录" class="el-icon-chat-dot-round" @click="showHistoryBox()"></div>
</div>

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

@ -0,0 +1,322 @@
<template>
<el-dialog :title="title" :visible.sync="visible" width="800px" :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.9)">
<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>
export default {
name: 'chatVideo',
props: {
visible: {
type: Boolean
},
friend: {
type: Object
},
master: {
type: Boolean
},
offer: {
type: Object
}
},
data() {
return {
stream: null,
loading: false,
peerConnection: null,
state: 'NOT_CONNECTED',
candidates: [],
configuration: {
iceServers: [{
"urls": navigator.mozGetUserMedia ? "stun:stun.services.mozilla.com" : navigator.webkitGetUserMedia ?
"stun:stun.l.google.com:19302" : "stun:23.21.150.121"
},
{
urls: "stun:stun.l.google.com:19302"
}
]
}
}
},
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;
this.$refs.mineVideo.srcObject = stream;
this.$refs.mineVideo.muted = true;
callback(stream)
},
(error) => {
this.$message.error("打开摄像头失败:" + error);
callback()
});
},
closeCamera(){
if(this.stream){
this.stream.getVideoTracks().forEach((track) =>{
track.stop();
this.$refs.mineVideo.srcObject = null;
});
this.stream = null;
}
},
setupPeerConnection(stream) {
this.peerConnection = new RTCPeerConnection(this.configuration);
this.peerConnection.onaddstream = (e) => {
console.log("onaddstream")
this.$refs.friendVideo.srcObject = e.stream;
};
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
if(this.state == 'CONNECTED'){
// ,
this.sendCandidate(event.candidate);
}else{
// ,
this.candidates.push(event.candidate)
}
}
}
if (stream) {
this.peerConnection.addStream(stream);
}
},
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';
// candidate
this.candidates.forEach((candidate) => {
this.sendCandidate(candidate);
})
}
if (msg.type == this.$enums.MESSAGE_TYPE.RTC_REJECT) {
this.$message.error("对方拒绝了您的视频请求");
this.peerConnection.close();
//
this.loading = false;
//
this.state = 'NOT_CONNECTED';
}
if (msg.type == this.$enums.MESSAGE_TYPE.RTC_FAILED) {
this.$message.error(msg.content)
//
this.loading = false;
//
this.state = 'NOT_CONNECTED';
}
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("对方已挂断");
this.close();
}
},
call() {
this.peerConnection.createOffer((offer) => {
this.peerConnection.setLocalDescription(offer);
this.$http({
url: `/webrtc/private/call?uid=${this.friend.id}`,
method: 'post',
data: offer
}).then(()=>{
this.loading = true;
this.state = 'CONNECTING';
});
},
(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: answer
})
this.state='CONNECTED';
},
(error) => {
this.$message.error(error);
});
},
handup() {
this.$http({
url: `/webrtc/private/handup?uid=${this.friend.id}`,
method: 'post'
})
this.close();
},
cancel(){
this.$http({
url: `/webrtc/private/cancel?uid=${this.friend.id}`,
method: 'post'
})
this.close();
},
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: candidate
})
},
close() {
this.$emit("close");
this.closeCamera();
this.loading = false;
this.state = 'NOT_CONNECTED';
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;
},
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;
}
},
watch: {
visible: {
handler(newValue, oldValue) {
if (newValue) {
this.init();
//
this.$store.commit("setUserState",this.$enums.USER_STATE.BUSY);
console.log(this.$store.state.userStore.state)
}
}
}
},
computed: {
title() {
return `视频聊天-${this.friend.nickName}`;
}
}
}
</script>
<style lang="scss">
.chat-video {
.chat-video-box {
position: relative;
border: #2C3E50 solid 1px;
background-color: #eeeeee;
.chat-video-friend {
height: 600px;
video {
width: 100%;
height: 100%;
}
}
.chat-video-mine {
position: absolute;
z-index: 99999;
width: 200px;
right: 0;
bottom: 0;
video {
width: 100%;
}
}
}
.chat-video-controllbar {
display: flex;
justify-content: space-around;
padding: 10px;
.icon {
font-size: 50px;
cursor: pointer;
}
}
}
</style>

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

@ -0,0 +1,124 @@
<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: 10px;
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>
Loading…
Cancel
Save