I have a web app getting some audio recording input from the user.
And there is a button to save it locally as an audio file.
The resulting file I am getting is of ogg-opus format. Concretely when I use the file command I get this in the terminal:
MyMac$ file Clip.wav
Clip.wav: Ogg data, Opus audio,
MyMac$
I can check that the recording is all right using VLC.
On the other hand I can't play the file with afplay as is normally possible with an mp3, m4a or wav file.
MyMac$ afplay Clip.wav
Error: AudioFileOpen failed ('typ?')
MyMac$
Here follows my relevant code:
if (navigator.mediaDevices.getUserMedia) {
// getUserMedia is supported.
const constraints = { audio: true };
let chunks = [];
let onSuccess = function(stream) {
const mediaRecorder = new MediaRecorder(stream);
visualize(stream);
........
mediaRecorder.onstop = function(e) {
........
audio.setAttribute('controls', '');
........
audio.controls = true;
const blob = new Blob(chunks, { 'type' : 'audio/wav;codecs=0' });
chunks = [];
........
upload.addEventListener("click",
function(event) {loadToServer(blob)})
........
}
mediaRecorder.ondataavailable = function(e) {
chunks.push(e.data);
}
}
let onError = function(err) {
console.log('The following error occured: ' + err);
}
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
} else {
console.log('getUserMedia not supported on your browser!');
}
I would like to know how to change my code in order to generate a proper wav file or even mp3.
Note:
I have made trials modifying this line of code:
const blob = new Blob(chunks, { 'type' : 'audio/wav;codecs=0' });
in various ways, but is has no effect at all.
In order to specify the mimeType of your recording you need to tell the MediaRecorder which mimeType you prefer before it starts the recording.
- const mediaRecorder = new MediaRecorder(stream);
+ const mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/wav' });
Unfortunately audio/wav is not supported by any browser. You will get an error when trying the snippet above.
Since I needed wav recordings as well I built a library which is meant to add this functionality. It's called extendable-media-recorder because it could be extended with any other (audio) codec you like.
If you don't want to use a third party library and keep the browser using the codec it likes best you can save your file like this in order to get a valid file with the correct suffix.
- const blob = new Blob(chunks, { 'type' : 'audio/wav;codecs=0' });
+ const blob = new Blob(chunks, { 'type' : mediaRecorder.mimeType });
The suffix would then be the portion of the type after the slash and before a possible semicolon.
Here is an example of a full HTML document that uses jspm to load extendable-media-recorder without a bundler.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
</head>
<body>
<button id="start" disabled>start</button>
<button id="stop" disabled>stop</button>
<script type="module">
import { MediaRecorder, register } from 'https://jspm.dev/extendable-media-recorder';
import { connect } from 'https://jspm.dev/extendable-media-recorder-wav-encoder';
const $start = document.getElementById('start');
const $stop = document.getElementById('stop');
await register(await connect());
const chunks = [];
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const mediaRecoder = new MediaRecorder(stream, { mimeType: 'audio/wav' });
mediaRecoder.addEventListener('dataavailable', ({ data }) => {
chunks.push(data);
});
mediaRecoder.addEventListener('stop', ({ data }) => {
const blob = new Blob(chunks, { type : mediaRecoder.mimeType });
console.log(blob);
});
$start.addEventListener('click', () => {
mediaRecoder.start();
$stop.addEventListener('click', () => {
$stop.disabled = true;
mediaRecoder.stop();
});
$start.disabled = true;
$stop.disabled = false;
});
$start.disabled = false;
</script>
</body>
</html>
Related
there
I have two videos segments array
video1 = ['v1_1.ts','v1_2.ts','v1_3.ts','v1_4.ts',...]
video2 = ['v2_1.ts','v2_2.ts','v2_3.ts','v2_4.ts',...]
MSE is working perfect with playing video1 segments, but when I try to play video segments 2 nothing happened
I can't even restart playing video1 segments
Can anyone explain, how to start playing new video segments in Media Source Extension
<html>
<head>
<title>Basic Transmuxer Test</title>
</head>
<body>
<div><button onclick="play1()">Restart video</button>
<video controls width="80%"></video>
<script src="https://github.com/videojs/mux.js/releases/latest/download/mux.js"></script>
<script>
// Create array of TS files to play
segments = [
"segment_1.ts",
"segment_2.ts",
"segment_3.ts",
"segment_4.ts"
];
// Replace this value with your files codec info
mime = 'video/mp4; codecs="mp4a.40.2,avc1.64001f"';
let mediaSource = new MediaSource();
mediaSource.addEventListener('error', function(err) {
throw new Error("MSE: " + err.message);
});
let sourceBuffer = null;
let transmuxer = new muxjs.mp4.Transmuxer();
video = document.querySelector('video');
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener("sourceopen", appendFirstSegment);
function play1(){
sourceBuffer.abort();
segments = [
"segment_0.ts",
"segment_1.ts",
"segment_2.ts",
"segment_3.ts",
"segment_4.ts",
"segment_5.ts",
"segment_6.ts",
"segment_7.ts",
"segment_8.ts",
"segment_9.ts"
];
appendFirstSegment()
}
function appendFirstSegment(){
if (segments.length == 0){
return;
}
console.log('appendFirstSegment')
if(!sourceBuffer){
sourceBuffer = mediaSource.addSourceBuffer(mime);
sourceBuffer.mode = 'sequence';
}
//mediaSource.duration = 0;
sourceBuffer.addEventListener('updateend', appendNextSegment,false);
transmuxer.off('data');
transmuxer.on('data', (segment) => {
let data = new Uint8Array(segment.initSegment.byteLength + segment.data.byteLength);
data.set(segment.initSegment, 0);
data.set(segment.data, segment.initSegment.byteLength);
console.log(muxjs.mp4.tools.inspect(data));
sourceBuffer.appendBuffer(data);
})
fetch(segments.shift()).then((response)=>{
return response.arrayBuffer();
}).then((response)=>{
transmuxer.push(new Uint8Array(response));
transmuxer.flush();
})
}
function appendNextSegment(){
// reset the 'data' event listener to just append (moof/mdat) boxes to the Source Buffer
transmuxer.off('data');
transmuxer.on('data', (segment) =>{
sourceBuffer.appendBuffer(new Uint8Array(segment.data));
})
if (segments.length == 0){
// notify MSE that we have no more segments to append.
//mediaSource.endOfStream();
}
if(segments.length>0){
fetch(segments.shift()).then((response)=>{
return response.arrayBuffer();
}).then((response)=>{
transmuxer.push(new Uint8Array(response));
transmuxer.flush();
})
}
}
</script>
</body>
</html>
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));
?>
I'm building a web app, where you can record audio from the browser and then upload it to the server so that other users can listen to it, give you an evaluation, corrections - language learning.
I have parts of the code but I cannot figure out how to combine them together:
JS (script for recording audio - works fine, skipped the part for record and stop button):
navigator.mediaDevices.getUserMedia({audio:true})
.then(stream => {handlerFunction(stream)})
function handlerFunction(stream) {
rec = new MediaRecorder(stream);
rec.ondataavailable = e => {
audioChunks.push(e.data);
if(rec.state == "inactive") {
let blob = new Blob(audioChunks, {type:'audio/mpeg-3'});
recordedAudio.src = URL.createObjectURL(blob);
recordedAudio.controls = true;
var url = recordedAudio.src;
var link = document.getElementById("download");
link.href = url;
link.download = 'audio.wav';
}
}
}
HTML part to create the controls: record, stop, then download the recorded audio:
<button id="record">Record</button>
<button id="stopRecord">Stop</button>
<p> <audio id="recordedAudio"></audio></p>
<div id="recordings"><a id="download"> Download</a></div>
Then I have a file chooser and an AngularJS directive to upload a file - after calling: doUploadFile() the file gets to the controller and is being uploaded to the server.
Uploading works fine with selecting the file, but I don't know how to combine the 2 pieces of code together.
<form>
<input type="file" file-model="uploadedFile">
<button type="submit" ng-click="doUploadFile()">Upload</button>
</form>
File upload directive:
.directive('fileModel', ['$parse', function($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
element.bind('change', function() {
scope.$apply(function() {
modelSetter(scope, element[0].files[0]);
});
});
}
};
}]);
What I want to achieve here:
After finishing recording a button/link pops up
This link is associated with the recorded audio file
Clicking this button calls the doUploadFile() method
How to modify the code and put the 2 pieces together so we can upload straight after recording is finished?
I'm still new to AngularJS, would be very happy for your help!
EDIT
Managed to get it working:
Erased the form for file upload. The directive is also not needed anymore. I put the code for the recording and uploading together. I added a FormData object inside of the controller and appended the blob. Works fine now.
var data = new FormData();
$scope.doUploadFile = function() {
var url = "/uploadfile";
var config = {
transformRequest: angular.identity,
transformResponse: angular.identity,
headers : { 'Content-Type': undefined }
}
$http.post(url, data, config)
.then(function(response) {
$scope.uploadResult = response.data;
data = new FormData();
});
};
navigator.mediaDevices.getUserMedia({audio:true})
.then(stream => {handlerFunction(stream)})
function handlerFunction(stream) {
rec = new MediaRecorder(stream);
rec.ondataavailable = e => {
audioChunks.push(e.data);
if(rec.state == "inactive") {
let blob = new Blob(audioChunks, {type:'audio/mpeg-3'});
recordedAudio.src = URL.createObjectURL(blob);
recordedAudio.controls = true;
recordedAudio.autoplay = false;
var url = recordedAudio.src;
data.append('uploadfile', blob, "recording.wav");
}
}
}
I am building a video surveillance application where the requirement is to save recorded video feeds to the server so that they can be served up to the viewer later. Am using MediaRecorder API to do so, and as the continuous stream would end up in a very large file difficult to be posted all at once, i plan to chop the stream blob into multiple chunks and keep posting periodically. Now the recording event is fired with a toggle switch in html page and then Javascript takes over.
Here is the code that i have so far:
HTML:
some code...
<div class="onoffswitch">
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="switch1">
<label class="onoffswitch-label" for="switch1" onclick="toggleVideoFeed();">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
some code...
JavaScript:
var mediaRecorder;
var pushInterval = 6000;
var id, counter = 0;
// var formData;
function toggleVideoFeed() {
var element = document.getElementById("switch1");
element.onchange = (function (onchange) {
return function (evt) {
// reference to event to pass argument properly
evt = evt || event;
// if an existing event already existed then execute it.
if (onchange) {
onchange(evt);
}
if (evt.target.checked) {
startRecord();
} else {
stopRecord();
};
}
})(element.onchange);
}
var dataAvailable = function (e) {
var formData = new FormData();
var fileName = "blob" + counter + ".mp4";
console.log("data size: ", e.data.size);
var encodeData = new Blob([e.data], { type: 'multipart/form-data' });
formData.append("blob", encodeData, fileName);
var request = new XMLHttpRequest();
request.open("POST", "/Device/Upload", false);
request.send(formData);
counter++;
}
function startRecord() {
navigator.getUserMedia = (navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
if (navigator.getUserMedia) {
navigator.getUserMedia(
// constraints
{
video: true,
audio: false
},
// successCallback
function (stream) {
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.start();
mediaRecorder.ondataavailable = dataAvailable;
// setInterval(function () { mediaRecorder.requestData() }, 10000);
},
// errorCallback
function (err) {
console.log("The following error occured: " + err);
}
);
} else {
console.log("getUserMedia not supported");
}
}
function stopRecord() {
mediaRecorder.stop();
}
Controller-C#
[HttpPost]
public JsonResult Upload(HttpPostedFileBase blob)
{
string fileName = blob.FileName;
int i = Request.Files.Count;
blob.SaveAs(#"C:\Users\priya_000\Desktop\Videos\" + fileName);
return Json("success: " + i + fileName);
}
THE PROBLEM:
When i try playing the received .mp4 files as i get them on the server end, i can play just the first file blob0 and the rest although they show similar sizes to the first file (4 mb each) do not contain any video data. Is it possible that the data i receive on the other end is corrupt/ garbled? Or is there something wrong with my code. Please help guys - Have been trying to solve this problem since the last 10 days with no clue how to figure it out.
mp4 files have a header which is put at the beginning of the file. The header contains information used to initialize the decoder and without it the file cannot be played. Try concatenating the files (the first one and the second one) and see if the whole file plays. If yes then the problem is a missing header. If not please send me the two files so I can inspect them and see what exactly is stored in these.
There is no way AFAIK to instruct MediaRecorder to append the header to each blob of video. You have to do it manually.
PS. sorry for the late response.
You can use RecordRTC by Muaz Khan and use the MVC Section to record and save video to the server.
I have pieced together a Javascript audio recorder that grabs the user microphone via getUserMedia and records and outputs a .OGG file for download. Note this only works in Firefox, which is fine for me.
Rather than provide a download link I want the file to be uploaded to the server via PHP as soon as it stops recording. I'm not sure how I should be going about doing that. I have tried using a form to grab it from $_FILES but I can't figure out how to pre populate the form with the BLOB to submit.
Here is what I have so far. Any input on how to move this along to a server via PHP would be appreciated!
<html>
<head>
</head>
<body>
<div id="container">
<audio controls autoplay></audio>
<a id="downloadLink" download="mediarecorder.ogg" name="mediarecorder.ogg" href></a>
<p id="data"></p>
<script >
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
var constraints = {audio: true};
var audioElement = document.querySelector('audio');
var dataElement = document.querySelector('#data');
var downloadLink = document.querySelector('a#downloadLink');
function errorCallback(error){
console.log("navigator.getUserMedia error: ", error);
}
var count = 0;
function startRecording(stream) {
log('Starting...');
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.onerror = function(e){
log('Error: ' + e);
};
mediaRecorder.onstart = function(e){
log('Started');
};
mediaRecorder.onstop = function(e){
log('Stopped');
};
mediaRecorder.onwarning = function(e){
log('Warning: ' + e);
};
// parameter is number of milliseconds of data to return in a single Blob
mediaRecorder.start();
window.setTimeout(function(){
mediaRecorder.stop();
}, 2400000);
}
window.onload = function(){
if (typeof MediaRecorder === 'undefined' || !navigator.getUserMedia) {
alert('Sorry! This demo requires Firefox Nightly.');
} else {
window.onkeydown = function(e){
if ( e.keyCode == 82 ) { //R pressed
navigator.getUserMedia(constraints, startRecording, errorCallback);
} else if ( e.keyCode == 83 ) { // S pressed
mediaRecorder.stop();
mediaRecorder.ondataavailable = function(e) {
log('Data available...');
count++;
if (count > 1) {
return;
}
console.log(e);
audioElement.src = window.URL.createObjectURL(e.data);
downloadLink.href = window.URL.createObjectURL(e.data); //Audio BLOB
downloadLink.innerHTML = "Download ogg audio file";
};
}
}
}
};
function log(message){
dataElement.innerHTML = message + '<br>' + dataElement.innerHTML ;
}
</script>
</div>
</body>
</html>
For security reasons in most browsers it is not allowed to pre fill an html upload element. Is the ogg file stored locally (on the client). If it is stored on the webserver an option could be to post the (public available) url to a php script and open it in by using file_get_contents.