├── README.md ├── index.html ├── script.js └── styles.css /README.md: -------------------------------------------------------------------------------- 1 | # converting-from-speech-to-text-with-javascript 2 | 本教程中,我们将尝试使用Web Speech API,这是一个非常强大的浏览器接口,可以用来记录语音并将其转换为文本,同样的,也可以用来朗读字符串。 3 | 4 | 接下来进入正题,这个App应当具有以下几个功能: 5 | - 通过语音录入或者键盘输入的方式保存笔记; 6 | - 将笔记保存到本地; 7 | - 显示历史笔记并可以通过语音朗读笔记; 8 | 9 | 此App无需使用任何花哨的依赖,只需使用jQuery来进行简单的DOM操作,以及Shoelace实现简单的样式,并使用CDN直接将它们包含到文件中。 10 | 11 | 代码解析:[http://www.icoder.top/blog/?p=675](http://www.icoder.top/blog/?p=675) 12 | ![](http://wx4.sinaimg.cn/large/005NrfBdly1fjuw65jl2hj30ll0ohq3e.jpg) 13 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 语音控制笔记应用 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |

语音控制笔记应用

17 |

可通过语音记笔记的小应用

18 | 19 |

您的浏览器不支持Web Speech API,请使用Chrome浏览器打开此应用。

20 | 21 |
22 |

添加新笔记

23 |
24 | 25 |
26 | 27 | 28 | 29 |

按下 开始识别语音 按钮并按提示给予相关权限。

30 | 31 |

历史笔记

32 | 37 | 38 |
39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | try { 2 | var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; 3 | var recognition = new SpeechRecognition(); 4 | } 5 | catch(e) { 6 | console.error(e); 7 | $('.no-browser-support').show(); 8 | $('.app').hide(); 9 | } 10 | 11 | 12 | var noteTextarea = $('#note-textarea'); 13 | var instructions = $('#recording-instructions'); 14 | var notesList = $('ul#notes'); 15 | 16 | var noteContent = ''; 17 | 18 | //获取并显示之前会话中保存的笔记 19 | var notes = getAllNotes(); 20 | renderNotes(notes); 21 | 22 | 23 | 24 | /*----------------------------- 25 | 语音识别 26 | ------------------------------*/ 27 | 28 | recognition.continuous = true; 29 | 30 | recognition.onresult = function(event) { 31 | 32 | // event 是一个SpeechRecognitionEvent 对象 33 | // 保存了所有历史捕获对象 34 | // 我们只取当前的内容 35 | var current = event.resultIndex; 36 | 37 | // 获取此前所说话的记录 38 | var transcript = event.results[current][0].transcript; 39 | 40 | // 将当前记录添加到笔记内容中 41 | // 解决安卓设备的bug 42 | var mobileRepeatBug = (current == 1 && transcript == event.results[0][0].transcript); 43 | 44 | if(!mobileRepeatBug) { 45 | noteContent += transcript; 46 | noteTextarea.val(noteContent); 47 | } 48 | }; 49 | 50 | recognition.onstart = function() { 51 | instructions.text('语音识别功能激活!请对着麦克风讲话。'); 52 | } 53 | 54 | recognition.onspeechend = function() { 55 | instructions.text('长时间未说话,已自动关闭录音。'); 56 | } 57 | 58 | recognition.onerror = function(event) { 59 | if(event.error == 'no-speech') { 60 | instructions.text('未检测到语音,请再试一次。'); 61 | }; 62 | } 63 | 64 | 65 | 66 | /*----------------------------- 67 | 应用功能按钮与输入 68 | ------------------------------*/ 69 | 70 | $('#start-record-btn').on('click', function(e) { 71 | if (noteContent.length) { 72 | noteContent += ' '; 73 | } 74 | recognition.start(); 75 | }); 76 | 77 | 78 | $('#pause-record-btn').on('click', function(e) { 79 | recognition.stop(); 80 | instructions.text('语音识别暂停。'); 81 | }); 82 | 83 | // 同步文本框文本与noteContent变量 84 | noteTextarea.on('input', function() { 85 | noteContent = $(this).val(); 86 | }) 87 | 88 | $('#save-note-btn').on('click', function(e) { 89 | recognition.stop(); 90 | 91 | if(!noteContent.length) { 92 | instructions.text('Could not save empty note. Please add a message to your note.'); 93 | } 94 | else { 95 | // 保存笔记到localStorage 96 | saveNote(new Date().toLocaleString(), noteContent); 97 | 98 | // 重置变量,更新界面 99 | noteContent = ''; 100 | renderNotes(getAllNotes()); 101 | noteTextarea.val(''); 102 | instructions.text('笔记保存成功。'); 103 | } 104 | 105 | }) 106 | 107 | 108 | notesList.on('click', function(e) { 109 | e.preventDefault(); 110 | var target = $(e.target); 111 | 112 | // 读出所选笔记 113 | if(target.hasClass('listen-note')) { 114 | var content = target.closest('.note').find('.content').text(); 115 | readOutLoud(content); 116 | } 117 | 118 | // 删除笔记 119 | if(target.hasClass('delete-note')) { 120 | var dateTime = target.siblings('.date').text(); 121 | deleteNote(dateTime); 122 | target.closest('.note').remove(); 123 | } 124 | }); 125 | 126 | 127 | 128 | /*----------------------------- 129 | 语音合成 130 | ------------------------------*/ 131 | 132 | function readOutLoud(message) { 133 | var speech = new SpeechSynthesisUtterance(); 134 | 135 | // 设置朗读内容和属性 136 | speech.text = message; 137 | speech.volume = 1; 138 | speech.rate = 1; 139 | speech.pitch = 1; 140 | 141 | window.speechSynthesis.speak(speech); 142 | } 143 | 144 | 145 | 146 | /*----------------------------- 147 | 所需函数 148 | ------------------------------*/ 149 | 150 | function renderNotes(notes) { 151 | var html = ''; 152 | if(notes.length) { 153 | notes.forEach(function(note) { 154 | html+= `
  • 155 |

    156 | ${note.date} 157 | 读出笔记 158 | 删除 159 |

    160 |

    ${note.content}

    161 |
  • `; 162 | }); 163 | } 164 | else { 165 | html = '
  • 暂无笔记

  • '; 166 | } 167 | notesList.html(html); 168 | } 169 | 170 | 171 | function saveNote(dateTime, content) { 172 | localStorage.setItem('note-' + dateTime, content); 173 | } 174 | 175 | 176 | function getAllNotes() { 177 | var notes = []; 178 | var key; 179 | for (var i = 0; i < localStorage.length; i++) { 180 | key = localStorage.key(i); 181 | 182 | if(key.substring(0,5) == 'note-') { 183 | notes.push({ 184 | date: key.replace('note-',''), 185 | content: localStorage.getItem(localStorage.key(i)) 186 | }); 187 | } 188 | } 189 | return notes; 190 | } 191 | 192 | 193 | function deleteNote(dateTime) { 194 | localStorage.removeItem('note-' + dateTime); 195 | } 196 | 197 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | ul { 2 | list-style: none; 3 | padding: 0; 4 | } 5 | 6 | p { 7 | color: #444; 8 | } 9 | 10 | button:focus { 11 | outline: 0; 12 | } 13 | 14 | .container { 15 | max-width: 700px; 16 | margin: 0 auto; 17 | padding: 100px 50px; 18 | text-align: center; 19 | } 20 | 21 | .container h1 { 22 | margin-bottom: 20px; 23 | } 24 | 25 | .page-description { 26 | font-size: 1.1rem; 27 | margin: 0 auto; 28 | } 29 | 30 | .tz-link { 31 | font-size: 1em; 32 | color: #1da7da; 33 | text-decoration: none; 34 | } 35 | 36 | .no-browser-support { 37 | display: none; 38 | font-size: 1.2rem; 39 | color: #e64427; 40 | margin-top: 35px; 41 | } 42 | 43 | .app { 44 | margin: 40px auto; 45 | } 46 | 47 | #note-textarea { 48 | margin: 20px 0; 49 | } 50 | 51 | #recording-instructions { 52 | margin: 15px auto 60px; 53 | } 54 | 55 | #notes { 56 | padding-top: 20px; 57 | } 58 | 59 | .note .header { 60 | font-size: 0.9em; 61 | color: #888; 62 | margin-bottom: 10px; 63 | } 64 | 65 | .note .delete-note, 66 | .note .listen-note { 67 | text-decoration: none; 68 | margin-left: 15px; 69 | } 70 | 71 | .note .content { 72 | margin-bottom: 40px; 73 | } 74 | 75 | @media (max-width: 768px) { 76 | .container { 77 | padding: 50px 25px; 78 | } 79 | 80 | button { 81 | margin-bottom: 10px; 82 | } 83 | 84 | } --------------------------------------------------------------------------------