Update static/js/12-calls.js
This commit is contained in:
parent
6b5cd404df
commit
6063da595c
@ -44,6 +44,20 @@ Glow.calls.ensureState = () => {
|
||||
state.ignoreOffer ||= new Map();
|
||||
state.isSettingRemoteAnswerPending ||= new Map();
|
||||
state.panelOpen ??= false;
|
||||
state.metrics ||= {
|
||||
packetLoss: null,
|
||||
uptime: null,
|
||||
latency: null,
|
||||
elapsedSeconds: 0,
|
||||
userCount: 0
|
||||
};
|
||||
state.drag ||= {
|
||||
active: false,
|
||||
x: null,
|
||||
y: null,
|
||||
offsetX: 0,
|
||||
offsetY: 0
|
||||
};
|
||||
};
|
||||
|
||||
Glow.calls.scopeLabel = invite => {
|
||||
@ -182,7 +196,8 @@ Glow.calls.upsertPeer = peer => {
|
||||
const normalized = {
|
||||
participantId,
|
||||
roomId: peer.roomId || peer.room_id || state.session?.call_id || "",
|
||||
username: peer.username || participantId
|
||||
username: peer.username || participantId,
|
||||
connectionState: peer.connectionState || "connecting"
|
||||
};
|
||||
|
||||
const index = peers.findIndex(item => item.participantId === participantId);
|
||||
@ -301,13 +316,23 @@ Glow.calls.ensurePeerConnection = async peerId => {
|
||||
};
|
||||
|
||||
pc.onconnectionstatechange = () => {
|
||||
Glow.calls.debug("pc connection state", peerId, pc.connectionState);
|
||||
const nextState = pc.connectionState || "connecting";
|
||||
Glow.calls.debug("pc connection state", peerId, nextState);
|
||||
|
||||
const peer = state.peers.find(item => item.participantId === peerId);
|
||||
if (peer) {
|
||||
peer.connectionState = nextState;
|
||||
}
|
||||
|
||||
const bad =
|
||||
pc.connectionState === "failed" ||
|
||||
pc.connectionState === "disconnected";
|
||||
nextState === "failed" ||
|
||||
nextState === "disconnected";
|
||||
if (bad) {
|
||||
Glow.showToast(`Connection issue with ${peerId}.`, "error");
|
||||
}
|
||||
|
||||
Glow.calls.renderParticipants();
|
||||
Glow.calls.updateSummaryMetrics();
|
||||
};
|
||||
|
||||
pc.onsignalingstatechange = () => {
|
||||
@ -475,6 +500,15 @@ Glow.calls.renderPendingInvites = () => {
|
||||
wrap.classList.remove("hidden");
|
||||
};
|
||||
|
||||
Glow.calls.getParticipantStateLabel = peer => {
|
||||
const state = String(peer?.connectionState || "").toLowerCase();
|
||||
if (state === "connected") return "active";
|
||||
if (state === "connecting" || state === "new") return "connecting";
|
||||
if (state === "disconnected") return "disconnected";
|
||||
if (state === "failed") return "reconnecting";
|
||||
return "joined";
|
||||
};
|
||||
|
||||
Glow.calls.renderParticipants = () => {
|
||||
if (!Glow.els.callParticipants) return;
|
||||
|
||||
@ -504,7 +538,7 @@ Glow.calls.renderParticipants = () => {
|
||||
selfRight.className = "call-participant-status";
|
||||
selfRight.textContent = state.activeCall
|
||||
? state.muted
|
||||
? "connected · muted"
|
||||
? "connected · muted"
|
||||
: "connected"
|
||||
: "idle";
|
||||
|
||||
@ -524,12 +558,16 @@ Glow.calls.renderParticipants = () => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "call-participant";
|
||||
|
||||
const left = document.createElement("div");
|
||||
left.className = "call-participant-name";
|
||||
const left = document.createElement("button");
|
||||
left.className = "call-participant-name profile-link";
|
||||
left.type = "button";
|
||||
left.textContent =
|
||||
participant.display_name || participant.username || `user ${participant.user_id}`;
|
||||
if (participant) {
|
||||
Glow.applyUserColor(left, participant);
|
||||
if (participant.username) {
|
||||
left.addEventListener("click", () => {
|
||||
Glow.openProfile?.(participant.username);
|
||||
});
|
||||
}
|
||||
|
||||
const right = document.createElement("div");
|
||||
@ -542,7 +580,9 @@ Glow.calls.renderParticipants = () => {
|
||||
);
|
||||
});
|
||||
|
||||
right.textContent = matchingPeer ? "connected" : "joined";
|
||||
const label = Glow.calls.getParticipantStateLabel(matchingPeer);
|
||||
right.textContent = label;
|
||||
right.dataset.state = label;
|
||||
|
||||
row.append(left, right);
|
||||
box.append(row);
|
||||
@ -560,13 +600,21 @@ Glow.calls.renderParticipants = () => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "call-participant";
|
||||
|
||||
const left = document.createElement("div");
|
||||
left.className = "call-participant-name";
|
||||
const left = document.createElement("button");
|
||||
left.className = "call-participant-name profile-link";
|
||||
left.type = "button";
|
||||
left.textContent = peer.username || peer.participantId;
|
||||
if (peer.username) {
|
||||
left.addEventListener("click", () => {
|
||||
Glow.openProfile?.(peer.username);
|
||||
});
|
||||
}
|
||||
|
||||
const right = document.createElement("div");
|
||||
right.className = "call-participant-status";
|
||||
right.textContent = "connecting";
|
||||
const label = Glow.calls.getParticipantStateLabel(peer);
|
||||
right.textContent = label;
|
||||
right.dataset.state = label;
|
||||
|
||||
row.append(left, right);
|
||||
box.append(row);
|
||||
@ -583,6 +631,54 @@ Glow.calls.renderParticipants = () => {
|
||||
}
|
||||
};
|
||||
|
||||
Glow.calls.updateSummaryMetrics = () => {
|
||||
Glow.calls.ensureState();
|
||||
const state = Glow.calls.state();
|
||||
|
||||
const createdAt =
|
||||
state.activeCall?.groupSession?.created_at ||
|
||||
state.session?.group_session?.created_at ||
|
||||
null;
|
||||
|
||||
let elapsedSeconds = 0;
|
||||
if (createdAt) {
|
||||
const ms = new Date(createdAt).getTime();
|
||||
if (!Number.isNaN(ms)) {
|
||||
elapsedSeconds = Math.max(0, Math.floor((Date.now() - ms) / 1000));
|
||||
}
|
||||
}
|
||||
|
||||
state.metrics.elapsedSeconds = elapsedSeconds;
|
||||
state.metrics.userCount =
|
||||
1 + Glow.calls.listOtherPeers().length;
|
||||
|
||||
if (Glow.els.callUserCountStat) {
|
||||
Glow.els.callUserCountStat.textContent = String(state.metrics.userCount);
|
||||
}
|
||||
|
||||
if (Glow.els.callElapsedStat) {
|
||||
const minutes = Math.floor(elapsedSeconds / 60);
|
||||
const seconds = elapsedSeconds % 60;
|
||||
Glow.els.callElapsedStat.textContent =
|
||||
minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
||||
}
|
||||
|
||||
if (Glow.els.callLatencyStat) {
|
||||
Glow.els.callLatencyStat.textContent =
|
||||
state.metrics.latency == null ? "—" : `${state.metrics.latency} ms`;
|
||||
}
|
||||
|
||||
if (Glow.els.callPacketLossStat) {
|
||||
Glow.els.callPacketLossStat.textContent =
|
||||
state.metrics.packetLoss == null ? "—" : `${state.metrics.packetLoss}%`;
|
||||
}
|
||||
|
||||
if (Glow.els.callUptimeStat) {
|
||||
Glow.els.callUptimeStat.textContent =
|
||||
state.metrics.uptime == null ? "—" : `${state.metrics.uptime}%`;
|
||||
}
|
||||
};
|
||||
|
||||
Glow.calls.connectWebSocket = async () => {
|
||||
Glow.calls.ensureState();
|
||||
const state = Glow.calls.state();
|
||||
@ -604,6 +700,7 @@ Glow.calls.connectWebSocket = async () => {
|
||||
state.reconnecting = false;
|
||||
state.joining = false;
|
||||
Glow.calls.setStatusText("Connected");
|
||||
Glow.calls.updateSummaryMetrics();
|
||||
settled = true;
|
||||
resolve();
|
||||
};
|
||||
@ -641,6 +738,7 @@ Glow.calls.connectWebSocket = async () => {
|
||||
if (signal.type === "joined") {
|
||||
state.selfPeerId = signal.participantId;
|
||||
Glow.calls.setStatusText("Call connected");
|
||||
Glow.calls.updateSummaryMetrics();
|
||||
Glow.renderCallUi();
|
||||
Glow.calls.renderParticipants();
|
||||
return;
|
||||
@ -651,8 +749,10 @@ Glow.calls.connectWebSocket = async () => {
|
||||
Glow.calls.upsertPeer({
|
||||
participantId: signal.participantId,
|
||||
roomId: signal.roomId,
|
||||
username: signal.username || signal.participantId
|
||||
username: signal.username || signal.participantId,
|
||||
connectionState: "connecting"
|
||||
});
|
||||
Glow.calls.updateSummaryMetrics();
|
||||
Glow.renderCallUi();
|
||||
Glow.calls.renderParticipants();
|
||||
|
||||
@ -668,6 +768,7 @@ Glow.calls.connectWebSocket = async () => {
|
||||
|
||||
if (signal.type === "participant-left") {
|
||||
Glow.calls.removePeer(signal.participantId);
|
||||
Glow.calls.updateSummaryMetrics();
|
||||
Glow.renderCallUi();
|
||||
Glow.calls.renderParticipants();
|
||||
Glow.showToast("Peer left call.");
|
||||
@ -697,7 +798,8 @@ Glow.calls.connectWebSocket = async () => {
|
||||
Glow.calls.upsertPeer({
|
||||
participantId: from,
|
||||
roomId: signal.roomId,
|
||||
username: signal.username || from
|
||||
username: signal.username || from,
|
||||
connectionState: "connecting"
|
||||
});
|
||||
Glow.calls.renderParticipants();
|
||||
|
||||
@ -893,6 +995,13 @@ Glow.calls.resetAll = () => {
|
||||
can_invite: false
|
||||
};
|
||||
state.panelOpen = false;
|
||||
state.metrics = {
|
||||
packetLoss: null,
|
||||
uptime: null,
|
||||
latency: null,
|
||||
elapsedSeconds: 0,
|
||||
userCount: 0
|
||||
};
|
||||
|
||||
Glow.calls.debug("resetAll complete");
|
||||
};
|
||||
@ -927,7 +1036,7 @@ Glow.calls.scheduleReconnect = () => {
|
||||
|
||||
clearTimeout(state.reconnectTimer);
|
||||
state.reconnecting = true;
|
||||
Glow.calls.setStatusText("Reconnecting…");
|
||||
Glow.calls.setStatusText("Reconnecting…");
|
||||
Glow.renderCallUi();
|
||||
|
||||
state.reconnectTimer = setTimeout(async () => {
|
||||
@ -938,7 +1047,7 @@ Glow.calls.scheduleReconnect = () => {
|
||||
|
||||
Glow.calls.debug("reconnect attempt", scope);
|
||||
Glow.calls.resetTransportOnly();
|
||||
Glow.calls.setStatusText("Reconnecting…");
|
||||
Glow.calls.setStatusText("Reconnecting…");
|
||||
state.session = await Glow.calls.fetchGroupCallSession(scope);
|
||||
state.permissions = state.session.permissions || state.permissions;
|
||||
state.scope = state.session.scope || state.scope;
|
||||
@ -969,6 +1078,7 @@ Glow.calls.refreshBackendParticipants = async () => {
|
||||
Glow.state.calls.permissions = data.permissions || Glow.state.calls.permissions;
|
||||
Glow.state.calls.scope = data.scope || Glow.state.calls.scope;
|
||||
Glow.calls.renderParticipants();
|
||||
Glow.calls.updateSummaryMetrics();
|
||||
Glow.renderCallUi();
|
||||
Glow.calls.debug("backend participants refreshed", data);
|
||||
} catch (error) {
|
||||
@ -1039,15 +1149,15 @@ Glow.renderCallUi = () => {
|
||||
if (!busy) {
|
||||
statusText = "Idle";
|
||||
} else if (state.joining) {
|
||||
statusText = "Joining call…";
|
||||
statusText = "Joining call…";
|
||||
} else if (state.reconnecting) {
|
||||
statusText = "Reconnecting…";
|
||||
statusText = "Reconnecting…";
|
||||
} else if (!Glow.calls.wsReady()) {
|
||||
statusText = "Connecting…";
|
||||
statusText = "Connecting…";
|
||||
} else if (peerCount > 0) {
|
||||
statusText = `Group call active · ${peerCount} peer(s)`;
|
||||
statusText = `Group call active · ${peerCount} peer(s)`;
|
||||
} else {
|
||||
statusText = "Call open · waiting for others";
|
||||
statusText = "Call open · waiting for others";
|
||||
}
|
||||
|
||||
Glow.els.callStatus.textContent = statusText;
|
||||
@ -1083,11 +1193,77 @@ Glow.renderCallUi = () => {
|
||||
Glow.els.callParticipants.classList.toggle("hidden", !overlayVisible);
|
||||
}
|
||||
|
||||
Glow.calls.updateSummaryMetrics();
|
||||
Glow.calls.renderParticipants();
|
||||
Glow.calls.renderPendingInvites();
|
||||
Glow.calls.renderHeaderCallChip();
|
||||
};
|
||||
|
||||
Glow.calls.applyPanelPosition = () => {
|
||||
const overlay = Glow.els.callOverlay;
|
||||
const drag = Glow.state.calls?.drag;
|
||||
if (!overlay || !drag) return;
|
||||
if (!Number.isFinite(drag.x) || !Number.isFinite(drag.y)) return;
|
||||
|
||||
overlay.style.left = `${drag.x}px`;
|
||||
overlay.style.top = `${drag.y}px`;
|
||||
overlay.style.right = "auto";
|
||||
overlay.style.bottom = "auto";
|
||||
overlay.style.alignItems = "flex-start";
|
||||
overlay.style.justifyContent = "flex-start";
|
||||
};
|
||||
|
||||
Glow.calls.loadPanelPosition = () => {
|
||||
const x = Number(localStorage.getItem("glow_call_panel_x"));
|
||||
const y = Number(localStorage.getItem("glow_call_panel_y"));
|
||||
if (!Number.isFinite(x) || !Number.isFinite(y)) return;
|
||||
Glow.state.calls.drag.x = x;
|
||||
Glow.state.calls.drag.y = y;
|
||||
Glow.calls.applyPanelPosition();
|
||||
};
|
||||
|
||||
Glow.calls.savePanelPosition = () => {
|
||||
const drag = Glow.state.calls?.drag;
|
||||
if (!drag) return;
|
||||
if (!Number.isFinite(drag.x) || !Number.isFinite(drag.y)) return;
|
||||
localStorage.setItem("glow_call_panel_x", String(drag.x));
|
||||
localStorage.setItem("glow_call_panel_y", String(drag.y));
|
||||
};
|
||||
|
||||
Glow.calls.beginDrag = event => {
|
||||
Glow.calls.ensureState();
|
||||
const overlay = Glow.els.callOverlay;
|
||||
if (!overlay) return;
|
||||
|
||||
const rect = overlay.getBoundingClientRect();
|
||||
Glow.state.calls.drag.active = true;
|
||||
Glow.state.calls.drag.x = rect.left;
|
||||
Glow.state.calls.drag.y = rect.top;
|
||||
Glow.state.calls.drag.offsetX = event.clientX - rect.left;
|
||||
Glow.state.calls.drag.offsetY = event.clientY - rect.top;
|
||||
};
|
||||
|
||||
Glow.calls.onDrag = event => {
|
||||
const drag = Glow.state.calls?.drag;
|
||||
const overlay = Glow.els.callOverlay;
|
||||
if (!drag?.active || !overlay) return;
|
||||
|
||||
const maxX = Math.max(0, window.innerWidth - overlay.offsetWidth);
|
||||
const maxY = Math.max(0, window.innerHeight - overlay.offsetHeight);
|
||||
|
||||
drag.x = Math.min(maxX, Math.max(0, event.clientX - drag.offsetX));
|
||||
drag.y = Math.min(maxY, Math.max(0, event.clientY - drag.offsetY));
|
||||
|
||||
Glow.calls.applyPanelPosition();
|
||||
};
|
||||
|
||||
Glow.calls.endDrag = () => {
|
||||
const drag = Glow.state.calls?.drag;
|
||||
if (!drag) return;
|
||||
drag.active = false;
|
||||
Glow.calls.savePanelPosition();
|
||||
};
|
||||
|
||||
Glow.startCall = async () => {
|
||||
Glow.calls.ensureState();
|
||||
|
||||
@ -1105,7 +1281,7 @@ Glow.startCall = async () => {
|
||||
|
||||
try {
|
||||
Glow.state.calls.joining = true;
|
||||
Glow.calls.setStatusText("Preparing call…");
|
||||
Glow.calls.setStatusText("Preparing call…");
|
||||
|
||||
const session = await Glow.calls.fetchGroupCallSession(scope);
|
||||
Glow.state.calls.session = session;
|
||||
@ -1129,14 +1305,15 @@ Glow.startCall = async () => {
|
||||
Glow.state.calls.panelOpen = true;
|
||||
|
||||
Glow.openCallPanel();
|
||||
Glow.calls.setStatusText("Connecting microphone…");
|
||||
Glow.calls.setStatusText("Connecting microphone…");
|
||||
await Glow.calls.ensureLocalAudio();
|
||||
Glow.calls.setStatusText("Joining call…");
|
||||
Glow.calls.setStatusText("Joining call…");
|
||||
await Glow.apiJoinGroupCall(session.group_session.id);
|
||||
await Glow.calls.connectWebSocket();
|
||||
Glow.calls.startParticipantSync();
|
||||
Glow.calls.startHeartbeat();
|
||||
Glow.state.calls.joining = false;
|
||||
Glow.calls.updateSummaryMetrics();
|
||||
Glow.renderCallUi();
|
||||
Glow.showToast("Group call ready.");
|
||||
} catch (error) {
|
||||
@ -1164,7 +1341,7 @@ Glow.leaveCall = async () => {
|
||||
if (!activeCall) return;
|
||||
|
||||
Glow.state.calls.leaving = true;
|
||||
Glow.calls.setStatusText("Leaving call…");
|
||||
Glow.calls.setStatusText("Leaving call…");
|
||||
|
||||
try {
|
||||
Glow.calls.sendSignal({ type: "hangup" });
|
||||
@ -1199,7 +1376,7 @@ Glow.endCallForEveryone = async () => {
|
||||
if (!values) return;
|
||||
|
||||
try {
|
||||
Glow.calls.setStatusText("Ending call…");
|
||||
Glow.calls.setStatusText("Ending call…");
|
||||
await Glow.apiEndGroupCall(activeCall.groupSession.id);
|
||||
Glow.showToast("Call ended.");
|
||||
await Glow.endLocalCallUi();
|
||||
@ -1331,7 +1508,7 @@ Glow.calls.openIncomingInviteDialog = async invite => {
|
||||
}
|
||||
|
||||
Glow.state.calls.joining = true;
|
||||
Glow.calls.setStatusText("Joining call…");
|
||||
Glow.calls.setStatusText("Joining call…");
|
||||
|
||||
const session = await Glow.calls.acceptInvite(invite.id);
|
||||
|
||||
@ -1356,16 +1533,17 @@ Glow.calls.openIncomingInviteDialog = async invite => {
|
||||
Glow.state.calls.panelOpen = true;
|
||||
|
||||
Glow.openCallPanel();
|
||||
Glow.calls.setStatusText("Connecting microphone…");
|
||||
Glow.calls.setStatusText("Connecting microphone…");
|
||||
await Glow.calls.ensureLocalAudio();
|
||||
if (session.group_session?.id) {
|
||||
Glow.calls.setStatusText("Joining call…");
|
||||
Glow.calls.setStatusText("Joining call…");
|
||||
await Glow.apiJoinGroupCall(session.group_session.id);
|
||||
}
|
||||
await Glow.calls.connectWebSocket();
|
||||
Glow.calls.startParticipantSync();
|
||||
Glow.calls.startHeartbeat();
|
||||
Glow.state.calls.joining = false;
|
||||
Glow.calls.updateSummaryMetrics();
|
||||
Glow.renderCallUi();
|
||||
Glow.showToast("Joined group call.");
|
||||
} catch (error) {
|
||||
@ -1429,3 +1607,29 @@ Glow.els.endCallButton?.addEventListener("click", () => {
|
||||
Glow.els.addToCallButton?.addEventListener("click", () => {
|
||||
Glow.openAddToCallDialog?.();
|
||||
});
|
||||
|
||||
Glow.els.callDragHandle?.addEventListener("mousedown", event => {
|
||||
event.preventDefault();
|
||||
Glow.calls.beginDrag(event);
|
||||
});
|
||||
|
||||
document.addEventListener("mousemove", event => {
|
||||
Glow.calls.onDrag?.(event);
|
||||
});
|
||||
|
||||
document.addEventListener("mouseup", () => {
|
||||
Glow.calls.endDrag?.();
|
||||
});
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
if (Glow.state.calls?.panelOpen) {
|
||||
Glow.calls.applyPanelPosition?.();
|
||||
}
|
||||
});
|
||||
|
||||
Glow.calls.loadPanelPosition?.();
|
||||
setInterval(() => {
|
||||
if (Glow.state.calls?.activeCall) {
|
||||
Glow.calls.updateSummaryMetrics?.();
|
||||
}
|
||||
}, 1000);
|
||||
Loading…
x
Reference in New Issue
Block a user