Files
test1/plugins/BoostedAudio/webhost/index.html
2024-11-12 23:57:52 +01:00

1512 lines
63 KiB
HTML

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8"/>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
transparent: 'transparent',
current: 'currentColor',
'main': '#262c3c',
'primary': '#202433',
'secondary': '#205295',
'accent': '#4797de',
}
}
}
}
</script>
<title>BoostedAudio Client</title>
</head>
<body class="flex h-screen w-screen bg-[url(https://i.imgur.com/6MelMqC.jpg)] bg-no-repeat bg-cover">
<div class="z-10 relative drop-shadow-lg hidden">
<div id="toogleDiv" class="hover:scale-150 duration-100 absolute top-1/2 -left-6">
<div id="toggleButton" class="h-10 w-14
rounded-3xl bg-main text-white cursor-pointer transition-transform duration-300 ease-in-out">
<svg class="h-full w-full relative left-3" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"/>
</svg>
</div>
</div>
<!-- Sidebar and Toggle Button -->
<div id="sidebar"
class="w-64 bg-main text-white p-4 transform -translate-x-full transition-transform duration-300 ease-in-out fixed h-screen">
<!-- Sidebar content here -->
Sidebar Content
</div>
</div>
<script>
// Side bar
const sidebar = document.getElementById('sidebar');
const toggleButton = document.getElementById('toggleButton');
const toogleDiv = document.getElementById('toogleDiv');
let barOpen = false;
document.addEventListener('click', (event) => {
if (sidebar.contains(event.target) || toggleButton.contains(event.target) || sidebar.classList.contains("-translate-x-full")) return;
sidebar.classList.add('-translate-x-full');
toggleButton.classList.toggle('translate-x-64');
barOpen = false;
});
toggleButton.addEventListener('click', () => {
sidebar.classList.toggle('-translate-x-full');
toggleButton.classList.toggle('translate-x-64');
barOpen = !barOpen;
});
toogleDiv.addEventListener("click", e => {
if (toogleDiv.classList.contains("hover:scale-150")) {
toogleDiv.classList.remove("hover:scale-150")
} else {
setTimeout(() => {
if (!barOpen) {
toogleDiv.classList.add("hover:scale-150")
}
}, 300)
}
})
</script>
<div class="w-full h-full
bg-gradient-to-t from-primary/80 via-secondary/80 to-primary/80">
<!--Connection-->
<div id="connection"
class="z-50 fixed bg-gray-800/60 left-0 w-full h-full flex justify-center items-center text-slate-200">
<div class="flex justify-center">
<p class="gray-animation relative bottom-20 text-5xl">Click on the page to connect</p>
</div>
</div>
<style>
@keyframes grayChange {
0% {
color: #f3f4f6;
}
50% {
color: #9CA3AF;
}
100% {
color: #f3f4f6;
}
}
.gray-animation {
animation: grayChange 2s ease-in-out infinite;
}
@keyframes redChange {
0% {
color: #f3f4f6;
}
50% {
color: #ef8080;
}
100% {
color: #f3f4f6;
}
}
.red-animation:hover {
animation: redChange 1.5s ease-in-out infinite;
}
</style>
<!--Settings-->
<div id="settings"
class="z-50 fixed top-0 left-0 w-full h-full bg-gray-800/50 flex justify-center items-center hidden text-slate-200">
<!--Params-->
<div id="params"
class="bg-main p-4 rounded-lg w-2/3 h-fit grid gap-5 drop-shadow-lg ring ring-primary ring-2 hover:ring-accent duration-300">
<!--Title-->
<div class="text-center">
<h1 class="text-xl font-bold">Settings</h1>
<label class="text-sm text-slate-400">Parameters of the audio client</label>
</div>
<!--Params-->
<div id="microphoneSelectDiv" class="w-full h-10">
<label class="block text-m font-bold mb-2">Microphone Device</label>
<select id="microphoneSelect"
class="w-full h-full bg-primary rounded focus:outline-none focus:ring focus:ring-accent hover:ring hover:ring-accent duration-300">
</select>
</div>
<style>
.sliderMicThreshold {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 15px;
border-radius: 7.5px;
background: #ddd;
outline: none;
opacity: 0.7;
-webkit-transition: .2s;
transition: opacity .2s;
}
.sliderMicThreshold::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
border-radius: 50%;
background: #7d7d7d;
cursor: pointer;
}
.sliderMicThreshold::-moz-range-thumb {
width: 25px;
height: 25px;
border-radius: 50%;
background: #7d7d7d;
cursor: pointer;
}
</style>
<label class="block font-bold mt-6">Microphone sensitivity</label>
<p>Sensivity of the microphone in dB, the noise bellow this dB amount will be cancel, the bar moving represent your current mic volume</p>
<input type="range" min="-100" max="0" step="0.01" value="0.5" class="sliderMicThreshold ring ring-primary"
id="sliderMicThreshold">
<label class="block font-bold mt-6">Noise Suppression</label>
<div class="flex gap-x-1 items-center">
<input type="checkbox" id="noiseSuppression"
class="accent-accent h-4 w-4 text-gray-800 rounded border-gray-300">
<label>Removes noise from your microphone audio</label>
</div>
<label class="block font-bold mt-6">Echo Cancellation</label>
<div class="flex gap-x-1 items-center">
<input type="checkbox" id="echoCancellation"
class="accent-accent h-4 w-4 text-gray-800 rounded border-gray-300">
<label>Removes echo from your microphone audio</label>
</div>
<!--Close Button-->
<div class="mt-5 flex justify-center">
<button id="closeSettings"
class="bg-secondary px-3 py-2 hover:bg-accent text-xl text-white font-bold rounded-xl duration-300">
Close Settings
</button>
</div>
</div>
</div>
<div class="h-full w-full p-8 text-slate-200">
<div class="bg-main rounded-lg shadow-2xl max-h-screen">
<!--Content-->
<div class="p-6">
<h1 class="text-2xl font-bold mb-4">BoostedAudio</h1>
<div class="flex mb-6 items-center gap-5">
<!--Ambient Volume-->
<div id="ambient" class="w-1/3 pr-4 grid grid-cols-1">
<label class="block text-sm font-medium mb-2">Ambient Volume</label>
<input id="ambientVolume" type="range" class="accent-accent w-full ring-slate-800" min="0"
max="100">
</div>
<!--Voice Volume-->
<div id="voice" class="w-1/3 pr-4 grid grid-cols-1">
<label class="block text-sm font-medium mb-2">Voice Volume</label>
<input id="voiceVolume" type="range" class="accent-accent w-full ring-slate-800" min="0"
max="150">
</div>
<div class="flex justify-center items-center relative m-5">
<div id="speakingHalo"
class="h-14 w-14 bg-emerald-500 absolute blur-lg opacity-80 rounded-full duration-300 hidden">
</div>
<svg id="muteButton" class="absolute z-10 h-12 w-12 text-white cursor-pointer
bg-secondary hover:bg-accent duration-300 font-bold rounded red-animation"
stroke="currentColor" fill="none" xmlns="http://www.w3.org/2000/svg" width="512.000000pt"
height="512.000000pt" viewBox="0 0 512.000000 512.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)" fill="currentColor"
stroke="currentColor">
<path d="M2393 4786 c-401 -77 -709 -387 -778 -785 -12 -69 -15 -226 -15 -881 0 -848 2 -885 49 -1023 65 -188 194 -361 358 -480 70 -51 224 -123 319 -149 114 -31 354 -31 467 0 217 59 408 188 535 360 66 88 136 235 164 342 l23 85 0 865 0 865 -23 85 c-92 348 -367 620 -713 705 -90 22 -296 28 -386 11z m357 -335 c93 -29 185 -86 260 -161 75 -76 120 -148 158 -252 l27 -73 0 -845 0 -845 -27 -73 c-38 -104 -83 -176 -158 -252 -76 -76 -167 -132 -265 -163 -57 -18 -93 -22 -190 -22 -104 1 -130 5 -193 27 -104 37 -176 83 -252 158 -75 76 -120 147 -158 252 l-27 73 -3 812 c-3 888 -3 886 56 1014 83 181 253 321 444 364 86 20 243 13 328 -14z"/>
<path d="M1066 2520 c-40 -13 -83 -56 -97 -99 -15 -45 -6 -206 20 -338 60 -311 205 -578 440 -814 259 -258 568 -413 924 -464 l47 -6 0 -185 c0 -164 2 -189 20 -223 54 -108 209 -114 273 -11 20 32 22 52 27 219 l5 184 106 18 c730 123 1314 785 1322 1501 2 138 -4 155 -67 202 -38 29 -134 29 -172 0 -58 -43 -67 -65 -76 -191 -18 -259 -86 -463 -221 -663 -66 -98 -202 -238 -302 -312 -116 -86 -295 -172 -440 -210 -115 -30 -128 -32 -315 -32 -186 0 -200 2 -313 32 -227 60 -417 170 -588 341 -237 237 -353 496 -377 844 -9 125 -18 149 -73 189 -29 21 -103 31 -143 18z"/>
</g>
</svg>
<svg id="mutedButton" class="absolute z-10 h-12 w-12 text-red-500 cursor-pointer
bg-secondary hover:bg-accent duration-300 font-bold rounded"
stroke="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
width="128.000000pt" height="128.000000pt" viewBox="0 0 128.000000 128.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,128.000000) scale(0.100000,-0.100000)"
fill="currentColor" stroke="currentColor">
<path d="M52 1228 c-7 -7 -12 -19 -12 -28 0 -20 1140 -1160 1161 -1160 19 0 39 20 39 39 0 9 -59 75 -131 147 l-131 131 31 48 c61 96 87 244 48 269 -30 20 -51 -4 -59 -70 -7 -55 -61 -184 -77 -184 -3 0 -28 21 -54 48 l-46 47 29 59 30 59 0 200 c0 239 -5 263 -75 332 -101 102 -229 102 -330 0 -56 -55 -75 -102 -75 -181 l0 -49 -153 153 c-84 83 -159 152 -168 152 -8 0 -20 -5 -27 -12z m676 -95 c65 -48 67 -56 70 -276 3 -173 1 -206 -14 -239 -9 -21 -20 -38 -23 -38 -3 0 -68 62 -143 137 l-138 138 0 87 c0 121 24 171 95 204 40 19 119 12 153 -13z"/>
<path d="M400 681 c0 -63 22 -114 75 -167 52 -52 116 -80 166 -72 23 3 21 6 -26 51 -77 72 -111 107 -165 167 l-50 55 0 -34z"/>
<path d="M212 668 c-26 -26 -8 -138 34 -223 59 -115 162 -200 283 -231 l70 -18 3 -70 c3 -76 21 -102 56 -80 13 9 18 28 20 82 3 69 4 72 30 78 44 9 116 34 132 44 13 8 11 13 -11 36 l-27 26 -68 -17 c-215 -54 -426 90 -451 307 -3 29 -10 59 -16 66 -12 15 -39 16 -55 0z"/>
</g>
</svg>
</div>
<!--MuteButton-->
<!--ParamButton-->
<div id="paramButton" class="h-12 w-12 p-1
bg-secondary hover:bg-accent duration-300 text-white font-bold rounded cursor-pointer">
<svg class="hover:animate-spin"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"/>
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
</div>
</div>
<h2 id="userList" class="text-lg font-bold mb-4">User List</h2>
<!--Search-->
<div id="searchDiv" class="p-1 w-1/5 mb-4 relative">
<input id="search" type="text"
class="w-full px-4 py-2 ring ring-primary focus:ring-accent hover:ring-accent duration-300 bg-secondary text-gray-300 border-none rounded-full focus:outline-none"
placeholder="Search..." maxlength="20">
<div class="absolute right-3 top-1/2 transform -translate-y-1/2">
<svg id="search-logo" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-300"
viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M14.833 13.392a8 8 0 1 0-1.44 1.44l5.334 5.333a1 1 0 0 0 1.5-1.333l-5.333-5.333zM2 8a6 6 0 1 1 12 0A6 6 0 0 1 2 8z"/>
</svg>
</div>
</div>
<script>
const searchInput = document.getElementById('search');
const svgIcon = document.getElementById('search-logo');
searchInput.addEventListener('focus', () => {
svgIcon.classList.add('animate-bounce');
});
searchInput.addEventListener('blur', () => {
svgIcon.classList.remove('animate-bounce');
});
</script>
<!--UserCount-->
<div id="userCountDiv" class="text-sm pb-3 flex gap-1">
<p class="text-slate-400">Players you can hear: </p>
<p id="userCount" class="text-slate-200">0</p>
</div>
<!--Mute Info-->
<p id="muteUser" class="text-red-500 text-center p-10 text-5xl italic hidden">You have been muted by the server</p>
<!--Users-->
<div id="users" class="overflow-auto
sm:max-h-[5rem] md:max-h-[20rem] 2xl:sm:max-h-[30rem]
min-h-0 bg-primary p-2 rounded-lg bg-scroll grid gap-4 sm:grid-cols-1 md:grid-cols-3 lg:grid-cols-4 drop-shadow-lg">
<div id="user"
class="user flex items-center bg-main p-4 rounded-lg focus:outline-none focus:ring hover:ring duration-300">
<img src="" alt="User 1" class="logo w-12 h-12 rounded-lg mr-4">
<div class="flex-grow">
<p class="font-medium username"></p>
<input type="range" class="user-volume accent-accent w-full mt-1" min="0" max="150">
</div>
<!--User Mute Button-->
<svg class="muteButton h-12 w-12 p-1 text-secondary hover:text-accent cursor-pointer
duration-300 font-bold rounded"
stroke="currentColor" fill="none" xmlns="http://www.w3.org/2000/svg"
width="512.000000pt"
height="512.000000pt" viewBox="0 0 512.000000 512.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
fill="currentColor"
stroke="currentColor">
<path d="M2393 4786 c-401 -77 -709 -387 -778 -785 -12 -69 -15 -226 -15 -881 0 -848 2 -885 49 -1023 65 -188 194 -361 358 -480 70 -51 224 -123 319 -149 114 -31 354 -31 467 0 217 59 408 188 535 360 66 88 136 235 164 342 l23 85 0 865 0 865 -23 85 c-92 348 -367 620 -713 705 -90 22 -296 28 -386 11z m357 -335 c93 -29 185 -86 260 -161 75 -76 120 -148 158 -252 l27 -73 0 -845 0 -845 -27 -73 c-38 -104 -83 -176 -158 -252 -76 -76 -167 -132 -265 -163 -57 -18 -93 -22 -190 -22 -104 1 -130 5 -193 27 -104 37 -176 83 -252 158 -75 76 -120 147 -158 252 l-27 73 -3 812 c-3 888 -3 886 56 1014 83 181 253 321 444 364 86 20 243 13 328 -14z"/>
<path d="M1066 2520 c-40 -13 -83 -56 -97 -99 -15 -45 -6 -206 20 -338 60 -311 205 -578 440 -814 259 -258 568 -413 924 -464 l47 -6 0 -185 c0 -164 2 -189 20 -223 54 -108 209 -114 273 -11 20 32 22 52 27 219 l5 184 106 18 c730 123 1314 785 1322 1501 2 138 -4 155 -67 202 -38 29 -134 29 -172 0 -58 -43 -67 -65 -76 -191 -18 -259 -86 -463 -221 -663 -66 -98 -202 -238 -302 -312 -116 -86 -295 -172 -440 -210 -115 -30 -128 -32 -315 -32 -186 0 -200 2 -313 32 -227 60 -417 170 -588 341 -237 237 -353 496 -377 844 -9 125 -18 149 -73 189 -29 21 -103 31 -143 18z"/>
</g>
</svg>
<svg class="mutedButton h-12 w-12 p-1 text-red-500 hover:text-accent cursor-pointer
duration-300 font-bold rounded"
stroke="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
width="128.000000pt" height="128.000000pt" viewBox="0 0 128.000000 128.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,128.000000) scale(0.100000,-0.100000)"
fill="currentColor" stroke="currentColor">
<path d="M52 1228 c-7 -7 -12 -19 -12 -28 0 -20 1140 -1160 1161 -1160 19 0 39 20 39 39 0 9 -59 75 -131 147 l-131 131 31 48 c61 96 87 244 48 269 -30 20 -51 -4 -59 -70 -7 -55 -61 -184 -77 -184 -3 0 -28 21 -54 48 l-46 47 29 59 30 59 0 200 c0 239 -5 263 -75 332 -101 102 -229 102 -330 0 -56 -55 -75 -102 -75 -181 l0 -49 -153 153 c-84 83 -159 152 -168 152 -8 0 -20 -5 -27 -12z m676 -95 c65 -48 67 -56 70 -276 3 -173 1 -206 -14 -239 -9 -21 -20 -38 -23 -38 -3 0 -68 62 -143 137 l-138 138 0 87 c0 121 24 171 95 204 40 19 119 12 153 -13z"/>
<path d="M400 681 c0 -63 22 -114 75 -167 52 -52 116 -80 166 -72 23 3 21 6 -26 51 -77 72 -111 107 -165 167 l-50 55 0 -34z"/>
<path d="M212 668 c-26 -26 -8 -138 34 -223 59 -115 162 -200 283 -231 l70 -18 3 -70 c3 -76 21 -102 56 -80 13 9 18 28 20 82 3 69 4 72 30 78 44 9 116 34 132 44 13 8 11 13 -11 36 l-27 26 -68 -17 c-215 -54 -426 90 -451 307 -3 29 -10 59 -16 66 -12 15 -39 16 -55 0z"/>
</g>
</svg>
</div>
</div>
</div>
<!--Footer of the div-->
<div class="relative p-2">
<!--DON'T REMOVE-->
<p class="z-10 absolute bottom-2 right-2 underline"><a
href="https://www.spigotmc.org/resources/boostedaudio-%E2%9C%A8proximity-voice-chat-and-music.112942/">Powered
by BoostedAudio</a></p>
</div>
</div>
</div>
</div>
</body>
</html>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="https://cdn.jsdelivr.net/npm/hark@1.2.3/hark.bundle.min.js"></script>
<script>
const servers = {
iceServers: [],
iceCandidatePoolSize: 10,
}
const RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
// Map<String(layerId), Map<UUID, PeerConnectionContainer>>
const peerConnections = new Map();
const layersSpatialized = new Map();
// Map<String, Function>
const registeredPackets = new Map();
registerPackets();
// Map<UUID, AudioObject>
const audioMap = new Map();
let audioContext;
let speechEvents;
let micStream;
let micGainNode;
let micDestination;
let playerId;
const params = new URLSearchParams(window.location.search);
const wsIP = "wss://localhost:8081";
let proximityChat = true
let serverInfoSet = false;
let ws;
let voiceVolume = 1;
let ambientVolume = 1;
let clientLoc;
let consent = false;
let currentDeviceId;
let noiseSuppression = true;
let echoCancellation = true;
let maxDistance;
let rolloffFactor;
let refDistance;
let distanceModel;
let muted = false;
let serverMuted = false;
// HTML elements
const users = document.getElementById('users');
const muteUser = document.getElementById('muteUser');
const voice = document.getElementById('voiceVolume');
const ambient = document.getElementById('ambientVolume');
const micThreshold = document.getElementById('sliderMicThreshold');
const microphoneSelect = document.getElementById('microphoneSelect');
const paramButton = document.getElementById('paramButton');
const closeButton = document.getElementById('closeSettings');
const settingsMenu = document.getElementById('settings');
const paramsWindow = document.getElementById('params');
const connectionDiv = document.getElementById("connection");
const muteButton = document.getElementById('muteButton');
const mutedButton = document.getElementById('mutedButton');
let userTemplate;
let isOpen = false;
console.log(adapter.browserDetails.browser)
console.log(adapter.browserDetails.version)
// Setup html user element
const user = document.getElementById("user")
userTemplate = user.outerHTML
user.remove()
// CONNECT BUTTON
connectionDiv.addEventListener("click", () => {
connectionDiv.classList.add("hidden");
setupWithConsent();
});
paramButton.addEventListener('click', () => {
settingsMenu.classList.remove('hidden');
});
closeButton.addEventListener('click', () => {
settingsMenu.classList.add('hidden');
});
settingsMenu.addEventListener('click', e => {
if (paramsWindow.contains(e.target)) return
settingsMenu.classList.add('hidden');
});
function getLayerMap(layerId) {
if (!peerConnections.has(layerId)) peerConnections.set(layerId, new Map());
return peerConnections.get(layerId)
}
// Search bar
const search = document.getElementById("search");
const userCount = document.getElementById("userCount");
if (!proximityChat) {
document.getElementById("userList").classList.add("hidden")
users.classList.add("hidden")
document.getElementById("searchDiv").classList.add("hidden")
document.getElementById("userCountDiv").classList.add("hidden")
document.getElementById("microphoneSelectDiv").classList.add("hidden")
muteButton.classList.add("hidden")
mutedButton.classList.add("hidden")
document.getElementById("voice").classList.add("hidden")
document.getElementById("paramButton").classList.add("hidden")
} else {
setInterval(() => {
userCount.textContent = getLayerMap("proximitychat").size
const userElement = users.getElementsByClassName("user")
if (search.value.length === 0) return
for (let i = 0; i < userElement.length; i++) {
const usr = userElement[i];
const nameElement = usr.querySelector(".username");
if (nameElement) {
if (nameElement.toLowerCase().textContent.includes(search.value.toLowerCase())) {
usr.classList.remove('hidden');
} else {
usr.classList.add('hidden');
}
}
}
}, 100);
askMic()
}
async function askMic() {
micStream = await getUserMic();
connectionDiv.classList.add("hidden");
populateMicrophoneOptions();
setupWithConsent();
}
function getUserMic(deviceId) {
try {
let constraints = {
audio: {
noiseSuppression: noiseSuppression,
echoCancellation: echoCancellation,
channelCount: 1,
autoGainControl: true
}
};
if (deviceId) constraints.audio.deviceId = deviceId;
return navigator.mediaDevices.getUserMedia(constraints);
} catch (err) {
console.log("Error with getUserMic ", err)
return null;
}
}
function initWithServerInfo() {
setupAudioSystem()
}
setInterval(async () => {
audioMap.forEach((audioObj, uuid) => {
const loc = audioObj.location;
//console.log("Spatialize audio", loc)
if (loc) {
spatializeAudio(audioObj, loc)
if (distance(loc, clientLoc) >= audioObj.maxVoiceDistance) {
audioObj.gainNode.gain.value = 0;
} else {
audioObj.gainNode.gain.value = 1;
}
}
})
}, 50);
function registerPackets() {
registeredPackets.set("AddPeerPacket", async packet => {
if (serverMuted) return
const from = packet.from;
const to = packet.to;
const layerId = packet.layerId;
const spatialized = packet.spatialized;
console.log("Spatialized", layerId, spatialized)
layersSpatialized.set(layerId, spatialized)
let username = packet.username;
let pc;
switch (packet.rtcDesc.type) {
// Packet action from server
case "createoffer":
pc = await createPeerConnection(layerId, from, username)
const offer = await pc.createOffer()
await pc.setLocalDescription(offer)
const offerPacket = [
{
"type": "AddPeerPacket",
"value": {
"layerId": layerId,
"from": playerId,
"to": from,
"username": "",
"spatialized": spatialized,
"rtcDesc": {
"type": offer.type,
"sdp": offer.sdp,
}
}
}
]
/*console.log("Offer: " + JSON.stringify(offerPacket))*/
console.log("Creating offer")
ws.send(JSON.stringify(offerPacket))
break
// Packet action from other user, check by server
// Receive offer
case "offer":
pc = await createPeerConnection(layerId, from, username)
const desc = new RTCSessionDescription(packet.rtcDesc);
await pc.setRemoteDescription(desc)
const answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
const answerPacket = [
{
"type": "AddPeerPacket",
"value": {
"layerId": layerId,
"from": to,
"to": from,
"username": "",
"spatialized": spatialized,
"rtcDesc": {
"type": answer.type,
"sdp": answer.sdp,
}
}
}
]
ws.send(JSON.stringify(answerPacket))
console.log("Receive OFFER, Sending answer")
break
// Packet action from other user, check by server
// Receive answer
case "answer":
pc = getLayerMap(layerId).get(from).pc
await pc.setRemoteDescription(new RTCSessionDescription(packet.rtcDesc))
console.log("Receive answer")
break
}
});
registeredPackets.set("RTCIcePacket", async packet => {
console.log("RECEIVING ICE CANDIDATE")
const from = packet.from;
const to = packet.to;
const layerId = packet.layerId;
console.log(from, to, layerId, packet)
const pc = getLayerMap(layerId).get(from).pc
const iceCandidate = new RTCIceCandidate({
"candidate": packet.candidate.candidate,
"sdpMid": packet.candidate.sdpMid,
"sdpMLineIndex": packet.candidate.sdpMLineIndex
});
const candidate = new RTCIceCandidate(iceCandidate);
await pc.addIceCandidate(candidate)
.catch((error) => {
console.error("Erreur with ICE candidate set : ", error);
});
console.log("ICE Candidate set")
});
registeredPackets.set("RemovePeerPacket", async packet => {
const layerId = packet.layerId;
const playerToRemoveId = packet.playerToRemove;
closePeerConnection(getLayerMap(layerId).get(playerToRemoveId))
});
registeredPackets.set("UpdatePeersLocationsPacket", async packet => {
// Parse clientloc
clientLoc = packet.clientLoc;
//console.log("UpdateClientLoc")
// Parse peers info
const players = packet.playersAround;
const map = new Map();
for (const key in players) {
if (players.hasOwnProperty(key)) map.set(key, players[key]);
}
if (proximityChat)
peerConnections.forEach((layerMap, layerId) => {
if (layersSpatialized.get(layerId) === true) {
layerMap.forEach((peerObj, uuid) => {
peerObj.loc = map.get(uuid)
spatializeAudio(peerObj, peerObj.loc)
})
}
})
});
registeredPackets.set("UpdateAudioLocationPacket", async packet => {
const audioId = packet.audioId;
//console.log("UpdateAudioLoc,", packet.newLocation)
audioMap.get(audioId).location = packet.newLocation;
});
registeredPackets.set("ChangeAudioTimePacket", async packet => {
audioMap.get(packet.audioId).audio.currentTime = packet.timeToPlay;
});
registeredPackets.set("AddAudioPacket", async packet => {
const id = packet.uuid;
let panner;
let audioSource;
const link = encodeURI(packet.link)
const spatialInfo = packet.spatialInfo;
const synchronous = packet.synchronous;
console.log(synchronous)
let maxVoiceDistance = 0;
let gainNode;
let audio;
if (spatialInfo) {
panner = new PannerNode(audioContext, {
panningModel: "HRTF",
distanceModel: spatialInfo.distanceModel,
refDistance: Math.max(spatialInfo.refDistance, 0.1),
rolloffFactor: spatialInfo.rolloffFactor,
coneInnerAngle: 360,
coneOuterAngle: 360,
coneOuterGain: 1,
});
const url = link.startsWith("https") ? link : link.startsWith("http") ? 'https://corsproxy.io/?' + link : link;
const audioHtml = new Audio(url)
audioHtml.crossOrigin = "anonymous"
audioSource = audioContext.createMediaElementSource(audioHtml);
audio = audioSource.mediaElement;
maxVoiceDistance = spatialInfo.maxVoiceDistance;
gainNode = audioContext.createGain();
audioSource.connect(panner);
panner.connect(gainNode);
gainNode.connect(audioContext.destination);
} else
audio = new Audio(link)
console.log("Playing " + id, " url", link)
const oldAudio = audioMap.get(id);
if (oldAudio) {
oldAudio.audio.pause();
}
if (synchronous) {
audio.addEventListener('loadedmetadata', () => {
let durMs = audio.duration * 1000;
let ts = Date.now();
audio.currentTime = ts % durMs / 1000;
audio.play();
});
} else audio.play();
audio.addEventListener('ended', function () {
console.log("Audio finished playing ", audioMap.get(id));
audioMap.delete(id)
if (gainNode) gainNode.disconnect();
const endAudioPacket =
[
{
"type": "RemoveAudioPacket",
"value": {
"uuid": id,
"fade": 0,
"audioLink": link,
}
}
]
ws.send(JSON.stringify(endAudioPacket))
});
const audioObj = {
audio: audio,
audioSource: audioSource,
panner: panner,
location: spatialInfo ? spatialInfo.location : null,
gainNode: gainNode,
maxVoiceDistance: maxVoiceDistance,
fadeInterval: null,
}
fadeIn(audio, packet.fadeIn, audioObj)
audioMap.set(id, audioObj)
});
registeredPackets.set("PausePlayAudioPacket", async packet => {
const audioObj = audioMap.get(packet.uuid);
const audio = audioObj.audio;
if (audio.paused)
fadeIn(audio, packet.fade, audioObj)
else
fadeOut(audio, packet.fade, audioObj)
});
registeredPackets.set("RemoveAudioPacket", async packet => {
const id = packet.uuid;
const audioObj = audioMap.get(id);
if (audioObj) {
console.log("Stop playing audio: " + id)
audioMap.delete(id)
fadeOut(audioObj.audio, packet.fade, () => {
if (audioObj.panner) audioObj.panner.disconnect();
clearInterval(audioObj.lerpInterval);
}, audioObj)
}
});
registeredPackets.set("TrustPacket", async packet => {
const serverInfo = packet.serverInfo;
maxDistance = serverInfo.maxDistance;
rolloffFactor = serverInfo.rolloffFactor;
refDistance = serverInfo.refDistance;
distanceModel = serverInfo.distanceModel;
playerId = serverInfo.playerId;
console.log("ServerInfo set")
serverInfoSet = true;
initWithServerInfo()
});
registeredPackets.set("IceServersPacket", async packet => {
servers.iceServers = packet.iceServers;
console.log("IceServers refreshed")
})
registeredPackets.set("ServerChangePacket", async packet => {
const serverName = packet.serverName;
console.log("Server change to " + serverName)
audioMap.forEach((audioObj, uuid) => {
const audio = audioObj.audio;
audio.pause();
audio.currentTime = 0;
})
audioMap.clear()
});
registeredPackets.set("ServerMutePacket", async packet => {
const mute = packet.mute;
serverMuted = mute;
if (mute) {
muteUser.classList.remove("hidden");
for (let value of peerConnections.values()) {
for (let pc of value.values()) {
closePeerConnection(pc);
}
}
} else {
muteUser.classList.add("hidden")
}
});
registeredPackets.set("ClientMutePacket", async packet => {
muteToggle();
});
}
async function handlePacket(type, packet) {
if (type !== "TrustPacket") {
if (!consent || !serverInfoSet) {
console.log("Delaying the Packet waiting for server response or user click (consent)...")
setTimeout(function () {
handlePacket(type, packet)
}, 100)
return
}
}
try {
registeredPackets.get(type)(packet);
} catch (e) {
console.log("Error packet processing", type, packet, e);
}
}
function distance(location1, location2) {
const x1 = location1.x;
const y1 = location1.y;
const z1 = location1.z;
const x2 = location2.x;
const y2 = location2.y;
const z2 = location2.z;
const dx = x2 - x1;
const dy = y2 - y1;
const dz = z2 - z1;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
async function fadeIn(audio, duration, audioObj) {
function fade() {
/* console.log(
"Starting FadeIn " + duration,
"Audio Volume " + audio.volume,
"Ambient Volume " + ambientVolume, audioObj
)*/
const interval = 10;
const stepNb = duration / interval;
const volumeStep = ambientVolume / stepNb;
//const step = interval / duration;
audio.volume = 0;
const fadeInterval = setInterval(() => {
if (audio.volume < ambientVolume) {
audio.volume = Math.min(ambientVolume, audio.volume + volumeStep);
} else {
clearInterval(fadeInterval);
/*console.log("End FadeIn ", audioObj)*/
audioObj.fadeInterval = null;
}
}, interval);
audioObj.fadeInterval = fadeInterval;
}
if (audioObj.fadeInterval) {
clearInterval(audioObj.fadeInterval)
/* console.log("Cancel Fade ", audioObj.fadeInterval)*/
}
fade()
}
async function fadeOut(audio, duration, afterFade, audioObj) {
function fade() {
const interval = 10;
const stepNb = duration / interval;
const volumeStep = ambientVolume / stepNb;
/* console.log(
"Starting FadeOut " + duration,
"Audio Volume " + audio.volume,
"Ambient Volume " + ambientVolume, audioObj
)*/
const fadeInterval = setInterval(() => {
if (audio.volume > 0) {
audio.volume = Math.max(0, audio.volume - volumeStep);
} else {
audio.pause();
clearInterval(fadeInterval);
/* console.log("End FadeOut ", audioObj)*/
audioObj.fadeInterval = null;
if (afterFade) afterFade()
}
}, interval);
audioObj.fadeInterval = fadeInterval;
}
if (audioObj.fadeInterval) {
clearInterval(audioObj.fadeInterval)
/* console.log("Cancel Fade ", audioObj.fadeInterval)*/
}
fade()
}
function spatializeAudio(pannerObject, loc) {
if (!loc) return;
pannerObject.t = 0;
if (!pannerObject.lerpInterval) {
pannerObject.lastLoc = loc;
pannerObject.currentLoc = loc;
pannerObject.lerpInterval = setInterval(() => {
const lerpLoc = {
x: lerp(pannerObject.lastLoc.x, pannerObject.currentLoc.x, pannerObject.t),
y: lerp(pannerObject.lastLoc.y, pannerObject.currentLoc.y, pannerObject.t),
z: lerp(pannerObject.lastLoc.z, pannerObject.currentLoc.z, pannerObject.t)
};
spatializeAudio0(pannerObject, lerpLoc)
//console.log("T: " + pannerObject.t + " ", lerpLoc)
pannerObject.t += 0.1;
}, 5);
} else {
pannerObject.lastLoc = pannerObject.currentLoc;
pannerObject.currentLoc = loc;
}
}
function spatializeAudio0(pannerObject, location) {
// console.log("Spatialize audio", objToSpatialize, pannerObject, clientLoc.yaw)
//console.log(pannerObject, location)
// Calc peer loc relative to the client
const x = location.x - clientLoc.x;
const y = location.y - clientLoc.y;
const z = location.z - clientLoc.z;
// Calc yaw
const yawRadian = degrees_to_radians(-clientLoc.yaw)
const cosYaw = Math.cos(yawRadian)
const sinYaw = Math.sin(yawRadian)
// Rotate
const newX = x * cosYaw - z * sinYaw;
const newZ = x * sinYaw + z * cosYaw;
//console.log(clientLoc.yaw + " | " + newX + " " + y + " " + newZ)
// Set the values
pannerObject.panner.positionX.value = newX
pannerObject.panner.positionY.value = y
pannerObject.panner.positionZ.value = newZ
}
function lerp(start, end, t) {
return start * (1 - t) + end * t;
}
function degrees_to_radians(degrees) {
const pi = Math.PI;
return degrees * (pi / 180);
}
async function createPeerConnection(layerId, peerId, username) {
console.log("New RTCPeerConnection")
let pc = new RTCPeerConnection(servers)
if (getLayerMap(layerId).has(peerId)) closePeerConnection(getLayerMap(layerId).get(peerId))
// Init var
const panner = new PannerNode(audioContext, {
panningModel: "HRTF",
distanceModel: distanceModel,
refDistance: Math.max(refDistance, 0.1),
rolloffFactor: rolloffFactor,
coneInnerAngle: 360,
coneOuterAngle: 360,
coneOuterGain: 1,
});
const gain = audioContext.createGain();
const mediaStream = new MediaStream();
const peerConnectionContainer = {
pc: pc,
panner: panner,
gain: gain,
gainValue: 1,
playerId: peerId,
username: username,
layerId: layerId,
mute: false,
loc: null,
element: null
}
// RTCICEPACKET
pc.onicecandidate = (event) => {
if (event.candidate) {
const packet = [
{
"type": "RTCIcePacket",
"value": {
"layerId": layerId,
"from": playerId,
"to": peerId,
type: 'candidate',
candidate: event.candidate
}
}
]
ws.send(JSON.stringify(packet));
console.log("Sending ICE candidate ", playerId)
}
}
getLayerMap(layerId).set(peerId, peerConnectionContainer)
pc.onicecandidateerror = event => {
console.error("Error ICE Candidate :", event);
};
pc.onerror = event => {
console.error("Error RTCPeerConnection :", event);
};
pc.ontrack = event => {
if (event.track.kind === 'audio') {
const track = event.track;
mediaStream.addTrack(track)
// Connecting things
const source = audioContext.createMediaStreamSource(mediaStream);
//source.connect(audioContext.destination)
source.connect(panner)
panner.connect(gain)
gain.connect(audioContext.destination)
// We use a dummy audio muted, because of a bug in chrome https://bugs.chromium.org/p/chromium/issues/detail?id=933677
const dummyAudio = new Audio();
dummyAudio.muted = true;
dummyAudio.srcObject = mediaStream;
console.log("Track set")
updateEveryVolume()
}
}
if (micStream) {
const finalMicStream = micDestination.stream;
pc.addTrack(finalMicStream.getAudioTracks()[0], finalMicStream);
}
async function userThing() {
// Show user
await showUser(peerConnectionContainer)
// Setup user bar
const userVolume = peerConnectionContainer.element.getElementsByClassName("user-volume").item(0);
const savedVolume = getCookie(peerConnectionContainer.playerId + '_volume');
const userVolumeValue = savedVolume ? parseFloat(savedVolume) : 100;
gain.gain.value = userVolumeValue / 100;
userVolume.value = userVolumeValue;
peerConnectionContainer.gainValue = userVolumeValue / 100;
userVolume.addEventListener('input', (e) => {
peerConnectionContainer.gainValue = parseFloat(e.target.value) / 100;
if (peerConnectionContainer.mute) return;
updateEveryVolume();
setCookie(peerConnectionContainer.playerId + '_volume', e.target.value);
});
}
userThing();
updateEveryVolume();
return pc;
}
function showUser(peerObj) {
return new Promise(async (resolve, reject) => {
let username = peerObj.username;
const element = htmlStringToElement(userTemplate)
const nameElement = element.getElementsByClassName("username").item(0)
const logoElement = element.getElementsByClassName("logo").item(0)
const muteButton = element.getElementsByClassName("muteButton").item(0)
const mutedButton = element.getElementsByClassName("mutedButton").item(0)
// Charger l'état mute et le volume depuis les cookies, ou utiliser des valeurs par défaut
const muteStatus = getCookie(peerObj.playerId + '_mute');
const volumeLevel = getCookie(peerObj.playerId + '_volume');
peerObj.mute = muteStatus === 'true';
peerObj.gain.gain.value = muteStatus === 'true' ? 0 : (volumeLevel ? parseFloat(volumeLevel) : peerObj.gainValue * voiceVolume);
// Mettre à jour l'interface en fonction de l'état mute
if (peerObj.mute) {
muteButton.classList.add('hidden');
mutedButton.classList.remove('hidden');
} else {
muteButton.classList.remove('hidden');
mutedButton.classList.add('hidden');
}
// Gestion des boutons Mute
muteButton.addEventListener('click', e => {
muteButton.classList.add('hidden');
mutedButton.classList.remove('hidden');
peerObj.mute = true;
peerObj.gain.gain.value = 0;
setCookie(peerObj.playerId + '_mute', 'true');
setCookie(peerObj.playerId + '_volume', '0');
});
mutedButton.addEventListener('click', e => {
mutedButton.classList.add('hidden');
muteButton.classList.remove('hidden');
peerObj.mute = false;
peerObj.gain.gain.value = peerObj.gainValue * voiceVolume;
setCookie(peerObj.playerId + '_mute', 'false');
setCookie(peerObj.playerId + '_volume', peerObj.gain.gain.value.toString());
});
element.id = peerObj.playerId
nameElement.textContent = username;
const uuid = peerObj.playerId;
logoElement.src = "https://cravatar.eu/avatar/" + username;
peerObj.element = element;
users.insertAdjacentElement("beforeend", element);
resolve();
})
}
function closePeerConnection(peerConnectionContainer) {
if (peerConnectionContainer == null) return
peerConnectionContainer.pc.close()
if (peerConnectionContainer.element != null) peerConnectionContainer.element.remove()
getLayerMap(peerConnectionContainer.layerId).delete(peerConnectionContainer.playerId)
}
function htmlStringToElement(htmlString) {
const tempContainer = document.createElement("div");
tempContainer.innerHTML = htmlString;
return tempContainer.firstElementChild;
}
function setCookie(name, value) {
document.cookie = name + "=" + value + ";path=/";
}
function getCookie(name) {
let nameEQ = name + "=";
let ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
function sendTrustPacket() {
const trustPacket = [{
"type": "TrustPacket",
"value": {
"token": params.get("t")
}
}]
ws.send(JSON.stringify(trustPacket))
}
async function populateMicrophoneOptions() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const audioInputDevices = devices.filter(device => device.kind === "audioinput");
microphoneSelect.innerHTML = ""; // Clear previous options
audioInputDevices.forEach(device => {
const option = document.createElement("option");
option.value = device.deviceId;
option.textContent = device.label || `Microphone ${microphoneSelect.childElementCount + 1}`;
microphoneSelect.appendChild(option);
});
} catch (error) {
console.error("Error populating microphone options:", error);
}
}
function updateEveryVolume() {
audioMap.forEach((audioObj, id) => {
audioObj.audio.volume = ambientVolume;
})
peerConnections.forEach((layerMap, layerId) => {
layerMap.forEach((peerObj, id) => {
peerObj.gain.gain.value = peerObj.gainValue * voiceVolume;
});
})
}
async function setupAudioSystem() {
const savedVoiceVolume = getCookie('voice_volume');
const savedAmbientVolume = getCookie('ambient_volume');
const savedMicThreshold = getCookie('mic_threshold');
const savedNoiseSuppression = getCookie('noiseSuppression');
const savedEchoCancellation = getCookie('echoCancellation');
voiceVolume = savedVoiceVolume ? parseFloat(savedVoiceVolume) / 100 : 1; // Valeur par défaut à 100 si pas de cookie
ambientVolume = savedAmbientVolume ? parseFloat(savedAmbientVolume) / 100 : 0.666; // Valeur par défaut à 66.6 si pas de cookie
voice.value = voiceVolume * 100;
ambient.value = ambientVolume * 100;
let micThre = savedMicThreshold ? savedMicThreshold : -80;
micThreshold.value = micThre;
if (speechEvents) speechEvents.setThreshold(micThre);
// Noise params
const noiseSupp = savedNoiseSuppression ? (savedNoiseSuppression === "true") : true;
const echoCancell = savedEchoCancellation ? (savedEchoCancellation === "true") : true;
if (noiseSupp !== noiseSuppression || echoCancell !== echoCancellation) updateMic()
noiseSuppression = noiseSupp;
echoCancellation = echoCancell;
const noiseSuppressionEl = document.getElementById('noiseSuppression');
const echoCancellationEl = document.getElementById('echoCancellation');
noiseSuppressionEl.checked = noiseSuppression;
echoCancellationEl.checked = echoCancellation;
if (proximityChat) {
voice.addEventListener('input', function () {
voiceVolume = parseFloat(this.value) / 100;
updateEveryVolume();
// Sauvegarder le nouveau volume dans les cookies
setCookie('voice_volume', this.value);
});
noiseSuppressionEl.addEventListener('change', () => {
const bool = noiseSuppressionEl.checked;
if (noiseSuppression === bool) return;
noiseSuppression = bool;
setCookie('noiseSuppression', bool);
updateMic(currentDeviceId);
});
echoCancellationEl.addEventListener('change', () => {
const bool = echoCancellationEl.checked;
if (echoCancellation === bool) return;
echoCancellation = bool;
setCookie('echoCancellation', bool);
updateMic(currentDeviceId);
});
}
ambient.addEventListener('input', function () {
ambientVolume = parseFloat(this.value) / 100;
updateEveryVolume();
// Sauvegarder le nouveau volume dans les cookies
setCookie('ambient_volume', this.value);
});
micThreshold.addEventListener('input', function () {
let newThreshold = parseFloat(this.value);
speechEvents.setThreshold(newThreshold);
console.log("Threshold set to: " + newThreshold)
// Sauvegarder le nouveau volume dans les cookies
setCookie('mic_threshold', this.value);
});
if (proximityChat)
microphoneSelect.addEventListener("change", async () => {
const selectedDeviceId = microphoneSelect.value;
updateMic(selectedDeviceId);
});
}
async function updateMic(deviceId) {
try {
if (deviceId) {
console.log("New device selected: " + deviceId)
currentDeviceId = deviceId;
}
const oldGain = micGainNode.gain.value;
console.log("OldGain: ", oldGain)
const isMuted = micStream.getTracks().at(0).enabled;
micStream = await getUserMic(deviceId);
micDestination = audioContext.createMediaStreamDestination();
micGainNode = audioContext.createGain();
audioContext.createMediaStreamSource(micStream).connect(micGainNode);
micGainNode.connect(micDestination);
audioContext.createMediaStreamSource(micStream).connect(micGainNode);
const newAudioTrack = micStream.getAudioTracks()[0];
newAudioTrack.enabled = isMuted;
// Update all
peerConnections.forEach((layerMap, id) => {
layerMap.forEach((peerObj, id) => {
const pc = peerObj.pc;
const senders = pc.getSenders();
const audioSender = senders.find(sender => sender.track.kind === 'audio');
if (audioSender) {
const currentAudioTrack = audioSender.track;
currentAudioTrack.stop();
const newTrack = micDestination.stream.getAudioTracks()[0];
audioSender.replaceTrack(newTrack);
console.log("replaceTrack", newTrack)
}
})
});
if (oldGain !== undefined) micGainNode.gain.value = oldGain;
console.log("New gain: ", micGainNode.gain.value)
} catch (error) {
console.error("Error getting selected microphone stream:", error);
}
}
function muteToggle() {
mute(!muted);
}
/**
* Mute the client
* @param bool
*/
function mute(bool) {
if (bool) {
muteButton.classList.add('hidden');
mutedButton.classList.remove('hidden');
if (micStream)
micStream.getTracks().forEach(t => {
t.enabled = false
})
else console.log("MicStream is null, this mean you browser certainly don't have access to your mic")
muted = true;
} else {
mutedButton.classList.add('hidden');
muteButton.classList.remove('hidden');
micStream.getTracks().forEach(t => {
t.enabled = true
})
muted = false;
}
const clientMutePacket =
[
{
"type": "ClientMutePacket",
"value": {
"muted": muted
}
}
]
ws.send(JSON.stringify(clientMutePacket))
}
function setupWithConsent() {
if (consent) return
consent = true;
audioContext = new (window.AudioContext || window.webkitAudioContext)();
if (proximityChat && micStream) {
micDestination = audioContext.createMediaStreamDestination();
micGainNode = audioContext.createGain();
audioContext.createMediaStreamSource(micStream).connect(micGainNode);
micGainNode.connect(micDestination);
// Mute Button
mutedButton.classList.add('hidden');
muteButton.addEventListener('click', e => {
muteToggle();
});
mutedButton.addEventListener('click', e => {
muteToggle();
});
// Hark
speechEvents = hark(micStream, {});
speechEvents.setInterval(10);
micGainNode.gain.value = 0;
const speakingHalo = document.getElementById("speakingHalo");
speechEvents.on('speaking', () => {
//console.log("speaking")
micGainNode.gain.value = 1;
speakingHalo.classList.remove("hidden");
});
speechEvents.on('stopped_speaking', () => {
//console.log("stopped_speaking")
micGainNode.gain.value = 0;
speakingHalo.classList.add("hidden");
});
speechEvents.on('volume_change', (currentdB, currentThreshold) => {
const percentage = 100 + currentdB;
micThreshold.style.background = `linear-gradient(to right, #4CAF50 0%, #e6a87c ${percentage}%, #ddd ${percentage}%, #ddd 100%)`;
});
} else {
muteButton.classList.add('hidden');
}
function connectWebSocket() {
if (!params.has("t")) {
alert("Without token the client can't connect")
console.log("Without token this will not connected to the websocket")
return
}
console.log("Starting WebSocket...")
ws = new WebSocket(wsIP);
ws.onopen = () => {
isOpen = true;
console.log("WebSocket Open")
sendTrustPacket()
setInterval(() => {
console.log("States:")
peerConnections.forEach((layerMap, k) => {
layerMap.forEach((pc, k) => {
console.log("- State: " + pc.pc.connectionState)
console.log(pc.gain.gain.value)
console.log(pc)
})
})
}, 3000)
}
ws.onmessage = (message) => {
handlePackets(message.data)
}
ws.onclose = (event) => {
console.log('Connexion WebSocket fermée. Raison :', event);
}
// Gérer les erreurs de connexion
ws.onerror = (error) => {
console.error('Erreur de connexion WebSocket :', error);
}
}
connectWebSocket()
let retryCount = 0;
let iframe = false;
const interval = setInterval(() => {
if (isOpen) {
clearInterval(interval);
return;
}
if (retryCount >= 3) {
if (!iframe)
alert("You are unable to connect to the websocket, try to refresh the page or restart your browser")
}
if (navigator.userAgent.indexOf("Firefox") !== -1 && !iframe) {
// If it's Firefox, create an iframe with a certain IP address
iframe = true;
window.open(wsIP.replace("wss", "https"), '_blank', "width=800,height=800");
alert("Try to refresh the page. Else You are on firefox and the server use a self signed certificate, so you need to accept it manually on the popup, click on advanced and click on accept, after accepting close this popup and alert. If you already accept the certificate, try refresh the page")
}
console.log("Retry to connect nb: ", retryCount)
connectWebSocket();
retryCount++;
}, 3000)
}
async function handlePackets(packetList) {
const jsonArray = JSON.parse(packetList);
for (const packetObject of jsonArray) {
const type = packetObject.type;
const value = packetObject.value;
await handlePacket(type, value)
}
}
</script>