Record meetings with LiveKit Egress instead of browser capture.
Deploy Ladill Meet / deploy (push) Successful in 52s

Server-side room composite egress replaces fragile MediaRecorder DOM capture when enabled, with webhook completion and browser fallback when egress is unavailable.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-04 03:18:28 +00:00
co-authored by Cursor
parent c2cb9cf33a
commit 4e4bed0df3
10 changed files with 674 additions and 10 deletions
+35 -1
View File
@@ -67,6 +67,8 @@ function readMeetRoomBootState() {
videoOffOnJoin: el.dataset.videoOffOnJoin === '1' || audioOnly,
isRecording: el.dataset.recordingActive === '1',
activeRecordingUuid: el.dataset.activeRecordingUuid || null,
recordingCaptureMode: el.dataset.captureMode || null,
serverEgress: el.dataset.serverEgress === '1',
isLocked: el.dataset.locked === '1',
watermark: el.dataset.watermark === '1',
liveCaptions: el.dataset.liveCaptions === '1',
@@ -139,6 +141,8 @@ function meetRoom() {
inBreakout: roomBoot.inBreakout ?? false,
assignedBreakoutRoomId: roomBoot.assignedBreakoutRoomId ?? null,
activeRecordingUuid: roomBoot.activeRecordingUuid ?? null,
recordingCaptureMode: roomBoot.recordingCaptureMode ?? null,
serverEgress: roomBoot.serverEgress ?? false,
sessionParticipants: roomBoot.sessionParticipants ?? [],
roomHost: roomBoot.roomHost ?? null,
captionText: '',
@@ -250,6 +254,8 @@ function meetRoom() {
this.videoOffOnJoin = el.dataset.videoOffOnJoin === '1' || this.audioOnly;
this.isRecording = el.dataset.recordingActive === '1';
this.activeRecordingUuid = el.dataset.activeRecordingUuid || null;
this.recordingCaptureMode = el.dataset.captureMode || null;
this.serverEgress = el.dataset.serverEgress === '1';
this.isLocked = el.dataset.locked === '1';
this.watermark = el.dataset.watermark === '1';
this.liveCaptions = el.dataset.liveCaptions === '1';
@@ -1725,11 +1731,19 @@ function meetRoom() {
}
},
usesServerEgressCapture() {
return this.recordingCaptureMode === 'egress';
},
async maybeStartAutoRecording() {
if (!this.isHost || !this.isRecording || recordingMediaRecorder) {
return;
}
if (this.usesServerEgressCapture()) {
return;
}
const el = document.getElementById('meet-config');
const existingUuid = this.activeRecordingUuid || el?.dataset.activeRecordingUuid || '';
@@ -1772,10 +1786,18 @@ function meetRoom() {
const data = await res.json();
this.activeRecordingUuid = data.recording_uuid;
this.recordingCaptureMode = data.capture_mode || 'browser';
if (this.usesServerEgressCapture()) {
this.isRecording = true;
return;
}
if (! await this.tryStartClientRecordingWithRetry()) {
await this.abortServerRecording('Could not capture meeting video in this browser.');
this.isRecording = false;
this.activeRecordingUuid = null;
this.recordingCaptureMode = null;
window.alert(`Recording could not start because this browser could not capture the ${this.sessionNoun()} video. Try Chrome on desktop, or disable stage-only layouts temporarily.`);
return;
}
@@ -1876,8 +1898,9 @@ function meetRoom() {
},
async stopServerAndUploadRecording() {
const isServerEgress = this.usesServerEgressCapture();
const hadClientRecorder = !!recordingMediaRecorder;
const blob = await this.stopClientRecording();
const blob = isServerEgress ? null : await this.stopClientRecording();
this.isRecording = false;
let recordingUuid = this.activeRecordingUuid;
@@ -1885,8 +1908,18 @@ function meetRoom() {
if (res.ok) {
const data = await res.json().catch(() => ({}));
recordingUuid = data.recording_uuid || recordingUuid;
if (data.capture_mode) {
this.recordingCaptureMode = data.capture_mode;
}
} else if (res.status !== 404 || !recordingUuid) {
this.activeRecordingUuid = null;
this.recordingCaptureMode = null;
return;
}
if (isServerEgress) {
this.activeRecordingUuid = null;
this.recordingCaptureMode = null;
return;
}
@@ -1908,6 +1941,7 @@ function meetRoom() {
}
this.activeRecordingUuid = null;
this.recordingCaptureMode = null;
},
async abortServerRecording(reason) {
+2
View File
@@ -107,6 +107,8 @@
data-locked="{{ $session->is_locked ? '1' : '0' }}"
data-recording-active="{{ $activeRecording ? '1' : '0' }}"
data-active-recording-uuid="{{ $activeRecording?->uuid ?? '' }}"
data-capture-mode="{{ $activeRecording?->metadata['capture_mode'] ?? '' }}"
data-server-egress="{{ ($serverEgressAvailable ?? false) ? '1' : '0' }}"
data-is-webinar="{{ ($isWebinar ?? false) ? '1' : '0' }}"
data-is-conference="{{ ($isConference ?? false) ? '1' : '0' }}"
data-session-noun="{{ $sessionNoun }}"