语音识别网页版转化成APP版

方法一. 浏览器语音版 先期配置

先在电脑上安装八云软件 ,原因是谷歌浏览器语音识别,,谷歌浏览器的服务端在国外,因此,需要安装翻墙软件

java 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
    <meta name="theme-color" content="#4F46E5">
    <title>智能语音助手</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            max-width: 600px;
            margin: 0 auto;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 32px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
            overflow: hidden;
        }

        .header {
            background: linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%);
            padding: 30px 20px;
            text-align: center;
            color: white;
        }

        .header h1 {
            font-size: 28px;
            margin-bottom: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
        }

        .header p {
            font-size: 14px;
            opacity: 0.9;
        }

        .voice-area {
            padding: 40px 20px;
            text-align: center;
            background: white;
        }

        .mic-button {
            width: 120px;
            height: 120px;
            border-radius: 60px;
            background: linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%);
            border: none;
            cursor: pointer;
            box-shadow: 0 10px 30px rgba(79, 70, 229, 0.4);
            transition: all 0.3s ease;
            margin: 0 auto;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .mic-button:active {
            transform: scale(0.95);
        }

        .mic-button.recording {
            animation: pulse 1.5s infinite;
            background: linear-gradient(135deg, #EF4444 0%, #DC2626 100%);
        }

        @keyframes pulse {
            0% {
                box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
            }
            70% {
                box-shadow: 0 0 0 20px rgba(239, 68, 68, 0);
            }
            100% {
                box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
            }
        }

        .mic-icon {
            font-size: 48px;
        }

        .status-text {
            margin-top: 20px;
            font-size: 14px;
            color: #666;
        }

        .result-area {
            padding: 20px;
            background: #F9FAFB;
            border-top: 1px solid #E5E7EB;
        }

        .section-title {
            font-size: 16px;
            font-weight: 600;
            color: #374151;
            margin-bottom: 12px;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }

        .current-result {
            background: white;
            border-radius: 16px;
            padding: 20px;
            min-height: 100px;
            border: 1px solid #E5E7EB;
            margin-bottom: 24px;
        }

        .result-text {
            font-size: 18px;
            line-height: 1.5;
            color: #1F2937;
            word-wrap: break-word;
        }

        .placeholder-text {
            color: #9CA3AF;
            font-style: italic;
        }

        .action-buttons {
            display: flex;
            gap: 12px;
            margin-top: 12px;
        }

        .btn {
            flex: 1;
            padding: 10px;
            border: none;
            border-radius: 12px;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s;
        }

        .btn-primary {
            background: #4F46E5;
            color: white;
        }

        .btn-primary:active {
            background: #4338CA;
        }

        .btn-secondary {
            background: #E5E7EB;
            color: #374151;
        }

        .btn-secondary:active {
            background: #D1D5DB;
        }

        .history-list {
            max-height: 300px;
            overflow-y: auto;
        }

        .history-item {
            background: white;
            border-radius: 12px;
            padding: 12px;
            margin-bottom: 8px;
            border: 1px solid #E5E7EB;
        }

        .history-time {
            font-size: 11px;
            color: #9CA3AF;
            margin-bottom: 6px;
        }

        .history-text {
            font-size: 14px;
            color: #1F2937;
            word-wrap: break-word;
            margin-bottom: 8px;
        }

        .history-actions {
            display: flex;
            gap: 8px;
            justify-content: flex-end;
        }

        .history-actions button {
            padding: 4px 8px;
            font-size: 11px;
            background: #F3F4F6;
            border: none;
            border-radius: 6px;
            cursor: pointer;
        }

        .empty-history {
            text-align: center;
            padding: 40px;
            color: #9CA3AF;
        }

        .language-select {
            padding: 20px;
            background: white;
            border-top: 1px solid #E5E7EB;
            display: flex;
            gap: 12px;
            align-items: center;
            flex-wrap: wrap;
        }

        .lang-btn {
            padding: 8px 16px;
            border: 1px solid #E5E7EB;
            background: white;
            border-radius: 20px;
            cursor: pointer;
            font-size: 13px;
            transition: all 0.2s;
        }

        .lang-btn.active {
            background: #4F46E5;
            color: white;
            border-color: #4F46E5;
        }

        .toast {
            position: fixed;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 10px 20px;
            border-radius: 30px;
            font-size: 14px;
            z-index: 1000;
            animation: fadeInOut 2s ease;
        }

        @keyframes fadeInOut {
            0% { opacity: 0; transform: translateX(-50%) translateY(20px); }
            15% { opacity: 1; transform: translateX(-50%) translateY(0); }
            85% { opacity: 1; transform: translateX(-50%) translateY(0); }
            100% { opacity: 0; transform: translateX(-50%) translateY(-20px); }
        }

        .warning-box {
            background: #FEF3C7;
            color: #92400E;
            padding: 12px;
            margin: 10px;
            border-radius: 12px;
            font-size: 13px;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>
                <span>🎙️</span>
                智能语音助手
            </h1>
            <p>点击麦克风开始说话,实时识别你的语音</p>
        </div>

        <div class="voice-area">
            <button class="mic-button" id="micBtn">
                <span class="mic-icon">🎤</span>
            </button>
            <div class="status-text" id="statusText">点击麦克风开始录音</div>
        </div>

        <div class="result-area">
            <div class="section-title">
                <span>📝 当前识别</span>
            </div>
            <div class="current-result">
                <div class="result-text" id="currentResult">
                    <span class="placeholder-text">等待识别...</span>
                </div>
                <div class="action-buttons">
                    <button class="btn btn-secondary" id="copyBtn">📋 复制</button>
                    <button class="btn btn-secondary" id="clearBtn">🗑️ 清除</button>
                    <button class="btn btn-primary" id="saveBtn">💾 保存到历史</button>
                </div>
            </div>

            <div class="section-title">
                <span>📋 历史记录</span>
                <button class="btn-secondary" id="clearHistoryBtn" style="padding: 4px 12px; font-size: 12px;">清空全部</button>
            </div>
            <div class="history-list" id="historyList">
                <div class="empty-history">暂无历史记录</div>
            </div>
        </div>

        <div class="language-select">
            <span style="font-size: 13px; color: #666;">🌐 语言:</span>
            <button class="lang-btn active" data-lang="zh-CN">中文(简体)</button>
            <button class="lang-btn" data-lang="en-US">English</button>
            <button class="lang-btn" data-lang="zh-TW">中文(繁体)</button>
            <button class="lang-btn" data-lang="ja-JP">日本語</button>
            <button class="lang-btn" data-lang="ko-KR">한국어</button>
        </div>
    </div>

    <script>
        // DOM 元素
        const micBtn = document.getElementById('micBtn');
        const statusText = document.getElementById('statusText');
        const currentResultDiv = document.getElementById('currentResult');
        const historyListDiv = document.getElementById('historyList');
        const copyBtn = document.getElementById('copyBtn');
        const clearBtn = document.getElementById('clearBtn');
        const saveBtn = document.getElementById('saveBtn');
        const clearHistoryBtn = document.getElementById('clearHistoryBtn');

        // 语音识别相关变量
        let recognition = null;
        let isRecording = false;
        let currentLanguage = 'zh-CN';
        let finalTranscript = '';
        let interimTranscript = '';

        // 历史记录存储
        let history = [];

        // ========== 环境检测 ==========
        function checkEnvironment() {
            const isSpeechSupported = ('webkitSpeechRecognition' in window) || ('SpeechRecognition' in window);
            const isSecure = window.isSecureContext || window.location.protocol === 'file:' || 
                           window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
            
            if (!isSpeechSupported) {
                statusText.textContent = '❌ 浏览器不支持语音识别';
                micBtn.disabled = true;
                micBtn.style.opacity = '0.5';
                showToast('请使用 Chrome、Edge 或 Safari 浏览器');
                return false;
            }
            
            if (!isSecure && window.location.protocol !== 'file:' && window.location.protocol !== 'http:') {
                // 对于 HTTP,给出警告但允许尝试(某些浏览器可能仍然可用)
                const warningDiv = document.createElement('div');
                warningDiv.className = 'warning-box';
                warningDiv.innerHTML = '⚠️ 注意:当前是 HTTP 环境,某些浏览器可能需要 HTTPS 才能使用麦克风。<br>建议使用 localhost 访问或打包成 APP。';
                document.querySelector('.voice-area').insertBefore(warningDiv, micBtn);
            }
            
            return true;
        }
        
        // ========== 初始化语音识别 ==========
        function initSpeechRecognition() {
            // 检查浏览器支持
            if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
                statusText.textContent = '❌ 浏览器不支持语音识别';
                micBtn.disabled = true;
                showToast('当前浏览器不支持语音识别');
                return false;
            }

            const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
            recognition = new SpeechRecognition();
            
            recognition.continuous = true;      // 持续识别
            recognition.interimResults = true;  // 显示临时结果
            recognition.maxAlternatives = 1;    // 只取最佳结果
            recognition.lang = currentLanguage;
            
            // 开始识别
            recognition.onstart = () => {
                console.log('语音识别已启动');
                isRecording = true;
                micBtn.classList.add('recording');
                statusText.textContent = '🎙️ 正在录音,请说话...';
                // 不清空已有内容,允许追加
                updateResultDisplay();
            };
            
            // 获取识别结果
            recognition.onresult = (event) => {
                console.log('收到识别结果:', event.results);
                interimTranscript = '';
                
                for (let i = event.resultIndex; i < event.results.length; i++) {
                    const transcript = event.results[i][0].transcript;
                    if (event.results[i].isFinal) {
                        finalTranscript += transcript;
                        console.log('最终结果:', finalTranscript);
                    } else {
                        interimTranscript += transcript;
                        console.log('临时结果:', interimTranscript);
                    }
                }
                updateResultDisplay();
            };
            
            // 识别结束
            recognition.onend = () => {
                console.log('语音识别已结束');
                isRecording = false;
                micBtn.classList.remove('recording');
                
                if (!finalTranscript && !interimTranscript) {
                    statusText.textContent = '🎤 点击麦克风开始录音';
                } else {
                    statusText.textContent = '✅ 录音结束,点击麦克风继续录音';
                }
            };
            
            // 错误处理
            recognition.onerror = (event) => {
                console.error('识别错误:', event.error);
                isRecording = false;
                micBtn.classList.remove('recording');
                
                switch(event.error) {
                    case 'not-allowed':
                        statusText.textContent = '🔇 未获得麦克风权限,请检查设置';
                        showToast('请允许麦克风权限');
                        break;
                    case 'no-speech':
                        statusText.textContent = '🎤 未检测到语音,点击重试';
                        showToast('未检测到语音,请点击麦克风重试');
                        break;
                    case 'audio-capture':
                        statusText.textContent = '🎤 未找到麦克风设备';
                        showToast('请检查麦克风是否连接');
                        break;
                    case 'network':
                        statusText.textContent = '🌐 网络错误,请检查连接';
                        showToast('网络连接失败');
                        break;
                    default:
                        statusText.textContent = '❌ 识别出错:' + event.error;
                        showToast('识别出错,请重试');
                }
            };
            
            return true;
        }
        
        // 开始录音
        function startRecording() {
            if (!recognition) {
                if (!initSpeechRecognition()) return;
            }
            
            try {
                // 重置当前会话的内容(可选:如果想追加内容就注释掉)
                // finalTranscript = '';
                // interimTranscript = '';
                updateResultDisplay();
                
                // 设置当前语言
                recognition.lang = currentLanguage;
                recognition.start();
                console.log('开始录音,语言:', currentLanguage);
            } catch (error) {
                console.error('启动失败:', error);
                // 如果已经启动,先停止再重新开始
                if (error.name === 'InvalidStateError') {
                    recognition.stop();
                    setTimeout(() => {
                        recognition.start();
                    }, 100);
                } else {
                    statusText.textContent = '❌ 启动失败:' + error.message;
                    showToast('启动失败,请刷新页面重试');
                }
            }
        }
        
        // 停止录音
        function stopRecording() {
            if (recognition && isRecording) {
                try {
                    recognition.stop();
                    console.log('停止录音');
                } catch (error) {
                    console.error('停止失败:', error);
                }
            }
        }
        
        // 切换录音状态
        function toggleRecording() {
            if (isRecording) {
                stopRecording();
            } else {
                startRecording();
            }
        }
        
        // ========== UI 相关函数 ==========
        function showToast(message) {
            const toast = document.createElement('div');
            toast.className = 'toast';
            toast.textContent = message;
            document.body.appendChild(toast);
            setTimeout(() => {
                if (toast.parentNode) toast.remove();
            }, 2000);
        }
        
        function escapeHtml(text) {
            const div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }
        
        function updateResultDisplay() {
            const displayText = finalTranscript || interimTranscript;
            if (displayText && displayText.trim()) {
                currentResultDiv.innerHTML = `<div class="result-text">${escapeHtml(displayText)}</div>`;
            } else {
                currentResultDiv.innerHTML = `<div class="result-text"><span class="placeholder-text">等待识别...</span></div>`;
            }
        }
        
        function copyCurrentResult() {
            const text = finalTranscript || interimTranscript;
            if (text && text.trim()) {
                navigator.clipboard.writeText(text).then(() => {
                    showToast('✅ 已复制到剪贴板');
                }).catch(() => {
                    showToast('❌ 复制失败');
                });
            } else {
                showToast('没有可复制的内容');
            }
        }
        
        function clearCurrent() {
            finalTranscript = '';
            interimTranscript = '';
            updateResultDisplay();
            showToast('已清除');
        }
        
        function saveCurrentToHistory() {
            const text = finalTranscript || interimTranscript;
            if (text && text.trim()) {
                addToHistory(text);
                clearCurrent();
            } else {
                showToast('没有可保存的内容');
            }
        }
        
        // ========== 历史记录管理 ==========
        function loadHistory() {
            const saved = localStorage.getItem('speech_history');
            if (saved) {
                try {
                    history = JSON.parse(saved);
                    renderHistory();
                } catch(e) {
                    console.error('加载历史失败', e);
                }
            }
        }
        
        function saveHistoryToStorage() {
            localStorage.setItem('speech_history', JSON.stringify(history));
        }
        
        function addToHistory(text) {
            if (!text || !text.trim()) return;
            history.unshift({
                id: Date.now(),
                text: text.trim(),
                time: new Date().toLocaleString('zh-CN')
            });
            if (history.length > 50) history = history.slice(0, 50);
            saveHistoryToStorage();
            renderHistory();
            showToast('已保存到历史记录');
        }
        
        function deleteHistoryItem(id) {
            history = history.filter(item => item.id !== id);
            saveHistoryToStorage();
            renderHistory();
            showToast('已删除');
        }
        
        function clearAllHistory() {
            if (history.length > 0 && confirm('确定要清空所有历史记录吗?')) {
                history = [];
                saveHistoryToStorage();
                renderHistory();
                showToast('已清空所有历史记录');
            }
        }
        
        function renderHistory() {
            if (!historyListDiv) return;
            
            if (history.length === 0) {
                historyListDiv.innerHTML = '<div class="empty-history">📭 暂无历史记录</div>';
                return;
            }
            
            historyListDiv.innerHTML = history.map(item => `
                <div class="history-item">
                    <div class="history-time">🕐 ${escapeHtml(item.time)}</div>
                    <div class="history-text">${escapeHtml(item.text)}</div>
                    <div class="history-actions">
                        <button onclick="window.copyHistoryItem('${escapeHtml(item.text).replace(/'/g, "\\'")}')">📋 复制</button>
                        <button onclick="window.deleteHistoryItem(${item.id})">🗑️ 删除</button>
                    </div>
                </div>
            `).join('');
        }
        
        window.copyHistoryItem = function(text) {
            navigator.clipboard.writeText(text).then(() => {
                showToast('✅ 已复制');
            });
        };
        
        window.deleteHistoryItem = deleteHistoryItem;
        
        function setLanguage(lang) {
            currentLanguage = lang;
            if (recognition) {
                recognition.lang = lang;
            }
            document.querySelectorAll('.lang-btn').forEach(btn => {
                if (btn.dataset.lang === lang) {
                    btn.classList.add('active');
                } else {
                    btn.classList.remove('active');
                }
            });
            showToast(`已切换到 ${getLangName(lang)}`);
        }
        
        function getLangName(lang) {
            const names = {
                'zh-CN': '中文(简体)',
                'en-US': 'English',
                'zh-TW': '中文(繁体)',
                'ja-JP': '日本語',
                'ko-KR': '한국어'
            };
            return names[lang] || lang;
        }
        
        // ========== 页面初始化 ==========
        document.addEventListener('DOMContentLoaded', () => {
            // 检查环境
            checkEnvironment();
            
            // 初始化语音识别
            initSpeechRecognition();
            
            // 加载历史记录
            loadHistory();
            
            // 绑定事件
            micBtn.addEventListener('click', toggleRecording);
            copyBtn.addEventListener('click', copyCurrentResult);
            clearBtn.addEventListener('click', clearCurrent);
            saveBtn.addEventListener('click', saveCurrentToHistory);
            clearHistoryBtn.addEventListener('click', clearAllHistory);
            
            // 语言切换
            document.querySelectorAll('.lang-btn').forEach(btn => {
                btn.addEventListener('click', () => {
                    setLanguage(btn.dataset.lang);
                });
            });
            
            console.log('页面初始化完成');
        });
    </script>
</body>
</html>

部署在阿里云上的位置SSL

java 复制代码
# 语音识别 -语音识别系统,重定向到tomcat 中 语音识别项目
ProxyPass /yuyin http://127.0.0.1:8080/yuyin
ProxyPassReverse /yuyin http://127.0.0.1:8080/yuyin

访问的路径

java 复制代码
https://服务器域名/yuyin/yuyin.html

方法二. APP语音识别版 先期配置

使用 HBuilderX(绿色图标)来打包 APP

*下载地址:https://www.dcloud.io/hbuilderx.html*

具体打包步骤我上一轮已经详细介绍过了,概括如下:

新建项目:文件 → 新建 → 项目 → 选择「5+App」或「uni-app」

导入网页:将你的 index.html 放入项目目录

可以把css、img、js 、unpackage 、文件夹删除,只保留manifest.json 、和html文件按

配置应用:打开 manifest.json,填写应用名称、图标,勾选 Speech 语音模块

1.配置 应用标识(ApplD)

升级时 ,切记应用版本名称

升级时必须高于上一次设置的值 。离线打包需另行配置:

2.配置 应用的图标 (1024*1024)

3.配置 应用的Speech 语音模块(百度语音识别)

(备注)讯飞语音识别与百度语音识别的区别

操作步骤:如何配置百度语音识别

1.获取百度语音识别参数

2.在 HBuilderX 中配置

  • 打开项目的 manifest.json 文件。

    切换到 「视图配置」 或 「App模块配置」 选项卡。

    找到 Speech(语音输入) 模块并勾选。

    在下方选择 「百度语音识别」。

    在出现的输入框中,将你刚才从百度控制台获取的 AppID、API Key、Secret Key 依次填入。

    完成后保存文件。

3.打包与权限配置

  • 提交云端打包:因为这些配置需要打包到App里才能生效,配置保存后,你需要重新提交云端打包(发行 ->

    原生App-云打包),生成新的APK。

    检查麦克风权限:为了确保App能正常录音,建议在 manifest.json 的 "App权限配置" 里,确认一下

    android.permission.RECORD_AUDIO 权限已被勾选

3.配置 模块权限(具有麦克风权限)

android.permission.RECORD_AUDIO 权限被 勾选
作用:允许应用访问设备的麦克风以录制音频​

云打包:发行 → 原生App-云打包 → 选择 Android → 等待下载 APK

获取证书的步骤

1.访问链接,获取证书的密码和密钥
https://dev.dcloud.net.cn/pages/app/list

2.获取Android云端证书

证书详情中含有 证书私钥密码
下载证书 后放在本地,打包时证书文件 需要填写它的下载到文件夹的路径地址

详情代码内容

java 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
    <meta name="theme-color" content="#4F46E5">
    <title>智能语音助手APP</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
    
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
    
        .container {
            max-width: 600px;
            margin: 0 auto;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 32px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
            overflow: hidden;
        }
    
        .header {
            background: linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%);
            padding: 30px 20px;
            text-align: center;
            color: white;
        }
    
        .header h1 {
            font-size: 28px;
            margin-bottom: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
        }
    
        .header p {
            font-size: 14px;
            opacity: 0.9;
        }
    
        .voice-area {
            padding: 40px 20px;
            text-align: center;
            background: white;
        }
    
        .mic-button {
            width: 120px;
            height: 120px;
            border-radius: 60px;
            background: linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%);
            border: none;
            cursor: pointer;
            box-shadow: 0 10px 30px rgba(79, 70, 229, 0.4);
            transition: all 0.3s ease;
            margin: 0 auto;
            display: flex;
            align-items: center;
            justify-content: center;
        }
    
        .mic-button:active {
            transform: scale(0.95);
        }
    
        .mic-button.recording {
            animation: pulse 1.5s infinite;
            background: linear-gradient(135deg, #EF4444 0%, #DC2626 100%);
        }
    
        @keyframes pulse {
            0% {
                box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
            }
            70% {
                box-shadow: 0 0 0 20px rgba(239, 68, 68, 0);
            }
            100% {
                box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
            }
        }
    
        .mic-icon {
            font-size: 48px;
        }
    
        .status-text {
            margin-top: 20px;
            font-size: 14px;
            color: #666;
        }
    
        .result-area {
            padding: 20px;
            background: #F9FAFB;
            border-top: 1px solid #E5E7EB;
        }
    
        .section-title {
            font-size: 16px;
            font-weight: 600;
            color: #374151;
            margin-bottom: 12px;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }
    
        .current-result {
            background: white;
            border-radius: 16px;
            padding: 20px;
            min-height: 100px;
            border: 1px solid #E5E7EB;
            margin-bottom: 24px;
        }
    
        .result-text {
            font-size: 18px;
            line-height: 1.5;
            color: #1F2937;
            word-wrap: break-word;
        }
    
        .placeholder-text {
            color: #9CA3AF;
            font-style: italic;
        }
    
        .action-buttons {
            display: flex;
            gap: 12px;
            margin-top: 12px;
        }
    
        .btn {
            flex: 1;
            padding: 10px;
            border: none;
            border-radius: 12px;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s;
        }
    
        .btn-primary {
            background: #4F46E5;
            color: white;
        }
    
        .btn-primary:active {
            background: #4338CA;
        }
    
        .btn-secondary {
            background: #E5E7EB;
            color: #374151;
        }
    
        .btn-secondary:active {
            background: #D1D5DB;
        }
    
        .history-list {
            max-height: 300px;
            overflow-y: auto;
        }
    
        .history-item {
            background: white;
            border-radius: 12px;
            padding: 12px;
            margin-bottom: 8px;
            border: 1px solid #E5E7EB;
        }
    
        .history-time {
            font-size: 11px;
            color: #9CA3AF;
            margin-bottom: 6px;
        }
    
        .history-text {
            font-size: 14px;
            color: #1F2937;
            word-wrap: break-word;
            margin-bottom: 8px;
        }
    
        .history-actions {
            display: flex;
            gap: 8px;
            justify-content: flex-end;
        }
    
        .history-actions button {
            padding: 4px 8px;
            font-size: 11px;
            background: #F3F4F6;
            border: none;
            border-radius: 6px;
            cursor: pointer;
        }
    
        .empty-history {
            text-align: center;
            padding: 40px;
            color: #9CA3AF;
        }
    
        .language-select {
            padding: 20px;
            background: white;
            border-top: 1px solid #E5E7EB;
            display: flex;
            gap: 12px;
            align-items: center;
            flex-wrap: wrap;
        }
    
        .lang-btn {
            padding: 8px 16px;
            border: 1px solid #E5E7EB;
            background: white;
            border-radius: 20px;
            cursor: pointer;
            font-size: 13px;
            transition: all 0.2s;
        }
    
        .lang-btn.active {
            background: #4F46E5;
            color: white;
            border-color: #4F46E5;
        }
    
        .toast {
            position: fixed;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 10px 20px;
            border-radius: 30px;
            font-size: 14px;
            z-index: 1000;
            animation: fadeInOut 2s ease;
        }
    
        @keyframes fadeInOut {
            0% { opacity: 0; transform: translateX(-50%) translateY(20px); }
            15% { opacity: 1; transform: translateX(-50%) translateY(0); }
            85% { opacity: 1; transform: translateX(-50%) translateY(0); }
            100% { opacity: 0; transform: translateX(-50%) translateY(-20px); }
        }
    
        .warning-box {
            background: #FEF3C7;
            color: #92400E;
            padding: 12px;
            margin: 10px;
            border-radius: 12px;
            font-size: 13px;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1><span>🎙️</span>智能语音助手</h1>
            <p>点击麦克风开始说话,实时识别你的语音</p>
        </div>
        <div class="voice-area">
            <button class="mic-button" id="micBtn">
                <span class="mic-icon">🎤</span>
            </button>
            <div class="status-text" id="statusText">点击麦克风开始录音</div>
        </div>
        <div class="result-area">
            <div class="section-title"><span>📝 当前识别</span></div>
            <div class="current-result">
                <div class="result-text" id="currentResult"><span class="placeholder-text">等待识别...</span></div>
                <div class="action-buttons">
                    <button class="btn btn-secondary" id="copyBtn">📋 复制</button>
                    <button class="btn btn-secondary" id="clearBtn">🗑️ 清除</button>
                    <button class="btn btn-primary" id="saveBtn">💾 保存到历史</button>
                </div>
            </div>
            <div class="section-title">
                <span>📋 历史记录</span>
                <button class="btn-secondary" id="clearHistoryBtn" style="padding: 4px 12px; font-size: 12px;">清空全部</button>
            </div>
            <div class="history-list" id="historyList"><div class="empty-history">暂无历史记录</div></div>
        </div>
        <div class="language-select">
            <span style="font-size: 13px; color: #666;">🌐 语言:</span>
            <button class="lang-btn active" data-lang="zh-CN">中文(简体)</button>
            <button class="lang-btn" data-lang="en-US">English</button>
            <button class="lang-btn" data-lang="zh-TW">中文(繁体)</button>
            <button class="lang-btn" data-lang="ja-JP">日本語</button>
            <button class="lang-btn" data-lang="ko-KR">한국어</button>
        </div>
    </div>

    <script>
        // DOM 元素
        const micBtn = document.getElementById('micBtn');
        const statusText = document.getElementById('statusText');
        const currentResultDiv = document.getElementById('currentResult');
        const historyListDiv = document.getElementById('historyList');
        const copyBtn = document.getElementById('copyBtn');
        const clearBtn = document.getElementById('clearBtn');
        const saveBtn = document.getElementById('saveBtn');
        const clearHistoryBtn = document.getElementById('clearHistoryBtn');

        // 语音识别相关变量
        let isRecording = false;
        let currentLanguage = 'zh-CN';
        let finalTranscript = '';
        
        // 语言映射:前端UI语言 -> 百度语音识别语言代码
        const languageMap = {
            'zh-CN': 'zh-cn',
            'en-US': 'en-us',
            'zh-TW': 'zh-tw',
            'ja-JP': 'jp',
            'ko-KR': 'kr'
        };

        // 历史记录存储
        let history = [];

        // ========== UI 辅助函数 ==========
        function showToast(message) {
            const toast = document.createElement('div');
            toast.className = 'toast';
            toast.textContent = message;
            document.body.appendChild(toast);
            setTimeout(() => { if (toast.parentNode) toast.remove(); }, 2000);
        }

        function escapeHtml(text) {
            const div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }

        function updateResultDisplay() {
            if (finalTranscript && finalTranscript.trim()) {
                currentResultDiv.innerHTML = `<div class="result-text">${escapeHtml(finalTranscript)}</div>`;
            } else {
                currentResultDiv.innerHTML = `<div class="result-text"><span class="placeholder-text">等待识别...</span></div>`;
            }
        }

        function copyCurrentResult() {
            if (finalTranscript && finalTranscript.trim()) {
                navigator.clipboard.writeText(finalTranscript).then(() => showToast('✅ 已复制到剪贴板'))
                    .catch(() => showToast('❌ 复制失败'));
            } else {
                showToast('没有可复制的内容');
            }
        }

        function clearCurrent() {
            finalTranscript = '';
            updateResultDisplay();
            showToast('已清除');
        }

        function saveCurrentToHistory() {
            if (finalTranscript && finalTranscript.trim()) {
                addToHistory(finalTranscript);
                clearCurrent();
            } else {
                showToast('没有可保存的内容');
            }
        }

        // ========== 历史记录管理 ==========
        function loadHistory() {
            const saved = localStorage.getItem('speech_history');
            if (saved) {
                try { history = JSON.parse(saved); renderHistory(); } catch(e) { console.error('加载历史失败', e); }
            }
        }

        function saveHistoryToStorage() { localStorage.setItem('speech_history', JSON.stringify(history)); }

        function addToHistory(text) {
            if (!text || !text.trim()) return;
            history.unshift({ id: Date.now(), text: text.trim(), time: new Date().toLocaleString('zh-CN') });
            if (history.length > 50) history = history.slice(0, 50);
            saveHistoryToStorage();
            renderHistory();
            showToast('已保存到历史记录');
        }

        function deleteHistoryItem(id) {
            history = history.filter(item => item.id !== id);
            saveHistoryToStorage();
            renderHistory();
            showToast('已删除');
        }

        function clearAllHistory() {
            if (history.length > 0 && confirm('确定要清空所有历史记录吗?')) {
                history = [];
                saveHistoryToStorage();
                renderHistory();
                showToast('已清空所有历史记录');
            }
        }

        function renderHistory() {
            if (!historyListDiv) return;
            if (history.length === 0) {
                historyListDiv.innerHTML = '<div class="empty-history">📭 暂无历史记录</div>';
                return;
            }
            historyListDiv.innerHTML = history.map(item => `
                <div class="history-item">
                    <div class="history-time">🕐 ${escapeHtml(item.time)}</div>
                    <div class="history-text">${escapeHtml(item.text)}</div>
                    <div class="history-actions">
                        <button onclick="window.copyHistoryItem('${escapeHtml(item.text).replace(/'/g, "\\'")}')">📋 复制</button>
                        <button onclick="window.deleteHistoryItem(${item.id})">🗑️ 删除</button>
                    </div>
                </div>
            `).join('');
        }

        window.copyHistoryItem = function(text) {
            navigator.clipboard.writeText(text).then(() => showToast('✅ 已复制'));
        };
        window.deleteHistoryItem = deleteHistoryItem;

        function setLanguage(lang) {
            currentLanguage = lang;
            document.querySelectorAll('.lang-btn').forEach(btn => {
                if (btn.dataset.lang === lang) btn.classList.add('active');
                else btn.classList.remove('active');
            });
            showToast(`已切换到 ${getLangName(lang)}`);
        }

        function getLangName(lang) {
            const names = { 'zh-CN': '中文(简体)', 'en-US': 'English', 'zh-TW': '中文(繁体)', 'ja-JP': '日本語', 'ko-KR': '한국어' };
            return names[lang] || lang;
        }

        // ========== 核心:使用 plus.speech 进行语音识别 ==========
        function startRecording() {
            // 检查 plus 是否就绪
            if (typeof plus === 'undefined') {
                statusText.textContent = '❌ 环境未就绪,请重启APP';
                showToast('环境未就绪,请重启APP');
                return;
            }

            // 检查是否已有识别在进行
            if (isRecording) {
                stopRecording();
                return;
            }

            try {
                // 配置语音识别参数
                var options = {
                    engine: 'baidu',                    // 使用百度语音识别引擎
                    lang: languageMap[currentLanguage] || 'zh-cn',  // 语言设置
                    punctuation: true,                  // 启用标点符号
                    timeout: 60 * 1000,                 // 60秒超时
                    continue: false,                    // 不连续识别(单次)
                    userInterface: true                  // 显示默认识别界面(静音时自动结束)
                };
                
                console.log('开始语音识别,参数:', options);
                
                // 调用 plus.speech 开始识别[citation:5][citation:9]
                plus.speech.startRecognize(
                    options,
                    function(result) {
                        // 识别成功回调
                        console.log('识别成功:', result);
                        if (result && result.trim()) {
                            finalTranscript = result;
                            updateResultDisplay();
                            statusText.textContent = '✅ 识别完成';
                        } else {
                            statusText.textContent = '🎤 未识别到内容,点击重试';
                            showToast('未识别到内容,请重试');
                        }
                        isRecording = false;
                        micBtn.classList.remove('recording');
                    },
                    function(error) {
                        // 识别失败回调
                        console.error('识别失败:', error);
                        isRecording = false;
                        micBtn.classList.remove('recording');
                        
                        // 处理不同错误码[citation:1][citation:7]
                        if (error.code === -4004 || error.code === -1) {
                            statusText.textContent = '❌ 语音引擎未配置';
                            showToast('请检查manifest.json中百度语音配置是否正确');
                        } else if (error.code === -3001) {
                            statusText.textContent = '🎤 未检测到语音';
                            showToast('未检测到语音,请对准麦克风说话');
                        } else {
                            statusText.textContent = '❌ 识别失败:' + (error.message || '未知错误');
                            showToast('识别失败,请重试');
                        }
                    }
                );
                
                isRecording = true;
                micBtn.classList.add('recording');
                statusText.textContent = '🎙️ 正在录音,请说话...';
                
            } catch (e) {
                console.error('启动识别异常:', e);
                statusText.textContent = '❌ 启动失败:' + e.message;
                showToast('启动失败,请检查语音模块配置');
                isRecording = false;
                micBtn.classList.remove('recording');
            }
        }

        function stopRecording() {
            if (typeof plus !== 'undefined' && plus.speech) {
                try {
                    plus.speech.stopRecognize();
                    console.log('停止语音识别');
                } catch(e) { console.error('停止失败:', e); }
            }
            isRecording = false;
            micBtn.classList.remove('recording');
            statusText.textContent = '🎤 点击麦克风开始录音';
        }

        function toggleRecording() {
            if (isRecording) {
                stopRecording();
            } else {
                startRecording();
            }
        }

        // ========== 页面初始化 ==========
       // ========== 页面初始化(修正版) ==========
       document.addEventListener('DOMContentLoaded', () => {
           // 加载历史记录(不依赖plus)
           loadHistory();
           
           // 绑定UI事件
           micBtn.addEventListener('click', toggleRecording);
           copyBtn.addEventListener('click', copyCurrentResult);
           clearBtn.addEventListener('click', clearCurrent);
           saveBtn.addEventListener('click', saveCurrentToHistory);
           clearHistoryBtn.addEventListener('click', clearAllHistory);
           
           document.querySelectorAll('.lang-btn').forEach(btn => {
               btn.addEventListener('click', () => { setLanguage(btn.dataset.lang); });
           });
           
           // 等待 plus 环境就绪(关键修改)
           if (typeof plus !== 'undefined') {
               // 已存在直接初始化
               initSpeechModule();
           } else {
               // 监听 plusready 事件
               document.addEventListener('plusready', function onPlusReady() {
                   console.log('plusready 事件已触发');
                   document.removeEventListener('plusready', onPlusReady);
                   initSpeechModule();
               });
               
               // 设置超时提示
               setTimeout(() => {
                   if (typeof plus === 'undefined') {
                       statusText.textContent = '⚠️ 环境加载异常,请重启APP';
                       console.error('plus 环境在 3 秒后仍未就绪');
                   }
               }, 3000);
           }
       });
     function initSpeechModule() {
         // 更新UI状态,消除警告提示
         statusText.textContent = '🎤 点击麦克风开始录音';
         console.log('✅ HTML5+环境已就绪');
         
         // 检查语音模块是否已配置
         if (plus.speech) {
             console.log('plus.speech 可用');
         } else {
             console.warn('plus.speech 不可用,请检查 manifest.json 模块配置');
             statusText.textContent = '⚠️ 语音模块未配置';
         }
         
         // 可选:将语音识别函数挂载到全局,确保后续调用正常
         window.startSpeechRecognition = startRecording;
         window.stopSpeechRecognition = stopRecording;
     }
    </script>
</body>
</html>
相关推荐
byzh_rc9 小时前
[自然语言处理-入门] 语音识别
人工智能·自然语言处理·语音识别
做萤石二次开发的哈哈1 天前
如何调用接口向指定设备下发语音播放?
人工智能·语音识别
05大叔1 天前
生成式任务
人工智能·语音识别
limingade1 天前
做自己的小爱通话-AI手机电话外呼-从手机ivr应答走向手机ai应答
人工智能·语音识别
私人珍藏库1 天前
【Android】Solid文件管理器3.5.2 安卓文件管理器
android·人工智能·app·工具·软件·多功能
天上路人2 天前
采用AI 神经网络降噪技术降噪模组A-59F如何区分“人声”与“环境噪声”?
人工智能·语音识别
YF02112 天前
深入剖析 Kotlin 的高效之道与核心实战
android·kotlin·app
2601_954035052 天前
手机办公+AI搭配用法:多款实用工具深度测评
语音识别
2601_954035053 天前
告别手动记纪要:2026年5款录音转文字工具测评
语音识别