webrtc学习----前端推流拉流,局域网socket版,一对多

提示:局域网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 init
npm 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地址访问

总结

踩坑路漫漫长@~@

相关推荐
腾讯TNTWeb前端团队4 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰7 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪7 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪7 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy8 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom9 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom9 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom9 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom9 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom9 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试