Bootstrap

音频可视化小工具

文章说明

这是一个基于 JavaScriptWeb Audio API 实现的音频可视化小工具。通过上传音频文件,工具可以实时展示音频的可视化效果,包括条形频谱、圆形频谱等多种模式。代码由 GPT 生成,适合用于学习、演示或嵌入到网页中。


功能特点

  1. 音频可视化

    • 支持条形频谱(Bar Mode)、圆形频谱(Circle Mode)和扇形频谱(Arc Mode)三种可视化模式。
    • 实时动态展示音频的频率数据。
  2. 音频播放控制

    • 支持播放、暂停功能。
    • 上传音频文件后自动播放。
  3. 元数据展示

    • 自动提取音频文件的元数据(如歌曲名称、艺术家、封面图片)。
    • 如果元数据不存在,则显示文件名作为歌曲名称。
  4. 交互设计

    • 提供上传按钮、模式切换按钮和暂停按钮,操作简单直观。
    • 界面美观,支持浅蓝色主题和毛玻璃效果。

使用说明

  1. 上传音频文件

    • 点击 Upload Audio File 按钮,选择本地音频文件(支持 MP3、WAV 等格式)。
    • 上传后,音频会自动播放,并显示可视化效果。
  2. 切换可视化模式

    • 点击 Bar ModeCircle ModeArc Mode 按钮,切换不同的可视化效果。
  3. 播放/暂停音频

    • 点击 Pause 按钮可以暂停音频,再次点击可以继续播放。
  4. 查看元数据

    • 上传音频后,工具会自动显示歌曲名称、艺术家和封面图片(如果音频文件包含元数据)。

技术细节

  1. 核心技术

    • 使用 Web Audio API 分析音频数据。
    • 通过 Canvas 绘制实时可视化效果。
    • 使用 jsmediatags 库提取音频文件的元数据(如 ID3 标签)。
  2. 可视化模式

    • 条形频谱:将音频频率数据绘制为条形图。
    • 圆形频谱:将音频频率数据围绕中心点绘制为圆形。
    • 扇形频谱:将音频频率数据围绕中心点绘制为扇形。
  3. 界面设计

    • 使用 CSS 实现毛玻璃效果和浅蓝色主题。
    • 动态切换按钮状态,提升用户体验。

核心代码

一个HTML页面

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

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Audio Visualization</title>
        <style>
            body {
                display: flex;
                justify-content: center;
                align-items: center;
                height: 100vh;
                margin: 0;
                background: linear-gradient(135deg, #1e3c72, #2a5298);
                color: #fff;
                font-family: 'Arial', sans-serif;
                overflow: hidden;
            }

            .container {
                display: flex;
                flex-direction: column;
                align-items: center;
                background: rgba(255, 255, 255, 0.1);
                border-radius: 20px;
                padding: 20px;
                box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
                backdrop-filter: blur(10px);
                width: 90%;
                max-width: 800px;
            }

            canvas {
                background: rgba(255, 255, 255, 0.1);
                border-radius: 15px;
                margin-top: 20px;
                width: 100%;
                height: 300px;
            }

            #upload-label {
                background: rgba(255, 255, 255, 0.2);
                padding: 10px 20px;
                border-radius: 25px;
                cursor: pointer;
                font-size: 16px;
                color: #fff;
                transition: background 0.3s ease;
                margin-bottom: 20px;
            }

            #upload-label:hover {
                background: rgba(255, 255, 255, 0.3);
            }

            #audioFile {
                display: none;
            }

            .music-info {
                display: flex;
                align-items: center;
                width: 100%;
                margin-bottom: 20px;
            }

            .music-cover {
                width: 100px;
                height: 100px;
                border-radius: 10px;
                margin-right: 20px;
                background: rgba(255, 255, 255, 0.1);
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
                object-fit: cover;
                opacity: 0;
                /* Initially hidden */
                transition: opacity 0.5s ease;
            }

            .music-cover.visible {
                opacity: 1;
                /* Show when cover is loaded */
            }

            .music-details {
                display: flex;
                flex-direction: column;
                justify-content: center;
            }

            .music-title {
                font-size: 20px;
                font-weight: bold;
                margin: 0;
            }

            .music-artist {
                font-size: 16px;
                color: rgba(255, 255, 255, 0.8);
                margin: 5px 0 0 0;
            }

            .mode-switcher {
                display: flex;
                gap: 10px;
                margin-top: 20px;
            }

            .mode-switcher button {
                background: rgba(255, 255, 255, 0.2);
                border: none;
                padding: 10px 20px;
                border-radius: 25px;
                color: #fff;
                font-size: 14px;
                cursor: pointer;
                transition: background 0.3s ease;
            }

            .mode-switcher button:hover {
                background: rgba(255, 255, 255, 0.3);
            }

            .mode-switcher button.active {
                background: #00b4db;
            }

            #pause-button {
                background: rgba(255, 255, 255, 0.2);
                border: none;
                padding: 10px 20px;
                border-radius: 25px;
                color: #fff;
                font-size: 14px;
                cursor: pointer;
                transition: background 0.3s ease;
                margin-top: 20px;
            }

            #pause-button:hover {
                background: rgba(255, 255, 255, 0.3);
            }
        </style>
    </head>

    <body>
        <div class="container">
            <label for="audioFile" id="upload-label">Upload Audio File</label>
            <input type="file" id="audioFile" accept="audio/*"/>
            <div class="music-info">
                <div class="music-cover" id="music-cover"></div>
                <div class="music-details">
                    <p class="music-title" id="music-title">Song Title</p>
                    <p class="music-artist" id="music-artist">Artist</p>
                </div>
            </div>
            <canvas id="visualizer" width="800" height="300"></canvas>
            <div class="mode-switcher">
                <button id="bar-mode" class="active">Bar Mode</button>
                <button id="circle-mode">Circle Mode</button>
                <button id="arc-mode">Arc Mode</button>
            </div>
            <button id="pause-button">Pause</button>
        </div>

        <!-- Include jsmediatags library -->
        <script src="https://unpkg.com/[email protected]/dist/jsmediatags.min.js"></script>
        <script>
            const canvas = document.getElementById('visualizer');
            const ctx = canvas.getContext('2d');
            const audioFileInput = document.getElementById('audioFile');
            const musicCover = document.getElementById('music-cover');
            const musicTitle = document.getElementById('music-title');
            const musicArtist = document.getElementById('music-artist');
            const barModeButton = document.getElementById('bar-mode');
            const circleModeButton = document.getElementById('circle-mode');
            const arcModeButton = document.getElementById('arc-mode');
            const pauseButton = document.getElementById('pause-button');

            let audioContext;
            let analyser;
            let source;
            let dataArray;
            let bufferLength;
            let currentMode = 'bar'; // Default mode
            let audio; // Audio element
            let isPlaying = false; // Track playback state

            // Event listeners for mode switching
            barModeButton.addEventListener('click', () => switchMode('bar'));
            circleModeButton.addEventListener('click', () => switchMode('circle'));
            arcModeButton.addEventListener('click', () => switchMode('arc'));

            // Event listener for pause button
            pauseButton.addEventListener('click', togglePlayback);

            function switchMode(mode) {
                currentMode = mode;
                barModeButton.classList.remove('active');
                circleModeButton.classList.remove('active');
                arcModeButton.classList.remove('active');
                if (mode === 'bar') barModeButton.classList.add('active');
                if (mode === 'circle') circleModeButton.classList.add('active');
                if (mode === 'arc') arcModeButton.classList.add('active');
            }

            function togglePlayback() {
                if (isPlaying) {
                    audio.pause();
                    pauseButton.textContent = 'Play';
                } else {
                    audio.play();
                    pauseButton.textContent = 'Pause';
                }
                isPlaying = !isPlaying;
            }

            audioFileInput.addEventListener('change', function (event) {
                const file = event.target.files[0];
                if (file) {
                    const fileURL = URL.createObjectURL(file);
                    audio = new Audio(fileURL);
                    setupAudioContext(audio);
                    audio.play();
                    isPlaying = true;
                    pauseButton.textContent = 'Pause';

                    // Extract metadata using jsmediatags
                    jsmediatags.read(file, {
                        onSuccess: function (tag) {
                            const tags = tag.tags;
                            // Update song title
                            if (tags.title) {
                                musicTitle.textContent = tags.title;
                            } else {
                                musicTitle.textContent = file.name.replace(/\.[^/.]+$/, ""); // Fallback to file name
                            }
                            // Update artist
                            if (tags.artist) {
                                musicArtist.textContent = tags.artist;
                            } else {
                                musicArtist.textContent = "Unknown Artist";
                            }
                            // Update cover image
                            if (tags.picture) {
                                const picture = tags.picture;
                                const base64String = Array.from(picture.data)
                                    .map(byte => String.fromCharCode(byte))
                                    .join('');
                                const base64 = `data:${picture.format};base64,${window.btoa(base64String)}`;
                                musicCover.style.backgroundImage = `url(${base64})`;
                                musicCover.classList.add('visible'); // Show cover
                            } else {
                                musicCover.style.backgroundImage = ''; // No cover
                                musicCover.classList.remove('visible'); // Hide cover
                            }
                        },
                        onError: function (error) {
                            console.error("Error reading metadata:", error);
                            // Fallback to file name if metadata cannot be read
                            musicTitle.textContent = file.name.replace(/\.[^/.]+$/, "");
                            musicArtist.textContent = "Unknown Artist";
                            musicCover.style.backgroundImage = ''; // No cover
                            musicCover.classList.remove('visible'); // Hide cover
                        }
                    });
                }
            });

            function setupAudioContext(audio) {
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
                analyser = audioContext.createAnalyser();
                source = audioContext.createMediaElementSource(audio);
                source.connect(analyser);
                analyser.connect(audioContext.destination);
                analyser.fftSize = 512;

                bufferLength = analyser.frequencyBinCount;
                dataArray = new Uint8Array(bufferLength);

                draw();
            }

            function draw() {
                requestAnimationFrame(draw);

                analyser.getByteFrequencyData(dataArray);

                ctx.clearRect(0, 0, canvas.width, canvas.height);

                if (currentMode === 'bar') {
                    drawBars();
                } else if (currentMode === 'circle') {
                    drawCircle();
                } else if (currentMode === 'arc') {
                    drawArc();
                }
            }

            function drawBars() {
                const barWidth = (canvas.width / bufferLength) * 1.5; // Thinner bars
                let barHeight;
                let x = 0;

                for (let i = 0; i < bufferLength; i++) {
                    barHeight = dataArray[i] / 2;

                    // Create a gradient for each bar
                    const gradient = ctx.createLinearGradient(x, canvas.height, x + barWidth, canvas.height - barHeight);
                    gradient.addColorStop(0, '#00b4db');
                    gradient.addColorStop(1, '#0083b0');

                    ctx.fillStyle = gradient;
                    ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight);

                    // Add glow effect
                    ctx.shadowBlur = 10;
                    ctx.shadowColor = 'rgba(0, 180, 219, 0.7)';

                    x += barWidth + 1;
                }
            }

            function drawCircle() {
                const centerX = canvas.width / 2;
                const centerY = canvas.height / 2;
                const radius = 100;
                const barWidth = 2; // Thinner bars
                const angleStep = (Math.PI * 2) / bufferLength;

                for (let i = 0; i < bufferLength; i++) {
                    const barHeight = dataArray[i] / 2;
                    const angle = i * angleStep;

                    const x = centerX + Math.cos(angle) * radius;
                    const y = centerY + Math.sin(angle) * radius;
                    const endX = centerX + Math.cos(angle) * (radius + barHeight);
                    const endY = centerY + Math.sin(angle) * (radius + barHeight);

                    // Create a gradient for each bar
                    const gradient = ctx.createLinearGradient(x, y, endX, endY);
                    gradient.addColorStop(0, '#00b4db');
                    gradient.addColorStop(1, '#0083b0');

                    ctx.strokeStyle = gradient;
                    ctx.lineWidth = barWidth;
                    ctx.beginPath();
                    ctx.moveTo(x, y);
                    ctx.lineTo(endX, endY);
                    ctx.stroke();

                    // Add glow effect
                    ctx.shadowBlur = 10;
                    ctx.shadowColor = 'rgba(0, 180, 219, 0.7)';
                }
            }

            function drawArc() {
                const centerX = canvas.width / 2;
                const centerY = canvas.height / 2;
                const radius = 100;
                const barWidth = 2; // Thinner bars
                const angleStep = (Math.PI * 2) / bufferLength;

                for (let i = 0; i < bufferLength; i++) {
                    const barHeight = dataArray[i] / 2;
                    const startAngle = i * angleStep;
                    const endAngle = startAngle + angleStep;

                    // Create a gradient for each bar
                    const gradient = ctx.createRadialGradient(
                        centerX, centerY, radius,
                        centerX, centerY, radius + barHeight
                    );
                    gradient.addColorStop(0, '#00b4db');
                    gradient.addColorStop(1, '#0083b0');

                    ctx.strokeStyle = gradient;
                    ctx.lineWidth = barWidth;
                    ctx.beginPath();
                    ctx.arc(centerX, centerY, radius + barHeight, startAngle, endAngle);
                    ctx.stroke();

                    // Add glow effect
                    ctx.shadowBlur = 10;
                    ctx.shadowColor = 'rgba(0, 180, 219, 0.7)';
                }
            }
        </script>
    </body>

</html>

效果展示

条形频谱模式
在这里插入图片描述

圆形频谱模式
在这里插入图片描述

源码下载

音频可视化小工具

;