二十五、WebRTC客户端的实现

let localVideo = document.querySelector('video#localvideo')
let remoteVideo = document.querySelector('video#remotevideo')

let btnConn = document.querySelector('button#connserver')
let btnLeave = document.querySelector('button#leave')

let localStream
let socket
let pc
let state = 'init'
let roomid = '111111'

let sendMessage = (roomid, data) => {
    console.log(`send p2p message:roomid-${roomid};data-${data}`)

    if(socket) {
        socket.emit('message', roomid, data)
    }
}

let call = () => {
    // 创建offer 发送给对端
    if(state === 'joined_conn') {
        if(pc) {
            options = {
                offerToReceiveAudio: 1,
                offerToReceiveVideo: 1,
            }
            pc.createOffer(options)
            .then((desc) => {
                pc.setLocalDescription(desc)
                sendMessage(roomid, desc)
            })
            .catch((err) => {
                console.log(`Failed to get Offer!${err}`)
            })
        }
    }
}

let conn = () => {
    // 连接信令服务器
    socket = io.connect()

    socket.on('joined', (roomid, id) => {
        console.log(`receive joined message: roomid=${roomid};id=${id}`)

        state = 'joined'

        creatPeerConnection()

        btnConn.disabled = true
        btnLeave.disabled = false
        
        console.log(`receive joined message: state=${state}`)

    })

    socket.on('otherjoin', (roomid, id) => {
        console.log(`receive  message: roomid-${roomid};id-${id}`)

        if(state === 'joined_unbind') {
            creatPeerConnection()
        }

        state = 'joined_coon'

        // 开 始 媒 体 协 商

        console.log(`receive otherjoin message: state=${state}`)
    })

    socket.on('full', (roomid, id) => {
        console.log(`receive full message: roomid-${roomid};id-${id}`)

        state = 'leaved'

        console.log(`receive full message: state=${state}`)

        socket.disconnect()

        alert(`the room is full!`)

        btnConn.disabled = false
        btnLeave.disabled = true
    })

    socket.on('leaved', (roomid, id) => {
        console.log(`receive leaved message: roomid-${roomid};id-${id}`)

        state = 'leaved'

        console.log(`receive leaved message: state=${state}`)

        socket.disconnect()

        btnConn.disabled = false
        btnLeave.disabled = true
    })

    socket.on('bye', (roomid, id) => {
        console.log(`receive bye message: roomid-${roomid};id-${id}`)

        state = 'joined_unbind'

        closePeerConnection()

        console.log(`receive bye message: state=${state}`)

    })

    socket.on('message', (roomid, data) => {
        console.log(`receive client message: roomid-${roomid};id-${data}`)

        // 媒 体 协 商
        // 首选判断传过来的数据是否正确
        if(data) {
            if(data.type === 'offer') {
                pc.setRemoteDescription(new RTCSessionDescription(desc))
                pc.createAnswer()
                .then((desc) => {
                    pc.setLocalDescription(desc)
                    sendMessage(roomid, desc)
                })
                .catch((err) => {
                    console.log(`Failed to get answer!  ${err}`)
                })
            }else if(data.type === 'answer') {
                pc.setRemoteDescription(new RTCSessionDescription(desc))
            }else if(data.type === 'candidate') {
                let candidate = new RTCIceCandidate({
                    sdpMLineIndex: data.label,
                    candidate: data.candidate
                })
                pc.addIceCandidate(candidate)
            }else {
                console.log(`the message is invalid! ${data}`)
            }
        }
    })

    // 加入123456789房间
    socket.emit('join', '123456789')

    return
}

btnConn.onclick = () => {
    // 开启本地视频
    if(!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
        console.log('the getUserMedia is not supported!')
        return
    }else {
        let constraints = {
            video: true,
            audio: true
        }
        navigator.mediaDevices.getUserMedia(constraints)
        .then((stream) => {
            localVideo.srcObject = stream
            localStream = stream

            // 获取音视频数据后,连接soket.io,并接收服务端消息
            conn()
        })
        .catch((err) => {
            console.log(`Failed to get Media Stream! - ${err}`)
        })
    }

    return true
}

btnLeave.onclick = () => {
    if(socket) {
        socket.emit('leave', '123456789')
    }

    //  释放资源

    closePeerConnection()
    closeLocalMedia()

    btnConn.disabled = false
    btnLeave.disabled = true
}

// 创建连接
var creatPeerConnection = () => {
    console.log(`creat RTCPeerConnection!`)
    // 创建peercoonection 监听candidate事件
    if(!pc) {
        let pcConfig = {
            'iceServers': [{
                'urls': 'turn:stun.al.learningrtc.cn:3478',
                'credential': 'mypasswd',
                'username': 'garrylea'
            }]
        }
        pc = new RTCPeerConnection(pcConfig)

        pc.onicecandidate = (e) => {
            if(e.candidate) {
                //端对端传输

                console.log(`find an new candidate:${e.candidate}`)

                sendMessage(roomid, {
                    type: 'candidate',
                    label: e.candidate.sdpMLineIndex,
                    id: e.candidate.sdpMid,
                    candidate: e.candidate.candidate
                })
            }
        }

        pc.ontrack = (e) => {
            remoteVideo.srcObject = e.streams[0]
        }
    }

    if(localStream) {
        localStream.getTracks().forEach(track => {
            pc.addTrack(track)
        });
    }
}

// 关闭连接
var closePeerConnection = () => {
    console.log(`close RTCPeerConnection!`)
    if(pc) {
        pc.close()
        pc = null
    }
}

// 关闭媒体设备
var closeLocalMedia = () => {
    if(localStream && localStream.getTracks()) {
        localStream.getTracks().forEach(track => {
            track.stop()
        })
    }
    localStream = null
}

注意要点

  • 网络连接要在音视频数据获取之后,否则有可能绑定音视频流失败
  • 当一端退出房间后,另一端的PeerConnection要关闭重建,否则与新用户互通时媒体协商会失败
  • 异步时间处理

你可能感兴趣的:(二十五、WebRTC客户端的实现)