文中需要用到的技术栈 pnpm、React、TypeScript 等
什么叫PeerJS
最先或是使出官网网址 PeerJS。
PeerJS 外包装了浏览器 WebRTC(Web Real Time Communication,互联网实时通信) API,帮我们快速地完成P2P(点到点)联接,简单说,P2P 就是一个相等互联网,它能使两部电子计算机立即通讯,相互之间网络服务器与手机客户端,而无需再一个传统 Server-Client 构造。
根据那样的特性大家就可以使用纯前端的计划方案就可以做到无线投屏,而无需一个屏幕视频流网络服务器 视频采集信号接收器的构造。
假如我们直接用 WebRTC API 其实其实是可以完成这个功能的,只是要的前置技术点较多,API 的获取也相对复杂一些,而 PeerJS 帮助我们简单化了那一系列实际操作,你几乎都不需要知道什么叫 P2P 通信。
建立 Peer 网络服务器
前边并不是说 P2P 通讯不用网络服务器吗,那 Peer 服务器是什么呢?
实际上 P2P 并不是在大多数前提下都可以彻底摆脱云服务器,有时候我们的通信都会被网络防火墙等东西隔绝,所以这也是我们就要一个相近中介 Peer 网络服务器来协助2个连接点可以相互之间寻找另一方,当然这中介公司网络服务器并不能被用来传送数据,“牵完线搭完桥,他就撤了”。
应用 PeerServer 来建立自己的网站
GitHub - peers/peerjs-server: Server for PeerJS
组装
pnpm i peer
代码编写
const { PeerServer } = require('peer');
const peerServer = PeerServer({ port: 9000, path: '/myPeerServer' });
Web 网页页面应用
new Peer({ host: 'localhost', port: 9000, path: '/myPeerServer' })
应用 PeerServer Cloud 给予服务器
PeerJS - Sign up for PeerServer Cloud
组装
pnpm i peerjs
Web 网页页面应用
new Peer()
实际上我们在使用 PeerJS 立即 new 时他会自动连接 PeerJS 为我们提供全部免费 PeerServer Cloud service。
连接点间的消息发送
重要编码
// 推送
const conn = peer.current.connect(friendId);
conn.on('open', () => {
console.log('Connected.');
conn.send({ id, msg: 'Hello, my friend!' });
});
// 接受
peer.current.on('connection', conn => {
conn.on('data', data => {
const received = data as SendData;
console.log(`Data from Peer(id: ${received.id}) => ${received.msg}`);
});
});
实际效果
// 推送
const conn = peer.current.connect(friendId);
conn.on('open', () => {
console.log('Connected.');
conn.send({ id, msg: 'Hello, my friend!' });
});
// 接受
peer.current.on('connection', conn => {
conn.on('data', data => {
const received = data as SendData;
console.log(`Data from Peer(id: ${received.id}) => ${received.msg}`);
});
});
共享桌面视频采集捕捉及传送
重要编码
// 推送桌面视频流
const sendMediaStream = () => {
try {
window.navigator.mediaDevices.getDisplayMedia({ video: true })
.then(mediaStream => {
peer.current.call(friendId, mediaStream);
});
} catch (e) {
console.error(e);
alert('Send failed.');
}
};
// 接受桌面视频流信息内容
peer.current.on('call', call => {
call.answer();
call.on('stream', remoteStream => {
if (myVideo.current) {
myVideo.current.srcObject = remoteStream;
myVideo.current.play();
}
});
});
实际效果
// 推送桌面视频流
const sendMediaStream = () => {
try {
window.navigator.mediaDevices.getDisplayMedia({ video: true })
.then(mediaStream => {
peer.current.call(friendId, mediaStream);
});
} catch (e) {
console.error(e);
alert('Send failed.');
}
};
// 接受桌面视频流信息内容
peer.current.on('call', call => {
call.answer();
call.on('stream', remoteStream => {
if (myVideo.current) {
myVideo.current.srcObject = remoteStream;
myVideo.current.play();
}
});
});
我搭建了一个当地web网页页面,我从我个人电脑上打开一个网页页面,作为一个 Peer。
再去我 iPad 上都开启这一网页页面,做为另一个 Peer。
我将电脑显示屏发送到 iPad 的 Web 页面中,这也是 iPad 上的截图:
详细编码
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import { Peer } from 'peerjs';
interface SendData {
id: string;
msg: string;
}
function App() {
const [id, setId] = useState('');
const [friendId, setFriendId] = useState('');
const peer = useRef(new Peer());
const myVideo = useRef<HTMLVideoElement>(null);
const handleFriendIdChange = (e: ChangeEvent<HTMLInputElement>) => {
setFriendId(e.target.value);
};
const sendMediaStream = () => {
try {
window.navigator.mediaDevices.getDisplayMedia({ video: true })
.then(mediaStream => {
peer.current.call(friendId, mediaStream);
});
} catch (e) {
console.error(e);
alert('Send failed.');
}
};
const sendData = () => {
if (!friendId) {
return;
}
const conn = peer.current.connect(friendId);
conn.on('open', () => {
console.log('Connected.');
conn.send({ id, msg: 'Hello, my friend!' });
sendMediaStream();
});
};
useEffect(() => {
peer.current.on('open', peerId => {
setId(peerId);
});
}, []);
// 用以接受其他结点推送来消息和流媒体播放信息内容
useEffect(() => {
if (!id) {
return;
}
peer.current.on('connection', conn => {
conn.on('data', data => {
const received = data as SendData;
console.log(`Data from Peer(id: ${received.id}) => ${received.msg}`);
});
});
peer.current.on('call', call => {
call.answer();
call.on('stream', remoteStream => {
if (myVideo.current) {
myVideo.current.srcObject = remoteStream;
myVideo.current.play();
}
});
});
}, [id]);
return (
<>
<p>My Peer ID: {id}</p>
<div>
<span>Friend Peer ID: </span>
<input type="text" value={friendId} onChange={handleFriendIdChange} style={{ width: 350 }} />
<input type="button" value="Send" onClick={sendData} style={{ marginInlineStart: 16 }} />
</div>
<video ref={myVideo} />
</>
);
}
export default App;
结束语
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import { Peer } from 'peerjs';
interface SendData {
id: string;
msg: string;
}
function App() {
const [id, setId] = useState('');
const [friendId, setFriendId] = useState('');
const peer = useRef(new Peer());
const myVideo = useRef<HTMLVideoElement>(null);
const handleFriendIdChange = (e: ChangeEvent<HTMLInputElement>) => {
setFriendId(e.target.value);
};
const sendMediaStream = () => {
try {
window.navigator.mediaDevices.getDisplayMedia({ video: true })
.then(mediaStream => {
peer.current.call(friendId, mediaStream);
});
} catch (e) {
console.error(e);
alert('Send failed.');
}
};
const sendData = () => {
if (!friendId) {
return;
}
const conn = peer.current.connect(friendId);
conn.on('open', () => {
console.log('Connected.');
conn.send({ id, msg: 'Hello, my friend!' });
sendMediaStream();
});
};
useEffect(() => {
peer.current.on('open', peerId => {
setId(peerId);
});
}, []);
// 用以接受其他结点推送来消息和流媒体播放信息内容
useEffect(() => {
if (!id) {
return;
}
peer.current.on('connection', conn => {
conn.on('data', data => {
const received = data as SendData;
console.log(`Data from Peer(id: ${received.id}) => ${received.msg}`);
});
});
peer.current.on('call', call => {
call.answer();
call.on('stream', remoteStream => {
if (myVideo.current) {
myVideo.current.srcObject = remoteStream;
myVideo.current.play();
}
});
});
}, [id]);
return (
<>
<p>My Peer ID: {id}</p>
<div>
<span>Friend Peer ID: </span>
<input type="text" value={friendId} onChange={handleFriendIdChange} style={{ width: 350 }} />
<input type="button" value="Send" onClick={sendData} style={{ marginInlineStart: 16 }} />
</div>
<video ref={myVideo} />
</>
);
}
export default App;
在研发以上项目时也碰到很多坑,包含但是不限于:
- 电脑浏览器管理权限:我们应该设置浏览器共享屏幕或是浏览监控摄像头、声频等管理权限,有的时候必须设定以后重新启动电脑浏览器
- 最好使用 https 协议书,不然在其他设备中访问的情况下可能会致使
window.navigator.mediaDevices.getDisplayMedia
等 API 不能用,电脑浏览器控制面板立马就出错了,可是它并不想让你知道什么不安全性或是权限不足什么的,反而是告知你没有这一方法 - P2P 能否连到很可能受到现阶段所属网络连接设置、网络防火墙的限制,对于哪种情况下能被限定,因为我也了解得不太深入......
之上作用只是展现了一些 PeerJS 的非常小一部分作用,实际上,视频采集的品质、屏幕分辨率等都是能够调整和配备的,之上新项目并没有做展现,你如果在工程含有必须能够来找有关 API 开展健全。
最终,不得不承认,我们不仅能用 PeerJS
换句话说 WebRTC
做无线投屏,还可以做实时监控闲聊、直播间、远程桌面连接等,总而言之作用十分强大。