提示:局域网socket版,一对多
文章目录
@[TOC](文章目录) 前言一、教程二、webrtc工作流程三、推流端四、拉流五、socket服务六、效果七、备注总结
前言
WebRTC(Web Real-Time Communication)是一种实时通讯技术,允许网络应用或站点在不借助中间媒介的情况下,建立浏览器之间的点对点(Peer-to-Peer)连接,实现视频流、音频流或其他任意数据的传输。WebRTC的核心功能包括音视频的采集、编解码、网络传输和显示等
WebRTC的技术特点
1、实时通信:WebRTC专注于实时通信,包括音频、视频和其他数据传输。
2、点对点通信:WebRTC支持点对点通信,即两个浏览器之间直接建立连接,无需通过中间服务器。
3、多媒体引擎:WebRTC包含一个多媒体引擎,处理音频和视频流,并提供丰富的API和协议。
4、NAT穿越:WebRTC提供机制,使得在NAT(Network Address Translation)和防火墙等网络设备背后进行通信更为容易。
5、TURN服务器:当P2P连接无法建立时,WebRTC会利用TURN服务器进行数据中转,确保通信的稳定性
一、教程
webrtc文档
二、webrtc工作流程
// 推流拉流过程/** * 推流端获取视频stream * 推流端生成offer * 推流端通过offer设置推流LocalDescription * 推流端发送offer给(拉)流端 * (拉)流端接收offer * (拉)流端通过offer设置(拉)流端RemoteDescription * (拉)流端生成answer * (拉)流端通过answer设置(拉)流端LocalDescription * (拉)流端发送answer给推流端 * 推流端接收answer设置推流端RemoteDescription * 推流端发送candidate(video,audio各一次) * (拉)流端接收candidate * (拉)流端发送candidate(video,audio各一次) * 推流端接收candidate * **/
三、推流端
一个拉流RTCPeerConnection,对应一个推流RTCPeerConnection
X 个拉流RTCPeerConnection,对应X 个推流RTCPeerConnection
push.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>推流</title></head><body> <video id="webrtcVideo" autoplay></video> <script> const video = document.getElementById('webrtcVideo'); // webscoket const ws = new WebSocket('ws://127.0.0.1:1990'); // 可换成局域网ip地址 let videoStream; // 一个拉流RTCPeerConnection对应一个推流RTCPeerConnection,xx个拉流RTCPeerConnection,对应xx个推流RTCPeerConnection const pushPool = {}; // rtc connection let pushRtcCon; // 打开摄像头,video标签播放视频流 const getStream = async () => { if(!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia)console.log('不支持:getUserMedia'); const stream = await navigator.mediaDevices.getUserMedia({video:true}); video.srcObject = stream; videoStream = stream; } getStream(); // 开始推流 const startPush = (pullId) => { if(!pushPool[pullId])pushPool[pullId] = pushRtcCon = new RTCPeerConnection(); // rtc connection 添加track videoStream.getVideoTracks().forEach(track => { pushRtcCon.addTrack(track,videoStream); }); // 监听icecandidate pushRtcCon.onicecandidate = (event)=>{ if(event.candidate)ws.send(JSON.stringify({type:'candidate',candidate:event.candidate,id:pullId})) } // 创建offer pushRtcCon.createOffer().then(offer=>{ console.log(offer) // 设置推流LocalDescription pushRtcCon.setLocalDescription(offer).then(()=>{ console.log('推流设置LocalDescription成功');}); // offer信息发送给拉流 ws.send(JSON.stringify({type:'offer',id:pullId,offer})) }); } // 开启websocket服务 ws.addEventListener('open',()=>{ // 初始化推流通道 ws.send(JSON.stringify({type:'push_init'})) console.log('websocket连接成功') }); // 接收wenbscoket信息 ws.addEventListener('message', (event) => { let data = JSON.parse(event.data); console.log(data) // 接收到拉流传来的answer 设置推流RemoteDescription if(data.type == 'answer')pushRtcCon.setRemoteDescription(data.answer).then(()=>{ console.log('推流设置RemoteDescription成功');}); // 接收拉流candidate 推流rtc connection 添加IceCandidate if(data.type == 'candidate'&&data.candidate)pushRtcCon.addIceCandidate(data.candidate).then(()=>{ console.log('推流添加candidate成功');}); // 接收拉流开启消息 开始推流 if(data.type == 'pull_start')startPush(data.id); }) </script></body></html>
四、拉流
pull.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <video id="pullVideo" autoplay preload muted></video> <div id="pullBtn">拉流</div> <script> const pullBtn = document.getElementById('pullBtn'); // 开始拉流 const startPll = () =>{ let ws = new WebSocket('ws://127.0.0.1:1990'); // 可换成局域网ip地址 const pullVideo = document.getElementById('pullVideo'); let pullStrem; // 拉流rtc connection const pullRtcCon = new RTCPeerConnection(); const pullID = new Date().getTime()+'io'+Math.round(Math.random()*10000); // 拉流监听icecandidate pullRtcCon.onicecandidate = (event)=>{ // 接收到icecandidate 发送candidate给推流端 if(event.candidate)ws.send(JSON.stringify({type:'candidate',candidate:event.candidate,num:1,id:pullID})) } // 监听track pullRtcCon.addEventListener('track' ,(event) => { pullStrem = event.streams[0]; pullVideo.srcObject = event.streams[0]; }) // 打开webscoket ws.addEventListener('open',async ()=>{ await ws.send(JSON.stringify({type:'pull_init',id:pullID})); // 通知推流端,开始推流 ws.send(JSON.stringify({type:'pull_start',id:pullID})); console.log('websocket连接成功') }); // 监听webscoket消息 ws.addEventListener('message',(event)=>{ let data = JSON.parse(event.data); // 接收到推流端offer console.log(data,'????') if(data.type == 'offer'){ // 设置拉流端 RemoteDescription pullRtcCon.setRemoteDescription(data.offer).then(()=>{ console.log('拉流设置RemoteDescription成功') // 创建answer pullRtcCon.createAnswer(data.offer).then((answer)=>{ // 设置拉流的LocalDescription pullRtcCon.setLocalDescription(answer).then(()=>{ console.log('拉流设置LocalDescription成功') }); // 发送answer到推流端 ws.send(JSON.stringify({type:'answer',answer,id:pullID})) }); }); } // 接收推流端candidate 拉流端添加IceCandidate if(data.type == 'candidate')pullRtcCon.addIceCandidate(data.candidate).then(()=>{ console.log('拉流添加candidate成功');}); }) } // 拉流按钮点击事件 pullBtn.addEventListener('click',startPll) </script></body></html>
五、socket服务
安装依赖
npm initnpm install nodejs-websocket -S
index.js
const ws = require('nodejs-websocket');const port = '1990';// 推流通道 拉流通道let wsPush,wsPull,pullPool={};const server = ws.createServer((connection)=>{ // websocket 连接接收数据 connection.on('text',(msg)=>{ let data = JSON.parse(msg); // 初始化推流websocket if(data.type == 'push_init')wsPush = connection; // 初始化拉流websocket if(data.type == 'pull_init')if(!pullPool[data.id]) pullPool[data.id] = connection; // 接收推流消息 发送给拉流 if(connection == wsPush&&pullPool[data.id])pullPool[data.id].send(msg); // 接收拉流消息 发送给推流 for(let key in pullPool){ if(connection == pullPool[key]&&wsPush)wsPush.send(msg); } }) // websocket 关闭 connection.on('close',()=>{ wsPush = null;wsPull = null; console.log('通道关闭') }) // websocket 报错 connection.on('err',(err)=>{ wsPush = null;wsPull = null; console.log('通道报错:'+err) })})server.listen(port,console.log('ws启动成功,127.0.0.1:'+port));
六、效果
推流端
拉流端(点击拉流按钮)
七、备注
1、socket地址可换成局域网IP地址访问
2、pull来流请求地址可换成局域网IP地址访问
总结
踩坑路漫漫长@~@