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要关闭重建,否则与新用户互通时媒体协商会失败
- 异步时间处理