iflytek/astron-agent · error · DOMException

InvalidStateError

InvalidStateError

Error message

The RTCPeerConnection's signalingState is 'closed'.

What it means

adapter.js's addTrack polyfill checks this.signalingState before doing anything: if the RTCPeerConnection has been closed (signalingState === 'closed', typically after pc.close()), any addTrack call is illegal and it throws DOMException("The RTCPeerConnection's signalingState is 'closed'.", 'InvalidStateError'). This mirrors the native spec behavior that a closed connection cannot accept new tracks.

Solutions

  1. Check pc.signalingState !== 'closed' (or pc.connectionState) before addTrack and bail/recreate the pc if closed.
  2. Guard async callbacks: capture the pc in a ref and verify it is still the active connection inside the .then before adding tracks.
  3. Recreate the RTCPeerConnection if the session must continue after close.
  4. Ensure SDK teardown is complete and idempotent so a later publish gets a fresh connection.

Example fix

// before
navigator.mediaDevices.getUserMedia({audio:true}).then(s => pc.addTrack(s.getAudioTracks()[0], s));
// after
navigator.mediaDevices.getUserMedia({audio:true}).then(s => {
  if (pc.signalingState === 'closed') return; // or recreate pc
  pc.addTrack(s.getAudioTracks()[0], s);
});
Defensive patterns

Strategy: try-catch

Validate before calling

if (!pc || pc.signalingState === 'closed') { pc = createNewPeerConnection(); }

Type guard

const isUsable = (pc) => pc instanceof RTCPeerConnection && pc.signalingState !== 'closed';

Try / catch

try { pc.addTrack(track, stream); } catch (e) {
  if (e.name === 'InvalidStateError' && pc.signalingState === 'closed') {
    pc = recreatePeerConnection();
    pc.addTrack(track, stream);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling pc.addTrack(track, stream) — or an SDK API that does — after pc.close() was called, or after the connection failed/was torn down; also happens when an async callback (e.g. getUserMedia .then) resolves after the component already closed the connection.

Common situations: Unmounting a player component while getUserMedia is still pending, then the promise resolves and adds the track to the closed pc; retry logic calling addTrack on a stale connection after a reconnect created a new pc.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12). Data as JSON: /api/errors/3dc0e086dd0856b7. Report an issue: GitHub.

Appendix: source

Thrown at console/frontend/src/utils/avatar-sdk-web_3.1.2.1002/xrtc-player-BJTnVhG9.js:19402

      Object.keys(e._reverseStreams || []).forEach(t => {
        const r = e._reverseStreams[t];
        i = i.replace(new RegExp(e._streams[r.id].id, 'g'), r.id);
      }),
      new RTCSessionDescription({ type: t.type, sdp: i })
    );
  }
  ((e.RTCPeerConnection.prototype.removeStream = function (e) {
    ((this._streams = this._streams || {}),
      (this._reverseStreams = this._reverseStreams || {}),
      n.apply(this, [this._streams[e.id] || e]),
      delete this._reverseStreams[
        this._streams[e.id] ? this._streams[e.id].id : e.id
      ],
      delete this._streams[e.id]);
  }),
    (e.RTCPeerConnection.prototype.addTrack = function (t, i) {
      if ('closed' === this.signalingState)
        throw new DOMException(
          "The RTCPeerConnection's signalingState is 'closed'.",
          'InvalidStateError'
        );
      const r = [].slice.call(arguments, 1);
      if (1 !== r.length || !r[0].getTracks().find(e => e === t))
        throw new DOMException(
          'The adapter.js addTrack polyfill only supports a single  stream which is associated with the specified track.',
          'NotSupportedError'
        );
      const n = this.getSenders().find(e => e.track === t);
      if (n)
        throw new DOMException('Track already exists.', 'InvalidAccessError');
      ((this._streams = this._streams || {}),
        (this._reverseStreams = this._reverseStreams || {}));
      const o = this._streams[i.id];
      if (o)
        (o.addTrack(t),
          Promise.resolve().then(() => {

View on GitHub (pinned to 5e758547a8)