Почему при повторном прослушивании песни, качество звука ухудшается?
У меня есть несколько аудио, которые могу слушать с помощью аудиоплеера. Для этого есть отдельный класс Audioplayer, в котором присутствуют 2 метода (на самом деле их больше, попытался сократить для большего понимания. Кому нужен весь код, то он находится здесь). Метод play начинает запускать проигрывание песни или ставить ее на паузу, в нем присутствует работа с AudioContext, а метод _setAudioBarsAnim просто отображает анимацию на холсте.
Проблема состоит в том, что в первый раз, когда слушаешь песню, ее звучание обычное, а потом, когда ставишь ее на паузу и опять проигрываешь, то она становится громче и хуже по звучанию.
Только-только разбираюсь с этим инструментом, поэтому не сильно разбираюсь во всех его аспектах. В чем может быть проблема?
Audioplayer
class Audioplayer {
constructor() {
this.elAudio = document.createElement("audio");
this.elCanvasAnim = document.querySelector(".audio-player__anim-canvas");
this.play = false;
this.animIsActive = false;
this.audioAnalyser = null;
}
playAudio(audioSrc) {
if (audioSrc !== this.elAudio.src) {
this.elAudio.src = audioSrc;
this.play = true;
}
const promise = fetch(audioSrc)
.then((res) => res.blob())
.then(() => this.elAudio.play());
if (promise !== undefined) {
promise
.then(() => {
if (this.play) {
this.elAudio.play();
const ctx = new (window.AudioContext || window.webkitAudioContext)();
const source = ctx.createMediaElementSource(this.elAudio);
this.audioAnalyser = ctx.createAnalyser();
this.audioAnalyser.connect(ctx.destination);
source.connect(ctx.destination);
source.connect(this.audioAnalyser);
if (!this.animIsActive) {
this.animIsActive = true;
this._setAudioBarsAnim();
}
} else {
this.elAudio.pause();
}
}).catch((err) => {
throw err;
});
}
}
_setAudioBarsAnim() {
const canvas = this.elCanvasAnim;
const ctx = canvas.getContext("2d");
const countLinesOnArea = 250;
const widthLine = canvas.offsetWidth / countLinesOnArea;
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
const drawBars = () => {
const fbcArray = new Uint8Array(this.audioAnalyser.frequencyBinCount);
this.audioAnalyser.getByteFrequencyData(fbcArray);
ctx.clearRect(0, 0, canvas.offsetWidth, canvas.offsetHeight);
fbcArray.slice(0, countLinesOnArea).forEach((n, i) => {
const x = i * widthLine;
const percent = Math.ceil((n / 255) * 100);
const height = (percent * canvas.offsetHeight) / 100;
ctx.fillStyle = "white";
ctx.fillRect(x, canvas.offsetHeight - height, widthLine, height);
});
(window.requestAnimationFrame || window.webkitRequestAnimationFrame)(drawBars);
};
drawBars();
}
}
Скрипт для работы плеера
(function () {
let url = null;
const audioplayer = new Audioplayer();
function uploadFiles() {
const elFile = document.querySelector("#file");
elFile.addEventListener("change", (e) => {
const file = e.target.files[0];
url = window.URL.createObjectURL(file);
});
}
function setPlayAudio() {
const elBtnPlay = document.querySelector(".btn-play");
elBtnPlay.addEventListener("click", () => {
audioplayer.play = !audioplayer.play;
if (url) {
audioplayer.playAudio(url);
}
});
}
uploadFiles();
setPlayAudio();
}());
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.audio-player__anim-canvas {
width: 400px;
height: 200px;
background-color: black;
}
</style>
</head>
<body>
<label for="file">
Загрузить аудио
<input type="file" id="file">
</label>
<canvas class="audio-player__anim-canvas"></canvas>
<button class="btn-play">play/pause audio</button>
<script src="index.js"></script>
</body>
</html>
Ответы (2 шт):
Как я понял проблема была в том, что я несколько раз инициализировал AudioContext, поэтому в этот раз я сделал проверку на то, что он уже инициализирован.
Поместил всю работу с AudioContext в отдельный метод для разделения логики.
Сделал проверку состояния AudioContext (кому интересно, то вам сюда)
Также добавил gainNode, который потом изменяю, если меняется громкость аудио (эта работа происходит уже в другом методе, здесь его нет, кому интересно, то вам сюда)
Метод _setAudioBarsAnim не изменял.
В конечном итоге получилось это:
class Audioplayer {
constructor() {
this.elAudio = document.createElement("audio");
this.elCanvasAnim = document.querySelector(".audio-player__anim-canvas");
this.play = false;
this.animIsActive = false;
this.audioContext = null;
this.gainNode = null;
this.audioAnalyser = null;
}
playAudio(audioSrc) {
if (audioSrc !== this.elAudio.src) {
this.elAudio.src = audioSrc;
this.play = true;
}
const promise = this.elAudio.play();
if (promise instanceof Promise) {
promise
.then(() => {
if (this.audioContext instanceof AudioContext && this.audioContext.state === "suspended") {
this.audioContext.resume();
}
if (this.play) {
this.elAudio.play();
this._initAudioContext();
if (!this.animIsActive) {
this.animIsActive = true;
this._setAudioBarsAnim();
}
} else {
this.elAudio.pause();
}
}).catch((err) => {
throw err;
});
}
}
_initAudioContext() {
if (this.audioContext) {
return;
}
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
this.gainNode = this.audioContext.createGain();
this.audioAnalyser = this.audioContext.createAnalyser();
const source = this.audioContext.createMediaElementSource(this.elAudio);
this.audioAnalyser.fftSize = 2048;
source
.connect(this.gainNode)
.connect(this.audioAnalyser)
.connect(this.audioContext.destination);
}
_setAudioBarsAnim() {
const canvas = this.elCanvasAnim;
const ctx = canvas.getContext("2d");
const countLinesOnArea = 250;
const widthLine = canvas.offsetWidth / countLinesOnArea;
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
const drawBars = () => {
const fbcArray = new Uint8Array(this.audioAnalyser.frequencyBinCount);
this.audioAnalyser.getByteFrequencyData(fbcArray);
ctx.clearRect(0, 0, canvas.offsetWidth, canvas.offsetHeight);
fbcArray.slice(0, countLinesOnArea).forEach((n, i) => {
const x = i * widthLine;
const percent = Math.ceil((n / 255) * 100);
const height = (percent * canvas.offsetHeight) / 100;
ctx.fillStyle = "white";
ctx.fillRect(x, canvas.offsetHeight - height, widthLine, height);
});
(window.requestAnimationFrame || window.webkitRequestAnimationFrame)(drawBars);
};
drawBars();
}
}
Хочу ещё предложить простой пример плеера с визуализатором, который работает так же само, и его можно модернизировать под любой вкус:
<html>
<head>
<title>визуализатор</title>
<meta charset="utf-8" />
<style>
#myCanvas{
border-width: 3px;
border-color: white;
border-radius: 15px;
border-style: solid;
box-shadow: 0 0 15px 15px #7e7;
</style>
</head>
<body><center>
<canvas id="myCanvas" width=1024 height=256 style="background:blue"></canvas><br /><br />
<audio controls></audio><br /><br />
<input id=myfile type=file />
</center></body>
<script>
var audio = document.querySelector('audio');
audio.onplay = function(){
var audioCtx = new AudioContext();
var source = audioCtx.createMediaElementSource(audio);
var analyser = audioCtx.createAnalyser();
source.connect(analyser); // Подключаем анализатор к элементу audio
analyser.connect(audioCtx.destination); // Без этой строки нет звука, но анализатор работает.
var frequencyData = new Uint8Array(analyser.frequencyBinCount);
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
var counter = 0;
setInterval(function(){
analyser.getByteFrequencyData(frequencyData); // Записываем в массив данные уровней частот
ctx.fillStyle="blue"; // Задаём цвет фона
ctx.globalAlpha=0.05; // Это для получения эффекта плавного погасания полос
ctx.fillRect (0, 0, canvas.width, canvas.height); // Полупрозрачно чистим экран
ctx.fillStyle="gold";
ctx.globalAlpha = 1;
for(i=0;i<1024;i++){
ctx.fillRect(i,255-frequencyData[i],1,frequencyData[i]); // рисуем полосу
}
}, 20);
}
myfile.oninput=function(){
audio.src = URL.createObjectURL(this.files[0]);
audio.play();
}
</script>
</html>
Может пригодится.