音频 时域图-技术鸭论坛-前端交流-技术鸭(jishuya.cn)

音频 时域图

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Audio Visualization with Canvas</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background-color: #000;
        }

        canvas,
        audio {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>

<body>
    <div>
        <canvas></canvas>
        <audio src="./华晨宇 - 烟火里的尘埃.mp3" controls></audio>
    </div>
    
</body>

</html>
<script>
        const audio = document.querySelector('audio');
        const canvas = document.querySelector('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = window.innerWidth * 0.8; // 响应式宽度
        canvas.height = 100; // 更高的画布以提供更好的视觉效果
        const audioContext = new (window.AudioContext || window.webkitAudioContext)();
        let audioBuffer;
        let isDragging = false;

        // 异步加载并解码音频文件
        fetch('./华晨宇 - 烟火里的尘埃.mp3')
            .then(response => response.arrayBuffer())
            .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
            .then(decodedData => {
                audioBuffer = decodedData;
                drawWaveform('grey'); // 首次绘制波形
            })
            .catch(error => console.error("Error loading or decoding audio:", error));

        function drawWaveform(color) {
            // 获取音频总时长
            const duration = audioBuffer.duration;
            // 获取音频的第一个通道的数据。如果是立体声音频,也可以获取第二个通道的数据
            const channelData = audioBuffer.getChannelData(0);
            // 获取画布的宽度,用于确定绘制波形的范围
            const width = canvas.width;
            // 获取画布的高度,用于计算波形的振幅
            const height = canvas.height;
            // 计算步长,即每个波形样本对应的原始音频数据的数量。这是为了确保波形图适应画布的宽度
            const step = Math.ceil(channelData.length / width);
            // 计算振幅,即波形图中最大振幅对应的像素值。这里将画布高度的一半作为最大振幅
            const amp = height / 2;

            // 清除画布,准备绘制新的波形图
            ctx.clearRect(0, 0, width, height);

            // 遍历画布的宽度,每次增加3个像素(为了提高性能和减少绘制的密度)
            for (let i = 0; i < width; i += 3) {
                // 初始化最小值和最大值,用于找到当前步长中的最大振幅和最小振幅
                let min = 1.0, max = -1.0;
                // 遍历当前步长中的所有样本
                for (let j = 0; j < step; j++) {
                    // 获取当前样本的值
                    const datum = channelData[i * step + j];
                    // 更新最小值和最大值
                    if (datum < min) min = datum;
                    if (datum > max) max = datum;
                }
                // 计算当前波形的最低点和最高点的Y坐标
                const yLow = (1 + min) * amp, yHigh = (1 + max) * amp;
                // 设置绘制颜色。如果当前位置小于音频的当前播放时间比例,则使用'blue',否则使用传入的颜色
                ctx.fillStyle = i / width < audio.currentTime / duration ? 'blue' : color;
                // 绘制一个矩形来表示当前的波形样本
                ctx.fillRect(i, yLow, 2, yHigh - yLow); // 使用2像素的宽度来提高波形的可见性
            }
        }

        // 监听音频播放进度更新,重绘波形图
        audio.ontimeupdate = () => drawWaveform('grey');

        // 设置音频时间
        function setAudioTime(x) {
            const duration = audio.duration;
            audio.currentTime = (x / canvas.width) * duration;
        }

        // 添加事件监听器以支持点击和拖动操作
        canvas.addEventListener('mousedown', e => {
            isDragging = true;
            setAudioTime(e.offsetX);
        });

        window.addEventListener('mouseup', () => {
            isDragging = false;
        });

        canvas.addEventListener('mousemove', e => {
            if (isDragging) {
                setAudioTime(e.offsetX);
            }
        });

        // 窗口大小改变时调整画布大小
        window.addEventListener('resize', () => {
            canvas.width = window.innerWidth * 0.8;
            drawWaveform('grey');
        });
    </script>

效果图

20240316172154898-Snipaste_2024-03-16_17-21-43

 

请登录后发表评论

    请登录后查看回复内容