当前位置: 首页 > news >正文

厦门方易网站制作有限公司做网站对象存储

厦门方易网站制作有限公司,做网站对象存储,seo分析网站,wordpress微信登陆在这个数字时代,音乐不仅是听觉的享受,更可以成为视觉的盛宴!本文用 HTML JavaScript 实现了一个音频可视化播放器,它不仅能播放本地音乐、控制进度和音量,还能通过 Canvas 绘制炫酷的音频频谱图,让你“听…

在这个数字时代,音乐不仅是听觉的享受,更可以成为视觉的盛宴!本文用 HTML + JavaScript 实现了一个音频可视化播放器,它不仅能播放本地音乐、控制进度和音量,还能通过 Canvas 绘制炫酷的音频频谱图,让你“听见色彩,看见旋律”。

效果演示

image-20250604212523138

image-20250604212640313

核心功能

本项目主要包含以下核心功能:

  • 音频播放控制:支持播放、暂停、上一首、下一首等基本操作。
  • 进度控制:显示当前播放时间和总时长,并支持点击进度条跳转。
  • 音量调节:提供滑动条调节播放音量。
  • 播放列表管理:支持动态添加本地音乐文件,显示播放列表并高亮当前播放曲目。
  • 音频可视化:通过Canvas实时绘制音频频谱图,增强用户体验。

页面结构

音频可视化容器

使用 HTML5 的 canvas 元素来绘制动态的音频频谱图。

<div class="visualizer"><canvas id="visualizer"></canvas>
</div>
操作控制区域

整个音乐播放器的主要控制区域,包含播放进度条与时间显示、播放控制按钮、音量调节滑块、文件上传控件。

<div class="controls"><div class="progress-container" id="progress-container"><div class="progress-bar" id="progress-bar"></div></div><div class="time-display"><span id="current-time">0:00</span><span id="total-time">0:00</span></div><div class="control-row"><div class="left"></div><div class="buttons"><button id="prev-btn">上一首</button><button id="play-btn">播放</button><button id="next-btn">下一首</button></div><div class="volume-control"><span>音量</span><input type="range" min="0" max="1" step="0.01" value="0.7" class="volume-slider" id="volume-control"></div></div><div class="file-upload"><input type="file" id="file-input" accept="audio/*" multiple><label for="file-input">添加音乐文件</label></div>
</div>
播放列表区域

该区域用于展示用户上传的音频文件列表,并提供一个初始为空时的提示信息。

<div class="playlist"><h2>播放列表</h2><div id="playlist-items"><div class="empty-playlist">暂无音乐,请添加音乐文件</div></div>
</div>

核心功能实现

添加本地音乐文件

使用 URL.createObjectURL 创建本地文件链接供 audio 播放。

function addMusicFiles(files) {for (let i = 0; i < files.length; i++) {const file = files[i];const url = URL.createObjectURL(file);playlist.push({name: file.name.replace(/\.[^/.]+$/, ""), // 移除扩展名url: url});}// 如果是第一次添加音乐,自动加载第一首if (playlist.length === files.length) {currentTrack = 0;loadTrack();}renderPlaylist();
}
加载当前曲目
function loadTrack() {if (playlist.length === 0) return;const track = playlist[currentTrack];audio.src = track.url;audio.load();updatePlaylistHighlight();if (isPlaying) {audio.play().catch(e => console.log('播放错误:', e));}
}
播放/暂停控制

判断播放列表是否为空,控制播放状态切换,并更新按钮文本,如果播放失败尝试下一首。

function togglePlay() {if (playlist.length === 0) {alert('播放列表为空,请先添加音乐');return;}if (isPlaying) {audio.pause();playBtn.textContent = '播放';} else {initAudioContext(); // 首次播放时才初始化音频上下文audio.play().catch(e => {console.log('播放错误:', e);nextTrack(); // 播放失败自动下一首});playBtn.textContent = '暂停';}isPlaying = !isPlaying;
}
音频上下文初始化

初始化 AudioContext,创建音频分析节点,将音频元素通过 createMediaElementSource 接入分析器,dataArray 用于后续可视化绘制。

function initAudioContext() {if (!audioContext) {audioContext = new (window.AudioContext || window.webkitAudioContext)();analyser = audioContext.createAnalyser();analyser.fftSize = 256;source = audioContext.createMediaElementSource(audio);source.connect(analyser);analyser.connect(audioContext.destination);dataArray = new Uint8Array(analyser.frequencyBinCount);}
}
音频可视化

使用 requestAnimationFrame 实现动画帧循环,使用 getByteFrequencyData() 获取实时音频数据,使用 HSL 颜色绘制彩色柱状图,形成“跳舞”的视觉效果。

function visualize() {if (!isPlaying || playlist.length === 0) {canvasCtx.clearRect(0, 0, canvas.width, canvas.height);return;}requestAnimationFrame(visualize);analyser.getByteFrequencyData(dataArray);canvasCtx.clearRect(0, 0, canvas.width, canvas.height);const barWidth = (canvas.width / analyser.frequencyBinCount) * 2.5;let x = 0;for (let i = 0; i < analyser.frequencyBinCount; i++) {const barHeight = dataArray[i] / 2;const hue = i * 360 / analyser.frequencyBinCount;canvasCtx.fillStyle = `hsl(${hue}, 100%, 50%)`;canvasCtx.fillRect(x,canvas.height - barHeight,barWidth,barHeight);x += barWidth + 1;}
}

扩展建议

  • 支持播放模式:单曲循环、随机播放、顺序播放。
  • 增加歌词同步功能:解析 LRC 歌词文件,并根据当前播放时间匹配对应歌词行,在页面中展示滚动歌词。
  • 支持拖拽排序播放列表,使用户可以自定义播放顺序。
  • 添加缓存机制:缓存播放历史、播放列表等信息,避免刷新后丢失
  • 支持在线音乐资源加载

完整代码

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>可视化音乐播放器</title><style>body {font-family: 'Arial', sans-serif;margin: 0;padding: 20px;background-color: #f5f5f5;color: #333;}.player-container {max-width: 800px;margin: 0 auto;background-color: white;border-radius: 10px;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);padding: 20px;}h1 {text-align: center;color: #2c3e50;}.visualizer {width: 100%;height: 200px;background-color: #2c3e50;margin-bottom: 20px;border-radius: 5px;position: relative;overflow: hidden;}canvas {width: 100%;height: 100%;}.controls {display: flex;flex-direction: column;gap: 15px;}.progress-container {width: 100%;height: 10px;background-color: #ecf0f1;border-radius: 5px;cursor: pointer;}.progress-bar {height: 100%;background-color: #3498db;border-radius: 5px;width: 0%;}.time-display {display: flex;justify-content: space-between;font-size: 14px;color: #7f8c8d;}.control-row {display: flex;justify-content: center;align-items: center;gap: 20px;}.control-row>div {flex: 1;}.buttons {display: flex;gap: 10px;}button {background-color: #3498db;color: white;border: none;border-radius: 5px;padding: 8px 15px;font-size: 14px;cursor: pointer;transition: all 0.3s;}button:hover {background-color: #2980b9;transform: scale(1.05);}button:active {transform: scale(0.95);}.playlist {margin-top: 30px;}.playlist h2 {border-bottom: 1px solid #ecf0f1;padding-bottom: 10px;margin-bottom: 15px;}.playlist-item {display: flex;justify-content: space-between;align-items: center;padding: 10px;border-radius: 5px;cursor: pointer;transition: background-color 0.2s;}.playlist-item:hover {background-color: #ecf0f1;}.playlist-item.active {background-color: #3498db;color: white;}.playlist-item-actions {display: flex;gap: 10px;}.delete-btn {background-color: #e74c3c;padding: 2px 8px;font-size: 12px;border-radius: 3px;}.delete-btn:hover {background-color: #c0392b;}.volume-control {display: flex;align-items: center;gap: 10px;}.volume-slider {width: 100px;}.file-upload {margin-top: 20px;text-align: center;}.file-upload input {display: none;}.file-upload label {background-color: #2ecc71;color: white;padding: 10px 15px;border-radius: 5px;cursor: pointer;transition: background-color 0.3s;}.file-upload label:hover {background-color: #27ae60;}.empty-playlist {text-align: center;color: #7f8c8d;padding: 20px;}</style>
</head>
<body>
<div class="player-container"><h1>可视化音乐播放器</h1><div class="visualizer"><canvas id="visualizer"></canvas></div><div class="controls"><div class="progress-container" id="progress-container"><div class="progress-bar" id="progress-bar"></div></div><div class="time-display"><span id="current-time">0:00</span><span id="total-time">0:00</span></div><div class="control-row"><div class="left"></div><div class="buttons"><button id="prev-btn">上一首</button><button id="play-btn">播放</button><button id="next-btn">下一首</button></div><div class="volume-control"><span>音量</span><input type="range" min="0" max="1" step="0.01" value="0.7" class="volume-slider" id="volume-control"></div></div><div class="file-upload"><input type="file" id="file-input" accept="audio/*" multiple><label for="file-input">添加音乐文件</label></div></div><div class="playlist"><h2>播放列表</h2><div id="playlist-items"><div class="empty-playlist">暂无音乐,请添加音乐文件</div></div></div>
</div><script>document.addEventListener('DOMContentLoaded', function() {// 音频上下文和分析器let audioContext;let analyser;let dataArray;let source;// 播放器状态let currentTrack = 0;let isPlaying = false;let audio = new Audio();// 播放列表 - 初始为空let playlist = [];// DOM 元素const playBtn = document.getElementById('play-btn');const prevBtn = document.getElementById('prev-btn');const nextBtn = document.getElementById('next-btn');const progressContainer = document.getElementById('progress-container');const progressBar = document.getElementById('progress-bar');const currentTimeDisplay = document.getElementById('current-time');const totalTimeDisplay = document.getElementById('total-time');const playlistItems = document.getElementById('playlist-items');const volumeControl = document.getElementById('volume-control');const fileInput = document.getElementById('file-input');const canvas = document.getElementById('visualizer');const canvasCtx = canvas.getContext('2d');// 初始化音频上下文function initAudioContext() {if (!audioContext) {audioContext = new (window.AudioContext || window.webkitAudioContext)();analyser = audioContext.createAnalyser();analyser.fftSize = 256;source = audioContext.createMediaElementSource(audio);source.connect(analyser);analyser.connect(audioContext.destination);dataArray = new Uint8Array(analyser.frequencyBinCount);}}// 加载当前曲目function loadTrack() {if (playlist.length === 0) {audio.src = '';return;}// 确保当前曲目索引有效if (currentTrack >= playlist.length) {currentTrack = playlist.length - 1;}if (currentTrack < 0) {currentTrack = 0;}const track = playlist[currentTrack];audio.src = track.url;audio.load();// 更新播放列表高亮updatePlaylistHighlight();// 如果正在播放,继续播放if (isPlaying) {audio.play().catch(e => console.log('播放错误:', e));}}// 播放/暂停function togglePlay() {if (playlist.length === 0) {alert('播放列表为空,请先添加音乐');return;}if (isPlaying) {audio.pause();playBtn.textContent = '播放';} else {initAudioContext();audio.play().catch(e => {console.log('播放错误:', e);// 如果播放失败,尝试下一首nextTrack();});playBtn.textContent = '暂停';}isPlaying = !isPlaying;}// 下一曲function nextTrack() {if (playlist.length === 0) return;currentTrack = (currentTrack + 1) % playlist.length;loadTrack();if (isPlaying) {audio.play().catch(e => console.log('播放错误:', e));}}// 上一曲function prevTrack() {if (playlist.length === 0) return;currentTrack = (currentTrack - 1 + playlist.length) % playlist.length;loadTrack();if (isPlaying) {audio.play().catch(e => console.log('播放错误:', e));}}// 删除曲目function deleteTrack(index) {// 如果删除的是当前正在播放的曲目if (index === currentTrack && isPlaying) {audio.pause();isPlaying = false;playBtn.textContent = '播放';}// 调整当前曲目索引if (index < currentTrack || (currentTrack === playlist.length - 1 && currentTrack > 0)) {currentTrack--;}// 从播放列表中移除playlist.splice(index, 1);// 重新渲染播放列表renderPlaylist();// 如果播放列表不为空,加载当前曲目if (playlist.length > 0) {loadTrack();} else {audio.src = '';currentTimeDisplay.textContent = '0:00';totalTimeDisplay.textContent = '0:00';progressBar.style.width = '0%';}}// 更新进度条function updateProgress() {const { currentTime, duration } = audio;const progressPercent = (currentTime / duration) * 100;progressBar.style.width = `${progressPercent}%`;// 更新时间显示currentTimeDisplay.textContent = formatTime(currentTime);totalTimeDisplay.textContent = formatTime(duration);}// 设置进度function setProgress(e) {if (playlist.length === 0) return;const width = this.clientWidth;const clickX = e.offsetX;const duration = audio.duration;audio.currentTime = (clickX / width) * duration;}// 格式化时间 (秒 -> MM:SS)function formatTime(seconds) {if (isNaN(seconds)) return '0:00';const minutes = Math.floor(seconds / 60);const secs = Math.floor(seconds % 60);return `${minutes}:${secs < 10 ? '0' : ''}${secs}`;}// 更新播放列表高亮function updatePlaylistHighlight() {const items = playlistItems.querySelectorAll('.playlist-item');items.forEach((item, index) => {item.classList.toggle('active', index === currentTrack);});}// 渲染播放列表function renderPlaylist() {if (playlist.length === 0) {playlistItems.innerHTML = '<div class="empty-playlist">暂无音乐,请添加音乐文件</div>';return;}playlistItems.innerHTML = '';playlist.forEach((track, index) => {console.log(index, currentTrack, isPlaying)const item = document.createElement('div');item.className = `playlist-item ${index === currentTrack ? 'active' : ''}`;item.innerHTML = `<span>${track.name}</span><div class="playlist-item-actions"><button class="delete-btn">删除</button></div>`;// 点击曲目切换播放item.addEventListener('click', (e) => {// 防止点击删除按钮时触发if (e.target.classList.contains('delete-btn')) return;currentTrack = index;loadTrack();if (!isPlaying) {togglePlay();}});// 删除按钮事件const deleteBtn = item.querySelector('.delete-btn');deleteBtn.addEventListener('click', (e) => {e.stopPropagation(); // 阻止事件冒泡deleteTrack(index);});playlistItems.appendChild(item);});}// 可视化音频function visualize() {if (!isPlaying || playlist.length === 0) {canvasCtx.clearRect(0, 0, canvas.width, canvas.height);return;}requestAnimationFrame(visualize);analyser.getByteFrequencyData(dataArray);canvasCtx.clearRect(0, 0, canvas.width, canvas.height);const barWidth = (canvas.width / analyser.frequencyBinCount) * 2.5;let x = 0;for (let i = 0; i < analyser.frequencyBinCount; i++) {const barHeight = dataArray[i] / 2;const hue = i * 360 / analyser.frequencyBinCount;canvasCtx.fillStyle = `hsl(${hue}, 100%, 50%)`;canvasCtx.fillRect(x,canvas.height - barHeight,barWidth,barHeight);x += barWidth + 1;}}// 添加音乐文件function addMusicFiles(files) {for (let i = 0; i < files.length; i++) {const file = files[i];const url = URL.createObjectURL(file);playlist.push({name: file.name.replace(/\.[^/.]+$/, ""), // 移除扩展名url: url});}// 如果是第一次添加音乐,自动加载第一首if (playlist.length === files.length) {currentTrack = 0;loadTrack();}renderPlaylist();}// 事件监听playBtn.addEventListener('click', togglePlay);nextBtn.addEventListener('click', nextTrack);prevBtn.addEventListener('click', prevTrack);audio.addEventListener('timeupdate', updateProgress);audio.addEventListener('ended', nextTrack);audio.addEventListener('loadedmetadata', updateProgress);progressContainer.addEventListener('click', setProgress);volumeControl.addEventListener('input', () => {audio.volume = volumeControl.value;});fileInput.addEventListener('change', (e) => {addMusicFiles(e.target.files);fileInput.value = ''; // 重置输入,允许重复选择相同文件});// 初始化renderPlaylist();audio.volume = volumeControl.value;// 设置canvas尺寸function resizeCanvas() {canvas.width = canvas.offsetWidth;canvas.height = canvas.offsetHeight;}window.addEventListener('resize', resizeCanvas);resizeCanvas();// 开始可视化setInterval(visualize, 30);});
</script>
</body>
</html>
http://www.dtcms.com/a/478535.html

相关文章:

  • 【Docker】零基础上手:原理+Ubuntu/Windows GUI 安装 + 镜像源 / 目录优化
  • 网站的引导页怎么做的手机虚拟空间
  • 大连网站开发公司力推选仟亿科技有源码如何搭建网站
  • 【Java虚拟机(JVM)全面解析】从原理到面试实战、JVM故障处理、类加载、内存区域、垃圾回收
  • 高并发面试
  • 模板网站 建设 方法西安网站建设中心
  • 《早期经验:语言智能体学习的中间道路》Agent Learning via Early Experience论文深度解读
  • QT6中Commd Link Button,Dialog Button Box,Tool Button 功能与应用
  • asp做网站安全性wordpress 文章 接口
  • 关系型数据库RDBMS与非关系型数据库NoSQL区别
  • 网站建设发布wordpress主题带会员中心
  • 单元测试 vs Main方法调试:何时使用哪种方式?
  • 03--CSS基础(2)
  • Wireshark笔记-从抓包的角度分析几种客户端不能正常获取IP地址的场景
  • 企业 网站 推广wordpress文章状态
  • typescript中infer常见用法
  • 科技赋能塞上农业:宁夏从黄土地到绿硅谷的蝶变
  • 第13讲:深入理解指针(3)——数组与指针的“深度绑定”
  • 基于MATLAB的匈牙利算法实现任务分配
  • Type-C 接口充电兼容设计(针对 5V1A 需求)
  • Anaconda 学习手册记录
  • Python-适用于硬件测试的小工具
  • 第三方软件测评机构:【Locust的性能测试和负载测试】
  • 【Python】列表 元组 字典 文件
  • 简单asp网站深圳做个商城网站设计
  • OpenTelemetry 入门
  • 昆山做网站找哪家好wordpress 算数验证码
  • 网站建设服务费入阿里云域名注册平台
  • 美颜的灵魂:磨皮技术的演进与实现原理详解
  • 自定义半精度浮点数modelsim仿真显示