Fix screen-share tile recovery and make AI summaries use full transcripts.
Deploy Ladill Meet / deploy (push) Successful in 1m17s
Deploy Ladill Meet / deploy (push) Successful in 1m17s
Defer summarization until recordings finish, merge chat with deduplicated live captions, and clear the screen-share overlay so participant cards return after sharing ends. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+130
-22
@@ -370,8 +370,18 @@ function meetRoom() {
|
||||
});
|
||||
room.on(RoomEvent.TrackUnsubscribed, (track, publication, participant) => {
|
||||
track.detach();
|
||||
if (track.kind === Track.Kind.Video && publication.source !== Track.Source.ScreenShare) {
|
||||
|
||||
const isScreen = publication?.source === Track.Source.ScreenShare
|
||||
|| track.source === Track.Source.ScreenShare;
|
||||
|
||||
if (isScreen) {
|
||||
this.clearScreenShareOverlay();
|
||||
return;
|
||||
}
|
||||
|
||||
if (track.kind === Track.Kind.Video) {
|
||||
this.updateParticipantTile(participant);
|
||||
this.reattachParticipantVideo(participant);
|
||||
}
|
||||
});
|
||||
room.on(RoomEvent.TrackMuted, (publication, participant) => {
|
||||
@@ -408,7 +418,11 @@ function meetRoom() {
|
||||
});
|
||||
room.on(RoomEvent.LocalTrackUnpublished, (publication, participant) => {
|
||||
if (publication.kind === Track.Kind.Video) {
|
||||
this.updateParticipantTile(participant);
|
||||
if (publication.source === Track.Source.ScreenShare) {
|
||||
this.clearScreenShareOverlay();
|
||||
} else {
|
||||
this.updateParticipantTile(participant);
|
||||
}
|
||||
}
|
||||
this.syncLocalMediaState();
|
||||
});
|
||||
@@ -660,6 +674,15 @@ function meetRoom() {
|
||||
const local = room.localParticipant;
|
||||
this.isMuted = !local.isMicrophoneEnabled;
|
||||
this.isVideoOff = !local.isCameraEnabled;
|
||||
this.isScreenSharing = Array.from(local.videoTrackPublications.values())
|
||||
.some((pub) => pub.source === Track.Source.ScreenShare && pub.track && !pub.isMuted);
|
||||
|
||||
if (!this.isScreenSharing) {
|
||||
const screen = document.getElementById('screen-share');
|
||||
if (screen && !screen.classList.contains('hidden')) {
|
||||
this.clearScreenShareOverlay();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
participantInitials(name) {
|
||||
@@ -1657,12 +1680,15 @@ function meetRoom() {
|
||||
|
||||
if (hasVideo) {
|
||||
avatar?.classList.add('hidden');
|
||||
if (!stage?.querySelector('video')) {
|
||||
const publication = Array.from(participant.videoTrackPublications.values())
|
||||
.find((pub) => pub.source !== Track.Source.ScreenShare && pub.track && !pub.isMuted);
|
||||
if (publication?.track) {
|
||||
this.attachVideoToTile(participant, publication.track);
|
||||
}
|
||||
const publication = Array.from(participant.videoTrackPublications.values())
|
||||
.find((pub) => pub.source !== Track.Source.ScreenShare && pub.track && !pub.isMuted);
|
||||
const video = stage?.querySelector('video');
|
||||
const needsAttach = !video
|
||||
|| video.readyState === HTMLMediaElement.HAVE_NOTHING
|
||||
|| !video.srcObject;
|
||||
|
||||
if (publication?.track && needsAttach) {
|
||||
this.attachVideoToTile(participant, publication.track);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -1707,27 +1733,76 @@ function meetRoom() {
|
||||
this.updateParticipantTile(participant);
|
||||
},
|
||||
|
||||
publishCaption(text) {
|
||||
const normalized = text.trim();
|
||||
if (!normalized || !this.config.captionUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalized === this._lastCaptionSent) {
|
||||
this.captionText = normalized;
|
||||
return;
|
||||
}
|
||||
|
||||
this._lastCaptionSent = normalized;
|
||||
this.captionText = normalized;
|
||||
|
||||
fetch(this.config.captionUrl, {
|
||||
method: 'POST',
|
||||
headers: this.headers(),
|
||||
body: JSON.stringify({ text: normalized }),
|
||||
}).catch(() => {});
|
||||
},
|
||||
|
||||
setupCaptionPipeline() {
|
||||
if (!this.liveCaptions || !this.config.captionUrl) return;
|
||||
if (room?.localParticipant?.isMicrophoneEnabled) return;
|
||||
|
||||
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
if (!SpeechRecognition) return;
|
||||
|
||||
if (this._captionRecognition) {
|
||||
return;
|
||||
}
|
||||
|
||||
const recognition = new SpeechRecognition();
|
||||
recognition.continuous = true;
|
||||
recognition.interimResults = true;
|
||||
recognition.onresult = (event) => {
|
||||
const text = event.results[event.results.length - 1][0].transcript.trim();
|
||||
if (!text) return;
|
||||
this.captionText = text;
|
||||
fetch(this.config.captionUrl, {
|
||||
method: 'POST',
|
||||
headers: this.headers(),
|
||||
body: JSON.stringify({ text }),
|
||||
}).catch(() => {});
|
||||
let interim = '';
|
||||
|
||||
for (let index = event.resultIndex; index < event.results.length; index += 1) {
|
||||
const result = event.results[index];
|
||||
const text = result[0]?.transcript?.trim() ?? '';
|
||||
if (!text) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.isFinal) {
|
||||
this.publishCaption(text);
|
||||
} else {
|
||||
interim = text;
|
||||
}
|
||||
}
|
||||
|
||||
if (interim) {
|
||||
this.captionText = interim;
|
||||
}
|
||||
};
|
||||
recognition.onerror = () => {};
|
||||
recognition.onend = () => {
|
||||
if (!this.liveCaptions || !room) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
recognition.start();
|
||||
} catch (e) {
|
||||
// browser may block without gesture
|
||||
}
|
||||
};
|
||||
|
||||
this._captionRecognition = recognition;
|
||||
|
||||
try {
|
||||
recognition.start();
|
||||
} catch (e) {
|
||||
@@ -2297,6 +2372,34 @@ function meetRoom() {
|
||||
this.updateParticipantTile(participant);
|
||||
},
|
||||
|
||||
clearScreenShareOverlay() {
|
||||
const screen = document.getElementById('screen-share');
|
||||
if (!screen) {
|
||||
return;
|
||||
}
|
||||
|
||||
screen.querySelectorAll('video').forEach((element) => {
|
||||
element.srcObject = null;
|
||||
});
|
||||
screen.classList.add('hidden');
|
||||
screen.replaceChildren();
|
||||
|
||||
if (room?.localParticipant) {
|
||||
this.isScreenSharing = Array.from(room.localParticipant.videoTrackPublications.values())
|
||||
.some((pub) => pub.source === Track.Source.ScreenShare && pub.track && !pub.isMuted);
|
||||
} else {
|
||||
this.isScreenSharing = false;
|
||||
}
|
||||
|
||||
if (!room || this.audioOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
const participants = [room.localParticipant, ...room.remoteParticipants.values()];
|
||||
participants.forEach((participant) => this.reattachParticipantVideo(participant));
|
||||
this.syncParticipants();
|
||||
},
|
||||
|
||||
attachTrack(track, publication, participant) {
|
||||
if (track.kind === Track.Kind.Audio) {
|
||||
if (participant === room?.localParticipant) return;
|
||||
@@ -2334,6 +2437,15 @@ function meetRoom() {
|
||||
const el = track.attach();
|
||||
el.className = 'h-full w-full rounded-xl object-contain';
|
||||
container.appendChild(el);
|
||||
|
||||
track.mediaStreamTrack?.addEventListener('ended', () => {
|
||||
this.clearScreenShareOverlay();
|
||||
}, { once: true });
|
||||
|
||||
if (participant === room?.localParticipant) {
|
||||
this.isScreenSharing = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2484,11 +2596,7 @@ function meetRoom() {
|
||||
this.isScreenSharing = !this.isScreenSharing;
|
||||
await room.localParticipant.setScreenShareEnabled(this.isScreenSharing);
|
||||
if (!this.isScreenSharing) {
|
||||
const screen = document.getElementById('screen-share');
|
||||
if (screen) {
|
||||
screen.classList.add('hidden');
|
||||
screen.innerHTML = '';
|
||||
}
|
||||
this.clearScreenShareOverlay();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user