临汾山东漯河饰品美体美文
投稿投诉
美文创意
爱情通信
用品婚姻
爱好看病
美体软件
影音星座
瑜伽周边
星座办公
饰品塑形
搞笑减肥
幼儿两性
智家潮品
漯河衢州
兴义眉山
桂林阳泉
玉溪简阳
山东遂宁
永城新余
梧州洛阳
泸州温州
临汾清远
营口常熟
浙江大连
桐乡宜昌

从0搭建一个WebRTC,实现多房间多对多通话,并实现屏幕录

7月15日 吴梦筱投稿
  这篇文章开始会实现一个一对一WebRTC和多对多的WebRTC,以及基于屏幕共享的录制。本篇会实现信令和前端部分,信令使用fastity来搭建,前端部分使用Vue3来实现。为什么要使用WebRTCWebRTC全称WebRealTimeCommunication,是一种实时音视频的技术,它的优势是低延时。本片文章食用者要求了解音视频基础能搭建简单的node服务,docker配置vue框架的使用
  环境搭建及要求废话不多说,现在开始搭建环境,首先是需要开启socket服务,采用的是fastify来进行搭建。详情可以见文档地址,本例使用的是3。x来启动的。接下来安装fastifysocket。io3。0。0插件,详细配置可以见文档,此处不做详细解释。接下来是搭建Vue3,使用vite脚手架搭建简单的demo。要求:前端服务运行在localhost或者https下。node需要redis进行数据缓存获取音视频要实现实时音视频第一步当然是要能获取到视频流,在这里我们使用浏览器提供的API,MediaDevices来进行摄像头流的捕获enumerateDevices第一个要介绍的API是enumerateDevices,是请求一个可用的媒体输入和输出设备的列表,例如麦克风,摄像机,耳机设备等。直接在控制台执行API,获取的设备如图
  我们注意到里面返回的设备ID和label是空的,这是由于浏览器的安全策略限制,必须授权摄像头或麦克风才能允许返回设备ID和设备标签,接下来我们介绍如何请求摄像头和麦克风getUserMedia这个API顾名思义,就是去获取用户的Meida的,那我们直接执行这个API来看看效果ps:由于掘金的代码片段的iframe没有配置camera属性,需要手动打开详情查看效果
  通过上述例子我们可以获取到本机的音视频画面,并且可以播放在video标签里,那么我们可以在获取了用户的流之后,重新再获取一次设备列表看看发生了什么变化
  在获取了音视频之后,获取的设备列表的详细信息已经出现,我们就可以获取指定设备的音视频数据,详情可以见
  这里介绍一下getUserMedia的参数constraints,视频参数配置interfaceMediaTrackConstraintSet{画面比例aspectRatio?:ConstrainD设备ID,可以从enumerateDevices中获取deviceId?:ConstrainDOMS摄像头前后置模式,一般适用于手机facingMode?:ConstrainDOMS帧率,采集视频的目标帧率frameRate?:ConstrainD组ID,用一个设备的输入输出的组ID是同一个groupId?:ConstrainDOMS视频高度height?:ConstrainULong视频宽度width?:ConstrainUL}
  音频参数配置interfaceMediaTrackConstraintSet{是否开启AGC自动增益,可以在原有音量上增加额外的音量autoGainControl?:ConstrainB声道配置channelCount?:ConstrainUL设备ID,可以从enumerateDevices中获取deviceId?:ConstrainDOMS是否开启回声消除echoCancellation?:ConstrainB组ID,用一个设备的输入输出的组ID是同一个groupId?:ConstrainDOMS延迟大小latency?:ConstrainD是否开启降噪noiseSuppression?:ConstrainB采样率单位HzsampleRate?:ConstrainUL采样大小,单位位sampleSize?:ConstrainUL本地音频在本地扬声器播放suppressLocalAudioPlayback?:ConstrainB}
  一对一连接当我们采集到了音视频数据,接下来就是要建立链接,在开始之前需要科普一下WebRTC的工作方式,我们常见有三种WebRTC的网络结构MeshMCUSFU关于这三种模式的区别可以查看文章来了解
  在这里由于设备的限制,我们采用Mesh的方案来进行开发一对一的流程我们建立一对一的链接需要知道后流程是怎么流转的,接下来上一张图,便可以清晰的了解
  这里是由ClientA发起B来接受A的视频数据。上图总结可以为A创建本地视频流,把视频流添加到PeerConnection里面创建一个Offer给B,B收到Offer以后,保存这个offer,并响应这个Offer给A,A收到B的响应后保存A的远端响应,进行NAT穿透,完成链接建立。话已经讲了这么多,我们该怎么建立呢,光说不做假把式,接下来,用我们的项目创建一个来试试初始化首先启动fastify服务,接下来在Vue项目安装socket。ioclient4然后连接服务端的socketimport{v4asuuid}import{io,Socket}fromsocket。constmyUserIdref(uuid());letsocket:Ssocketio(http:127。0。0。1:7070,{query:{房间号,由输入框输入获得room:room。value,userId通过uuid获取userId:myUserId。value,昵称,由输入框输入获得nick:nick。value}});
  可以查看chrome的控制台,检查ws的链接情况,如果出现跨域,请查看socket。io的server配置并开启cors配置。【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(CC,Linux,FFmpegwebRTCrtmphlsrtspffplaysrs等等)有需要的可以后台私信扣1免费领取
  创建offer
  开始创建RTCPeerConnection,这里采用google的公共stun服务constpeerConnectnewRTCPeerConnection({iceServers:〔{urls:stun:stun。l。google。com:19302}〕})
  根据上面的流程图我们下一步要做的事情是用上面的方式获取视频流,并将获取到的流添加到RTCPeerConnection中,并创建offer,把这个offer设置到这个rtcPeer中,并把offer发送给socket服务letlocalStream:MediaSstream。getTracks()。forEach((track){peerConnect。addTrack(track,stream)})constofferawaitpeerConnect。createOffer();awaitpeerConnect。setLocalDescription(offer);socket。emit(offer,{creatorUserId:myUserId。value,sdp:offer},(res:any){console。log(res);});
  socket服务收到了这份offer后需要给B发送A的offerfastify。io。on(connection,async(socket){socket。on(offer,async(offer,callback){socket。emit(offer,offer);callback({status:ok})})})处理offer
  B需要监听socket里面的offer事件并创建RTCPeerConnection,将这个offer设置到远端,接下来来创建响应。并且将这个响应设置到本地,发送answer事件回复给Asocket。on(offer,async(offer:{sdp:RTCSessionDescriptionInit,creatorUserId:string}){constpeerConnectnewRTCPeerConnection({iceServers:〔{urls:stun:stun。l。google。com:19302}〕})awaitpeerConnect。setRemoteDescription(offer。sdp);constanswerawaitpeerConnect。createAnswer();awaitpeerConnect。setLocalDescription(answer);socket。emit(answer,{sdp:answer},(res:any){console。log(res);})})处理answer
  服务端广播answersocket。on(offer,async(offer,callback){socket。emit(offer,offer);callback({status:ok})})
  A监听到socket里面的answer事件,需要将刚才的自己的RTCpeer添加远端描述socket。on(answer,async(data:{sdp:RTCSessionDescriptionInit}){awaitpeerConnect。setRemoteDescription(data。sdp)})处理ICEcandidate
  接下来A会获取到ICE候选信息,需要发送给BpeerConnect。onicecandidate(candidateInfo:RTCPeerConnectionIceEvent){if(candidateInfo。candidate){socket。emit(ICEcandidate,{sdp:candidateInfo。candidate},(res:any){console。log(res);})}}
  广播消息是同理这里就不再赘述了,B获取到了A的ICE,需要设置候选socket。on(ICEcandidate,async(data:{sdp:RTCIceCandidate}){awaitpeerConnect。addIceCandidate(data。sdp)})
  接下来B也会获取到ICE候选信息,同理需要发送给A,待A设置完成之后便可以建立链接,代码同上,B接下来会收到流添加的事件,这个事件会有两次,分别是音频和视频的数据处理音视频数据peerConnect。ontrack(track:RTCTrackEvent){if(track。track。kindvideo){constvideodocument。createElement(video);video。srcObjecttrack。streams〔0〕;video。video。style。setProperty(width,400px);video。style。setProperty(aspectratio,169);video。setAttribute(id,track。track。id)document。body。appendChild(video)}if(track。track。kindaudio){constaudiodocument。createElement(audio);audio。srcObjecttrack。streams〔0〕;audio。audio。setAttribute(id,track。track。id)document。body。appendChild(audio)}}
  到这里你就可以见到两个视频建立的P2P链接了。到这里为止只是建立了视频的一对一链接,但是我们可以通过这些操作进行复制,就能进行多对多的连接了。多对多连接在开始我们需要知道,一个人和另一个人建立连接双方都需要创建自己的peerConnection。对于多人的情况,首先我们需要知道进入的房间里面当前的人数,给每个人都创建一个RtcPeer,同时收到的人也回复这个offer给发起的人。
  对于后进入的人,需要让已经创建音视频的人给后进入的人创建新的offer。基于上面的流程,我们现在先实现一个成员列表的接口成员列表的接口在我们登录socket服务的时候我们在query参数里面有房间号,userId和昵称,我们可以通过redis记录对应的房间号的登录和登出,从而实现成员列表。可以在某一个人登录的时候获取一下redis对应房间的成员列表,如果没有这个房间,就把这个人丢进新的房间,并且存储到redis中,方便其他人登录这个房间的时候知道现在有多少人。fastify。io。on(connection,async(socket){constroomsocket。handshake。query。constredisfastify。letuserL获取当前房间的数据awaitgetUserList()asyncfunctiongetUserList(){constroomUserawaitredis。get(room);if(roomUser){userListnewMap(JSON。parse(roomUser))}else{userListnewMap();}}asyncfunctionsetRedisRoom(){awaitredis。set(room,JSON。stringify(〔。。。userList〕))}functionrmUser(userId){userList。delete(userId);}if(room){将这人加入到对应的socket房间socket。join(room);awaitsetRedisRoom();广播有人加入了socket。to(room)。emit(join,userId);}这个人断开了链接需要将这个人从redis中删除socket。on(disconnect,async(socket){awaitgetUserList();rmUser(userId);awaitsetRedisRoom();})})
  到上面为止,我们实现了成员的记录、广播和删除。接下来是需要实现一个成员列表的接口,提供给前端项目调用。fastify。get(userlist,asyncfunction(request,reply){constredisfastify。returnawaitredis。get(request。query。room);})多对多初始化
  由于需要给每个人发送offer,需要对上面的初始化函数进行封装。创建RTCPeerConnectionparamcreatorUserId创建者id,本人paramrecUserId接收者idconstinitPeerasync(creatorUserId:string,recUserId:string){constpeerConnectnewRTCPeerConnection({iceServers:〔{urls:stun:stun。l。google。com:19302}〕})returnpeerC})
  由于存在多份rtc的映射关系,我们这里可以用Map来实现映射的保存constpeerConnectListnewMap();constinitPeer(){ice,track,newPeer等其他代码。。。。。。peerConnectList。set({creatorUserId}{recUserId},peerConnect);}获取成员列表
  上面实现了成员列表。接下来进入了对应的房间后需要轮询获取对应的成员列表letuserListref(〔〕);constintoRoom(){其他代码。。。。。。setInterval((){axios。get(userlist,{params:{room:room。value}})。then((res){userList。valueres。data})},1000)}创建多对多的Offer和Answer
  在我们获取到视频流的时候,可以对在线列表里除了自己的人都创建一个RTCpeer,来进行一对一连接,从而达到多对多连接的效果。过滤自己constemitListuserList。value。filter((item)item〔0〕!myUserId。value);for(constitemofemitList){item〔0〕就是目标人的userIdconstpeerawaitinitPeer(myUserId。value,item〔0〕);awaitcreateOffer(item〔0〕,peer);}constcreateOfferasync(recUserId:string,peerConnect:RTCPeerConnection,stream:MediaStreamlocalStream){if(!localStream)stream。getTracks()。forEach((track){peerConnect。addTrack(track,stream)})constofferawaitpeerConnect。createOffer();awaitpeerConnect。setLocalDescription(offer);socket。emit(offer,{creatorUserId:myUserId。value,sdp:offer,recUserId},(res:any){console。log(res);});}
  那么在socket服务中我们怎么只给对应的人进行事件广播,不对其他人进行广播,我们可以用找到这个人userId对应的socketId,进而只给这一个人广播事件。首先获取IO对应的nameSpaceconstIONameSpacefastify。io。of();发送Offer给对应的人socket。on(offer,async(offer,callback){重新从reids获取用户列表awaitgetUserList();找到目标的UserId的数据constuseruserList。get(offer。recUserId);if(user){找到对应的socketIdconstioIONameSpace。sockets。get(user。sockId);if(!io)io。emit(offer,offer);callback({status:ok})}})
  其他人需要监听socket的事件,每个人都需要处理对应自己的offer。socket。on(offer,handleOffer);consthandleOfferasync(offer:{sdp:RTCSessionDescriptionInit,creatorUserId:string,recUserId:string}){constpeerawaitinitPeer(offer。creatorUserId,offer。recUserId);awaitpeer。setRemoteDescription(offer。sdp);constanswerawaitpeer。createAnswer();awaitpeer。setLocalDescription(answer);socket。emit(answer,{recUserId:myUserId。value,sdp:answer,creatorUserId:offer。creatorUserId},(res:any){console。log(res);})}
  接下来的步骤其实就是和一对一是一样的了,后面还需要发起offer的人处理对应peer的offer、以及ICE候选,还有流进行挂载播放。socket。on(answer,handleAnswer)应答方回复consthandleAnswerasync(data:{sdp:RTCSessionDescriptionInit,recUserId:string,creatorUserId:string}){constpeerpeerConnectList。get({data。creatorUserId}{data。recUserId});if(!peer){console。warn(handleAnswerpeer获取失败)}awaitpeer。setRemoteDescription(data。sdp)}。。。。。。处理播放,处理ICE候选
  到目前为止,就实现了一个基于mesh的WebRTC的多对多通信。在这里附上了一个完整的Demo可供参考socketServerFontPage基于WebRTC的屏幕录制getDisplayMedia这个API是在MediaDevices里面的一个方法,是用来获取屏幕共享的。这个MediaDevices接口的getDisplayMedia()方法提示用户去选择和授权捕获展示的内容或部分内容(如一个窗口)在一个MediaStream里。然后,这个媒体流可以通过使用MediaStreamRecordingAPI被记录或者作为WebRTC会话的一部分被传输。awaitnavigator。mediaDevices。getDisplayMedia()复制代码MediaRecorder获取到屏幕共享流后,需要使用MediaRecorder这个api来对流进行录制,接下来我们先获取屏幕流,同时创建一个MeidaRecord类letscreenStream:MediaSletmediaRecord:MediaRletblobMedia:(Blob)〔〕〔〕;conststartLocalRecordasync(){blobMedia〔〕;try{screenStreamawaitnavigator。mediaDevices。getDisplayMedia();screenStream。getVideoTracks()〔0〕。addEventListener(ended,(){console。log(用户中断了屏幕共享);endLocalRecord()})mediaRecordnewMediaRecorder(screenStream,{mimeType:videowebm});mediaRecord。ondataavailable(e){if(e。datae。data。size0){blobMedia。push(e。data);}};500是每隔500ms进行一个保存数据mediaRecord。start(500)}catch(e){console。log(屏幕共享失败{e});}}
  获取到了之后可以使用Blob进行处理constreplayLocalRecordasync(){if(blobMedia。length){constscVideodocument。querySelector(screenVideo)asHTMLVideoEconstblobnewBlob(blobMedia,{type:videowebm})if(scVideo){scVideo。srcURL。createObjectURL(blob);}}else{console。log(没有录制文件);}}constdownloadLocalRecordasync(){if(!blobMedia。length){console。log(没有录制文件);}constblobnewBlob(blobMedia,{type:videowebm});consturlURL。createObjectURL(blob);constadocument。createElement(a);a。a。download录屏{Date。now()}。a。click();}
  这里有一个基于Vue2的完整例子
  ps:由于掘金的代码片段的iframe没有配置camera属性,需要手动打开详情查看效果
  后续将会更新,WebRTC的自动化测试,视频画中画,视频截图等功能
  作者:sxuan
投诉 评论

华为与北汽再推新模式产品?极狐销量不振处境尴尬文九才出品节点AUTO近日,关于华为和北汽将在极狐品牌之外,再推出智选车的消息甚嚣尘上。对此,极狐汽车向《证券日报》回应称:目前还不完全掌握相关信息,请以未来官方渠……大摩警惕房间里的大象QT,美股可能还要再跌15当市场把焦点放在美联储加息上时,忽略了另一大威胁缩表(QT)。摩根士丹利警告称,QT可能令美股降至新低。在量化宽松等政策的影响下,美股迎来了十年长牛,而如今量化紧缩已然开……从0搭建一个WebRTC,实现多房间多对多通话,并实现屏幕录这篇文章开始会实现一个一对一WebRTC和多对多的WebRTC,以及基于屏幕共享的录制。本篇会实现信令和前端部分,信令使用fastity来搭建,前端部分使用Vue3来实现。为什……2023,我要带你来内蒙古跨年!2023时光如梭一转眼我们将告别2022迎来全新的2023一个再没有疫情无须出示核算行程码的2023你想好在哪里迎来这个具有特殊意义的……顺丰同城急送寄丢20克黄金,保价八千只赔两千,顺丰遗失黄金已9月13日,顺丰寄丢20克黄金保价八千只赔两千这一话题登上热搜,引发热议。此事发生以后,公司第一时间协助客户报警,已按照8000元的保价金额全额先行赔付到账。经过警方的调……笑中芯国际55nm?业内人外行人通病芯片制程,分为高端和中低端。其中,智能手机,PC,服务器等所使用的CPU,GPU一般都是基于先进制程,而智能汽车,航天,军用等领域使用的芯片一般都是基于中低端制程。因为智……四川首富新希望四兄弟放弃铁饭碗,从养鹌鹑迈向世界五百强!2022年新希望成立40周年。历经40年发展,新希望发展壮大成营业规模超2500亿元、员工超10万人的世界500强企业,目前世界排名356位置。在40年的发展中,新希望始终坚持……中国男篮最致命的三大问题从易建联退出国家队之后,中国男篮每况愈下,今年的亚洲杯已经沦落到了亚洲第八,并且很可能会连续很多年无法进入奥运会。当前国家队的主教练杜峰也是争议不断,不仅带队成绩差,还任人唯亲……冬季到盐湖来看硝花近日,随着气温不断下降,有中国死海之称的山西运城盐湖出现冬日特有的硝花景象,成为一道靓丽的风景。硝花又叫硝凇,是芒硝结晶在盐板上的一种特有的物理现象。随着冬日气温的持续走……成都妹子出任脸书CFO!15岁考上斯坦福的她,打破华裔天花板前几天,美国社交媒体巨头Meta(Facebook母公司)CEO扎克伯格宣布裁员13,人数超过11000人。大浪淘沙过后,能留下来的都是精英中的精英。出生于成都的华……张怡宁嫁给爱情,婚后笑容多了,六旬老公欲拼三胎她全力配合张怡宁今年41岁,过了年马上就42了,但她现在的颜值却远远超过了年轻时,因为笑容多了,看来她这是嫁给了爱情。据说老公徐威有意拼三胎,她要全力配合,她为爱情的牺牲蛮大的!……抖音突袭,华为入局网约车有了新内涵作者肖卓一则在抖音上打网约车或将成为现实的消息,让本就不平静的网约车行业再起波澜。12月26日,Tech星球报道称,抖音已经开放交通出行服务的平台服务商入驻资格,T……
双子星大战第二场晏鼠组合VS黑人篮球教练刘佳华师大队长丁超头条创作挑战赛在昨天结束的由恶鼠梓祎发起的双子星大战揭幕战中,经过上下半场鏖战,最终恶鼠梓祎和北体彭于晏程鑫凯组成的晏鼠组合102:94力克由华侨大学刘江潮及队友张高铭组成的高……曝华为P50Pocketnew折叠屏搭载XMAGE影像9月13日消息,此前型号为BALAL80的手机被认为是华为P50Pocket竖向折叠屏手机衍生产品,通过了3C认证,支持40W快充。近期这款华为P50Pocketnew折叠屏新……听良言听良言董卿说:世间一切,都是遇见。人生道阻且长,遇见很多事,也遇见很多人。有的人,一相遇就惊艳了时光,也温暖了岁月。就像是命运的馈赠,为我们照亮前路,启迪未来。人生有五大……2022影像旗舰大盘点,不止有iPhone14Pro,这三款手机影像一直是各大厂商们互卷的重点领域,近几年来手机影像实力越来越强,诞生出许多知名的影像旗舰,2022年也是如此。今年各家互卷影像的力度再次加大,连苹果都升级了自家iPhon……一个赶走老痰的中成药,还能治这5种病,你知道吗?建议收藏大家好,我是沈医生。今天给大家讲一个赶走我们身上老痰的中成药,但是咱今天不说它化痰的作用,咱说说它容易被忽略的5种作用,来看看你知不知道吧。今天沈医生要说的这个中成药,叫……美国飞机餐简单粗暴,还是中国飞机餐得劲,空腹进好吃到扶墙走生活水平提高了,大家在节假日大多会选择旅游,如今出行的交通工具越来越多元,短途可以自驾,中长途都可以选择动车高铁,需要远行最好选择乘坐飞机。因为速度非常快,给旅途带来了很……CBA最新积分榜山西9782天津排名第5北京时间2022年10月27日,20222023赛季CBA常规赛第8轮继续进行,在第8轮第1比赛日中一共有5场比赛,目前5场比赛全部战罢,主要战报信息如下:江苏肯帝亚91比90……I。1。12请不要轻率地拿婴幼儿的天真稚气来寻开心现实中不时会看到这样的场景:几个大人围着小屁孩一问一答地寻乐。绝大多数情况下都是小屁孩充满天真稚气地对答,大人们就偶尔会乐翻了天,不少人还会拿出手机拍照录像。因为小孩子的天真稚……如何看待中国8月出口?高智谋王青表示,8月出口增速下滑,但仍体现较强韧性。张静静认为,未来出口增速主要取决于高温缺电、疫情反扑、人民币贬值、对欧盟替代效应四个新变化的影响。在海外经济增长……怪物猎人崛起曙光全盟友任务ampampamp血量一览《怪物猎人曙光》的要害使命和鼓起设定相同,只需完结满意使命数量就能触发紧迫使命抵达下个阶段推动剧情等,不过这次大师会把一段等级分为23部分使命触发多次紧迫使命;《怪物猎人……海洋旅游玩起来!大邮轮中国造,第一艘就快到中新网8月20日电(中新财经记者吴涛)到2025年,邮轮游艇装备产业体系初步建成,国产大型邮轮建成交付,中型邮轮加快推进,小型邮轮实现批量建造,游艇产品系列多样规模化生产,旅游……老成都小吃的全套配方,当初自用的,小店大利润,成都小吃很火爆餐饮新人建议从小餐饮店开始,只要好好做几乎没赔的。下面四款,加上冰粉小面,就是一家投资少利润大的店。复制酱油。物料:生抽500g,老抽500g,红糖500g,冰糖1……
友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找