index_2.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. <template>
  2. <div class="voice-recorder">
  3. <button
  4. @mousedown="startRecording"
  5. @mouseup="stopRecording"
  6. @touchstart="startRecording"
  7. @touchend="stopRecording"
  8. :disabled="isRecording"
  9. >
  10. {{ isRecording ? '录音中...' : '按住说话' }}
  11. </button>
  12. <div class="volume-indicator" :style="{ width: volume + '%' }"></div>
  13. <div v-if="transcript" class="transcript">
  14. <h3>识别结果:</h3>
  15. <p>{{ transcript }}</p>
  16. </div>
  17. <div v-if="error" class="error">{{ error }}</div>
  18. </div>
  19. </template>
  20. <script>
  21. import { ref } from 'vue';
  22. export default {
  23. name: 'VoiceRecorder',
  24. setup() {
  25. const isRecording = ref(false);
  26. const volume = ref(0);
  27. const transcript = ref('');
  28. const error = ref('');
  29. let mediaStream = null;
  30. let audioContext = null;
  31. let analyser = null;
  32. let microphone = null;
  33. let scriptProcessor = null;
  34. let recognition = null;
  35. const startRecording = async () => {
  36. try {
  37. // 重置状态
  38. transcript.value = '';
  39. error.value = '';
  40. isRecording.value = true;
  41. volume.value = 0;
  42. // 获取麦克风权限
  43. mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
  44. // 设置音频分析
  45. audioContext = new (window.AudioContext || window.webkitAudioContext)();
  46. analyser = audioContext.createAnalyser();
  47. microphone = audioContext.createMediaStreamSource(mediaStream);
  48. scriptProcessor = audioContext.createScriptProcessor(2048, 1, 1);
  49. analyser.smoothingTimeConstant = 0.8;
  50. analyser.fftSize = 1024;
  51. microphone.connect(analyser);
  52. analyser.connect(scriptProcessor);
  53. scriptProcessor.connect(audioContext.destination);
  54. // 音量分析
  55. scriptProcessor.onaudioprocess = () => {
  56. const array = new Uint8Array(analyser.frequencyBinCount);
  57. analyser.getByteFrequencyData(array);
  58. let values = 0;
  59. const length = array.length;
  60. for (let i = 0; i < length; i++) {
  61. values += array[i];
  62. }
  63. const average = values / length;
  64. volume.value = Math.min(100, Math.max(0, average * 0.5));
  65. };
  66. // 语音识别
  67. if ('webkitSpeechRecognition' in window) {
  68. recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
  69. recognition.continuous = true;
  70. recognition.interimResults = true;
  71. recognition.onresult = (event) => {
  72. let interimTranscript = '';
  73. let finalTranscript = '';
  74. for (let i = event.resultIndex; i < event.results.length; i++) {
  75. const transcript = event.results[i][0].transcript;
  76. if (event.results[i].isFinal) {
  77. finalTranscript += transcript;
  78. } else {
  79. interimTranscript += transcript;
  80. }
  81. }
  82. transcript.value = finalTranscript || interimTranscript;
  83. };
  84. recognition.onerror = (event) => {
  85. console.log(event)
  86. error.value = `识别错误: ${event.error}`;
  87. };
  88. recognition.start();
  89. } else {
  90. error.value = '您的浏览器不支持语音识别';
  91. }
  92. } catch (err) {
  93. error.value = `错误: ${err.message}`;
  94. isRecording.value = false;
  95. }
  96. };
  97. const stopRecording = () => {
  98. isRecording.value = false;
  99. volume.value = 0;
  100. // 停止音频分析
  101. if (scriptProcessor) {
  102. scriptProcessor.disconnect();
  103. scriptProcessor = null;
  104. }
  105. if (microphone) {
  106. microphone.disconnect();
  107. microphone = null;
  108. }
  109. if (analyser) {
  110. analyser.disconnect();
  111. analyser = null;
  112. }
  113. // 停止媒体流
  114. if (mediaStream) {
  115. mediaStream.getTracks().forEach(track => track.stop());
  116. mediaStream = null;
  117. }
  118. // 停止语音识别
  119. if (recognition) {
  120. recognition.stop();
  121. recognition = null;
  122. }
  123. // 关闭音频上下文
  124. if (audioContext && audioContext.state !== 'closed') {
  125. audioContext.close();
  126. audioContext = null;
  127. }
  128. };
  129. return {
  130. isRecording,
  131. volume,
  132. transcript,
  133. error,
  134. startRecording,
  135. stopRecording
  136. };
  137. }
  138. };
  139. </script>
  140. <style scoped>
  141. .voice-recorder {
  142. max-width: 500px;
  143. margin: 0 auto;
  144. padding: 20px;
  145. text-align: center;
  146. }
  147. button {
  148. padding: 12px 24px;
  149. font-size: 16px;
  150. background-color: #4CAF50;
  151. color: white;
  152. border: none;
  153. border-radius: 4px;
  154. cursor: pointer;
  155. transition: background-color 0.3s;
  156. }
  157. button:disabled {
  158. background-color: #cccccc;
  159. cursor: not-allowed;
  160. }
  161. button:hover:not(:disabled) {
  162. background-color: #45a049;
  163. }
  164. .volume-indicator {
  165. height: 10px;
  166. background-color: #4CAF50;
  167. margin: 15px 0;
  168. transition: width 0.1s;
  169. border-radius: 5px;
  170. }
  171. .transcript {
  172. margin-top: 20px;
  173. padding: 15px;
  174. background-color: #f5f5f5;
  175. border-radius: 4px;
  176. text-align: left;
  177. }
  178. .error {
  179. color: #f44336;
  180. margin-top: 15px;
  181. }
  182. </style>