Chrome MediaRecorder API can not record REMOTE video from FreeSwitch, but local media can be recorded

1.4k views Asked by At

Issue: When we record remote video (only) streams in Chrome by using the built-in MediaRecorder API, it returns blob objects; when we merge all blob files with the Blob API and create an object URL, it’s not playable with html5 players.

I’m using jssip on the client side and FreeSwitch on the server side.

Example code:

var options = {
    videoBitsPerSecond: 2500000,
    ignoreMutedMedia: true,
    mimeType: 'video/webm'
}
_this.recorder = new MediaRecorder(e.stream, options);
_this.recorder.ondataavailable = function (event) {
    if (event.data.size > 0) {
        chunks.push(event.data);
    }
};
_this.recorder.onstop = function () {
    var superBuffer = new Blob(chunks, {
        type: 'video/webm'
    });

    var url = URL.createObjectURL(superBuffer);
    var a = document.createElement('a');
    document.body.appendChild(a);
    a.style = 'display: none';
    a.href = url;
    a.download = 'test.webm';
    a.click();
}

_this.recorder.start();

With that code example we are recording video from a REMOTE stream. Once recording is stopped, we are creating a blob file, which is not playable in Chrome.

The blob file has size and type as expected.

If we change _this.recorder = new MediaRecorder(e.stream, options) to _this.recorder = new MediaRecorder(localMediaStream, options), then the file can be recorded and it is playable.

If we play a remote or local stream directly to the video object, then video is playing and there are no problems with that.

The very same code with REMOTE and LOCAL media streams is working very well in FireFox. The problem is only in Chrome.

My mediaConstraints is this:

mediaConstraints = {
    audio: false,
    video: true
}

With those mediaConstraints, recording is not working.

But when I change mediaConstraints to:

mediaConstraints = {
    audio: true,
    video: true
}

…then REMOTE video can be recorded and it is playable in Chrome.

Here is the invite SDP body:

>     v=0
>     o=- 8064839774906199900 2 IN IP4 127.0.0.1
>     s=-
>     t=0 0
>     a=group:BUNDLE video
>     a=msid-semantic: WMS mn9jKVP8YQWdoANy4IuThCbAkYDNH6Rn48wy
>     m=video 45331 UDP/TLS/RTP/SAVPF 96 98 100 102 127 97 99 101 125
>     c=IN IP4 x.x.x.x
>     a=rtcp:9 IN IP4 0.0.0.0
>     a=candidate:2702239670 1 udp 2122260223 192.168.1.103 45331 typ host generation 0 network-id 2 network-cost 10
>     a=candidate:542695682 1 udp 1686052607 x.x.x.x 45331 typ srflx raddr 192.168.1.103 rport 45331 generation 0 network-id 2
> network-cost 10
>     a=candidate:4019395398 1 tcp 1518280447 192.168.1.103 9 typ host tcptype active generation 0 network-id 2 network-cost 10
>     a=ice-ufrag:WoJH
>     a=ice-pwd:0plhRV6alFSPYNFZzPeLIQjD
>     a=fingerprint:sha-256 76:74:B1:90:73:A5:7C:AC:1F:D5:F6:99:63:FC:60:6F:07:C0:F6:6A:CB:78:30:A4:19:3E:09:27:F0:EB:04:5E
>     a=setup:actpass
>     a=mid:video
>     a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
>     a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
>     a=extmap:4 urn:3gpp:video-orientation
>     a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
>     a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
>     a=sendrecv
>     a=rtcp-mux
>     a=rtcp-rsize
>     a=rtpmap:96 VP8/90000
>     a=rtcp-fb:96 ccm fir
>     a=rtcp-fb:96 nack
>     a=rtcp-fb:96 nack pli
>     a=rtcp-fb:96 goog-remb
>     a=rtcp-fb:96 transport-cc
>     a=rtpmap:98 VP9/90000
>     a=rtcp-fb:98 ccm fir
>     a=rtcp-fb:98 nack
>     a=rtcp-fb:98 nack pli
>     a=rtcp-fb:98 goog-remb
>     a=rtcp-fb:98 transport-cc
>     a=rtpmap:100 H264/90000
>     a=rtcp-fb:100 ccm fir
>     a=rtcp-fb:100 nack
>     a=rtcp-fb:100 nack pli
>     a=rtcp-fb:100 goog-remb
>     a=rtcp-fb:100 transport-cc
>     a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
>     a=rtpmap:102 red/90000
>     a=rtpmap:127 ulpfec/90000
>     a=rtpmap:97 rtx/90000
>     a=fmtp:97 apt=96
>     a=rtpmap:99 rtx/90000
>     a=fmtp:99 apt=98
>     a=rtpmap:101 rtx/90000
>     a=fmtp:101 apt=100
>     a=rtpmap:125 rtx/90000
>     a=fmtp:125 apt=102
>     a=ssrc-group:FID 2091687367 3809002701
>     a=ssrc:2091687367 cname:6FAXPNxjytcrrFeT
>     a=ssrc:2091687367 msid:mn9jKVP8YQWdoANy4IuThCbAkYDNH6Rn48wy 7369ece0-53c3-44b6-8ada-8fd112f8018b
>     a=ssrc:2091687367 mslabel:mn9jKVP8YQWdoANy4IuThCbAkYDNH6Rn48wy
>     a=ssrc:2091687367 label:7369ece0-53c3-44b6-8ada-8fd112f8018b
>     a=ssrc:3809002701 cname:6FAXPNxjytcrrFeT
>     a=ssrc:3809002701 msid:mn9jKVP8YQWdoANy4IuThCbAkYDNH6Rn48wy 7369ece0-53c3-44b6-8ada-8fd112f8018b
>     a=ssrc:3809002701 mslabel:mn9jKVP8YQWdoANy4IuThCbAkYDNH6Rn48wy
>     a=ssrc:3809002701 label:7369ece0-53c3-44b6-8ada-8fd112f8018b

And here is the server response SDP:

v=0
o=matrix 1346400881 1346400882 IN IP4 x.x.x.x
s=matrix
c=IN IP4 x.x.x.x
t=0 0
m=video 27194 UDP/TLS/RTP/SAVPF 96
b=AS:1024
a=rtpmap:96 VP8/90000
a=fingerprint:sha-256 8E:7D:E1:1A:77:BA:97:64:39:B1:41:FC:E5:28:8A:9E:85:EB:FB:DB:A7:57:0F:EF:E6:CA:AF:EE:45:89:47:D5
a=setup:active
a=rtcp-mux
a=rtcp:27194 IN IP4 x.x.x.x
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=ssrc:1346687882 cname:v1aY4xDDn9cbcqMy
a=ssrc:1346687882 msid:NcXTCdxPIZFIYcoKvW2crReVhSijNter v0
a=ssrc:1346687882 mslabel:NcXTCdxPIZFIYcoKvW2crReVhSijNter
a=ssrc:1346687882 label:NcXTCdxPIZFIYcoKvW2crReVhSijNterv0
a=ice-ufrag:dEAumFftnoD1HHi2
a=ice-pwd:yiQtGkWTkZVmgxKj2bEfGKyU
a=candidate:1419127948 1 udp 659136 x.x.x.x 27194 typ host generation 0
a=end-of-candidates

What can possibly be the problem here?

1

There are 1 answers

5
Woodsy On

I was struggling with a similar issue so I think what I discovered might help you.

Chrome doesn't support the IgnoreMutedMedia property for MediaRecorder so the media recorder is waiting for the audio track to start before it starts recording. You can test this by running a mediaRecorder.requestData() to fire the ondataavailable event, then look at the value of event.data.size:

setInterval(function(){
    _this.recorder.requestData(); // request data every second
},1000);

_this.recorder.ondataavailable = function (event) {
    console.log("DATA Size: " + event.data.size) // print data size
    if (event.data.size > 0) {
        chunks.push(event.data);
    }
};

So what I had to do was isolate just the video track then create a new MediaStream object with only a video track from the original stream.

_this.recorder = new MediaRecorder(new MediaStream(e.stream.getVideoTracks()), options);

Hope this works for you.