Playing audio backwards with HTMLMediaElement - javascript

I am trying to load and play an audio file in chrome successfully, but I can't play it backwards:
audio = new Audio('http://mathweirdo.com/bingo/audio/buzzer.mp3');
audio.playbackRate = -1;
audio.currentTime = audio.duration; // I have tried ommiting this line
audio.play()
This produces no sound and only one timeupdate event firing.

Using negative values is currently not supported so you will have to load and reverse the buffers manually.
Note that this will require CORS enabled audio source (the one in the example isn't, so I couldn't set up a live demo). Here is one way of doing this:
Load the data via AJAX (this requires CORS enabled for the audio file)
Let the browser parse the buffer into an audio buffer
Get the channel buffer(s) (references)
Reverse the buffer(s)
Initialize the audio buffer and play
This will of course limit you some as you cannot use the Audio element anymore. You will have to support the features you want by adding controls and code for them manually.
// load audio as a raw array buffer:
fetch("http://mathweirdo.com/bingo/audio/buzzer.mp3", process);
// then process the buffer using decoder
function process(file) {
var actx = new (window.AudioContext || window.webkitAudioContext);
actx.decodeAudioData(file, function(buffer) {
var src = actx.createBufferSource(), // enable using loaded data as source
channel, tmp, i, t = 0, len, len2;
// reverse channels
while(t < buffer.numberOfChannels) { // iterate each channel
channel = buffer.getChannelData(t++); // get reference to a channel
len = channel.length - 1; // end of buffer
len2 = len >>> 1; // center of buffer (integer)
for(i = 0; i < len2; i++) { // loop to center
tmp = channel[len - i]; // from end -> tmp
channel[len - i] = channel[i]; // end = from beginning
channel[i] = tmp; // tmp -> beginning
}
}
// play
src.buffer = buffer;
src.connect(actx.destination);
if (!src.start) src.start = src.noteOn;
src.start(0);
},
function() {alert("Could not decode audio!")}
)
}
// ajax loader
function fetch(url, callback) {
var xhr = new XMLHttpRequest();
try {
xhr.open("GET", url);
xhr.responseType = "arraybuffer";
xhr.onerror = function() {alert("Network error")};
xhr.onload = function() {
if (xhr.status === 200) callback(xhr.response);
else alert(xhr.statusText);
};
xhr.send();
} catch (err) {alert(err.message)}
}

Related

data uploading by ajax in iOS webview

In iOS webview I have implemented file uploading:
function upload(blobOrFile) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) { ... };
xhr.send(blobOrFile);
}
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
var blob = this.files[0];
const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes.
const SIZE = blob.size;
var start = 0;
var end = BYTES_PER_CHUNK;
while(start < SIZE) {
upload(blob.slice(start, end));
start = end;
end = start + BYTES_PER_CHUNK;
}
}, false);
})();
This JavaScript work well in all browser, but in iOS webview it send empty POST ?
Before send I tried to alert() blob length was correct, but in server side I get 0 content length. What can cause this problem and is possible to fix this from JS side ?

How to save renderedBuffer as an audio file

I am using the web audio api's offlineAudioContext to render an audio file with a different playback speed. When I do this the altered audio is held in the renderedBuffer.
I am wondering how I can download the audio held in the renderedBuffer?
Here is my code:
// define online and offline audio context
var audioCtx = new AudioContext();
var offlineCtx = new OfflineAudioContext(2,44100*40,44100);
source = offlineCtx.createBufferSource();
// define variables
var pre = document.querySelector('pre');
var myScript = document.querySelector('script');
var play = document.querySelector('.play');
var stop = document.querySelector('.stop');
// use XHR to load an audio track, and
// decodeAudioData to decode it and stick it in a buffer.
// Then we put the buffer into the source
function getData() {
request = new XMLHttpRequest();
request.open('GET', 'sound.mp4', true);
request.responseType = 'arraybuffer';
request.onload = function() {
var audioData = request.response;
audioCtx.decodeAudioData(audioData, function(buffer) {
myBuffer = buffer;
source.buffer = myBuffer;
source.playbackRate.value = 0.50;
source.connect(offlineCtx.destination);
source.start();
//source.loop = true;
offlineCtx.startRendering().then(function(renderedBuffer) {
console.log('Rendering completed successfully');
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var song = audioCtx.createBufferSource();
song.buffer = renderedBuffer;
song.connect(audioCtx.destination);
play.onclick = function() {
song.start();
}
}).catch(function(err) {
console.log('Rendering failed: ' + err);
// Note: The promise should reject when startRendering is called a second time on an OfflineAudioContext
});
});
}
request.send();
}
// Run getData to start the process off
getData();
// dump script to pre element
pre.innerHTML = myScript.innerHTML;

How to listen when audioContext.destination do not play sound in Web Audio API

I'm using the $timeout angular function to call tick() each 512 ms in order to play datas which are in my audio queue. I'm using this to perform a live audio stream. Sometimes there are some cuts in the sounds and I really need to maintain a delta of one second between emitting and receiving sound. So I want to delete some audio datas in my queue corresponding to the duration of each cuts.
Do you know if there is a way to listen to those cuts on the audioContext.destination like :
audioContext.destination.oncuts = function(duration) {
audioQueue.read(duration);
});
Here is my tick and audioQueue functions :
var tick = function() {
$scope.soundclock = Date.now();
$timeout(tick, $scope.tickInterval);
if(startStream && isFocused) {
if(isChrome === true || isOpera === true || isIE === true || isFirefox === true) {
if(audioQueue.length()>=size) {
float32 = audioQueue.read(size);
source = audioContext.createBufferSource();
audioBuffer = audioContext.createBuffer(1, size, sampleRate);
data = audioBuffer.getChannelData(0);
for(var i=0; i<size;i++) {
data[i] = float32[i];
}
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.start(0);
}
}
if(isSafari === true) {
if(audioQueue.length()>=size) {
float32 = audioQueue.read(size);
source = audioContext.createBufferSource();
audioBuffer = audioContext.createBuffer(1, size, sampleRate);
data = audioBuffer.getChannelData(0);
for(var j=0; j<size;j++) {
data[j] = float32[j];
}
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.noteOn(0);
}
}
}
};
var audioQueue = {
buffer: new Float32Array(0),
write: function(newAudio){
currentQLength = this.buffer.length;
newBuffer = new Float32Array(currentQLength+newAudio.length);
d = Date.now() - date;
console.log('Queued '+newBuffer.length+' samples. ');
date = Date.now();
newBuffer.set(this.buffer, 0);
newBuffer.set(newAudio, currentQLength);
this.buffer = newBuffer;
},
read: function(nSamples){
samplesToPlay = this.buffer.subarray(0, nSamples);
this.buffer = this.buffer.subarray(nSamples, this.buffer.length);
console.log('Queue at '+this.buffer.length+' samples. ');
return samplesToPlay;
},
length: function(){
return this.buffer.length;
}
};
You need to not rely on Javascript timers (which are, for audio purposes, horribly inaccurate) and schedule your ticks ahead of time. Check out http://www.html5rocks.com/en/tutorials/audio/scheduling/, which I wrote a while ago about scheduling timers.

Converting a Float32Array of decoded samples to an AudioBuffer

Because one of the browsers I'm trying to support doesn't allow me to decode a specific codec using AudioContext.decodeAudioData() I'm using Aurora.js to decode a audio files.
How can I change the decoded samples received from Aurora.js into an AudioBuffer I can actually use to playback the audio?
This is my code so far:
var AudioContext = (window.AudioContext || window.webkitAudioContext);
var context = new AudioContext();
var segmentUrls = [
'/segments/00.wav',
'/segments/05.wav',
'/segments/10.wav',
'/segments/15.wav',
'/segments/20.wav',
'/segments/25.wav',
'/segments/30.wav',
'/segments/35.wav',
'/segments/40.wav',
'/segments/45.wav',
'/segments/50.wav',
'/segments/55.wav'
];
Promise.all(segmentUrls.map(loadSound))
.then(function(buffers) {
var startAt = 0;
buffers.forEach(function(buffer) {
playSound(startAt, buffer);
startAt += buffer.duration;
});
})
.catch(function(err) {
console.error(err);
});
function playSound(offset, buffer) {
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.start(offset);
}
function loadSound(url) {
return new Promise(function(resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = function onLoad() {
resolve(decodeAudioData(request.response));
};
request.onerror = function onError() {
reject('Could not request file');
};
request.send();
});
}
function decodeAudioData(audioData) {
return new Promise(function(resolve, reject) {
var asset = AV.Asset.fromBuffer(audioData);
asset.decodeToBuffer(function(buffer) {
// Create an AudioBuffer here
});
});
}
You'll have to create an AudioBuffer of the appropriate size and # of channels, and copy the data from one Float32 buffer to another.
Here is the MDN code snippet to put data in an AudioBuffer and then play it:
https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer
// Stereo
var channels = 2;
// Create an empty two second stereo buffer at the
// sample rate of the AudioContext
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var frameCount = audioCtx.sampleRate * 2.0;
var myArrayBuffer = audioCtx.createBuffer(channels, frameCount, audioCtx.sampleRate);
button.onclick = function() {
// Fill the buffer with white noise;
// just random values between -1.0 and 1.0
for (var channel = 0; channel < channels; channel++) {
// This gives us the actual array that contains the data
var nowBuffering = myArrayBuffer.getChannelData(channel);
for (var i = 0; i < frameCount; i++) {
// Math.random() is in [0; 1.0]
// audio needs to be in [-1.0; 1.0]
nowBuffering[i] = Math.random() * 2 - 1;
}
}
// Get an AudioBufferSourceNode.
// This is the AudioNode to use when we want to play an AudioBuffer
var source = audioCtx.createBufferSource();
// set the buffer in the AudioBufferSourceNode
source.buffer = myArrayBuffer;
// connect the AudioBufferSourceNode to the
// destination so we can hear the sound
source.connect(audioCtx.destination);
// start the source playing
source.start();
}

HTML Audio recording until silence?

I'm looking for a browser-based way of recording until a silence occurs.
HTML audio recording from the microphone is possible in Firefox and Chrome - using
Recordmp3js see:
http://nusofthq.com/blog/recording-mp3-using-only-html5-and-javascript-recordmp3-js/
and the code on github: http://github.com/nusofthq/Recordmp3js
I can't see a way to change that code to record until silence.
Record until silence can be done (and tuned) using Java for a native Android App - see here:
Android audio capture silence detection
Google Voice Search demonstrates a browser can doit - but how can I using Javascript?
Any ideas?
If you use the Web Audio API, open up a live microphone audio capture by making a call to : navigator.getUserMedia , then create a node using : createScriptProcessor, then you assign to that node a callback for its event : onaudioprocess . Inside your callback function (below I use script_processor_analysis_node) you have access to the live real-time audio buffer to which you can then parse looking for silence (some length of time where amplitude is low [stays close to zero]).
for normal time domain audio curve see : array_time_domain
which is populated fresh upon each call to callback script_processor_analysis_node ... similarly for frequency domain see array_freq_domain
Turn down your speaker volume or use headphones to avoid feedback from mic -> speaker -> mic ...
<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>capture microphone then show time & frequency domain output</title>
<script type="text/javascript">
var webaudio_tooling_obj = function () {
var audioContext = new AudioContext();
console.log("audio is starting up ...");
var BUFF_SIZE_RENDERER = 16384;
var audioInput = null,
microphone_stream = null,
gain_node = null,
script_processor_node = null,
script_processor_analysis_node = null,
analyser_node = null;
if (!navigator.getUserMedia)
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia || navigator.msGetUserMedia;
if (navigator.getUserMedia){
navigator.getUserMedia({audio:true},
function(stream) {
start_microphone(stream);
},
function(e) {
alert('Error capturing audio.');
}
);
} else { alert('getUserMedia not supported in this browser.'); }
// ---
function show_some_data(given_typed_array, num_row_to_display, label) {
var size_buffer = given_typed_array.length;
var index = 0;
console.log("__________ " + label);
if (label === "time") {
for (; index < num_row_to_display && index < size_buffer; index += 1) {
var curr_value_time = (given_typed_array[index] / 128) - 1.0;
console.log(curr_value_time);
}
} else if (label === "frequency") {
for (; index < num_row_to_display && index < size_buffer; index += 1) {
console.log(given_typed_array[index]);
}
} else {
throw new Error("ERROR - must pass time or frequency");
}
}
function process_microphone_buffer(event) {
var i, N, inp, microphone_output_buffer;
microphone_output_buffer = event.inputBuffer.getChannelData(0); // just mono - 1 channel for now
}
function start_microphone(stream){
gain_node = audioContext.createGain();
gain_node.connect( audioContext.destination );
microphone_stream = audioContext.createMediaStreamSource(stream);
microphone_stream.connect(gain_node);
script_processor_node = audioContext.createScriptProcessor(BUFF_SIZE_RENDERER, 1, 1);
script_processor_node.onaudioprocess = process_microphone_buffer;
microphone_stream.connect(script_processor_node);
// --- enable volume control for output speakers
document.getElementById('volume').addEventListener('change', function() {
var curr_volume = this.value;
gain_node.gain.value = curr_volume;
console.log("curr_volume ", curr_volume);
});
// --- setup FFT
script_processor_analysis_node = audioContext.createScriptProcessor(2048, 1, 1);
script_processor_analysis_node.connect(gain_node);
analyser_node = audioContext.createAnalyser();
analyser_node.smoothingTimeConstant = 0;
analyser_node.fftSize = 2048;
microphone_stream.connect(analyser_node);
analyser_node.connect(script_processor_analysis_node);
var buffer_length = analyser_node.frequencyBinCount;
var array_freq_domain = new Uint8Array(buffer_length);
var array_time_domain = new Uint8Array(buffer_length);
console.log("buffer_length " + buffer_length);
script_processor_analysis_node.onaudioprocess = function() {
// get the average for the first channel
analyser_node.getByteFrequencyData(array_freq_domain);
analyser_node.getByteTimeDomainData(array_time_domain);
// draw the spectrogram
if (microphone_stream.playbackState == microphone_stream.PLAYING_STATE) {
show_some_data(array_freq_domain, 5, "frequency");
show_some_data(array_time_domain, 5, "time"); // store this to record to aggregate buffer/file
// examine array_time_domain for near zero values over some time period
}
};
}
}(); // webaudio_tooling_obj = function()
</script>
</head>
<body>
<p>Volume</p>
<input id="volume" type="range" min="0" max="1" step="0.1" value="0.5"/>
</body>
</html>
This is an old post but I am sure many will have the same problem so I am posting my solution here.
Use hark.js
Below is sample demo code i used for my electron app
hark = require('./node_modules/hark/hark.bundle.js')
navigator.getUserMedia({ audio : true}, onMediaSuccess, function(){});
function onMediaSuccess(blog) {
var options = {};
var speechEvents = hark(blog, options);
speechEvents.on('speaking', function() {
console.log('speaking');
});
speechEvents.on('stopped_speaking', function() {
console.log('stopped_speaking');
});
};
The solution from #Scott Stensland doesn't allow me to parse for silence. I am getting the same value for when I parse the two arrays - that is I'm getting 0 always when parsing arrayFreqDomain and 128 always when parsing arrayTimeDomain
let analyser = context.createAnalyser();
analyser.smoothingTimeConstant = 0;
analyser.fftSize = 2048;
let buffLength = analyser.frequencyBinCount;
let arrayFreqDomain = new Uint8Array(buffLength);
let arrayTimeDomain = new Uint8Array(buffLength);
processor.connect(analyser);
processor.onaudioprocess = (event) => {
/**
*
* Parse live real-time buffer looking for silence
*
**/
analyser.getByteFrequencyData(arrayFreqDomain);
analyser.getByteTimeDomainData(arrayTimeDomain);
if (context.state === "running") {
let sizeBuffer = arrayTimeDomain.length;
let index = 0;
for (; index < 5 && index < sizeBuffer; index += 1) {
console.log(arrayTimeDomain[index]); <----
}
}
}

Categories

Resources