Fix stuck recording processing and add invoice service API test.
Deploy Ladill Meet / deploy (push) Successful in 52s
Deploy Ladill Meet / deploy (push) Successful in 52s
Capture stage spotlight video for browser recordings, fail incomplete uploads, allow post-session host uploads, and expire stale processing recordings automatically. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+70
-10
@@ -1486,8 +1486,10 @@ function meetRoom() {
|
||||
const data = await res.json();
|
||||
this.activeRecordingUuid = data.recording_uuid;
|
||||
if (! await this.tryStartClientRecordingWithRetry()) {
|
||||
await this.abortServerRecording('Could not capture meeting video in this browser.');
|
||||
this.isRecording = false;
|
||||
this.activeRecordingUuid = 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;
|
||||
}
|
||||
|
||||
@@ -1530,7 +1532,11 @@ function meetRoom() {
|
||||
const screenVideo = screenEl && !screenEl.classList.contains('hidden')
|
||||
? screenEl.querySelector('video')
|
||||
: null;
|
||||
const videoSource = screenVideo || document.getElementById('video-grid');
|
||||
|
||||
let videoSource = screenVideo;
|
||||
if (!videoSource) {
|
||||
videoSource = this.recordingVideoCaptureElement();
|
||||
}
|
||||
|
||||
if (!videoSource?.captureStream) {
|
||||
return null;
|
||||
@@ -1542,6 +1548,26 @@ function meetRoom() {
|
||||
return tracks.length ? new MediaStream(tracks) : null;
|
||||
},
|
||||
|
||||
recordingVideoCaptureElement() {
|
||||
if (!this.usesStageLayout) {
|
||||
return document.getElementById('video-grid');
|
||||
}
|
||||
|
||||
const spotlight = document.getElementById('meet-stage-spotlight');
|
||||
const grid = document.getElementById('video-grid');
|
||||
const stage = document.getElementById('meet-stage');
|
||||
|
||||
if (spotlight?.querySelector('video')) {
|
||||
return spotlight;
|
||||
}
|
||||
|
||||
if (grid?.querySelector('video')) {
|
||||
return grid;
|
||||
}
|
||||
|
||||
return stage || spotlight || grid;
|
||||
},
|
||||
|
||||
collectMeetingAudioTracks() {
|
||||
const tracks = [];
|
||||
const addFromParticipant = (participant) => {
|
||||
@@ -1564,30 +1590,56 @@ function meetRoom() {
|
||||
|
||||
async stopServerAndUploadRecording() {
|
||||
const hadClientRecorder = !!recordingMediaRecorder;
|
||||
const res = await fetch(this.config.recordingStopUrl, { method: 'POST', headers: this.headers() });
|
||||
const blob = await this.stopClientRecording();
|
||||
this.isRecording = false;
|
||||
|
||||
if (!res.ok) {
|
||||
let recordingUuid = this.activeRecordingUuid;
|
||||
const res = await fetch(this.config.recordingStopUrl, { method: 'POST', headers: this.headers() });
|
||||
if (res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
recordingUuid = data.recording_uuid || recordingUuid;
|
||||
} else if (res.status !== 404 || !recordingUuid) {
|
||||
this.activeRecordingUuid = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json().catch(() => ({}));
|
||||
const recordingUuid = data.recording_uuid || this.activeRecordingUuid;
|
||||
|
||||
if (blob && recordingUuid) {
|
||||
const uploaded = await this.uploadRecording(blob, recordingUuid);
|
||||
if (!uploaded) {
|
||||
window.alert(`Recording upload failed. Open Recordings and try again, or re-record the ${this.sessionNoun()}.`);
|
||||
window.alert(`Recording upload failed. Open Recordings and refresh shortly, or re-record the ${this.sessionNoun()}.`);
|
||||
}
|
||||
} else if (recordingUuid) {
|
||||
await this.failServerRecording(
|
||||
recordingUuid,
|
||||
hadClientRecorder
|
||||
? `No recording was captured from this ${this.sessionNoun()}.`
|
||||
: 'Recording capture stopped before any video was saved.',
|
||||
);
|
||||
if (hadClientRecorder) {
|
||||
window.alert(`No recording was captured from this ${this.sessionNoun()}. Start recording again while the ${this.sessionNoun()} is live.`);
|
||||
}
|
||||
} else if (recordingUuid && hadClientRecorder) {
|
||||
window.alert(`No recording was captured from this ${this.sessionNoun()}. Start recording again while the ${this.sessionNoun()} is live.`);
|
||||
}
|
||||
|
||||
this.activeRecordingUuid = null;
|
||||
},
|
||||
|
||||
async abortServerRecording(reason) {
|
||||
await fetch(this.config.recordingStopUrl, {
|
||||
method: 'POST',
|
||||
headers: this.headers(),
|
||||
body: JSON.stringify({ failed: true, reason }),
|
||||
});
|
||||
},
|
||||
|
||||
async failServerRecording(recordingUuid, reason) {
|
||||
const url = `/room/${this.config.sessionUuid}/recordings/${recordingUuid}/fail`;
|
||||
await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: this.headers(),
|
||||
body: JSON.stringify({ reason }),
|
||||
});
|
||||
},
|
||||
|
||||
stopClientRecording() {
|
||||
return new Promise((resolve) => {
|
||||
const recorder = recordingMediaRecorder;
|
||||
@@ -1826,7 +1878,7 @@ function meetRoom() {
|
||||
},
|
||||
|
||||
submitEndMeeting() {
|
||||
if (this.isRecording) {
|
||||
if (this.isRecording || this.activeRecordingUuid || recordingMediaRecorder) {
|
||||
this.stopServerAndUploadRecording().finally(() => {
|
||||
document.getElementById('meet-end-form')?.requestSubmit();
|
||||
});
|
||||
@@ -1836,6 +1888,14 @@ function meetRoom() {
|
||||
this.endMeetingConfirmOpen = false;
|
||||
},
|
||||
|
||||
async submitLeave() {
|
||||
if (this.isHost && (this.isRecording || this.activeRecordingUuid || recordingMediaRecorder)) {
|
||||
await this.stopServerAndUploadRecording();
|
||||
}
|
||||
|
||||
document.getElementById('meet-leave-form')?.submit();
|
||||
},
|
||||
|
||||
async uploadFile(event) {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file || !this.config.filesUploadUrl) return;
|
||||
|
||||
@@ -427,7 +427,7 @@
|
||||
<div class="meet-toolbar__divider sm:hidden" aria-hidden="true"></div>
|
||||
|
||||
<div class="meet-toolbar__actions sm:contents">
|
||||
<form method="POST" action="{{ route('meet.room.leave', $session) }}" class="inline">
|
||||
<form id="meet-leave-form" method="POST" action="{{ route('meet.room.leave', $session) }}" class="inline" @submit.prevent="submitLeave()">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="rounded-full bg-red-600 p-3 font-medium hover:bg-red-500 sm:px-5 sm:py-3 sm:text-sm"
|
||||
|
||||
Reference in New Issue
Block a user