feat(ui): add Join Video button — joins call and auto-starts camera
Some checks failed
Mirror to GitHub / mirror (push) Failing after 39s
Build Release Binaries / build-amd64 (push) Failing after 3m25s

Blue FAB alongside Join Voice; click handler connects then calls
startCamera() so video is active from the moment the call starts.
Cam button inside drawer still toggles camera after joining either way.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-05-25 16:39:27 +04:00
parent 7fd66be6c8
commit c41ced53e1
3 changed files with 47 additions and 1 deletions

View File

@@ -43,12 +43,16 @@
</div>
</div>
<!-- Voice join FAB -->
<!-- Voice / Video join FABs -->
<div class="lobby-fab-row">
<button id="join-voice-btn" class="fab" title="Join Voice Chat">
<span class="fab-icon">&#x1F3A7;</span>
<span class="fab-label">Join Voice</span>
</button>
<button id="join-video-btn" class="fab fab-video" title="Join with Video">
<span class="fab-icon">&#x1F4F9;</span>
<span class="fab-label">Join Video</span>
</button>
</div>
<!-- Incoming call banner -->

View File

@@ -62,6 +62,7 @@ const lobbyFp = document.getElementById("lobby-fp")!;
const lobbyUserList = document.getElementById("lobby-user-list")!;
const lobbyUserCount = document.getElementById("lobby-user-count")!;
const joinVoiceBtn = document.getElementById("join-voice-btn")!;
const joinVideoBtn = document.getElementById("join-video-btn")!;
const incomingBanner = document.getElementById("incoming-call-banner")!;
const incomingCallerName = document.getElementById("incoming-caller-name")!;
const incomingIdenticon = document.getElementById("incoming-identicon")!;
@@ -396,10 +397,40 @@ joinVoiceBtn.addEventListener("click", async () => {
}
});
joinVideoBtn.addEventListener("click", async () => {
if (inVoice || connectPending) return;
const relay = getRelay();
const s = loadSettings();
if (!relay) { showToast("No relay configured"); return; }
connectPending = true;
const origText = joinVideoBtn.textContent;
joinVideoBtn.textContent = "Connecting…";
(joinVideoBtn as HTMLButtonElement).disabled = true;
try {
await connectWithTimeout({
relay: relay.address,
room: s.room || "general",
alias: s.alias || "",
osAec: s.osAec,
quality: s.quality || "auto",
});
enterVoice(false);
startCamera();
} catch (e: any) {
console.error("connect failed:", e);
showToast(`Join failed: ${errorMessage(e)}`);
} finally {
connectPending = false;
joinVideoBtn.textContent = origText;
(joinVideoBtn as HTMLButtonElement).disabled = false;
}
});
function enterVoice(isDirect: boolean) {
inVoice = true;
const s = loadSettings();
joinVoiceBtn.classList.add("hidden");
joinVideoBtn.classList.add("hidden");
voiceDrawer.classList.remove("hidden");
vdRoom.textContent = isDirect && directCallPeer
? (directCallPeer.alias || directCallPeer.fingerprint.substring(0, 16))
@@ -429,6 +460,7 @@ function leaveVoice() {
pendingCallId = null;
voiceDrawer.classList.add("hidden");
joinVoiceBtn.classList.remove("hidden");
joinVideoBtn.classList.remove("hidden");
vdLevelBar.style.width = "0%";
if (statusInterval) { clearInterval(statusInterval); statusInterval = null; }
stopCamera();

View File

@@ -204,6 +204,16 @@ body {
padding: 12px 0;
display: flex;
justify-content: center;
gap: 12px;
}
.fab-video {
background: #3b82f6;
box-shadow: 0 4px 16px rgba(59, 130, 246, 0.3);
}
.fab-video:hover {
box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4);
}
.fab {