javascript - Possible memory leak or something else? -
i've made visualizer in javascript when select music directory your're able select files within directory play , have visualizer move to. seem after loading directory , changing song more 4 times results in less responsive movement visualizer. i'm unsure why happening. heres example of happening.
keep changing song drop down box until see starting slow.
window.onload = function() { var input = document.getelementbyid("file"); var audio = document.getelementbyid("audio"); var selectlabel = document.queryselector("label[for=select]"); var audiolabel = document.queryselector("label[for=audio]"); var select = document.queryselector("select"); var context = void 0, src = void 0, res = [], url = ""; function processdirectoryupload(event) { var webkitresult = []; var mozresult = []; var files; console.log(event); select.innerhtml = ""; // mozilla stuff function mozreaddirectories(entries, path) { console.log("dir", entries, path); return [].reduce.call(entries, function(promise, entry) { return promise.then(function() { return promise.resolve(entry.getfilesanddirectories() || entry) .then(function(dir) { return dir }) }) }, promise.resolve()) .then(function(items) { var dir = items.filter(function(folder) { return folder instanceof directory }); var files = items.filter(function(file) { return file instanceof file }); if (files.length) { // console.log("files:", files, path); mozresult = mozresult.concat.apply(mozresult, files); } if (dir.length) { // console.log(dir, dir[0] instanceof directory); return mozreaddirectories(dir, dir[0].path || path); } else { if (!dir.length) { return promise.resolve(mozresult).then(function(complete) { return complete }) } } }) }; function handleentries(entry) { let file = "webkitgetasentry" in entry ? entry.webkitgetasentry() : entry return promise.resolve(file); } function handlefile(entry) { return new promise(function(resolve) { if (entry.isfile) { entry.file(function(file) { listfile(file, entry.fullpath).then(resolve) }) } else if (entry.isdirectory) { var reader = entry.createreader(); reader.readentries(webkitreaddirectories.bind(null, entry, handlefile, resolve)) } else { var entries = [entry]; return entries.reduce(function(promise, file) { return promise.then(function() { return listdirectory(file) }) }, promise.resolve()) .then(function() { return promise.all(entries.map(function(file) { return listfile(file) })).then(resolve) }) } }) function webkitreaddirectories(entry, callback, resolve, entries) { console.log(entries); return listdirectory(entry).then(function(currentdirectory) { console.log(`iterating ${currentdirectory.name} directory`, entry); return entries.reduce(function(promise, directory) { return promise.then(function() { return callback(directory) }); }, promise.resolve()) }).then(resolve); } } function listdirectory(entry) { console.log(entry); return promise.resolve(entry); } function listfile(file, path) { path = path || file.webkitrelativepath || "/" + file.name; console.log(`reading ${file.name}, size: ${file.size}, path:${path}`); webkitresult.push(file); return promise.resolve(webkitresult) }; function processfiles(files) { promise.all([].map.call(files, function(file, index) { return handleentries(file, index).then(handlefile) })) .then(function() { console.log("complete", webkitresult); res = webkitresult; res.reduce(function(promise, track) { return promise.then(function() { return playmusic(track) }) }, displayfiles(res)) }) .catch(function(err) { alert(err.message); }) } if ("getfilesanddirectories" in event.target) { return (event.type === "drop" ? event.datatransfer : event.target).getfilesanddirectories() .then(function(dir) { if (dir[0] instanceof directory) { console.log(dir) return mozreaddirectories(dir, dir[0].path || path) .then(function(complete) { console.log("complete:", webkitresult); event.target.value = null; }); } else { if (dir[0] instanceof file && dir[0].size > 0) { return promise.resolve(dir) .then(function() { console.log("complete:", mozresult); res = mozresult; res.reduce(function(promise, track) { return promise.then(function() { return playmusic(track) }) }, displayfiles(res)) }) } else { if (dir[0].size == 0) { throw new error("could not process '" + dir[0].name + "' directory" + " @ drop event @ firefox, upload folders @ 'choose folder...' input"); } } } }).catch(function(err) { alert(err) }) } files = event.target.files; if (files) { processfiles(files) } } function displayfiles(files) { select.innerhtml = ""; return promise.all(files.map(function(file, index) { return new promise(function(resolve) { if (/^audio/.test(file.type)) { /* stuff, code within promise resolver function */ } else { /* proceed next file */ resolve() } var option = new option(file.name, index); select.appendchild(option); resolve() }) })) } function handleselectedsong(event) { if (res.length) { var index = select.value; var track = res[index]; playmusic(track) .then(function(filename) { console.log(filename + " playback completed") }) } else { console.log("no songs play") } } function playmusic(file) { return new promise(function(resolve) { audio.pause(); audio.onended = function() { audio.onended = null; if (url) url.revokeobjecturl(url); resolve(file.name); } if (url) url.revokeobjecturl(url); url = url.createobjecturl(file); audio.load(); audio.src = url; audio.play(); audiolabel.textcontent = file.name; context = context || new audiocontext(); src = src || context.createmediaelementsource(audio); src.disconnect(context); var analyser = context.createanalyser(); var canvas = document.getelementbyid("canvas"); canvas.width = window.innerwidth; canvas.height = window.innerheight; var ctx = canvas.getcontext("2d"); src.connect(analyser); analyser.connect(context.destination); analyser.fftsize = 16384; var bufferlength = analyser.frequencybincount; console.log(bufferlength); var dataarray = new uint8array(bufferlength); var width = canvas.width; var height = canvas.height; var barwidth = (width / bufferlength) * 32; var barheight; var x = 0; function renderframe() { requestanimationframe(renderframe); x = 0; analyser.getbytefrequencydata(dataarray); ctx.fillstyle = "#1b1b1b"; ctx.fillrect(0, 0, width, height); (var = 0; < bufferlength; i++) { barheight = dataarray[i]; ctx.fillstyle = "rgb(5,155,45)" ctx.fillrect(x, (((height - barheight - 5 % barheight) + (20 % height - barheight))), barwidth, barheight + 20 % height); x += barwidth + 2; } } renderframe(); }) } input.addeventlistener("change", processdirectoryupload); select.addeventlistener("change", handleselectedsong); }
<canvas id="canvas" width="window.innerwidth" height="window.innerheight"></canvas> <div id="content"> <label class="custom-file-upload"> select music directory <input id="file" type="file" accept="audio/*" directory allowdirs webkitdirectory/> <p style="color: rgb(5,195,5);">now playing:<label for="audio"></label></p> <p style="color: rgb(5,195,5);">select song</p> <select id="select"> </select> <audio id="audio" controls></audio>
like noted yuriy636, starting new animation every new song, without never stopping previous one. when played 5 songs, still have 5 visualizations rendering loop running @ every frame, , 5 analyzers.
the best here refactor code :
- create single analyzer, update stream feeds it
- make canvas animation autonomous, declare once @ first load
- since you've got 1
<canvas>
, start 1 rendering animation
when using single analyzer, render doesn't use new when change source, it's same canvas, same analyzer, same visualization.
here quick proof of concept, dirty, hope you'll able undertstand did , why.
window.onload = function() { var input = document.getelementbyid("file"); var audio = document.getelementbyid("audio"); var selectlabel = document.queryselector("label[for=select]"); var audiolabel = document.queryselector("label[for=audio]"); var select = document.queryselector("select"); var viz = null; // removed idk meant directory special handlers function displayfiles() { select.innerhtml = ""; // that's synchronous, why promises ? res = array.prototype.slice.call(input.files); res.foreach(function(file, index) { if (/^audio/.test(file.type)) { var option = new option(file.name, index); select.appendchild(option); } }); if (res.length) { var analyser = initaudioanalyser(); viz = initvisualization(analyser); // pre-select first song ? handleselectedsong(); audio.pause(); } } function handleselectedsong(event) { if (res.length) { var index = select.value; var track = res[index]; playmusic(track) .then(function(filename) { console.log(filename + " playback completed") }) viz.play(); } else { console.log("no songs play") } } function playmusic(file) { return new promise(function(resolve) { var url = audio.src; audio.pause(); audio.onended = function() { audio.onended = null; // arguablily useless here since bloburis pointers real file on user's system if (url) url.revokeobjecturl(url); resolve(file.name); } if (url) url.revokeobjecturl(url); url = url.createobjecturl(file); // audio.load(); // set 404 since revoked url before audio.src = url; audio.play(); audiolabel.textcontent = file.name; }); } function initaudioanalyser() { var context = new audiocontext(); var analyser = context.createanalyser(); analyser.fftsize = 16384; var src = context.createmediaelementsource(audio); src.connect(analyser); src.connect(context.destination); return analyser; } function initvisualization(analyser) { var canvas = document.getelementbyid("canvas"); canvas.width = window.innerwidth; canvas.height = window.innerheight; var ctx = canvas.getcontext("2d"); var bufferlength = analyser.frequencybincount; var dataarray = new uint8array(bufferlength); var width = canvas.width; var height = canvas.height; var barwidth = (width / bufferlength) * 32; var barheight; var x = 0; var paused = true; function renderframe() { if (!paused) { requestanimationframe(renderframe); } else { return; } x = 0; analyser.getbytefrequencydata(dataarray); ctx.fillstyle = "#1b1b1b"; ctx.fillrect(0, 0, width, height); ctx.fillstyle = "rgb(5,155,45)" ctx.beginpath(); (var = 0; < bufferlength; i++) { barheight = dataarray[i]; // micro-optimisation, concatenating rects in single shape easier cpu ctx.rect(x, (((height - barheight - 5 % barheight) + (20 % height - barheight))), barwidth, barheight + 20 % height); x += barwidth + 2; } ctx.fill(); } var viz = window.viz = { play: function() { if(paused){ paused = false; renderframe(); } }, pause: function() { paused = true; cleartimeout(pausetimeout); pausetimeout = null; }, }; // can add auto pause linked audio element var pausetimeout = null; audio.onpause = function() { // let's in 2s keep tear down effect pausetimeout = settimeout(viz.pause, 2000); } audio.onplaying = function() { cleartimeout(pausetimeout); // not playing if(!pausetimeout){ viz.play(); } } return viz; } input.addeventlistener("change", displayfiles); select.addeventlistener("change", handleselectedsong); }
<canvas id="canvas" width="window.innerwidth" height="window.innerheight"></canvas> <div id="content"> <label class="custom-file-upload"> select music directory <input id="file" type="file" accept="audio/*" directory allowdirs webkitdirectory/> <p style="color: rgb(5,195,5);">now playing:<label for="audio"></label></p> <p style="color: rgb(5,195,5);">select song</p> <select id="select"> </select> <audio id="audio" controls></audio>
Comments
Post a Comment