前端WebRTC局域网1V1视频通话

基本概念

WebRTC(Web Real-Time Communications)

网络实时通讯,它允许网络应用或者站点,在不借助中间媒介的情况下,建立点对点(Peer-to-Peer)的连接,实现视频流和音频流或者其他任意数据的传输

NAT(Network Address Translation)

网络地址转换协议,用来给私网设备映射一个公网的 IP 地址

STUN(Session Traversal Utilities for NAT)

会话穿透,通过NAT找到公网地址进行P2P通信

TURN(Traversal Using Relay around NAT)

中继转发,当STUN不可用时,通过TURN转发音视频数据,显然这样是开销最大的

开源STUN&TURN服务器:coturn

ICE(Interactive Connectivity Establishment)

交互式连接建立,即网络信息

candidate:候选,优先级为:局域网、STUN、TURN

SDP(Session Description Protocol)

会话描述协议,即媒体信息,不是音视频流,在WebRTC中分为offer和answer

Signaling Server

信令服务器,用来交换ICE和SDP信息,WebRTC未做规定,自己选择实现技术,比如WebSocket

局域网视频通信

局域网不需要STUN/TURN服务器,只需信令服务器,这里使用Node.js ws库实现

效果

代码

客户端 index.html
html 复制代码
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <title>WebRTC Demo</title>
    <style>
        button {
            margin: 1rem;
        }

        video {
            width: 300px;
        }
    </style>
</head>

<body>
    <div>
        <div>
            信令服务器地址:
            <input id="inputServer" value="ws://192.168.205.165:8888" />
            <button onclick="start()">开始</button>
        </div>
        <video id="localVideo" autoplay muted></video>
        <video id="remoteVideo" autoplay muted></video>
    </div>

    <script>
        const inputServer = document.querySelector("#inputServer");
        const remoteVideo = document.querySelector("#remoteVideo");
        const localVideo = document.querySelector("#localVideo");

        let peerConn;
        let webSocket;
        let localStream;

        // 打开本地摄像头
        navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then((mediaStream) => {
            localStream = mediaStream;
            localVideo.srcObject = mediaStream;
        }).catch((err) => {
            console.error(err);
        });

        // 创建WebRTC连接
        peerConn = new RTCPeerConnection();
        peerConn.addEventListener('icecandidate', (event) => {
            if (event.candidate) {
                webSocket.send(JSON.stringify({
                    type: "ice",
                    candidate: event.candidate
                }));
            }
        });

        peerConn.addEventListener("track", (event) => {
            remoteVideo.srcObject = event.streams[0];
        });

        function start() {
            // 连接信令服务器
            webSocket = new WebSocket(inputServer.value);
            webSocket.addEventListener('open', () => {
                webSocket.send(JSON.stringify({
                    type: "join"
                }));
            });

            // 收到服务端消息
            webSocket.addEventListener('message', (event) => {
                const msg = JSON.parse(event.data);
                console.log(msg);
                switch (msg.type) {
                    case "sendOffer":
                        peerConn.addTrack(localStream.getVideoTracks()[0], localStream);
                        peerConn.createOffer({ offerToReceiveAudio: true, offerToReceiveVideo: true }).then((offer) => {
                            peerConn.setLocalDescription(offer).then(() => {
                                webSocket.send(JSON.stringify(offer));
                            })
                        });
                        break;
                    case "offer":
                        peerConn.addTrack(localStream.getVideoTracks()[0], localStream);
                        peerConn.setRemoteDescription(msg).then(() => {
                            peerConn.createAnswer().then((answer) => {
                                peerConn.setLocalDescription(answer).then(() => {
                                    webSocket.send(JSON.stringify(answer));
                                })
                            })
                        });
                        break;
                    case "answer":
                        peerConn.setRemoteDescription(msg);
                        break;
                    case "ice":
                        peerConn.addIceCandidate(msg.candidate);
                        break;
                    default:
                }
            });

            webSocket.addEventListener('close', () => {
                console.log("websocket close");
            });
        }
    </script>
</body>

</html>
服务端 server.mjs
js 复制代码
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8888 });

let clients = []; // 已连接的客户端

wss.on('connection', function connection(ws) {

	ws.on('message', function message(rawData) {
		const data = rawData.toString();
		const obj = JSON.parse(data);
		console.log("type", obj.type);
		switch (obj.type) {
			case "join":
				if (clients.length < 2) {
					clients.push(ws);
					if (clients.length === 2) {
						clients[0].send(JSON.stringify({ type: "sendOffer" }));
					}
				} else {
					console.log("room is full");
				}
				break;
			case "offer":
				clients[1].send(data);
				break;
			case "answer":
				clients[0].send(data);
				break;
			case "ice":
				clients.forEach((item) => {
					if (item !== ws) {
						item.send(data);
					}
				})
				break;
			default:
		}
	});

	ws.on('error', (err) => console.error("error:", err));

	ws.on('close', (code) => {
		console.log("ws close", code);
		clients = clients.filter((item) => {
			if (item === ws) {
				item = null;
				return false;
			}
			return true;
		});
	});
});

使用

  1. 在文件目录运行命令:node server.mjs
  2. 修改信令服务器地址,浏览器打开 index.html
  3. 将 index.html 复制到另一台电脑上用浏览器打开
  4. 允许使用摄像头和麦克风,两边点击开始按钮即可

参考资料

WebRTC_API
前端使用WebRTC

相关推荐
美狐美颜sdk8 小时前
直播APP源码可以直接接入视频美颜sdk吗?技术方案详解
android·人工智能·音视频·美颜sdk·直播美颜sdk
明月_清风8 小时前
Deno 终局来了:从挑战 Node 到被 Cloudflare 收编
前端·后端·node.js
揽秀亭长9 小时前
从音频到五线谱|扒谱过程中的关键技术环节
人工智能·音视频
Csvn9 小时前
框架性能优化
前端
回眸&啤酒鸭11 小时前
【回眸】OpenSwarm 多智能体协作系统实战指南
大数据·前端·人工智能
用户693717500138411 小时前
2026,程序员的时代拐点到了
android·前端·后端
大龄秃头程序员11 小时前
一次 iBeacon + BLE 无感解锁方案的实现记录
前端
小兔子11 小时前
Python 的 GIL 与 free-threading:3.13 之后「去 GIL」走到哪一步了
前端
IT_陈寒11 小时前
SpringBoot自动配置差点让我加班到凌晨
前端·人工智能·后端
guslegend12 小时前
脚手架原理与本地调试:从 bin 软链接到 npm link
前端·npm·node.js·脚手架·前端工程化