vscode语音正文, 让信息更丰盛 (中)
前言
上一篇咱们做完了最根底的性能"辨认语音正文", 本篇咱们要一起开发语音'播放'等相干性能。
一、mac电脑获取音频文件(前期有坑,到时会填)
要开发音频'播放'性能, 那咱们首先须要一份音频文件, 上网找mp3
文件下载少数都须要注册, 那索性间接应用电脑自带的录音性能生成mp3
就好了,这种形式有bug前期咱们再解决。
这里演示mac
电脑的录音性能:
第一步: 找到软件:
第二步: 将录制好的音频分享到某个app上
m4a
文件: (咱们能够手动批改后缀名)
“m4a是MPEG-4音频标准文件的扩展名,与大家相熟的mp3一样,也是一种音频格式文件,苹果公司用此命名来辨别mpeg4视频。”
二、播放音频插件的抉择
这里的播放指的是"鼠标悬停"即可播放音频, 那么就不能是web
意义上的播放, 因为咱们无奈利用audio
标签实现, vscode
是基于Electron
开发的, 所以其内的插件也是处于node
环境里的, 那咱们就能够利用node
将音频流
输出到音频输出设备
从而达到播放的目标。
在网上用node
播放音频的插件不多, 在这里举荐其中两款:play.js
&node-wav-player
- play.js: github地址
- node-wav-player: github地址
play.js
有个瑕疵, 就是无奈暂停播放
这个问题可能是开发者无法忍受的, 所以我最终抉择了node-wav-player
, 别看它叫wav
播放器, mp3
也是能播的。
装置走起:
yarn add
应用播放:(这里临时应用相对地址)
在`hover.ts文件内增加:
import * as vscode from 'vscode';import * as player from 'node-wav-player';import { getVoiceAnnotationDirPath, targetName, testTargetReg } from './util'let stopFn: () => void;function playVoice(id: string) { const voiceAnnotationDirPath = getVoiceAnnotationDirPath() if (voiceAnnotationDirPath) { player.play({ path: `/xxx/xxxx/xx.mp3` }).catch(() => { vscode.window.showErrorMessage('播放失败') }) stopFn = () => { player.stop() } }}export default vscode.languages.registerHoverProvider("*", { provideHover(documennt: vscode.TextDocument, position: vscode.Position) { stopFn?.() const word = documennt.getText(documennt.getWordRangeAtPosition(position)); const testTargetRes = testTargetReg.exec(word); if (testTargetRes) { playVoice(testTargetRes[1]) return new vscode.Hover('播放中 ...') } }})
player.play
的 path
播放地址临时写死, 你会发现以后能够失常播放音频, 如果此时你认为播放性能ok了那就大错特错了。
三、node-wav-player的外围原理
node-wav-player
的代码非常繁难, 远比我设想的简练, 上面都是我将代码化简后的样子, 是不是清新很多:
初始化的play
办法, 只负责整顿数据, 真正播放是靠_play
办法
_play办法
node child_process.spawn
用来启动一个新的'子过程', 这个就是用来启动音频播放的'子过程' 第一个参数是命令语句, 第二个参数是数组的话就是执行命令的地位。
spawn
的应用办法我演示一下:
比如说 afplay 音频地址
就能够在mac下面播放声音。
监听报错
如果不是code
为 0
亦或是this._called_stop === true
人为手动调用进行的状况, 则报错"播放失败", 如果500
毫秒内并未报错, 则removeAllListeners("close")
移除敞开的监听。
如何终止播放
在stop
办法中间接kill
掉'子过程'即可:
四、'何处'录制音频?
咱们做这个插件的体验主旨就是方便快捷, 所以录制音频的"链路肯定要短", 最好用户一键就能够进行'录制', 一键就能够生成音频正文。
我最开始的想法是尽可能在vscode
外部实现, 也就是不要新开一个 h5页面
, 让用户的不要越出vscode
这个层级。
录制音频就不能像播放音频一样, 因为录制波及到录音完结后的重播, 音频文件的保留, 以及开始+暂停+完结
等等状态的操作, 所以最好要有个操作界面而不是靠node
单打独斗。
五、webview
创立webview
vscode
外部提供了webview
的能力, 看到它的第一眼我就'心动'了, 咱们应用上面的代码就能够减少一个webview
页。
const panel = vscode.window.createWebviewPanel( "类型xxx", "题目xxx", vscode.ViewColumn.One, {} );
定义内容
须要用到panel.webview.html
属性, 相似innerHTML
:
const panel = vscode.window.createWebviewPanel( "类型xxx", "题目xxx", vscode.ViewColumn.One, {} ); panel.webview.html = `<div>123</div>`;
局限性
浏览了官网文档也查看了ts类型文件, 然而遗憾没能发现为音频受权的办法, 所以导致无奈应用audio
标签来采集到用户的音频信息, 只能抉择换种形式实现。
六、右键录音
我参考了一些音乐播放软件, 发现大家播放性能简直都是通过关上h5
页面实现的, 那咱们的录音性能也能够尝试这种形式, 原理当然是利用node
启动一个web服务
, 而后帮忙用户关上相似http://localhost:8830/
这种地址, 这个地址返回给用户一段html
, 这里就是录音的中央。
定义右键导航
package.json
文件内减少
"contributes": { "menus": { "editor/context": [ { "when": "editorFocus", "command": "vn.recording", "group": "navigation" } ] }, "commands": [ { "command": "vn.recording", "title": "此工程内录制语音正文" } ]}
editor/context
外面定义了右键呼出的菜单栏的内容。when
在什么生命周期激活这个性能定义, 这里抉择了当取得编辑焦点时。command
定义了命令名称。title
就是显示在菜单中的名称。
关上h5
页面
extension.ts
新增navigation
模块:
import * as vscode from 'vscode';import hover from './hover';import initVoiceAnnotationStyle from './initVoiceAnnotationStyle';import navigation from './navigation' // 新增export function activate(context: vscode.ExtensionContext) { initVoiceAnnotationStyle() context.subscriptions.push(hover); context.subscriptions.push(navigation); // 新增 context.subscriptions.push( vscode.window.onDidChangeActiveTextEditor(() => { initVoiceAnnotationStyle() }) )}export function deactivate() { }
navigation,ts
文件, 负责启动服务并且关上浏览器
跳到对应页面:
yarn add open
import * as vscode from 'vscode';import * as open from 'open';import server from './server';import { serverProt } from './util';import { Server } from 'http';let serverObj: Server;export default vscode.commands.registerCommand("vn.recording", function () { const voiceAnnotationDirPath = getVoiceAnnotationDirPath() if (voiceAnnotationDirPath) { if (!serverObj) { serverObj = server() } open(`http://127.0.0.1:${serverProt()}`); }})
启动server
因为咱们的插件要尽可能的小, 这里当然不应用任何框架, 手撸原生即可:
新建server.ts
文件:
import * as fs from 'fs';import * as http from 'http';import * as path from 'path';import * as url from 'url';import { targetName, getVoiceID } from './util';export default function () { const server = http.createServer(function ( req: http.IncomingMessage, res: http.ServerResponse) { res.write(123) res.end() }).listen(8830) return server}
七、返回页面, 定义api
server
光启动不行, 当初开始定义接口能力, 在server.ts
内:
import * as fs from 'fs';import * as http from 'http';import * as path from 'path';import * as url from 'url';import { serverProt, targetName, getVoiceID } from './util';const temp = fs.readFileSync( path.join(__dirname, "./index.html"))export default function () { const server = http.createServer(function (req: http.IncomingMessage, res: http.ServerResponse) { if (req.method === "POST" && req.url === "/create_voice") { createVoice(req, res) }else { res.writeHead(200, { "content-type": 'text/html;charset="fs.unwatchFile-8"' }) res.write(temp) res.end() } }).listen(serverProt()) return server}
src/html/index.html
文件是咱们的录音的h5
界面文件。- 咱们定义上传为"POST"申请, 并且申请地址为
/create_voice
。
createVoice
办法
此办法用于接管音频文件, 并将音频文件保留在用户指定的地位:
function createVoice(req: http.IncomingMessage, res: http.ServerResponse) { let data: Uint8Array[] = []; req.on("data", (chunck: Uint8Array) => { data.push(chunck) }) req.on("end", () => { let buffer = Buffer.concat(data); const voiceId = getVoiceID() try { fs.writeFileSync(`保留音频的地位`, buffer, ) } catch (error) { res.writeHead(200) res.end() } res.writeHead(200) res.end(JSON.stringify({ voiceId: `// ${targetName}_${voiceId}` })) })}
- 因为前端会应用
formData
的模式进行音频文件的传递, 所以须要这种接管形式。 - 将最初生成的这种
// voice_annotation_20220220153713111
音频正文字符串返回给前端, 不便前端间接放入用户剪切板。
end
接下来是音频的录制与上传(波及到webRTC相干常识) , 以及如何定义贮存音频文件的门路, 并且附加vscode
插件的公布, 这次就是这样, 心愿与你一起提高。