Get duration of a collection of audios - javascript

I am creating an audio player from an audio collection on an HTML page:
...
<div class="tr track" id="01">
<div class="td">
<button class="play" onclick="Play(this)">▶</button>
<button class="play" onclick="Pause(this)">❚❚</button>
<span class="title">A Corda da Liberdade</span>
</div>
<div class="td">
<audio preload="metadata" src="discografia/le-gauche-gnosis/01-a-corda-da-liberdade.ogg"></audio>
</div>
<div class="td">
<span class="duracao"></span>
</div>
</div>
...
I want the <span class="duracao"></span> element to show the duration of the audio it is related to:
// This function format the audio duration number in the way I want to present
function secToStr(sec_num) {
sec_num = Math.floor( sec_num );
var horas = Math.floor(sec_num / 3600);
var minutos = Math.floor((sec_num - (horas * 3600)) / 60);
var segundos = sec_num - (horas * 3600) - (minutos * 60);
if (horas < 10) {horas = "0"+horas;}
if (minutos < 10) {minutos = "0"+minutos;}
if (segundos < 10) {segundos = "0"+segundos;}
var tempo = minutos+':'+segundos;
return tempo;
}
var i;
var audios = document.getElementsByTagName('audio'); // get all audios elements of the player
for (i = 0; i < audios.length; i++) { // looping through audios
var audio = audios[i]; // get actual audio
var duracao = audio.parentNode.nextElementSibling.getElementsByClassName('duracao')[0] // get actual audio 'duration <span>'
audio.onloadedmetadata = function() {
duracao.innerHTML = secToStr(audio.duration);
}
}
The for loop is supposed to do the job but is just adding the duration of the last audio element to the last <span class="duracao"></span> element:
Any help?

The general approach with asynchronous loops would be to promisify the async action and then wait for a Promise.all(all_promises).
However, in this particular case, it might not be that easy:
Some browsers (Chrome to not tell their name) have a limit on the maximum number of parallel network requests a page can make for Media.
From there, you won't be able to get the duration of more than six different media at the same time...
So we need to load them one after the other.
The async/await syntax introduced in ES6 can help here:
const urls = [
'1cdwpm3gca9mlo0/kick.mp3',
'h8pvqqol3ovyle8/tom.mp3',
'agepbh2agnduknz/camera.mp3',
'should-break.mp3',
'/hjx4xlxyx39uzv7/18660_1464810669.mp3',
'kbgd2jm7ezk3u3x/hihat.mp3',
'h2j6vm17r07jf03/snare.mp3'
]
.map(url => 'https://dl.dropboxusercontent.com/s/' + url);
getAllDurations(urls)
.then(console.log)
.catch(console.error);
async function getAllDurations(urls) {
const loader = generateMediaLoader();
let total = 0;
for(let i = 0; i < urls.length; i++) {
total += await loader.getDuration(urls[i]);
}
return total;
}
// use a single MediaElement to load our media
// this is a bit verbose but can be reused for other purposes where you need a preloaded MediaElement
function generateMediaLoader() {
const elem = new Audio();
let active = false; // so we wait for previous requests
return {
getDuration,
load
};
// returns the duration of the media at url or 0
function getDuration(url) {
return load(url)
.then((res) => res && res.duration || 0)
.catch((_) => 0);
}
// return the MediaElement when the metadata has loaded
function load(url) {
if(active) {
return active.then((_) => load(url));
}
return (active = new Promise((res, rej) => {
elem.onloadedmetadata = e => {
active = false;
res(elem);
};
elem.onerror = e => {
active = false;
rej();
};
elem.src = url;
}));
}
}
But it's also very possible to make it ES5 style.
var urls = [
'1cdwpm3gca9mlo0/kick.mp3',
'h8pvqqol3ovyle8/tom.mp3',
'agepbh2agnduknz/camera.mp3',
'should-break.mp3',
'/hjx4xlxyx39uzv7/18660_1464810669.mp3',
'kbgd2jm7ezk3u3x/hihat.mp3',
'h2j6vm17r07jf03/snare.mp3'
]
.map(function(url) {
return 'https://dl.dropboxusercontent.com/s/' + url;
});
getAllDurations(urls, console.log);
function getAllDurations(urls, callback) {
var loader = new Audio();
var loaded = 0;
var total = 0;
loader.onloadedmetadata = function(e) {
total += loader.duration;
loadNext();
};
loader.onerror = loadNext;
loadNext();
function loadNext() {
if(loaded >= urls.length) {
return callback(total);
}
loader.src = urls[loaded++];
}
}

This is an excellent place to learn about, and then use Array.reduce() instead of a for-loop.
The concept of reduce is that you start with some starting value (which can be anything, not just a number), and you then walk through the array in a way that, at ever step, lets you run some code to update that value. So:
const total = [1,2,3,4,5].reduce( (sofar, value) => sofar + value, 0)
will run through the array, with start value 0, and at every step it runs (sofar, value) => sofar + value, where the first argument is always "whatever the original start value is at this point". This function assumes that value is a number (or a string) and adds (or concatenates) it to the start value. So at each step we get:
start = 0
first element: add 1 to this value: 0 + 1 = 1
second element: add 2 to this value: 1 + 2 = 3
third element: add 3 to this value: 3 + 3 = 6
fourth element: add 4 to this value: 6 + 4 = 10
fifth element: add 5 to this value: 10 + 5 = 15
We can apply the same to your audio elements: once they're all done loading in, you can tally their total duration with a single reduce call:
const total = audios.reduce((sofar, audioElement) => {
sofar += audioElement.duration;
}, 0); // this "0" is the starting value for the reduce-function's first argument
console.log(`Total number of seconds of play: ${total}`);
And then you can convert total into whatever format you need.
Alternatively, you can keep a global tally, but making each audo element update the total length themselves, simply by finishing loading:
let total = 0;
function updateTotal(increment) {
total += increment;
// and then update whatever HTML element shows that value on the page,
// in whatever format you need.
}
document.querySelectorAll('audio').forEach(element => {
element.onload = (evt) => {
updateTotal(element.duration);
})
});

Related

Count to numbers above a billion in a few seconds without browser freezing

I am trying to make a counter that counts to big numbers such as 6 billion (like google's random number generator) but the browser freezes.
var counter = document.querySelector("#blahblahblah");
var nb = 0;
var ne = 6_000_000_000;
for (;nb<=ne;nb++) {
counter.innerHTML = nb;
};
The best thing you can do is use requestAnimationFrame. This represents the fastest rate that you can update the DOM.
Use the callback to update your element text to the number proportional to the time allowed; 3 seconds (a few) from your question title
const runCounter = (num, timeout, el) => {
let start;
const step = (ts) => {
if (!start) {
start = ts; // record the first iteration timestamp
}
const progress = (ts - start) / timeout; // how far into the time span
if (progress < 1) {
el.textContent = Math.floor(num * progress);
requestAnimationFrame(step); // request the next frame
} else {
el.textContent = num; // finish
}
};
requestAnimationFrame(step);
};
runCounter(6_000_000_000, 3000, document.getElementById("blahblahblah"));
<output id="blahblahblah">0</output>

Creating a variable counter in javascript with variable speed

I'm having a problem.
I want to make a counter that counts from 1 to 9 and repeats.
The time between the counts should be variable (1 to 10 seconds in the same series).
Sometimes, it should add 1, sometimes 2 (so sometimes skip one number).
Is that possible with javascript?
Thank you in advance.
This is the code I have, but is only counts, does not skip a number sometimes and the time to count is fixed at 500 ms.
<div id="value">1</div>
<script>
function animateValue(id){
var obj = document.getElementById(id);
var current = obj.innerHTML;
setInterval(function(){
obj.innerHTML = current++;
},500);
}
animateValue('value');
</script>
</html>````
First, a JSFiddle:
http://jsfiddle.net/5k0xsrj6/embedded/result/
JSFiddle with larger, stylized text:
https://jsfiddle.net/9f4vgLbx/embedded/result
Edit: I see you're not familiar with JavaScript. I've included non-ES6 JavaScript as well.
The biggest issue you'll face with your code is the use of setInterval, as you want a variable timer.
Instead of setInterval, consider a function that calls itself and sets a timer. Once the setTimeout is called, it will invoke the function again to set another timeout, effectively creating an interval.
Non ES6 Script:
var el = document.body;
var max_count = 9;
var current_count = 1;
// Function which sets our timer
function timer(delay) {
// Set a timeout with our passed `delay` arg
setTimeout(function () {
// Adds either 1 or 2 based on the return value of getIteration
current_count += getIteration();
// As we have a max, reset to 1 if we're over
if (current_count > max_count) {
current_count = 1;
}
// Update innerHTML
writer();
// Call next iteration
loop();
}, delay);
}
// Writes our innerHTML
function writer() {
el.innerHTML = current_count;
}
// Returns 1000 through 10000
function getDelay() {
return Math.ceil(Math.random() * 10) * 1000;
}
// Returns either 1 or 2
function getIteration() {
return Math.ceil(Math.random() * 2);
}
// Our main function to loop
function loop() {
// getDelay will return a value between 1000 - 10000
timer(getDelay());
}
// Sets Initial Value
writer();
// Main
loop();
Original:
Here's an example of the code on the JSFiddle. I've included comments to hopefully explain the logic.
{
const el = document.body;
const max_count = 9;
let current_count = 1;
// Function which sets our timer
const timer = delay => {
setTimeout(() => {
current_count += getIteration();
if (current_count > max_count) {
current_count = 1;
}
// Update innerHTML
writer();
// Call next iteration
main();
}, delay);
}
// Writes our innerHTML
const writer = (str, log) => {
if (log) {
console.log(str);
} else {
el.innerHTML = `Current count: ${current_count}`;
}
}
// Returns 1000 through 10000
const getDelay = () => {
return Math.ceil(Math.random() * 10) * 1000;
}
// Returns either 1 or 2
const getIteration = () => {
return Math.ceil(Math.random() * 2);
}
// Our main function to loop
const main = () => {
const delay = getDelay();
writer(`Next delay is ${delay}ms`, true);
timer(delay);
}
// Set Initial Value
writer();
// Main
main();
}
Hope this helps! If you have any questions, feel free to ask!

Page freezes when 3 second pause is inside a loop

When i loop through an array in javascript i have it pause for 3 seconds after each item. It does this successfully, but it freezes the webpage until the array completes.
function launchTutorial() {
HideFloatingMenu(); //freezes on page and it doesn't when i comment out the subsequent array loop
//highlightElement("diLeftColumn");
//the classes of each element to highlight in the tutorial
var tutorialClasses = [
"diLeftColumn",
"diMiddleColumn",
"diRightColumn"
];
var threeSec = new Date().getTime() + 3000;
for (var i = 0; i < tutorialClasses.length; i++) {
//$.each(tutorialClasses, function (key, value) {
if (i != 0) {
var now = new Date().getTime();
if (now >= threeSec) {
highlightElement(tutorialClasses[i]);
threeSec = new Date().getTime() + 3000;
}
else {
i = i - 1; //go back to this item if it hasn't been 3 seconds
}
}
else {
highlightElement(tutorialClasses[i]);
threeSec = new Date().getTime() + 3000;
}
}
}
I have tried setTimeout(), setInterval(0, delay(), 2 different custom sleep functions, and a while loop. none of them worked.
Use this. In javascript, when you do a while loop that takes time x, the whole page freezes for that time x. So using a while loop is no option. But you can use the function setTimeout like this. This will run the printNextElement function every 10 seconds (in my example).
At the console.log place, do your logic. And change the 10000 to your time.
const ar = ['Hello', 'World'];
let index = 0;
const printNextElement = () => {
console.log(ar[index]);
index += 1;
if(ar.length === index) {
return;
}
window.setTimeout(printNextElement, 10000);
};
printNextElement();

Vue.js timing calculations are not matching plain JavaScript version

I'm trying to create a 'beats per minute' (BPM) calculator, identical (for now) to the one you can find here. But for some reason, when I use the BPM calculator at that link on a test song, it gets within 1 BPM of the actual value of 85.94 within of 7 keypresses and just gets more accurate from there, ending within 0.05 of the actual BPM, whereas with my (essentially identically-coded) Vue.js version, it starts much higher (182-->126-->110) and goes down from there, but even after 60 keypresses it's still off by ~2 BPM, and after a full song, it was still off by about 0.37 BPM.
Here's the code for the plain-JavaScript version at that link:
var count = 0;
var msecsFirst = 0;
var msecsPrevious = 0;
function ResetCount()
{
count = 0;
document.TAP_DISPLAY.T_AVG.value = "";
document.TAP_DISPLAY.T_TAP.value = "";
document.TAP_DISPLAY.T_RESET.blur();
}
function TapForBPM(e)
{
document.TAP_DISPLAY.T_WAIT.blur();
timeSeconds = new Date;
msecs = timeSeconds.getTime();
if ((msecs - msecsPrevious) > 1000 * document.TAP_DISPLAY.T_WAIT.value)
{
count = 0;
}
if (count == 0)
{
document.TAP_DISPLAY.T_AVG.value = "First Beat";
document.TAP_DISPLAY.T_TAP.value = "First Beat";
msecsFirst = msecs;
count = 1;
}
else
{
bpmAvg = 60000 * count / (msecs - msecsFirst);
document.TAP_DISPLAY.T_AVG.value = Math.round(bpmAvg * 100) / 100;
document.TAP_DISPLAY.T_WHOLE.value = Math.round(bpmAvg);
count++;
document.TAP_DISPLAY.T_TAP.value = count;
}
msecsPrevious = msecs;
return true;
}
document.onkeypress = TapForBPM;
// End -->
And here's my version:
computed: {
tappedOutBpm: function() {
let totalElapsedSeconds = (this.timeOfLastBpmKeypress - this.timeOfFirstBpmKeypress) / 1000.0
let bpm = (this.numberOfTapsForBpm / totalElapsedSeconds) * 60.0
return Math.round(100*bpm)/100;
},
},
methods: {
tapForBPM: function() {
let now = new Date;
now = now.getTime();
// let now = window.performance.now()
if (this.timeOfFirstBpmKeypress === 0 || now - this.timeOfLastBpmKeypress > 5000) {
this.timeOfFirstBpmKeypress = now
this.timeOfLastBpmKeypress = now
this.numberOfTapsForBpm = 1
} else {
this.timeOfLastBpmKeypress = now
this.numberOfTapsForBpm++
}
}
}
I figured it out by stepping through both of our code.
The problem was that I was setting the number of taps to 1 as soon as the user tapped the key the first time, when in reality it's not taps that I want to count, but beats, and the first beat requires not one tap, but two: the start and the end of that beat. So what I should do is rename the variable to numberOfTappedOutBeats and set it to 0 after the first tap rather than 1.

Generated nonce length is getting changed

I am trying to generate fixed length nonce (length 9).
But my code is printing sometimes nonce of 8 length and sometime 9 length.
this is what I am trying to do but with different approach (I have modified it for fixed nonce length)
I am not able to understand why it is printing nonce of length 8 when i am passing length as 9 as argument??
It would be great if someone can tell why this is happening.
Below is complete Nodejs code
var last_nonce = null;
var nonce_incr = null;
// if you call new Date to fast it will generate
// the same ms, helper to make sure the nonce is
// truly unique (supports up to 999 calls per ms).
module.exports = {
getNonce: function(length) {
if (length === undefined || !length) {
length = 8;
}
var MOD = Math.pow(10, length);
var now = (+new Date());
if (now !== last_nonce) {
nonce_incr = -1;
}
nonce_incr++;
last_nonce = now;
var nonce_multiplier = ((nonce_incr < 10) ? 10 : ((nonce_incr < 100) ? 100 : 1000));
var s = (((now % MOD) * nonce_multiplier) + nonce_incr) % MOD;
return s;
}
}
//test code
if(require.main === module) {
console.time("run time");
//importing async module
var async = require('async');
var arr = [];
//generating 1000 length array to use it in making 1000 async calls
//to getNonce function
for(var i=0; i<1000; i++) arr.push(i);
//this will call getNonce function 1000 time parallely
async.eachLimit(arr, 1000, function(item, cb) {
console.log(module.exports.getNonce(9));
cb();
}, function(err) {console.timeEnd("run time");});
}
Sample output:
708201864 --> nonce length 9
708201865
708201866
70820190 --> nonce length 8 (why it is coming 8?? when passed length is 9)
70820191
70820192
70820193
70820194
70820195
70820196
70820197
70820198
70820199
708201910
708201911
708201912
708201913
708201914
708201915
708201916
708201917
708201918
In case someone needs it, here is a nonce generator free from convoluted logic, allowing you to control both character sample and nonce size:
const generateNonce = (options) => {
const {
length = 32,
sample = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
} = options || {};
const getRand = () => Math.floor(Math.random() * sample.length);
return Array.from({ length }, () => sample.charAt(getRand())).join('');
};
If you prefer Typescript:
const generateNonce = (options?: { sample?: string, length?: number }) => {
const {
length = 32,
sample = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
} = options || {};
const getRand = () => Math.floor(Math.random() * sample.length);
return Array.from({ length }, () => sample.charAt(getRand())).join('');
};

Categories

Resources