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>
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.
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.
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:
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.):
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);
}
}
?>
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.
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. :-)