WebRTC网页视频聊天(包含stun部署及SSL证书申请方法)

使用WebRTC实现一个简单的网页聊天室

效果图大概是这样,因为不想曝光,放一个网图

直接上页面和源码

index.html

<!DOCTYPE html>
<html>
<head>
  <script type='text/javascript' src='https://cdn.scaledrone.com/scaledrone.min.js'></script>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <style>
    body {
      display: flex;
      height: 100vh;
      margin: 0;
      align-items: center;
      justify-content: center;
      padding: 0 50px;
      font-family: -apple-system, BlinkMacSystemFont, sans-serif;
    }
    video {
      max-width: calc(50% - 100px);
      margin: 0 50px;
      box-sizing: border-box;
      border-radius: 2px;
      padding: 0;
      box-shadow: rgba(156, 172, 172, 0.2) 0px 2px 2px, rgba(156, 172, 172, 0.2) 0px 4px 4px, rgba(156, 172, 172, 0.2) 0px 8px 8px, rgba(156, 172, 172, 0.2) 0px 16px 16px, rgba(156, 172, 172, 0.2) 0px 32px 32px, rgba(156, 172, 172, 0.2) 0px 64px 64px;
    }
    .copy {
      position: fixed;
      top: 10px;
      left: 50%;
      transform: translateX(-50%);
      font-size: 16px;
      color: rgba(0, 0, 0, 0.5);
    }
  </style>
  <script type='text/javascript' src='https://cdn.scaledrone.com/scaledrone.min.js'></script>
</head>
<body>
  <div class="copy">复制URL发送给对方,复制到浏览器即可通话</div>
  <video id="localVideo" autoplay muted></video>
  <video id="remoteVideo" autoplay></video>
  <script src="script.js"></script>
</body>
</html>

js 

//产生随机数
if (!location.hash) {
  location.hash = Math.floor(Math.random() * 0xFFFFFF).toString(16);
}
//获取房间号
const roomHash = location.hash.substring(1);

//放置自己的频道id
const drone = new ScaleDrone('yiS12Ts5RdNhebyM');
//房间名必须以'observable-'开头
const roomName = 'observable-' + roomHash;
//使用谷歌的stun服务,也可以自己部署
const configuration = {
  iceServers: [{
    urls: 'stun:stun.l.google.com:19302'
  }]
};
let room;
let pc;


function onSuccess() {};
function onError(error) {
  console.error(error);
};

drone.on('open', error => {
  if (error) {
    return console.error(error);
  }
  room = drone.subscribe(roomName);
  room.on('open', error => {
    if (error) {
      onError(error);
    }
  });
  //我们连接到房间后会收到一个'members'数组,代表房间里的成员
  //这个时候信令服务已经就绪
  room.on('members', members => {
    console.log('MEMBERS', members);
    // 如果是第二个进入房间的人,就会创建offer
    const isOfferer = members.length === 2;
    startWebRTC(isOfferer);
  });
});

//通过scaledrone发送信令
function sendMessage(message) {
  drone.publish({
    room: roomName,
    message
  });
}

function startWebRTC(isOfferer) {
  pc = new RTCPeerConnection(configuration);

  //当本地ICE Agent需要通过信号服务器发送消息到其他端时,会触发icecandidate事件回调

  pc.onicecandidate = event => {
    if (event.candidate) {
      sendMessage({'candidate': event.candidate});
    }
  };

  //如果是第二个进入的人,就在negotiationneded事件后创建sdp
  if (isOfferer) {

    pc.onnegotiationneeded = () => {

      pc.createOffer().then(localDescCreated).catch(onError);
    }
  }

  //当远程数据流到达时将数据流装载到video中
  pc.ontrack = event => {
    const stream = event.streams[0];
    if (!remoteVideo.srcObject || remoteVideo.srcObject.id !== stream.id) {
      remoteVideo.srcObject = stream;
    }
  };
  //获取本地媒体流
  navigator.mediaDevices.getUserMedia({
    audio: true,
    video: true,
  }).then(stream => {
    //将本地捕获的视频流装载到本地video中
    localVideo.srcObject = stream;
    //将本地流加入RTCPeerConnection 实例中 发送到其他端
    stream.getTracks().forEach(track => pc.addTrack(track, stream));
  }, onError);

  //从Scaledrone监听信令数据
  room.on('data', (message, client) => {
    //自己发送的消息不处理
    if (client.id === drone.clientId) {
      return;
    }

    if (message.sdp) {
      //设置远程sdp, 在offer 或者 answer后
      pc.setRemoteDescription(new RTCSessionDescription(message.sdp), () => {
        //当收到offer 后就接听
        if (pc.remoteDescription.type === 'offer') {
          pc.createAnswer().then(localDescCreated).catch(onError);
        }
      }, onError);
    } else if (message.candidate) {
      //增加新的 ICE canidatet 到本地的链接中
      pc.addIceCandidate(
        new RTCIceCandidate(message.candidate), onSuccess, onError
      );
    }
  });
}

function localDescCreated(desc) {
  pc.setLocalDescription(
    desc,
    () => sendMessage({'sdp': pc.localDescription}),
    onError
  );
}

本例子是参考webrtc-tutorial-simple-video-chat做的。

遇到的问题

问题有两个

一、stun服务器需要部署

这个问题我用的是网上公开的stun服务器,但缺点是stun服务器响应比较慢.,如果有需求可以自己在服务器上部署一个,这就不多说了。

放一段安装和配置的说明,有兴趣的可以试一下

1. coturn 的底层网络部分依赖libevent.  所以需要先安装libevent2, 地址在此 http://libevent.org/

2. coturn的安装很简单. configure make make install 三部曲就完事了. 

3. coturn的文档说明挺详细的. 但比较多. 我只是大概说明下. 

更具体的说明可以看 源码目录下的README.turnserver README.turnadmin README.turnutils

在bin目录下生成六个可执行文件

turnadmin turnutils_peer turnutils_stunclient
turnserver turnutils_rfc5769check turnutils_uclient

turnserver 就是我们需要的服务器. 

turnadmin 用来管理账户. 

turnutils_stunclient 用于测试stun服务

turnutils_uclient 用于测试turn服务. 模拟多个UDP,TCP,TLS or DTLS 类型的客户端

example 目录主要是示范如何配置和使用turn. 包含一些测试用例. 

example/etc 下是pem证书和conf配置文件

example/var/db 下是sqlite的db库. 用于示范数据库的格式.

coturn 支持三种配置. 命令行, conf文件和数据库. 数据库支持sqlite, mysql, postgresql, MongoDB, redis.

examples\scripts 下一些测试用例: 

loadbalance 示范如何进行负载均衡.  设置一个master turn server 然后配置若干个slave turn server. 

longtermsecure 示范如何使用long-term 验证

longtermsecuredb 与 longtermsecure 类似, 不过是从数据库配置

shorttermsecure 示范如何使用short-term验证. 

restapi 示范了web方面的使用. 

stun 定义了两种验证方式. 

Long-Term Credential
Short-Term Credential

具体可以参考stun标准 http://tools.ietf.org/html/rfc5389#section-15.4

但是对于webrtc而言. 仅支持long-term . 

http://www.ietf.org/proceedings/87/slides/slides-87-behave-10.pdf
TURN REST Server API

这个PDF 描述了. turn服务器和客户端的交互流程.

二、从服务器打开报错

Only secure origins are allowed (see: https://goo.gl/Y0ZkNV)

对,就是这个错误,一般自己的服务器都是http访问,一般浏览器不会允许不安全的网站调用摄像头,这个时候只能把访问更改为https访问。

部署https需要申请证SSL证书,这里推荐Let's Encrypt证书申请,比较好用。

猜你喜欢

转载自blog.csdn.net/qq_32691569/article/details/88964257