forked from olcxjas-softworks/LarpixClient
fix group call popup & improve vc topbar design
This commit is contained in:
parent
135fbf23ca
commit
88dde78581
10 changed files with 286 additions and 56 deletions
141
webroot/voice.js
141
webroot/voice.js
|
|
@ -44,13 +44,48 @@ function getStoredVolume(type, clientId) {
|
|||
return v === undefined ? 100 : v;
|
||||
}
|
||||
|
||||
let voiceConnectedAt = null;
|
||||
let voiceTimerInterval = null;
|
||||
let voicePingMs = null;
|
||||
|
||||
function formatVoiceDuration(ms) {
|
||||
let totalSec = Math.floor(ms / 1000);
|
||||
let h = Math.floor(totalSec / 3600);
|
||||
let m = Math.floor((totalSec % 3600) / 60);
|
||||
let s = totalSec % 60;
|
||||
let mm = String(m).padStart(2, "0"), ss = String(s).padStart(2, "0");
|
||||
return h > 0 ? `${h}:${mm}:${ss}` : `${m}:${ss}`;
|
||||
}
|
||||
|
||||
function updateVoicePing() {
|
||||
let pingEl = document.getElementById("voice-ping");
|
||||
if (pingEl) pingEl.innerText = voicePingMs !== null ? `${voicePingMs} ms` : "";
|
||||
}
|
||||
|
||||
function setVoiceStatus(state) {
|
||||
let el = document.getElementById("voice-status");
|
||||
if (!el) return;
|
||||
el.classList.toggle("connected", state === "connected");
|
||||
el.classList.toggle("connecting", state !== "connected");
|
||||
el.classList.toggle("connecting", state === "connecting");
|
||||
el.classList.toggle("disconnected", state === "disconnected");
|
||||
|
||||
let textEl = el.querySelector(".voice-status-text");
|
||||
if (textEl) textEl.innerText = processBlah(state === "connected" ? "status.voice.connected" : "status.voice.connecting");
|
||||
if (state === "connected") {
|
||||
if (voiceConnectedAt === null) voiceConnectedAt = Date.now();
|
||||
if (!voiceTimerInterval) {
|
||||
voiceTimerInterval = setInterval(() => {
|
||||
let tickEl = document.querySelector("#voice-status .voice-status-text");
|
||||
if (tickEl && voiceConnectedAt !== null) tickEl.innerText = formatVoiceDuration(Date.now() - voiceConnectedAt);
|
||||
}, 1000);
|
||||
}
|
||||
if (textEl) textEl.innerText = formatVoiceDuration(Date.now() - voiceConnectedAt);
|
||||
updateVoicePing();
|
||||
} else {
|
||||
if (voiceTimerInterval) { clearInterval(voiceTimerInterval); voiceTimerInterval = null; }
|
||||
voiceConnectedAt = null;
|
||||
if (state === "disconnected") { voicePingMs = null; updateVoicePing(); }
|
||||
if (textEl) textEl.innerText = processBlah(state === "disconnected" ? "status.voice.disconnected" : "status.voice.connecting");
|
||||
}
|
||||
}
|
||||
|
||||
function setTileSpeaking(clientId, speaking) {
|
||||
|
|
@ -208,6 +243,7 @@ async function startVoiceSession(serverUrl) {
|
|||
showVoiceUI();
|
||||
updateVoiceUIUsers();
|
||||
setVoiceStatus("connecting");
|
||||
updateVoiceChatTitle();
|
||||
|
||||
try {
|
||||
const wsUrl = new URL(serverUrl);
|
||||
|
|
@ -234,8 +270,11 @@ async function startVoiceSession(serverUrl) {
|
|||
try {
|
||||
const baseUrl = currentVoiceWs.url.split('/ws')[0];
|
||||
const httpUrl = baseUrl.replace('ws:', 'http:').replace('wss:', 'https:');
|
||||
const t0 = performance.now();
|
||||
const res = await fetch(`${httpUrl}/room/users?room=${currentVoiceCallId}`);
|
||||
const usersRaw = await res.json();
|
||||
voicePingMs = Math.round(performance.now() - t0);
|
||||
updateVoicePing();
|
||||
const users = usersRaw.map(u => String(u));
|
||||
let changed = (users.length !== voiceActiveUsers.length);
|
||||
for(let u of users) if(!voiceActiveUsers.includes(u)) changed = true;
|
||||
|
|
@ -485,23 +524,46 @@ function handleCallUpdate(data) {
|
|||
}
|
||||
|
||||
async function resolveCallerInfo(roomId) {
|
||||
let name = window.dmUsernameCache ? window.dmUsernameCache[roomId] : null;
|
||||
if (name && name.includes(':')) name = name.split(':')[0];
|
||||
if (!name && window.fetchAsync) {
|
||||
if (window.fetchEncrypted) {
|
||||
try {
|
||||
let fetched = await window.fetchAsync(`${window.url}/idtoname?id=${roomId}`);
|
||||
if (fetched && !fetched.startsWith("error:")) {
|
||||
name = fetched.includes(':') ? fetched.split(':')[0] : fetched;
|
||||
if (window.dmUsernameCache) window.dmUsernameCache[roomId] = name;
|
||||
let infoPayload = await fetchEncrypted("group/info", roomId);
|
||||
if (infoPayload && infoPayload.startsWith("{")) {
|
||||
let info = JSON.parse(infoPayload);
|
||||
if (info.string2) {
|
||||
let pfpUrl = null;
|
||||
if (window.getGroupAvatarUrl) {
|
||||
try { pfpUrl = await getGroupAvatarUrl(roomId, info.string2); } catch (e) {}
|
||||
}
|
||||
return { name: info.string2, pfpUrl };
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
let pfpUrl = window.dmPfpCache ? window.dmPfpCache[roomId] : null;
|
||||
if (!pfpUrl && window.getAvatarUrl) {
|
||||
try { pfpUrl = await window.getAvatarUrl(roomId, name || "User"); } catch (e) {}
|
||||
if (pfpUrl && window.dmPfpCache) window.dmPfpCache[roomId] = pfpUrl;
|
||||
|
||||
let targetId = String(roomId);
|
||||
let parts = targetId.split('_');
|
||||
let myId = `${window.id};${window.host}`;
|
||||
if (parts.length === 2 && (parts[0] === myId || parts[1] === myId)) {
|
||||
targetId = parts[0] === myId ? parts[1] : parts[0];
|
||||
}
|
||||
return { name: name || processBlah("title.unknown"), pfpUrl };
|
||||
|
||||
let name = window.dmUsernameCache ? window.dmUsernameCache[targetId] : null;
|
||||
if (!name && window.fetchAsync) {
|
||||
try {
|
||||
let fetched = await window.fetchAsync(`${window.url}/idtoname?id=${targetId}`);
|
||||
if (fetched && !fetched.startsWith("error:")) {
|
||||
name = fetched;
|
||||
if (window.dmUsernameCache) window.dmUsernameCache[targetId] = fetched;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
let pfpUrl = window.dmPfpCache ? window.dmPfpCache[targetId] : null;
|
||||
if (!pfpUrl && window.getAvatarUrl) {
|
||||
try { pfpUrl = await window.getAvatarUrl(targetId, name || "User"); } catch (e) {}
|
||||
if (pfpUrl && window.dmPfpCache) window.dmPfpCache[targetId] = pfpUrl;
|
||||
}
|
||||
return { name: (name ? name.split(':')[0] : null) || processBlah("title.unknown"), pfpUrl };
|
||||
}
|
||||
|
||||
function showIncomingCallUI(roomId) {
|
||||
|
|
@ -600,6 +662,39 @@ async function handleRoomSignal(data) {
|
|||
}
|
||||
}
|
||||
|
||||
async function resolveVoiceChatTitle() {
|
||||
try {
|
||||
if (currentVoiceCallType === 'groups') {
|
||||
if (!currentVoiceCallId || !window.fetchEncrypted) return null;
|
||||
let infoPayload = await fetchEncrypted("group/info", currentVoiceCallId);
|
||||
if (infoPayload && infoPayload.startsWith("{")) {
|
||||
return JSON.parse(infoPayload).string2 || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (!currentVoiceCallId) return null;
|
||||
let parts = String(currentVoiceCallId).split('_');
|
||||
let myId = `${window.id};${window.host}`;
|
||||
let targetId = parts[0] === myId ? parts[1] : parts[0];
|
||||
if (!targetId) return null;
|
||||
let name = window.dmUsernameCache ? window.dmUsernameCache[targetId] : null;
|
||||
if (!name && window.fetchAsync) {
|
||||
name = await window.fetchAsync(`${window.url}/idtoname?id=${targetId}`);
|
||||
if (name && !name.startsWith("error:") && window.dmUsernameCache) window.dmUsernameCache[targetId] = name;
|
||||
}
|
||||
if (!name || name.startsWith("error:")) return null;
|
||||
return name.split(':')[0];
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function updateVoiceChatTitle() {
|
||||
let name = await resolveVoiceChatTitle();
|
||||
let el = document.querySelector("#voice-header .voice-title span");
|
||||
if (el && name) el.innerText = name;
|
||||
}
|
||||
|
||||
function showVoiceUI() {
|
||||
if (voiceUiContainer) return;
|
||||
|
||||
|
|
@ -614,6 +709,7 @@ function showVoiceUI() {
|
|||
</div>
|
||||
<div id="voice-status" class="connecting">
|
||||
<span class="status-dot"></span>
|
||||
<span class="status-ping" id="voice-ping"></span>
|
||||
<span class="voice-status-text">${processBlah("status.voice.connecting")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -834,8 +930,8 @@ function updateVoiceUIUsers() {
|
|||
}
|
||||
});
|
||||
}
|
||||
if (authorName && authorName.includes(':')) authorName = authorName.split(':')[0];
|
||||
if (!authorName && u === String(window.id)) authorName = window.username;
|
||||
let resolvedName = authorName && authorName !== u ? authorName : null;
|
||||
if (!resolvedName && u === String(window.id) && window.username) resolvedName = window.host ? `${window.username}:${window.host}` : window.username;
|
||||
|
||||
let avatarRes = window.dmPfpCache ? window.dmPfpCache[u] : null;
|
||||
let avatarUrl = avatarRes ? avatarRes : "assets/default_avatar.png";
|
||||
|
|
@ -852,7 +948,7 @@ function updateVoiceUIUsers() {
|
|||
|
||||
div.style.backgroundImage = `url('${avatarUrl.replace(/'/g, "\\'")}')`;
|
||||
let nameEl = document.getElementById(`voice-name-${u}`);
|
||||
if (nameEl) nameEl.innerText = authorName || u;
|
||||
if (nameEl) nameEl.innerText = resolvedName ? `${resolvedName} (${u})` : String(u);
|
||||
}
|
||||
|
||||
Array.from(container.children).forEach(child => {
|
||||
|
|
@ -890,10 +986,13 @@ function updateVoiceUIUsers() {
|
|||
}
|
||||
|
||||
let authorName = window.dmUsernameCache ? window.dmUsernameCache[clientId] : null;
|
||||
if (authorName && authorName.includes(':')) authorName = authorName.split(':')[0];
|
||||
|
||||
if (!authorName && clientId === String(window.id) && window.username) authorName = window.host ? `${window.username}:${window.host}` : window.username;
|
||||
|
||||
let streamLabel = processBlah(type == '1' ? "label.stream.cam" : "label.stream.screen");
|
||||
let baseName = authorName && authorName !== clientId ? `${authorName} (${clientId})` : clientId;
|
||||
|
||||
let nameEl = document.getElementById(`voice-stream-name-${decoderId}`);
|
||||
if (nameEl) nameEl.innerText = `${authorName || clientId} (${processBlah(type == '1' ? "label.stream.cam" : "label.stream.screen")})`;
|
||||
if (nameEl) nameEl.innerText = `${baseName} · ${streamLabel}`;
|
||||
}
|
||||
|
||||
Array.from(container.children).forEach(child => {
|
||||
|
|
@ -1048,6 +1147,8 @@ function toggleVoiceMic() {
|
|||
}
|
||||
|
||||
function endVoiceSession() {
|
||||
setVoiceStatus("disconnected");
|
||||
|
||||
if (currentVoiceCallId) {
|
||||
let payload = JSON.stringify({ string1: currentVoiceCallType, string2: currentVoiceCallId });
|
||||
if (window.fetchEncrypted) window.fetchEncrypted("room/voice/leave", payload).catch(()=>{});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue