vscode語音注釋, 讓信息更豐富(中)
作者:lulu_up
來源:SegmentFault 思否社區(qū)
前言
上一篇我們做完了最基礎(chǔ)的功能"識別語音注釋", 本篇我們要一起開發(fā)語音'播放'等相關(guān)功能。
一、mac電腦獲取音頻文件(后期有坑,到時會填)
要開發(fā)音頻'播放'功能, 那我們首先需要一份音頻文件, 上網(wǎng)找mp3文件下載多數(shù)都需要注冊, 那索性直接使用電腦自帶的錄音功能生成mp3就好了,這種方式有bug后期我們再解決。
這里演示mac電腦的錄音功能:
第一步: 找到軟件:

第二步: 將錄制好的音頻分享到某個app上



m4a文件: (我們可以手動修改后綴名)
“m4a是MPEG-4音頻標準文件的擴展名,與大家熟悉的mp3一樣,也是一種音頻格式文件,蘋果公司用此命名來區(qū)分mpeg4視頻。”
二、播放音頻插件的選擇
這里的播放指的是"鼠標懸停"即可播放音頻, 那么就不能是web意義上的播放, 因為我們無法利用audio標簽實現(xiàn), vscode是基于Electron開發(fā)的, 所以其內(nèi)的插件也是處于node環(huán)境里的, 那我們就可以利用node將音頻流輸入到音頻輸出設(shè)備從而達到播放的目的。
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方法, 只負責整理數(shù)據(jù), 真正播放是靠_play方法

_play方法


監(jiān)聽報錯

如何終止播放

四、'何處'錄制音頻?
我們做這個插件的體驗宗旨就是方便快捷, 所以錄制音頻的"鏈路一定要短", 最好用戶一鍵就可以進行'錄制', 一鍵就可以生成音頻注釋。
五、webview
創(chuàng)建webview
vscode內(nèi)部提供了webview的能力, 看到它的第一眼我就'心動'了, 我們使用下面的代碼就可以增加一個webview頁。
const panel = vscode.window.createWebviewPanel(
"類型xxx",
"標題xxx",
vscode.ViewColumn.One,
{}
);

定義內(nèi)容
需要用到panel.webview.html屬性, 類似innerHTML:
const panel = vscode.window.createWebviewPanel(
"類型xxx",
"標題xxx",
vscode.ViewColumn.One,
{}
);
panel.webview.html = `<div>123</div>`;
局限性
六、右鍵錄音
我參考了一些音樂播放軟件, 發(fā)現(xiàn)大家播放功能幾乎都是通過打開h5頁面實現(xiàn)的, 那咱們的錄音功能也可以嘗試這種方式, 原理當然是利用node啟動一個web服務(wù), 然后幫助用戶打開類似http://localhost:8830/這種地址, 這個地址返回給用戶一段html, 這里就是錄音的地方。
定義右鍵導航
"contributes": {
"menus": {
"editor/context": [
{
"when": "editorFocus",
"command": "vn.recording",
"group": "navigation"
}
]
},
"commands": [
{
"command": "vn.recording",
"title": "此工程內(nèi)錄制語音注釋"
}
]
}
editor/context里面定義了右鍵呼出的菜單欄的內(nèi)容。
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光啟動不行, 現(xiàn)在開始定義接口能力, 在server.ts內(nèi):
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

評論
圖片
表情

