webRTC第二章:实现视频通话

193 阅读4分钟

视频通话流程

通话发起方点击接收方的名字向对方发起视频通话,通话接收方可以选择接听或者拒绝接听。如果接听,双方都会看到对方的视频,如果拒绝接听,发起方会接收到拒绝提示。

发起通话

step1 . 前端

通过socket.emit发送视频发起方和接收方的信息

let socket 
export const connectWithSocket = ()=>{ 
    socket = socketClient(SERVER) 
    ...
}

socket.emit("pre-offer", {
    //呼叫方和应答方的信息
})

step2 . 后端

接收信息:通过socket.on监听视频发起方发送的信息

发送信息:通过io.to()指定视频接收方,通过.emit发送视频发起方的信息

io.on("connection", socket=>{ 
    socket.emit("connection", null) 
    ...
    // 监听客户端发送过来的预呼叫并获取data,传递给应答方
    socket.on("pre-offer", data=>{
        // 向应答方发送data数据
        io.to(data.callee.socketId).emit("pre-offer", {
            //呼叫方的信息
        })
    })
})

step3 . 前端

通过socket.on接收信息,并在客户端展示呼叫弹出框

    let socket 
    export const connectWithSocket = ()=>{ 
        socket = socketClient(SERVER) 
        socket.on("pre-offer", data=>{
            //进行想要的操作
        })
        ...
    }

回复通话请求

步骤同上

step1 : 前端通过socket.emit发送接收方的回复信息和发送者信息

step2 : 后端通过socket.on监听前端发送的信息,通过io.to()指定视频发起方,通过.emit发送接收方的回复信息

step3 : 前端通过socket.on接收信息,就可以获取应答方的回复信息了。然后就可以根据应答方是否拒绝接听来进行下一步操作

应答方接听视频通话

前期准备

在页面加载时就创建对等连接,即下面的操作

    let peerConnection;
    const configuration = {
        iceServers: [{
            urls: "stun:stun.l.google.com:19302"
        }]
    }
    // 创建对等连接
    const createPeerConnection = ()=>{
        peerConnection = new RTCPeerConnection(configuration)
        const localStream = store.getState().localStream;
        // addTrack为初始化后的本地流对象添加音视频轨道,若该本地流已经被发布,则该流会自动重新发布到远端
        for(const track of localStream.getTracks()){
            peerConnection.addTrack(track, localStream)
        }
        // 每当远端的音视频数据传递过来,onTrack事件就会被触发
        peerConnection.ontrack = ({streams:[stream]})=>{
            //stream即为远端数据流
        }
    }

操作(SDP交换)

step1. 发起方得知应答方同意视频通话后,通过信令服务器给应答方发送SDP

    const offer = await peerConnection.createOffer();
    await peerConnection.setLocalDescription(offer)
    // offer发送SDP到信令服务器
    socket.emit("webRTC-offer",{
        calleeSocketId: connectUserSocketId,
        offer: offer
    })

step2. 应答方获取SDP后,通过信令服务器给发起方发送SDP answer

socket.on("webRTC-offer", data=>{
    await peerConnection.setRemoteDescription(data.offer)
    // 创建answer SDP
    const answer = await peerConnection.createAnswer()
    await peerConnection.setLocalDescription(answer)

    socket.emit("webRTC-answer",{
        callerSocketId: connectUserSocketId,
        answer: answer
    })
})

step3. 发起方获取到SDP answer后

socket.on("webRTC-answer", data=>{
    await peerConnection.setRemoteDescription(data.answer)
})

获取视频

该项目中的音视频分为两种,分别是本地音视频流和远端发送的音视频流。

所以,接下来我们一个一个实现。

获取本地视频流

前端

我们可以通过navigator.mediaDevices.getUserMedia(defaultConstrains)来获取本地视频流,但其返回的结果是promise对象,所以我们需要用then和catch来接收结果。

如果浏览器允许打开摄像头和麦克风,那么promise会返回成功的结果,结果值就是音视频流;如果不允许,promise会返回失败的结果

    const defaultConstrains = {
        video: {
            width: 480,
            height: 360
        },
        audio: true
    }
    navigator.mediaDevices.getUserMedia(defaultConstrains)
        .then(stream=>{
           //stream为本地视频流
        }).catch(error=>{
            console.log("获取本地媒体流访问权限失败", error);
        })

获取远端视频流

在前面创建对等连接时,我们已经可以通过peerConnection.ontrack来获取远端视频流

peerConnection.ontrack = ({streams:[stream]})=>{ 
    //stream即为远端数据流 
}

展示视频

展示视频我们可以通过 < v i d e o / >实现

useEffect(()=>{
    if(stream){
        const Video = videoRef.current;
        Video.srcObject = stream;
        Video.onloadedmetadata = ()=>{
            Video.play()
        }
    }
},[stream])

return <div>
    <video ref={videoRef} autoPlay/>
</div>

结束通话

结束通话需通过服务器告知另一方,并且重置状态

step1: 前端

// 通过服务器向另一方告知想要挂断
socket.emit("user-hanged-up", {
    //另一方的用户id
})
// 挂断之后进行状态的重置
//关闭并清空peerConnection之后重新连接
peerConnection.close()
peerConnection = null
createPeerConnection()
//如果桌面共享处于开启状态,那么结束桌面共享
if(
    //桌面共享开启状态
){
    screenSharingStream.getTracks().forEach(track=> track.stop())
}

step2: 后端

    // 监听挂断的通知
    socket.on("user-hanged-up", data=>{
        io.to(前端传过来的另一方的用户id).emit("user-hanged-up")
    })

step3: 前端

这一部分的操作是在进行状态的重置,与step1中的状态重置是相同的

// 监听服务器挂断
    socket.on("user-hanged-up", ()=>{
        // 监听到挂断之后进行状态的重置
        //关闭并清空peerConnection之后重新连接
        peerConnection.close()
        peerConnection = null
        createPeerConnection()
        //如果桌面共享处于开启状态,那么结束桌面共享
        if(
            //桌面共享开启状态
        ){
            screenSharingStream.getTracks().forEach(track=> track.stop())
        }
    })