我已经在javascript中创建了一个可视化工具,当您选择音乐目录时,您可以选择该目录中的文件进行播放并让可视化工具移动到。但是在加载目录然后更改歌曲超过4次之后,它似乎会导致从可视化工具中响应较少的运动。我不确定为什么会这样。 这是这种情况的一个例子。
继续从下拉框中更改歌曲,直到您看到它开始变慢。
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 = "";
// do 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" + " at drop event at firefox, upload folders at '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)) { /* do stuff, that is all code currently within Promise resolver function */ } else { /* proceed to 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 to 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);
for (var i = 0; i < 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);
}
&#13;
<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>
&#13;
答案 0 :(得分:2)
如yuriy636所述,您正在为每首新歌开始一个新动画,而不会停止前一首。 因此,当你播放5首歌曲时,你仍然有5个可视化渲染循环在每一帧运行,以及5个分析器。
这里最好的做法是重构你的代码:
<canvas>
,所以只启动一个渲染动画使用单个分析器时,渲染在更改源时不会使用任何新内容,它始终是相同的画布,相同的分析器,相同的可视化。
这是一个概念的快速证明,真的很脏,但我希望你能够做到我所做的和为什么。
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 all the IDK what it was meant for directory special handlers
function displayFiles() {
select.innerHTML = "";
// that's all 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 the 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 to 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 are just pointers to real file on the user's system
if (url) URL.revokeObjectURL(url);
resolve(file.name);
}
if (url) URL.revokeObjectURL(url);
url = URL.createObjectURL(file);
// audio.load(); // would just set a 404 since you revoked the URL just 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();
for (var i = 0; i < bufferLength; i++) {
barHeight = dataArray[i];
// micro-optimisation, but concatenating all the rects in a single shape is easier for the 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;
},
};
// we can even add auto pause linked to the audio element
var pauseTimeout = null;
audio.onpause = function() {
// let's really do it in 2s to keep the tear down effect
pauseTimeout = setTimeout(viz.pause, 2000);
}
audio.onplaying = function() {
clearTimeout(pauseTimeout);
// we were 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>