Web cam view goes black, upon 'Start Recording'

The webcam view appears upon page load (successfully)(Chrome), but when the ‘Start Recording’ button is selected the screen goes black (temporarily) and the dialog pops up “Unable to capture your camera. Please check console logs.” But, if I refresh the page, the webcam view will appear again, and ‘start recording’ selected will work (or show a black screen again). How can I resolve the black screen from appearing, and not having to keep refreshing the page?

I’ve attached two images. I look forward to your comments/help/suggestions. Here’s the code:

<!DOCTYPE html>
<head>
<title>Video Recording | RecordRTC</title>
<script src="https://cdn.webrtc-experiment.com/RecordRTC.js"></script>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
</head>

<body>
    <style>
        html,
        body {
            margin: 0!important;
            padding: 0!important;
            width: 100%;
        }

        video {
            width: 50%;
            float: left;
        }
    </style>
    <header id="header"></header>
    <h1>WebCamRecordRTC</h1>

    <br>
    <!--
    <button id="btn-start-camera" onclick="startCamera()">Re-Record</button>
    <button id="btn-stop-camera" onclick="stopCamera()" disabled>Stop Camera</button>
    -->
    <button id="btn-start-recording" onclick="resetStartRecording()">Start Recording</button>
    <button id="btn-stop-recording" onclick="stopRecording()" disabled>Stop Recording</button>
	<button id="btn-start-camera" onclick="resetCamera()">Reset Cam</button>
    <button id="upload-video" onclick="InitUploading()" disabled>UPLOAD</button>

    <hr>
    <video autoplay></video>
    <!--
    <video controls autoplay id="recorded_video"></video>
    -->
    <script>
        var video = document.querySelector('video');
        var recorded_video = video;
        var recorder; // globally accessible
        var global_camera;

        function captureCamera(callback) {
            navigator.mediaDevices.getUserMedia({
                audio: true,
                video: true
            }).then(function (camera) {
                callback(camera);
            }).catch(function (error) {
                alert('Unable to capture your camera. Please check console logs.');
                console.error(error);
            });
        }

        function startCamera() {
            //document.getElementById('btn-start-camera').disabled = true;
            document.getElementById('btn-start-recording').disabled = false;
            //document.getElementById('btn-stop-camera').disabled = false;
            captureCamera(function (camera) {
                setSrcObject(camera, video);
                video.play();
                global_camera = camera;
                video.controls = false;
            });
        }


		//Reset the Camera
		function resetCamera(){
				global_camera.getTracks().forEach(function (track) {
                track.stop();
            });
				global_camera.stop();
				startCamera();
		}

		//Reset The camera and Start Recording
		function resetStartRecording(){
				global_camera.getTracks().forEach(function (track) {
                track.stop();
            });
				global_camera.stop();

            document.getElementById('btn-start-recording').disabled = false;
            captureCamera(function (camera) {
                setSrcObject(camera, video);
				video.currentTime = 0;
                video.play();
                global_camera = camera;
                startRecording();
            });
		}


        function startRecording() {
            recorder = RecordRTC(global_camera, {
                type: 'video'
            });
            recorder.camera = global_camera;
            document.getElementById('btn-start-recording').disabled = true;
            document.getElementById('btn-stop-recording').disabled = false;
  		    video.controls = true;
            recorder.startRecording();
        }

        function stopRecording() {
            document.getElementById('btn-stop-recording').disabled = true;
            document.getElementById('upload-video').disabled = false;
            document.getElementById('btn-start-recording').disabled = false;
            video.controls = true;
            recorder.stopRecording(stopRecordingCallback);
        }

        function stopCamera() {
            if (recorder)
                stopRecording();
            // release camera
            global_camera.getTracks().forEach(function (track) {
                track.stop();
            });

            global_camera.stop();
        }

        function stopRecordingCallback() {
            recorded_video.src = URL.createObjectURL(recorder.getBlob());
            PrepareBlob();
            document.getElementById("upload-video").disabled = false;
            recorder.destroy();
            recorder = null;
            stopCamera();
        }
        var blob, fileName, fileObject;

        function PrepareBlob() {
            // get recorded blob
            blob = recorder.getBlob();
            // generating a random file name
            fileName = getFileName('webm');
            // we need to upload "File" --- not "Blob"
            fileObject = new File([blob], fileName, {
                type: 'video/webm'
            });
        }

        function InitUploading() {
            uploadToPHPServer(fileObject, function (response, fileDownloadURL) {
                if (response !== 'ended') {
                    document.getElementById('header').innerHTML = response; // upload progress
                    return;
                }
                				document.getElementById('header').innerHTML = '<a href="' + fileDownloadURL + '" target="_blank">' + fileDownloadURL + '</a>';
								alert('Successfully uploaded recorded blob.');

						                // preview uploaded file
						                video.src = fileDownloadURL;
						                // open uploaded file in a new tab
						                window.open(fileDownloadURL);
						            });
		        }

        function uploadToPHPServer(blob, callback) {
            // create FormData
            var formData = new FormData();
            formData.append('video-filename', blob.name);
            formData.append('video-blob', blob);
            callback('Uploading recorded-file to server.');
            makeXMLHttpRequest('save.php', formData, function (progress) {
                if (progress !== 'upload-ended') {
                    callback(progress);
                    return;
                }
                var initialURL = 'uploadz/' + blob.name;
                callback('ended', initialURL);
            });
        }

        function makeXMLHttpRequest(url, data, callback) {
            var request = new XMLHttpRequest();
            request.onreadystatechange = function () {
                if (request.readyState == 4 && request.status == 200) {
                    if (request.responseText === 'success') {
                        callback('Upload Complete');
                        return;
                    }
                    //alert(request.responseText);
                    window.location.href = '../index.com/';
                    return;
                }
            };
            request.upload.onloadstart = function () {
                callback('Upload started...');
            };
            request.upload.onprogress = function (event) {
                callback('Upload Progress ' + Math.round(event.loaded / event.total * 100) + "%");
            };
            request.upload.onload = function () {
                callback('Progress Ending');
            };
            request.upload.onload = function () {
                callback('Upload Complete');
            };
            request.upload.onerror = function (error) {
                callback('Upload Failed.');
            };
            request.upload.onabort = function (error) {
                callback('Upload Aborted.');
            };
            request.open('POST', url);
            request.send(data);
        }
        // this function is used to generate random file name
        function getFileName(fileExtension) {
            var d = new Date();
            var year = d.getUTCFullYear();
            var month = d.getUTCMonth();
            var date = d.getUTCDate();
            return 'RecordRTC-' + year + month + date + '-' + getRandomString() + '.' + fileExtension;
        }

        function getRandomString() {
            if (window.crypto && window.crypto.getRandomValues && navigator.userAgent.indexOf('Safari') === -1) {
                var a = window.crypto.getRandomValues(new Uint32Array(3)),
                    token = '';
                for (var i = 0, l = a.length; i < l; i++) {
                    token += a[i].toString(36);
                }
                return token;
            } else {
                return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, '');
            }
        }
        startCamera();
    </script>
</body>
</html>

From MDN

Although the user granted permission to use the matching devices, a hardware error occurred at the operating system, browser, or Web page level which prevented access to the device.

1 Like

Thanks for your reply. Much appreciated.

Can you tell me how/where you determined that error, please?

Also, I’m not sure how I would get a solution from that message.
Any additional help/explanation will be welcomed.
Much thanks again

I searched for NotReadableError

I’m not up on webcam stuff but found this on another search (I searched for “web page level webcam”)

Thanks again for your reply.
I appreciate the link, but I have already successfully have the webcam on the web page.
I’m just looking for javascript code assistance with why it turns black upon select the “Start Recording” button, and suggestions as to why it seems to need to be refreshed to access the web cam. It works after the black screen and refreshing once (or twice).

Any other help, regarding javascript will be welcomed.

I think that’s because you’re requesting camera permissions anew when clicking that button, and during that the capturing is getting blocked. The solution would be to use the same stream you’re using for playback, rather than starting a new stream for recording.

It works for me without page refresh… so this is probably an issue with your system/browser settings, rather than with your code itself.

Thanks so much for your reply/comments.

I would be interested to see if not starting a new stream would help. I need guidance in how to modify in order to “use the same stream you’re using for playback, rather than starting a new stream for recording”.
I look forward to any help with that.

Sorry, I only just noticed you’re using an external library for that… I’m not familiar with RecordRTC I’m afraid, but here’s an example using the native recording API:

<video src=""></video>
<button class="start" disabled>Start recording</button>
<button class="stop" disabled>Stop recording</button>
const video = document.querySelector('video')
const start = document.querySelector('.start')
const stop = document.querySelector('.stop')

const initRecorder = stream => {
  // Initialise the recorder from the current stream.
  // This won't do anything until the recorder got
  // actually started
  const recorder = new MediaRecorder(stream)

  // Set the video source object to the same stream
  // that got passed to the recorder constructor, so
  // you don't have to request it anew for recording
  video.srcObject = stream
  start.removeAttribute('disabled')

  // Start playing once the video source is ready
  video.addEventListener('loadedmetadata', () => {
    video.play()
  })

  // When the recorder has data available, set the video
  // source to that data. Unless specified otherwise,
  // this event gets emitted once the recorder got stopped
  recorder.addEventListener('dataavailable', ({ data }) => {
    video.srcObject = null
    video.src = URL.createObjectURL(data)
  })

  // Start the recorder
  start.addEventListener('click', () => {
    stop.removeAttribute('disabled')
    recorder.start()
  })

  // Stop the recorder, having it emit the data it recorded
  stop.addEventListener('click', () => {
    recorder.stop()
  })
}

navigator
  .mediaDevices
  .getUserMedia({ video: true })
  .then(initRecorder)
  .catch(console.error)

Also here’s a pen so you can see it in action. The idea is to initialise the recorder right away once the stream is available (i.e. the same stream that is used for playback), but start recording only when the start-button got clicked. From a quick look at the RecorderRTC API, something like this should be possible there as well as the constructor takes the stream as its first argument.

Thank you. I like how that works, so I tried to add in the upload piece without success. The record works, but can’t upload. Any additional suggestions will be appreciated.

	<button class="start" disabled>Start</button>
	<button class="stop" disabled>Stop</button>
	<button class="reset" disabled>Reset</button>
	<button class="upload" onclick="InitUploading()" disabled>Upload</button>

    <script>

    const video = document.querySelector('video')
	const start = document.querySelector('.start')
	const stop = document.querySelector('.stop')

	const initRecorder = stream => {

	  const recorder = new MediaRecorder(stream)

	  video.srcObject = stream
	  start.removeAttribute('disabled')

	  video.addEventListener('loadedmetadata', () => {
	    video.play()
	  })

	  recorder.addEventListener('dataavailable', ({ data }) => {
	    video.srcObject = null
	    video.src = URL.createObjectURL(data)
	  })

	  start.addEventListener('click', () => {
	    stop.removeAttribute('disabled')
	    recorder.start()
	  })

	  stop.addEventListener('click', () => {
	    recorder.stop()
	  })
	}

	navigator
	  .mediaDevices
	  .getUserMedia({ video: true })
	  .then(initRecorder)
  .catch(console.error)


// ADD IN THE UPLOAD PIECE??

var blob, fileName, fileObject;

        function PrepareBlob() {
            // get recorded blob
            blob = URL.createObjectURL();
            // generating a random file name
            fileName = getFileName('webm');
            // we need to upload "File" --- not "Blob"
            fileObject = new File([blob], fileName, {
                type: 'video/webm'
            });
        }

        function InitUploading() {
            uploadToPHPServer(fileObject, function (response, fileDownloadURL) {
                if (response !== 'ended') {
                    document.getElementById('header').innerHTML = response; // upload progress
                    return;
                }
                document.getElementById('header').innerHTML = '<a href="' + fileDownloadURL +
                    '" target="_blank">' +
                    fileDownloadURL + '</a>';
                alert('Successfully uploaded recorded blob.');
                // preview uploaded file
                video.src = fileDownloadURL;
                // open uploaded file in a new tab
                window.open(fileDownloadURL);
            });
        }

        function uploadToPHPServer(blob, callback) {
            // create FormData
            var formData = new FormData();
            formData.append('video-filename', blob.name);
            formData.append('video-blob', blob);
            callback('Uploading recorded-file to server.');
            makeXMLHttpRequest('save.php', formData, function (progress) {
                if (progress !== 'upload-ended') {
                    callback(progress);
                    return;
                }
                var initialURL = 'uploads/' + blob.name;
                callback('ended', initialURL);
            });
        }

        function makeXMLHttpRequest(url, data, callback) {
            var request = new XMLHttpRequest();
            request.onreadystatechange = function () {
                if (request.readyState == 4 && request.status == 200) {
                    if (request.responseText === 'success') {
                        callback('Upload Complete');
                        return;
                    }
                    alert(request.responseText);
                    window.location.href = '/index.com/';
                    return;
                }
            };
            request.upload.onloadstart = function () {
                callback('Upload started...');
            };
            request.upload.onprogress = function (event) {
                callback('Upload Progress ' + Math.round(event.loaded / event.total * 100) + "%");
            };
            request.upload.onload = function () {
                callback('Progress Ending');
            };
            request.upload.onload = function () {
                callback('Upload Complete');
            };
            request.upload.onerror = function (error) {
                callback('Upload Failed.');
            };
            request.upload.onabort = function (error) {
                callback('Upload aborted.');
            };
            request.open('POST', url);
            request.send(data);
        }
        // this function is used to generate random file name
        function getFileName(fileExtension) {
            var d = new Date();
            var year = d.getUTCFullYear();
            var month = d.getUTCMonth();
            var date = d.getUTCDate();
            return 'RecordRTC-' + year + month + date + '-' + getRandomString() + '.' + fileExtension;
        }

        function getRandomString() {
            if (window.crypto && window.crypto.getRandomValues && navigator.userAgent.indexOf('Safari') === -1) {
                var a = window.crypto.getRandomValues(new Uint32Array(3)),
                    token = '';
                for (var i = 0, l = a.length; i < l; i++) {
                    token += a[i].toString(36);
                }
                return token;
            } else {
                return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, '');
            }
        }
        startCamera();

  </script>

From what I can see, an immediate problem would be that fileObject is undefined; you’d have to assign the blob to it withing the dataavailable event handler. Mutatis mutandis that might look like

let blob

recorder.addEventListener('dataavailable', ({ data }) => {
  video.srcObject = null
  video.src = URL.createObjectURL(data)

  // Create a blob from the data for upload
  blob = new Blob([data], { type: 'video/webm' })
})

Then the upload part might look like this (I’m using the fetch API here for brevity):

const body = new FormData()

body.append('myvideo', blob)

fetch('upload.php', { method: 'POST', body })
  .then(() => console.log('Success!'))
  .catch(console.error)

Putting it all together, here’s a minimal workable solution (reduced to the essentials w/o the crypto stuff etc.):

Thanks a lot for the additional assistance. Greatly appreciated.
However, I copied your code from here: https://gist.github.com/m3g4p0p/a820071780407874f35b6c843e1906b9

to test, and it did not upload for me successfully. It may be my upload button - what do you think?

<button class="upload" disabled>Upload</button>

Or it may be my upload.php, here:

  <?php
  foreach(array('video', 'audio') as $type) {
  if (isset($_FILES["${type}-blob"])) {
  
  //echo 'uploads/';
  
  $fileName = $_POST["${type}-filename"];
  $uploadDirectory = 'uploads/'.$fileName;
  
  if (!move_uploaded_file($_FILES["${type}-blob"]["tmp_name"], $uploadDirectory)) {
  echo(" problem moving uploaded file");
  }
  
  echo($fileName);
  }
  }
?>

Any additional feedback will be much appreciated. Thanks again

You’re welcome. :-) Well as I said, I omitted all the crypto and filename stuff; the file is simply called myvideo in my example. You’d have to adjust this part accordingly to work with your backend code; if you var_dump($_FILES) you should see the video listed there though.

Edit: whoops, I also had a small typo in the gist… always a good idea to check the console for errors. ^^ Anyway just updated it, so it should work now.

Thanks again for your reply. However, I’m still not seeing any upload. I’m sure I have something not correct. Any help will be welcomed. Here is the code:

	<button class="start" disabled>Start</button>
	<button class="stop" disabled>Stop</button>
	<button class="upload" disabled>Upload</button>

    <script>
const video = document.querySelector('video')
const start = document.querySelector('.start')
const stop = document.querySelector('.stop')
const upload = document.querySelector('.upload')

const initRecorder = stream => {
  const recorder = new MediaRecorder(stream)
  let blob

  video.srcObject = stream
  start.removeAttribute('disabled')

  video.addEventListener('loadedmetadata', () => {
    video.play()
  })

  recorder.addEventListener('dataavailable', ({ data }) => {
    video.srcObject = null
    video.src = URL.createObjectURL(data)

    // Create a blob from the data for upload
    blob = new Blob([data], { type: 'video/webm' })
  })

  start.addEventListener('click', () => {
    stop.removeAttribute('disabled')
    recorder.start()
  })

  stop.addEventListener('click', () => {
    upload.removeAttribute('disabled')
    recorder.stop()
  })

  // Upload the video blob as form data
  upload.addEventListener('click', () => {
    const body = new FormData()

    body.append('myvideo', blob)

    fetch('upload.php', { method: 'POST', body })
      .then(() => console.log('Success!'))
      .catch(console.error)
  })
}

navigator
  .mediaDevices
  .getUserMedia({ video: true })
  .then(initRecorder)
  .catch(console.error)

  </script>

And the upload.php file:

  <?php
  foreach(array('video', 'audio') as $type) {
  if (isset($_FILES["${type}-blob"])) {
  
  //echo 'uploads/';
  
  $fileName = $_POST["${type}-filename"];
  $uploadDirectory = 'uploads/'.$fileName;
  
  if (!move_uploaded_file($_FILES["${type}-blob"]["tmp_name"], $uploadDirectory)) {
  echo(" problem moving uploaded file");
  }
  
  echo($fileName);
  }
  }
?>

The file name is set to myvideo here:

body.append('myvideo', blob)

You need to change that to what you’d like to have it named on the backend. Try changing the fetch part to the following:

fetch('upload.php', { method: 'POST', body })
  .then(res => res.text())
  .then(console.log)
  .catch(console.error)

And the upload.php to simply:

<?php

var_dump($_FILES);

Now you should see something like this in the browser console:

array(1) {
  ["myvideo"]=>
  array(5) {
    ["name"]=>
    string(4) "blob"
    ["type"]=>
    string(10) "video/webm"
    ["tmp_name"]=>
    string(14) "/tmp/php4rzIUK"
    ["error"]=>
    int(0)
    ["size"]=>
    int(99843)
  }
}

Thank you for your reply.
I tried what you suggested without any results, unfortunately.
However, I’m actually trying to get the file to upload to a PHP folder.

What do you mean without any results? You should at least see either the server response or an error message in the console… but you might also check the network panel if the request gets actually sent.

Thanks for your reply.
Here is the code.
I don’t see any results at console or network.
I look forward to any feedback

<!DOCTYPE html>
<html>
<html lang="en">
<head></head>
<body>

    <header id="header"></header>
    <h1>WebCamRecord-3</h1>

    <video></video>
     <br/>
	<button class="start" disabled>Start</button>
	<button class="stop" disabled>Stop</button>
	<button class="reset" disabled>Reset</button>
	<button class="upload" disabled>Upload</button>

<script>
const video = document.querySelector('video')
const start = document.querySelector('.start')
const stop = document.querySelector('.stop')
const upload = document.querySelector('.upload')

const initRecorder = stream => {
const recorder = new MediaRecorder(stream)

  let blob

  video.srcObject = stream
  start.removeAttribute('disabled')

  video.addEventListener('loadedmetadata', () => {
    video.play()
  })

  recorder.addEventListener('dataavailable', ({ data }) => {
    video.srcObject = null
    video.src = URL.createObjectURL(data)

    // Create a blob from the data for upload
    blob = new Blob([data], { type: 'video/webm' })
  })

  start.addEventListener('click', () => {
    stop.removeAttribute('disabled')
    recorder.start()
  })

  stop.addEventListener('click', () => {
    upload.removeAttribute('disabled')
    recorder.stop()
  })

  // Upload the video blob as form data
  upload.addEventListener('click', () => {
    const body = new FormData()

    body.append('myvideo', blob)

fetch('upload.php', { method: 'POST', body })
  .then(res => res.text())
  .then(console.log)
  .catch(console.error)
  })
}

navigator
  .mediaDevices
  .getUserMedia({ video: true })
  .then(initRecorder)
  .catch(console.error)
  </script>
</body>
</html>

And the upload.php:

<?php


var_dump($_FILES);

?>

I checked the link you sent me via PM and I do see the var dump in the console. Maybe we are talking past each other though… I mean the console of your browser dev tools (e.g. here in chrome), not the shell where you are running your PHP server. So the file seems to be getting uploaded correctly.

Thanks so much again. I see the var dump now. I appreciate it.
I would be grateful for code help that would direct me to upload to a php folder.
I look forward to any comments/feedback/remedy.
Thanks again.

The very basic way to do this (w/o error handling etc.) would go like this:

// Assuming the name is set to myvideo... 
// might be something-blob as well
$myVideo = $_FILES['myvideo'];

move_uploaded_file(
  $myVideo['tmp_name'],
  './my-directoy/' . basename($myVideo['name'])
);

You can check out the PHP docs for details on this topic… or maybe start a new thread on the PHP board. :-)