// get DOM elements
var dataChannelLog = document.getElementById("data-channel"),
iceConnectionLog = document.getElementById("ice-connection-state"),
iceGatheringLog = document.getElementById("ice-gathering-state"),
signalingLog = document.getElementById("signaling-state");
let wsConnection = null;
let answer = null;
let offer = null;
let startStamp = null;
// peer connection
var pc = null;
var session_token = null;
async function createPeerConnection() {
var config = {
sdpSemantics: "unified-plan",
};
if (document.getElementById("use-stun").checked) {
// Fetch ICE servers first
try {
const sfu = document.getElementById("use-sfu").checked;
const faceId = document.getElementById("faceId").value;
if (faceId === "") {
alert("Please enter faceId");
return;
}
const metadata = {
// faceId: "tmp9i8bbq7c",
faceId: faceId,
handleSilence: document.getElementById("handle-silence").checked,
maxSessionLength: parseInt(document.getElementById("maxSessionLength").value),
maxIdleTime: parseInt(document.getElementById("maxIdleTime").value),
};
console.log(metadata);
console.log(parseInt(document.getElementById("maxSessionLength").value));
const sessionPromise = await fetch("/compose/token", {
method: "POST",
body: JSON.stringify(metadata),
headers: {
"Content-Type": "application/json",
"x-simli-api-key": document.getElementById("apiKey").value,
},
});
session_token = await sessionPromise.json();
wsURL = new URL(window.location.origin + "/compose/webrtc/p2p");
wsURL.searchParams.set("session_token", session_token.session_token);
wsURL.searchParams.set("enableSFU", sfu);
const ws = new WebSocket(wsURL);
wsConnection = ws;
ws.addEventListener("open", async () => {
while (!offer) {
await new Promise((r) => setTimeout(r, 10));
}
ws.send(
JSON.stringify({
sdp: offer.sdp,
type: offer.type,
}),
);
// wsConnection.send(session_token.session_token);
});
ws.addEventListener("message", async (evt) => {
dataChannelLog.textContent += "< " + evt.data + "\n";
if (evt.data === "START") {
dcZeroAudio = setTimeout(() => {
var message = new Uint8Array(64000);
wsConnection.send(message);
console.log("SEND");
}, 100);
return;
}
if (evt.data === "STOP") {
stop();
return;
} else if (evt.data.slice(0, 4) === "pong") {
console.log("PONG");
var elapsed_ms = current_stamp() - parseInt(evt.data.substring(5), 10);
dataChannelLog.textContent += " RTT " + elapsed_ms + " ms\n";
} else {
try {
const message = JSON.parse(evt.data);
if (message.type !== "answer") {
return;
}
answer = message;
document.getElementById("answer-sdp").textContent = answer.sdp;
} catch (e) {
console.log(e);
}
}
});
ws.addEventListener("close", () => {
console.log("Websocket closed");
});
const icePromise = await fetch("/compose/ice", {
headers: {
"Content-Type": "application/json",
},
method: "GET",
headers: {
"x-simli-api-key": document.getElementById("apiKey").value,
},
});
// iceServers = await response.json();
const results = await Promise.all([icePromise]);
console.log(results);
config.iceServers = await results[0].json();
} catch (error) {
console.error("Error fetching ICE servers:", error);
// Fallback to Google STUN server if fetch fails
config.iceServers = [{urls: ["stun:stun.l.google.com:19302"]}];
}
}
console.log(config);
pc = new RTCPeerConnection(config);
// register some listeners to help debugging
pc.addEventListener(
"icegatheringstatechange",
() => {
iceGatheringLog.textContent += " -> " + pc.iceGatheringState;
},
false,
);
iceGatheringLog.textContent = pc.iceGatheringState;
pc.addEventListener(
"iceconnectionstatechange",
() => {
iceConnectionLog.textContent += " -> " + pc.iceConnectionState;
},
false,
);
iceConnectionLog.textContent = pc.iceConnectionState;
pc.addEventListener(
"signalingstatechange",
() => {
signalingLog.textContent += " -> " + pc.signalingState;
},
false,
);
signalingLog.textContent = pc.signalingState;
// connect audio / video
pc.addEventListener("track", (evt) => {
if (evt.track.kind == "video") {
document.getElementById("video").srcObject = evt.streams[0];
document.getElementById("video").requestVideoFrameCallback(() => {
const startupDuration = (new Date().getTime() - startStamp) / 1000;
console.log("First video frame rendered!", startupDuration);
document.getElementById("startTime").textContent += startupDuration.toString() + "s";
});
document;
} else {
document.getElementById("audio").srcObject = evt.streams[0];
}
});
pc.onicecandidate = (event) => {
if (event.candidate === null) {
console.log(JSON.stringify(pc.localDescription));
} else {
console.log(event.candidate);
// console.log(JSON.stringify(pc.localDescription));
candidateCount += 1;
// console.log(candidateCount);
}
};
return pc;
}
function enumerateInputDevices() {
const populateSelect = (select, devices) => {
let counter = 1;
devices.forEach((device) => {
const option = document.createElement("option");
option.value = device.deviceId;
option.text = device.label || "Device #" + counter;
select.appendChild(option);
counter += 1;
});
};
navigator.mediaDevices
.enumerateDevices()
.then((devices) => {
populateSelect(
document.getElementById("audio-input"),
devices.filter((device) => device.kind == "audioinput"),
);
populateSelect(
document.getElementById("video-input"),
devices.filter((device) => device.kind == "videoinput"),
);
})
.catch((e) => {
alert(e);
});
}
let candidateCount = 0;
let prevCandidateCount = -1;
function CheckIceCandidates() {
if (pc.iceGatheringState === "complete" || candidateCount === prevCandidateCount) {
console.log(pc.iceGatheringState, candidateCount);
connectToRemotePeer().catch();
} else {
prevCandidateCount = candidateCount;
setTimeout(CheckIceCandidates, 250);
}
}
function negotiate() {
return pc
.createOffer()
.then((offer) => {
return pc.setLocalDescription(offer);
})
.then(() => {
prevCandidateCount = candidateCount;
setTimeout(CheckIceCandidates, 250);
});
}
async function connectToRemotePeer() {
offer = pc.localDescription;
while (answer === null) {
await new Promise((r) => setTimeout(r, 10));
}
console.log(answer);
await pc.setRemoteDescription(answer);
}
var time_start = null;
const current_stamp = () => {
if (time_start === null) {
time_start = new Date().getTime();
return 0;
} else {
return new Date().getTime() - time_start;
}
};
function start() {
startStamp = new Date().getTime();
document.getElementById("start").style.display = "none";
pc = createPeerConnection().then((pc) => {
document.getElementById("media").style.display = "block";
pc.addTransceiver("audio", {direction: "recvonly"});
pc.addTransceiver("video", {direction: "recvonly"});
negotiate();
document.getElementById("stop").style.display = "inline-block";
});
}
function sendZeros() {
// Add event listener for file input change event
if (dc && dc.readyState === "open") {
wsConnection.send(new Uint8Array(64000));
console.log("SEND ZEROS", Date.now());
}
dataChannelLog.textContent += "- Sent Zeros: " + "64000" + "\n";
}
function sendFile() {
// Add event listener for file input change event
var file = document.getElementById("fileInput").files[0];
var reader = new FileReader();
reader.onload = async function (e) {
var arrayBuffer = e.target.result;
var uint8Array = new Uint8Array(arrayBuffer);
var button = document.getElementById("sendFile");
let chunkSize = parseInt(document.getElementById("chunkSize").value);
// console.log(uint8Array)
if (dc && dc.readyState === "open") {
// for (var x = 0; x < 600; x++)
{
for (var i = 0; i < uint8Array.length; i += chunkSize) {
wsConnection.send(uint8Array.slice(i, i + chunkSize));
console.log("SEND", Date.now());
button.textContent = "Sending...";
}
await new Promise((r) => setTimeout(r, 200));
}
// dc.send(uint8Array);
// console.log("SUNEN");
dataChannelLog.textContent += "- Sent file: " + file.name + "\n";
button.textContent = "Sent";
}
};
reader.readAsArrayBuffer(file);
}
function playImmediate() {
// Add event listener for file input change event
var file = document.getElementById("fileInput").files[0];
var reader = new FileReader();
reader.onload = async function (e) {
const arrayBuffer = new Uint8Array(e.target.result);
const asciiStr = "PLAY_IMMEDIATE";
const encoder = new TextEncoder(); // Default is utf-8
const strBytes = encoder.encode(asciiStr); // Uint8Array of " World!"
var uint8Array = new Uint8Array(strBytes.length + arrayBuffer.length);
uint8Array.set(strBytes, 0);
uint8Array.set(arrayBuffer, strBytes.length);
var button = document.getElementById("playImmediate");
// let chunkSize = parseInt(document.getElementById("chunkSize").value);
// console.log(uint8Array)
if (dc && dc.readyState === "open") {
// for (var x = 0; x < 600; x++)
wsConnection.send(uint8Array);
// dc.send(uint8Array);
// console.log("SUNEN");
dataChannelLog.textContent += "- Sent file: " + file.name + "\n";
button.textContent = "Sent";
}
};
reader.readAsArrayBuffer(file);
}
function playImmediateAndChunk() {
// Add event listener for file input change event
var file = document.getElementById("fileInput").files[0];
var reader = new FileReader();
reader.onload = async function (e) {
const arrayBuffer = new Uint8Array(e.target.result);
const asciiStr = "PLAY_IMMEDIATE";
const encoder = new TextEncoder(); // Default is utf-8
const strBytes = encoder.encode(asciiStr); // Uint8Array of " World!"
const firstChunkSize = 16000 * 2 * 4;
const firstSlice = arrayBuffer.slice(0, Math.min(firstChunkSize, arrayBuffer.length));
var uint8Array = new Uint8Array(strBytes.length + firstSlice.length);
uint8Array.set(strBytes, 0);
uint8Array.set(firstSlice, strBytes.length);
var button = document.getElementById("playImmediateAndChunk");
let chunkSize = parseInt(document.getElementById("chunkSize").value);
// console.log(uint8Array)
if (dc && dc.readyState === "open") {
// for (var x = 0; x < 600; x++)
wsConnection.send(uint8Array);
// await new Promise((r) => setTimeout(r, 1000));
if (arrayBuffer.length > firstChunkSize) {
for (var i = firstChunkSize; i < arrayBuffer.length; i += chunkSize) {
wsConnection.send(arrayBuffer.slice(i, i + chunkSize));
console.log("SEND", Date.now());
button.textContent = "Sending...";
}
}
// dc.send(uint8Array);
// console.log("SUNEN");
dataChannelLog.textContent += "- Sent file: " + file.name + "\n";
button.textContent = "Sent";
}
};
reader.readAsArrayBuffer(file);
}
function skip() {
wsConnection.send("SKIP");
}
function stop() {
if (wsConnection) {
wsConnection.send("DONE");
}
// close transceivers
if (pc.getTransceivers()) {
pc.getTransceivers().forEach((transceiver) => {
if (transceiver.stop) {
transceiver.stop();
}
});
}
// close local audio / video
pc.getSenders().forEach((sender) => {
sender.track.stop();
});
// close peer connection
pc.close();
}
addEventListener("beforeunload", (event) => {
stop();
});