网页版WebRTC多人聊天Demo本文基于Codelab中step7,在其基础上作简单修改,使其支持多人视频通讯,本文暂时只支持星状结构三人聊天,多人聊天可以在基础上扩展,原理相同。一.源码分析该工程包括三个文件:server.js,main.js,index.html。1.server.jsif(numClients == 0){socket.join(room);socket.emit('created', room);}elseif(numClients == 1) {io.sockets.in(room).emit('join', room);socket.join(room);socket.emit('joined', room);}else{//max two clientssocket.emit('full', room);}socket.emit('emit(): client ' + socket.id + ' joined room ' +room);socket.broadcast.emit('broadcast(): client ' + socket.id + ' joined room ' + room);后台服务代码,负责异步消息通讯。当有新用户加入房间时,向客户端发送消息,客户端接收到消息后作相应的处理。2.index.html网站主页,包括两块视频区域和文本区域。
WebRTC clientSend3.main.js核心代码区域,包括房间的创建,RTCPeerConnection创建和两点间的视频通话。3.1消息处理socket.on('created',function(room){console.log('Created room ' +room);isInitiator=true;});socket.on('full',function(room){console.log('Room ' + room + ' is full');});socket.on('join',function(room){console.log('Another peer made a request to join room ' +room);console.log('This peer is the initiator of room ' + room + '!');isChannelReady=true;});socket.on('joined',function(room){console.log('This peer has joined room ' +room);isChannelReady=true;});socket.on('message',function(message){console.log('Received message:', message);if(message === 'got user media') {maybeStart();}elseif(message.type === 'offer') {if(!isInitiator && !isStarted) {maybeStart();}pc.setRemoteDescription(newRTCSessionDescription(message));doAnswer();}elseif(message.type === 'answer' &&isStarted;) {pc.setRemoteDescription(newRTCSessionDescription(message));}elseif(message.type === 'candidate' &&isStarted;) {varcandidate =newRTCIceCandidate({sdpMLineIndex:message.label,candidate:message.candidate});pc.addIceCandidate(candidate);}elseif(message === 'bye' &&isStarted;) {handleRemoteHangup();}});3.2peerconnection创建和通讯functioncreatePeerConnection() {try{pc=newRTCPeerConnection(pc_config, pc_constraints);pc.onicecandidate=handleIceCandidate;console.log('Created RTCPeerConnnection with:\n' +' config: \'' + JSON.stringify(pc_config) + '\';\n' +' constraints: \'' + JSON.stringify(pc_constraints) + '\'.');}catch(e) {console.log('Failed to create PeerConnection, exception: ' +e.message);alert('Cannot create RTCPeerConnection object.');return;}pc.onaddstream=handleRemoteStreamAdded;pc.onremovestream=handleRemoteStreamRemoved;if(isInitiator) {try{//Reliable Data Channels not yet supported in ChromesendChannel = pc.createDataChannel("sendDataChannel",{reliable:false});sendChannel.onmessage=handleMessage;trace('Created send data channel');}catch(e) {alert('Failed to create data channel. ' +'You need Chrome M25 or later with RtpDataChannel enabled');trace('createDataChannel() failed with exception: ' +e.message);}sendChannel.onopen=handleSendChannelStateChange;sendChannel.onclose=handleSendChannelStateChange;console.log('....................this is a initiator = true....................');}else{pc.ondatachannel=gotReceiveChannel;console.log('....................this is not a initiator = false....................');}}3.3视频源的输出展现functionhandleRemoteStreamAdded(event) {console.log('Remote stream added.');//reattachMediaStream(miniVideo, localVideo);attachMediaStream(remoteVideo, event.stream);remoteStream=event.stream;//waitForRemoteVideo();}二.简单工作流程介绍与修改思路1.工作过程如下:1.1.浏览器A访问主页,允许访问摄像头音频设备,server接收到'create or join'消息,计算此时连接到服务器的客户端数量,此时数量为0,则向客户端发送'created'消息。1.2.浏览器A接收到'created'消息,将isInitiator设为true,该值为true表示该客户断是peerconnection的发起者。1.3.浏览器B访问主页,允许访问摄像头音频设备,server接收到'create or join'消息,计算此时连接到服务器的客户端数量,此时数量为1,则向客户端发送join和joined消息。1.4.浏览器A和浏览器B都接收到join和joined消息,设置isChannelReady=true,表示此时准备好建立连接。浏览器A发起peerconnection连接doCall,浏览器B回应peerconnection连接doAnswer,A和B建立P2P连接。1.5.A和B分别将来自本地和远端的视频stream显示在页面上。注意:浏览器A和浏览器B都接受来自server相同的消息,而两者在接收到相同的消息后的处理却不一样(main.js代码是一样的),一个是发起者,一个是应答者。可以使用状态机来理解,程序所处状态不一样,虽然接收到相同的命令,但可以做出不同的处理(通过isInitiator变量区分不同的状态)。2.三人聊天室的实现简单起见,我们暂时先实现三人视频通讯,使用星状结构。下面是修改思路:a.A和B以及建立连接,此时如C加入,可以将A和C建立连接,同时保持A和B之前的连接。此时,A能看到B和C,而B和C只能看到A。b.如果A B C三者需要互相看到,则需要A将B的视频传给C,并将C的视频传给B。本文暂时只实现A与B通讯,A与C通讯,BC之间不能通讯。下面是具体的代码修改步骤:2.1server.jsif(numClients == 0){socket.join(room);socket.emit('created', room);}elseif(numClients <=2 ) {//第三个用户加入后仍然发送join joined消息io.sockets.in(room).emit('join', room);socket.join(room);socket.emit('joined', room);}else{//max two clientssocket.emit('full', room);}2.2index.html可以采用动态方式添加,这里简单起见直接增加一路视频实现块。
//本地视频 A// remote视频B//remote视频c2.3 main.jsa.增加一个全局变量isPeerEstablished用来表示该客户端是否已经创建了PeerConnection。isPeerEstablished和isInitiator两者可以区分发起者和应答者,因为具有超过2个客户端,所以必须使用isPeerEstablished来选择尚未创建连接的客户端作为应答者。var isPeerEstablished=false;b.处理message机制修改在判断条件里面加入(!isPeerEstablished||isInitiator),表示尚未创建链接C和发起者A才会执行peerconnection。保证新加入者C和A创建链接,同时保持A和B的连接。socket.on('message',function(message){console.log('Received message:', message);if(message === 'got user media'&&(!isPeerEstablished||isInitiator)) {maybeStart();}elseif(message.type === 'offer'&&(!isPeerEstablished||isInitiator)) {if(!isInitiator && !isStarted) {maybeStart();}pc.setRemoteDescription(newRTCSessionDescription(message));doAnswer();}elseif(message.type === 'answer' && isStarted&&(!isPeerEstablished||isInitiator)) {pc.setRemoteDescription(newRTCSessionDescription(message));}elseif(message.type === 'candidate' && isStarted&&(!isPeerEstablished||isInitiator)) {varcandidate =newRTCIceCandidate({sdpMLineIndex:message.label,candidate:message.candidate});pc.addIceCandidate(candidate);}elseif(message === 'bye' &&isStarted;) {handleRemoteHangup();}});c.视频流展现如果isInitiator和isPeerEstablished都为true,说明此时A和B已经建立链接。此时,应该将新的视频流显示在remoteVideo2中。其他情况将视频流展示在remoteVideo中。functionhandleRemoteStreamAdded(event) {console.log('Remote stream added.');//reattachMediaStream(miniVideo, localVideo);if(isInitiator&&isPeerEstablished;){attachMediaStream(remoteVideo2, event.stream);remoteStream2=event.stream;}else{attachMediaStream(remoteVideo, event.stream);remoteStream=event.stream;}isPeerEstablished=true;//waitForRemoteVideo();}d.其他两处修改varremoteVideo2 = document.querySelector('#remoteVideo2');......functionhandleRemoteHangup() {console.log('Session terminated.');stop();//isInitiator = false; //总是保持A的发起者角色}三人聊天效果图:本文链接:网页版WebRTC多人聊天Demo,转载请注明。