4

I am capturing a user's audio and video with the navigator.mediaDevices.getUserMedia() and then using MediaRecorder and its ondataavailable to store that video and audio blob locally to upload later.

Now Im dealing with an issue where for some reason the ondataavailable stops being called midway through the recording. I'm not sure why and I get no alerts that anything went wrong. So first, does anyone know why this might happen and how to catch the errors?

Second, I have tried to reproduce. By doing something like this.

navigator.mediaDevices.getUserMedia({ audio: true, video: true })
  .then(function(camera) {
          local_media_stream = camera;
          camera.getVideoTracks()[0].onended = function() { console.log("VIDEO ENDED") }
          camera.getAudioTracks()[0].onended = function() { console.log("Audio ENDED") }
          camera.onended = function() { console.log("--- ENDED") }
          camera.onremovetrack = (event) => { console.log(`${event.track.kind} track removed`); };
  }).catch(function(error) {
        alert('Unable to capture your camera. Please check logs.' + error);
        console.error(error);
  });

And recording the stream with

recorder = new MediaRecorder(local_media_stream, {
    mimeType: encoding_options,
    audioBitsPerSecond: 128000,
    videoBitsPerSecond: bits_per_second,
});
recorder.ondataavailable = function(e) {
    save_blob(e.data, blob_index)
    blob_index++;
}
recorder.onstop = function(e) {
    console.log("recorder stopped");
    console.log(e)
}
recorder.onerror = function(error) {
    console.log("recorder error");
    alert(error)
    throw error;
}
recorder.onstart = function() {
    console.log('started');
};
recorder.onpause = function() {
    console.log('paused');
};
recorder.onresume = function() {
    console.log('resumed');
};
recorder.start(15000)

Then I try to kill the stream manually to hopefully reproduce whatever issue is occurring by doing

local_media_stream.getVideoTracks()[0].stop()

Now ondataavailable is no longer called but none of the onended events were called. The recording is still going and the local_media_stream is still active.

If I kill the audio too

local_media_stream.getAudioTracks()[0].stop()

Now the local_media_stream is not active but still no event was called telling me the stream stopped and the recorder is still going but ondatavailable is never being called.

What can I do? I want to know that the local stream is being recorded successfully and if not be alerted so I can at least inform the user that the recording is no longer saving.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
John
  • 87
  • 1
  • 11

2 Answers2

2

MediaRecorder has a recorder.stop() method. I don't see you calling it in your example code. Try calling it.

When you call track[n].stop() on the tracks of your media stream, you tell them to stop feeding data to MediaRecorder. So, unsurprisingly, MediaRecorder stops generating its coded output stream.

You also might, if you're running on Google Chrome, try a shorter timeslice than your recorder.start(15000). Or force the delivery of your dataavailable event by using recorder.requestData().

Edit When you call .requestData(), it invokes the ondataavailable event handler. (And, if you specified a timeslice in your .start() call the handler is called automatically.) Each call to that handler delivers the coded media data since the previous call. If you need the whole data stream you can accumulate it in your handler. But when you do that, of course, it needs to go into the browser's RAM heap, so you can't just keep accumulating it indefinitely.

O. Jones
  • 103,626
  • 17
  • 118
  • 172
  • Yea I dont call recorder.stop(). For some reason the user's recorder never fires the ondataavailable event anymore. I suspect something happens to their media stream. The only wayI can reproduce is stopping the video stream and the onended event doesnt get fired for the track and recorder.onstop doesnt get called either. – John Jan 28 '21 at 21:34
  • `recorder.start(15000)` did the magic, thanks – Yasser Nascimento Jul 22 '21 at 20:12
  • 1
    Will recorder.requestData() display data for the whole recording to that point or the data from the last time recorder.requestData() was called? If its the whole recording, how can I get just the data from the last time recorder.requestData() was called? – John Oct 20 '22 at 18:40
0

Stopping both tracks should stop your recorder, it does for me in both FF and Chrome: https://jsfiddle.net/gpt51d6y/

But that's very improbable your users are calling stop() themselves.
Most probably the problem isn't in the MediaRecorder, but before that in the MediaStream's tracks.
A MediaRecorder for which all its tracks are muted won't emit new dataavailable event, because from its perspective, there is still something that might be going on, the track may unmute at any time.

Think for instance of a microphone with an hardware push-to-talk feature, the track from this microphone would get muted every time the user releases such button, but the MediaRecorder, even though it won't record anything during this time, still has to increment its internal timing, so that the "gaps" don't get "glued" in the final media. However, since it had nothing passed to its graph, it won't either emit new dataavailable events, it will simply adjust the timestamp in the next chunk that will get emitted.

For your case of trying to find where the problem comes from, you can try to listen for the MediaRecorder's error event, it may fire in some cases.

But you should also definitely add events to every tracks of its stream, and don't forget the mute event:

recorder.stream.getTracks().forEach( (track) => {
  track.onmute = track.onended = console.warn;
};
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • But if you only stop() the video track, the ondataavailable wont be called anymore and the onended callback wont be called. This is my guess to what is happening but I dont even know. – John Jan 28 '21 at 21:32
  • 1
    @John AFK right now so I can't double check but if you only stop one of the tracks the recorder should still record the other one yes. But anyway, your users are not xalling `.stop()` something else happens on their end, use the events I talked about in my answer to find out what happens there. – Kaiido Jan 28 '21 at 22:00
  • @John I could try and indeed Chrome is buggy here, I will open an issue. By specs they should continue recording "until all the tracks have ended" (Firefox does correctly). For the time being, you could catch this by handling the "ended" event of all the tracks, then to ensure you are facing the bug you could call recorder.requestData twice with some delay between both calls. If the second calls results in an empty dataavailableevent.data, the bug happened. – Kaiido Jan 28 '21 at 23:45
  • @John I opened https://bugs.chromium.org/p/chromium/issues/detail?id=1172056 – Kaiido Jan 29 '21 at 03:04
  • Thanks @Kaiido! On another note, you don't happen to know why on the iPad the video goes black due you? :) Another issue I'm trying to solve but not many people seem to be too knowledgeable. Hopefully, you have ideas! – John Jan 29 '21 at 18:22
  • I don't have access to any (recent) iOS device unfortunately no. And you mean videos recorded by that device, right? On every browsers? – Kaiido Jan 30 '21 at 07:38