vscode语音注释, 让信息更丰富(中)
共 11711字,需浏览 24分钟
·
2022-03-04 01:35
作者:lulu_up
来源:SegmentFault 思否社区
前言
上一篇我们做完了最基础的功能"识别语音注释", 本篇我们要一起开发语音'播放'等相关功能。
一、mac电脑获取音频文件(后期有坑,到时会填)
要开发音频'播放'功能, 那我们首先需要一份音频文件, 上网找mp3文件下载多数都需要注册, 那索性直接使用电脑自带的录音功能生成mp3就好了,这种方式有bug后期我们再解决。
这里演示mac电脑的录音功能:
第一步: 找到软件:
第二步: 将录制好的音频分享到某个app上
m4a文件: (我们可以手动修改后缀名)
“m4a是MPEG-4音频标准文件的扩展名,与大家熟悉的mp3一样,也是一种音频格式文件,苹果公司用此命名来区分mpeg4视频。”
二、播放音频插件的选择
这里的播放指的是"鼠标悬停"即可播放音频, 那么就不能是web意义上的播放, 因为我们无法利用audio标签实现, vscode是基于Electron开发的, 所以其内的插件也是处于node环境里的, 那我们就可以利用node将音频流输入到音频输出设备从而达到播放的目的。
play.js: github地址
node-wav-player: github地址
https://github.com/futomi/node-wav-player
安装走起:
yarn add
使用播放:(这里暂时使用绝对地址)
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('播放中 ...')
}
}
})
三、node-wav-player的核心原理
初始化的play方法, 只负责整理数据, 真正播放是靠_play方法
_play方法
监听报错
如何终止播放
四、'何处'录制音频?
我们做这个插件的体验宗旨就是方便快捷, 所以录制音频的"链路一定要短", 最好用户一键就可以进行'录制', 一键就可以生成音频注释。
五、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>`;
局限性
六、右键录音
我参考了一些音乐播放软件, 发现大家播放功能几乎都是通过打开h5页面实现的, 那咱们的录音功能也可以尝试这种方式, 原理当然是利用node启动一个web服务, 然后帮助用户打开类似http://localhost:8830/这种地址, 这个地址返回给用户一段html, 这里就是录音的地方。
定义右键导航
"contributes": {
"menus": {
"editor/context": [
{
"when": "editorFocus",
"command": "vn.recording",
"group": "navigation"
}
]
},
"commands": [
{
"command": "vn.recording",
"title": "此工程内录制语音注释"
}
]
}
editor/context里面定义了右键呼出的菜单栏的内容。
when在什么生命周期激活这个功能定义, 这里选择了当获得编辑焦点时。
command定义了命令名称。
title就是显示在菜单中的名称。
打开h5页面
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() { }
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
因为咱们的插件要尽可能的小, 这里当然不使用任何框架, 手撸原生即可:
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音频注释字符串返回给前端, 方便前端直接放入用户剪切板。