fix(webapp): log recording upload failures and restore 6s clips

This commit is contained in:
2026-04-15 11:15:00 +01:00
parent 995cecd4ac
commit 9dc202ce03

View File

@@ -34,7 +34,7 @@ const MOTION_DETECTION_PROFILES = {
releaseThreshold: 0.05,
consecutiveTriggerFrames: 3,
cooldownMs: 12000,
minimumEventMs: 9000
minimumEventMs: 6000
},
balanced: {
profile: 'balanced',
@@ -46,7 +46,7 @@ const MOTION_DETECTION_PROFILES = {
releaseThreshold: 0.04,
consecutiveTriggerFrames: 3,
cooldownMs: 9000,
minimumEventMs: 8000
minimumEventMs: 6000
},
responsive: {
profile: 'responsive',
@@ -58,7 +58,7 @@ const MOTION_DETECTION_PROFILES = {
releaseThreshold: 0.035,
consecutiveTriggerFrames: 2,
cooldownMs: 7000,
minimumEventMs: 7000
minimumEventMs: 6000
}
};
@@ -1478,6 +1478,24 @@ const startOfferToClient = async (streamSessionId, requesterDeviceId) => {
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const summarizeFetchFailure = async (response) => {
if (!response) {
return 'no response returned';
}
const parts = [`status ${response.status}`];
try {
const bodyText = (await response.text()).trim();
if (bodyText) {
parts.push(bodyText.slice(0, 240));
}
} catch {
// Ignore body parsing failures for diagnostics.
}
return parts.join(' · ');
};
const finalizeRecordingForStream = async (streamSessionId, captureResult) => {
const currentDevice = getAppState().device;
if (!currentDevice?.id) {
@@ -1496,6 +1514,8 @@ const finalizeRecordingForStream = async (streamSessionId, captureResult) => {
if (!captureResult?.blob || captureResult.blob.size === 0) {
throw new Error('No captured video blob to upload');
}
addActivity('Recording', `Preparing upload for stream ${streamSessionId}`);
const compressedBlob = await compressRecordingBlob(captureResult.blob);
const uploadMeta = await api.request('/videos/upload-url', {
@@ -1507,6 +1527,26 @@ const finalizeRecordingForStream = async (streamSessionId, captureResult) => {
recordingId: recording.id
})
});
const uploadOrigin = (() => {
try {
return new URL(uploadMeta.uploadUrl).origin;
} catch {
return 'invalid upload URL';
}
})();
console.info('[recording.upload] upload URL issued', {
recordingId: recording.id,
streamSessionId,
objectKey: uploadMeta.objectKey,
bucket: uploadMeta.bucket,
uploadOrigin,
blobSize: compressedBlob.size,
blobType: compressedBlob.type || 'video/webm'
});
addActivity(
'Recording',
`Upload URL ready for ${uploadMeta.objectKey} via ${uploadOrigin}`
);
const uploadResponse = await fetch(uploadMeta.uploadUrl, {
method: 'PUT',
@@ -1515,9 +1555,19 @@ const finalizeRecordingForStream = async (streamSessionId, captureResult) => {
});
if (!uploadResponse.ok) {
throw new Error(`Upload failed with status ${uploadResponse.status}`);
throw new Error(`Upload failed: ${await summarizeFetchFailure(uploadResponse)}`);
}
console.info('[recording.upload] object uploaded', {
recordingId: recording.id,
streamSessionId,
objectKey: uploadMeta.objectKey,
bucket: uploadMeta.bucket,
status: uploadResponse.status,
sizeBytes: compressedBlob.size
});
addActivity('Recording', `Upload completed for ${uploadMeta.objectKey}`);
await api.events.finalizeRecording(recording.id, {
objectKey: uploadMeta.objectKey,
bucket: uploadMeta.bucket,
@@ -1525,14 +1575,29 @@ const finalizeRecordingForStream = async (streamSessionId, captureResult) => {
sizeBytes: compressedBlob.size
});
console.info('[recording.upload] recording finalized', {
recordingId: recording.id,
streamSessionId,
objectKey: uploadMeta.objectKey,
durationSeconds: captureResult.durationSeconds,
sizeBytes: compressedBlob.size
});
addActivity('Recording', 'Recording uploaded and finalized');
return true;
} catch (error) {
console.error('Recording upload failed, falling back to simulated key', error);
console.error('[recording.upload] stream upload failed, falling back to simulated key', {
recordingId: recording.id,
streamSessionId,
error: error instanceof Error ? error.message : String(error)
});
addActivity(
'Recording',
`Upload failed for stream ${streamSessionId}: ${error instanceof Error ? error.message : 'unknown error'}`
);
const fallbackObjectKey = `sim/${streamSessionId}/${Date.now()}.webm`;
await api.events.finalizeRecording(recording.id, {
objectKey: fallbackObjectKey,
durationSeconds: captureResult?.durationSeconds ?? 15,
durationSeconds: captureResult?.durationSeconds ?? 6,
sizeBytes: captureResult?.blob?.size ?? 5000000
});
addActivity('Recording', 'Upload failed; finalized with simulator fallback');
@@ -1560,6 +1625,7 @@ const uploadStandaloneMotionRecording = async (captureResult) => {
}
try {
addActivity('Recording', 'Preparing standalone motion clip upload');
const compressedBlob = await compressRecordingBlob(captureResult.blob);
const uploadMeta = await api.request('/videos/upload-url', {
method: 'POST',
@@ -1570,6 +1636,23 @@ const uploadStandaloneMotionRecording = async (captureResult) => {
eventId: lastMotionEventId
})
});
const uploadOrigin = (() => {
try {
return new URL(uploadMeta.uploadUrl).origin;
} catch {
return 'invalid upload URL';
}
})();
console.info('[recording.upload] standalone upload URL issued', {
eventId: lastMotionEventId,
recordingId: uploadMeta.video?.id,
objectKey: uploadMeta.objectKey,
bucket: uploadMeta.bucket,
uploadOrigin,
blobSize: compressedBlob.size,
blobType: compressedBlob.type || 'video/webm'
});
addActivity('Recording', `Standalone upload URL ready via ${uploadOrigin}`);
const uploadResponse = await fetch(uploadMeta.uploadUrl, {
method: 'PUT',
@@ -1578,9 +1661,19 @@ const uploadStandaloneMotionRecording = async (captureResult) => {
});
if (!uploadResponse.ok) {
throw new Error(`Upload failed with status ${uploadResponse.status}`);
throw new Error(`Upload failed: ${await summarizeFetchFailure(uploadResponse)}`);
}
console.info('[recording.upload] standalone object uploaded', {
eventId: lastMotionEventId,
recordingId: uploadMeta.video?.id,
objectKey: uploadMeta.objectKey,
bucket: uploadMeta.bucket,
status: uploadResponse.status,
sizeBytes: compressedBlob.size
});
addActivity('Recording', `Standalone upload completed for ${uploadMeta.objectKey}`);
await api.events.finalizeRecording(uploadMeta.video.id, {
objectKey: uploadMeta.objectKey,
bucket: uploadMeta.bucket,
@@ -1588,11 +1681,24 @@ const uploadStandaloneMotionRecording = async (captureResult) => {
sizeBytes: compressedBlob.size
});
console.info('[recording.upload] standalone recording finalized', {
eventId: lastMotionEventId,
recordingId: uploadMeta.video?.id,
objectKey: uploadMeta.objectKey,
durationSeconds: captureResult.durationSeconds,
sizeBytes: compressedBlob.size
});
addActivity('Recording', `Motion clip uploaded (${uploadMeta.objectKey})`);
return true;
} catch (error) {
console.error('Standalone motion upload failed', error);
addActivity('Recording', 'Standalone motion upload failed');
console.error('[recording.upload] standalone motion upload failed', {
eventId: lastMotionEventId,
error: error instanceof Error ? error.message : String(error)
});
addActivity(
'Recording',
`Standalone motion upload failed: ${error instanceof Error ? error.message : 'unknown error'}`
);
return false;
}
};