乐趣区

关于前端:vscode语音注释-让信息更丰富中

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

  1. play.js: github 地址
  2. 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.playpath 播放地址临时写死, 你会发现以后能够失常播放音频, 如果此时你认为播放性能 ok 了那就大错特错了。

三、node-wav-player 的外围原理

     node-wav-player的代码非常繁难, 远比我设想的简练, 上面都是我将代码化简后的样子, 是不是清新很多:

初始化的 play 办法, 只负责整顿数据, 真正播放是靠 _play 办法

_play 办法


    node child_process.spawn 用来启动一个新的 ’ 子过程 ’, 这个就是用来启动音频播放的 ’ 子过程 ’ 第一个参数是命令语句, 第二个参数是数组的话就是执行命令的地位。

     spawn的应用办法我演示一下:

     比如说 afplay 音频地址 就能够在 mac 下面播放声音。

监听报错

如果不是 code0亦或是 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": "此工程内录制语音正文"
      }
    ]
}
  1. editor/context外面定义了右键呼出的菜单栏的内容。
  2. when在什么生命周期激活这个性能定义, 这里抉择了当取得编辑焦点时。
  3. command定义了命令名称。
  4. 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
}
  1. src/html/index.html文件是咱们的录音的 h5 界面文件。
  2. 咱们定义上传为 ”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}` }))
    })
}
  1. 因为前端会应用 formData 的模式进行音频文件的传递, 所以须要这种接管形式。
  2. 将最初生成的这种 // voice_annotation_20220220153713111 音频正文字符串返回给前端, 不便前端间接放入用户剪切板。

end

     接下来是音频的录制与上传 (波及到 webRTC 相干常识) , 以及如何定义贮存音频文件的门路, 并且附加vscode 插件的公布, 这次就是这样, 心愿与你一起提高。

退出移动版