流程介绍通过MediaDevices.getUserMedia()获取音视频轨道。通过createOffer()启动与远程对等点的新WebRTC连接。信令用于传达上传错误并控制会话的打开或关闭。交换媒体和客户端信息初始化操作元素conststartButton=document.getElementById('startButton');constcallButton=document.getElementById('callButton');consthangupButton=document.getElementById('hangupButton');constvideo1=document.querySelector('video#video1');constvideo2=document.querySelector('video#video2');constvideo3=document.querySelector('video#video3');callButton.disabled=true;hangupButton。禁用=真;startButton.onclick=start;callButton.onclick=call;hangupButton.onclick=hangup;letpc1Local;letpc1Remote;letpc2Local;letpc2Remote;constofferOptions={offerToReceiveAudio:1,offerToReceiveVideo:1};开始收集音频和视频mediaDevices是Navigator的一个只读属性,它返回一个MediaDevices对象,该对象提供对媒体输入设备(例如相机和麦克风)的连接访问,包括屏幕共享。MediaDevices接口提供对连接的媒体输入设备(例如相机和麦克风)和屏幕共享的访问。它允许您从任何硬件资源获取媒体数据。MediaDevices.getUserMedia()将提示用户允许使用媒体输入,这将生成一个包含所请求媒体类型的轨道的MediaStream。此流可以包含视频轨道(来自硬件或虚拟视频源,例如相机、视频捕获设备、屏幕共享服务等)、音频轨道(也来自硬件或虚拟音频源,例如麦克风、A/D转换器等),以及可能的其他轨道类型。它返回一个Promise对象,当成功时,它会解析并回调一个MediaStream对象。如果用户拒绝许可,或者所需的媒体源不可用,则承诺将拒绝并返回PermissionDeniedError或NotFoundError。函数开始(){startButton.disabled=true;navigator.mediaDevices.getUserMedia({audio:true,video:true}).then(stream=>{video1.srcObject=stream;window.localStream=stream;callButton.disabled=false;}).catch(e=>console.log(e));}远程播放视频RTCPeerConnection()构造函数返回一个新创建的RTCPeerConnection实例,它表示本地机器和远程机器之间的连接。接口表示从本地计算机到远程端的WebRTC连接。此接口提供用于创建、维护、监视和关闭连接的方法的实现。RTCPeerConnection接口的createOffer()方法启动SDP提议的创建,目的是启动与远程对等点的新WebRTC连接。SDP报价包括有关MediaStreamTrack附加到WebRTC会话的任何对象的信息、编解码器和浏览器支持的选项,以及有关ICE代理收集的用于通过信令通道发送给潜在对等方的任何候选人的信息建立请求连接或更新现有连接的配置,返回值是一个Promise,在创建报价后,将使用包含新创建的报价的RTCSessionDescription对象进行解析。函数调用(){callButton.disabled=true;hangupButton.disabled=false;constaudioTracks=window.localStream.getAudioTracks();constvideoTracks=window.localStream.getVideoTracks();if(audioTracks.length>0){console.log(`使用音频设备:${audioTracks[0].label}`);}if(videoTracks.length>0){console.log(`使用视频设备:${videoTracks[0].label}`);}常量服务器=空;pc1Local=newRTCPeerConnection(服务器);pc1Remote=newRTCPeerConnection(服务器);pc1Remote.ontrack=gotRemoteStream1;pc1Local.onicecandidate=iceCallback1Local;pc1Remote.onicecandidate=iceCallback1Remote;pc2Local=newRTCPeerConnection(服务器);pc2Remote=newRTCPeerConnection(服务器);pc2Remote.ontrack=gotRemoteStream2;pc2Local.onicecandidate=iceCallback2Local;pc2Remote.onicecandidate=iceCallback2Remote;window.localStream.getTracks().forEach(track=>pc1Local.addTrack(track,window.localStream));pc1Local.createOffer(offerOptions).then(gotDescription1Local,onCreateSessionDescriptionError);window.localStream.getTracks().forEach(track=>pc2Local.addTrack(track,window.localStream));pc2Local.createOffer(offerOptions).then(gotDescription2Local,onCreateSessionDescriptionError);}其他方法RTCPeerConnection.setRemoteDescription()方法改变了连接相关的描述,主要是描述连接的一些属性,比如对端连接使用的解码器受此更改的影响,并且必须能够支持新旧描述。该方法接受三个参数,RTCSessionDescription对象用于设置,然后有一个更改成功的回调方法,一个更改失败的回调方法。RTCPeerConnection方法setLocalDescription()更改与连接关联的本地描述。此描述指定连接本地端的属性,包括媒体格式。此方法采用一个参数-会话描述-它返回一个Promise,一旦描述被更改,它会异步完成,如果在连接已经就位时调用setLocalDescription(),表明正在进行重新协商(可能是为了适应不断变化的网络条件)。由于描述是在两个对等点就配置达成一致之前交换的,因此通过调用setLocalDescription()提交的描述不会立即生效。相反,当前连接配置将保持不变,直到协商完成。只有这样,约定的配置才会生效。functiononCreateSessionDescriptionError(error){console.log(`无法创建会话描述:${error.toString()}`);}functiongotDescription1Local(desc){pc1Local.setLocalDescription(desc);pc1Remote.setRemoteDescription(desc);pc1Remote.createAnswer().then(gotDescription1Remote,onCreateSessionDescriptionError);}functiongotDescription1Remote(desc){pc1Remote.setLocalDescription(desc);console.log(`来自pc1Remote的应答\n${desc.sdp}`);pc1Local.setRemoteDescription(desc);}functiongotDescription2Local(desc){pc2Local.setLocalDescription(desc);pc2Remote.setRemoteDescription(desc);pc2Remote.createAnswer().then(gotDescription2Remote,onCreateSessionDescriptionError);}functiongotDescription2Remote(desc){pc2Remote.setLocalDescription(desc);pc2Local.setRemoteDescription(desc);}functionhangup(){console.log('结束通话');pc1Local.close();pc1Remote.close();pc2Local.close();pc2Remote.close();pc1Local=pc1Remote=null;pc2Local=pc2Remote=null;hangupButton.disabled=true;callButton.disabled=false;}functiongotRemoteStream1(e){if(video2.srcObject!==e.streams[0]){video2.srcObject=e.streams[0];console.log('pc1:收到远程流');}}functiongotRemoteStream2(e){if(video3.srcObject!==e.streams[0]){video3.srcObject=e.streams[0];}}functioniceCallback1Local(event){handleCandidate(event.candidate,pc1Remote,'pc1:','local');}functioniceCallback1Remote(event){handleCandidate(event.candidate,pc1Local,'pc1:','remote');}functioniceCallback2Local(event){handleCandidate(event.candidate,pc2Remote,'pc2:','local');}functioniceCallback2Remote(event){handleCandidate(event.candidate,pc2Local,'pc2:','remote');}functionhandleCandidate(candidate,dest,prefix,type){dest.addIceCandidate(candidate).then(onAddIceCandidateSuccess,onAddIceCandidateError);}functiononAddIceCandidateSuccess(){console.log('AddIceCandidatesuccess.');}functiononAddIceCandidateError(error){console.log(`添加ICE候选失败:${error.toString()}`);}HTML
