WebRTC音视频开发读书笔记(四)

WebRTC中,连接是核心内容,通过RTCPeerConnection接口可以将本地流MediaStream发送至远端,同时也可以远端媒体发送至本地,从而建立对等连接。本地与远端之间进行媒体协商及网络协商成功后,将本地媒体流发送到远端的过程称为连接建立。

七、建立连接

1、RTCConnection

RTCPeerConnection简称PC对象,即连接对象,本地为Local对象,远端为Remote对象,主要有以下API,如表所示:

2、连接建立

术语:

        Peer-A  : 简写为 A ,即本地

        Peer-B : 简写为 B  即远端

        RTCPeerConnection :简写为PC    

        RTCPeerConnection连接A端即为PC-A,连接B端即为PC-B

连接过程:
(1)A 获取媒体流MediaStream

代码如下:

navigator.mediaDevices.getUserMedia
(2) A(生成PC-A对象

RTCPeerConnection接口代表一个由本地计算机至远端的WebRTC连接。该接口提供了创建、保持、监控、关闭连接的方法,代码如下:

//可选参数包括ICE服务器等
pc=new RTCPeerConnection([RTCConfiguration dictionary])

 ICE服务器的设置如下所示:

 //设置ICE Server,使用Google服务器
    let configuration = { "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] };
(3) A将流加入PC-A

代码如下:

//该方法已经不推荐使用,推荐使用addTrack方法
pc.addStream(stream);


//加入轨道

/循环迭代本地流的所有轨道
localStream.getTracks().forEach((track) => {
/把音视频轨道添加到连接里去
     PC.addTrack(track, stream);
  });
   (4)A创建提议Offer

       PC接口的CreateOffer()方法启动创建一个SDP offer ,目的是启动一个新的WebRTC去连接远程端点。代码如下:

offer=await pc.createOffer()
(5) 设置本地描述

       A 创建提议Offer成功后,会生成RTCSessionDescription对象,然后调用PC-A的setLocalDescription方法设置本地描述,代码如下:

await pc.setLocalDescription(desc)
        (6)   A将Offer发送给B

        A将Offer信息发送给B, 通常需要架设一个信令服务器转发Offer数据。WebSocket是一种常规的实现方式。

     (7)B生成PC-B对象

        B端也要生成一个RTCPeerConnection对象,用来进行应答Answer,发送流,接收等处理。

      (8)B 设置远端描述

        B收到信令服务器转发来的Offer信息后,调用PC-B的setRemoteDescription()方法设置远端描述。

      (9) B生成应答Answer

        PC-B的createAnswer()方法会生成一个应答SDP信息,应答Answer和提议Offer是成对出现。

       (10)   B 设置本地描述

        B创建应答成功后,会生成RTCSessionDescription对象,然后调用PC-B的setLocalDescription()方法设置本地描述信息。

     (11) B把Answer发送给 A

        B将应答信息通过信令服务器转发给A。

     (12)A设置远端描述

        A收到信令服务器转发的应答信息后,调用PC-A的setRemoteDescription()方法设置远端描述。

 (13)交换ICE候选地址

        在建立连接的过程中,会回调onicecandidate事件,传递ICE候选地址,将其发送至另一端,并通过另一端的addiceCandidate()方法设置对方的候选地址。大致代码如下:

pc.addEventLisener('icecandidate',this.onIceCandidate);

onIceCandidate=async(event)=>{

     if(event.candidate){
         //发送Candidate至另一端
         let iceinfo=event.candidate;

    }
}

//另一端接收到Candidate
pc.addIceCandidate(new RTCIceCandidate);

        理想情况下,现在已经建立连接了

(14)交换与使用媒体流

      当一方执行addTrack后,另一方的PC会触 发track事件回调,通过事件参数可以获取对方轨道里的媒体流,代码如下:

pc.addEventListener('track',this.gotRemoteSteam);

//获取到远端媒体流
gotRemoteStream=(e)=>{
      //远端媒体流
    remoteVideo.srcObject=e.streams[0];

}
示例完整代码

        完整代码如下:

import React from "react";
import { Button } from "antd";

//本地视频
let localVideo;
//远端视频
let remoteVideo;
//本地流
let localStream;
//PeerA连接对象
let peerConnA;
//PeerB连接对象
let peerConnB;
/**
 * 连接建立示例
 */
class PeerConnection extends React.Component {

  componentDidMount() {
    //初始化本地视频对象
    localVideo = this.refs['localVideo'];
    //初始化远端视频对象
    remoteVideo = this.refs['remoteVideo'];

    //获取本地视频尺寸
    localVideo.addEventListener('loadedmetadata', () => {
      console.log(`本地视频尺寸为: videoWidth: ${localVideo.videoWidth}px,  videoHeight: ${localVideo.videoHeight}px`);
    });

    //获取远端视频尺寸
    remoteVideo.addEventListener('loadedmetadata', () => {
      console.log(`远端视频尺寸为: videoWidth: ${remoteVideo.videoWidth}px,  videoHeight: ${remoteVideo.videoHeight}px`);
    });

    //监听远端视频尺寸大小变化
    remoteVideo.addEventListener('resize', () => {
      console.log(`远端视频尺寸为: ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);
    });

  }

  //开始
  start = async () => {
    console.log('开始获取本地媒体流');
    try {
      //获取音视频流
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
      console.log('获取本地媒体流成功');
      //本地视频获取流
      localVideo.srcObject = stream;
      localStream = stream;
    } catch (e) {
      console.log("getUserMedia错误:" + e);
    }
  }

  //呼叫
  call = async () => {
    console.log('开始呼叫...');
    //视频轨道
    const videoTracks = localStream.getVideoTracks();
    //音频轨道
    const audioTracks = localStream.getAudioTracks();
    //判断视频轨道是否有值
    if (videoTracks.length > 0) {
      //输出摄像头的名称
      console.log(`使用的视频设备为: ${videoTracks[0].label}`);
    }
    //判断音频轨道是否有值
    if (audioTracks.length > 0) {
      //输出麦克风的名称
      console.log(`使用的音频设备为: ${audioTracks[0].label}`);
    }

    //设置ICE Server,使用Google服务器
    let configuration = { "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] };

    //创建RTCPeerConnection对象
    peerConnA = new RTCPeerConnection(configuration);
    console.log('创建本地PeerConnection成功:peerConnA');
    //监听返回的Candidate信息
    peerConnA.addEventListener('icecandidate', this.onIceCandidateA);

    //创建RTCPeerConnection对象
    peerConnB = new RTCPeerConnection(configuration);
    console.log('创建本地PeerConnection成功:peerConnB');
    //监听返回的Candidate信息
    peerConnB.addEventListener('icecandidate',  this.onIceCandidateB);

    //监听ICE状态变化
    peerConnA.addEventListener('iceconnectionstatechange', this.onIceStateChangeA);
    //监听ICE状态变化
    peerConnB.addEventListener('iceconnectionstatechange', this.onIceStateChangeB);

    //监听track事件,可以获取到远端视频流
    peerConnB.addEventListener('track', this.gotRemoteStream);

    //peerConnA.addStream(localStream);
    //循环迭代本地流的所有轨道
    localStream.getTracks().forEach((track) => {
      //把音视频轨道添加到连接里去
      peerConnA.addTrack(track, localStream);
    });
    console.log('将本地流添加到peerConnA里');

    try {
      console.log('peerConnA创建提议Offer开始');
      //创建提议Offer
      const offer = await peerConnA.createOffer();
      //创建Offer成功
      await this.onCreateOfferSuccess(offer);
    } catch (e) {
      //创建Offer失败
      this.onCreateSessionDescriptionError(e);
    }
  }

  //创建会话描述错误
  onCreateSessionDescriptionError = (error) => {
    console.log(`创建会话描述SD错误: ${error.toString()}`);
  }

  //创建提议Offer成功
  onCreateOfferSuccess = async (desc) => {
    //peerConnA创建Offer返回的SDP信息
    console.log(`peerConnA创建Offer返回的SDP信息\n${desc.sdp}`);
    console.log('设置peerConnA的本地描述start');
    try {
      //设置peerConnA的本地描述
      await peerConnA.setLocalDescription(desc);
      this.onSetLocalSuccess(peerConnA);
    } catch (e) {
      this.onSetSessionDescriptionError();
    }

    console.log('peerConnB开始设置远端描述');
    try {
      //设置peerConnB的远端描述
      await peerConnB.setRemoteDescription(desc);
      this.onSetRemoteSuccess(peerConnB);
    } catch (e) {
      //创建会话描述错误
      this.onSetSessionDescriptionError();
    }

    console.log('peerConnB开始创建应答Answer');
    try {
      //创建应答Answer
      const answer = await peerConnB.createAnswer();
      //创建应答成功
      await this.onCreateAnswerSuccess(answer);
    } catch (e) {
      //创建会话描述错误
      this.onCreateSessionDescriptionError(e);
    }
  }

  //设置本地描述完成
  onSetLocalSuccess = (pc) => {
    console.log(`${this.getName(pc)}设置本地描述完成:setLocalDescription`);
  }

  //设置远端描述完成
  onSetRemoteSuccess = (pc) => {
    console.log(`${this.getName(pc)}设置远端描述完成:setRemoteDescription`);
  }

  //设置描述SD错误
  onSetSessionDescriptionError = (error) => {
    console.log(`设置描述SD错误: ${error.toString()}`);
  }

  getName = (pc) => {
    return (pc === peerConnA) ? 'peerConnA' : 'peerConnB';
  }

  //获取到远端视频流
  gotRemoteStream = (e) => {
    if (remoteVideo.srcObject !== e.streams[0]) {
      //取集合第一个元素
      remoteVideo.srcObject = e.streams[0];
      console.log('peerConnB开始接收远端流');
    }
  }

  //创建应答成功
  onCreateAnswerSuccess = async (desc) => {
    //输出SDP信息
    console.log(`peerConnB的应答Answer数据:\n${desc.sdp}`);
    console.log('peerConnB设置本地描述开始:setLocalDescription');
    try {
      //设置peerConnB的本地描述信息
      await peerConnB.setLocalDescription(desc);
      this.onSetLocalSuccess(peerConnB);
    } catch (e) {
      this.onSetSessionDescriptionError(e);
    }
    console.log('peerConnA设置远端描述开始:setRemoteDescription');
    try {
      //设置peerConnA的远端描述,即peerConnB的应答信息
      await peerConnA.setRemoteDescription(desc);
      this.onSetRemoteSuccess(peerConnA);
    } catch (e) {
      this.onSetSessionDescriptionError(e);
    }
  }

  //Candidate事件回调方法
  onIceCandidateA = async (event) => {
    try {
      if(event.candidate){
        //将会peerConnA的Candidate添加至peerConnB里
        await peerConnB.addIceCandidate(event.candidate);
        this.onAddIceCandidateSuccess(peerConnB);
      }
    } catch (e) {
      this.onAddIceCandidateError(peerConnB, e);
    }
    console.log(`IceCandidate数据:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
  }

  //Candidate事件回调方法
  onIceCandidateB = async (event) => {
    try {
      if(event.candidate){
        //将会peerConnB的Candidate添加至peerConnA里
        await peerConnA.addIceCandidate(event.candidate);
        this.onAddIceCandidateSuccess(peerConnA);
      }
    } catch (e) {
      this.onAddIceCandidateError(peerConnA, e);
    }
    console.log(`IceCandidate数据:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
  }

  //添加Candidate成功
  onAddIceCandidateSuccess = (pc) => {
    console.log(`${this.getName(pc)}添加IceCandidate成功`);
  } 

  //添加Candidate失败
  onAddIceCandidateError = (pc, error) => {
    console.log(`${this.getName(pc)}添加IceCandidate失败: ${error.toString()}`);
  }

  //监听ICE状态变化事件回调方法
  onIceStateChangeA = (event) => {
    console.log(`peerConnA连接的ICE状态: ${peerConnA.iceConnectionState}`);
    console.log('ICE状态改变事件: ', event);
  }

  //监听ICE状态变化事件回调方法
  onIceStateChangeB = (event) => {
    console.log(`peerConnB连接的ICE状态: ${peerConnB.iceConnectionState}`);
    console.log('ICE状态改变事件: ', event);
  }

  //断开连接
  hangup = () => {
    console.log('结束会话');
    //关闭peerConnA
    peerConnA.close();
    //关闭peerConnB
    peerConnB.close();
    //peerConnA置为空
    peerConnA = null;
    //peerConnB置为空
    peerConnB = null;
  }

  render() {
    return (
      <div className="container">
        <h1>
          <span>RTCPeerConnection示例</span>
        </h1>
        {/* 本地视频 */}
        <video ref="localVideo" playsInline autoPlay muted></video>
        {/* 远端视频 */}
        <video ref="remoteVideo" playsInline autoPlay></video>
        <div>
        <Button ref="startButton" onClick={this.start} style={{marginRight:"10px"}}>开始</Button>
        <Button ref="callButton" onClick={this.call} style={{marginRight:"10px"}}>呼叫</Button>
        <Button ref="hangupButton" onClick={this.hangup} style={{marginRight:"10px"}}>挂断</Button>
        </div>
      </div>
    );
  }
}
//导出组件
export default PeerConnection;

   

      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值