diff --git a/kkkmeet-server.el b/kkkmeet-server.el index b9fd29a..d3518e3 100644 --- a/kkkmeet-server.el +++ b/kkkmeet-server.el @@ -180,19 +180,21 @@ When KEEP-OPEN is nil, close PROCESS after writing." (defun kkkmeet-server--remove-peer (process) "Remove PROCESS from its room and notify remaining peers." (let ((room-id (process-get process :kkkmeet-room-id)) - (peer-id (process-get process :kkkmeet-peer-id))) + (peer-id (process-get process :kkkmeet-peer-id)) + (name (or (process-get process :kkkmeet-name) "A participant"))) (when (and room-id peer-id) (let ((room (gethash room-id kkkmeet-server--rooms))) (when room (remhash peer-id room) (kkkmeet-server--broadcast - room-id peer-id `((type . "peer-left") (peerId . ,peer-id))) + room-id peer-id `((type . "peer-left") (peerId . ,peer-id) (name . ,name))) (when (equal (alist-get 'peerId (gethash room-id kkkmeet-server--presentation-states)) peer-id) (remhash room-id kkkmeet-server--presentation-states) (kkkmeet-server--broadcast room-id peer-id `((type . "presentation-state") (from . ,peer-id) + (name . ,name) (sharing . :false)))) (when (= (hash-table-count room) 0) (remhash room-id kkkmeet-server--rooms) diff --git a/public/app.js b/public/app.js index 2064388..15a2ce3 100644 --- a/public/app.js +++ b/public/app.js @@ -10,6 +10,7 @@ const nameDialogSubmit = document.querySelector("#name-dialog-submit"); const initialNameForm = document.querySelector("#initial-name-form"); const initialNameInput = document.querySelector("#initial-name-input"); const statusText = document.querySelector("#status"); +const activityStatusText = document.querySelector("#activity-status"); const youtubeForm = document.querySelector("#youtube-form"); const youtubeInput = document.querySelector("#youtube-input"); const youtubeShell = document.querySelector("#youtube-shell"); @@ -73,6 +74,7 @@ let youtubeReadyResolvers = []; let watchApplySequence = 0; let youtubePlaybackRequestId = 0; let watchPlaylist = []; +let remoteParticipantCount = 0; const peers = new Map(); function persistentClientId() { @@ -133,6 +135,26 @@ function setStatus(message) { statusText.textContent = message; } +function participantDisplayName(name, fallback = "Someone") { + return normalizeName(String(name || "")) || fallback; +} + +function setSessionSummary() { + if (!peerId) { + setStatus(displayName ? `Ready as ${displayName}.` : "Enter your name to join the call."); + return; + } + const suffix = remoteParticipantCount === 1 ? "participant" : "participants"; + setStatus(`You are ${displayName}. ${remoteParticipantCount} other ${suffix}.`); +} + +function setActivity(name, action) { + const actor = participantDisplayName(name); + const message = `${actor} ${action}`; + activityStatusText.textContent = message; + console.info(`[kkkmeet activity] ${message}`); +} + function normalizeName(name) { return name.trim().replace(/\s+/g, " ").slice(0, 48); } @@ -177,6 +199,7 @@ function setDisplayName(nextName) { displayName = normalizeName(nextName); localStorage.setItem(nameStorageKey, displayName); localNameLabel.textContent = displayName ? `${displayName} (you)` : "You"; + setSessionSummary(); } function openNameDialog(mode) { @@ -528,6 +551,7 @@ async function sendWatchState({ updatedAt: Date.now() }) }); + setActivity(displayName, "synced YouTube playback."); } function currentWatchState(playing) { @@ -1150,7 +1174,9 @@ async function answerPeer(message) { async function handleSignal(message) { if (message.type === "welcome") { peerId = message.peerId; - setStatus(`Joined the call as ${displayName}. ${message.peers.length} other participant(s).`); + remoteParticipantCount = message.peers.length; + setSessionSummary(); + setActivity(displayName, "joined."); if (message.watchState) { const watchShouldPlay = message.watchState.action === "play" || (!message.watchState.action && Boolean(message.watchState.playing)); @@ -1175,13 +1201,20 @@ async function handleSignal(message) { if (message.type === "peer-joined") { updateRemoteLabel(message.peerId, message.name); announceParticipantJoined(message.name); - setStatus(`${message.name || "A participant"} joined.`); + remoteParticipantCount += 1; + setSessionSummary(); + setActivity(message.name, "joined."); return; } if (message.type === "peer-left") { + const peerName = message.name || + peers.get(message.peerId)?.name || + `Participant ${message.peerId?.slice(0, 4) || ""}`.trim(); closePeer(message.peerId); - setStatus("A participant left."); + remoteParticipantCount = Math.max(0, remoteParticipantCount - 1); + setSessionSummary(); + setActivity(peerName, "left."); return; } @@ -1197,21 +1230,22 @@ async function handleSignal(message) { allowJoinedYoutubeStartPrompt = false; } await applyWatchState(message, { allowStartPrompt }); - setStatus(`${message.name || "Someone"} synced YouTube playback.`); + setActivity(message.name, "synced YouTube playback."); return; } if (message.type === "presentation-state") { setRemotePresentation(message.from, message.sharing, message.name); - setStatus(message.sharing - ? `${message.name || "Someone"} is sharing their screen.` - : `${message.name || "Someone"} stopped sharing their screen.`); + setActivity( + message.name, + message.sharing ? "started sharing their screen." : "stopped sharing their screen." + ); return; } if (message.type === "voice-announcement") { speakAnnouncement(message); - setStatus(`${message.name || "Someone"} sent an announcement.`); + setActivity(message.name, "sent an announcement."); return; } @@ -1260,8 +1294,9 @@ function leaveCall() { events?.close(); events = undefined; peerId = ""; + remoteParticipantCount = 0; for (const remotePeerId of peers.keys()) closePeer(remotePeerId); - setStatus(displayName ? `Ready as ${displayName}.` : "Enter your name to join the call."); + setSessionSummary(); } function setTrackEnabled(kind, enabled) { @@ -1356,7 +1391,7 @@ async function sendVoiceAnnouncement(text) { await sendRoomEvent({ type: "voice-announcement", text: announcementText }); speakAnnouncement({ text: announcementText }); - setStatus("Announcement sent."); + setActivity(displayName, "sent an announcement."); } async function startScreenShare() { @@ -1368,6 +1403,7 @@ async function startScreenShare() { await replaceOutboundVideoTrack(screenTrack); shareButton.textContent = "Stop Sharing"; await sendRoomEvent({ type: "presentation-state", sharing: true }); + setActivity(displayName, "started sharing their screen."); screenTrack.onended = () => { stopScreenShare().catch((error) => { @@ -1389,6 +1425,7 @@ async function stopScreenShare() { } shareButton.textContent = "Share Screen"; await sendRoomEvent({ type: "presentation-state", sharing: false }); + setActivity(displayName, "stopped sharing their screen."); } initialNameForm.addEventListener("submit", (event) => { diff --git a/public/index.html b/public/index.html index 4bc51d7..7031314 100644 --- a/public/index.html +++ b/public/index.html @@ -12,6 +12,7 @@
Enter your name to join the call.
+