H5音频上传-简单示例
示例效果:(可同时上传多个;并适配安卓、ios、鸿蒙系统,主流的音频格式mp3, wav, m4a, aac,ogg, flac,amr,wma)

上传到本地项目文件夹中!后台接口实现用C# .NET Framework 4.5实现实例
引用js\css:(没有就官网下载、或引用官网链接!)
layui-v2.7.6/layui/layui.js、layui-v2.7.6/layui/css/layui.css、jquery-3.7.1.js
HTML\css\js
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>音频上传与播放</title> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <link href="../scripts/layui/layui-v2.7.6/layui/css/layui.css" rel="stylesheet" /><!--引用本地加载快--> <script src="../scripts/layui/layui-v2.7.6/layui/layui.js"></script> <script src="../scripts/jquery-3.7.1.js"></script> <style> :root { --primary-color: #1e9fff; --safe-area-top: env(safe-area-inset-top); --safe-area-bottom: env(safe-area-inset-bottom); } * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; background-color: #f5f7fa; color: #333; line-height: 1.5; padding-top: var(--safe-area-top); padding-bottom: var(--safe-area-bottom); } /* 移动端优化容器 */ .container { max-width: 100%; margin: 0 auto; padding: 15px; min-height: 100vh; background-color: #f5f7fa; } .header { text-align: center; margin-bottom: 20px; padding: 15px; background-color: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .header h1 { color: var(--primary-color); font-size: 1.5rem; font-weight: 600; margin-bottom: 8px; } .header p { color: #666; font-size: 0.875rem; } /* 卡片样式优化 */ .card { background-color: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); margin-bottom: 15px; padding: 15px; position: relative; } .card-title { font-size: 1.125rem; font-weight: 600; margin-bottom: 15px; color: #333; display: flex; align-items: center; } .card-title i { margin-right: 10px; color: var(--primary-color); } /* 上传区域优化 */ .upload-area { border: 2px dashed #e0e0e0; border-radius: 12px; padding: 30px 15px; text-align: center; transition: all 0.3s; background-color: #fafafa; cursor: pointer; margin-bottom: 15px; position: relative; touch-action: manipulation; } .upload-area:active { background-color: rgba(30, 159, 255, 0.08); transform: translateY(1px); } .upload-area.dragover { border-color: var(--primary-color); background-color: rgba(30, 159, 255, 0.05); } .upload-icon { font-size: 2.5rem; color: var(--primary-color); margin-bottom: 10px; } .upload-text { margin-bottom: 8px; font-size: 1rem; font-weight: 500; } .upload-hint { color: #999; font-size: 0.8125rem; line-height: 1.4; } /* 音频列表优化 */ .audio-list { margin-top: 15px; } .audio-item { display: flex; flex-direction: column; padding: 15px; border: 1px solid #f0f0f0; border-radius: 12px; margin-bottom: 12px; background-color: #fdfdfd; transition: all 0.3s; position: relative; } .audio-item:active { background-color: #f8f9fa; } .audio-info { width: 100%; margin-bottom: 15px; } .audio-name { font-weight: 500; margin-bottom: 8px; font-size: 1rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .audio-meta { font-size: 0.75rem; color: #888; display: flex; flex-wrap: wrap; gap: 10px; } /* 音频播放器优化 */ .audio-player { width: 100%; margin-top: 10px; } .player-btn { width: 44px; height: 44px; border-radius: 50%; background-color: var(--primary-color); color: white; border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 1.125rem; flex-shrink: 0; touch-action: manipulation; -webkit-appearance: none; -moz-appearance: none; appearance: none; } .player-btn:active { background-color: #0d8bf2; transform: scale(0.98); } .player-btn.paused::before { content: "\e6a8"; /* layui播放图标 */ } .player-btn.playing::before { content: "\e6aa"; /* layui暂停图标 */ } .player-controls { display: flex; align-items: center; gap: 10px; width: 100%; } .progress-area { flex: 1; margin: 0 10px; } .progress-bar { height: 6px; background-color: #e8e8e8; border-radius: 3px; position: relative; cursor: pointer; overflow: hidden; touch-action: none; } .progress { height: 100%; background-color: var(--primary-color); border-radius: 3px; width: 0%; transition: width 0.1s; } .time-info { display: flex; justify-content: space-between; font-size: 0.75rem; color: #888; margin-top: 5px; } .audio-volume { display: none; /* 移动端默认隐藏音量控制,使用系统音量 */ } /* 音频控制按钮组 */ .audio-controls { display: flex; justify-content: flex-end; gap: 10px; margin-top: 10px; border-top: 1px solid #f0f0f0; padding-top: 10px; } .audio-controls button { min-height: 36px; padding: 0 15px; border-radius: 6px; font-size: 0.875rem; touch-action: manipulation; } /* 空状态样式 */ .empty-state { text-align: center; padding: 40px 15px; color: #999; } .empty-icon { font-size: 3rem; color: #d9d9d9; margin-bottom: 15px; } /* 移动端特定优化 */ @media (max-width: 768px) { .container { padding: 12px; } .header { padding: 12px; margin-bottom: 15px; } .card { padding: 12px; margin-bottom: 12px; } /* iOS Safari特定优化 */ @supports (-webkit-touch-callout: none) { .player-btn { -webkit-tap-highlight-color: rgba(30, 159, 255, 0.3); } .progress-bar { height: 8px; /* iOS上更易点击 */ } /* 防止iOS上的弹性滚动影响播放器交互 */ .audio-player { -webkit-overflow-scrolling: touch; } } } /* 大屏设备优化 */ @media (min-width: 769px) { .container { max-width: 800px; padding: 20px; } .audio-volume { display: flex; align-items: center; margin-left: 10px; } .volume-slider { width: 125px; } } /* 暗色模式支持 */ @media (prefers-color-scheme: dark) { body { background-color: #121212; color: #e0e0e0; } .header, .card, .audio-item { background-color: #1e1e1e; color: #e0e0e0; } .upload-area { background-color: #2a2a2a; border-color: #444; } .audio-item { border-color: #333; background-color: #252525; } .audio-name { color: #e0e0e0; } .progress-bar { background-color: #444; } .empty-icon { color: #444; } } /* 防止长按菜单 */ .player-btn, .progress-bar, .audio-controls button { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } /* 加载动画 */ .loading-spinner { display: inline-block; width: 20px; height: 20px; border: 2px solid rgba(30, 159, 255, 0.3); border-radius: 50%; border-top-color: var(--primary-color); animation: spin 1s ease-in-out infinite; margin-right: 8px; } @keyframes spin { to { transform: rotate(360deg); } } /* 系统提示 */ .system-alert { display: none; padding: 12px 15px; background-color: #fff9e6; border-left: 4px solid #ffc107; margin-bottom: 15px; border-radius: 4px; font-size: 0.875rem; color: #856404; } /* 适配iOS底部安全区域 */ .ios-safe-bottom { padding-bottom: calc(15px + var(--safe-area-bottom)); } /* 上传状态样式 */ .upload-status { margin-top: 10px; } .upload-file-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; background-color: #f8f9fa; border-radius: 6px; margin-bottom: 5px; font-size: 0.875rem; } .upload-file-name { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 10px; } .upload-file-progress { width: 80px; height: 6px; background-color: #e9ecef; border-radius: 3px; overflow: hidden; } .upload-file-progress-bar { height: 100%; background-color: var(--primary-color); width: 0%; transition: width 0.3s; } .dah { position: fixed; top: 0px; width: 100%; height: 100%; background-color: #333; opacity: 0.5; z-index: 9998; } .sjgscss { opacity: 0.98; position: fixed; bottom: 0px; box-sizing: border-box; width: 100%; background-color: #fafafa; text-align: center; line-height: 18px; padding: 2em; overflow-y: auto; z-index: 9999; } #jz { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #fff; padding: 20px 40px; border-radius: 8px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); z-index: 10000; font-size: 16px; } #loading { width: 100%; height: 100%; display: none; position: fixed; top: 0; left: 0; z-index: 9997; } @keyframes loading { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } } </style> </head> <body> <!-- iOS音频播放提示(仅iOS显示) --> <div class="system-alert" id="iosAudioAlert"> <i class="layui-icon layui-icon-tips"></i> 提示:iOS系统需要在用户交互后播放音频,请点击播放按钮开始播放 </div> <div class="container ios-safe-bottom"> <div class="header"> <h1>音频上传与播放</h1> <p>支持安卓、iOS、鸿蒙系统,兼容主流音频格式</p> </div> <div class="card"> <div class="card-title"> <i class="layui-icon layui-icon-upload"></i> <span>上传音频文件</span> </div> <div class="upload-area" id="uploadArea"> <div class="upload-icon"> <i class="layui-icon layui-icon-upload"></i> </div> <div class="upload-text">点击或拖拽音频文件到此处上传</div> <div class="upload-hint">支持 MP3、M4A、WAV、AAC 等格式,单文件不超过 50MB</div> </div> <!-- 上传状态显示区域 --> <div id="uploadStatus" style="display: none;"> <div class="layui-progress layui-progress-big" lay-filter="uploadProgress" style="margin-bottom: 10px;"> <div class="layui-progress-bar" lay-percent="0%"></div> </div> <div id="uploadInfo" style="text-align: center; margin-top: 10px; color: #666; font-size: 0.875rem;"></div> <!-- 多文件上传进度列表 --> <div id="uploadFileList" class="upload-status"></div> </div> </div> <div class="card"> <div class="card-title"> <i class="layui-icon layui-icon-play"></i> <span>音频列表</span> <span id="audioCount" class="layui-badge layui-bg-blue" style="margin-left: 10px;">0</span> </div> <div class="audio-list" id="audioList"> <!-- 空状态 --> <div class="empty-state" id="emptyState"> <div class="empty-icon"> <i class="layui-icon layui-icon-audio"></i> </div> <div>暂无音频文件,请先上传音频</div> </div> </div> </div> <!-- 加载动画 --> <div id="loading"> <div class="dah"></div> <div class="sjgscss" style="padding: 0;"> <div id="jz"> <i class="layui-icon layui-icon-loading" style="font-size: 24px; margin-right: 10px;"></i> 上传中... </div> </div> </div> <!-- 音频播放器模板 --> <script type="text/html" id="audioItemTpl"> <div class="audio-item" data-id="{{d.id}}"> <div class="audio-info"> <div class="audio-name">{{d.name}}</div> <div class="audio-meta"> <span>格式: {{d.format}}</span> <span>大小: {{d.size}}</span> <span>时长: {{d.duration}}</span> </div> <div class="audio-player"> <div class="player-controls"> <button class="player-btn paused" type="button"></button> <div class="progress-area"> <div class="progress-bar"> <div class="progress"></div> </div> <div class="time-info"> <span class="current-time">00:00</span> <span class="duration">{{d.durationDisplay}}</span> </div> </div> <!-- 桌面端显示音量控制 --> <div class="audio-volume desktop-only"> <i class="layui-icon layui-icon-speaker volume-icon"></i> <div class="volume-slider"> <input type="range" min="0" max="100" value="80" class="volume-control"> </div> </div> </div> </div> </div> <div class="audio-controls"> <button class="layui-btn layui-btn-sm download-btn" data-url="{{d.url}}"> <i class="layui-icon layui-icon-download"></i>下载 </button> <button class="layui-btn layui-btn-sm layui-btn-danger delete-btn"> <i class="layui-icon layui-icon-delete"></i>删除 </button> </div> </div> </script> <script> // 检测设备类型 var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; var isAndroid = /Android/.test(navigator.userAgent); var isHarmony = /HarmonyOS/.test(navigator.userAgent) || /HMSCore/.test(navigator.userAgent); // 显示iOS特定提示 if (isIOS) { $('#iosAudioAlert').show(); } // 音频管理器 var AudioManager = { // 音频数据存储 - 不再使用localStorage audioData: [], // 修改:初始化为空数组,不从localStorage读取 audioCount: 0, nextId: 1, currentPlayingId: null, // 初始化 init: function () { this.audioCount = this.audioData.length; this.nextId = this.audioData.length > 0 ? Math.max(...this.audioData.map(item => item.id)) + 1 : 1; this.renderAudioList(); }, // 更新音频计数 updateAudioCount: function () { $('#audioCount').text(this.audioCount); if (this.audioCount > 0) { $('#emptyState').hide(); } else { $('#emptyState').show(); } }, // 渲染音频列表 renderAudioList: function () { var audioListHtml = ''; var audioItemTpl = $('#audioItemTpl').html(); for (var i = 0; i < this.audioData.length; i++) { var audio = this.audioData[i]; // 确保duration和durationDisplay有默认值 var duration = audio.duration || '未知'; var durationDisplay = audio.durationDisplay || '00:00'; var itemHtml = audioItemTpl .replace(/\{\{d\.id\}\}/g, audio.id) .replace(/\{\{d\.name\}\}/g, this.escapeHtml(audio.name)) .replace(/\{\{d\.format\}\}/g, audio.format) .replace(/\{\{d\.size\}\}/g, audio.size) .replace(/\{\{d\.duration\}\}/g, duration) .replace(/\{\{d\.durationDisplay\}\}/g, durationDisplay) .replace(/\{\{d\.url\}\}/g, this.escapeHtml(audio.url)); audioListHtml += itemHtml; } $('#audioList').html(audioListHtml); this.updateAudioCount(); this.bindAudioEvents(); // 移动端优化:如果在大屏设备上,显示音量控制 if (window.innerWidth > 768) { $('.desktop-only').show(); } }, // 转义HTML特殊字符 escapeHtml: function (text) { if (!text) return ''; var map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text.toString().replace(/[&<>"']/g, function (m) { return map[m]; }); }, // 格式化时间(秒 -> MM:SS) formatTime: function (seconds) { if (isNaN(seconds) || seconds === Infinity) return '00:00'; var mins = Math.floor(seconds / 60); var secs = Math.floor(seconds % 60); return (mins < 10 ? '0' : '') + mins + ':' + (secs < 10 ? '0' : '') + secs; }, // 获取音频时长 getAudioDuration: function (file, callback) { var audio = new Audio(); audio.preload = 'metadata'; audio.onloadedmetadata = function () { window.URL.revokeObjectURL(audio.src); callback(audio.duration); }; audio.onerror = function () { window.URL.revokeObjectURL(audio.src); callback(0); }; audio.src = URL.createObjectURL(file); }, // 添加新音频 addAudio: function (file, fileSize, duration, url) { // 确保duration有值,如果为0或undefined,设置为'未知' var durationText = duration && duration > 0 ? this.formatTime(duration) : '未知'; var durationDisplay = this.formatTime(duration || 0); var newAudio = { id: this.nextId++, name: file.name, format: file.name.split('.').pop().toUpperCase(), size: fileSize, time: new Date().toLocaleString(), duration: durationText, durationDisplay: durationDisplay, url: url, // 存储事件处理器,用于删除时清理 eventHandlers: {} }; this.audioData.push(newAudio); this.audioCount = this.audioData.length; // 重新渲染列表 this.renderAudioList(); }, // 删除音频 deleteAudio: function (audioId) { // 找到要删除的音频 var audioIndex = this.audioData.findIndex(item => item.id === audioId); if (audioIndex === -1) return; var audio = this.audioData[audioIndex]; // 停止播放并清理音频元素 if (audio && audio.audioElement) { // 先暂停播放 audio.audioElement.pause(); // 移除所有事件监听器 if (audio.eventHandlers) { if (audio.eventHandlers.timeupdate) { audio.audioElement.removeEventListener('timeupdate', audio.eventHandlers.timeupdate); } if (audio.eventHandlers.loadedmetadata) { audio.audioElement.removeEventListener('loadedmetadata', audio.eventHandlers.loadedmetadata); } if (audio.eventHandlers.ended) { audio.audioElement.removeEventListener('ended', audio.eventHandlers.ended); } if (audio.eventHandlers.error) { audio.audioElement.removeEventListener('error', audio.eventHandlers.error); } } // 清空音频源 audio.audioElement.src = ''; audio.audioElement.load(); // 删除音频元素引用 delete audio.audioElement; delete audio.eventHandlers; } // 从数据中移除 this.audioData.splice(audioIndex, 1); this.audioCount = this.audioData.length; // 更新当前播放ID if (this.currentPlayingId === audioId) { this.currentPlayingId = null; } // 重新渲染列表 this.renderAudioList(); }, // 更新音频播放进度 updateProgress: function ($audioItem, audioElement) { var currentTime = audioElement.currentTime; var duration = audioElement.duration; var progressPercent = (currentTime / duration) * 100 || 0; $audioItem.find('.progress').css('width', progressPercent + '%'); $audioItem.find('.current-time').text(this.formatTime(currentTime)); }, // 更新音频总时长 updateDuration: function ($audioItem, audioElement) { var duration = audioElement.duration; if (!isNaN(duration) && duration !== Infinity) { $audioItem.find('.duration').text(this.formatTime(duration)); // 更新数据中的时长 var audioId = $audioItem.data('id'); var audio = this.audioData.find(item => item.id === audioId); if (audio) { audio.duration = this.formatTime(duration); audio.durationDisplay = this.formatTime(duration); } } }, // 绑定音频事件 bindAudioEvents: function () { var self = this; // 绑定播放/暂停按钮事件 $('.player-btn').off('touchstart click').on('touchstart click', function (e) { e.preventDefault(); e.stopPropagation(); // 防止快速多次点击 if ($(this).hasClass('click-disabled')) return; $(this).addClass('click-disabled'); setTimeout(() => { $(this).removeClass('click-disabled'); }, 300); var $btn = $(this); var $audioItem = $btn.closest('.audio-item'); var audioId = $audioItem.data('id'); var audio = self.audioData.find(item => item.id === audioId); if (!audio) { return; // 音频已被删除,直接返回 } if (!audio.audioElement) { // 创建audio元素 audio.audioElement = new Audio(); // 添加时间戳参数避免缓存 var audioUrl = audio.url; if (audioUrl.indexOf('?') === -1) { audioUrl += '?_t=' + Date.now(); } else { audioUrl += '&_t=' + Date.now(); } audio.audioElement.src = audioUrl; audio.audioElement.preload = 'auto'; // iOS Safari需要特殊处理 if (isIOS) { audio.audioElement.load(); } // 创建事件处理器对象 audio.eventHandlers = {}; // 绑定audio事件并存储处理器 audio.eventHandlers.timeupdate = function () { self.updateProgress($audioItem, this); }; audio.audioElement.addEventListener('timeupdate', audio.eventHandlers.timeupdate); audio.eventHandlers.loadedmetadata = function () { self.updateDuration($audioItem, this); }; audio.audioElement.addEventListener('loadedmetadata', audio.eventHandlers.loadedmetadata); audio.eventHandlers.ended = function () { $btn.removeClass('playing').addClass('paused'); self.currentPlayingId = null; // 重置进度 $audioItem.find('.progress').css('width', '0%'); $audioItem.find('.current-time').text('00:00'); }; audio.audioElement.addEventListener('ended', audio.eventHandlers.ended); audio.eventHandlers.error = function (e) { // 检查音频是否还存在 var currentAudio = self.audioData.find(item => item.id === audioId); if (!currentAudio) { return; // 音频已被删除,不显示错误 } console.error('音频加载失败:', e); layer.msg('音频加载失败,请检查音频文件', { icon: 2 }); $btn.removeClass('playing').addClass('paused'); }; audio.audioElement.addEventListener('error', audio.eventHandlers.error); } // 处理播放/暂停逻辑 if ($btn.hasClass('paused')) { // 暂停其他正在播放的音频 if (self.currentPlayingId && self.currentPlayingId !== audioId) { var prevAudio = self.audioData.find(item => item.id === self.currentPlayingId); if (prevAudio && prevAudio.audioElement) { prevAudio.audioElement.pause(); $(`.audio-item[data-id="${self.currentPlayingId}"] .player-btn`) .removeClass('playing').addClass('paused'); } } // 尝试播放音频 var playPromise = audio.audioElement.play(); if (playPromise !== undefined) { playPromise.then(function () { // 播放成功 $btn.removeClass('paused').addClass('playing'); self.currentPlayingId = audioId; }).catch(function (error) { // 播放失败 console.error('播放失败:', error); if (isIOS) { layer.msg('iOS系统需要用户手势触发播放,请再次点击', { icon: 2 }); } else { layer.msg('播放失败: ' + error.message, { icon: 2 }); } }); } } else { // 暂停当前音频 audio.audioElement.pause(); $btn.removeClass('playing').addClass('paused'); self.currentPlayingId = null; } }); // 绑定进度条点击/触摸事件 $('.progress-bar').off('touchstart mousedown').on('touchstart mousedown', function (e) { var $progressBar = $(this); var $audioItem = $progressBar.closest('.audio-item'); var audioId = $audioItem.data('id'); var audio = self.audioData.find(item => item.id === audioId); if (audio && audio.audioElement) { // 获取点击位置 var clientX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX; var offsetLeft = $progressBar.offset().left; var width = $progressBar.width(); var percentage = (clientX - offsetLeft) / width; // 限制范围 percentage = Math.max(0, Math.min(1, percentage)); audio.audioElement.currentTime = audio.audioElement.duration * percentage; self.updateProgress($audioItem, audio.audioElement); // 绑定拖动事件 var moveHandler = function (moveEvent) { var moveClientX = moveEvent.type === 'touchmove' ? moveEvent.touches[0].clientX : moveEvent.clientX; var newPercentage = (moveClientX - offsetLeft) / width; newPercentage = Math.max(0, Math.min(1, newPercentage)); audio.audioElement.currentTime = audio.audioElement.duration * newPercentage; self.updateProgress($audioItem, audio.audioElement); }; var upHandler = function () { $(document).off('touchmove mousemove', moveHandler); $(document).off('touchend mouseup', upHandler); }; $(document).on('touchmove mousemove', moveHandler); $(document).on('touchend mouseup', upHandler); } // 阻止默认行为 e.preventDefault(); }); // 绑定音量控制事件(桌面端) $('.volume-control').off('input').on('input', function () { var $slider = $(this); var $audioItem = $slider.closest('.audio-item'); var audioId = $audioItem.data('id'); var audio = self.audioData.find(item => item.id === audioId); if (audio && audio.audioElement) { audio.audioElement.volume = $slider.val() / 100; } }); // 绑定下载按钮事件 $('.download-btn').off('touchstart click').on('touchstart click', function (e) { e.preventDefault(); var url = $(this).data('url'); var $audioItem = $(this).closest('.audio-item'); var audioId = $audioItem.data('id'); var audio = self.audioData.find(item => item.id === audioId); if (audio && audio.url) { // 创建临时下载链接,添加时间戳避免缓存 var downloadUrl = audio.url; if (downloadUrl.indexOf('?') === -1) { downloadUrl += '?_t=' + Date.now(); } else { downloadUrl += '&_t=' + Date.now(); } var a = document.createElement('a'); a.href = downloadUrl; a.download = audio.name; document.body.appendChild(a); a.click(); document.body.removeChild(a); layer.msg('开始下载: ' + audio.name, { icon: 1, time: 1500 }); } else { layer.msg('下载链接无效', { icon: 2 }); } }); // 绑定删除按钮事件 $('.delete-btn').off('touchstart click').on('touchstart click', function (e) { e.preventDefault(); e.stopPropagation(); // 阻止事件冒泡 var $audioItem = $(this).closest('.audio-item'); var audioId = $audioItem.data('id'); layer.confirm('确定删除这个音频文件吗?', { icon: 3, title: '删除确认', btn: ['确定', '取消'] }, function (index) { self.deleteAudio(audioId); layer.close(index); layer.msg('删除成功', { icon: 1, time: 1500 }); }); }); } }; // 初始化layui layui.use(['upload', 'util', 'layer', 'element'], function () { var upload = layui.upload; var util = layui.util; var layer = layui.layer; var element = layui.element; // 初始化音频管理器 AudioManager.init(); // 显示加载动画的函数 function showLoading() { document.getElementById('loading').style.display = 'block'; } // 隐藏加载动画的函数 function hideLoading() { document.getElementById('loading').style.display = 'none'; } // 存储上传文件的进度信息 var uploadFiles = {}; var uploadFileCount = 0; var completedUploadCount = 0; var uploadTimeoutTimer = null; // 添加超时计时器 var currentUploadInstance = null; // 存储当前上传实例 // 清空上传状态的函数 function resetUploadState() { uploadFiles = {}; uploadFileCount = 0; completedUploadCount = 0; if (uploadTimeoutTimer) { clearTimeout(uploadTimeoutTimer); uploadTimeoutTimer = null; } $('#uploadStatus').hide(); element.progress('uploadProgress', '0%'); $('#uploadFileList').empty(); $('#uploadInfo').html(''); hideLoading(); // 如果存在layui上传实例,清空其文件队列 if (currentUploadInstance) { try { // 清空layui内部文件队列 currentUploadInstance.resetFile(); } catch (e) { console.log('重置上传实例:', e); } } } // 初始化上传组件 - 修复上传逻辑 var uploadInst = upload.render({ elem: '#uploadArea', url: '../Ashx/GM.ashx?action=JKaudio&_t=' + Date.now(), // 添加时间戳避免缓存 accept: 'audio', acceptMime: 'audio/*', exts: 'mp3|m4a|wav|aac|ogg|flac|amr', size: 50 * 1024, // 50MB multiple: true, auto: false, // 不自动上传 drag: true, // 注意:layui的choose回调有问题,改用before before: function (obj) { // 每次选择文件前清空之前的文件状态 resetUploadState(); currentUploadInstance = obj; }, choose: function (obj) { // 清除可能存在的超时计时器 if (uploadTimeoutTimer) { clearTimeout(uploadTimeoutTimer); uploadTimeoutTimer = null; } // 获取选择的文件 var filesObj = obj.pushFile(); uploadFiles = {}; uploadFileCount = 0; completedUploadCount = 0; var fileNames = []; // 遍历文件对象(不是数组) for (var key in filesObj) { if (filesObj.hasOwnProperty(key)) { var file = filesObj[key]; fileNames.push(file.name); uploadFileCount++; // 存储文件信息 uploadFiles[key] = { file: file, progress: 0 }; } } if (uploadFileCount === 0) { layer.msg('请选择音频文件', { icon: 2 }); return; } // 显示上传状态和加载动画 $('#uploadStatus').show(); $('#uploadFileList').empty(); showLoading(); // 设置上传超时(60秒) uploadTimeoutTimer = setTimeout(function () { if (completedUploadCount < uploadFileCount) { layer.msg('上传超时,请重试', { icon: 2 }); resetUploadState(); } }, 60000); // 创建文件上传项 for (var key in uploadFiles) { if (uploadFiles.hasOwnProperty(key)) { var file = uploadFiles[key].file; var fileItemHtml = '<div class="upload-file-item" id="uploadFile_' + key + '">' + '<div class="upload-file-name">' + file.name + '</div>' + '<div class="upload-file-progress">' + '<div class="upload-file-progress-bar" id="uploadFileProgress_' + key + '" style="width: 0%"></div>' + '</div>' + '</div>'; $('#uploadFileList').append(fileItemHtml); } } $('#uploadInfo').html('准备上传 ' + uploadFileCount + ' 个文件: ' + fileNames.join(', ')); // 开始上传每个文件 for (var key in uploadFiles) { if (uploadFiles.hasOwnProperty(key)) { // 注意:这里需要确保obj.pushFile()返回的对象包含上传方法 obj.upload(key, uploadFiles[key].file); } } }, progress: function (n, elem, index, file) { // 更新单个文件的上传进度 if (uploadFiles[index]) { uploadFiles[index].progress = n; $('#uploadFileProgress_' + index).css('width', n + '%'); // 计算总进度 var totalProgress = 0; var fileCount = 0; for (var key in uploadFiles) { if (uploadFiles.hasOwnProperty(key)) { totalProgress += uploadFiles[key].progress; fileCount++; } } var avgProgress = fileCount > 0 ? Math.round(totalProgress / fileCount) : 0; element.progress('uploadProgress', avgProgress + '%'); $('#uploadInfo').html('上传中: ' + file.name + ' (' + n + '%)'); } }, done: function (res, index, upload) { console.log('上传完成回调:', res, index, upload); // 上传成功回调 - res 是服务器返回的数据 completedUploadCount++; if (res && res.success) { // 获取原始文件用于获取音频时长 var originalFile = uploadFiles[index] ? uploadFiles[index].file : null; // 构建音频访问URL - 添加时间戳避免缓存 var audioUrl = ''; if (res.fileName) { // 注意:这里需要根据您的实际文件访问路径来构建URL // 本地测试用 audioUrl = '../audio/' + res.fileName; } if (originalFile) { // 获取音频时长 AudioManager.getAudioDuration(originalFile, function (duration) { // 计算文件大小显示文本 var sizeText = ''; if (res.fileSize) { sizeText = (res.fileSize / (1024 * 1024)).toFixed(2) + ' MB'; } else if (originalFile.size) { sizeText = (originalFile.size / (1024 * 1024)).toFixed(2) + ' MB'; } // 添加到音频管理器 AudioManager.addAudio( originalFile, sizeText, duration, audioUrl ); // 更新上传状态 $('#uploadFileProgress_' + index).css('width', '100%'); $('#uploadFile_' + index).css('background-color', '#e8f5e9'); layer.msg('文件 ' + (res.fileName || originalFile.name) + ' 上传成功', { icon: 1, time: 1000 }); }); } else { // 如果没有原始文件,仍然添加音频但可能无法获取时长 var sizeText = res.fileSize ? (res.fileSize / (1024 * 1024)).toFixed(2) + ' MB' : '未知大小'; AudioManager.addAudio( { name: res.fileName || '未知文件' }, sizeText, 0, audioUrl ); layer.msg('文件 ' + (res.fileName || '未知文件') + ' 上传成功', { icon: 1, time: 1000 }); } } else { // 服务器返回上传失败 var errorMsg = res ? res.message : '上传失败,服务器未返回正确数据'; layer.msg(errorMsg, { icon: 2 }); // 更新上传状态 $('#uploadFileProgress_' + index).css('width', '100%'); $('#uploadFile_' + index).css('background-color', '#ffebee'); } // 清理该文件的上传记录 if (uploadFiles[index]) { delete uploadFiles[index]; } // 检查是否所有文件都上传完成 if (completedUploadCount >= uploadFileCount) { // 清除超时计时器 if (uploadTimeoutTimer) { clearTimeout(uploadTimeoutTimer); uploadTimeoutTimer = null; } // 延迟一点时间再清空状态,让用户看到完成信息 setTimeout(function () { resetUploadState(); layer.msg('全部文件上传完成', { icon: 1, time: 1500 }); }, 500); } }, error: function (index, upload) { console.log('上传错误回调:', index, upload); // 上传失败回调(网络错误等) completedUploadCount++; var fileName = uploadFiles[index] ? uploadFiles[index].file.name : '未知文件'; layer.msg('文件 ' + fileName + ' 上传失败,请重试', { icon: 2 }); $('#uploadFileProgress_' + index).css('width', '100%'); $('#uploadFile_' + index).css('background-color', '#ffebee'); // 清理该文件的上传记录 if (uploadFiles[index]) { delete uploadFiles[index]; } if (completedUploadCount >= uploadFileCount) { // 清除超时计时器 if (uploadTimeoutTimer) { clearTimeout(uploadTimeoutTimer); uploadTimeoutTimer = null; } // 延迟一点时间再清空状态 setTimeout(function () { resetUploadState(); }, 500); } } }); // 初始化进度条组件 element.progress('uploadProgress', '0%'); // 处理移动端返回键 if (isAndroid || isHarmony) { document.addEventListener('backbutton', function (e) { // 如果有音频正在播放,停止播放 if (AudioManager.currentPlayingId) { var audio = AudioManager.audioData.find(item => item.id === AudioManager.currentPlayingId); if (audio && audio.audioElement) { audio.audioElement.pause(); AudioManager.currentPlayingId = null; } } }, false); } // 处理页面可见性变化(切换应用时暂停音频) document.addEventListener('visibilitychange', function () { if (document.hidden && AudioManager.currentPlayingId) { var audio = AudioManager.audioData.find(item => item.id === AudioManager.currentPlayingId); if (audio && audio.audioElement) { audio.audioElement.pause(); $(`.audio-item[data-id="${AudioManager.currentPlayingId}"] .player-btn`) .removeClass('playing').addClass('paused'); } } }); // 窗口大小变化时调整UI $(window).on('resize', function () { if (window.innerWidth > 768) { $('.desktop-only').show(); } else { $('.desktop-only').hide(); } }); }); // 页面卸载前清理音频对象 window.addEventListener('beforeunload', function () { // 暂停所有音频并释放资源 $('.player-btn.playing').each(function () { var $btn = $(this); var $audioItem = $btn.closest('.audio-item'); var audioId = $audioItem.data('id'); // 从audioData获取音频 var audioData = AudioManager.audioData; // 修改:直接使用AudioManager.audioData var audio = audioData.find(item => item.id === audioId); if (audio && audio.audioElement) { audio.audioElement.pause(); audio.audioElement.src = ''; delete audio.audioElement; } }); }); </script> </body> </html>
C# .NET Framework 4.5 实现接口
namespace HealthApp.Ashx { /// <summary> /// GM 的摘要说明 /// </summary> public class GM : IHttpHandler { public void ProcessRequest(HttpContext context) { string action = context.Request["action"] == null ? "" : context.Request["action"].ToString(); if (action == "JKaudio") { context.Response.ContentType = "application/json"; var response = new { success = false, message = "", filePath = "", fileName = "", fileSize = 0, fileType = "" }; try { HttpFileCollection files = context.Request.Files; if (files.Count == 0) { response = new { success = false, message = "没有接收到音频文件", filePath = "", fileName = "", fileSize = 0, fileType = "" }; context.Response.Write(JsonConvert.SerializeObject(response)); return; } // 获取音频存储路径配置 string uploadFolder = ""; // 本地文件夹测试路径 uploadFolder = "F:\\xx\\项目\\audio\\; // 确保目录存在 if (!Directory.Exists(uploadFolder)) { Directory.CreateDirectory(uploadFolder); } // 允许的音频文件扩展名 string[] allowedExtensions = { ".mp3", ".wav", ".m4a", ".aac", ".ogg", ".flac", ".amr", ".wma" }; // 处理所有上传文件(通常音频上传是单个文件) HttpPostedFile file = null; string fileName = ""; string fileExtension = ""; int fileSize = 0; string uniqueFileName = ""; string savePath = ""; string relativePath = ""; // 处理所有上传文件(通常音频上传是单个文件) foreach (string fileKey in files) { file = files[fileKey]; if (file.ContentLength == 0) continue; // 安全获取文件名 fileName = Path.GetFileName(file.FileName); fileExtension = Path.GetExtension(fileName).ToLower(); fileSize = file.ContentLength; // 生成唯一文件名,保留原始文件名便于识别 string fileBaseName = Path.GetFileNameWithoutExtension(fileName); uniqueFileName = $"{fileBaseName}_{DateTime.Now:yyyyMMddHHmmss}_{Guid.NewGuid().ToString().Substring(0, 4)}{fileExtension}"; // 移除文件名中的特殊字符 uniqueFileName = RemoveInvalidFileNameChars(uniqueFileName); // 组合完整保存路径 savePath = Path.Combine(uploadFolder, uniqueFileName); // 保存文件 file.SaveAs(savePath); // 返回相对路径(相对于音频存储根目录) relativePath = usid + "/" + uniqueFileName; response = new { success = true, message = "音频上传成功", filePath = relativePath, // 返回相对路径 fileName = uniqueFileName, fileSize = fileSize, fileType = fileExtension }; // 通常音频上传一次只处理一个文件 break; } context.Response.Write(JsonConvert.SerializeObject(response)); } catch (Exception ex) { response = new { success = false, message = $"音频上传失败:{ex.Message}", filePath = "", fileName = "", fileSize = 0, fileType = "" }; context.Response.Write(JsonConvert.SerializeObject(response)); } } } /// <summary> /// 移除文件名中的非法字符 /// </summary> private string RemoveInvalidFileNameChars(string fileName) { if (string.IsNullOrEmpty(fileName)) return fileName; char[] invalidChars = Path.GetInvalidFileNameChars(); foreach (char c in invalidChars) { fileName = fileName.Replace(c.ToString(), "_"); } return fileName; } } }
浙公网安备 33010602011771号