最近在学习视频音频流,于是从最简单的WebRTC开始,从github上获取了一个很少代码的demo,只实现最基本的视频音频通话。
这是gitbub镜像地址获取的
虚拟机:Ubuntu20.04
工具:VSCode,需要插件open in browser ,node.js
总共三个文件代码:
index.js
const app = require('express')();
const wsInstance = require('express-ws')(app);
app.ws('/', ws => {
ws.on('message', data => {
// 未做业务处理,收到消息后直接广播
wsInstance.getWss().clients.forEach(server => {
if (server !== ws) {
server.send(data);
}
});
});
});
app.get('/', (req, res) => {
res.sendFile('./client/index.html', { root: __dirname });
});
app.get('/p2p', (req, res) => {
res.sendFile('./client/p2p.html', { root: __dirname });
});
app.listen(8080);
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>p2p webrtc</title>
<style>
.container {
width: 250px;
margin: 100px auto;
padding: 10px 30px;
border-radius: 4px;
border: 1px solid #ebeef5;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
color: #303133;
}
</style>
</head>
<body>
<div class="container">
<p>流程:</p>
<ul>
<li>打开<a href="http://localhost:8080/p2p?type=answer" target="_blank">接收方页面</a>;</li>
<li>打开<a href="http://localhost:8080/p2p?type=offer" target="_blank">发起方页面</a>;</li>
<li>确认双方都已建立ws连接;</li>
<li>发起方点击 start 按钮。</li>
</ul>
</div>
</body>
</html>
p2p.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.container {
width: 100%;
display: flex;
display: -webkit-flex;
justify-content: space-around;
padding-top: 20px;
}
.video-box {
position: relative;
width: 800px;
height: 400px;
}
#remote-video {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
border: 1px solid #eee;
background-color: #F2F6FC;
}
#local-video {
position: absolute;
right: 0;
bottom: 0;
width: 240px;
height: 120px;
object-fit: cover;
border: 1px solid #eee;
background-color: #EBEEF5;
}
.start-button {
position: absolute;
left: 50%;
top: 50%;
width: 100px;
display: none;
line-height: 40px;
outline: none;
color: #fff;
background-color: #409eff;
border: none;
border-radius: 4px;
cursor: pointer;
transform: translate(-50%, -50%);
}
.logger {
width: 40%;
padding: 14px;
line-height: 1.5;
color: #4fbf40;
border-radius: 6px;
background-color: #272727;
}
.logger .error {
color: #DD4A68;
}
</style>
</head>
<body>
<div class="container">
<div class="video-box">
<video id="remote-video"></video>
<video id="local-video" muted></video>
<button class="start-button" onclick="startLive()">start</button>
</div>
<div class="logger"></div>
</div>
<script>
const message = {
el: document.querySelector('.logger'),
log (msg) {
this.el.innerHTML += `<span>${new Date().toLocaleTimeString()}:${msg}</span><br/>`;
},
error (msg) {
this.el.innerHTML += `<span class="error">${new Date().toLocaleTimeString()}:${msg}</span><br/>`;
}
};
//location.search 是浏览器中 window.location 对象的一部分,表示当前页面 URL 中的查询字符串部分(即 ? 后面的内容)。
//slice(6),从第 6 个字符开始获取剩余部分
const target = location.search.slice(6);
const localVideo = document.querySelector('#local-video');
const remoteVideo = document.querySelector('#remote-video');
const button = document.querySelector('.start-button');
localVideo.onloadeddata = () => {
message.log('播放本地视频');
localVideo.play();
}
remoteVideo.onloadeddata = () => {
message.log('播放对方视频');
remoteVideo.play();
}
document.title = target === 'offer' ? '发起方' : '接收方';
message.log('信令通道(WebSocket)创建中......');
//创建 WebSocket 连接
// ws:// 是 WebSocket 协议,localhost 表示本地机器,8080 是服务器监听的端口。
const socket = new WebSocket('ws://localhost:8080');
//监听 WebSocket 连接成功
//socket.onopen 是 WebSocket 连接成功时触发的事件
socket.onopen = () => {
message.log('信令通道创建成功!');
target === 'offer' && (button.style.display = 'block');
}
//socket.onerror 监听 WebSocket 连接发生错误时触发的事件
socket.onerror = () => message.error('信令通道创建失败!');
//处理收到的 WebSocket 消息
//socket.onmessage 是 WebSocket 接收到消息时触发的事件
socket.onmessage = e => {
//解析 WebSocket 传递的消息数据。消息的格式假设为 JSON,包含三个字段:
// type:表示消息的类型(例如 offer、answer 等)。
// sdp:Session Description Protocol(会话描述协议),用于描述媒体会话的参数(例如视频编解码方式)。
// iceCandidate:ICE 候选者,WebRTC 用于建立连接的网络信息。
const { type, sdp, iceCandidate } = JSON.parse(e.data)
if (type === 'answer') {
//RTCSessionDescription 是 WebRTC 中用于描述会话的信息,包含媒体流的参数(例如视频的编码格式、分辨率等)。
peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));
} else if (type === 'answer_ice') {
//调用 peer.addIceCandidate 方法将 ICE 候选者信息(网络连接信息)添加到 WebRTC 的对等连接中。
// 这是 WebRTC 中用来建立连接的步骤
peer.addIceCandidate(iceCandidate);
} else if (type === 'offer') {
//调用 startLive 函数开始媒体会话。
//传递 RTCSessionDescription 来描述会话的 SDP 信息(如视频编解码方式)
startLive(new RTCSessionDescription({ type, sdp }));
} else if (type === 'offer_ice') {
//用来协商网络连接
peer.addIceCandidate(iceCandidate);
}
};
//window.RTCPeerConnection 是标准的 WebRTC API 实现,适用于大多数现代浏览器。
//window.mozRTCPeerConnection 是 Firefox 浏览器的实现。
//window.webkitRTCPeerConnection 是早期 WebKit 浏览器(如 Safari)的实现。
const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
//如果浏览器不支持 WebRTC,PeerConnection 会是 undefined,这时通过 !PeerConnection 判断并调用 message.error('浏览器不支持WebRTC!') 提示用户浏览器不支持 WebRTC。
!PeerConnection && message.error('浏览器不支持WebRTC!');
//peer 是一个 RTCPeerConnection 实例,负责与远端建立点对点连接并进行媒体流传输(如音频、视频)
const peer = new PeerConnection();
//peer.ontrack 是 WebRTC 中的一个事件,当远端媒体流被接收到时触发
//e.streams 是一个包含远端音频/视频流的数组。通常情况下,e.streams[0] 会是我们需要的流。
peer.ontrack = e => {
if (e && e.streams) {
message.log('收到对方音频/视频流数据...');
// 将流设置为 HTML <video> 元素(remoteVideo)的 srcObject,这会使得远端的视频流显示在用户界面中。
remoteVideo.srcObject = e.streams[0];
}
};
//peer.onicecandidate 事件会在 ICE 候选者被收集时触发
peer.onicecandidate = e => {
//如果 e.candidate 存在,表示新的候选者被收集到了
if (e.candidate) {
message.log('搜集并发送候选人');
//通过 socket.send 将候选者信息发送到对端。这是为了帮助对端使用相同的候选者建立连接。
socket.send(JSON.stringify({
//根据当前角色(发起方或响应方),它的值是 ${target}_ice,比如如果是发起方,则 type 会是 offer_ice。
type: `${target}_ice`,
iceCandidate: e.candidate
}));
} else {
message.log('候选人收集完成!');
}
};
async function startLive (offerSdp) {
//target 是一个标识当前是发起方(offer)还是接收方(answer)的变量。
// offer 表示发起连接的一方。如果是发起方,隐藏用于启动直播的按钮(例如,视频通话按钮)。
target === 'offer' && (button.style.display = 'none');
//获取本地媒体流(摄像头和麦克风)
let stream;
try {
message.log('尝试调取本地摄像头/麦克风');
stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
message.log('摄像头/麦克风获取成功!');
localVideo.srcObject = stream;
} catch {
message.error('摄像头/麦克风获取失败!');
return;
}
message.log(`------ WebRTC ${target === 'offer' ? '发起方' : '接收方'}流程开始 ------`);
message.log('将媒体轨道添加到轨道集');
//stream.getTracks() 获取媒体流中的所有轨道(视频和音频轨道)
stream.getTracks().forEach(track => {
//每个轨道添加到 RTCPeerConnection 的轨道集合中,使得这些轨道可以通过 WebRTC 进行传输。
peer.addTrack(track, stream);
});
//根据是否传入 offerSdp 来分两种情况处理,分别是发起方和接收方
//如果是发起方(offerSdp 为 false):
if (!offerSdp) {
message.log('创建本地SDP');
//创建一个本地的 SDP(Offer)
const offer = await peer.createOffer();
//设置本地描述
await peer.setLocalDescription(offer);
message.log(`传输发起方本地SDP`);
//将本地 SDP(即 Offer)发送给远端设备。offer 会被序列化为 JSON 格式进行发送。
socket.send(JSON.stringify(offer));
} else {
message.log('接收到发送方SDP');
await peer.setRemoteDescription(offerSdp);
message.log('创建接收方(应答)SDP');
//创建一个应答 SDP(Answer)
const answer = await peer.createAnswer();
message.log(`传输接收方(应答)SDP`);
//将应答 SDP(Answer)发送给远端设备
socket.send(JSON.stringify(answer));
await peer.setLocalDescription(answer);
}
}
</script>
</body>
</html>
启动命令:
node index.js启动端口
打开index.html在网页中,分别点击两个链接,然后点击发送方按钮,实现视频通信
注意事项:
我的是笔记本,虚拟机中要能使用摄像头需要有两步操作:
1,选择虚拟机设置中的USB控制器,USB兼容性选USB 3.1
2,选择虚拟机的可移动设备->Quanta HP HD Camera->连接