Failed to set local answer sdp: Called in wrong state: kStable : <p>for a couple of days I'm now stuck with trying to get my webRTC client to work and I can't figure out what I'm doing wrong.
I'm trying to create multi peer webrtc client and am testing both sides with Chrome.
When the callee receives the call and create the Answer, I get the following error:</p>

<pre><code>Failed to set local answer sdp: Called in wrong state: kStable
</code></pre>

<p>The receiving side correctly establishes both video connnections and is showing the local and remote streams. But the caller seems not to receive the callees answer. Can someone hint me what I am doing wrong here?</p>

<p>Here is the code I am using (it's a stripped version to just show the relevant parts and make it better readable)</p>

<pre><code>class WebRTC_Client
{
    private peerConns = {};
    private sendAudioByDefault = true;
    private sendVideoByDefault = true;
    private offerOptions = {
        offerToReceiveAudio: true,
        offerToReceiveVideo: true
    };
    private constraints = {
        "audio": true,
        "video": {
            frameRate: 5,
            width: 256,
            height: 194
        }
    };
    private serversCfg = {
        iceServers: [{
            urls: ["stun:stun.l.google.com:19302"]
        }]
    };
    private SignalingChannel;

    public constructor(SignalingChannel){
        this.SignalingChannel = SignalingChannel;
        this.bindSignalingHandlers();
    }

    /*...*/

    private gotStream(stream) {
        (&lt;any&gt;window).localStream = stream;
        this.videoAssets[0].srcObject = stream;
     }

    private stopLocalTracks(){}

    private start() {
        var self = this;

        if( !this.isReady() ){
            console.error('Could not start WebRTC because no WebSocket user connectionId had been assigned yet');
        }

        this.buttonStart.disabled = true;

        this.stopLocalTracks();

        navigator.mediaDevices.getUserMedia(this.getConstrains())
            .then((stream) =&gt; {
                self.gotStream(stream);
                self.SignalingChannel.send(JSON.stringify({type: 'onReadyForTeamspeak'}));
            })
            .catch(function(error) { trace('getUserMedia error: ', error); });
    }

    public addPeerId(peerId){
        this.availablePeerIds[peerId] = peerId;
        this.preparePeerConnection(peerId);
    }

    private preparePeerConnection(peerId){
        var self = this;

        if( this.peerConns[peerId] ){
            return;
        }

        this.peerConns[peerId] = new RTCPeerConnection(this.serversCfg);
        this.peerConns[peerId].ontrack = function (evt) { self.gotRemoteStream(evt, peerId); };
        this.peerConns[peerId].onicecandidate = function (evt) { self.iceCallback(evt, peerId); };
        this.peerConns[peerId].onnegotiationneeded = function (evt) { if( self.isCallingTo(peerId) ) { self.createOffer(peerId); } };

        this.addLocalTracks(peerId);
    }

    private addLocalTracks(peerId){
        var self = this;

        var localTracksCount = 0;
        (&lt;any&gt;window).localStream.getTracks().forEach(
            function (track) {
                self.peerConns[peerId].addTrack(
                    track,
                    (&lt;any&gt;window).localStream
            );
                localTracksCount++;
            }
        );
        trace('Added ' + localTracksCount + ' local tracks to remote peer #' + peerId);
    }

    private call() {
        var self = this;

        trace('Start calling all available new peers if any available');

        // only call if there is anyone to call
        if( !Object.keys(this.availablePeerIds).length ){
            trace('There are no callable peers available that I know of');
            return;
        }

        for( let peerId in this.availablePeerIds ){
            if( !this.availablePeerIds.hasOwnProperty(peerId) ){
                continue;
            }
            this.preparePeerConnection(peerId);
        }
    }

    private createOffer(peerId){
        var self = this;

        this.peerConns[peerId].createOffer( this.offerOptions )
            .then( function (offer) { return self.peerConns[peerId].setLocalDescription(offer); } )
            .then( function () {
                trace('Send offer to peer #' + peerId);
                self.SignalingChannel.send(JSON.stringify({ "sdp": self.peerConns[peerId].localDescription, "remotePeerId": peerId, "type": "onWebRTCPeerConn" }));
            })
            .catch(function(error) { self.onCreateSessionDescriptionError(error); });
    }

    private answerCall(peerId){
        var self = this;

        trace('Answering call from peer #' + peerId);

        this.peerConns[peerId].createAnswer()
            .then( function (answer) { return self.peerConns[peerId].setLocalDescription(answer); } )
            .then( function () {
                trace('Send answer to peer #' + peerId);
                self.SignalingChannel.send(JSON.stringify({ "sdp": self.peerConns[peerId].localDescription, "remotePeerId": peerId, "type": "onWebRTCPeerConn" }));
            })
            .catch(function(error) { self.onCreateSessionDescriptionError(error); });
    }

    private onCreateSessionDescriptionError(error) {
        console.warn('Failed to create session description: ' + error.toString());
    }

    private gotRemoteStream(e, peerId) {
        if (this.audioAssets[peerId].srcObject !== e.streams[0]) {
            this.videoAssets[peerId].srcObject = e.streams[0];
            trace('Added stream source of remote peer #' + peerId + ' to DOM');
        }
    }

    private iceCallback(event, peerId) {
        this.SignalingChannel.send(JSON.stringify({ "candidate": event.candidate, "remotePeerId": peerId, "type": "onWebRTCPeerConn" }));
    }

    private handleCandidate(candidate, peerId) {
        this.peerConns[peerId].addIceCandidate(candidate)
            .then(
                this.onAddIceCandidateSuccess,
                this.onAddIceCandidateError
            );
        trace('Peer #' + peerId + ': New ICE candidate: ' + (candidate ? candidate.candidate : '(null)'));
    }

    private onAddIceCandidateSuccess() {
        trace('AddIceCandidate success.');
    }

    private onAddIceCandidateError(error) {
        console.warn('Failed to add ICE candidate: ' + error.toString());
    }

    private hangup() {}

    private bindSignalingHandlers(){
        this.SignalingChannel.registerHandler('onWebRTCPeerConn', (signal) =&gt; this.handleSignals(signal));
    }

    private handleSignals(signal){
        var self = this,
            peerId = signal.connectionId;

        if( signal.sdp ) {
            trace('Received sdp from peer #' + peerId);

            this.peerConns[peerId].setRemoteDescription(new RTCSessionDescription(signal.sdp))
                .then( function () {
                    if( self.peerConns[peerId].remoteDescription.type === 'answer' ){
                        trace('Received sdp answer from peer #' + peerId);
                    } else if( self.peerConns[peerId].remoteDescription.type === 'offer' ){
                        trace('Received sdp offer from peer #' + peerId);
                        self.answerCall(peerId);
                    } else {
                        trace('Received sdp ' + self.peerConns[peerId].remoteDescription.type + ' from peer #' + peerId);
                    }
                })
                .catch(function(error) { trace('Unable to set remote description for peer #' + peerId + ': ' + error); });
        } else if( signal.candidate ){
            this.handleCandidate(new RTCIceCandidate(signal.candidate), peerId);
        } else if( signal.closeConn ){
            trace('Closing signal received from peer #' + peerId);
            this.endCall(peerId,true);
        }
    }
}
</code></pre>
