效果
前端代码
<template>
<div class="flex items-center flex-col text-center p-12 h-screen">
<div class="relative h-full mb-4 fBox">
<video id="localVideo"></video>
<video id="remoteVideo"></video>
<div v-if="caller && calling">
<p class="mb-4 text-white">等待对方接听...</p>
<img style="width: 60px;" @click="hangUp" src="@/assets/guaDuang.png" alt="">
</div>
<div v-if="called && calling">
<p>收到视频邀请...</p>
<div class="flex">
<img style="width: 60px" @click="hangUp" src="@/assets/guaDuang.png" alt="">
<img style="width: 60px" @click="acceptCall" src="@/assets/jieTing.png" alt="">
</div>
</div>
</div>
<div>
<button @click="callRemote" style="margin-right: 10px">发起视频</button>
<button @click="hangUp" style="margin-left: 10px">挂断视频</button>
</div>
</div>
</template>
<script>
import { io, Socket } from "socket.io-client";
let roomId = '001';
export default {
name: 'HelloWorld',
props: {
msg: String
},
data(){
return{
wsSocket:null,//实例
called:false,// 是否是接收方
caller:false,// 是否是发起方
calling:false,// 呼叫中
communicating:false,// 视频通话中
localVideo:null,// video标签实例,播放本人的视频
remoteVideo:null,// video标签实例,播放对方的视频
peer:null,
localStream:null,
}
},
methods:{
// 发起方发起视频请求
async callRemote(){
let that = this;
console.log('发起视频');
that.caller = true;
that.calling = true;
// await getLocalStream()
// 向信令服务器发送发起请求的事件
await that.getLocalStream();
that.wsSocket.emit('callRemote', roomId)
},
// 接收方同意视频请求
acceptCall(){
console.log('同意视频邀请');
this.wsSocket.emit('acceptCall', roomId)
},
// 挂断视频
hangUp(){
this.wsSocket.emit('hangUp', roomId)
},
reset(){
this.called = false;
this.caller = false;
this.calling = false;
this.communicating = false;
this.peer = null;
this.localVideo.srcObject = null;
this.remoteVideo.srcObject = null;
this.localStream = undefined;
console.log('挂断结束视频-------')
},
// 获取本地音视频流
async getLocalStream(){
let that = this;
let obj = { audio: true, video: true };
const stream = await navigator.mediaDevices.getUserMedia(obj); // 获取音视频流
that.localVideo.srcObject = stream;
that.localVideo.play();
that.localStream = stream;
return stream;
}
},
mounted() {
let that = this;
that.$nextTick(()=>{
that.localVideo = document.getElementById('localVideo');
that.remoteVideo = document.getElementById('remoteVideo');
})
let sock = io('localhost:3000'); // 对应服务的端口
// 连接成功
sock.on('connectionSuccess', (sock) => {
console.log('连接成功:');
});
sock.emit('joinRoom', roomId) // 前端发送加入房间事件
sock.on('callRemote', (sock) => {
// 如果是发送方自己收到这个事件就不用管
if (!that.caller){ // 不是发送方(用户A)
that.called = true; // 接听方
that.calling = true; // 视频通话中
}
});
sock.on('acceptCall',async ()=>{
if (that.caller){
// 用户A收到用户B同意视频的请求
that.peer = new RTCPeerConnection();
// 添加本地音视频流
that.peer.addStream && that.peer.addStream(that.localStream);
// 通过监听onicecandidate事件获取candidate信息
that.peer.onicecandidate = (event) => {
if (event.candidate) {
console.log('用户A获取candidate信息', event.candidate);
// 通过信令服务器发送candidate信息给用户B
sock.emit('sendCandidate', {roomId, candidate: event.candidate})
}
}
// 接下来用户A和用户B就可以进行P2P通信流
// 监听onaddstream来获取对方的音视频流
that.peer.onaddstream = (event) => {
console.log('用户A收到用户B的stream',event.stream);
that.calling = false;
that.communicating = true;
that.remoteVideo.srcObject = event.stream;
that.remoteVideo.play();
}
// 生成offer
let offer = await that.peer.createOffer({
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
})
console.log('offer', offer);
// 设置本地描述的offer
await that.peer.setLocalDescription(offer);
// 通过信令服务器将offer发送给用户B
sock.emit('sendOffer', { offer, roomId })
}
})
// 收到offer
sock.on('sendOffer',async (offer) => {
if (that.called){ // 接收方 - 用户B
console.log('收到offer',offer);
// 创建自己的RTCPeerConnection
that.peer = new RTCPeerConnection();
// 添加本地音视频流
const stream = await that.getLocalStream();
that.peer.addStream && that.peer.addStream(stream);
// 通过监听onicecandidate事件获取candidate信息
that.peer.onicecandidate = (event) => {
if (event.candidate) {
console.log('用户B获取candidate信息', event.candidate);
// 通过信令服务器发送candidate信息给用户A
sock.emit('sendCandidate', {roomId, candidate: event.candidate})
}
}
// 接下来用户A和用户B就可以进行P2P通信流
// 监听onaddstream来获取对方的音视频流
that.peer.onaddstream = (event) => {
console.log('用户B收到用户A的stream',event.stream);
that.calling = false;
that.communicating = true;
that.remoteVideo.srcObject = event.stream;
that.remoteVideo.play();
}
// 设置远端描述信息
await that.peer.setRemoteDescription(offer);
let answer = await that.peer.createAnswer();
console.log('用户B生成answer',answer);
await that.peer.setLocalDescription(answer);
// 发送answer给信令服务器
sock.emit('sendAnswer', { answer, roomId })
}
})
// 用户A收到answer
sock.on('sendAnswer',async (answer) => {
if (that.caller){ // 接收方 - 用户A 判断是否是发送方
// console.log('用户A收到answer',answer);
await that.peer.setRemoteDescription(answer);
}
})
// 收到candidate信息
sock.on('sendCandidate',async (candidate) => {
console.log('收到candidate信息',candidate);
// await that.peer.addIceCandidate(candidate) // 用户A和用户B分别收到candidate后,都添加到自己的peer对象上
// await that.peer.addCandidate(candidate)
await that.peer.addIceCandidate(candidate)
})
// 挂断
sock.on('hangUp',()=>{
that.reset()
})
that.wsSocket = sock;
}
}
</script>
服务端代码
const socket = require('socket.io');
const http = require('http');
const server = http.createServer()
const io = socket(server, {
cors: {
origin: '*' // 配置跨域
}
});
io.on('connection', sock => {
console.log('连接成功...')
// 向客户端发送连接成功的消息
sock.emit('connectionSuccess');
sock.on('joinRoom',(roomId)=>{
sock.join(roomId);
console.log('joinRoom-房间ID:'+roomId);
})
// 广播有人加入到房间
sock.on('callRemote',(roomId)=>{
io.to(roomId).emit('callRemote')
})
// 广播同意接听视频
sock.on('acceptCall',(roomId)=>{
io.to(roomId).emit('acceptCall')
})
// 接收offer
sock.on('sendOffer',({offer,roomId})=>{
io.to(roomId).emit('sendOffer',offer)
})
// 接收answer
sock.on('sendAnswer',({answer,roomId})=>{
io.to(roomId).emit('sendAnswer',answer)
})
// 收到candidate
sock.on('sendCandidate',({candidate,roomId})=>{
io.to(roomId).emit('sendCandidate',candidate)
})
// 挂断结束视频
sock.on('hangUp',(roomId)=>{
io.to(roomId).emit('hangUp')
})
})
server.listen(3000, () => {
console.log('服务器启动成功');
});
完整代码gitee地址: https://gitee.com/wade-nian/wdn-webrtc.git
参考文章:基于WebRTC实现音视频通话_npm create vite@latest webrtc-client -- --template-CSDN博客