基于WebRTC实现音视频通话

最近闲暇时间(摸鱼时间很难熬)都在搞这个仿微信的项目,所以接了个音视频通话。

因为域名备案死活过不了,暂时用ngrok内网穿透的,带宽很慢,同时为了能够正常访问到图片,也请允许访问f09cebf3fbe0.ngrok-free.app

在线体验

已存在用户:13006789001~13006789010 密码都是123456

前端项目:github.com/Juenfy/cove...

后端项目:github.com/Juenfy/cove...

npm包:github.com/Juenfy/vue-...

客户端采用WebRTC技术(推流),通讯用websocket。

WebRTC像是一个面试过程: 第一步:发起方(拨打电话者)点击拨打电话时,获取本地媒体流并推流给接收方同时捕获接收方推过来的流,捕获到后把流设置到dom上,监听ICE候选确保能点对连接,生成offer,通过websocket告知接收方并拉起等待接听界面。

javascript 复制代码
//获取媒体流
stream.value = await navigator.mediaDevices.getUserMedia({
	video: true,
	audio: true
});

// 初始化 PeerConnection
peerConnection.value = new RTCPeerConnection({
    iceServers: [
        {
            urls: 'stun:stun.l.google.com:19302'
        }
    ]
});

// 推流给接收方
stream.value.getTracks().forEach((track) => {
	peerConnection.value.addTrack(track, stream.value);
});

// 捕获接收方的流
peerConnection.value.ontrack = (event) => {
	remoteStream.value = event.streams[0];
	if (callType.value === TypeVideo) {
		remoteVideo.value.srcObject = remoteStream.value;
	} else {
		remoteAudio.value.srcObject = remoteStream.value;
	}
};

// 监听ICE候选,确保 WebRTC 的点对点连接能够成功建立
peerConnection.value.onicecandidate = (event) => {
	if (event.candidate) {
		//发送candidate
		ws.send(event.candidate);
	}
};
// 创建 offer
const offer = await peerConnection.value.createOffer();
await peerConnection.value.setLocalDescription(offer);

//发送offer,这里发送的offer可以理解成是接收方用来捕获发起方流的一个凭证,接收方通过peerConnection.value.ontrack可以捕获到。
ws.send(offer);
//拉起等待接听界面
showCall.value = true;
//状态等待接听
callStatus.value = 'wating';

第二步:接收方收到offer后,第一步是拉起来电界面,第二步是选择接听或者挂断。 1)拉起来电接听界面

javascript 复制代码
//拉起来电接听界面
showCall.vue = true;
//状态来电接听
callStatus.value = 'coming';
//初始化来电人信息等
....

2)挂断,就是告诉发起方我挂断了,发起方就把RTC关掉、停止推流,dom置空就好了

javascript 复制代码
//接收方
showCall.value = false;
callStatus.value = 'closing';
ws.send('reject');

//发起方
if (peerConnection.value) {
	peerConnection.value.close();
	peerConnection.value = null;
}
if (stream.value) {
	const tracks = stream.value.getTracks();
	tracks.forEach((track) => track.stop());
}
if (localVideo.value)
	localVideo.value.srcObject = null;
if (remoteVideo.value)
	remoteVideo.value.srcObject = null;
if (remoteAudio.value)
	remoteAudio.value.srcObject = null;
showCall.value = false;
callStatus.value = 'closing';

2)接听,操作跟拨打流程差不多,需要设置远端SDP(发起方的offer),添加ICE候选(发起方的ice,这里需要注意的是只有远端SDP初始化完毕状态下才能设置ice)

javascript 复制代码
// 获取本地媒体流
...同发起方
// 初始化 PeerConnection
...同发起方
// 推流给发起方
...同发起方
// 捕获发起方的流
...同发起方
// 监听ICE候选
...同发起方

//设置远端SDP
await peerConnection.value.setRemoteDescription(new RTCSessionDescription(caller.value.offer));

// 添加发起方发过来的ice
iceCandidateQueue.value.forEach(async (candidate) => {
await  peerConnection.value.addIceCandidate(candidate);
});
iceCandidateQueue.value  = [];

// 创建 answer
const  answer  =  await  peerConnection.value.createAnswer();
await  peerConnection.value.setLocalDescription(answer);

//发送answer给发起方
ws.send(answer);
//状态通话中
callStatus.value = 'calling';

关于ice的处理,就是远端SDP初始化完毕状态可以直接设置,未初始化完毕就存到iceCandidateQueue队列备用

javascript 复制代码
// 处理新的 ICE 候选
const handleNewICECandidate = async (candidate) => {
    const iceCandidate = new RTCIceCandidate(candidate);
    if (peerConnection.value?.signalingState === 'have-remote-offer' || peerConnection.value?.signalingState === 'stable') {
        peerConnection.value.addIceCandidate(iceCandidate);
    } else {
        iceCandidateQueue.value.push(iceCandidate);
    }
};

最后一步:发起方收到接收方的答复(接收方接听了),设置远端SDP(接收方的answer),设置ICE(接受方的ice)

javascript 复制代码
//设置远端SDP
await peerConnection.value.setRemoteDescription(new RTCSessionDescription(caller.value.answer));

//添加ICE
iceCandidateQueue.value.forEach(async (candidate) => {
	await peerConnection.value.addIceCandidate(candidate);
});
iceCandidateQueue.value = [];
//状态接听中
callStatus.value = 'calling';

这就是WebRTC视频通话的关键代码跟流程!

我的项目实现效果图:

相关推荐
We་ct17 分钟前
LeetCode 97. 交错字符串:动态规划详解
前端·算法·leetcode·typescript·动态规划
Chengbei1126 分钟前
轻量化 Web 安全日志分析神器 星川智盾日志威胁检测、地理溯源、MITRE ATT&CK 映射,支持 Windows/macOS/Linux
前端·人工智能·安全·web安全·macos·系统安全·安全架构
风流 少年28 分钟前
Python Web框架:FastAPI
前端·python·fastapi
GISer_Jing36 分钟前
AI时代面试新常态——从“会用工具”到“深挖原理”的跨越
前端·人工智能·ai编程
IT_陈寒1 小时前
React的useEffect把我坑惨了,这些闭包陷阱真要命
前端·人工智能·后端
前端之虎陈随易1 小时前
有生之年系列,Nodejs进程管理pm2 v7.0发布
前端·typescript·npm·node.js
ayqy贾杰1 小时前
Cursor SDK发布!开发者可直接搬走其内核
前端·vue.js·面试
椰猫子1 小时前
SpringMVC(SpringMVC简介、请求与响应(请求映射路径、请求参数、日期类型参数传递、响应json数据))
java·前端·数据库
love530love1 小时前
如何在 Google Chrome 中强制开启 Gemini AI 侧边栏(完整图文教程)
前端·人工智能·chrome·windows
光影少年1 小时前
对typescript开发框架的理解?
前端·javascript·typescript