<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          vscode語音注釋, 讓信息更豐富(中)

          共 11711字,需瀏覽 24分鐘

           ·

          2022-03-04 01:35

          作者: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è)備從而達到播放的目的。


          在網(wǎng)上用node播放音頻的插件不多, 在這里推薦其中兩款:play.js&node-wav-player

          • play.js: github地址

          https://github.com/Marak/play.js
          • node-wav-player: github地址

          https://github.com/futomi/node-wav-player


          play.js有個瑕疵, 就是無法暫停播放這個問題可能是開發(fā)者無法忍受的, 所以我最終選擇了node-wav-player, 別看它叫wav播放器, mp3也是能播的。

          安裝走起:

          yarn add

          使用播放:(這里暫時使用絕對地址)

          在`hover.ts文件內(nèi)添加:

          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 播放地址暫時寫死, 你會發(fā)現(xiàn)當前可以正常播放音頻, 如果此時你認為播放功能ok了那就大錯特錯了。

          三、node-wav-player的核心原理



          node-wav-player的代碼十分簡易, 遠比我想象的簡練, 下面都是我將代碼化簡后的樣子, 是不是清爽很多:

          初始化的play方法, 只負責整理數(shù)據(jù), 真正播放是靠_play方法


          _play方法



          node child_process.spawn 用來啟動一個新的'子進程', 這個就是用來啟動音頻播放的'子進程' 第一個參數(shù)是命令語句, 第二個參數(shù)是數(shù)組的話就是執(zhí)行命令的位置。

          spawn的使用方法我演示一下:


          比如說 afplay 音頻地址 就可以在mac上面播放聲音。

          監(jiān)聽報錯


          如果不是code為 0亦或是this._called_stop === true人為手動調(diào)用停止的情況, 則報錯"播放失敗", 如果500毫秒內(nèi)并未報錯, 則removeAllListeners("close")移除關(guān)閉的監(jiān)聽。
          如何終止播放

          stop方法中直接kill掉'子進程'即可:


          四、'何處'錄制音頻?



          我們做這個插件的體驗宗旨就是方便快捷, 所以錄制音頻的"鏈路一定要短", 最好用戶一鍵就可以進行'錄制', 一鍵就可以生成音頻注釋。


          我最開始的想法是盡可能在vscode內(nèi)部完成, 也就是不要新開一個 h5頁面, 讓用戶的不要越出vscode這個層級。

          錄制音頻就不能像播放音頻一樣, 因為錄制涉及到錄音結(jié)束后的重播, 音頻文件的保存, 以及開始+暫停+結(jié)束等等狀態(tài)的操作, 所以最好要有個操作界面而不是靠node單打獨斗。

          五、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>`;




          局限性

          閱讀了官方文檔也查看了ts類型文件, 但是遺憾沒能發(fā)現(xiàn)為音頻授權(quán)的方法, 所以導致無法使用audio標簽來采集到用戶的音頻信息, 只能選擇換種方式實現(xiàn)。


          六、右鍵錄音



          我參考了一些音樂播放軟件, 發(fā)現(xiàn)大家播放功能幾乎都是通過打開h5頁面實現(xiàn)的, 那咱們的錄音功能也可以嘗試這種方式, 原理當然是利用node啟動一個web服務(wù), 然后幫助用戶打開類似http://localhost:8830/這種地址, 這個地址返回給用戶一段html, 這里就是錄音的地方。


          定義右鍵導航

          package.json文件內(nèi)增加
            

          "contributes"
          : {
              "menus": {
                "editor/context": [
                  {
                    "when""editorFocus",
                    "command""vn.recording",
                    "group""navigation"
                  }
                ]
              },
              "commands": [
                {
                  "command""vn.recording",
                  "title""此工程內(nèi)錄制語音注釋"
                }
              ]
          }

          1. editor/context里面定義了右鍵呼出的菜單欄的內(nèi)容。

          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文件, 負責啟動服務(wù)并且打開瀏覽器跳到對應(yīng)頁面:

          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光啟動不行, 現(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



          接下來是音頻的錄制與上傳(涉及到webRTC相關(guān)知識) , 以及如何定義儲存音頻文件的路徑, 并且附加vscode插件的發(fā)布, 這次就是這樣, 希望與你一起進步。



          點擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動和交流,掃描下方”二維碼“或在“公眾號后臺回復“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

          - END -


          瀏覽 42
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本一道码高清无码 | 先锋影音av资源网 | 无码精品一区二区三区四区找到 | 一级国产A片 | 天堂网在线最新版www中文网 |