Older/WebApplication/js/WebRTCClient.js
amass e3580ec8f4
All checks were successful
Deploy / Build (push) Successful in 5m54s
add ws ping/pong.
2025-01-15 15:24:47 +08:00

175 lines
5.9 KiB
JavaScript

WT_DECLARE_WT_MEMBER(1, JavaScriptConstructor, "WebRTCClient", function (WT, client, offerId, offerBtn, sendMsg, sendBtn, textBrowser, localId, url) {
this.peerConnectionMap = {};
this.dataChannelMap = {};
this.ws = null;
this.config = {
iceServers: [
{
urls: "stun:amass.fun:5439"
},
{
urls: "turns:amass.fun",
username: 'amass',
credential: '88888888'
},
],
};
this.log = (text) => {
textBrowser.value = `${textBrowser.value}\n${text}`;
};
function sendLocalDescription(ws, id, pc, type) {
(type == 'offer' ? pc.createOffer() : pc.createAnswer())
.then((desc) => pc.setLocalDescription(desc))
.then(() => {
const { sdp, type } = pc.localDescription;
ws.send(JSON.stringify({
id,
type,
description: sdp,
}));
});
};
function sendLocalCandidate(ws, id, cand) {
const { candidate, sdpMid } = cand;
ws.send(JSON.stringify({
id,
type: 'candidate',
candidate,
mid: sdpMid,
}));
};
this.setupDataChannel = (dc, id) => {
dc.onopen = () => {
console.log(`DataChannel from ${id} open`);
sendMsg.disabled = false;
sendBtn.disabled = false;
sendBtn.onclick = () => dc.send(sendMsg.value);
};
dc.onclose = () => { console.log(`DataChannel from ${id} closed`); };
dc.onmessage = (e) => {
if (typeof (e.data) != 'string')
return;
console.log(`Message from ${id} received: ${e.data}`);
this.log(`${id}: ${e.data}`);
};
this.dataChannelMap[id] = dc;
return dc;
};
this.createPeerConnection = (ws, id) => {
const pc = new RTCPeerConnection(this.config);
pc.oniceconnectionstatechange = () => console.log(`Connection state: ${pc.iceConnectionState}`);
pc.onicegatheringstatechange = () => console.log(`Gathering state: ${pc.iceGatheringState}`);
pc.onicecandidate = (e) => {
if (e.candidate && e.candidate.candidate) {
sendLocalCandidate(ws, id, e.candidate);
}
};
pc.ondatachannel = (e) => {
const dc = e.channel;
console.log(`DataChannel from ${id} received with label "${dc.label}"`);
this.setupDataChannel(dc, id);
dc.send(`Hello from ${localId}`);
sendMsg.disabled = false;
sendBtn.disabled = false;
sendBtn.onclick = () => dc.send(sendMsg.value);
};
this.peerConnectionMap[id] = pc;
return pc;
};
this.offerPeerConnection = function (ws, id) {
console.log(`Offering to ${id}`);
pc = this.createPeerConnection(ws, id);
const label = "test";
console.log(`Creating DataChannel with label "${label}"`);
const dc = pc.createDataChannel(label);
this.setupDataChannel(dc, id);
sendLocalDescription(ws, id, pc, 'offer');
};
this.openSignaling = function (url) {
return new Promise((resolve, reject) => {
const ws = new WebSocket(url);
ws.onopen = () => resolve(ws);
ws.onerror = () => {
if (this.pingTimer) {
clearInterval(this.pingTimer);
this.pingTimer = 0;
}
reject(new Error('WebSocket error'));
};
ws.onclose = () => {
if (this.pingTimer) {
clearInterval(this.pingTimer);
this.pingTimer = 0;
}
console.error('WebSocket disconnected');
};
ws.onmessage = (e) => {
if (typeof (e.data) != 'string') return;
const message = JSON.parse(e.data);
if (message.type == undefined || message.type != "pong") {
console.log(message);
}
const { id, type } = message;
let pc = this.peerConnectionMap[id];
if (!pc) {
if (type != 'offer')
return;
console.log(`Answering to ${id}`);
pc = this.createPeerConnection(ws, id);
}
switch (type) {
case 'offer':
case 'answer':
pc.setRemoteDescription({
sdp: message.description,
type: message.type,
}).then(() => {
if (type == 'offer') {
sendLocalDescription(ws, id, pc, 'answer');
}
});
break;
case 'candidate':
pc.addIceCandidate({
candidate: message.candidate,
sdpMid: message.mid,
});
break;
}
}
});
};
client.wtWebRTCClient = this;
this.openSignaling(`${url}/${localId}`).then(ws => {
this.log('WebSocket connected, signaling ready');
offerId.disabled = false;
offerBtn.disabled = false;
offerBtn.onclick = () => {
this.offerPeerConnection(ws, offerId.value);
};
this.ws = ws;
this.pingTimer = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
type: "ping",
}));
} else {
clearInterval(this.pingTimer);
this.pingTimer = 0;
}
}, 3000);
});
});