视频通话流程
通话发起方点击接收方的名字向对方发起视频通话,通话接收方可以选择接听或者拒绝接听。如果接听,双方都会看到对方的视频,如果拒绝接听,发起方会接收到拒绝提示。
发起通话
step1 . 前端
通过socket.emit发送视频发起方和接收方的信息
let socket
export const connectWithSocket = ()=>{
socket = socketClient(SERVER)
...
}
socket.emit("pre-offer", {
//呼叫方和应答方的信息
})
step2 . 后端
接收信息:通过socket.on监听视频发起方发送的信息
发送信息:通过io.to()指定视频接收方,通过.emit发送视频发起方的信息
io.on("connection", socket=>{
socket.emit("connection", null)
...
// 监听客户端发送过来的预呼叫并获取data,传递给应答方
socket.on("pre-offer", data=>{
// 向应答方发送data数据
io.to(data.callee.socketId).emit("pre-offer", {
//呼叫方的信息
})
})
})
step3 . 前端
通过socket.on接收信息,并在客户端展示呼叫弹出框
let socket
export const connectWithSocket = ()=>{
socket = socketClient(SERVER)
socket.on("pre-offer", data=>{
//进行想要的操作
})
...
}
回复通话请求
步骤同上
step1 : 前端通过socket.emit发送接收方的回复信息和发送者信息
step2 : 后端通过socket.on监听前端发送的信息,通过io.to()指定视频发起方,通过.emit发送接收方的回复信息
step3 : 前端通过socket.on接收信息,就可以获取应答方的回复信息了。然后就可以根据应答方是否拒绝接听来进行下一步操作
应答方接听视频通话
前期准备
在页面加载时就创建对等连接,即下面的操作
let peerConnection;
const configuration = {
iceServers: [{
urls: "stun:stun.l.google.com:19302"
}]
}
// 创建对等连接
const createPeerConnection = ()=>{
peerConnection = new RTCPeerConnection(configuration)
const localStream = store.getState().localStream;
// addTrack为初始化后的本地流对象添加音视频轨道,若该本地流已经被发布,则该流会自动重新发布到远端
for(const track of localStream.getTracks()){
peerConnection.addTrack(track, localStream)
}
// 每当远端的音视频数据传递过来,onTrack事件就会被触发
peerConnection.ontrack = ({streams:[stream]})=>{
//stream即为远端数据流
}
}
操作(SDP交换)
step1. 发起方得知应答方同意视频通话后,通过信令服务器给应答方发送SDP
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer)
// offer发送SDP到信令服务器
socket.emit("webRTC-offer",{
calleeSocketId: connectUserSocketId,
offer: offer
})
step2. 应答方获取SDP后,通过信令服务器给发起方发送SDP answer
socket.on("webRTC-offer", data=>{
await peerConnection.setRemoteDescription(data.offer)
// 创建answer SDP
const answer = await peerConnection.createAnswer()
await peerConnection.setLocalDescription(answer)
socket.emit("webRTC-answer",{
callerSocketId: connectUserSocketId,
answer: answer
})
})
step3. 发起方获取到SDP answer后
socket.on("webRTC-answer", data=>{
await peerConnection.setRemoteDescription(data.answer)
})
获取视频
该项目中的音视频分为两种,分别是本地音视频流和远端发送的音视频流。
所以,接下来我们一个一个实现。
获取本地视频流
前端
我们可以通过navigator.mediaDevices.getUserMedia(defaultConstrains)来获取本地视频流,但其返回的结果是promise对象,所以我们需要用then和catch来接收结果。
如果浏览器允许打开摄像头和麦克风,那么promise会返回成功的结果,结果值就是音视频流;如果不允许,promise会返回失败的结果
const defaultConstrains = {
video: {
width: 480,
height: 360
},
audio: true
}
navigator.mediaDevices.getUserMedia(defaultConstrains)
.then(stream=>{
//stream为本地视频流
}).catch(error=>{
console.log("获取本地媒体流访问权限失败", error);
})
获取远端视频流
在前面创建对等连接时,我们已经可以通过peerConnection.ontrack来获取远端视频流
peerConnection.ontrack = ({streams:[stream]})=>{
//stream即为远端数据流
}
展示视频
展示视频我们可以通过 < v i d e o / >实现
useEffect(()=>{
if(stream){
const Video = videoRef.current;
Video.srcObject = stream;
Video.onloadedmetadata = ()=>{
Video.play()
}
}
},[stream])
return <div>
<video ref={videoRef} autoPlay/>
</div>
结束通话
结束通话需通过服务器告知另一方,并且重置状态
step1: 前端
// 通过服务器向另一方告知想要挂断
socket.emit("user-hanged-up", {
//另一方的用户id
})
// 挂断之后进行状态的重置
//关闭并清空peerConnection之后重新连接
peerConnection.close()
peerConnection = null
createPeerConnection()
//如果桌面共享处于开启状态,那么结束桌面共享
if(
//桌面共享开启状态
){
screenSharingStream.getTracks().forEach(track=> track.stop())
}
step2: 后端
// 监听挂断的通知
socket.on("user-hanged-up", data=>{
io.to(前端传过来的另一方的用户id).emit("user-hanged-up")
})
step3: 前端
这一部分的操作是在进行状态的重置,与step1中的状态重置是相同的
// 监听服务器挂断
socket.on("user-hanged-up", ()=>{
// 监听到挂断之后进行状态的重置
//关闭并清空peerConnection之后重新连接
peerConnection.close()
peerConnection = null
createPeerConnection()
//如果桌面共享处于开启状态,那么结束桌面共享
if(
//桌面共享开启状态
){
screenSharingStream.getTracks().forEach(track=> track.stop())
}
})