方法一. 浏览器语音版 先期配置
先在电脑上安装八云软件 ,原因是谷歌浏览器语音识别,,谷歌浏览器的服务端在国外,因此,需要安装翻墙软件
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.获取百度语音识别参数
-
打开浏览器,访问 百度智能云控制台(console.bce.baidu.com/ai/),如果没有账号,需要先注册并完成实名认证。
在控制台中找到"语音技术"或"语音识别"服务,创建一个新应用。
创建成功后,在应用详情页就能看到你需要的三个关键信息:AppID、API Key、Secret Key。请把它们记录下来。
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>