// 从地址栏获取参数
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,感谢作者~