Commit 4f096f2c authored by Daniel Sucno's avatar Daniel Sucno

feat: add basic worker

-registration functionality

-call functionality (this needs some revision because there is no audio)
parent 1aefa8dd
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Registro SIP con Janus</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
#logs {
margin-top: 20px;
padding: 10px;
border: 1px solid #ddd;
background-color: #f9f9f9;
height: 200px;
overflow-y: auto;
}
button {
padding: 10px 20px;
font-size: 16px;
}
</style>
</head>
<body>
<audio id="audioSignOut" src="./sign-out.mp3" preload="auto"></audio>
<audio src="./silence.wav" loop style="display: none;"></audio>
<h1>Registro SIP con Janus</h1>
<div>
<label for="extension">Extensión SIP:</label>
<input type="text" id="extension" placeholder="extension" />
</div>
<div>
<label for="password">Clave SIP:</label>
<input type="password" id="password" placeholder="Clave SIP" />
</div>
<div>
<label for="callTo">Llamar a:</label>
<input type="text" id="callTo" placeholder="Llamar">
</div>
<button id="registerBtn">Registrar Extensión</button>
<button id="callToBtn">Llamar</button>
<button id="hangup">Cortar</button>
<div id="logs"></div>
<button id="playSoundBtn">play</button>
<video id="remoteAudio" autoplay playsinline loop style="display:none"></video>
<script>
const worker = new Worker('janusWorker.js');
const logs = document.getElementById("logs");
let peerConnection;
let localStream;
let remoteStream;
worker.onmessage = (e) => {
const { type, message, destination, jsep } = e.data;
if (type === "log") {
logs.innerHTML += `<p>${message}</p>`;
logs.scrollTop = logs.scrollHeight;
} else if (type === "playAudio") {
document.getElementById('audioSignOut').play();
} else if (type === "registered") {
alert("Extensión SIP registrada con éxito.");
} else if (type === "callStarted") {
logs.innerHTML += `<p>Llamada iniciada.</p>`;
} else if (type === "callEnded") {
logs.innerHTML += `<p>Llamada finalizada.</p>`;
if (peerConnection) {
peerConnection.close();
}
}else if(type === "accepted"){
logs.innerHTML += `<p>Llamada aceptada.</p>`;
peerConnection.setRemoteDescription(jsep).then(()=>{
console.log('remote sdp',jsep.sdp)
peerConnection.remoteSdp = jsep.sdp;
})
}
};
document.getElementById("registerBtn").addEventListener("click", () => {
const extension = document.getElementById("extension").value.trim();
const password = document.getElementById("password").value.trim();
if (!extension || !password) {
logs.innerHTML += "<p>Por favor, llena todos los campos.</p>";
return;
}
logs.innerHTML += "<p>Conectando al servidor Janus...</p>";
worker.postMessage({ type: "connect" });
setTimeout(() => {
worker.postMessage({ type: "register", extension: `sip:${extension}@192.168.56.58:5060`, password });
}, 3000);
});
async function createPeerConnection() {
const configuration = {
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
forceEncodedAudioInsertableStreams: true,
encodedInsertableStreams: true,
iceTransportPolicy: "all",
sdpSemantics: "unified-plan"
};
const constraints = {
optional:{
DtlsSrtpKeyAgreement: true,
}
}
peerConnection = new RTCPeerConnection(configuration, constraints)
peerConnection.addTransceiver('audio', { direction: 'sendrecv' })
localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false })
localStream.getTracks().forEach(track =>{
peerConnection.addTrack(track, localStream)
})
remoteStream = new MediaStream();
peerConnection.ontrack = event => {
console.log("Track received:", event);
console.log("eventstream", event.streams[0].getTracks())
if (event.streams && event.streams[0]) {
event.streams[0].getTracks().forEach(track => {
remoteStream.addTrack(track)
});
document.getElementById('remoteAudio').srcObject = remoteStream;
document.getElementById('remoteAudio').volume = 1
logs.innerHTML += `<p>Flujo remoto recibido y vinculado.</p>`;
} else {
logs.innerHTML += `<p>Evento ontrack recibido sin streams.</p>`;
}
};
peerConnection.onicecandidate = (event) => {
};
}
function mungeSdpForSimulcasting(sdp) {
// Let's munge the SDP to add the attributes for enabling simulcasting
// (based on https://gist.github.com/ggarber/a19b4c33510028b9c657)
let lines = sdp.split("\r\n");
let video = false;
let ssrc = [ -1 ], ssrc_fid = [ -1 ];
let cname = null, msid = null, mslabel = null, label = null;
let insertAt = -1;
for(var i=0; i<lines.length; i++) {
let mline = lines[i].match(/m=(\w+) */);
if(mline) {
let medium = mline[1];
if(medium === "video") {
// New video m-line: make sure it's the first one
if(ssrc[0] < 0) {
video = true;
} else {
// We're done, let's add the new attributes here
insertAt = i;
break;
}
} else {
// New non-video m-line: do we have what we were looking for?
if(ssrc[0] > -1) {
// We're done, let's add the new attributes here
insertAt = i;
break;
}
}
continue;
}
console.log('linea de wea',lines[i])
if(!video)
continue;
var fid = lines[i].match(/a=ssrc-group:FID (\d+) (\d+)/);
if(fid) {
ssrc[0] = fid[1];
ssrc_fid[0] = fid[2];
lines.splice(i, 1); i--;
continue;
}
console.log(fid)
if(ssrc[0]) {
var match = lines[i].match('a=ssrc:' + ssrc[0] + ' cname:(.+)')
if(match) {
cname = match[1];
}
match = lines[i].match('a=ssrc:' + ssrc[0] + ' msid:(.+)')
if(match) {
msid = match[1];
}
match = lines[i].match('a=ssrc:' + ssrc[0] + ' mslabel:(.+)')
if(match) {
mslabel = match[1];
}
match = lines[i].match('a=ssrc:' + ssrc[0] + ' label:(.+)')
if(match) {
label = match[1];
}
if(lines[i].indexOf('a=ssrc:' + ssrc_fid[0]) === 0) {
lines.splice(i, 1); i--;
continue;
}
if(lines[i].indexOf('a=ssrc:' + ssrc[0]) === 0) {
lines.splice(i, 1); i--;
continue;
}
}
if(lines[i].length == 0) {
lines.splice(i, 1); i--;
continue;
}
}
if(ssrc[0] < 0) {
// Couldn't find a FID attribute, let's just take the first video SSRC we find
insertAt = -1;
video = false;
for(var i=0; i<lines.length; i++) {
var mline = lines[i].match(/m=(\w+) */);
if(mline) {
var medium = mline[1];
if(medium === "video") {
// New video m-line: make sure it's the first one
if(ssrc[0] < 0) {
video = true;
} else {
// We're done, let's add the new attributes here
insertAt = i;
break;
}
} else {
// New non-video m-line: do we have what we were looking for?
if(ssrc[0] > -1) {
// We're done, let's add the new attributes here
insertAt = i;
break;
}
}
continue;
}
if(!video)
continue;
if(ssrc[0] < 0) {
var value = lines[i].match(/a=ssrc:(\d+)/);
if(value) {
ssrc[0] = value[1];
lines.splice(i, 1); i--;
continue;
}
} else {
var match = lines[i].match('a=ssrc:' + ssrc[0] + ' cname:(.+)')
if(match) {
cname = match[1];
}
match = lines[i].match('a=ssrc:' + ssrc[0] + ' msid:(.+)')
if(match) {
msid = match[1];
}
match = lines[i].match('a=ssrc:' + ssrc[0] + ' mslabel:(.+)')
if(match) {
mslabel = match[1];
}
match = lines[i].match('a=ssrc:' + ssrc[0] + ' label:(.+)')
if(match) {
label = match[1];
}
if(lines[i].indexOf('a=ssrc:' + ssrc_fid[0]) === 0) {
lines.splice(i, 1); i--;
continue;
}
if(lines[i].indexOf('a=ssrc:' + ssrc[0]) === 0) {
lines.splice(i, 1); i--;
continue;
}
}
if(lines[i].length === 0) {
lines.splice(i, 1); i--;
continue;
}
}
}
if(ssrc[0] < 0) {
// Still nothing, let's just return the SDP we were asked to munge
return sdp;
}
if(insertAt < 0) {
// Append at the end
insertAt = lines.length;
}
// Generate a couple of SSRCs (for retransmissions too)
// Note: should we check if there are conflicts, here?
ssrc[1] = Math.floor(Math.random()*0xFFFFFFFF);
ssrc[2] = Math.floor(Math.random()*0xFFFFFFFF);
ssrc_fid[1] = Math.floor(Math.random()*0xFFFFFFFF);
ssrc_fid[2] = Math.floor(Math.random()*0xFFFFFFFF);
// Add attributes to the SDP
for(var i=0; i<ssrc.length; i++) {
if(cname) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' cname:' + cname);
insertAt++;
}
if(msid) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' msid:' + msid);
insertAt++;
}
if(mslabel) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' mslabel:' + mslabel);
insertAt++;
}
if(label) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' label:' + label);
insertAt++;
}
// Add the same info for the retransmission SSRC
if(cname) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' cname:' + cname);
insertAt++;
}
if(msid) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' msid:' + msid);
insertAt++;
}
if(mslabel) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' mslabel:' + mslabel);
insertAt++;
}
if(label) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' label:' + label);
insertAt++;
}
}
lines.splice(insertAt, 0, 'a=ssrc-group:FID ' + ssrc[2] + ' ' + ssrc_fid[2]);
lines.splice(insertAt, 0, 'a=ssrc-group:FID ' + ssrc[1] + ' ' + ssrc_fid[1]);
lines.splice(insertAt, 0, 'a=ssrc-group:FID ' + ssrc[0] + ' ' + ssrc_fid[0]);
lines.splice(insertAt, 0, 'a=ssrc-group:SIM ' + ssrc[0] + ' ' + ssrc[1] + ' ' + ssrc[2]);
sdp = lines.join("\r\n");
if(!sdp.endsWith("\r\n"))
sdp += "\r\n";
return sdp;
}
// Iniciar llamada
document.getElementById("callToBtn").addEventListener("click", async () => {
const callTo = document.getElementById("callTo").value.trim();
if (!callTo) {
logs.innerHTML += "<p>Por favor, ingresa un número para llamar.</p>";
return;
}
logs.innerHTML += `<p>Iniciando llamada a ${callTo}...</p>`;
await createPeerConnection();
peerConnection.createOffer({offerToReceiveAudio: true})
.then(offer => {
console.log('modified sdp',mungeSdpForSimulcasting(offer.sdp))
offer.sdp = mungeSdpForSimulcasting(offer.sdp)
return peerConnection.setLocalDescription(offer);
})
.then(() => {
worker.postMessage({
type: "call",
destination: `sip:${callTo}@192.168.56.58:5060`,
sdp: peerConnection.localDescription.sdp
});
})
.catch(error => {
logs.innerHTML += `<p>Error al crear la oferta SDP: ${error}</p>`;
});
});
document.getElementById("hangup").addEventListener("click", () => {
logs.innerHTML += "<p>Terminando llamada...</p>";
worker.postMessage({ type: "hangup" });
});
</script>
</body>
</html>
let ws; // WebSocket connection
let sessionId; // Janus session ID
let handleId; // Janus plugin handle ID
const transactions = {}; // Object to track transactions
const apiKey = "5786EA93T6QP1KoAdh17Ac49sz0p70MG6I9F7FrPeVl32LFw3u"; // Replace with your API Key
const janusServer = "wss://arwebrtc06.hiperpbx.com:8192/janus"; // Replace with your Janus server URL
let keepAliveInterval; // Interval for keep-alive
// Generate a unique transaction ID
function generateTransactionId() {
return Math.random().toString(36).substring(2, 12);
}
// WebSocket event handlers
function connectWebSocket() {
ws = new WebSocket(janusServer, 'janus-protocol');
ws.onopen = () => {
postMessage({ type: "log", message: "WebSocket connection opened." });
createSession();
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
postMessage({ type: "log", message: `Message received: ${JSON.stringify(message)}` });
handleJanusMessage(message);
};
ws.onerror = (error) => {
postMessage({ type: "log", message: `WebSocket error: ${error}` });
};
ws.onclose = () => {
postMessage({ type: "log", message: "WebSocket connection closed." });
clearInterval(keepAliveInterval);
postMessage({ type: "playAudio" });
};
}
// Create a Janus session
function createSession() {
const transactionId = generateTransactionId();
transactions[transactionId] = { type: "createSession" };
const createSessionMessage = {
janus: "create",
transaction: transactionId,
apisecret: apiKey,
};
ws.send(JSON.stringify(createSessionMessage));
}
// Attach SIP plugin
function attachPlugin() {
const transactionId = generateTransactionId();
transactions[transactionId] = { type: "attachPlugin", plugin: "janus.plugin.sip" };
const attachPluginMessage = {
janus: "attach",
plugin: "janus.plugin.sip",
session_id: sessionId,
transaction: transactionId,
apisecret: apiKey,
};
ws.send(JSON.stringify(attachPluginMessage));
}
// Register SIP extension
function registerSIP(extension, password) {
const registerMessage = {
janus: "message",
body: {
request: "register",
username: extension,
secret: password,
},
handle_id: handleId,
session_id: sessionId,
transaction: generateTransactionId(),
apisecret: apiKey,
};
ws.send(JSON.stringify(registerMessage));
}
// Send Keep-Alive for Janus session
function sendKeepAlive() {
if (sessionId) {
const keepAliveMessage = {
janus: "keepalive",
session_id: sessionId,
transaction: generateTransactionId(),
apisecret: apiKey
};
ws.send(JSON.stringify(keepAliveMessage));
postMessage({ type: "log", message: "Keep-Alive sent." });
}
}
// Initiate a call
function initiateCall(destination, sdp) {
console.log(sdp)
const callMessage = {
janus: "message",
body: {
request: "call",
uri: destination
},
jsep:{
type: "offer",
sdp: sdp
},
handle_id: handleId,
session_id: sessionId,
transaction: generateTransactionId(),
apisecret: apiKey,
};
ws.send(JSON.stringify(callMessage));
postMessage({ type: "log", message: `Calling ${destination}...` });
}
// Hang up a call
function hangupCall() {
const hangupMessage = {
janus: "message",
body: {
request: "hangup"
},
handle_id: handleId,
session_id: sessionId,
transaction: generateTransactionId(),
apisecret: apiKey,
};
ws.send(JSON.stringify(hangupMessage));
postMessage({ type: "log", message: "Call terminated." });
}
// Handle incoming Janus messages
function handleJanusMessage(message) {
const { transaction, janus, data, error, jsep } = message;
if (janus === "success") {
if (transaction && transactions[transaction]) {
const transactionInfo = transactions[transaction];
if (transactionInfo.type === "createSession") {
sessionId = data.id;
postMessage({ type: "log", message: `Session created: ${sessionId}` });
attachPlugin();
keepAliveInterval = setInterval(sendKeepAlive, 30000);
} else if (transactionInfo.type === "attachPlugin" && transactionInfo.plugin === "janus.plugin.sip") {
handleId = data.id;
postMessage({ type: "log", message: `SIP plugin attached: ${handleId}` });
postMessage({ type: "registered" });
}
}
} else if (janus === "event" && data) {
if (data.sip) {
if (data.sip.result === "accepted") {
postMessage({ type: "log", message: "Call accepted by the remote party." });
} else if (data.sip.result === "hangup") {
postMessage({ type: "log", message: "Remote party hung up the call." });
}
}
} else if (janus === "error") {
postMessage({ type: "log", message: `Error: ${error?.reason}` });
if (error.code === 458) {
ws.close();
}
} else if(jsep){
postMessage({type: "accepted", jsep})
}
console.log('wea',message)
if (transaction) {
delete transactions[transaction];
}
}
// Handle messages from the main thread
onmessage = (e) => {
const { type, extension, password, destination, sdp } = e.data;
if (type === "connect") {
connectWebSocket();
} else if (type === "register") {
registerSIP(extension, password);
} else if (type === "call") {
initiateCall(destination, sdp);
} else if (type === "hangup") {
hangupCall();
}
};
File added
File added
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment