// 从地址栏获取参数 | |
const url=location.href; | |
let name = getParamFromUrl(url, "name"); | |
let role = getParamFromUrl(url, "role"); | |
let remote = getParamFromUrl(url, "remote"); // answer none remote | |
var localOpened = false; | |
// 信令服务器 | |
const signaling = new SignalingChannel("wss://www.zhaosonghan.com:9001", name, role, onmessage); | |
signaling.remote = remote; | |
// TURN 参数 | |
const constraints = {audio: true, video: true}; | |
const configuration = {iceServers: [{ | |
urls:'turn:43.138.235.180:9002', | |
username: name, | |
credential: 'mypwd' | |
}]}; | |
// 最重要的对象 | |
const pc = new RTCPeerConnection(configuration); | |
// 没太大用 | |
pc.getStats = (status) => { | |
console.log("getStats "); | |
} | |
// 没太大用,状态改变会触发 | |
pc.oniceconnectionstatechange = async (ev) => { | |
console.log("oniceconnectionstatechange " + pc.iceConnectionState); | |
// 如果是 answer 并且不是 local,打开自己本地的视频 | |
if (ROLE_ANSWER == role && !localOpened) { | |
localOpened = true; | |
const stream = await navigator.mediaDevices.getUserMedia(constraints); | |
stream.getTracks().forEach((track) => pc.addTrack(track, stream)); | |
// now, trigger onnegotiationneeded / onicecandidate | |
document.getElementById('local').srcObject = stream; | |
} | |
} | |
// ICE 失败会有调用 | |
pc.onicecandidateerror = (event) => { | |
console.log("onicecandidateerror"); | |
} | |
// 触发产生 candidate | |
// Send any ice candidates to the other peer. | |
pc.onicecandidate = ({candidate}) => { | |
if (null == candidate) | |
return; | |
log("onicecandidate=" + JSON.stringify(candidate)); | |
signaling.sendToPeer(signaling.remote, {id:ID_WEBRTC_CANDI, candi:candidate}); | |
} | |
// getUserMedia 调用后,会触发 | |
// Let the "negotiationneeded" event trigger offer generation. | |
// offer trigger | |
pc.onnegotiationneeded = async () => { | |
try { | |
log("onnegotiationneeded"); | |
await pc.setLocalDescription(await pc.createOffer()); | |
// Send the offer to the other peer. | |
signaling.sendToPeer(signaling.remote, {id:ID_WEBRTC_DESC, desc: pc.localDescription}); | |
} catch (err) { | |
console.error(err); | |
} | |
}; | |
// Once remote track media arrives, show it in remote video element. | |
pc.ontrack = (event) => { | |
// Don't set srcObject again if it is already set. | |
let remoteView = document.getElementById('remote'); | |
if (remoteView.srcObject) return; | |
remoteView.srcObject = event.streams[0]; | |
}; | |
// 最开始的地方 | |
// Call start() to initiate. | |
async function start() { | |
try { | |
// Get local stream, show it in 'local' | |
const stream = | |
await navigator.mediaDevices.getUserMedia(constraints); | |
stream.getTracks().forEach((track) => pc.addTrack(track, stream)); | |
// now, trigger onnegotiationneeded / onicecandidate | |
document.getElementById('local').srcObject = stream; | |
} catch (err) { | |
log("start error=" + err); | |
} | |
} | |
// 信令服务器交互 | |
function onmessage(data) { | |
//console.log("onmsg=" + data); | |
let jsonRoot = eval('(' + data + ')'); | |
let cmd = parseInt(jsonRoot.head.cmd); | |
//console.log("onmessage cmd=" + cmd); | |
switch (cmd) { | |
case CMD_REQ_SEND_PEER: | |
handleCmdFromPeer(data); | |
break; | |
case CMD_RSP_SEND_PEER: | |
handleSendPeerResp(data); | |
break; | |
case CMD_RSP_LOGIN: | |
handleLogin(data); | |
break; | |
default: | |
break; | |
} | |
} | |
function handleLogin(data) { | |
let jsonRoot = eval('(' + data + ')'); | |
log("login rst=" + jsonRoot.data.ret); | |
} | |
function handleSendPeerResp(data) { | |
let jsonRoot = eval('(' + data + ')'); | |
log("handleSendPeerResp=" + jsonRoot.data.ret); | |
} | |
function handleCmdFromPeer(data) { | |
let jsonRoot = eval('(' + data + ')'); | |
signaling.remote = jsonRoot.head.name; | |
log("cmd from peer " + signaling.remote); | |
let cmdid = parseInt(jsonRoot.data.msg.id); | |
switch (cmdid) { | |
case ID_WEBRTC_DESC: | |
setRemoteDesc(jsonRoot.data.msg.desc); | |
break; | |
case ID_WEBRTC_CANDI: | |
setCandidate(jsonRoot.data.msg.candi); | |
break; | |
default: | |
break; | |
} | |
} | |
// 添加 candidate | |
async function setCandidate(candi) { | |
log("setCandidate " + JSON.stringify(candi)); | |
await pc.addIceCandidate(candi); | |
} | |
// 设置远端desc | |
async function setRemoteDesc(json) { | |
if ('offer' == json.type) { | |
await pc.setRemoteDescription(json); | |
const stream = await navigator.mediaDevices.getUserMedia(constraints); | |
stream.getTracks().forEach((track) => pc.addTrack(track, stream)); | |
await pc.setLocalDescription(await pc.createAnswer()); | |
log("offer setremotedesc local desc=" + pc.localDescription); | |
signaling.sendToPeer(signaling.remote, {id:ID_WEBRTC_DESC, desc: pc.localDescription}); | |
} else if ('answer' == json.type) { | |
await pc.setRemoteDescription(json); | |
log("answer setremotedesc local desc=" + pc.localDescription); | |
} else { | |
log('Unsupported SDP type.'); | |
} | |
} | |
function getParamFromUrl(url, param) | |
{ | |
let vars = url.split("&"); | |
for (let i = 0; i < vars.length; i++) | |
{ | |
let pair = vars[i].split("="); | |
if (param == pair[0]) | |
return pair[1]; | |
} | |
return ""; | |
} |
以上是 html5 使用 webrtc 的核心代码,其实代码并不复杂,可以运行示例例观察调用流程;但是自己需要实现一个信令服务器。
可以使用如下链接直接看效果
- pc端
- offer https://www.zhaosonghan.com/h5/webrtc_pc.html?&name=123456&remote=abcdef&role=1
- answer https://www.zhaosonghan.com/h5/webrtc_pc.html?&name=abcdef&role=2
- 移动端 (手机可扫二维码访问,方便一些)
- offer https://www.zhaosonghan.com/h5/webrtc_mobile.html?&name=123456&remote=abcdef&role=1
- answer https://www.zhaosonghan.com/h5/webrtc_mobile.html?&name=abcdef&role=2
以上示例来自 https://www.zhaosonghan.com/archives/1704808721108,感谢作者~