乐趣区

关于javascript:WebRTC-简单了解

WebRTC DEMO

花了两天工夫简略理解了一下WEB RTC,并由此写入三个 DEMO。

  1. p2p 点对点
  2. o2m 一对多
  3. 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 建设还是很简略的,只须要替换单方 sdpice-candidate,即可建设通信。

具体流程

p2p 1 对 1 视频

呼叫方创立 offer sdp 接管方依据 offer sdp 创立 answer sdp

一、sdp 替换

  1. 呼叫方 建设 WebRTC
  2. 接管方 期待 信令服务器 转发 类型为offersdp
  3. 呼叫方 监听onnegotiationneeded 并创立 offer sdp 并调用 setLocalDescription 设置为本地形容
  4. 呼叫方 向 信令服务器 发送 offer sdp 并监听 answer sdp
  5. 接管方 失去 offer sdp 并调用 setRemoteDescription 设置为近程形容
  6. 接管方 创立 answer sdp 并设置本地形容(setLocalDescription)同时向 信令服务器 发送 answer sdp
  7. 呼叫方 收到 answer sdp 并设近程形容(setRemoteDescription

二、ice-candidate 替换

  1. 监听 onicecandidate 失去 candidate 后进行发送
  2. 监听 信令服务器 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 做信令服务器

目前逻辑很简略,只为数据定向转发

目前次要对 sdpice 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 和 sdp
socket.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 和 sdp
socket.on('ice-candidate', (data)=>{if(data.type === 'sender'){const candidate = new RTCIceCandidate(data.value);
    pc.addIceCandidate(candidate)
  }
});
退出移动版