专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

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

  这篇文章开始会实现一个一对一WebRTC和多对多的WebRTC,以及基于屏幕共享的录制。本篇会实现信令和前端部分,信令使用fastity来搭建,前端部分使用Vue3来实现。为什么要使用WebRTC
  WebRTC全称WebRealTimeCommunication,是一种实时音视频的技术,它的优势是低延时。本文章食用者要求了解音视频基础能搭建简单的node服务,docker配置vue框架的使用环境搭建及要求
  废话不多说,现在开始搭建环境,首先是需要开启socket服务,采用的是fastify来进行搭建。详情可以见文档地址,本例使用的是3。x来启动的。接下来安装fastifysocket。io3。0。0插件,详细配置可以见文档,此处不做详细解释。接下来是搭建Vue3,使用vite脚手架搭建简单的demo。
  要求:前端服务运行在localhost或者https下。node需要redis进行数据缓存
  C音视频开发WebRTC学习资料:点击领取音视频开发(资料文档视频教程面试题)(FFmpegWebRTCRTMPRTSPHLSRTP)
  获取音视频
  要实现实时音视频第一步当然是要能获取到视频流,在这里我们使用浏览器提供的API,MediaDevices来进行摄像头流的捕获enumerateDevices
  第一个要介绍的API是enumerateDevices,是请求一个可用的媒体输入和输出设备的列表,例如麦克风,摄像机,耳机设备等。直接在控制台执行API,获取的设备如图
  我们注意到里面返回的设备ID和label是空的,这是由于浏览器的安全策略限制,必须授权摄像头或麦克风才能允许返回设备ID和设备标签,接下来我们介绍如何请求摄像头和麦克风getUserMedia
  这个API顾名思义,就是去获取用户的Meida的,那我们直接执行这个API来看看效果
  ps:由于掘金的代码片段的iframe没有配置allowdisplaycapture;microphone;camera属性,需要手动打开详情查看效果
  通过上述例子我们可以获取到本机的音视频画面,并且可以播放在video标签里,那么我们可以在获取了用户的流之后,重新再获取一次设备列表看看发生了什么变化
  在获取了音视频之后,获取的设备列表的详细信息已经出现,我们就可以获取指定设备的音视频数据,
  这里介绍一下getUserMedia的参数constraints,视频参数配置interfaceMediaTrackConstraintSet{画面比例aspectRatio?:ConstrainDouble;设备ID,可以从enumerateDevices中获取deviceId?:ConstrainDOMString;摄像头前后置模式,一般适用于手机facingMode?:ConstrainDOMString;帧率,采集视频的目标帧率frameRate?:ConstrainDouble;组ID,用一个设备的输入输出的组ID是同一个groupId?:ConstrainDOMString;视频高度height?:ConstrainULong视频宽度width?:ConstrainULong;}音频参数配置interfaceMediaTrackConstraintSet{是否开启AGC自动增益,可以在原有音量上增加额外的音量autoGainControl?:ConstrainBoolean;声道配置channelCount?:ConstrainULong;设备ID,可以从enumerateDevices中获取deviceId?:ConstrainDOMString;是否开启回声消除echoCancellation?:ConstrainBoolean;组ID,用一个设备的输入输出的组ID是同一个groupId?:ConstrainDOMString;延迟大小latency?:ConstrainDouble;是否开启降噪noiseSuppression?:ConstrainBoolean;采样率单位HzsampleRate?:ConstrainULong;采样大小,单位位sampleSize?:ConstrainULong;本地音频在本地扬声器播放suppressLocalAudioPlayback?:ConstrainBoolean;}
  C音视频开发WebRTC学习资料:点击领取音视频开发(资料文档视频教程面试题)(FFmpegWebRTCRTMPRTSPHLSRTP)
  一对一连接
  当我们采集到了音视频数据,接下来就是要建立链接,在开始之前需要科普一下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}fromuuid;import{io,Socket}fromsocket。ioclient;constmyUserIdref(uuid());letsocket:Socket;socketio(http:127。0。0。1:7070,{query:{房间号,由输入框输入获得room:room。value,userId通过uuid获取userId:myUserId。value,昵称,由输入框输入获得nick:nick。value}});
  可以查看chrome的控制台,检查ws的链接情况,如果出现跨域,请查看socket。io的server配置并开启cors配置。创建offer
  开始创建RTCPeerConnection,这里采用google的公共stun服务constpeerConnectnewRTCPeerConnection({iceServers:〔{urls:stun:stun。l。google。com:19302}〕})
  根据上面的流程图我们下一步要做的事情是用上面的方式获取视频流,并将获取到的流添加到RTCPeerConnection中,并创建offer,把这个offer设置到这个rtcPeer中,并把offer发送给socket服务letlocalStream:MediaStream;stream。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接下来会收到流添加的事件,这个事件会有两次,分别是音频和视频的数据
  C音视频开发WebRTC学习资料:点击领取音视频开发(资料文档视频教程面试题)(FFmpegWebRTCRTMPRTSPHLSRTP)
  处理音视频数据peerConnect。ontrack(track:RTCTrackEvent){if(track。track。kindvideo){constvideodocument。createElement(video);video。srcObjecttrack。streams〔0〕;video。autoplaytrue;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。autoplaytrue;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。room;constredisfastify。redis;letuserList;获取当前房间的数据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。redis;returnawaitredis。get(request。query。room);})多对多初始化
  由于需要给每个人发送offer,需要对上面的初始化函数进行封装。创建RTCPeerConnectionparamcreatorUserId创建者id,本人paramrecUserId接收者idconstinitPeerasync(creatorUserId:string,recUserId:string){constpeerConnectnewRTCPeerConnection({iceServers:〔{urls:stun:stun。l。google。com:19302}〕})returnpeerConnect;})
  由于存在多份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)return;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)return;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获取失败)return;}awaitpeer。setRemoteDescription(data。sdp)}。。。。。。处理播放,处理ICE候选
  到目前为止,就实现了一个基于mesh的WebRTC的多对多通信
  C音视频开发WebRTC学习资料:点击领取音视频开发(资料文档视频教程面试题)(FFmpegWebRTCRTMPRTSPHLSRTP)
  基于WebRTC的屏幕录制getDisplayMedia
  这个API是在MediaDevices里面的一个方法,是用来获取屏幕共享的。
  这个MediaDevices接口的getDisplayMedia()方法提示用户去选择和授权捕获展示的内容或部分内容(如一个窗口)在一个MediaStream里。然后,这个媒体流可以通过使用MediaStreamRecordingAPI被记录或者作为WebRTC会话的一部分被传输。awaitnavigator。mediaDevices。getDisplayMedia()
  MediaRecorder
  获取到屏幕共享流后,需要使用MediaRecorder这个api来对流进行录制,接下来我们先获取屏幕流,同时创建一个MeidaRecord类letscreenStream:MediaStream;letmediaRecord:MediaRecorder;letblobMedia:(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)asHTMLVideoElement;constblobnewBlob(blobMedia,{type:videowebm})if(scVideo){scVideo。srcURL。createObjectURL(blob);}}else{console。log(没有录制文件);}}constdownloadLocalRecordasync(){if(!blobMedia。length){console。log(没有录制文件);return;}constblobnewBlob(blobMedia,{type:videowebm});consturlURL。createObjectURL(blob);constadocument。createElement(a);a。hrefurl;a。download录屏{Date。now()}。webm;a。click();}
  这里有一个基于Vue2的完整例子ps:由于掘金的代码片段的iframe没有配置allowdisplaycapture;microphone;camera属性,需要手动打开详情查看效果
  后续将会更新,WebRTC的自动化测试,视频画中画,视频截图等功能

小米RedmiPad限时降价华为电子医保卡内测苹果AppleTV断连bug苹果这产品现断连bug在过去的几个月里,大量的苹果AppleTV用户一直反映SiriRemote遥控器的连接存在问题,然而苹果仍然没有为这些问题推出修复方案,即使最近更新了tvOS话题曼城追到只差3分天王山之战备受期待又不是我们举报的你,干嘛下手这么狠?这是社交平台上部分阿斯顿维拉球迷在球队1比3不敌曼城后的留言。身处话题旋涡中的曼城将士近来压力的确不小,而让他们暂时放松的最好方式就是去赢下比赛归化2金1银后,谷爱凌晒出美照,身穿水手服,大长腿衬托更吸引最近几年中国体育在有些项目提出了归化,效果十分明显,可能很多人会拿中国男足说事,说他们花了几个亿,归化了一大堆外援和华裔球员,但是最后中国男足并没有进入世界杯,很多人觉得这样的归化再见小波特!火箭灰熊商讨1换1交易,莫兰特替补空降休斯顿?小凯文波特即将回归!但火箭球迷心中似乎更加希望他赛季报销。近期,火箭主帅塞拉斯在接受采访时表示,小波特恢复良好,有望在全明星赛之后回归赛场,他此前由于脚趾伤势,已缺席了15场比赛。随着绿凯4连胜,篮网2连败,东部排名已成定局?1东部第一波士顿凯尔特人,战绩41胜16负,预估后面19胜6负,最终60胜22负高居东部第1!凯尔特人整体太强了,内线罗威霍福德慕斯卡拉,锋线塔图姆格威布朗,后场布罗格登斯玛特怀特时隔一年2。12奇迹再现!中国速度又一次震惊国际滑冰赛场2月12日,林孝埈成功问鼎短道速度滑冰荷兰多德雷赫特站男子500米赛场,收获了他在本赛季世界杯的第3枚金牌。去年2月12日,名将高亭宇在北京冬奥会男子速度滑冰500米力挫群雄,实现蔡斌为中国女排殚精竭虑,却得不到球迷认可,根源或是刁琳宇蔡斌出任中国女排主教练一职至今已经有一年有余。在这一年多的时间里,中国女排在蔡斌的带领下完成了球员选拔,球队重组等重要基础工作,并参加了世界女排和世锦赛两项国际大赛。从基础工作上说历史最强十大左后卫,卡洛斯布雷默马尔蒂尼马塞洛谁更强序言相比中前场球员,边后卫对身体素质的要求更加苛刻。该位置的球员除进攻以外,还被多安排了一项防守任务,速度自然也就成了最基本的指标。对于中锋而言,速度慢一点没关系,只要吃饼能力强,NBA东部最新积分榜绿衫军4连胜稳居榜首,猛龙反超公牛挤进前十北京时间2月13日,由于超级碗的关系,NBA今天只有2场比赛,而且早早结束,分别是凯尔特人119109战胜西部第二灰熊,猛龙主场119118险胜活塞,我们一起看看东部积分榜的变动。中国足球,需要上演一场爱情保卫战最近关注和讨论中国足球的人少了,中国足球的社会影响力在下降,为什么呢?仔细想想问题还是出在中国足球自身。所谓爱之深,责之切!球迷伤心了,失望了,甚至是绝望了!中国足球已经陷入了信任重庆市青少年游泳锦标赛圆满落下帷幕来源重庆市体育局部门动态2月12日,2022年奔跑吧少年重庆市青少年游泳锦标赛在万州区落下帷幕。本次赛事为期三天,共有来自市内的29支代表队400余名运动员参加。赛事分为1112岁
漳州龙池岩寺,同南普陀寺同年代的寺庙,悄悄告诉你求财很灵验这座寺庙距今已有1600多年了,很多人都知道厦门有南普陀寺,却很少有人知道在漳州还有一座在漳州和厦门本地特别出名的一座寺庙。它就是龙池岩寺。龙池岩寺和南普陀寺建筑年代很相近,为什么你们的水桶包包有几种背法包治百病这句真理是真的太懂女人的心思了。对于一个女人来讲,是比较注重日常出行搭配的,所以家里面都存在各种各式的包包,不同的包包搭配不同的场合,以至于拥有众多款式的包包,女人对包包的2023超全!Chanel春夏新款包包,有你喜欢的吗?码住了万众期待!香奈儿2023春夏系列来炸场!姐妹们准备好你们钱包了吗?下面就一起来看看吧2023超全!Chanel春夏新款包包嫩绿系列迷你口盖包参考价38,200亮面小羊皮与提花金属淡LORFFINY洛菲尼亚包包是什么档次?有多贵?洛菲尼亚是近些年比较流行的一个轻奢时尚品牌,英文名LORFFINY,旗下的产品涵盖范围非常广,有包包服饰等。作为新锐轻奢时尚品牌,LORFFINY的价格不算很高,极具性价比,款式非穷鬼版实用通勤大包!包包届卷王实锤!头条创作挑战赛有没有和我一样爱买包包的姐妹!都说女生的衣柜里永远缺一件衣服,我的衣柜真的很缺包包!需要通勤或者是带着一堆书上课的姐妹,我真的懂你们一定超想入一款既实用百搭又时髦独特宋丹丹夫妇设宴请客,邓婕拎2万6包包赴约,张国立戴礼帽显大佬范3月21日一早,有八卦媒体释出一则宋丹丹赵玉吉夫妇与张国立邓婕夫妇深夜聚餐的画面,老友相聚分外放松,现场气氛融洽十分热络!晚宴结束后,68岁赵玉吉站在酒店门口忙着张罗与应酬,作为东手把手教你,如何定制一只爱马仕包包分100期买表值得吗?精选文章推荐2020年,Angel(太太)25公岁,也是我们结婚25周年。每到纪念日总是考验人,为礼物发愁,但是对我来说小菜一碟。早在2019年就定了一只表,大牌包包昵称,你知道多少,轻松get时尚黑话刚接触奢侈品大牌的姐妹在初期一定会被很多包包名称搞的不知所措,什么是云朵包,什么是野餐包,为什么叫千禧娃娃,很多人都会感到奇怪,今天就给大家讲讲奢侈品很多命名方式以外形命名奢侈品包香奈儿包包官网CHANEL22mini新经典!香奈儿官网新包价格目录香奈儿包包官网CHANEL22是香奈儿众多经典之外的后起之秀,跟鼎鼎大名的19包一样,以创作年份2022年为包款命名纪念,22最具识别的就是包身温润柔软的皮革与率性而传递大无谓姿态妇女能顶半边天竟然是从杭州起源的!它现在又融入了民族团结新元素之江同心石榴红杭州民族团结好故事杭州市委统战部杭州市民族宗教事务局联合都市快报推出在杭州建德流传着一张老照片上个世纪50年代中期,千鹤村的稻田一派农忙景象,妇女成了下田劳作的重要劳牛仔裤穿腻了?今年穿这条西装裤,时髦又高级!嗨,各位小仙女们,大家好呀!如果你穿腻了牛仔裤,那么今年一定要给自己入手一条西装裤,这种裤子最大的优点就是休闲舒适,版型更是宽松的,所以,无论是什么身材穿起来,都会把双腿显得又瘦又
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网