Skip to content

Commit ea4230e

Browse files
authored
Prevent ongoing renegotiations from declaring the negotiation as timed out (#1813)
1 parent e33d9ba commit ea4230e

4 files changed

Lines changed: 41 additions & 9 deletions

File tree

.changeset/great-crabs-yawn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"livekit-client": patch
3+
---
4+
5+
Prevent ongoing renegotiations from declaring the negotiation as timed out

src/room/PCTransport.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ export default class PCTransport extends EventEmitter {
278278
await this._pc.setRemoteDescription(currentSD);
279279
} else {
280280
this.renegotiate = true;
281+
this.log.debug('requesting renegotiation', { ...this.logContext });
281282
return;
282283
}
283284
} else if (!this._pc || this._pc.signalingState === 'closed') {

src/room/PCTransportManager.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -229,28 +229,46 @@ export class PCTransportManager {
229229

230230
async negotiate(abortController: AbortController) {
231231
return new TypedPromise<void, NegotiationError | Error>(async (resolve, reject) => {
232-
const negotiationTimeout = setTimeout(() => {
232+
let negotiationTimeout = setTimeout(() => {
233233
reject(new NegotiationError('negotiation timed out'));
234234
}, this.peerConnectionTimeout);
235235

236-
const abortHandler = () => {
236+
const cleanup = () => {
237237
clearTimeout(negotiationTimeout);
238+
this.publisher.off(PCEvents.NegotiationStarted, onNegotiationStarted);
239+
abortController.signal.removeEventListener('abort', abortHandler);
240+
};
241+
242+
const abortHandler = () => {
243+
cleanup();
238244
reject(new NegotiationError('negotiation aborted'));
239245
};
240246

241-
abortController.signal.addEventListener('abort', abortHandler);
242-
this.publisher.once(PCEvents.NegotiationStarted, () => {
247+
// Reset the timeout each time a renegotiation cycle starts. This
248+
// prevents premature timeouts when the negotiation machinery is
249+
// actively renegotiating (offers going out, answers coming back) but
250+
// NegotiationComplete hasn't fired yet because new requirements keep
251+
// arriving between offer/answer round-trips.
252+
const onNegotiationStarted = () => {
243253
if (abortController.signal.aborted) {
244254
return;
245255
}
246-
this.publisher.once(PCEvents.NegotiationComplete, () => {
247-
clearTimeout(negotiationTimeout);
248-
resolve();
249-
});
256+
clearTimeout(negotiationTimeout);
257+
negotiationTimeout = setTimeout(() => {
258+
cleanup();
259+
reject(new NegotiationError('negotiation timed out'));
260+
}, this.peerConnectionTimeout);
261+
};
262+
263+
abortController.signal.addEventListener('abort', abortHandler);
264+
this.publisher.on(PCEvents.NegotiationStarted, onNegotiationStarted);
265+
this.publisher.once(PCEvents.NegotiationComplete, () => {
266+
cleanup();
267+
resolve();
250268
});
251269

252270
await this.publisher.negotiate((e) => {
253-
clearTimeout(negotiationTimeout);
271+
cleanup();
254272
if (e instanceof Error) {
255273
reject(e);
256274
} else {

src/room/RTCEngine.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,6 +1524,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
15241524
reject(new NegotiationError('cannot negotiate on closed engine'));
15251525
}
15261526
this.on(EngineEvent.Closing, handleClosed);
1527+
this.on(EngineEvent.Restarting, handleClosed);
15271528

15281529
this.pcManager.publisher.once(
15291530
PCEvents.RTPVideoPayloadTypes,
@@ -1543,6 +1544,12 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
15431544
await this.pcManager.negotiate(abortController);
15441545
resolve();
15451546
} catch (e: unknown) {
1547+
if (abortController.signal.aborted) {
1548+
// negotiation was aborted due to engine close or restart, resolve
1549+
// cleanly to avoid triggering a cascading reconnect loop
1550+
resolve();
1551+
return;
1552+
}
15461553
if (e instanceof NegotiationError) {
15471554
this.fullReconnectOnNext = true;
15481555
}
@@ -1554,6 +1561,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
15541561
}
15551562
} finally {
15561563
this.off(EngineEvent.Closing, handleClosed);
1564+
this.off(EngineEvent.Restarting, handleClosed);
15571565
}
15581566
});
15591567
}

0 commit comments

Comments
 (0)