WebRTC实现多人视频聊天之客户端设计

写在前面

在开始之前,需要对如何建立点对点连接有一个了解,参考我的另一篇博文:WebRTC之点对点连接。下图是来自参考博文中的一张图片。

如何建立点对点连接


基础想法

基础架构如下:

多人视频聊天交互设计

客户端主要在于处理从WebSockets接收到的消息,并且有着发起连接和被动接受连接的功能。


实现
  1. 连接建立成功时,注册用户身份,表明想加入的房间号。

    ws.onopen = function () {
            console.log("connect success ... ");
            // 初始化当前用户身份
            let urlParams = urlSearch();
            user = {
                name: urlParams['name'],
                roomId: urlParams['roomId']
            };
            console.log("current user...", user);
            ws.send(JSON.stringify({
                msgType: requestType.USER_IDENTITY,
                msgBody: user
            }));
        };
    
  2. 通过web sockets 发起创建多个连接的请求。

    // call 按钮的点击事件中执行
    ws.send(JSON.stringify({
            msgType: requestType.CLIENT_MULTIPLY_CONNECTION_CREATEQUE,
            msgBody: user
        }));
    
  3. 接收服务器响应的房间成员信息,并创建多个用户连接(封装一个具有username和连接属性的对象),发送多个用户offer(username和offer)给信令服务器

    // web socket 接收到该消息
    ws.onmessage = function (evt) {
            // 接收到成员消息响应,创建多个连接
            if(result.msgType === requestType.SERVER_CALLEESRESP){
                console.log("接收到创建多个连接消息响应" + result.msgBody);
                createConnections(result.msgBody).catch((error)=>{
                    console.log("创建成员连接出错..." , result.msgBody,error);
                });
            }
        };
    
    // 创建多个点对点连接和video元素
    async function createConnections(userNames) {
        const tempPcs = [];
        userNames.forEach(function (username){
            tempPcs.push({
                userName: username,
                pc: createRTCPeerConnection(username,createRemoteVideo(username))
            });
        });
        // 发送多个用户的username和offer
        const offers = [];
        for (let i = 0; i < tempPcs.length; i++) {
            const pcc = tempPcs[i];
            const offer = await pcc.pc.createOffer(offerOptions);
            pcc.pc.setLocalDescription(offer).catch(()=>{
                console.log(pcc.userName + ">>> setLocalDescription error" , offer);
            });
            console.log("offer 创建完成...",offer);
            offers.push({
                userName: pcc.userName,
                sdp: offer
            });
        }
        if(offers.length !== 0){
            console.log("发送 多个offer消息...",offers);
            ws.send(JSON.stringify({
                msgType: requestType.SDP_OFFER,
                msgBody: offers
            }));
        }
    }
    
    // 创建点对点连接
    function createRTCPeerConnection(username,remoteVideo){
        const configuration = getSelectedSdpSemantics();
        console.log('RTCPeerConnection configuration:', configuration);
        const pc = new RTCPeerConnection(configuration);
        console.log(username+'>>> Created peer connection object pc');
        pc.addEventListener('icecandidate', e => onIceCandidate(e,username));
        pc.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc, e));
        pc.addEventListener('track', e => {
            if (remoteVideo.srcObject !== e.streams[0]) {
                remoteVideo.srcObject = e.streams[0];
                console.log(username+'>>> pc received remote stream');
            }
        });
        localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
        pcs.push({
            userName: username,
            pc: pc
        });
        return pc;
    }
    // 
    function createRemoteVideo(id) {
        let remoteVideo = document.createElement('video');
        remoteVideo.playsinline = true;
        remoteVideo.autoplay = true;
        remoteVideo.id=id;
        //获取body节点
        localVideo.parentNode.insertBefore(remoteVideo, localVideo.nextSibling);
        return remoteVideo;
    }
    
    
    
  4. 客户端接收到offer消息时,创建连接,响应answer;并发起创建多个连接的请求消息,收到信令服务器响应的成员信息,创建多个连接,发送多个用户offer给信令服务器,并将之前创建的用户连接放入连接池中。

    // 接收到offer消息时
            if(result.msgType === requestType.SDP_OFFER){
                console.log("接收到offer消息" , result.msgBody);
                // 创建
                const rtcPeerConnection = createRTCPeerConnection(result.msgBody.userName, createRemoteVideo(result.msgBody.userName));
                rtcPeerConnection.setRemoteDescription(JSON.parse(msgBody.sdp)).catch((error)=>{
                    console.log(result.msgBody.userName + ">>> setRemoteDesc error" , msgBody.sdp,error);
                });
                // 应答
                rtcPeerConnection.createAnswer().catch((error)=>{
                    console.log(result.msgBody.userName + ">>> createAnswer error" , error);
                }).then(answer=>{
                    rtcPeerConnection.setLocalDescription(answer).catch(()=>{
                        console.log(result.msgBody.userName + ">>> setLocalDesc error" , answer);
                    });
                    // 响应answer
                    ws.send(JSON.stringify({
                        msgType: requestType.SDP_ANSWER,
                        msgBody: {
                            userName:msgBody.userName,
                            sdp: answer
                        }
                    }));
                });
                // 发送创建多个请求消息
                console.log("发送创建多个请求消息...");
                ws.send(JSON.stringify({
                    msgType: requestType.CLIENT_MULTIPLY_CONNECTION_CREATEQUE,
                    msgBody: user
                }));
            }
    
  5. 客户端收到单个answer消息时,放入对应用户连接的desc

    // 接收到answer消息时
            if(result.msgType === requestType.SDP_ANSWER){
                console.log("接收到answer消息...");
                pcs.forEach(function (pcc) {
                   if(pcc.userName === msgBody.userName){
                       pcc.pc.setRemoteDescription(JSON.parse(msgBody.sdp)).catch(()=>{
                           console.log(msgBody.userName + ">>> setRemoteDesc error" + msgBody.sdp);
                       });
                   }
                });
            }
    
  6. 客户端设置发送candidate(包含用户信息和candidate)

    async function onIceCandidate(event,username) {
        try {
            console.log(username+">>> event.candidate..." + event.candidate);
            ws.send(JSON.stringify(
                {
                    msgType: requestType.SDP_CANDIDATE,
                    msgBody: {
                        userName: username,
                        sdp: event.candidate
                    }
                }
            ));
            console.log(username+">>> send candidate msg success");
        } catch (e) {
            onAddIceCandidateError(pc, e);
        }
    }
    
  7. 客户端收到单个candidate消息时,添加到对应用户连接的IceCandidate中

    // 接收到candidate消息时
            if(result.msgType === requestType.SDP_CANDIDATE){
                console.log("接收到candidate消息...");
                pcs.forEach(function (pcc) {
                    console.log(pcc.userName + "----" + msgBody.userName);
                    if(pcc.userName === msgBody.userName){
                        pcc.pc.addIceCandidate(JSON.parse(msgBody.sdp)).catch((error)=>{
                            console.log(msgBody.userName + ">>> addIceCandidate error" , msgBody.sdp,error);
                        });
                        console.log(`${pcc.pc} addIceCandidate success`);
                    }
                });
            }
    

推荐博文



参考博文


https://blog.youkuaiyun.com/heqinghua217/article/details/52174541 webRtc+websocket多人视频通话

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值