Issue
I have a page where users can upload files with the help of FormData and an XMLHttpRequest.
Uploading the file works fine. But the upload.onprogress is only working when uploading from an HTTP connection.
HTTPS
HTTP
I've tested this on Heroku and on an Amazon EC2 instance. But it's always the same:
Progress is shown when uploading via HTTP
Progress event is never triggered when uploading via HTTPS
Javascript (Angular 7)
const xhr = new XMLHttpRequest();
let progress = 0;
/** THIS EVENT IS NOT WORKING WITH HTTPS */
xhr.upload.onprogress = (event: ProgressEvent) => {
if (event.lengthComputable) {
progress = 100 * (event.loaded / event.total);
}
};
xhr.responseType = 'json';
xhr.open('POST', `${API_URL}/${this.API_PATH}/upload`, true);
xhr.setRequestHeader('authorization', this.authService.getAuthToken());
xhr.send(payload);
xhr.onload = () => {
observer.next(xhr.response);
observer.complete();
};
Node.Js
const busboyBodyParser = require('busboy-body-parser');
app.use(busboyBodyParser())
const busboy = new Busboy({ headers: req.headers })
busboy.on('finish', async () => {
const fileData = req.files.file
const fileId = req.body.fileId
const params = {
Body: fileData.data,
Bucket: awsConfig.bucket,
ContentType: fileData.mimetype,
Key: fileId,
StorageClass: 'ONEZONE_IA',
}
awsConfig.s3.upload(params, (err, data) => { /* ... */ }
})
req.pipe(busboy)
What I've also tried
I also tried to use the .addEventListener syntax for listening for progress:
xhr.upload.addEventListener("progress", uploadProgress, false);
But this didn't work, either.
Source Code
Node.Js (server.js)
Node.Js (upload-file.js)
Angular Service (editor-file.service.ts)
Notes
Please note, that I have already asked a question about this topic. But I got no working answer and I really need this to work.
Old question: XHR upload onprogress Event not Working on HTTPS Connection
It's important to set Listener between:
xhr.open('POST'...);
...put you listener here....
xhr.send(data)
In this case it gonna work!
I'm doing just the same with one of my webapps but without any angular, just JS and PHP.
My xhr works like a charm and is looking like this:
var totalSize = 0;
var xhr = new XMLHttpRequest(); // den AJAX Request anlegen
xhr.open('POST', 'data/upload.php'); // Angeben der URL und des Requesttyps
xhr.upload.addEventListener("progress", handleProgress);
xhr.addEventListener("load", handleComplete);
and this my handleProgess method:
handleProgress = function(event){
var progress = totalProgress + event.loaded;
document.getElementById('progress').innerHTML = 'Aktueller Fortschritt: ' + ((progress - totalSize < 0) ? Math.floor(progress / totalSize * 10000) / 100 : 100) + '%';
}
As I've tried to reproduce this problem, I didn't face the same issue. Could you please check below simple Heroku app that I've created to test this specific problem? Also, if there is any missing part that I am not seeing, please inform me.
Heroku Test-Purpose App: https://erdsav-test-app.herokuapp.com/
Below is the JS code that I am tried to build on top of your code and it simply uploads the zip data and downloads it afterwards (cannot store on Heroku because of having Ephemeral filesystem) to ensure that the file is uploaded successfully;
import { Observable } from "../js/Observable.js";
document.addEventListener("DOMContentLoaded", function(event) {
var progressBar = document.getElementById("progress"),
fileNameSpan = document.getElementById("file_name"),
fileSizeSpan = document.getElementById("file_size"),
fileUploadComp = document.getElementById("file_upload"),
loadButton = document.getElementById("upload_button"),
displaySpan = document.getElementById("progress_display"),
fileDetails = document.getElementById("file_details"),
selectButton = document.getElementById("select_button"),
formData = null;
function hideElements(){
fileDetails.style.display = "none";
}
function showElements(){
fileDetails.style.display = "block";
}
function upload(payload, fileName){
return new Observable(observer => {
const xhr = new XMLHttpRequest();
let progress = 0;
/** THIS EVENT IS NOT WORKING WITH HTTPS */
xhr.upload.onprogress = (event => {
if (event.lengthComputable) {
progressBar.max = event.total;
progressBar.value = event.loaded;
progress = Math.floor((event.loaded / event.total) * 100);
displaySpan.innerText = progress + '%';
observer.next(progress);
}
});
xhr.upload.onloadstart = function(e) {
progressBar.value = 0;
displaySpan.innerText = '0%';
}
xhr.upload.onloadend = function(e) {
progressBar.value = e.loaded;
loadButton.disabled = false;
loadButton.innerHTML = 'Start Upload Process';
}
xhr.responseType = 'blob';
xhr.open('POST', "https://erdsav-test-app.herokuapp.com/upload.php", true);
xhr.send(payload);
xhr.returnedFileName = fileName;
xhr.onload = () => {
download(xhr.response, xhr.returnedFileName, "application/zip");
observer.next(100);
observer.complete();
};
});
}
function showUploadedFile(file){
var fileName = file.name;
var fileSize = file.size;
fileNameSpan.innerText = fileName;
fileSizeSpan.innerText = Math.floor(fileSize / 1000) + ' KB';
}
function buildFormData(file) {
if (formData) {
formData.append("file", file);
}
return formData;
}
hideElements();
if (window.FormData) {
formData = new FormData();
}
else{
alert("FormData is not supported in this browser!");
}
fileUploadComp.onchange = function(){
var file = fileUploadComp.files[0];
if(file){
showElements();
showUploadedFile(file);
}
else{
hideElements();
}
}
selectButton.addEventListener("click", function(e){
fileUploadComp.value = "";
hideElements();
fileUploadComp.click();
e.preventDefault();
});
loadButton.addEventListener("click", function(e) {
if(fileUploadComp.files !== undefined && fileUploadComp.files.length > 0){
this.disabled = true;
this.innerHTML = "Uploading. Please wait...";
var obs = upload(buildFormData(fileUploadComp.files[0]), fileUploadComp.files[0].name);
obs.subscribe(
function valueHandler(value){
console.log("UPLOADING");
if(value){
console.log(value);
}
},
function errorHandler(err){
console.log("THERE IS AN ERROR");
},
function completeHandler(){
console.log("COMPLETE");
}
);
}
else{
alert("No file is selected");
}
e.preventDefault();
});
});
PHP side
<?php
if ($_FILES['file']['error'] != $UPLOAD_ERR_OK) {
//writeLog($_FILES['file']['error']);
echo 'An error occurred!';
exit();
}
else {
$filePath = $_FILES['file']['tmp_name'];
$fileName = $_FILES['file']['name'];
if (file_exists($filePath)) {
ob_start();
$fileSize = readfile($filePath);
$content = ob_get_clean();
header('Content-Type: application/octet-stream;');
header("Content-Disposition: attachment; filename=\"" . $fileName . "\"");
header('Expires: 0');
header('Pragma: no cache');
header('Content-Length: ' . $fileSize);
echo $content;
}
else{
echo 'File is not found';
exit();
}
}
?>
HTML page source
<!DOCTYPE html>
<html lang="en">
<title>ProgressBar Progress Test</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="ProgressBar Progress Test">
<body>
<form method="post" enctype="multipart/form-data">
<input type="file" id="file_upload" accept="application/zip" style="width:0px" />
<button id="select_button">Choose File to Upload</button>
<progress id="progress" value="0"></progress>
<span id="progress_display"></span>
<button type="submit" id="upload_button">Start Upload Process</button>
</form>
<div id="file_details" style="display: none">
<h3>Selected File Details</h3>
<span id="file_name"></span><br>
<span id="file_size"></span>
</div>
<script type="module" src="js/Observable.js"></script>
<script src="js/download.js"></script>
<script type="module" src="js/main.js"></script>
</body>
</html>
Below image is captured by slowing the network to see the current percentage while uploading process continues;
Browsers used in testing;
Firefox Developer Edition 67.0b13 (64-bit/Up-to-date)
Google Chrome 74.0.3729.108 (64-bit/Up-to-date)
Now in 2022
The 2016's specifications are outdated, here is the new standard for XMLHttpRequest.
The upload is a getter that returns an XMLHttpRequestUpload object
The XMLHttpRequestUpload object implements the XMLHttpRequestEventTarget interface, which handles the onprogress event.
Now here's what MDN says about this:
Note: The spec also seems to indicate that event listeners should be attached after open(). However, browsers are buggy on this matter, and often need the listeners to be registered before open() to work.
Related
I am using this
<button onclick="startRecording(this);">record</button>
<button onclick="stopRecording(this);" disabled>stop</button>
<h2>Recordings</h2>
<ul id="recordingslist"></ul>
<h2>Log</h2>
<pre id="log"></pre>
<script>
function __log(e, data) {
log.innerHTML += "\n" + e + " " + (data || '');
}
var audio_context;
var recorder;
function startUserMedia(stream) {
var input = audio_context.createMediaStreamSource(stream);
__log('Media stream created.' );
__log("input sample rate " +input.context.sampleRate);
// Feedback!
//input.connect(audio_context.destination);
__log('Input connected to audio context destination.');
recorder = new Recorder(input, {
numChannels: 1
});
__log('Recorder initialised.');
}
function startRecording(button) {
recorder && recorder.record();
button.disabled = true;
button.nextElementSibling.disabled = false;
__log('Recording...');
}
function stopRecording(button) {
recorder && recorder.stop();
button.disabled = true;
button.previousElementSibling.disabled = false;
__log('Stopped recording.');
// create WAV download link using audio data blob
createDownloadLink();
recorder.clear();
}
function createDownloadLink() {
recorder && recorder.exportWAV(function(blob) {
/*var url = URL.createObjectURL(blob);
var li = document.createElement('li');
var au = document.createElement('audio');
var hf = document.createElement('a');
au.controls = true;
au.src = url;
hf.href = url;
hf.download = new Date().toISOString() + '.wav';
hf.innerHTML = hf.download;
li.appendChild(au);
li.appendChild(hf);
recordingslist.appendChild(li);*/
});
}
window.onload = function init() {
try {
// webkit shim
window.AudioContext = window.AudioContext || window.webkitAudioContext;
navigator.getUserMedia = ( navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
window.URL = window.URL || window.webkitURL;
audio_context = new AudioContext;
__log('Audio context set up.');
__log('navigator.getUserMedia ' + (navigator.getUserMedia ? 'available.' : 'not present!'));
} catch (e) {
alert('No web audio support in this browser!');
}
navigator.getUserMedia({audio: true}, startUserMedia, function(e) {
__log('No live audio input: ' + e);
});
};
</script>
<script src="js/jquery-1.11.0.min.js"></script>
<script src="recordmp3.js"></script>
To capture audio and offer an mp3 download but what I really need to do is upload the recordings to the server and as a bonus if I could rename the files before upload that would make it perfect! Can't show an example of what I've tried as I have no idea where to start on this one, any help greatly appreciated.
The flow would be to capture a handful of short recordings, rename them then hit an upload button which will upload them as mp3's then redirect to a success page.
Full code here https://github.com/Audior/Recordmp3js
You should upload the data as a XMLHttpRequest, however on the server side you should save it to a file with a custom name. This is also safer, since you can very easily avoid file traversal attacks by not specifying a path (From what I've understood, other people can't upload files to your computer/server, however I still believe you should approach this method because it is a great introduction to protecting an application with basic security measures).
You can use the following (modified) answer, taken from here, to do the job. This is how the JS function createDownloadLink should look after the modifications:
function createDownloadLink() {
recorder && recorder.exportWAV(function(blob) {
// Generate the 'File'
var data = new FormData();
data.append('file', blob);
// Make the HTTP request
var oReq = new XMLHttpRequest();
// POST the data to upload.php
oReq.open("POST", 'upload.php', true);
oReq.onload = function(oEvent) {
// Data has been uploaded
};
oReq.send(data);
});
}
On the PHP side, however, you should also make some modifications, since this is now a classic file upload, rather than a POST request with the data being sent as a parameter. Modify upload.php like so (information about moving uploaded files taken from here):
<?php
// Make sure recordings folder exists
if(!is_dir("recordings")){
$res = mkdir("recordings",0777);
}
// Declare file name
$filename = 'recordings/' . 'audio_recording_' . date( 'Y-m-d-H-i-s' ) .'.mp3';
// Move the uploaded file to the directory
move_uploaded_file($_FILES['file']['tmp_name'], $filename));
?>
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 4 years ago.
Improve this question
Now its almost 6 days and I am trying to fix image upload issue in cordova-php but not able to fix it. I tried multiple solutions from Google and Stack Overflow. But none of them is working for me.
I am using the below code as front end.
<div>
<h3>Server URL for upload.php:</h3>
<input id="serverUrl" type="text" value="http://sample.com/mobile_app/upload_img.php" />
</div>
<script type="text/javascript" charset="utf-8">
var deviceReady = false;
/**
* Take picture with camera
*/
function takePicture() {
navigator.camera.getPicture(
function(uri) {
var img = document.getElementById('camera_image');
img.style.visibility = "visible";
img.style.display = "block";
img.src = uri;
document.getElementById('camera_status').innerHTML = "Success";
},
function(e) {
console.log("Error getting picture: " + e);
document.getElementById('camera_status').innerHTML = "Error getting picture.";
},
{ quality: 50, destinationType: navigator.camera.DestinationType.FILE_URI});
};
/**
* Select picture from library
*/
function selectPicture() {
navigator.camera.getPicture(
function(uri) {
var img = document.getElementById('camera_image');
img.style.visibility = "visible";
img.style.display = "block";
img.src = uri;
document.getElementById('camera_status').innerHTML = "Success";
},
function(e) {
console.log("Error getting picture: " + e);
document.getElementById('camera_status').innerHTML = "Error getting picture.";
},
{ quality: 50, destinationType: navigator.camera.DestinationType.FILE_URI, sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY});
};
/**
* Upload current picture
*/
function uploadPicture() {
// Get URI of picture to upload
var img = document.getElementById('camera_image');
var imageURI = img.src;
if (!imageURI || (img.style.display == "none")) {
document.getElementById('camera_status').innerHTML = "Take picture or select picture from library first.";
return;
}
// Verify server has been entered
server = document.getElementById('serverUrl').value;
if (server) {
// Specify transfer options
var options = new FileUploadOptions();
options.fileKey="fileUpload";
options.httpMethod="POST";
options.fileName=imageURI.substr(imageURI.lastIndexOf('/')+1);
options.mimeType="image/jpeg";
options.chunkedMode = false;
// var op;
//op = new FileUploadOptions();
options.headers = {
Connection: "close"
};
// Transfer picture to server
var ft = new FileTransfer();
ft.upload(imageURI, server, function(r) {
document.getElementById('camera_status').innerHTML = "Upload successful: "+r.response+" bytes uploaded."; alert(r.response);
}, function(error) {
alert(r.response);
document.getElementById('camera_status').innerHTML = "Upload failed: Code = "+error.code;
}, options);
}
}
/**
* View pictures uploaded to the server
*/
function viewUploadedPictures() {
// Get server URL
server = document.getElementById('serverUrl').value;
if (server) {
// Get HTML that lists all pictures on server using XHR
var xmlhttp = new XMLHttpRequest();
// Callback function when XMLHttpRequest is ready
xmlhttp.onreadystatechange=function(){
if(xmlhttp.readyState === 4){
// HTML is returned, which has pictures to display
if (xmlhttp.status === 200) {
document.getElementById('server_images').innerHTML = xmlhttp.responseText;
}
// If error
else {
document.getElementById('server_images').innerHTML = "Error retrieving pictures from server.";
}
}
};
xmlhttp.open("GET", server , true);
xmlhttp.send();
}
}
/**
* Function called when page has finished loading.
*/
function init() {
document.addEventListener("deviceready", function() {deviceReady = true;}, false);
window.setTimeout(function() {
if (!deviceReady) {
alert("Error: PhoneGap did not initialize. Demo will not run correctly.");
}
},2000);
}
</script>
And here comes the backend (PHP) code.
<?php
header("Access-Control-Allow-Origin: *");
//header("Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS");
header("content-type: image/png");
// Directory where uploaded images are saved
$dirname = "user_img";//"mobile_app/user_img";
var_dump($_POST);
$new_image_name = urldecode($_FILES["fileUpload"]["name"]).".png";
// If uploading file
//echo $_FILES;
echo $new_image_name;
print_r($_FILES["fileUpload"]);
if ($_FILES) {
print_r($_FILES);
//mkdir ($dirname, 0777, true);
move_uploaded_file($_FILES["fileUpload"]["tmp_name"],$dirname."/".$_FILES["fileUpload"]["name"]);
}
// If retrieving an image
else if (isset($_GET['image'])) {
$file = $dirname."/".$_GET['image'];
// Specify as jpeg
header('Content-type: image/jpeg');
// Resize image for mobile
list($width, $height) = getimagesize($file);
$newWidth = 120.0;
$size = $newWidth / $width;
$newHeight = $height * $size;
$resizedImage = imagecreatetruecolor($newWidth, $newHeight);
$image = imagecreatefromjpeg($file);
imagecopyresampled($resizedImage, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
imagejpeg($resizedImage, null, 80);
}
// If displaying images
else {
$baseURI = "http://".$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].$_SERVER['REQUEST_URI'];
$images = scandir($dirname);
$ignore = Array(".", "..");
if ($images) {
foreach($images as $curimg){
if (!in_array($curimg, $ignore)) {
echo "Image: ".$curimg."<br>";
echo "<img src='".$baseURI."?image=".$curimg."&rnd=".uniqid()."'><br>";
}
}
}
else {
echo "No images on server";
}
}
?>
I found $_FILES is empty I am getting Array() only. I don't know what is wrong with the code because same code i have seen on multiple question and examples.
I used two web servers but same result.
user_img folder have 777 access.
Finally I fixed the issue, the fix was simple.
The url I had given was http://sample.com/upload_img.php. I just needed to add www in the url. So, working URL is http://www/sample.com/upload_img.php.
It fixed the issue.
I am trying to download files using Ajax and show a custom download progress bar.
The problem is I can't understand how to do so. I wrote the code to log the progress but don't know how to initiate the download.
NOTE: The files are of different types.
Thanks in advance.
JS
// Downloading of files
filelist.on('click', '.download_link', function(e){
e.preventDefault();
var id = $(this).data('id');
$(this).parent().addClass("download_start");
$.ajax({
xhr: function () {
var xhr = new window.XMLHttpRequest();
// Handle Download Progress
xhr.addEventListener("progress", function (evt) {
if(evt.lengthComputable) {
var percentComplete = evt.loaded / evt.total;
console.log(percentComplete);
}
}, false);
return xhr;
},
complete: function () {
console.log("Request finished");
}
})
});
HTML and PHP
<li>
<div class="f_icon"><img src="' . $ico_path . '"></div>
<div class="left_wing">
<div class="progressbar"></div>
<a class="download_link" href="#" id="'.$file_id.'"><div class="f_name">' . $full_file_name . '</div></a>
<div class="f_time_size">' . date("M d, Y", $file_upload_time) . ' ' . human_filesize($file_size) . '</div>
</div>
<div class="right_wing">
<div class="f_delete">
<a class="btn btn-danger" href="#" aria-label="Delete" data-id="'.$file_id.'" data-filename="'.$full_file_name.'"><i class="fa fa-trash-o fa-lg" aria-hidden="true" title="Delete this?"></i>
</a>
</div>
</div>
</li>
If you want to show the user a progress-bar of the downloading process - you must do the download within the xmlhttprequest. One of the problems here is that if your files are big - they will be saved in the memory of the browser before the browser will write them to the disk (when using the regular download files are being saved directly to the disk, which saves a lot of memory on big files).
Another important thing to note - in order for the lengthComputable to be true - your server must send the Content-Length header with the size of the file.
Here is the javascript code:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="a1" data-filename="filename.xml">Click to download</div>
<script>
$('#a1').click(function() {
var that = this;
var page_url = 'download.php';
var req = new XMLHttpRequest();
req.open("POST", page_url, true);
req.addEventListener("progress", function (evt) {
if(evt.lengthComputable) {
var percentComplete = evt.loaded / evt.total;
console.log(percentComplete);
}
}, false);
req.responseType = "blob";
req.onreadystatechange = function () {
if (req.readyState === 4 && req.status === 200) {
var filename = $(that).data('filename');
if (typeof window.chrome !== 'undefined') {
// Chrome version
var link = document.createElement('a');
link.href = window.URL.createObjectURL(req.response);
link.download = filename;
link.click();
} else if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE version
var blob = new Blob([req.response], { type: 'application/force-download' });
window.navigator.msSaveBlob(blob, filename);
} else {
// Firefox version
var file = new File([req.response], filename, { type: 'application/force-download' });
window.open(URL.createObjectURL(file));
}
}
};
req.send();
});
</script>
And here is an example for the php code you can use:
<?php
$filename = "some-big-file";
$filesize = filesize($filename);
header("Content-Transfer-Encoding: Binary");
header("Content-Length:". $filesize);
header("Content-Disposition: attachment");
$handle = fopen($filename, "rb");
if (FALSE === $handle) {
exit("Failed to open stream to URL");
}
while (!feof($handle)) {
echo fread($handle, 1024*1024*10);
sleep(3);
}
fclose($handle);
Note that I added a sleep to simulate a slow connection for testing on localhost.
You should remove this on production :)
I've searched everything but my error stays.
Following scripts is my code:
var client = null;
function fileChange()
{
var fileList = document.getElementById("fileA").files;
var file = fileList[0];
if(!file)
return;
document.getElementById("progress").value = 0;
document.getElementById("prozent").innerHTML = "0%";
}
function uploadFile()
{
var file = document.getElementById("fileA").files[0];
var formData = new FormData();
client = new XMLHttpRequest();
var prog = document.getElementById("progress");
if(!file)
return;
prog.value = 0;
prog.max = 100;
formData.append("datei", file);
client.onerror = function(e) {
alert("onError");
};
client.onload = function(e) {
document.getElementById("prozent").innerHTML = "100%";
prog.value = prog.max;
};
client.upload.onprogress = function(e) {
var p = Math.round(100 / e.total * e.loaded);
document.getElementById("progress").value = p;
document.getElementById("prozent").innerHTML = p + "%";
};
client.onabort = function(e) {
alert("Upload abgebrochen");
};
client.open("POST", "upload.php");
client.send(formData);
}
And my upload.php:
<?php
if (isset($_FILES['datei']))
{
//console.log("upload.php!");
//move_uploaded_file($_FILES['datei']['tmp_name'], 'upload/'.basename($_FILES['datei']['name']));
move_uploaded_file($_FILES['datei']['tmp_name'], 'upload/hextoflash.hex');
}
else
{
echo "error in $_files";
}
?>
and html:
<form name="uploadform" method="post" enctype="multipart/form-data" action="">
<br> <input name="uploaddatei" type="file" id="fileA" onchange="fileChange();" /> </br>
<input name="uploadbutton" value="Hochladen!" style="width: 108px" type="button" onclick="uploadFile();" />
</form>
The point is, i already tried to edit php.ini.
I checked max_size and so on, its already in the right dimension
File was working with an old jquery.js, i updated to jquery2.2.0 because of datatables plugin, so i need that, and since then it seems to not work anymore
the rights of the upload folder are 777
So i don't get where the problem is, and if its within jquery, im not able to fix that. is there a method to fix this problem?
I found the solution.
It was so easy i would never think of it.
The server drive was simply 100% full.
Deleted some junk and voilá it's running.
I'm trying to record audio from a website user and save the audio to my server. Many of the posts I have studied so far have referenced Matt Diamond's recorderjs. I attempted to recreate the demo at http://webaudiodemos.appspot.com/AudioRecorder/index.html by opening the source code through my browser. I copied the html, "audiodisplay.js", "recorder.js", and "main.js" and put them on my server. I also added the "recorderWorker.js" file from his GitHub site. In the recorder.js file, I changed var WORKER_PATH = 'js/recorderjs/recorderWorker.js' to var WORKER_PATH = 'recorderWorker.js';
When I run the demo I set up, I'm getting the "would you like to share your microphone.." warning and I can start the recording by pressing the mic icon on the right side. However, when I stop recording, the audio waveform doesn't show up below like in Matt's demo and the save icon doesn't become activated.
If I can get the demo up and running, the next problem I have is saving the wav file to the server instead of locally like in the demo. I've found several posts saying to use XMLHttpRequest(), however I can't really figure out how to connect those examples to recorderjs. Saving WAV File Recorded in Chrome to Server HTML5 & getUserMedia - Record Audio & Save to Web Server after Certain Time RecorderJS uploading recorded blob via AJAX
Using XMLHttpRequest to post wav or mp3 blobs to server is simple.
Just run this code wherever you have access to the blob element:
var xhr=new XMLHttpRequest();
xhr.onload=function(e) {
if(this.readyState === 4) {
console.log("Server returned: ",e.target.responseText);
}
};
var fd=new FormData();
fd.append("audio_data",blob, "filename");
xhr.open("POST","upload.php",true);
xhr.send(fd);
I prefer XMLHttpRequest to $.ajax() because it does not require jQuery.
Server-side, upload.php is as simple as:
$input = $_FILES['audio_data']['tmp_name']; //temporary name that PHP gave to the uploaded file
$output = $_FILES['audio_data']['name'].".wav"; //letting the client control the filename is a rather bad idea
//move the file from temp name to local folder using $output name
move_uploaded_file($input, $output)
Source: https://blog.addpipe.com/using-recorder-js-to-capture-wav-audio-in-your-html5-web-site/
Live demo: https://addpipe.com/simple-recorderjs-demo/
I figured out one solution, but would still welcome others related to recorderjs. I used MP3RecorderJS at https://github.com/icatcher-at/MP3RecorderJS. The demo html works if you change the top of the html from src="js/jquery.min.js" and src="js/mp3recorder.js" to wherever they're located in your server. For me, it is src="jquery.min.js" and src="mp3recorder.js" I also had to do the same thing to the "mp3recorder.js" file: var RECORDER_WORKER_PATH = 'js/recorderWorker.js'; var ENCODER_WORKER_PATH = 'js/mp3Worker.js'; changed to var RECORDER_WORKER_PATH = 'recorderWorker.js'; var ENCODER_WORKER_PATH = 'mp3Worker.js';
The program is set up to record both mp3 and wav. I wanted wav, so I made a few more adjustments to the html file. At line 55 you'll find:
recorderObject.exportMP3(function(base64_mp3_data) {
var url = 'data:audio/mp3;base64,' + base64_mp3_data;
var au = document.createElement('audio');
I changed that to:
recorderObject.exportWAV(function(base64_wav_data) {
var url = 'data:audio/wav;base64,' + base64_wav_data;
var au = document.createElement('audio');
The demo appends a new player each time you record. To prevent this, I deleted (commented out) the $recorder.append(au); part, made a new div to store the audio player, and then I clear that div each time, before the audio player is created. To upload to my server, I used a technique I learned from uploading images to a server save canvas image to server Basically, the "url" variable in line 56 was what I needed, but couldn't figure out how to put it in a universal variable to use by another function. So, I made a hidden div and made the contents of it equal to "url". I then referenced that div in a new function called "upload". I then used a php file called "uploadWav.php". I still have to figure out a way to activate and deactivate the upload button to prevent the user from uploading a blank file before recording, but that's another issue. Here's the final html and php that worked for me:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>MP3 Recorder test</title>
</head>
<body id="index" onload="">
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="mp3recorder.js"></script>
<script type="text/javascript">
var audio_context;
function __log(e, data) {
log.innerHTML += "\n" + e + " " + (data || '');
}
$(function() {
try {
// webkit shim
window.AudioContext = window.AudioContext || window.webkitAudioContext;
navigator.getUserMedia = ( navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
window.URL = window.URL || window.webkitURL;
var audio_context = new AudioContext;
__log('Audio context set up.');
__log('navigator.getUserMedia ' + (navigator.getUserMedia ? 'available.' : 'not present!'));
} catch (e) {
alert('No web audio support in this browser!');
}
$('.recorder .start').on('click', function() {
$this = $(this);
$recorder = $this.parent();
navigator.getUserMedia({audio: true}, function(stream) {
var recorderObject = new MP3Recorder(audio_context, stream, { statusContainer: $recorder.find('.status'), statusMethod: 'replace' });
$recorder.data('recorderObject', recorderObject);
recorderObject.start();
}, function(e) { });
});
$('.recorder .stop').on('click', function() {
$this = $(this);
$recorder = $this.parent();
recorderObject = $recorder.data('recorderObject');
recorderObject.stop();
recorderObject.exportWAV(function(base64_wav_data) {
var url = 'data:audio/wav;base64,' + base64_wav_data;
var au = document.createElement('audio');
document.getElementById("playerContainer").innerHTML = "";
//console.log(url)
var duc = document.getElementById("dataUrlcontainer");
duc.innerHTML = url;
au.controls = true;
au.src = url;
//$recorder.append(au);
$('#playerContainer').append(au);
recorderObject.logStatus('');
});
});
});
</script>
<script>
function upload(){
var dataURL = document.getElementById("dataUrlcontainer").innerHTML;
$.ajax({
type: "POST",
url: "uploadWav.php",
data: {
wavBase64: dataURL
}
}).done(function(o) {
console.log('saved');
});
}
</script>
<div class="recorder">
Recorder 1
<input type="button" class="start" value="Record" />
<input type="button" class="stop" value="Stop" />
<pre class="status"></pre>
</div>
<div><button onclick="upload()">Upload</button></div>
<div id="playerContainer"></div>
<div id="dataUrlcontainer" hidden></div>
<pre id="log"></pre>
</body>
</html>
and the "uploadWav.php" file:
<?php
// requires php5
define('UPLOAD_DIR', 'uploads/');
$img = $_POST['wavBase64'];
$img = str_replace('data:audio/wav;base64,', '', $img);
$img = str_replace(' ', '+', $img);
$data = base64_decode($img);
$file = UPLOAD_DIR . uniqid() . '.wav';
$success = file_put_contents($file, $data);
print $success ? $file : 'Unable to save the file.';
?>
//**Server Side Code**
package myPack;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
#WebServlet("/MyServlet")
#MultipartConfig
public class MyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public MyServlet() {
super();
}
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
try {
String name = request.getParameter("fname");
String url = request.getParameter("myUrl");
url = url.replace("data:audio/wav;base64,", "");
url = url.replace(" ", "+");
byte[] bytes = url.getBytes();
byte[] valueDecoded = Base64.decodeBase64(bytes);
FileOutputStream os = new FileOutputStream(new File("D://" + name
+ ".wav"));
os.write(valueDecoded);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
**Client Side Code**
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>MP3 Recorder test</title>
</head>
<body id="index" onload="">
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/recorder.js"></script>
<script type="text/javascript">
var audio_context;
function __log(e, data) {
log.innerHTML += "\n" + e + " " + (data || '');
}
$(function() {
try {
// webkit shim
window.AudioContext = window.AudioContext || window.webkitAudioContext;
navigator.getUserMedia = ( navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
window.URL = window.URL || window.webkitURL;
var audio_context = new AudioContext;
__log('Audio context set up.');
__log('navigator.getUserMedia ' + (navigator.getUserMedia ? 'available.' : 'not present!'));
} catch (e) {
alert('No web audio support in this browser!');
}
$('.recorder .start').on('click', function() {
$this = $(this);
$recorder = $this.parent();
navigator.getUserMedia({audio: true}, function(stream) {
var recorderObject = new MP3Recorder(audio_context, stream, { statusContainer: $recorder.find('.status'), statusMethod: 'replace' });
$recorder.data('recorderObject', recorderObject);
recorderObject.start();
}, function(e) { });
});
$('.recorder .stop').on('click', function() {
$this = $(this);
$recorder = $this.parent();
recorderObject = $recorder.data('recorderObject');
recorderObject.stop();
recorderObject.exportWAV(function(base64_wav_data) {
var url = 'data:audio/wav;base64,' + base64_wav_data;
var au = document.createElement('audio');
document.getElementById("playerContainer").innerHTML = "";
//console.log(url)
var duc = document.getElementById("dataUrlcontainer");
duc.innerHTML = url;
au.controls = true;
au.src = url;
//$recorder.append(au);
$('#playerContainer').append(au);
var fd = new FormData();
fd.append('fname', 'test.wav');
fd.append('myUrl', duc.innerHTML);
$.ajax({
type: "POST",
url: "/audioPart2/MyServlet",
data: fd,
processData: false,
contentType: false
});
recorderObject.logStatus('');
});
});
});
</script>
<div class="recorder">
Recorder 1 <input type="button" class="start" value="Record" /> <input
type="button" class="stop" value="Stop" />
<div id="playerContainer"></div>
<div id="dataUrlcontainer" hidden></div>
<pre class="status"></pre>
</div>
<!-- <div class="recorder"> -->
<!-- Recorder 2 <input type="button" class="start" value="Record" /> <input -->
<!-- type="button" class="stop" value="Stop" /> -->
<!-- <pre class="status"></pre> -->
<!-- </div> -->
<pre id="log"></pre>
</body>
</html>
**// Required JS
1)jquery.min.js
2) recorder.js**
**recorder.js is below**
(function(window){
var RECORDER_WORKER_PATH = 'js/recorderWorker.js';
var ENCODER_WORKER_PATH = 'js/mp3Worker.js';
var MP3Recorder = function(context, stream, cfg) {
var config = cfg || { statusContainer: null, statusMethod: 'append' }
var bufferLen = 4096;
var recording = false;
this.source = context.createMediaStreamSource(stream);
this.node = (context.createScriptProcessor || context.createJavaScriptNode).call(context, bufferLen, 1, 1);
var recorderWorker = new Worker(RECORDER_WORKER_PATH);
var encoderWorker = new Worker(ENCODER_WORKER_PATH);
var exportCallback;
// initialize the Recorder Worker
recorderWorker.postMessage({ cmd: 'init', sampleRate: context.sampleRate });
// the recording loop
this.node.onaudioprocess = function(e) {
if(!recording) return;
recorderWorker.postMessage({ cmd: 'record', buffer: e.inputBuffer.getChannelData(0) });
}
this.start = function() {
recording = true;
this.logStatus('recording...');
}
this.stop = function() {
recording = false;
this.logStatus('stopping...');
}
this.destroy = function() { recorderWorker.postMessage({ cmd: 'destroy' }); }
this.logStatus = function(status) {
if(config.statusContainer) {
if(config.statusMethod == 'append') {
config.statusContainer.text(config.statusContainer.text + "\n" + status);
} else {
config.statusContainer.text(status);
}
}
}
this.exportBlob = function(cb) {
exportCallback = cb;
if (!exportCallback) throw new Error('Callback not set');
recorderWorker.postMessage({ cmd: 'exportBlob' });
}
this.exportWAV = function(cb) {
// export the blob from the worker
this.exportBlob(function(blob) {
var fileReader = new FileReader();
// read the blob as array buffer and convert it
// to a base64 encoded WAV buffer
fileReader.addEventListener("loadend", function() {
var resultBuffer = new Uint8Array(this.result);
cb(encode64(resultBuffer));
});
fileReader.readAsArrayBuffer(blob);
});
}
this.exportMP3 = function(cb) {
this.logStatus('converting...');
// export the blob from the worker
this.exportBlob(function(blob) {
var fileReader = new FileReader();
fileReader.addEventListener("loadend", function() {
var wavBuffer = new Uint8Array(this.result);
var wavData = parseWav(wavBuffer);
encoderWorker.addEventListener('message', function(e) {
if (e.data.cmd == 'data') {
cb(encode64(e.data.buffer));
}
});
encoderWorker.postMessage({ cmd: 'init', config: { mode: 3, channels: 1, samplerate: wavData.sampleRate, bitrate: wavData.bitsPerSample } });
encoderWorker.postMessage({ cmd: 'encode', buf: Uint8ArrayToFloat32Array(wavData.samples) });
encoderWorker.postMessage({ cmd: 'finish' });
});
fileReader.readAsArrayBuffer(blob);
});
}
// event listener for return values of the recorderWorker
recorderWorker.addEventListener('message', function(e) {
switch(e.data.from) {
case 'exportBlob':
exportCallback(e.data.blob);
break;
};
});
// HELPER FUNCTIONS
function encode64(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for(var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
function parseWav(wav) {
function readInt(i, bytes) {
var ret = 0, shft = 0;
while(bytes) {
ret += wav[i] << shft; shft += 8;
i++; bytes--;
}
return ret;
}
if(readInt(20, 2) != 1) throw 'Invalid compression code, not PCM';
if(readInt(22, 2) != 1) throw 'Invalid number of channels, not 1';
return { sampleRate: readInt(24, 4), bitsPerSample: readInt(34, 2), samples: wav.subarray(44) };
}
function Uint8ArrayToFloat32Array(u8a){
var f32Buffer = new Float32Array(u8a.length);
for (var i = 0; i < u8a.length; i++) {
var value = u8a[i<<1] + (u8a[(i<<1)+1]<<8);
if (value >= 0x8000) value |= ~0x7FFF;
f32Buffer[i] = value / 0x8000;
}
return f32Buffer;
}
this.source.connect(this.node);
this.node.connect(context.destination); // this should not be necessary
}
window.MP3Recorder = MP3Recorder;
})(window);