{"record":{"id":"3dc0e086dd0856b7","repo":"iflytek/astron-agent","slug":"invalidstateerror","errorCode":"InvalidStateError","errorMessage":"The RTCPeerConnection's signalingState is 'closed'.","messagePattern":"The RTCPeerConnection's signalingState is 'closed'\\.","errorType":"exception","errorClass":"DOMException","httpStatus":null,"severity":"error","filePath":"console/frontend/src/utils/avatar-sdk-web_3.1.2.1002/xrtc-player-BJTnVhG9.js","lineNumber":19402,"sourceCode":"      Object.keys(e._reverseStreams || []).forEach(t => {\n        const r = e._reverseStreams[t];\n        i = i.replace(new RegExp(e._streams[r.id].id, 'g'), r.id);\n      }),\n      new RTCSessionDescription({ type: t.type, sdp: i })\n    );\n  }\n  ((e.RTCPeerConnection.prototype.removeStream = function (e) {\n    ((this._streams = this._streams || {}),\n      (this._reverseStreams = this._reverseStreams || {}),\n      n.apply(this, [this._streams[e.id] || e]),\n      delete this._reverseStreams[\n        this._streams[e.id] ? this._streams[e.id].id : e.id\n      ],\n      delete this._streams[e.id]);\n  }),\n    (e.RTCPeerConnection.prototype.addTrack = function (t, i) {\n      if ('closed' === this.signalingState)\n        throw new DOMException(\n          \"The RTCPeerConnection's signalingState is 'closed'.\",\n          'InvalidStateError'\n        );\n      const r = [].slice.call(arguments, 1);\n      if (1 !== r.length || !r[0].getTracks().find(e => e === t))\n        throw new DOMException(\n          'The adapter.js addTrack polyfill only supports a single  stream which is associated with the specified track.',\n          'NotSupportedError'\n        );\n      const n = this.getSenders().find(e => e.track === t);\n      if (n)\n        throw new DOMException('Track already exists.', 'InvalidAccessError');\n      ((this._streams = this._streams || {}),\n        (this._reverseStreams = this._reverseStreams || {}));\n      const o = this._streams[i.id];\n      if (o)\n        (o.addTrack(t),\n          Promise.resolve().then(() => {","sourceCodeStart":19384,"sourceCodeEnd":19420,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/console/frontend/src/utils/avatar-sdk-web_3.1.2.1002/xrtc-player-BJTnVhG9.js#L19384-L19420","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check pc.signalingState !== 'closed' (or pc.connectionState) before addTrack and bail/recreate the pc if closed.","Guard async callbacks: capture the pc in a ref and verify it is still the active connection inside the .then before adding tracks.","Recreate the RTCPeerConnection if the session must continue after close.","Ensure SDK teardown is complete and idempotent so a later publish gets a fresh connection."],"exampleFix":"// before\nnavigator.mediaDevices.getUserMedia({audio:true}).then(s => pc.addTrack(s.getAudioTracks()[0], s));\n// after\nnavigator.mediaDevices.getUserMedia({audio:true}).then(s => {\n  if (pc.signalingState === 'closed') return; // or recreate pc\n  pc.addTrack(s.getAudioTracks()[0], s);\n});","handlingStrategy":"try-catch","validationCode":"if (!pc || pc.signalingState === 'closed') { pc = createNewPeerConnection(); }","typeGuard":"const isUsable = (pc) => pc instanceof RTCPeerConnection && pc.signalingState !== 'closed';","tryCatchPattern":"try { pc.addTrack(track, stream); } catch (e) {\n  if (e.name === 'InvalidStateError' && pc.signalingState === 'closed') {\n    pc = recreatePeerConnection();\n    pc.addTrack(track, stream);\n  } else throw e;\n}","preventionTips":["Check signalingState before every addTrack/addStream call","In async getUserMedia callbacks, verify the pc is still the active one (store in ref, compare identity)","Cancel in-flight getUserMedia promises on component unmount/close","Tie peer connection lifecycle to the session, not to transient retries"],"tags":["webrtc","adapter-js","closed-connection","invalid-state","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}