JSFiddle - React, Tailwind, and code Playground
HTML
<video id="left-flix" controls autoplay style="width: 320px; border-bottom: 1px solid #ccc;"></video>
<video id="right-flix" controls autoplay style="width: 320px; border-bottom: 1px solid #ccc;"></video>
<div id="errors" style="color: red;"></div>
<div>
<h1>Use Firefox 59 or later.</h1>
<p>click buttons from left to right</p>
<div>
<h3>Working</h3>
<button onclick="setup()">setup</button>
<button onclick="addStream()">add stream</button>
<button onclick="runSignaling()">signaling!</button>
<button onclick="removeStream()">remove stream</button>
<button onclick="tearDown()">tearDown</button>
</div>
<div>
<h3>Broken</h3>
<button onclick="setup()">setup</button>
<button onclick="runSignaling()">signaling!</button>
<button onclick="addStream()">add stream</button>
<button onclick="runSignaling()">signaling!</button>
<button onclick="removeStream()">remove stream</button>
<button onclick="tearDown()">tearDown</button>
</div>
<h2>Problem</h2>
<ol>
<li>send an offer without any streams first</li>
<li>add a stream to the local peer connection and send an offer</li>
<li>remote does not display the stream, although it receives data</li>
<li>removing the upstream throws an exception</li>
</ol>
<h2>Expected</h2>
<ul>
<li>you can send as many offers with/without stream as you like</li>
<li>removing stream from a peer connection does not throw</li>
</ul>
</div>
JavaScript
var local;
var remote;
var upstreamVideo = document.getElementById('left-flix');
var downstreamVideo = document.getElementById('right-flix');
var err = document.getElementById('errors');
function getVideo() {
return navigator.mediaDevices.getUserMedia({video: true});
}
function setup() {
local = new RTCPeerConnection(null);
remote = new RTCPeerConnection(null);
local.onicecandidate = function(candidate) {
remote.addIceCandidate(candidate.candidate);
};
remote.onicecandidate = function(candidate) {
local.addIceCandidate(candidate.candidate);
};
remote.ontrack = function(event) {
console.info('got downstream', event.streams[0]);
downstreamVideo.srcObject = event.streams[0];
};
}
function signaling() {
return local.createOffer({offerToReceiveVideo: 1})
.then(function(offer){
local.setLocalDescription(offer);
return remote.setRemoteDescription(offer);
})
.then(function() {
return remote.createAnswer();
})
.then(function(answer){
remote.setLocalDescription(answer);
local.setRemoteDescription(answer);
});
}
function addStream() {
return getVideo().then(function(stream){
upstreamVideo.srcObject = stream;
stream.getTracks().forEach(function(track) {local.addTrack(track, stream)});
});
}
function removeStream() {
local.getSenders().forEach(function(sender) {local.removeTrack(sender);});
}
function tearDown() {
local.close();
remote.close();
upstreamVideo.srcObject = null;
downstreamVideo.srcObject = null;
}
window.setup = setup;
window.addStream = addStream;
window.removeStream = removeStream;
window.runSignaling = signaling;
window.tearDown = tearDown;
window.onerror = function(...error) {
err.innerHTML += error + '<br>';
};