
几天前,在上课的时候,老师的麦克风连接不上展台的电子白板,所以就无法扩音.这让我产生了一个想法,如果有一个工具,能利用手机作为麦克风,电脑1作为展示端,最好还能允许其他电脑将自己的屏幕画面推送到展示端去,是不是就能改善这个问题了呢?说干就干,我总结了这个工具的一些特点:
- 不需要安装,而且最好使用方便,操作简单,因此排除各大视频会议软件
- 传输延迟低
- 保密性,最好流传输时不经过服务器,如果一定要经过,那就经过自己的服务器
针对于以上特点,进行技术选型的时候,我做了以下选择
- 基于浏览器,只有基于浏览器,才能实现真正意义的无需安装,而且大多数电脑现在都配备了较新的浏览器,可以使用新特性
- 视频/音频传输方案使用webrtc的P2P,如果无法P2P,就通过自己的服务器进行中转
名词解释:
- stun: 用于webrtc的P2P连接中的nat打洞的协议,也就是说,如果要实现P2P连接,他是必不可少的.视频音频流量是不会通过stun服务器的,他只负责建立连接.
- turn: 用于webrtc的P2P连接中的中继的协议,如果stun的nat打洞失败(比如防火墙限制),turn可以作为公网中继,这时的rtc并不是P2P连接,而是Peer --> Server --> Peer连接,视频和音频流量需要经过此服务器
- SDP: rtc建立P2P连接的时候,双方需要交换对方的网络信息,而sdp是一种描述格式,他定义了交换过程需要发送给对方的字符串的统一格式
- ice: rtc建立连接的协议集,stun和turn是他的子集,他还包括了SDP,客户端尝试建立P2P连接时使用ice服务器列表来建立最短,最优化的连接
- Coturn: 一个开源的rtc stun + turn服务器实现
- Docker: 容器引擎,用于快速部署coturn
- docker-compose: 容器编排引擎,用于快速部署coturn
- socketio: 一种基于websocket的RPC框架,每一个peer都需要与公网上的socketio连接并在P2P连接尚未建立时交换Peers的信息.又称为信令服务器.
确定了方案以后,就开始了实施.首先部署了位于公网的coturn服务器部署,这里不展开解释,直接上docker相关配置文件.
# docker-compose.yml
version: "3"
services:
coturn:
build: ./coturn
restart: always
network_mode: "host"
# coturn/Dockerfile
FROM instrumentisto/coturn:latest
COPY turnserver.conf /etc/coturn/turnserver.conf
# coturn/turnserver.conf
listening-port=3478
listening-ip=阿里云内网IP
relay-ip=阿里云内网IP
min-port=49152
max-port=65535
fingerprint
lt-cred-mech
user=xqe2011:xqe2011
realm=test
no-multicast-peers
no-tcp
no-tls
no-cli
WebRTC建立连接分为几个角色:
对于Peer1(发起端)而言:
- 初始化socketio连接.
let sio = io(window.location.protocol + "//" + window.location.host);
- 初始化PeerConnection,配置ice服务器列表,我们只有1台stun+turn服务器,所以只需要配置候选列表使接收到来自服务器下发的信息(candidate)并将其通过发送到对端上.同时我们需要配置屏幕视频流,配置好后一旦连接建立,视频流就会启动传输.
let webrtcConfiguration = {
'iceServers': [
{
urls: 'turn:服务器地址:3478'
}
],
};
rtc = new RTCPeerConnection(webrtcConfiguration);
rtc.addEventListener('icecandidate', (candidate) => {
if (candidate.candidate != null) {
sio.emit("candidate", candidate.candidate);
}
});
// 请求获取屏幕视频流,并将其配置到rtc中
let displayMediaOptions = {
video: {
cursor: "always"
},
audio: true
};
media = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
rtc.addTrack(media.getVideoTracks()[0], media);
- 发送Offer给对端
const offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
const offer = await rtc.createOffer(offerOptions);
await rtc.setLocalDescription(offer);
sio.emit('offer', offer);
- 等待对端发送answer,并配置
sio.on('answer', (ans) =>
rtc.setRemoteDescription(ans);
});
- 连接建立,webrtc会自动分析网络,如果可以,会通过stun建立P2P的连接.如果不可以,会通过中继turn服务器建立连接.
对于Peer2(接受端)而言:
- 与Peer1相同
- 除了不需要最后的“配置视频流发送”,其他与Peer1相同
- 等待接受offer,并回应answer
sio.on('offer', async (offer) => {
try {
await rtc.setRemoteDescription(offer);
const answer = await rtc.createAnswer();
await rtc.setLocalDescription(answer);
sio.emit('answer', answer);
} catch (e) {
console.log("[WebRTC] Get answer failed:", e);
}
});
- 等待接受视频流
rtc.addEventListener('track', obj => {
document.getElementById('mainVideo').srcObject = obj.streams[0];
});
对于Sockerio服务端而言:
并没有什么好说的,只需要接受客户端的offer,answer,candidate,直接广播即可.
中间遇到的坑:
- 获取不到candidate或获取到了也转发了就是怎么也连接不上.
解决方法: 检查是否开启了代理,包括各种系统级代理,浏览器代理,关闭即可.
- 获取到视频流了(即Peer2.4)中,也配置成功了,就是怎样都没声音
解决方法: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes, 如果只是为了调试,可以在safari浏览器中的“此网站设置”中选择“自动播放”中的“允许全部自动播放”.
开源
这个项目时开源的,但由于时间较赶,代码质量不佳,仅供参考.
Star