Web实现HTML页面实时录音本地文件,支持上传语音文件【复制|粘贴 即可实现】

3 阅读3分钟

前端代码

<!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;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        .recording-container {
            text-align: center;
            margin-top: 50px;
        }
        .controls {
            margin: 20px 0;
        }
        button {
            padding: 10px 20px;
            margin: 0 10px;
            font-size: 16px;
            cursor: pointer;
        }
        #recordingsList {
            margin-top: 30px;
            text-align: left;
        }
        audio {
            width: 100%;
            margin: 10px 0;
        }
        .upload-status {
            margin-top: 10px;
            padding: 10px;
            border-radius: 5px;
        }
        .upload-success {
            background-color: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        .upload-error {
            background-color: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
        .upload-progress {
            background-color: #fff3cd;
            color: #856404;
            border: 1px solid #ffeaa7;
        }
    </style>
</head>
<body>
<div class="recording-container">
    <h1>录音功能演示</h1>

    <div class="controls">
        <button id="recordButton">开始录音</button>
        <button id="stopButton" disabled>停止录音</button>
    </div>

    <div id="recordingStatus">
        <p>点击"开始录音"按钮开始录音</p>
    </div>

    <div id="recordingsList">
        <h2>录音列表</h2>
        <div id="recordings"></div>
    </div>
</div>

<script>
    let mediaRecorder;
    let recordedChunks = [];
    let recordingCount = 0;

    const recordButton = document.getElementById('recordButton');
    const stopButton = document.getElementById('stopButton');
    const recordingStatus = document.getElementById('recordingStatus');
    const recordings = document.getElementById('recordings');

    // 检查浏览器是否支持录音功能
    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
        console.log('录音功能可用');
    } else {
        recordingStatus.innerHTML = '<p style="color: red;">您的浏览器不支持录音功能</p>';
    }

    // 开始录音
    recordButton.addEventListener('click', async () => {
        try {
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
            // 使用浏览器支持的最佳音频格式
            const options = getSupportedMimeTypes();
            mediaRecorder = new MediaRecorder(stream, options);
            recordedChunks = [];

            mediaRecorder.start();
            recordingStatus.innerHTML = '<p style="color: green;">正在录音...</p>';
            recordButton.disabled = true;
            stopButton.disabled = false;

            mediaRecorder.addEventListener('dataavailable', event => {
                if (event.data.size > 0) {
                    recordedChunks.push(event.data);
                }
            });

            mediaRecorder.addEventListener('stop', () => {
                debugger
                const mimeType = mediaRecorder.mimeType || 'audio/mp3';
                const fileExtension = getFileExtension(mimeType);
                const blob = new Blob(recordedChunks, { type: mimeType });

                // 生成文件名
                const fileName = `recording${recordingCount + 1}.${fileExtension}`;

                // 创建本地播放元素
                createRecordingElement(blob, fileName);

                // 自动上传到服务器
                uploadRecording(blob, fileName);

                recordingStatus.innerHTML = '<p>录音已停止,正在上传...</p>';
                recordButton.disabled = false;
                stopButton.disabled = true;
            });

        } catch (error) {
            console.error('录音失败:', error);
            recordingStatus.innerHTML = '<p style="color: red;">录音失败: ' + error.message + '</p>';
        }
    });

    // 获取浏览器支持的MIME类型
    function getSupportedMimeTypes() {
        const possibleTypes = [
            'audio/mp3',
            'audio/webm',
            'audio/ogg',
            'audio/wav'
        ];

        for (let type of possibleTypes) {
            if (MediaRecorder.isTypeSupported(type)) {
                return { mimeType: type };
            }
        }

        // 如果都不支持,使用默认配置
        return {};
    }

    // 根据MIME类型获取文件扩展名
    function getFileExtension(mimeType) {
        const mimeToExtension = {
            'audio/mp3': 'mp3',
            'audio/mpeg': 'mp3',
            'audio/webm': 'webm',
            'audio/ogg': 'ogg',
            'audio/wav': 'wav',
            'audio/wave': 'wav'
        };

        return mimeToExtension[mimeType] || 'mp3';
    }

    // 创建录音播放元素
    function createRecordingElement(blob, fileName) {
        const url = URL.createObjectURL(blob);

        recordingCount++;
        const recordingElement = document.createElement('div');
        recordingElement.innerHTML = `
            <h3>录音 ${recordingCount}</h3>
            <audio controls>
                <source src="${url}" type="${blob.type}">
                您的浏览器不支持音频播放。
            </audio>
            <a href="${url}" download="${fileName}">下载录音</a>
            <div id="uploadStatus${recordingCount}" class="upload-status"></div>
        `;

        recordings.prepend(recordingElement);
        return recordingCount;
    }

    // 上传录音文件到服务器
    function uploadRecording(blob, fileName) {
        const formData = new FormData();
        formData.append('audio', blob, fileName);

        // 创建当前录音的上传状态元素
        const currentUploadStatusId = `uploadStatus${recordingCount + 1}`;

        // 更新上传状态显示
        const updateUploadStatus = (message, className) => {
            const statusElement = document.getElementById(currentUploadStatusId);
            if (statusElement) {
                statusElement.textContent = message;
                statusElement.className = `upload-status ${className}`;
            }
        };

        // 发送上传请求
        fetch('https://170.170.10.82:8443/api/upload', {
            method: 'POST',
            body: formData
        }).then(response => {
            if (response.ok) {
                updateUploadStatus('上传成功!', 'upload-success');
                console.log('录音文件上传成功');
            } else {
                throw new Error(`上传失败: ${response.status} ${response.statusText}`);
            }
        }).catch(error => {
            console.error('上传失败:', error);
            updateUploadStatus(`上传失败: ${error.message}`, 'upload-error');
        });
    }

    // 停止录音
    stopButton.addEventListener('click', () => {
        if (mediaRecorder && mediaRecorder.state !== 'inactive') {
            mediaRecorder.stop();
            // 停止所有音轨
            mediaRecorder.stream.getTracks().forEach(track => track.stop());
        }
    });
</script>
</body>
</html>

java代码

package com.et.webrtc.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.http.ResponseEntity; import org.springframework.http.HttpStatus;

import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.UUID;

@RestController @CrossOrigin(origins = "*") @RequestMapping("/api") public class AudioUploadController {

@Value("${upload.dir:uploads/}")
private String uploadDir;

@PostMapping("/upload")
public ResponseEntity<?> uploadAudio(@RequestParam("audio") MultipartFile file) {
    try {
        // 检查文件是否为空
        if (file.isEmpty()) {
            return ResponseEntity.badRequest().body(createResponse("error", "文件为空", null, null));
        }

        // 创建上传目录
        Path uploadPath = Paths.get(uploadDir);
        if (!Files.exists(uploadPath)) {
            Files.createDirectories(uploadPath);
        }

        // 生成唯一文件名
        String originalFilename = file.getOriginalFilename();
        String fileExtension = getFileExtension(originalFilename);
        String uniqueFilename = System.currentTimeMillis() + "_" + UUID.randomUUID().toString() + "." + fileExtension;

        // 保存文件
        Path filePath = uploadPath.resolve(uniqueFilename);
        Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);

        // 返回成功响应
        return ResponseEntity.ok()
                .body(createResponse("success", "文件上传成功", uniqueFilename, filePath.toString()));

    } catch (IOException e) {
        e.printStackTrace();
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(createResponse("error", "文件上传失败: " + e.getMessage(), null, null));
    } catch (Exception e) {
        e.printStackTrace();
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(createResponse("error", "上传过程中发生错误: " + e.getMessage(), null, null));
    }
}

// 获取文件扩展名
private String getFileExtension(String filename) {
    if (filename == null || filename.lastIndexOf(".") == -1) {
        return "audio";
    }
    return filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
}

// 创建响应对象
private UploadResponse createResponse(String status, String message, String filename, String filepath) {
    return new UploadResponse(status, message, filename, filepath);
}

// 响应数据类
public static class UploadResponse {
    private String status;
    private String message;
    private String filename;
    private String filepath;

    public UploadResponse(String status, String message, String filename, String filepath) {
        this.status = status;
        this.message = message;
        this.filename = filename;
        this.filepath = filepath;
    }

    // Getters and Setters
    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }

    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }

    public String getFilename() { return filename; }
    public void setFilename(String filename) { this.filename = filename; }

    public String getFilepath() { return filepath; }
    public void setFilepath(String filepath) { this.filepath = filepath; }
}

}