Reputation: 546
I have an audio blob and I want to chop it up at a specific time.How should I do that in Javascript ?
Example:
sliceAudioBlob( audio_blob, 0, 10000 ); // Time in milliseconds [ start = 0, end = 10000 ]
Note: I have no clue how to do that, so a little hint would be really appreciated.
Update :
I'm trying to build a simple audio recorder, but the problem is that there are differences in time duration for each browser, some of them adds few seconds ( Firefox ) and others don't ( Chrome ). So, I came up with the idea to code a method that returns only the slice I want.
Full HTML code :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Audio Recorder</title>
<style>
audio{
display: block;
}
</style>
</head>
<body>
<button type="button" onclick="mediaRecorder.start(1000)">Start</button>
<button type="button" onclick="mediaRecorder.stop()">Stop</button>
<script type="text/javascript">
var mediaRecorder = null,
chunks = [],
max_duration = 10000;// in milliseconds.
function onSuccess( stream ) {
mediaRecorder = new MediaRecorder( stream );
mediaRecorder.ondataavailable = function( event ) {
// chunks.length is the number of recorded seconds
// since every chunk is 1 second duration.
if ( chunks.length < max_duration / 1000 ) {
chunks.push( event.data );
} else {
if (mediaRecorder.state === 'recording') {
mediaRecorder.stop();
}
}
}
mediaRecorder.onstop = function() {
var audio = document.createElement('audio'),
audio_blob = new Blob(chunks, {
'type' : 'audio/mpeg'
});
audio.controls = 'controls';
audio.autoplay = 'autoplay';
audio.src = window.URL.createObjectURL( audio_blob );
document.body.appendChild(audio);
};
}
var onError = function(err) {
console.log('Error: ' + err);
}
navigator.mediaDevices.getUserMedia({ audio: true }).then(onSuccess, onError);
</script>
</body>
</html>
Upvotes: 2
Views: 7404
Reputation: 1004
Try to collect timeSliced chunks on pause, then filter these out:
if(this.isPaused){
this.deleteChunks.push(...audioChunks)
}
const pushChunks = _.filter(audioChunks, chunk=> _.indexOf(this.deleteChunks, chunk) > -1 ? false : true)
console.log("audioChunks::", this.deleteChunks, pushChunks.length, audioChunks.length)
Upvotes: 0
Reputation: 136708
There is no straight forward way to slice an Audio media like that, and this is because your file is made of more than sound signal: there are multiple segments in it, with some headers etc, which position can't be determined just by a byteLength. It is like you can't crop a jpeg image just by getting its x firsts bytes.
There might be ways using the Web Audio API to convert your media File to an AudioBuffer, and then slicing this AudioBuffer's raw PCM data as you wish before packing it back in a media File with the correct new descriptors, but I think you are facing an X-Y problem and if I got it correctly, there is a simple way to fix this X problem.
Indeed, the problem you describe is that either Chrome or Firefox doesn't produce an 10s media from your code.
But that is because you are relying on the timeslice argument of MediaRecorder.start(timeslice) to give you chunks of perfect time.
It won't. This argument should only be understood as a clue you are giving to the browser, but they may well impose their own minimum timeslice and thus not respect your argument. (2.3[Methods].5.4).
Instead, you'll be better using a simple setTimeout
to trigger your recorder's stop()
method when you want:
start_btn.onclick = function() {
mediaRecorder.start(); // we don't even need timeslice
// now we'll get similar max duration in every browsers
setTimeout(stopRecording, max_duration);
};
stop_btn.onclick = stopRecording;
function stopRecording() {
if (mediaRecorder.state === "recording")
mediaRecorder.stop();
};
Here is a live example using gUM hosted on jsfiddle.
And a live snippet using a silent stream from the Web Audio API because StackSnippet's protection doesn't run well with gUM...
var start_btn = document.getElementById('start'),
stop_btn = document.getElementById('stop');
var mediaRecorder = null,
chunks = [],
max_duration = 10000; // in milliseconds.
start_btn.onclick = function() {
mediaRecorder.start(); // we don't even need timeslice
// now we'll get similar max duration in every browsers
setTimeout(stopRecording, max_duration);
this.disabled = !(stop_btn.disabled = false);
};
stop_btn.onclick = stopRecording;
function stopRecording() {
if (mediaRecorder.state === "recording")
mediaRecorder.stop();
stop_btn.disabled = true;
};
function onSuccess(stream) {
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = function(event) {
// simply always push here, the stop will be controlled by setTimeout
chunks.push(event.data);
}
mediaRecorder.onstop = function() {
var audio_blob = new Blob(chunks);
var audio = new Audio(URL.createObjectURL(audio_blob));
audio.controls = 'controls';
document.body.appendChild(audio);
// workaround https://crbug.com/642012
audio.currentTime = 1e12;
audio.onseeked = function() {
audio.onseeked = null;
console.log(audio.duration);
audio.currentTime = 0;
audio.play();
}
};
start_btn.disabled = false;
}
var onError = function(err) {
console.log('Error: ' + err);
}
onSuccess(SilentStream());
function SilentStream() {
var ctx = new(window.AudioContext || window.webkitAudioContext),
gain = ctx.createGain(),
dest = ctx.createMediaStreamDestination();
gain.connect(dest);
return dest.stream;
}
<button id="start" disabled>start</button>
<button id="stop" disabled>stop</button>
Upvotes: 7
Reputation: 4348
In your line:
<button type="button" onclick="mediaRecorder.start(1000)">Start</button>
mediaRecorder.start receive as a parameter the timeslice. The timeslice specifies the size in milliseconds of the chunks. So in order to cut your audio you should modify the chunk array that you have been creating on mediaRecorder.ondataavailable
E.g: You pass 1000 as the timeslice that means that you have slices of 1 second, and you want to cut the first 2 seconds of the recording.
You just have to do something like this:
mediaRecorder.onstop = function() {
//Remove first 2 seconds of the audio
var chunksSliced = chunks.slice(2);
var audio = document.createElement('audio'),
// create the audio from the sliced chunk
audio_blob = new Blob(chunksSliced, {
'type' : 'audio/mpeg'
});
audio.controls = 'controls';
audio.autoplay = 'autoplay';
audio.src = window.URL.createObjectURL( audio_blob );
document.body.appendChild(audio);
};
}
You can reduce the size of the chunks in milliseconds if needed. Just pass different number to start
and slice the array where you want.
To have a more specific answer you could do this for audioSlice:
const TIMESLICE = 1000;
// @param chunks Array with the audio chunks
// @param start where to start cutting in seconds
// @param end where to stop cutting in seconds
function audioSlice(chunks, start, end) {
const timeSliceToSeconds = TIMESLICE/1000;
const startIndex = Math.round(start / timeSliceToSeconds);
const endIndex = Math.round(end / timeSliceToSeconds);
if (startIndex < chunks.length && endIndex < chunks.length) {
return chunks.slice(startIndex, endIndex)
}
throw Error('You cannot cut this at those points');
}
If you modify TIMESLICE for your value, it will calculate in which place to cut when passed in seconds
Upvotes: 1