fix(webapp): avoid overlapping client stream sessions

This commit is contained in:
2026-04-01 14:00:00 +01:00
parent 2044754666
commit 8c877c0e85

View File

@@ -766,6 +766,23 @@ const clearClientStream = () => {
setClientStreamMode('none');
};
const endClientStreamSession = async (streamSessionId, { teardown = true } = {}) => {
if (!streamSessionId) return;
try {
await api.streams.end(streamSessionId);
} catch (error) {
const message = error?.message || '';
if (!/cannot be ended|not found/i.test(message)) {
console.warn('Failed ending client stream session', error);
}
}
if (teardown) {
teardownPeerConnection(streamSessionId);
}
};
const hasReusableClientStreamSession = (streamSessionId) =>
Boolean(streamSessionId && (remoteStreams.has(streamSessionId) || streamTimers.has(streamSessionId)));
@@ -1700,13 +1717,6 @@ const pollClientData = async () => {
recordings: recs.recordings || [],
linkedCameras
});
for (const link of linkedCameras) {
if (!requestedStreams.has(link.cameraDeviceId)) {
requestedStreams.add(link.cameraDeviceId);
void actions.requestStream(link.cameraDeviceId);
}
}
};
const startPolling = () => {
@@ -2109,17 +2119,22 @@ const actions = {
async requestStream(cameraDeviceId) {
try {
requestedStreams.add(cameraDeviceId);
await api.streams.request(cameraDeviceId);
} catch (error) {
requestedStreams.delete(cameraDeviceId);
pushToast(error.message || 'Failed to request stream', 'error');
}
},
selectCamera(cameraDeviceId) {
async selectCamera(cameraDeviceId) {
const currentState = getAppState();
const sessions = currentState.cameraSessions || {};
const existingSessionId = sessions[cameraDeviceId] || null;
const reusableSessionId = hasReusableClientStreamSession(existingSessionId) ? existingSessionId : null;
const previousActiveStreamSessionId = currentState.activeStreamSessionId;
const isSwitchingStreams =
Boolean(previousActiveStreamSessionId) && previousActiveStreamSessionId !== reusableSessionId;
if (currentState.activeCameraDeviceId !== cameraDeviceId || currentState.activeStreamSessionId !== reusableSessionId) {
clearClientStream();
@@ -2131,6 +2146,10 @@ const actions = {
openLinkedCameraMenuId: null
});
if (isSwitchingStreams) {
void endClientStreamSession(previousActiveStreamSessionId, { teardown: false });
}
if (reusableSessionId) {
attachClientStreamToElement();
setClientStreamMode(remoteStreams.has(reusableSessionId) ? 'video' : 'connecting');
@@ -2138,12 +2157,14 @@ const actions = {
}
setClientStreamMode('connecting');
void actions.requestStream(cameraDeviceId);
await actions.requestStream(cameraDeviceId);
},
closeStreamViewer() {
const streamSessionId = getAppState().activeStreamSessionId;
setAppState({ activeCameraDeviceId: null, activeStreamSessionId: null });
clearClientStream();
void endClientStreamSession(streamSessionId, { teardown: false });
},
async openRecording(recordingId) {