diff --git a/.gitignore b/.gitignore index 667aaef..bceaed0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ target/ .mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ +.env ### STS ### .apt_generated diff --git a/src/main/resources/static/app.html b/src/main/resources/static/app.html index c6ef4e5..cdaf9af 100644 --- a/src/main/resources/static/app.html +++ b/src/main/resources/static/app.html @@ -4,7 +4,7 @@ - + Random Video Chat @@ -12,7 +12,7 @@ - + @@ -20,6 +20,13 @@
+ + +
Searching for a partner...
@@ -31,18 +38,22 @@
- - - - - - + + + + + +
@@ -59,7 +70,7 @@

Find New Partner?

- + diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 72bc9a0..01b2dcb 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -22,14 +22,14 @@ - + - + diff --git a/src/main/resources/static/privacy.html b/src/main/resources/static/privacy.html index 80d675e..15b2c90 100644 --- a/src/main/resources/static/privacy.html +++ b/src/main/resources/static/privacy.html @@ -9,7 +9,7 @@ - + diff --git a/src/main/resources/static/script.js b/src/main/resources/static/script.js index edd8536..137bafd 100644 --- a/src/main/resources/static/script.js +++ b/src/main/resources/static/script.js @@ -116,15 +116,24 @@ ws.onmessage = async (message) => { break; case "offer": setControlsEnabled(true); - await ensurePeerConnection(); - await peerConnection.setRemoteDescription( - new RTCSessionDescription(data.offer) - ); + + // If not in have-remote-offer, forcibly reset connection + if (!peerConnection || peerConnection.signalingState !== "have-remote-offer") { + if (peerConnection) peerConnection.close(); + await ensurePeerConnection(); + await ensureLocalMediaAndTracks(); + } + + await peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer)); await processQueuedIceCandidates(); - await ensureLocalMediaAndTracks(); - const answer = await peerConnection.createAnswer(); - await peerConnection.setLocalDescription(answer); - ws.send(JSON.stringify({ type: "answer", answer: answer })); + + if (peerConnection.signalingState === "have-remote-offer" || peerConnection.signalingState === "have-local-pranswer") { + const answer = await peerConnection.createAnswer(); + await peerConnection.setLocalDescription(answer); + ws.send(JSON.stringify({ type: "answer", answer: answer })); + } else { + console.warn("Skipping createAnswer: signalingState is", peerConnection.signalingState); + } break; case "answer": await peerConnection.setRemoteDescription( @@ -164,7 +173,7 @@ async function ensurePeerConnection() { remoteIceCandidatesQueue = []; const iceServersFromBackend = await fetchTurnConfig(); - console.log(iceServersFromBackend); + //console.log(iceServersFromBackend); peerConnection = new RTCPeerConnection({ iceServers: iceServersFromBackend @@ -464,4 +473,62 @@ async function fetchTurnConfig() { console.warn('Failed to fetch TURN config:', e); return []; } -} \ No newline at end of file +} + +const audioOutputBtn = document.getElementById("audio-output-btn"); +const audioOutputIcon = document.getElementById("audio-output-icon"); + +let isAudioStopped = false; + +// Detect and set default output +async function setDefaultAudioOutput() { + if (!navigator.mediaDevices?.enumerateDevices) return; + + try { + const devices = await navigator.mediaDevices.enumerateDevices(); + const earphones = devices.find( + (d) => + d.kind === "audiooutput" && + (d.label.toLowerCase().includes("headphone") || + d.label.toLowerCase().includes("bluetooth")) + ); + + if (typeof remoteVideo.setSinkId === "function") { + if (earphones) { + await remoteVideo.setSinkId(earphones.deviceId); + // console.log("Earphones detected - switched to earphones."); + showNotification("Earphones connected"); + audioOutputIcon.className = "fa-solid fa-headphones"; + } else { + await remoteVideo.setSinkId(""); + // console.log("Defaul to speaker."); + showNotification("Using speaker"); + audioOutputIcon.className = "fa-solid fa-volume-high"; + } + } + } catch (err) { + console.warn("Audio device detection failed:", err); + } +} + +// device connection changes +if (navigator.mediaDevices?.addEventListener) { + navigator.mediaDevices.addEventListener("devicechange", setDefaultAudioOutput); +} + +// stop/start button +audioOutputBtn.addEventListener("click", () => { + if (isAudioStopped) { + remoteVideo.muted = false; + showNotification("Audio resumed 🔊"); + audioOutputIcon.className = "fa-solid fa-volume-high"; + } else { + remoteVideo.muted = true; + showNotification("Audio stopped 🔇"); + audioOutputIcon.className = "fa-solid fa-volume-xmark"; + } + isAudioStopped = !isAudioStopped; +}); + +// on load +setDefaultAudioOutput(); diff --git a/src/main/resources/static/style.css b/src/main/resources/static/style.css index bad3708..d9e38bf 100644 --- a/src/main/resources/static/style.css +++ b/src/main/resources/static/style.css @@ -302,6 +302,20 @@ video { background-color: #000; } +.top-left-btn { + position: absolute; + top: 20px; + left: 20px; + z-index: 50; +} + +.top-right-btn { + position: absolute; + top: 20px; + right: 20px; + z-index: 50; +} + @media (max-width: 768px) { #localVideo { bottom: 85px; @@ -324,4 +338,13 @@ video { width: 95%; padding: 1.25rem; } + + .top-left-btn { + top: 10px; + left: 10px; + } + .top-right-btn { + top: 10px; + right: 10px; + } } \ No newline at end of file