feat(ui): add Join Video button — joins call and auto-starts camera
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:
@@ -43,12 +43,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Voice join FAB -->
|
<!-- Voice / Video join FABs -->
|
||||||
<div class="lobby-fab-row">
|
<div class="lobby-fab-row">
|
||||||
<button id="join-voice-btn" class="fab" title="Join Voice Chat">
|
<button id="join-voice-btn" class="fab" title="Join Voice Chat">
|
||||||
<span class="fab-icon">🎧</span>
|
<span class="fab-icon">🎧</span>
|
||||||
<span class="fab-label">Join Voice</span>
|
<span class="fab-label">Join Voice</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button id="join-video-btn" class="fab fab-video" title="Join with Video">
|
||||||
|
<span class="fab-icon">📹</span>
|
||||||
|
<span class="fab-label">Join Video</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Incoming call banner -->
|
<!-- Incoming call banner -->
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ const lobbyFp = document.getElementById("lobby-fp")!;
|
|||||||
const lobbyUserList = document.getElementById("lobby-user-list")!;
|
const lobbyUserList = document.getElementById("lobby-user-list")!;
|
||||||
const lobbyUserCount = document.getElementById("lobby-user-count")!;
|
const lobbyUserCount = document.getElementById("lobby-user-count")!;
|
||||||
const joinVoiceBtn = document.getElementById("join-voice-btn")!;
|
const joinVoiceBtn = document.getElementById("join-voice-btn")!;
|
||||||
|
const joinVideoBtn = document.getElementById("join-video-btn")!;
|
||||||
const incomingBanner = document.getElementById("incoming-call-banner")!;
|
const incomingBanner = document.getElementById("incoming-call-banner")!;
|
||||||
const incomingCallerName = document.getElementById("incoming-caller-name")!;
|
const incomingCallerName = document.getElementById("incoming-caller-name")!;
|
||||||
const incomingIdenticon = document.getElementById("incoming-identicon")!;
|
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) {
|
function enterVoice(isDirect: boolean) {
|
||||||
inVoice = true;
|
inVoice = true;
|
||||||
const s = loadSettings();
|
const s = loadSettings();
|
||||||
joinVoiceBtn.classList.add("hidden");
|
joinVoiceBtn.classList.add("hidden");
|
||||||
|
joinVideoBtn.classList.add("hidden");
|
||||||
voiceDrawer.classList.remove("hidden");
|
voiceDrawer.classList.remove("hidden");
|
||||||
vdRoom.textContent = isDirect && directCallPeer
|
vdRoom.textContent = isDirect && directCallPeer
|
||||||
? (directCallPeer.alias || directCallPeer.fingerprint.substring(0, 16))
|
? (directCallPeer.alias || directCallPeer.fingerprint.substring(0, 16))
|
||||||
@@ -429,6 +460,7 @@ function leaveVoice() {
|
|||||||
pendingCallId = null;
|
pendingCallId = null;
|
||||||
voiceDrawer.classList.add("hidden");
|
voiceDrawer.classList.add("hidden");
|
||||||
joinVoiceBtn.classList.remove("hidden");
|
joinVoiceBtn.classList.remove("hidden");
|
||||||
|
joinVideoBtn.classList.remove("hidden");
|
||||||
vdLevelBar.style.width = "0%";
|
vdLevelBar.style.width = "0%";
|
||||||
if (statusInterval) { clearInterval(statusInterval); statusInterval = null; }
|
if (statusInterval) { clearInterval(statusInterval); statusInterval = null; }
|
||||||
stopCamera();
|
stopCamera();
|
||||||
|
|||||||
@@ -204,6 +204,16 @@ body {
|
|||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
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 {
|
.fab {
|
||||||
|
|||||||
Reference in New Issue
Block a user