So if producer.close() has been called and later Transport calls handler.stopSending() this happens in Chrome111 (and probably in many other handlers):
async stopSending(localId: string): Promise<void>
{
this.assertSendDirection();
logger.debug('stopSending() [localId:%s]', localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver)
{
throw new Error('associated RTCRtpTransceiver not found');
}
transceiver.sender.replaceTrack(null);
this._pc.removeTrack(transceiver.sender);
const mediaSectionClosed =
this._remoteSdp!.closeMediaSection(transceiver.mid!);
and this._pc.removeTrack(transceiver.sender); will throw with "DOMException: The peer connection is closed".
Here we are NOT doing await this._handler.stopSending(producer.localId) or this._handler.stopSending(producer.localId).catch(() => {}) so if it rejects then an unhandled exception happens.
So possible solutions:
Solution 1
In all methods in handlers (specially in stopSending() check if this._pc.signalingState === 'closed' and if so do nothing (early return).
Solution 2
In the producer.on('@close') listener in Transport.ts add await to this._handler.stopSending(producer.localId). Problems:
It will make things slower since the queue must wait for the stopSending() method to resolve (or reject) before it processes next enqueued task.
It will still show the warning "producer.close() failed". We don't want to show that error since it's not legitimate. We closed the Producer already so it shouldn't happen.
Proposed Solution
A mix between both above:
Check if this._pc.signalingState === 'closed' in all handler methods and return early if so. NOTE: Not in all methods because ofc we want that producer.pause() throws if the Producer is closed.
Add await into all handler related tasks passed to the queue in Transport.ts.
WOW!
But this is funny because right now the queue is NOT enqueing anything. It's just enqueing NON async methods since there is no await at all. To be clear, this._awaitQueue.push(async () => this._handler.stopSending(producer.localId) ... is like introducing a sync function in the queue. The task given to AwaitQueue is async () => this._handler.stopSending(producer.localId). This is gonna return immediately. For it to really wait until the task completes, it should be async () => await this._handler.stopSending(producer.localId).
So if
producer.close()
has been called and laterTransport
callshandler.stopSending()
this happens inChrome111
(and probably in many other handlers):and
this._pc.removeTrack(transceiver.sender);
will throw with "DOMException: The peer connection is closed".It happens here in
Transport.ts
:This block is just wrong:
Here we are NOT doing
await this._handler.stopSending(producer.localId)
orthis._handler.stopSending(producer.localId).catch(() => {})
so if it rejects then an unhandled exception happens.So possible solutions:
Solution 1
In all methods in handlers (specially in
stopSending()
check ifthis._pc.signalingState === 'closed'
and if so do nothing (early return).Solution 2
In the
producer.on('@close')
listener inTransport.ts
addawait
tothis._handler.stopSending(producer.localId)
. Problems:stopSending()
method to resolve (or reject) before it processes next enqueued task.Proposed Solution
A mix between both above:
this._pc.signalingState === 'closed'
in all handler methods and return early if so. NOTE: Not in all methods because ofc we want thatproducer.pause()
throws if the Producer is closed.await
into all handler related tasks passed to the queue inTransport.ts
.WOW!
But this is funny because right now the queue is NOT enqueing anything. It's just enqueing NON async methods since there is no
await
at all. To be clear,this._awaitQueue.push(async () => this._handler.stopSending(producer.localId) ...
is like introducing a sync function in the queue. Thetask
given toAwaitQueue
isasync () => this._handler.stopSending(producer.localId)
. This is gonna return immediately. For it to really wait until the task completes, it should beasync () => await this._handler.stopSending(producer.localId)
.