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 和 sdpsocket.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 和 sdpsocket.on('ice-candidate', (data)=>{ if(data.type === 'sender'){ const candidate = new RTCIceCandidate(data.value); pc.addIceCandidate(candidate) }});