白猫儿

导航

H5音频上传-简单示例

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

408234816e2cdfc0fc957768cb57a6a3

 

 上传到本地项目文件夹中!后台接口实现用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 = {
                        '&': '&amp;',
                        '<': '&lt;',
                        '>': '&gt;',
                        '"': '&quot;',
                        "'": '&#039;'
                    };
                    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>
View Code

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;
        }    
    }
}
View Code

 

posted on 2026-02-04 15:53  白猫儿  阅读(1)  评论(0)    收藏  举报