简单快速上手的 webRTC,完整前端和后端, 服务器是node,服务器逻辑简单

本文详细介绍了WebRTC的实现过程,从信令服务器搭建到前端代码解析,涵盖了offer与answer的交互、ICE候选人的处理等核心步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我也是第一次写webRTC,之前一直没有写过,也是一边百度一边写 最终还是写出来了,我来简单说一下了,webRTC其实人家已经写的很完整的,我们要做的仅仅就是 将两个机器的  信令交互一下 

先看一下效果图吧,因为是在本地 所以两个浏览器显示的都是 一个摄像头内容

大家可以将 文件放到两台电脑上  在同一个局域网里面 启动node就可以使用了

 

完整的代码 可以到github上下载https://github.com/wenccro/webRTC

webRTC API不懂可以查询https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling#Starting_a_call

     首先,说一下我这个demo的协议规则

我到规定是,主动呼叫 需要发送 offer, 接受端 在收到offer 时 需要回复一个answer,定义的这样个规则也符合人之常情,当别叫你名字的时候 你不得 答应人家一下

let obj = {
      "type": "", // 有 offer,answer
      "sdp": 
    }

 

     然后 说一下 信令服务器

我这里使用的是 HttpRequest,然后定时器循环去请求。最好还是用webSocket ,因为你可以直接在呼叫后 直接发送给被呼叫着

而不是 使用轮询的方式去查询

下面这个就是我的node服务器跑的流程图,

全局声明get和post地址

  // 请更换成你node本地跑起来 所在的 ip地址     
 let getUrl = "http://(node服务器地址:3001)/data/local"
 let postUrl = "http://(node服务器地址:3001)/data/remo"

 

好了 我们就开始 来一点一点的  揭开 webRTC的面纱吧

先上 信令服务器上的内容  也就是  node 代码

var express = require('express')
const bodyParser = require('body-parser')
var app = express()
const Router = require('router')
const router = Router()

router.__dataStore = {}

// 自定义跨域中间件
var allowCors = function (req, res, next) {
  res.header('Access-Control-Allow-Origin', req.headers.origin)
  res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
  res.header('Access-Control-Allow-Headers', 'Content-Type')
  res.header('Access-Control-Allow-Credentials', 'true')
  next()
}
app.use(allowCors) // 使用跨域中间件

app.get('/data/:id', function (req, res) {
  console.log('get')
  const deviceId = req.params.id
  console.log(deviceId)
  if (!router.__dataStore[deviceId] || router.__dataStore[deviceId].length === 0) {
    console.log('无数据')
    res.statusCode = 200
    res.end('11')
  } else {
    console.log(router.__dataStore)
    const data = router.__dataStore[deviceId].shift()
    console.log('我发给了谁' + deviceId)
    console.log(data)
    res.statusCode = 200
    res.end(JSON.stringify(data))
  }
})
app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json({ type: 'application/*+json' }))
// var urlencodedParser = bodyParser.urlencoded({ extended: true })
app.post('/data/:id', (req, res) => {
  console.log('post请求成功')
  const deviceId = req.params.id
  console.log(deviceId)
  if (!router.__dataStore[deviceId]) {
    router.__dataStore[deviceId] = []
  }
  console.log('我准备push了')
  console.log(req.body)
  router.__dataStore[deviceId].push(req.body)
  res.statusCode = 200
  res.end('11')
})

app.listen(3001, () => {
  console.log(3001)
})

node 服务器就做了2件事 ,就是一个get请求  查询 有没有他的  sdp内容,第二个是post请求,将自己的spd 存入对方的脚下

就是信令服务器代码了  就两个 一个get 一个post ,你可以用node以外的任何后端语言来实现 它的逻辑。

但最推荐  得还算实用webSocket 

接下来 我们来看前端 代码 

我的  前端有两个页面 和两个js  页面和js都是差不多的

唯一不同的仅仅只是 get和post请求的 url地址不同

这里 我就以 local.html 和local.js来讲述它的原理,完整的代码我已经上传带github上了  在文章开头就写了  你可以直接下载

 

下面 我们就在看一下前端代码

html 

 第一了视频显示的位置 和绑定事件

<!DOCTYPE html>
<html>
  <head>
    <title>Realtime communication with WebRTC</title>
    <style>
      body {
        font-family: sans-serif;
      }

      video {
        max-width: 100%;
        width: 320px;
      }
    </style>
  </head>

  <body>
    <h1>Realtime communication with WebRTC</h1>

    <video id="localVideo" autoplay playsinline></video>
    <video id="remoteVideo" autoplay playsinline></video>

    <div>
      <button id="startButton">A端开始</button>
      <button id="callButton">A端调用</button>
      <button id="hangupButton">挂断A端定时器</button>
    </div>
    <script src="./jquery.js"></script>
    <script src="./local.js"></script>
  </body>
</html>

 

点击了开始按钮

我们先来看第一个  按钮 点击后做了什么,就是循环去发送get请求  由于 我node不太熟,所以每次get node服务器它在没有数据的时候 会给我返回一个11的标志位 这里面有一个 关键的 方法  就是chackData(),这个方法就是每次拿到数据后 ,做自己的规则检查  也就是 收到offer回复 answer

 // 点击开始按钮
  $('#startButton').click(function () {
    startQuery()
  })
// 这个方法循环去请求去
  function startQuery () {
    timer = setInterval(function () {
      $.ajax({
        type: "GET",
        url: getUrl,
        success: function (res) {
          if (res === '11' || res === ' ') {

          } else {
            let msg = JSON.parse(res)
            chackData(msg)
          }
        },
        error: function (res) {}
      })
    }, 1000)
  }

现在我们就是 看一下 这个  chackData具体做了什么,看完代码 你会发现 原来 它对这个信息做了一个分支.

解释:msg.MessageType: 1 表示 收到对方发送的 offer,  2 表示收到对方发送的 answer ,3 表示 收到对方的ice 

// 检测函数
  function chackData (msg) {
    switch (msg.MessageType) {
      case '1':
        handleVideoOfferMsg(msg);
        break;
      case '2':
        handleVideoAnswerMsg(msg);
        break;
      case '3':
        handleNewICECandidateMsg(msg);
        break;
    }
  }

接着 我们来看 第一件事,就是收到 offer后 它到底做了些什么

  // 收到别的 offer 需要调用post发送 内容
  async function handleVideoOfferMsg (msg) {
    console.log('收到offer')
    if (!localPeerConnection) {
      createPeerConnection();
    }
    let obj = {
      "type": "offer",
      "sdp": msg.Data
    }
    var desc = new RTCSessionDescription(obj);
    localPeerConnection.setRemoteDescription(desc)
    if (!webcamStream) {
      try { // 播放本地视频
        webcamStream = await navigator.mediaDevices.getUserMedia(mediaStreamConstraints);
        localVideo.srcObject = webcamStream
      } catch (err) {
        handleGetUserMediaError(err);
        return;
      }
      try {
        webcamStream.getTracks().forEach(
          transceiver = track => localPeerConnection.addTransceiver(track, { streams: [webcamStream] })
        );
      } catch (err) {
        handleGetUserMediaError(err);
      }
    }
    await localPeerConnection.setLocalDescription(await localPeerConnection.createAnswer());
    // 调用 post 请求 回复 
    let objs = sendDataByType('Answer')
    startPost(objs)
  }

收到 offer以后 一 检查 localPeerConnection(new RTCPeerConnection 对等连接对象有没有) 

 没有就执行 createPeerConnection()方法,后面会讲。接着往下看

 let obj = { "type": "offer", "sdp": msg.Data }

    var desc = new RTCSessionDescription(obj);

    localPeerConnection.setRemoteDescription(desc)

这个是收到对方的sdp 并将其设置为 本地远端绘画描述。

接着 二 判断,webcamStream  这个就是  本地视频流 ,本地视频流如果没有打开 就开启本地视频流

localPeerConnection.addTransceiver 这个是将 本地流发送给对方的。

三 就是使用post将 自己的sdp发送给对方

 

接下来是补充  收到offer 这个handleVideoOfferMsg方法中引用的其方法

第一个  是createPeerConnection()创建对等连接

创建了对象并写了 回调事件

async function createPeerConnection () {
    localPeerConnection = new RTCPeerConnection();
    localPeerConnection.onicecandidate = handleICECandidateEvent;
    localPeerConnection.ontrack = handleTrackEvent;
    localPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent;
  }

这3个回调 的方法 我就不不再这里叙述了 你直接gethub下载下来就可以看到了。回调是什么 ,做了什么 也很清楚

收到offer 这个handleVideoOfferMsg方法中引用的其方法

第二个  是 发送post个服务器

let objs = sendDataByType('Answer')

startPost(objs)

// 返回需要发送的数据
  function sendDataByType (type) {
    let obj = {
      Data: localPeerConnection.localDescription.sdp,
      IceDataSeparator: ' '
    }
    switch (type) {
      case "Offer":
        obj.MessageType = '1';
        break;
      case "Answer":
        obj.MessageType = '2';
        break;
    }
    return obj
  }
 // post请求方法
  function startPost (obj) {
    $.ajax({
      type: "post",
      url: postUrl,
      data: obj,
      dataType: 'json',
      success: function (res) {},
      error: function (res) { }
    })
  }

 

第二件事 就是收到answer后 做了些什么?

收到answer和收到offer都做了(解释在上面) RTCSessionDescription,和 setRemoteDescription

  // 收到answer
  async function handleVideoAnswerMsg (msg) {
    console.log('收到answer')
    let obj = {
      "type": "answer",
      "sdp": msg.Data
    }
    var desc = new RTCSessionDescription(obj);
    await localPeerConnection.setRemoteDescription(desc).catch();
  }

第三件事 收到 ice,就是收到ICE候选人 做了

 new RTCIceCandidate()和localPeerConnection.addIceCandidate

  async function handleNewICECandidateMsg (msg) {
    let arr = msg.Data.split(msg.IceDataSeparator)
    let obj = {
      "candidate": arr[0],
      "sdpMid": arr[1],
      "sdpMLineIndex": arr[2]
    }
    var candidate = new RTCIceCandidate(obj);
    try {
      await localPeerConnection.addIceCandidate(candidate)
    } catch (err) {}
  }

至此 以上部分就是 点击 开始后  接收 offer 或者 answer 或者 ice 的所有事件了。

接下来 是 点击了调用按钮

点击开始是 等待被呼叫,点击调用就是主动去呼叫,

我这里 做了两件事 第一件事 就是重新打开get请求 也就是为了防止 在没有点击开始  就点击 调用了  报错问题

  $('#callButton').click(function () {
    startQuery()
    startAction()
  })

startQuery()这个方法上面已经说过了  现在 我们来重点看看 startAction()方法

做了两件事 ,第一件事就是  打开本地视频流  第二件事就是发布自己的本地视频流

ceatePeerConnection() 在上面已经说过了  这里就不说了

  async function startAction () {
    createPeerConnection()
    try { // 播放本地视频
      webcamStream = await navigator.mediaDevices.getUserMedia(mediaStreamConstraints);
      localVideo.srcObject = webcamStream
    } catch (err) {
      handleGetUserMediaError(err);
      return;
    }
    try {
      webcamStream.getTracks().forEach(
        transceiver = track => localPeerConnection.addTransceiver(track, { streams: [webcamStream] })
      );
    } catch (err) {
      handleGetUserMediaError(err);
    }
  }

最后 就是点击关闭按钮

这里 没有写完  只是简单的写了一下  结束定时器 get数据。你还应该写情况视频流 关闭摄像头等事件

 $("#hangupButton").click(function () {
    clearInterval(timer)
  })

 

至此  整个wenRTC就可以实现简单的 通话了,总结一下  就是 首先 必须有一个 信令服务器  作用就是交换 彼此的SDP,其次就是在 正确的时间做正确的事,也就是 在收到offer 后给人家回复 answer  在收到别人的 answer后设置成远端流并,方便对方打开摄像头。收到ice就及时的交换ice候选人信息。我开头所发的参考API的那个地址里面有更加详细的描述,你也可以参考它的和我的 来写  

加油小宝贝

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值