332 lines
12 KiB
HTML
332 lines
12 KiB
HTML
|
|
<!--<!DOCTYPE html>-->
|
|||
|
|
<!--<html lang="zh">-->
|
|||
|
|
<!--<head>-->
|
|||
|
|
<!-- <meta charset="UTF-8">-->
|
|||
|
|
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">-->
|
|||
|
|
<!-- <title>语音识别系统</title>-->
|
|||
|
|
<!-- <style>-->
|
|||
|
|
<!-- body {-->
|
|||
|
|
<!-- font-family: Arial, sans-serif;-->
|
|||
|
|
<!-- max-width: 800px;-->
|
|||
|
|
<!-- margin: 0 auto;-->
|
|||
|
|
<!-- padding: 20px;-->
|
|||
|
|
<!-- background-color: #f5f5f5;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- .container {-->
|
|||
|
|
<!-- background-color: white;-->
|
|||
|
|
<!-- padding: 30px;-->
|
|||
|
|
<!-- border-radius: 10px;-->
|
|||
|
|
<!-- box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- h1 {-->
|
|||
|
|
<!-- color: #333;-->
|
|||
|
|
<!-- text-align: center;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- .btn {-->
|
|||
|
|
<!-- background-color: #4CAF50;-->
|
|||
|
|
<!-- color: white;-->
|
|||
|
|
<!-- padding: 12px 24px;-->
|
|||
|
|
<!-- border: none;-->
|
|||
|
|
<!-- border-radius: 5px;-->
|
|||
|
|
<!-- cursor: pointer;-->
|
|||
|
|
<!-- font-size: 16px;-->
|
|||
|
|
<!-- margin: 10px;-->
|
|||
|
|
<!-- touch-action: manipulation;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- .btn:hover {-->
|
|||
|
|
<!-- background-color: #45a049;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- .btn:disabled {-->
|
|||
|
|
<!-- background-color: #cccccc;-->
|
|||
|
|
<!-- cursor: not-allowed;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- .record-btn {-->
|
|||
|
|
<!-- background-color: #f44336;-->
|
|||
|
|
<!-- padding: 15px 30px;-->
|
|||
|
|
<!-- font-size: 18px;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- .record-btn:hover {-->
|
|||
|
|
<!-- background-color: #d32f2f;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- .record-btn.recording {-->
|
|||
|
|
<!-- background-color: #ff9800;-->
|
|||
|
|
<!-- animation: pulse 1s infinite;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- @keyframes pulse {-->
|
|||
|
|
<!-- 0% {-->
|
|||
|
|
<!-- transform: scale(1);-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- 50% {-->
|
|||
|
|
<!-- transform: scale(1.05);-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- 100% {-->
|
|||
|
|
<!-- transform: scale(1);-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- .stream-result {-->
|
|||
|
|
<!-- margin-top: 20px;-->
|
|||
|
|
<!-- padding: 15px;-->
|
|||
|
|
<!-- border-radius: 5px;-->
|
|||
|
|
<!-- background-color: #e3f2fd;-->
|
|||
|
|
<!-- min-height: 50px;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- .recording-indicator {-->
|
|||
|
|
<!-- text-align: center;-->
|
|||
|
|
<!-- margin: 10px 0;-->
|
|||
|
|
<!-- font-weight: bold;-->
|
|||
|
|
<!-- color: #ff9800;-->
|
|||
|
|
<!-- display: none;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- .error-message {-->
|
|||
|
|
<!-- color: #f44336;-->
|
|||
|
|
<!-- background-color: #ffebee;-->
|
|||
|
|
<!-- padding: 10px;-->
|
|||
|
|
<!-- border-radius: 5px;-->
|
|||
|
|
<!-- margin: 10px 0;-->
|
|||
|
|
<!-- display: none;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- /* 移动端适配 */-->
|
|||
|
|
<!-- @media screen and (max-width: 768px) {-->
|
|||
|
|
<!-- body {-->
|
|||
|
|
<!-- padding: 10px;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- .container {-->
|
|||
|
|
<!-- padding: 15px;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- .btn {-->
|
|||
|
|
<!-- width: 100%;-->
|
|||
|
|
<!-- margin: 10px 0;-->
|
|||
|
|
<!-- padding: 15px;-->
|
|||
|
|
<!-- font-size: 18px;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- .record-btn {-->
|
|||
|
|
<!-- padding: 20px;-->
|
|||
|
|
<!-- font-size: 20px;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- @media screen and (max-width: 480px) {-->
|
|||
|
|
<!-- h1 {-->
|
|||
|
|
<!-- font-size: 24px;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- </style>-->
|
|||
|
|
<!--</head>-->
|
|||
|
|
<!--<body>-->
|
|||
|
|
<!--<div class="container">-->
|
|||
|
|
<!-- <h1>实时语音识别系统</h1>-->
|
|||
|
|
|
|||
|
|
<!-- <div>-->
|
|||
|
|
<!-- <p>按住下方按钮开始录音,识别结果将实时显示</p>-->
|
|||
|
|
<!-- <button class="btn record-btn" id="recordBtn">按住录音-->
|
|||
|
|
<!-- </button>-->
|
|||
|
|
<!-- <div class="error-message" id="errorMessage"></div>-->
|
|||
|
|
<!-- </div>-->
|
|||
|
|
<!-- <div class="recording-indicator" id="recordingIndicator">正在录音和识别...</div>-->
|
|||
|
|
<!-- <div class="stream-result" id="recordStreamResult" style="display: none;"></div>-->
|
|||
|
|
|
|||
|
|
<!--</div>-->
|
|||
|
|
|
|||
|
|
<!--<script>-->
|
|||
|
|
<!-- let websocket;-->
|
|||
|
|
<!-- const recordButton = document.getElementById('recordBtn');-->
|
|||
|
|
<!-- const status = document.getElementById('recordingIndicator');-->
|
|||
|
|
<!-- const recordStreamResult = document.getElementById('recordStreamResult');-->
|
|||
|
|
<!-- let mediaRecorder;-->
|
|||
|
|
<!-- let audioChunks = [];-->
|
|||
|
|
<!-- let isRecording = false;-->
|
|||
|
|
<!-- let isRecorderInitialized = false;-->
|
|||
|
|
<!-- // connectWebSocket();-->
|
|||
|
|
|
|||
|
|
<!-- // 初始化录音功能-->
|
|||
|
|
<!-- async function initRecorder() {-->
|
|||
|
|
<!-- if (isRecorderInitialized) return; // 避免重复初始化-->
|
|||
|
|
|
|||
|
|
<!-- try {-->
|
|||
|
|
<!-- const stream = await navigator.mediaDevices.getUserMedia({audio: true});-->
|
|||
|
|
<!-- mediaRecorder = new MediaRecorder(stream);-->
|
|||
|
|
<!-- isRecorderInitialized = true;-->
|
|||
|
|
|
|||
|
|
<!-- // 监听录制的数据-->
|
|||
|
|
<!-- mediaRecorder.addEventListener('dataavailable', event => {-->
|
|||
|
|
<!-- audioChunks.push(event.data);-->
|
|||
|
|
<!-- });-->
|
|||
|
|
|
|||
|
|
<!-- // 录音停止后处理数据-->
|
|||
|
|
<!-- mediaRecorder.addEventListener('stop', () => {-->
|
|||
|
|
<!-- status.textContent = '正在上传...';-->
|
|||
|
|
|
|||
|
|
<!-- // 将音频数据组合成 Blob-->
|
|||
|
|
<!-- const audioBlob = new Blob(audioChunks, {type: 'audio/wav'}); // 可根据需要改为 audio/wav 等-->
|
|||
|
|
<!-- audioChunks = []; // 清空缓存-->
|
|||
|
|
<!-- // websocket.send(audioBlob)-->
|
|||
|
|
<!-- // 发送到后端-->
|
|||
|
|
<!-- uploadAudioToBackend(audioBlob);-->
|
|||
|
|
<!-- });-->
|
|||
|
|
|
|||
|
|
<!-- } catch (err) {-->
|
|||
|
|
<!-- console.error('无法访问麦克风:', err);-->
|
|||
|
|
<!-- status.textContent = '麦克风访问被拒绝或不可用';-->
|
|||
|
|
<!-- showError('无法访问麦克风,请检查权限设置');-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- // 开始录音-->
|
|||
|
|
<!-- function startRecording() {-->
|
|||
|
|
<!-- // 如果录音器尚未初始化,则初始化-->
|
|||
|
|
<!-- if (!isRecorderInitialized) {-->
|
|||
|
|
<!-- initRecorder().then(() => {-->
|
|||
|
|
<!-- // 初始化成功后开始录音-->
|
|||
|
|
<!-- if (isRecorderInitialized && mediaRecorder && mediaRecorder.state !== 'recording') {-->
|
|||
|
|
<!-- audioChunks = [];-->
|
|||
|
|
<!-- mediaRecorder.start();-->
|
|||
|
|
<!-- isRecording = true;-->
|
|||
|
|
<!-- recordButton.textContent = '正在录音...';-->
|
|||
|
|
<!-- status.textContent = '录音中...';-->
|
|||
|
|
<!-- status.style.display = 'block';-->
|
|||
|
|
<!-- recordStreamResult.style.display = 'block';-->
|
|||
|
|
<!-- recordStreamResult.innerHTML = '<h3>实时识别结果:</h3><p>正在识别...</p>';-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- }).catch(err => {-->
|
|||
|
|
<!-- console.error('初始化失败:', err);-->
|
|||
|
|
<!-- status.textContent = '初始化录音失败';-->
|
|||
|
|
<!-- });-->
|
|||
|
|
<!-- } else if (mediaRecorder && mediaRecorder.state !== 'recording') {-->
|
|||
|
|
<!-- audioChunks = [];-->
|
|||
|
|
<!-- mediaRecorder.start();-->
|
|||
|
|
<!-- isRecording = true;-->
|
|||
|
|
<!-- recordButton.textContent = '正在录音...';-->
|
|||
|
|
<!-- status.textContent = '录音中...';-->
|
|||
|
|
<!-- status.style.display = 'block';-->
|
|||
|
|
<!-- recordStreamResult.style.display = 'block';-->
|
|||
|
|
<!-- recordStreamResult.innerHTML = '<h3>实时识别结果:</h3><p>正在识别...</p>';-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- // 停止录音-->
|
|||
|
|
<!-- function stopRecording() {-->
|
|||
|
|
<!-- if (mediaRecorder && mediaRecorder.state === 'recording') {-->
|
|||
|
|
<!-- mediaRecorder.stop();-->
|
|||
|
|
<!-- isRecording = false;-->
|
|||
|
|
<!-- recordButton.textContent = '按住说话';-->
|
|||
|
|
<!-- status.textContent = '已停止';-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- // 显示错误信息-->
|
|||
|
|
<!-- function showError(message) {-->
|
|||
|
|
<!-- const errorElement = document.getElementById('errorMessage');-->
|
|||
|
|
<!-- errorElement.textContent = message;-->
|
|||
|
|
<!-- errorElement.style.display = 'block';-->
|
|||
|
|
<!-- setTimeout(() => {-->
|
|||
|
|
<!-- errorElement.style.display = 'none';-->
|
|||
|
|
<!-- }, 5000); // 5秒后隐藏错误信息-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- // 上传音频到后端-->
|
|||
|
|
<!-- async function uploadAudioToBackend(blob) {-->
|
|||
|
|
<!-- const formData = new FormData();-->
|
|||
|
|
<!-- formData.append('file', blob, 'recording.wav'); // 文件名可自定义-->
|
|||
|
|
|
|||
|
|
<!-- try {-->
|
|||
|
|
<!-- const response = await fetch('http://192.168.1.2:5000/transcribe', {-->
|
|||
|
|
<!-- method: 'POST',-->
|
|||
|
|
<!-- body: formData-->
|
|||
|
|
<!-- });-->
|
|||
|
|
|
|||
|
|
<!-- if (response.ok) {-->
|
|||
|
|
<!-- const result = await response.json();-->
|
|||
|
|
<!-- console.log('上传成功:', result);-->
|
|||
|
|
<!-- status.textContent = '上传成功!';-->
|
|||
|
|
<!-- recordStreamResult.innerHTML += `<p>${result.result}</p>`;-->
|
|||
|
|
<!-- } else {-->
|
|||
|
|
<!-- status.textContent = '上传失败';-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- } catch (error) {-->
|
|||
|
|
<!-- console.error('上传出错:', error);-->
|
|||
|
|
<!-- status.textContent = '上传失败:网络错误';-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- // 初始化事件监听-->
|
|||
|
|
<!-- recordButton.addEventListener('mousedown', () => {-->
|
|||
|
|
<!-- if (!isRecording) startRecording();-->
|
|||
|
|
<!-- });-->
|
|||
|
|
|
|||
|
|
<!-- recordButton.addEventListener('mouseup', () => {-->
|
|||
|
|
<!-- if (isRecording) stopRecording();-->
|
|||
|
|
<!-- });-->
|
|||
|
|
|
|||
|
|
<!-- // 支持移动端(touch事件)-->
|
|||
|
|
<!-- recordButton.addEventListener('touchstart', (e) => {-->
|
|||
|
|
<!-- e.preventDefault(); // 防止默认行为-->
|
|||
|
|
<!-- if (!isRecording) startRecording();-->
|
|||
|
|
<!-- });-->
|
|||
|
|
|
|||
|
|
<!-- recordButton.addEventListener('touchend', (e) => {-->
|
|||
|
|
<!-- e.preventDefault();-->
|
|||
|
|
<!-- if (isRecording) stopRecording();-->
|
|||
|
|
<!-- });-->
|
|||
|
|
|
|||
|
|
<!-- // 建立WebSocket连接-->
|
|||
|
|
<!-- function connectWebSocket() {-->
|
|||
|
|
<!-- // 处理不同协议的情况-->
|
|||
|
|
<!-- let wsUrl = "ws://localhost:5000/ws/stream_transcribe";-->
|
|||
|
|
<!-- if (window.location.protocol === "https:") {-->
|
|||
|
|
<!-- wsUrl = "wss://localhost:5000/ws/stream_transcribe";-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!-- websocket = new WebSocket(wsUrl);-->
|
|||
|
|
|
|||
|
|
<!-- websocket.onopen = function (event) {-->
|
|||
|
|
<!-- console.log("WebSocket连接已建立");-->
|
|||
|
|
<!-- };-->
|
|||
|
|
|
|||
|
|
<!-- websocket.onmessage = function (event) {-->
|
|||
|
|
<!-- // 更新识别结果-->
|
|||
|
|
<!-- const currentContent = recordStreamResult.innerHTML;-->
|
|||
|
|
<!-- // 如果是第一次收到结果,替换"正在识别..."提示-->
|
|||
|
|
<!-- if (currentContent.includes("正在识别...")) {-->
|
|||
|
|
<!-- recordStreamResult.innerHTML = '<h3>实时识别结果:</h3><p>' + event.data + '</p>';-->
|
|||
|
|
<!-- } else {-->
|
|||
|
|
<!-- // 否则追加结果-->
|
|||
|
|
<!-- const newContent = currentContent.replace('</p>', '') + event.data + '</p>';-->
|
|||
|
|
<!-- recordStreamResult.innerHTML = newContent;-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- };-->
|
|||
|
|
|
|||
|
|
<!-- websocket.onerror = function (error) {-->
|
|||
|
|
<!-- console.log("WebSocket错误:", error);-->
|
|||
|
|
<!-- showError('连接服务器失败,请检查服务器是否运行');-->
|
|||
|
|
<!-- recordStreamResult.innerHTML = '<h3>实时识别结果:</h3><p>识别出错,请查看控制台了解详情</p>';-->
|
|||
|
|
<!-- };-->
|
|||
|
|
|
|||
|
|
<!-- websocket.onclose = function (event) {-->
|
|||
|
|
<!-- console.log("WebSocket连接已关闭");-->
|
|||
|
|
<!-- if (event.wasClean) {-->
|
|||
|
|
<!-- console.log(`连接关闭,代码=${event.code},原因=${event.reason}`);-->
|
|||
|
|
<!-- } else {-->
|
|||
|
|
<!-- console.log('连接意外中断');-->
|
|||
|
|
<!-- }-->
|
|||
|
|
<!-- };-->
|
|||
|
|
<!-- }-->
|
|||
|
|
|
|||
|
|
<!--</script>-->
|
|||
|
|
<!--</body>-->
|
|||
|
|
<!--</html>-->
|