diff --git a/resources/css/app.css b/resources/css/app.css index 7568394..003999e 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -28,6 +28,40 @@ margin-left: auto; } +.meet-tile-speaking { + box-shadow: 0 0 0 2px rgb(52 211 153 / 0.9); +} + +.meet-speaking-bar { + transform-origin: center bottom; + animation: meet-speaking-pulse 0.9s ease-in-out infinite; +} + +.meet-speaking-bar--1 { + animation-delay: 0s; +} + +.meet-speaking-bar--2 { + animation-delay: 0.15s; +} + +.meet-speaking-bar--3 { + animation-delay: 0.3s; +} + +@keyframes meet-speaking-pulse { + 0%, + 100% { + transform: scaleY(0.45); + opacity: 0.65; + } + + 50% { + transform: scaleY(1); + opacity: 1; + } +} + /* Meeting room footer */ @media (max-width: 639px) { .meet-toolbar__track { diff --git a/resources/js/meet-room.js b/resources/js/meet-room.js index 7018d78..049ad39 100644 --- a/resources/js/meet-room.js +++ b/resources/js/meet-room.js @@ -59,6 +59,7 @@ function meetRoom() { connectionStatus: 'Connecting…', participants: [], sessionParticipants: [], + activeSpeakerIds: [], roomHost: null, floatingReactions: [], pollTimer: null, @@ -254,6 +255,9 @@ function meetRoom() { this.connectionStatus = 'Connected'; } }); + room.on(RoomEvent.ActiveSpeakersChanged, (speakers) => { + this.syncActiveSpeakers(speakers); + }); await room.prepareConnection(this.config.url, this.config.token); @@ -545,6 +549,71 @@ function meetRoom() { return labels[role] || 'Participant'; }, + isSpeaking(uuid) { + return this.activeSpeakerIds.includes(uuid); + }, + + activeSpeakerNames() { + return this.activeSpeakerIds + .map((id) => { + const person = this.sessionParticipants.find((p) => p.uuid === id); + if (person?.display_name) { + return person.display_name; + } + + if (room?.localParticipant?.identity === id) { + return room.localParticipant.name || 'You'; + } + + return room?.remoteParticipants.get(id)?.name || 'Someone'; + }) + .filter(Boolean); + }, + + activeSpeakersMessage() { + const names = this.activeSpeakerNames(); + if (names.length === 0) return ''; + if (names.length === 1) return `${names[0]} is speaking`; + if (names.length === 2) return `${names[0]} and ${names[1]} are speaking`; + + const last = names[names.length - 1]; + const rest = names.slice(0, -1).join(', '); + + return `${rest}, and ${last} are speaking`; + }, + + activeSpeakerCount() { + return this.activeSpeakerIds.length; + }, + + inCallParticipantsSorted() { + return [...this.inCallParticipants()].sort((a, b) => { + const aSpeaking = this.isSpeaking(a.uuid); + const bSpeaking = this.isSpeaking(b.uuid); + + if (aSpeaking !== bSpeaking) { + return aSpeaking ? -1 : 1; + } + + return a.display_name.localeCompare(b.display_name); + }); + }, + + syncActiveSpeakers(speakers) { + this.activeSpeakerIds = speakers.map((p) => p.identity); + + const grid = document.getElementById('video-grid'); + if (!grid) return; + + grid.querySelectorAll('[data-participant-tile]').forEach((tile) => { + const identity = tile.dataset.participantTile; + const speaking = this.activeSpeakerIds.includes(identity); + + tile.classList.toggle('meet-tile-speaking', speaking); + tile.querySelector('[data-tile-speaking]')?.classList.toggle('hidden', !speaking); + }); + }, + async admitParticipant(uuid) { if (!this.isHost || !this.config.sessionUuid) return; @@ -591,6 +660,13 @@ function meetRoom() {
+ @@ -609,6 +685,10 @@ function meetRoom() { circle.textContent = this.participantInitials(displayName); tile.querySelector('[data-tile-name]').textContent = displayName; + const speaking = this.activeSpeakerIds.includes(identity); + tile.classList.toggle('meet-tile-speaking', speaking); + tile.querySelector('[data-tile-speaking]')?.classList.toggle('hidden', !speaking); + return tile; }, @@ -1020,6 +1100,8 @@ function meetRoom() { tile.remove(); } }); + + this.syncActiveSpeakers(room.activeSpeakers ?? []); }, pruneTilesFromSession() { diff --git a/resources/views/meet/room/show.blade.php b/resources/views/meet/room/show.blade.php index 50add25..4bb42f6 100644 --- a/resources/views/meet/room/show.blade.php +++ b/resources/views/meet/room/show.blade.php @@ -81,6 +81,10 @@

{{ $room->title }}

+