fix(sim): prevent duplicate stream start loops and noisy recording fallback errors
This commit is contained in:
@@ -186,6 +186,9 @@ let sfuSendTransport = null;
|
||||
let sfuRecvTransport = null;
|
||||
let sfuPublishedProducerId = null;
|
||||
let sfuConsumedTrack = null;
|
||||
let streamRequestInFlight = false;
|
||||
let hasAutoRequestedInitialStream = false;
|
||||
const inflightCameraStreamCommands = new Set();
|
||||
const rtcConfig = {
|
||||
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
|
||||
};
|
||||
@@ -914,7 +917,14 @@ const finalizeRecordingForStream = async (streamSessionId, captureResult) => {
|
||||
if (recording?.id) {
|
||||
try {
|
||||
if (!captureResult?.blob || captureResult.blob.size === 0) {
|
||||
throw new Error('No captured video blob to upload');
|
||||
const fallbackObjectKey = `sim/${streamSessionId}/${Date.now()}.webm`;
|
||||
await API.events.finalizeRecording(recording.id, {
|
||||
objectKey: fallbackObjectKey,
|
||||
durationSeconds: captureResult?.durationSeconds ?? 15,
|
||||
sizeBytes: captureResult?.blob?.size ?? 0,
|
||||
});
|
||||
addActivity('Recording', 'No local blob; finalized with simulator fallback');
|
||||
return true;
|
||||
}
|
||||
|
||||
const uploadMeta = await API.request('/videos/upload-url', {
|
||||
@@ -998,11 +1008,26 @@ const connectSocket = () => {
|
||||
try {
|
||||
if (payload.commandType === 'start_stream') {
|
||||
const streamId = payload.payload.streamSessionId;
|
||||
if (inflightCameraStreamCommands.has(streamId)) {
|
||||
addActivity('Stream', `Duplicate start ignored for ${streamId.substring(0, 8)}`);
|
||||
socket.emit('command:ack', { commandId: payload.commandId, status: 'acknowledged' });
|
||||
return;
|
||||
}
|
||||
inflightCameraStreamCommands.add(streamId);
|
||||
|
||||
const ready = await startCameraPreview();
|
||||
if (!ready) {
|
||||
throw new Error('Camera permission is required before streaming');
|
||||
}
|
||||
try {
|
||||
await API.streams.accept(streamId);
|
||||
} catch (error) {
|
||||
const message = error?.message || '';
|
||||
if (!message.includes('status 409')) {
|
||||
throw error;
|
||||
}
|
||||
addActivity('Stream', 'Accept already handled, continuing publish setup');
|
||||
}
|
||||
await API.streams.getPublishCreds(streamId);
|
||||
await startLocalRecording();
|
||||
if (isSfuMode()) {
|
||||
@@ -1018,6 +1043,7 @@ const connectSocket = () => {
|
||||
addActivity('Stream', 'Accepted & Published');
|
||||
// Auto-stop after 15s for simulation
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const captureResult = await stopLocalRecording();
|
||||
await API.streams.end(streamId);
|
||||
await finalizeRecordingForStream(streamId, captureResult);
|
||||
@@ -1032,11 +1058,17 @@ const connectSocket = () => {
|
||||
teardownPeerConnection();
|
||||
store.update({ activeCameraDeviceId: null, activeStreamSessionId: null });
|
||||
addActivity('Stream', 'Ended auto-simulation');
|
||||
} finally {
|
||||
inflightCameraStreamCommands.delete(streamId);
|
||||
}
|
||||
}, 15000);
|
||||
}
|
||||
|
||||
socket.emit('command:ack', { commandId: payload.commandId, status: 'acknowledged' });
|
||||
} catch (e) {
|
||||
if (payload?.payload?.streamSessionId) {
|
||||
inflightCameraStreamCommands.delete(payload.payload.streamSessionId);
|
||||
}
|
||||
socket.emit('command:ack', { commandId: payload.commandId, status: 'rejected', error: e.message });
|
||||
}
|
||||
});
|
||||
@@ -1348,12 +1380,25 @@ const Actions = {
|
||||
},
|
||||
|
||||
requestStream: async (camId) => {
|
||||
if (streamRequestInFlight) {
|
||||
return;
|
||||
}
|
||||
const current = store.get();
|
||||
if (current.activeStreamSessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
streamRequestInFlight = true;
|
||||
try {
|
||||
store.update({ activeCameraDeviceId: camId });
|
||||
Toast.show('Requesting Stream...', 'info');
|
||||
await API.streams.request(camId);
|
||||
// Socket will handle the rest ('stream:started')
|
||||
} catch (e) { }
|
||||
} catch (e) {
|
||||
store.update({ activeCameraDeviceId: null });
|
||||
} finally {
|
||||
streamRequestInFlight = false;
|
||||
}
|
||||
},
|
||||
|
||||
openRecording: async (recordingId) => {
|
||||
@@ -1469,7 +1514,8 @@ const render = (state) => {
|
||||
|
||||
// 5. Client Mode Lists
|
||||
if (state.device?.role === 'client' && state.screen === 'home') {
|
||||
if (!state.activeCameraDeviceId && state.linkedCameras.length > 0) {
|
||||
if (!hasAutoRequestedInitialStream && !state.activeCameraDeviceId && state.linkedCameras.length > 0 && !streamRequestInFlight) {
|
||||
hasAutoRequestedInitialStream = true;
|
||||
void Actions.requestStream(state.linkedCameras[0].cameraDeviceId);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user