• 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连接。该接口提供了创建、保持、监控、关闭连接的方法,代码如下:

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

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

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

    代码如下:

    1. //该方法已经不推荐使用,推荐使用addTrack方法
    2. pc.addStream(stream);
    3. //加入轨道
    4. /循环迭代本地流的所有轨道
    5. localStream.getTracks().forEach((track) => {
    6. /把音视频轨道添加到连接里去
    7. PC.addTrack(track, stream);
    8. });
       (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()方法设置对方的候选地址。大致代码如下:

    1. pc.addEventLisener('icecandidate',this.onIceCandidate);
    2. onIceCandidate=async(event)=>{
    3. if(event.candidate){
    4. //发送Candidate至另一端
    5. let iceinfo=event.candidate;
    6. }
    7. }
    8. //另一端接收到Candidate
    9. pc.addIceCandidate(new RTCIceCandidate);

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

    (14)交换与使用媒体流

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

    1. pc.addEventListener('track',this.gotRemoteSteam);
    2. //获取到远端媒体流
    3. gotRemoteStream=(e)=>{
    4. //远端媒体流
    5. remoteVideo.srcObject=e.streams[0];
    6. }
    示例完整代码

            完整代码如下:

    1. import React from "react";
    2. import { Button } from "antd";
    3. //本地视频
    4. let localVideo;
    5. //远端视频
    6. let remoteVideo;
    7. //本地流
    8. let localStream;
    9. //PeerA连接对象
    10. let peerConnA;
    11. //PeerB连接对象
    12. let peerConnB;
    13. /**
    14. * 连接建立示例
    15. */
    16. class PeerConnection extends React.Component {
    17. componentDidMount() {
    18. //初始化本地视频对象
    19. localVideo = this.refs['localVideo'];
    20. //初始化远端视频对象
    21. remoteVideo = this.refs['remoteVideo'];
    22. //获取本地视频尺寸
    23. localVideo.addEventListener('loadedmetadata', () => {
    24. console.log(`本地视频尺寸为: videoWidth: ${localVideo.videoWidth}px, videoHeight: ${localVideo.videoHeight}px`);
    25. });
    26. //获取远端视频尺寸
    27. remoteVideo.addEventListener('loadedmetadata', () => {
    28. console.log(`远端视频尺寸为: videoWidth: ${remoteVideo.videoWidth}px, videoHeight: ${remoteVideo.videoHeight}px`);
    29. });
    30. //监听远端视频尺寸大小变化
    31. remoteVideo.addEventListener('resize', () => {
    32. console.log(`远端视频尺寸为: ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);
    33. });
    34. }
    35. //开始
    36. start = async () => {
    37. console.log('开始获取本地媒体流');
    38. try {
    39. //获取音视频流
    40. const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
    41. console.log('获取本地媒体流成功');
    42. //本地视频获取流
    43. localVideo.srcObject = stream;
    44. localStream = stream;
    45. } catch (e) {
    46. console.log("getUserMedia错误:" + e);
    47. }
    48. }
    49. //呼叫
    50. call = async () => {
    51. console.log('开始呼叫...');
    52. //视频轨道
    53. const videoTracks = localStream.getVideoTracks();
    54. //音频轨道
    55. const audioTracks = localStream.getAudioTracks();
    56. //判断视频轨道是否有值
    57. if (videoTracks.length > 0) {
    58. //输出摄像头的名称
    59. console.log(`使用的视频设备为: ${videoTracks[0].label}`);
    60. }
    61. //判断音频轨道是否有值
    62. if (audioTracks.length > 0) {
    63. //输出麦克风的名称
    64. console.log(`使用的音频设备为: ${audioTracks[0].label}`);
    65. }
    66. //设置ICE Server,使用Google服务器
    67. let configuration = { "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] };
    68. //创建RTCPeerConnection对象
    69. peerConnA = new RTCPeerConnection(configuration);
    70. console.log('创建本地PeerConnection成功:peerConnA');
    71. //监听返回的Candidate信息
    72. peerConnA.addEventListener('icecandidate', this.onIceCandidateA);
    73. //创建RTCPeerConnection对象
    74. peerConnB = new RTCPeerConnection(configuration);
    75. console.log('创建本地PeerConnection成功:peerConnB');
    76. //监听返回的Candidate信息
    77. peerConnB.addEventListener('icecandidate', this.onIceCandidateB);
    78. //监听ICE状态变化
    79. peerConnA.addEventListener('iceconnectionstatechange', this.onIceStateChangeA);
    80. //监听ICE状态变化
    81. peerConnB.addEventListener('iceconnectionstatechange', this.onIceStateChangeB);
    82. //监听track事件,可以获取到远端视频流
    83. peerConnB.addEventListener('track', this.gotRemoteStream);
    84. //peerConnA.addStream(localStream);
    85. //循环迭代本地流的所有轨道
    86. localStream.getTracks().forEach((track) => {
    87. //把音视频轨道添加到连接里去
    88. peerConnA.addTrack(track, localStream);
    89. });
    90. console.log('将本地流添加到peerConnA里');
    91. try {
    92. console.log('peerConnA创建提议Offer开始');
    93. //创建提议Offer
    94. const offer = await peerConnA.createOffer();
    95. //创建Offer成功
    96. await this.onCreateOfferSuccess(offer);
    97. } catch (e) {
    98. //创建Offer失败
    99. this.onCreateSessionDescriptionError(e);
    100. }
    101. }
    102. //创建会话描述错误
    103. onCreateSessionDescriptionError = (error) => {
    104. console.log(`创建会话描述SD错误: ${error.toString()}`);
    105. }
    106. //创建提议Offer成功
    107. onCreateOfferSuccess = async (desc) => {
    108. //peerConnA创建Offer返回的SDP信息
    109. console.log(`peerConnA创建Offer返回的SDP信息\n${desc.sdp}`);
    110. console.log('设置peerConnA的本地描述start');
    111. try {
    112. //设置peerConnA的本地描述
    113. await peerConnA.setLocalDescription(desc);
    114. this.onSetLocalSuccess(peerConnA);
    115. } catch (e) {
    116. this.onSetSessionDescriptionError();
    117. }
    118. console.log('peerConnB开始设置远端描述');
    119. try {
    120. //设置peerConnB的远端描述
    121. await peerConnB.setRemoteDescription(desc);
    122. this.onSetRemoteSuccess(peerConnB);
    123. } catch (e) {
    124. //创建会话描述错误
    125. this.onSetSessionDescriptionError();
    126. }
    127. console.log('peerConnB开始创建应答Answer');
    128. try {
    129. //创建应答Answer
    130. const answer = await peerConnB.createAnswer();
    131. //创建应答成功
    132. await this.onCreateAnswerSuccess(answer);
    133. } catch (e) {
    134. //创建会话描述错误
    135. this.onCreateSessionDescriptionError(e);
    136. }
    137. }
    138. //设置本地描述完成
    139. onSetLocalSuccess = (pc) => {
    140. console.log(`${this.getName(pc)}设置本地描述完成:setLocalDescription`);
    141. }
    142. //设置远端描述完成
    143. onSetRemoteSuccess = (pc) => {
    144. console.log(`${this.getName(pc)}设置远端描述完成:setRemoteDescription`);
    145. }
    146. //设置描述SD错误
    147. onSetSessionDescriptionError = (error) => {
    148. console.log(`设置描述SD错误: ${error.toString()}`);
    149. }
    150. getName = (pc) => {
    151. return (pc === peerConnA) ? 'peerConnA' : 'peerConnB';
    152. }
    153. //获取到远端视频流
    154. gotRemoteStream = (e) => {
    155. if (remoteVideo.srcObject !== e.streams[0]) {
    156. //取集合第一个元素
    157. remoteVideo.srcObject = e.streams[0];
    158. console.log('peerConnB开始接收远端流');
    159. }
    160. }
    161. //创建应答成功
    162. onCreateAnswerSuccess = async (desc) => {
    163. //输出SDP信息
    164. console.log(`peerConnB的应答Answer数据:\n${desc.sdp}`);
    165. console.log('peerConnB设置本地描述开始:setLocalDescription');
    166. try {
    167. //设置peerConnB的本地描述信息
    168. await peerConnB.setLocalDescription(desc);
    169. this.onSetLocalSuccess(peerConnB);
    170. } catch (e) {
    171. this.onSetSessionDescriptionError(e);
    172. }
    173. console.log('peerConnA设置远端描述开始:setRemoteDescription');
    174. try {
    175. //设置peerConnA的远端描述,即peerConnB的应答信息
    176. await peerConnA.setRemoteDescription(desc);
    177. this.onSetRemoteSuccess(peerConnA);
    178. } catch (e) {
    179. this.onSetSessionDescriptionError(e);
    180. }
    181. }
    182. //Candidate事件回调方法
    183. onIceCandidateA = async (event) => {
    184. try {
    185. if(event.candidate){
    186. //将会peerConnA的Candidate添加至peerConnB里
    187. await peerConnB.addIceCandidate(event.candidate);
    188. this.onAddIceCandidateSuccess(peerConnB);
    189. }
    190. } catch (e) {
    191. this.onAddIceCandidateError(peerConnB, e);
    192. }
    193. console.log(`IceCandidate数据:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
    194. }
    195. //Candidate事件回调方法
    196. onIceCandidateB = async (event) => {
    197. try {
    198. if(event.candidate){
    199. //将会peerConnB的Candidate添加至peerConnA里
    200. await peerConnA.addIceCandidate(event.candidate);
    201. this.onAddIceCandidateSuccess(peerConnA);
    202. }
    203. } catch (e) {
    204. this.onAddIceCandidateError(peerConnA, e);
    205. }
    206. console.log(`IceCandidate数据:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
    207. }
    208. //添加Candidate成功
    209. onAddIceCandidateSuccess = (pc) => {
    210. console.log(`${this.getName(pc)}添加IceCandidate成功`);
    211. }
    212. //添加Candidate失败
    213. onAddIceCandidateError = (pc, error) => {
    214. console.log(`${this.getName(pc)}添加IceCandidate失败: ${error.toString()}`);
    215. }
    216. //监听ICE状态变化事件回调方法
    217. onIceStateChangeA = (event) => {
    218. console.log(`peerConnA连接的ICE状态: ${peerConnA.iceConnectionState}`);
    219. console.log('ICE状态改变事件: ', event);
    220. }
    221. //监听ICE状态变化事件回调方法
    222. onIceStateChangeB = (event) => {
    223. console.log(`peerConnB连接的ICE状态: ${peerConnB.iceConnectionState}`);
    224. console.log('ICE状态改变事件: ', event);
    225. }
    226. //断开连接
    227. hangup = () => {
    228. console.log('结束会话');
    229. //关闭peerConnA
    230. peerConnA.close();
    231. //关闭peerConnB
    232. peerConnB.close();
    233. //peerConnA置为空
    234. peerConnA = null;
    235. //peerConnB置为空
    236. peerConnB = null;
    237. }
    238. render() {
    239. return (
    240. <div className="container">
    241. <h1>
    242. <span>RTCPeerConnection示例span>
    243. h1>
    244. {/* 本地视频 */}
    245. <video ref="localVideo" playsInline autoPlay muted>video>
    246. {/* 远端视频 */}
    247. <video ref="remoteVideo" playsInline autoPlay>video>
    248. <div>
    249. <Button ref="startButton" onClick={this.start} style={{marginRight:"10px"}}>开始Button>
    250. <Button ref="callButton" onClick={this.call} style={{marginRight:"10px"}}>呼叫Button>
    251. <Button ref="hangupButton" onClick={this.hangup} style={{marginRight:"10px"}}>挂断Button>
    252. div>
    253. div>
    254. );
    255. }
    256. }
    257. //导出组件
    258. export default PeerConnection;

       

          

  • 相关阅读:
    跳出打工圈!程序员要如何走上创业逆袭路,获得财富自由
    【前段基础入门之】=>CSS 的常用属性
    14种神笔记方法,只需选择1招,让你的学习和工作效率提高100倍!
    暑假学习IB课程,怎么学?
    面试经典150题——Day17
    Cognex Visionpro-9.5 software handbook translate
    基于Java毕业设计云盘系统修改密码演示2021源码+系统+mysql+lw文档+部署软件
    harbor v1.7.1镜像仓库无法访问,并提示502 Bad Gateway
    【机器学习+NER】手把手教你用机器学习CRF模型构建NER系统(CCL2021)
    ​力扣解法汇总641-设计循环双端队列
  • 原文地址:https://blog.csdn.net/ch_s_t/article/details/141256221