您的位置:首页 > 游戏 > 手游 > WebRTC音视频开发读书笔记(四)

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

2024/10/6 16:24:46 来源:https://blog.csdn.net/ch_s_t/article/details/141256221  浏览:    关键词: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开始');//创建提议Offerconst 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 {//创建应答Answerconst 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('结束会话');//关闭peerConnApeerConnA.close();//关闭peerConnBpeerConnB.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;

   

      

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com