Search code examples
javascripthtmlaudiocameragetusermedia

getUserMedia() audio blocks camera


I have a forum post form that allows the poster to attach an audio recording and/or a webcam snapshot. Both of these are implemented using the navigator.getUserMedia() API. For the audio I built off a variant of Matt Diamond's well-known recorder.js library, and for the camera capture I adapted some script offered up by David Walsh, available here.

Each function on its own works beautifully. The problem is when a poster tries to make a voice recording and then take a picture, the <video> element doesn't load the camera stream. The browser asks for permission, the webcam light even lights up, but nothing appears on the screen. If I 'restart' the video stream (with a 'take another picture' button that restarts all the relevant elements), it works again, even though it uses the exact same code as the button that starts the camera in the first place.

In the opposite order, first camera, then microphone, everything works fine.

A little code for clarity's sake:

The getUserMedia and AudioContext objects are obtained in the audio recording script thus:

try {
  window.AudioContext = window.AudioContext || window.webkitAudioContext;
  navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
  window.URL = window.URL || window.webkitURL;
  if (navigator.getUserMedia === !1) {
    alert('Your browser doesn\'t support audio recording');
  }
function record (o, i) {
    if (e.voice.initCalled === !1) {
        this.init();
        e.voice.initCalled = !0;
    }
    ;
    navigator.getUserMedia({audio: true}, function (t) {
        var r = e.voice.context.createMediaStreamSource(t);
        if (o === !0) {
            r.connect(e.voice.context.destination);
        }
        ;
        e.voice.recorder = new Recorder(r, {workerPath: e.voice.workerPath});
        e.voice.stream = t;
        e.voice.recorder.record();
        i(t);
    }, function () {
        alert('No live audio input');
    });
}

And in the photo capture script, like this:

function startMediaStream() {
    // Put video listeners into place
    if (navigator.getUserMedia) { // Standard
        navigator.getUserMedia(videoObj, function (stream) {
            mediaStream = stream;
            video.src = stream;
            video.play();
        }, errBack);
    } else if (navigator.webkitGetUserMedia) { // WebKit-prefixed
        navigator.webkitGetUserMedia(videoObj, function (stream) {
            mediaStream = stream;
            video.src = window.URL.createObjectURL(stream);
            video.play();
        }, errBack);
    }
    else if (navigator.mozGetUserMedia) { // Firefox-prefixed
        navigator.mozGetUserMedia(videoObj, function (stream) {
            mediaStream = stream;
            video.src = window.URL.createObjectURL(stream);
            video.play();
        }, errBack);
    }
}

The question is, what in the script would cause the stream in the second script to fail or somehow be blocked?

Update

Before posting, I decided to be a mensch and try debugging first. What I discovered is some sort of listener (I'm not quite the JavaScript ninja I wish I was, so some of these more advanced concepts are not totally clear to me -- my native tongue is Java) in the recorder.js script that causes the code to loop endlessly on this line (the first one) as soon as the camera turns on :

this.node.onaudioprocess = function(e){
  if (!recording) return;
  var buffer = [];
  for (var channel = 0; channel < numChannels; channel++){
      buffer.push(e.inputBuffer.getChannelData(channel));
  }
  worker.postMessage({
    command: 'record',
    buffer: buffer
  });
};

Sorry for the lengthy post. Any ideas on what to modify in the recorder.js file in order to not have this problem?


Solution

  • this.node.onaudioprocess is not your problem. I am guessing that below part is:

    audio recorder has this line:

    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
    

    and snapshot has this:

    if (navigator.getUserMedia) {
    ...
    

    so if you try to take snapshot after you record audio, it is bound to go inside the wrong if/else block. My solution would be,

    in a common global space:

    window.AudioContext = window.AudioContext || window.webkitAudioContext;
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
    window.URL = window.URL || window.webkitURL;
    

    then for snapshot, remove all the redundancy, modify it to:

    function startMediaStream() {
        if (navigator.getUserMedia === !1) {
          alert('Your browser doesn\'t support audio recording');
          return;
        }
    
        navigator.getUserMedia({video: true}, function (stream) {
            mediaStream = stream;
            video.src = window.URL.createObjectURL(stream);
            video.play();
        }, errBack);
    }