fix: Add support for synchronously reseting go2rtc sessions (#2478)
This is part 1 of https://docs.google.com/document/d/1zbHYWqulEUaNLeX0fK3rfIiLugMlCQo_SFg3hcgiRRw/edit?tab=t.0 . There is a significant chance this will improve issues people have been experiencing with poorly designed doorbells that can only open a single stream at a time.
This commit is contained in:
committed by
dermotduffy
parent
31d0c9322f
commit
9d088d7bcb
@@ -74,7 +74,7 @@ export class AdvancedCameraCardGo2RTC extends LitElement implements MediaPlayer
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
this._player = undefined;
|
||||
this._destroyPlayer();
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
@@ -86,6 +86,14 @@ export class AdvancedCameraCardGo2RTC extends LitElement implements MediaPlayer
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private _destroyPlayer(): void {
|
||||
// Tear down synchronously so backchannel-enabled streams (e.g. doorbell
|
||||
// 2-way audio) release immediately rather than waiting out
|
||||
// DISCONNECT_TIMEOUT.
|
||||
this._player?.reset();
|
||||
this._player = undefined;
|
||||
}
|
||||
|
||||
private _createPlayer(): void {
|
||||
const src = this._signedURLController.getValue();
|
||||
if (!src) {
|
||||
@@ -112,7 +120,7 @@ export class AdvancedCameraCardGo2RTC extends LitElement implements MediaPlayer
|
||||
if (changedProps.has('camera')) {
|
||||
// Clear old player; the new one is created by the
|
||||
// SignedURLController's valueChangeCallback once the URL resolves.
|
||||
this._player = undefined;
|
||||
this._destroyPlayer();
|
||||
}
|
||||
|
||||
// Only treat a missing go2rtc endpoint as an error after the camera's
|
||||
|
||||
@@ -35,5 +35,6 @@ export class VideoRTC extends HTMLElement {
|
||||
microphoneStream: MediaStream | null;
|
||||
targetID: string | null;
|
||||
reconnect();
|
||||
reset(): void;
|
||||
setControls(controls: boolean): void;
|
||||
}
|
||||
|
||||
@@ -236,6 +236,29 @@ export class VideoRTC extends HTMLElement {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously tear down the stream: clear pending disconnect/reconnect
|
||||
* timers, abort any in-flight load registration, and close the WebSocket
|
||||
* and peer connection immediately. Used when intentionally destroying the
|
||||
* player (e.g. swapping cameras) to avoid the DISCONNECT_TIMEOUT defer
|
||||
* that would otherwise keep backchannel-enabled streams connected.
|
||||
*/
|
||||
reset() {
|
||||
if (this.disconnectTID) {
|
||||
clearTimeout(this.disconnectTID);
|
||||
this.disconnectTID = 0;
|
||||
}
|
||||
if (this.reconnectTID) {
|
||||
clearTimeout(this.reconnectTID);
|
||||
this.reconnectTID = 0;
|
||||
}
|
||||
this._abortController?.abort();
|
||||
this._abortController = null;
|
||||
if (this.wsState !== WebSocket.CLOSED || this.pcState !== WebSocket.CLOSED) {
|
||||
this.ondisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect the stream.
|
||||
*/
|
||||
@@ -714,14 +737,39 @@ export class VideoRTC extends HTMLElement {
|
||||
}
|
||||
};
|
||||
|
||||
this.createOffer(pc).then((offer) => {
|
||||
this.send({ type: 'webrtc/offer', value: offer.sdp });
|
||||
});
|
||||
this.negotiateOffer(pc);
|
||||
|
||||
this.pcState = WebSocket.CONNECTING;
|
||||
this.pc = pc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and send the WebRTC offer, guarding against the peer connection being
|
||||
* torn down or replaced while `createOffer` is in flight (e.g. by `reset()`
|
||||
* during a stream swap, or by a `webrtc/offer` error message closing `pc`
|
||||
* without resetting `this.pc`/`this.pcState`). Without these guards a stale
|
||||
* offer would be sent and `createOffer` rejections from mid-await
|
||||
* `pc.close()` would surface as unhandled promise rejections.
|
||||
* @param pc {RTCPeerConnection}
|
||||
*/
|
||||
async negotiateOffer(pc) {
|
||||
try {
|
||||
const offer = await this.createOffer(pc);
|
||||
if (
|
||||
this.pc !== pc ||
|
||||
this.pcState === WebSocket.CLOSED ||
|
||||
pc.signalingState === 'closed'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.send({ type: 'webrtc/offer', value: offer.sdp });
|
||||
} catch (er) {
|
||||
if (this.pc === pc && this.pcState !== WebSocket.CLOSED) {
|
||||
console.warn(er);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pc {RTCPeerConnection}
|
||||
* @return {Promise<RTCSessionDescriptionInit>}
|
||||
|
||||
Reference in New Issue
Block a user