WebRTC DEMO
花了两天工夫简略理解了一下WEB RTC
,并由此写入三个 DEMO。
- p2p 点对点
- o2m 一对多
- live 直播
目前次要都是按 p2p
进行的简略扩大。
WebRTC 简略理解
目前材料不算少,不过的确也不多,而且实践偏多,新手入门其实还是有点压力的。
这边举荐几个材料和视频。
MDN 文档 记得出问题看看文档先
WebRTC samples 没有思路的时候记得看看
哔哩哔哩 – 一只斌 这个 b 站 up,大略算是由浅入深讲了这个货色,然而有些根底概念被带过了(应该次要是我根底较单薄),其中圣诞特辑中的流程其实还是比拟清晰的。
知乎 – 为什么 webrtc 那么贵?留神看评论区
知乎 – 能够用 WebRTC 来做视频直播吗?留神看评论区
WebRTC 的一些概念
WebRTC 根底状况下只须要一个‘信令服务’作为业务需要,并不需要间接治理流。
p2p
点对点通信(pc 与 pc 间接通信),不通过服务器
信令服务
用于建设通信和业务交互的服务端
SDP
寄存媒体信息、会话的形容。如编码解码信息
NAT / STUN / TURN / ice
strn
用于 p2p 连贯。(上面仅集体了解
因为没有公网 ip 的两个主机没有方法间接进行间接通信,所以须要一个“直达的服务器”,然而因为直达服务器过于依赖服务器带宽,所以采纳NAT 穿刺
,这样单方通信就不须要依赖服务器。
ice
整合了 STUN 和 TURN 的框架
理论具体不须要管,ice 服务器能够应用公开的
实际
webrtc 建设还是很简略的,只须要替换单方 sdp
和ice-candidate
,即可建设通信。
具体流程
p2p 1 对 1 视频
呼叫方创立
offer sdp
接管方依据offer sdp
创立answer sdp
一、sdp 替换
- 呼叫方 建设 WebRTC
- 接管方 期待 信令服务器 转发 类型为
offer
的sdp
- 呼叫方 监听
onnegotiationneeded
并创立offer sdp
并调用setLocalDescription
设置为本地形容 - 呼叫方 向 信令服务器 发送
offer sdp
并监听answer sdp
- 接管方 失去
offer sdp
并调用setRemoteDescription
设置为近程形容 - 接管方 创立
answer sdp
并设置本地形容(setLocalDescription
)同时向 信令服务器 发送answer sdp
- 呼叫方 收到
answer sdp
并设近程形容(setRemoteDescription
)
二、ice-candidate 替换
- 监听
onicecandidate
失去candidate
后进行发送 - 监听
信令服务器
ice-candidate 失去后调用addIceCandidate
获取媒体 流
function getUserMedia(constrains) {
let promise = null;
if (navigator.mediaDevices.getUserMedia) {
// 最新规范 API
promise = navigator.mediaDevices.getUserMedia(constrains)
} else if (navigator.webkitGetUserMedia) {
//webkit 内核浏览器
promise = navigator.webkitGetUserMedia(constrains)
} else if (navigator.mozGetUserMedia) {
//Firefox 浏览器
promise = navagator.mozGetUserMedia(constrains)
} else if (navigator.getUserMedia) {
// 旧版 API
promise = navigator.getUserMedia(constrains);
}
return promise;
}
// 失去流
const stream = await getUserMedia({
video: true,
audio: true,
});
// 展现
$('video').srcObject = stream;
信令服务器
demo 应用 node + socket-io 做信令服务器
目前逻辑很简略,只为数据定向转发
目前次要对 sdp
和ice candidate
进行一个定向转发
const SocketIo = require('socket.io');
const consola = require('consola'); // log 工具
const users = new Map(); // 用户存储
/**
*
* @param {http.Server} server
*/
const signaling = (server) => {
// 创立 socketio 服务
const io = new SocketIo.Server(server);
const p2p = io.of('/p2p');
// 连贯
p2p.on('connect', (socket) => {consola.info('[%s] connect', socket.id);
// **********
// 用户操作
// sdp 转发
socket.on('sdp', (data) => {console.log('sdp data.to[%s] type[%s]', data.to, data.type);
const user = users.get(data.to)
if (user) {user.emit('sdp', data);
}
});
// ice-candidate 转发
socket.on('ice-candidate', (data) => {console.log('ice-candidate data.to', data.to);
const user = users.get(data.to)
if (user) {user.emit('ice-candidate', data);
}
});
// 用户操作
// **********
// ----------
// 用户操作
socket.once('disconnect', () => {consola.info('[%s] disconnect', socket.id);
users.delete(socket.id);
p2p.emit('leave', {user: socket.id});
});
socket.emit('users', {users: Array.from(users.keys())
});
p2p.emit('join', {user: socket.id});
users.set(socket.id, socket);
// 用户操作
// ----------
});
};
module.exports = signaling;
建设 WebRTC
这里在代码中辨别了发送和接管具体可参考业务
呼叫方
// ************
// 呼叫方
// ************
// 1. 建设 rct 连贯
const pc = new RTCPeerConnection({
iceServers: [
{urls: ["stun:stun.counterpath.net:3478"] // 能够间接百度找一些凋谢的 stun 服务器
}
]
});
// 2. 绑定流
const stream = await getUserMedia({
video: true,
audio: true,
});
// 增加媒体轨道 如果 video 和 audio 都为 true 则 getTracks 能够取得两个轨道
stream.getTracks().forEach(track => pc[toUser].addTrack(track, stream));
// 3. 监听
pc.onnegotiationneeded = ()=>{
pc
.createOffer() // 创立 offer sdp
.then((offer) => {
// 设置为本地形容
return pc.setLocalDescription(offer);
})
.then(() => {
// 定向转发 sdp
socket.emit('sdp', {
type: 'sender',
value: pc.localDescription
});
});
}
pc.onicecandidate = (ev)=>{
// 转发 ice-candidate
socket.emit('ice-candidate', {
type: 'sender',
value: ev.candidate,
});
}
pc.ontrack = (ev)=>{
// 这里能够的失去对方的流
let stream = ev.streams[0];
}
// 监听 ice 和 sdp
socket.on('ice-candidate', (data)=>{if(data.type === 'receive'){const candidate = new RTCIceCandidate(data.value);
pc.addIceCandidate(candidate)
}
});
socket.on('sdp', (data)=>{if(data.type === 'receive'){const sdp = new RTCSessionDescription(data.value);
pc.setRemoteDescription(sdp);
}
});
接管方
// ************
// 接管方
// ************
// 建设 rct 连贯
const pc = new RTCPeerConnection({
iceServers: [
{urls: ["stun:stun.counterpath.net:3478"] // 能够间接百度找一些凋谢的 stun 服务器
}
]
});
socket.on('sdp', async (data)=>{if(data.type === 'sender'){const sdp = new RTCSessionDescription(data.value);
pc.setRemoteDescription(sdp);
// 绑定流
const stream = await getUserMedia({
video: true,
audio: true,
});
// 增加媒体轨道 如果 video 和 audio 都为 true 则 getTracks 能够取得两个轨道
stream.getTracks().forEach(track => pc[toUser].addTrack(track, stream));
// 监听
pc.onnegotiationneeded = ()=>{
pc
.createAnswer() // 创立 offer sdp
.then((answer) => {
// 设置为本地形容
return pc.setLocalDescription(answer);
})
.then(() => {
// 定向转发 sdp
socket.emit('sdp', {
type: 'receive',
value: pc.localDescription
});
});
}
pc.onicecandidate = (ev)=>{
// 转发 ice-candidate
socket.emit('ice-candidate', {
type: 'receive',
value: ev.candidate,
});
}
pc.ontrack = (ev)=>{
// 这里能够的失去对方的流
let stream = ev.streams[0];
}
}
});
// 监听 ice 和 sdp
socket.on('ice-candidate', (data)=>{if(data.type === 'sender'){const candidate = new RTCIceCandidate(data.value);
pc.addIceCandidate(candidate)
}
});