fix(webapp): log recording upload failures and restore 6s clips
This commit is contained in:
@@ -34,7 +34,7 @@ const MOTION_DETECTION_PROFILES = {
|
|||||||
releaseThreshold: 0.05,
|
releaseThreshold: 0.05,
|
||||||
consecutiveTriggerFrames: 3,
|
consecutiveTriggerFrames: 3,
|
||||||
cooldownMs: 12000,
|
cooldownMs: 12000,
|
||||||
minimumEventMs: 9000
|
minimumEventMs: 6000
|
||||||
},
|
},
|
||||||
balanced: {
|
balanced: {
|
||||||
profile: 'balanced',
|
profile: 'balanced',
|
||||||
@@ -46,7 +46,7 @@ const MOTION_DETECTION_PROFILES = {
|
|||||||
releaseThreshold: 0.04,
|
releaseThreshold: 0.04,
|
||||||
consecutiveTriggerFrames: 3,
|
consecutiveTriggerFrames: 3,
|
||||||
cooldownMs: 9000,
|
cooldownMs: 9000,
|
||||||
minimumEventMs: 8000
|
minimumEventMs: 6000
|
||||||
},
|
},
|
||||||
responsive: {
|
responsive: {
|
||||||
profile: 'responsive',
|
profile: 'responsive',
|
||||||
@@ -58,7 +58,7 @@ const MOTION_DETECTION_PROFILES = {
|
|||||||
releaseThreshold: 0.035,
|
releaseThreshold: 0.035,
|
||||||
consecutiveTriggerFrames: 2,
|
consecutiveTriggerFrames: 2,
|
||||||
cooldownMs: 7000,
|
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 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 finalizeRecordingForStream = async (streamSessionId, captureResult) => {
|
||||||
const currentDevice = getAppState().device;
|
const currentDevice = getAppState().device;
|
||||||
if (!currentDevice?.id) {
|
if (!currentDevice?.id) {
|
||||||
@@ -1496,6 +1514,8 @@ const finalizeRecordingForStream = async (streamSessionId, captureResult) => {
|
|||||||
if (!captureResult?.blob || captureResult.blob.size === 0) {
|
if (!captureResult?.blob || captureResult.blob.size === 0) {
|
||||||
throw new Error('No captured video blob to upload');
|
throw new Error('No captured video blob to upload');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addActivity('Recording', `Preparing upload for stream ${streamSessionId}`);
|
||||||
const compressedBlob = await compressRecordingBlob(captureResult.blob);
|
const compressedBlob = await compressRecordingBlob(captureResult.blob);
|
||||||
|
|
||||||
const uploadMeta = await api.request('/videos/upload-url', {
|
const uploadMeta = await api.request('/videos/upload-url', {
|
||||||
@@ -1507,6 +1527,26 @@ const finalizeRecordingForStream = async (streamSessionId, captureResult) => {
|
|||||||
recordingId: recording.id
|
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, {
|
const uploadResponse = await fetch(uploadMeta.uploadUrl, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
@@ -1515,9 +1555,19 @@ const finalizeRecordingForStream = async (streamSessionId, captureResult) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!uploadResponse.ok) {
|
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, {
|
await api.events.finalizeRecording(recording.id, {
|
||||||
objectKey: uploadMeta.objectKey,
|
objectKey: uploadMeta.objectKey,
|
||||||
bucket: uploadMeta.bucket,
|
bucket: uploadMeta.bucket,
|
||||||
@@ -1525,14 +1575,29 @@ const finalizeRecordingForStream = async (streamSessionId, captureResult) => {
|
|||||||
sizeBytes: compressedBlob.size
|
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');
|
addActivity('Recording', 'Recording uploaded and finalized');
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} 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`;
|
const fallbackObjectKey = `sim/${streamSessionId}/${Date.now()}.webm`;
|
||||||
await api.events.finalizeRecording(recording.id, {
|
await api.events.finalizeRecording(recording.id, {
|
||||||
objectKey: fallbackObjectKey,
|
objectKey: fallbackObjectKey,
|
||||||
durationSeconds: captureResult?.durationSeconds ?? 15,
|
durationSeconds: captureResult?.durationSeconds ?? 6,
|
||||||
sizeBytes: captureResult?.blob?.size ?? 5000000
|
sizeBytes: captureResult?.blob?.size ?? 5000000
|
||||||
});
|
});
|
||||||
addActivity('Recording', 'Upload failed; finalized with simulator fallback');
|
addActivity('Recording', 'Upload failed; finalized with simulator fallback');
|
||||||
@@ -1560,6 +1625,7 @@ const uploadStandaloneMotionRecording = async (captureResult) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
addActivity('Recording', 'Preparing standalone motion clip upload');
|
||||||
const compressedBlob = await compressRecordingBlob(captureResult.blob);
|
const compressedBlob = await compressRecordingBlob(captureResult.blob);
|
||||||
const uploadMeta = await api.request('/videos/upload-url', {
|
const uploadMeta = await api.request('/videos/upload-url', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -1570,6 +1636,23 @@ const uploadStandaloneMotionRecording = async (captureResult) => {
|
|||||||
eventId: lastMotionEventId
|
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, {
|
const uploadResponse = await fetch(uploadMeta.uploadUrl, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
@@ -1578,9 +1661,19 @@ const uploadStandaloneMotionRecording = async (captureResult) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!uploadResponse.ok) {
|
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, {
|
await api.events.finalizeRecording(uploadMeta.video.id, {
|
||||||
objectKey: uploadMeta.objectKey,
|
objectKey: uploadMeta.objectKey,
|
||||||
bucket: uploadMeta.bucket,
|
bucket: uploadMeta.bucket,
|
||||||
@@ -1588,11 +1681,24 @@ const uploadStandaloneMotionRecording = async (captureResult) => {
|
|||||||
sizeBytes: compressedBlob.size
|
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})`);
|
addActivity('Recording', `Motion clip uploaded (${uploadMeta.objectKey})`);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Standalone motion upload failed', error);
|
console.error('[recording.upload] standalone motion upload failed', {
|
||||||
addActivity('Recording', '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;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user