Почему при повторном прослушивании песни, качество звука ухудшается?

Рейтинг: 0Ответов: 2Опубликовано: 22.06.2023

У меня есть несколько аудио, которые могу слушать с помощью аудиоплеера. Для этого есть отдельный класс 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();
    }
}
▲ 0

Хочу ещё предложить простой пример плеера с визуализатором, который работает так же само, и его можно модернизировать под любой вкус:

<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>

Может пригодится.