Web Audio Api - Download edited MP3 - javascript

I'm currently editing my mp3 file with multiple effects like so
var mainVerse = document.getElementById('audio1');
var s = source;
source.disconnect(audioCtx.destination);
for (var i in filters1) {
s.connect(filters1[i]);
s = filters1[i];
}
s.connect(audioCtx.destination);
The mp3 plays accordingly on the web with the filters on it. Is it possible to create and download a new mp3 file with these new effects, using web audio api or any writing to mp3 container javascript library ? If not whats the best to solve this on the web ?
UPDATE - Using OfflineAudioContext
Using the sample code from https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext/oncomplete
I've tried using the offline node like so;
var audioCtx = new AudioContext();
var offlineCtx = new OfflineAudioContext(2,44100*40,44100);
osource = offlineCtx.createBufferSource();
function getData() {
request = new XMLHttpRequest();
request.open('GET', 'Song1.mp3', true);
request.responseType = 'arraybuffer';
request.onload = function() {
var audioData = request.response;
audioCtx.decodeAudioData(audioData, function(buffer) {
myBuffer = buffer;
osource.buffer = myBuffer;
osource.connect(offlineCtx.destination);
osource.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);
song.start();
rec = new Recorder(song, {
workerPath: 'Recorderjs/recorderWorker.js'
});
rec.exportWAV(function(e){
rec.clear();
Recorder.forceDownload(e, "filename.wav");
});
}).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();
Still getting the recorder to download an empty file, I'm using the song source as the source for the recorder. The song plays and everything with his code but recorder doesn't download it

Use https://github.com/mattdiamond/Recorderjs to record a .wav file. Then use https://github.com/akrennmair/libmp3lame-js to encode it to .mp3.
There's a nifty guide here, if you need a hand: http://audior.ec/blog/recording-mp3-using-only-html5-and-javascript-recordmp3-js/
UPDATE
Try moving
rec = new Recorder(song, {
workerPath: 'Recorderjs/recorderWorker.js'
});
so that it is located above the call to start rendering, and connect it to osource instead, like so:
rec = new Recorder(osource, {
workerPath: 'Recorderjs/recorderWorker.js'
});
osource.connect(offlineCtx.destination);
osource.start();
offlineCtx.startRendering().then(function(renderedBuffer) {
.....

Related

javascript audio api play blob url

I worked out a testing function for Web Audio API to play url blob:
// trigger takes a sound play function
function loadSound(url, trigger) {
let context = new (window.AudioContext || window.webkitAudioContext)();
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
context.decodeAudioData(request.response, function(buffer) {
trigger(()=>{
// play sound
var source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.start();
});
}, e=>{
console.log(e);
});
}
request.send();
}
loadSound(url, fc=>{
window.addEventListener('click', fc);
});
this is just for test, actually, I need a function to call to directly play the sound from url, if any current playing, quit it.
let ac;
function playSound(url) {
if(ac){ac.suspend()}
let context = new (window.AudioContext || window.webkitAudioContext)();
let request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
context.decodeAudioData(request.response, function(buffer) {
// play sound
let source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
// source.noteOn(0); // play the source now
ac = context;
source.start();
}, e=>{
console.log(e);
});
}
request.send();
}
window.addEventListener('click',()=>{
playSound(url);
});
I did not do much modification, however, the second version, triggers works fine, but always produces no sound.
I suspect it may be this variable scope issue, I will be very glad if you can help me debug it.
since the blob url is too long, I put two versions in code pen.
working version
not working version
Instead of calling supsend on the stored AudioContext, save a reference to the AudioBufferSourceNode that is currently playing. Then check if the reference exits and call stop() whenever you play a new sound.
const context = new AudioContext();
let bufferSource = null;
function playSound(url) {
if (bufferSource !== null) {
bufferSource.stop();
bufferSource = null;
}
let request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = function() {
context.decodeAudioData(request.response, (buffer) => {
bufferSource = context.createBufferSource();
bufferSource.buffer = buffer;
bufferSource.connect(context.destination);
bufferSource.start();
bufferSource.addEventListener('ended', () => {
bufferSource = null;
});
}, (error) => {
console.log(error);
});
}
request.send();
}
window.addEventListener('click', () => {
playSound(url);
});

XMLHTTPrequest plain text to blob (video)

I achieved to get a video from php using this code :
var some_video_element = document.querySelector('video')
var req = new XMLHttpRequest();
req.onload = function () {
var blob_uri = URL.createObjectURL(this.response);
some_video_element.src = blob_uri;
some_video_element.addEventListener('oncanplaythrough', (e) => {
URL.revokeObjectURL(blob_uri);
});
};
req.open("get", "vid.php", true);
req.overrideMimeType('blob');
req.send(null);
However, the loading is long so I want to show data as soon as I get it. From Mozilia, it is indicated we can use plain or "" as mime to get the text in progress. However, I can't achieve to convert plain/text to video/mp4 using a blob. Currently this is the code that doesn't work. I try to get the video when some part is available while the rest is still downloading.
var some_video_element = document.querySelector('video')
var req = new XMLHttpRequest();
req.onprogress = function () {
var text = b64toBlob(Base64.encode(this.response), "video/mp4");
var blob_uri = URL.createObjectURL(text);
some_video_element.src = blob_uri;
some_video_element.addEventListener('oncanplaythrough', (e) => {
URL.revokeObjectURL(blob_uri);
});
};
req.onload = function () {
var text = b64toBlob(this.response, "video/mp4");
var blob_uri = URL.createObjectURL(text);
some_video_element.src = blob_uri;
some_video_element.addEventListener('oncanplaythrough', (e) => {
URL.revokeObjectURL(blob_uri);
});
};
req.open("get", "vid.php", true);
req.overrideMimeType('text\/plain');
req.send(null);
Thanks.
NB : This JavaScript is fetching for this php code : https://codesamplez.com/programming/php-html5-video-streaming-tutorial
But echo data has been changed by echo base64_encode(data);
If you use the Fetch API instead of XMLHttpRequest you can consume the response as a ReadableStream which can be fed into a SourceBuffer. This will allow the video to be playable as soon as it starts to load instead of waiting for the full file to download. This does not require any special video container formats, back-end processing or third-party libraries.
const vid = document.getElementById('vid');
const format = 'video/webm; codecs="vp8,vorbis"';
const mediaSource = new MediaSource();
let sourceBuffer = null;
mediaSource.addEventListener('sourceopen', event => {
sourceBuffer = mediaSource.addSourceBuffer(format);
fetch('https://bes.works/dev/samples/test.webm')
.then(response => process(response.body.getReader()))
.catch(err => console.error(err));
}); vid.src = URL.createObjectURL(mediaSource);
function process(stream) {
return new Response(
new ReadableStream({
start(controller) {
async function read() {
let { done, value } = await stream.read();
if (done) { controller.close(); return; }
sourceBuffer.appendBuffer(value);
sourceBuffer.addEventListener(
'updateend', event => read(),
{ once: true }
);
} read();
}
})
);
}
video { width: 300px; }
<video id="vid" controls></video>
As indicated in the comments, you are missing some decent components.
You can implement what you are asking for but you need to make some changes. Following up on the HTML5 streaming API you can create a stream that will make the video using segments you fetch from the server.
Something to keep in mind is the HLS or DASH protocol that already exists can help, looking at the HLS protocol can help as it's simple to use with the idea of segments that can reach out to your server and just decode your base64'd feed.
https://videojs.github.io/videojs-contrib-hls/

How to combine a video with an audio in javascript?

I am coding youtube video downloader chrome extension. But youtube has separated mp4 and mp3. How can I combine the audio file and image file I received in blob type and turn it into a video with sound?
async function downloadFile(urlToSend) {
return new Promise(resolve => {
var req = new XMLHttpRequest();
req.open("GET", urlToSend, true);
req.responseType = "blob";
req.onload = function (event) {
// var blob = req.response;
// var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
// var link = document.createElement('a');
// link.href = window.URL.createObjectURL(blob);
resolve(req.response)
};
req.send();
})
};
async function zfc() {
var v = await downloadFile('/videoplayback.mp4')
var a = await downloadFile('/videoplayback.weba')
let newBlob = new Blob([v, a], { type: 'video/mp4' })
var as = document.createElement('a')
as.href = window.URL.createObjectURL(newBlob)
as.download = window.URL.createObjectURL(newBlob)
console.log(as)
console.log(newBlob)
// as.click()
var c = document.createElement('video')
c.src = window.URL.createObjectURL(newBlob)
document.body.appendChild(c)
}
zfc()
I tried merging with new blob but the video still has no sound. Can you please help?
Example video link:
https://rr7---sn-u0g3uxax3-xncs.googlevideo.com/videoplayback?expire=1641956798&ei=XvHdYbG8MI2qx_AP14yRoAQ&ip=95.2.13.77&id=o-APHbyEMFJZdr7FwyLDOkQWqycmDmo9oy8bSvx7qP4z-P&itag=313&aitags=133%2C134%2C135%2C136%2C137%2C160%2C242%2C243%2C244%2C247%2C248%2C271%2C278%2C313&source=youtube&requiressl=yes&mh=YY&mm=31%2C29&mn=sn-u0g3uxax3-xncs%2Csn-hgn7yn76&ms=au%2Crdu&mv=m&mvi=7&pl=21&initcwndbps=88750&vprv=1&mime=video%2Fwebm&ns=O-4SxebNzTxani0g_ScQEtMG&gir=yes&clen=589586219&dur=347.800&lmt=1638064072881015&mt=1641934876&fvip=2&keepalive=yes&fexp=24001373%2C24007246&c=WEB&txp=4532434&n=hBnxjZJEX82hOJ&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&sig=AOq0QJ8wRQIhAIu0SR_UsiQyUpJIkL_erKc_dElHk-1rwJMCI1486YaSAiBkH4jg8WHzRvEDsxnTTheBM_f1KsBFzqLiIUFJAIKh5w%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AG3C_xAwRAIgapuFt6YijG3nAVfbULkJq2_uAwcjOnZpd0ZNUo1h5NMCIGgJh22ksRMeMOUkhhQUlRapjqa4DhVv-KfcfnYhkW8l
Example sound link:
https://rr7---sn-u0g3uxax3-xncs.googlevideo.com/videoplayback?expire=1641956798&ei=XvHdYbG8MI2qx_AP14yRoAQ&ip=95.2.13.77&id=o-APHbyEMFJZdr7FwyLDOkQWqycmDmo9oy8bSvx7qP4z-P&itag=251&source=youtube&requiressl=yes&mh=YY&mm=31%2C29&mn=sn-u0g3uxax3-xncs%2Csn-hgn7yn76&ms=au%2Crdu&mv=m&mvi=7&pl=21&initcwndbps=88750&vprv=1&mime=audio%2Fwebm&ns=O-4SxebNzTxani0g_ScQEtMG&gir=yes&clen=5822955&dur=347.821&lmt=1638059244799001&mt=1641934876&fvip=2&keepalive=yes&fexp=24001373%2C24007246&c=WEB&txp=4532434&n=hBnxjZJEX82hOJ&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&sig=AOq0QJ8wRQIgaqKAjgRHlNms4IMVKwGJmRb2DOl7slWujc2OeIqIlSkCIQDvVhAPmxgLg0g2WvrgjB0iNNnCyDbyRQQvu5ODx4PLXA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AG3C_xAwRAIgapuFt6YijG3nAVfbULkJq2_uAwcjOnZpd0ZNUo1h5NMCIGgJh22ksRMeMOUkhhQUlRapjqa4DhVv-KfcfnYhkW8l
I am also currently working on a YouTube video downloader extension for Firefox. If you are using ytdl-core, the response object contains a link that has both in res.player_response.streaming_data.formats[0]. However, there is only one such link, which doesn't allow for users to select their preferred resolution, so being able to merge the two would be extremely helpful.
Also if you are using youtube-dl or anything similar to such the response object should be the same or very similar to ytdl-core's

How to fix “cracking” audio when using Audio Worklets?

I’m trying to understand how AudioWorklet is working and made some tests.
So far, I have a huge “cracking” problem when I let the browser play the sound in the background and do something else (e.g. opening a CPU-heavy application like Photoshop or VSCode and move the window around).
At first I thought it was a hardware problem. I upgraded to Catalina, removed any system audio extension I found, but it’s the same on Android, and some other friends’ computers (Mac, PC).
I’m using Version 1.0.1 Chromium: 78.0.3904.108 (Official Build) (64-bit) myself.
This YouTube video demonstrates the cracking audio issue.
I made two CodePen demos you can test here:
Web Audio Cracks (Vanila + no Worklet):
const ctx = new(window.AudioContext || window.webkitAudioContext)();
const request = new XMLHttpRequest();
const gainNode = ctx.createGain();
const sourceNode = ctx.createBufferSource();
request.open('GET', 'https://poppublic.s3.amazonaws.com/other/2.mp3', true);
request.responseType = 'arraybuffer';
request.onload = () => {
ctx.decodeAudioData(request.response, buffer => {
sourceNode.buffer = buffer;
console.log(sourceNode.buffer.sampleRate);
});
};
request.onerror = function(e) {
console.log('HTTP error', e);
};
request.send();
play = () => {
sourceNode.connect(gainNode);
gainNode.connect(ctx.destination);
sourceNode.start(0);
}
stop = () => {
sourceNode.stop(0);
}
<button onClick="play()">Play</button>
<button onClick="stop()">Stop</button>
Web Audio Cracks (Vanila + Worklet):
const ctx = new(window.AudioContext || window.webkitAudioContext)();
const request = new XMLHttpRequest();
let gainNode = null;
let sourceNode = null;
let buffer = null;
let worklet = null;
try {
const script = 'https://poppublic.s3.amazonaws.com/other/worklet/processor.js';
ctx.audioWorklet.addModule(script).then(() => {
worklet = new AudioWorkletNode(ctx, 'popscord-processor')
request.open('GET', 'https://poppublic.s3.amazonaws.com/other/2.mp3', true);
request.responseType = 'arraybuffer';
request.onload = () => {
ctx.decodeAudioData(request.response, buff => {
buffer = buff;
console.log(buff.sampleRate);
});
};
request.onerror = function(e) {
console.log('HTTP error', e);
};
request.send();
});
} catch (e) {
this.setState({
moduleLoaded: false
});
console.log('Failed to load module', e);
}
play = () => {
stop();
gainNode = ctx.createGain();
sourceNode = ctx.createBufferSource();
sourceNode.buffer = buffer;
sourceNode.connect(gainNode);
gainNode.connect(ctx.destination);
sourceNode.start(0);
}
stop = () => {
try {
sourceNode.disconnect();
gainNode.disconnect();
sourceNode.stop(0);
} catch (e) {
console.log(e.message)
}
}
<button onClick="play()">Play</button>
<button onClick="stop()">Stop</button>
The piano MP3 you’ll hear is a 48000Hz / 32bits / 320kb audio recorded in studio.
Before filing any bugs, I need to make sure my code is correct. Maybe I’m not chaining the things the way it should.
When using a worklet, the default priority of the audio thread is normal. This is not good for audio as you've seen by running webaudio and then moving a window around.
What you can do is go to chrome://flags, search for worklet and enable the flag named "Use realtime priority thread for Audio Worklet". This should help on mac and windows. I don't know if it will make a difference on Android.
If you are hearing cracks with WebAudio without a worklet, as you do in your first codepen example, then that's unexpected and you really should file an issue on that.

Trim an audio file using javascript (first 3 seconds)

I have a question that can I trim my audio file that is recorded via javascript? Like I want to trim the first 3 seconds. I recorded the audio file using p5.js and merged the recorded file with karaoke audio with AudioContext() and I want to trim it because of an unpleasant sound at the start.
You will probably need to read the audio into an AudioBuffer using something like AudioContext.decodeAudioData(), plug the AudioBuffer into a AudioBufferSourceNode. Then you can skip the first 3 seconds using the offset parameter of AudioBufferSourceNode.start() and record the resulting output stream.
Example code:
var source = audioCtx.createBufferSource();
var dest = audioCtx.createMediaStreamDestination();
var mediaRecorder = new MediaRecorder(dest.stream);
var request = new XMLHttpRequest();
request.open('GET', 'your.ogg', true);
request.responseType = 'arraybuffer';
request.onload = function() {
var audioData = request.response;
audioCtx.decodeAudioData(
audioData,
function(buffer) {
source.buffer = buffer;
source.connect(dest);
mediaRecorder.start();
source.start(audioCtx.currentTime, 3);
// etc...
},
function(e){
console.log("Error with decoding audio data" + e.err);
}
);
}
request.send();

Categories

Resources