前端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

相关推荐
徐小夕5 小时前
我用 AI 撸了个开源"万能预览器":浏览器直接打开 Office、CAD 和 3D 模型
前端·vue.js·github
小码哥_常6 小时前
Flutter Android 延迟加载代码指南:提升应用性能的关键
前端
这是个栗子6 小时前
TypeScript(三)
前端·javascript·typescript·react
kvo7f2JTy6 小时前
基于机器学习算法的web入侵检测系统设计与实现
前端·算法·机器学习
北风toto6 小时前
前端CSS样式详细笔记
前端·css·笔记
nanfeiyan6 小时前
git commit
前端
starvapour8 小时前
Ubuntu系统下基于终端的音频相关命令
linux·ubuntu·音视频
前端精髓9 小时前
移除 Effect 依赖
前端·javascript·react.js
码云之上9 小时前
从一个截图函数到一个 npm 包——pdf-snapshot 的诞生记
前端·node.js·github
码事漫谈9 小时前
AI提效,到底能强到什么程度?
前端·后端