JavaScript >> Javascript 文檔 >  >> Tags >> API

如何使用 HTML5 語音合成 API 添加文本轉語音

如何使用 HTML5 語音合成 API 為您的應用添加文本到語音,並提供多種語音選項。

開始使用

在本教程中,我們將使用 CheatCode 的全棧 JavaScript 框架 Joystick。 Joystick 將前端 UI 框架與用於構建應用的 Node.js 後端結合在一起。

首先,我們要通過 NPM 安裝 Joystick。確保在安裝之前使用 Node.js 16+ 以確保兼容性(如果您需要學習如何安裝 Node.js 或在計算機上運行多個版本,請先閱讀本教程):

終端

npm i -g @joystick.js/cli

這將在您的計算機上全局安裝操縱桿。安裝好之後,接下來我們新建一個項目:

終端

joystick create app

幾秒鐘後,您將看到一條消息已註銷到 cd 進入你的新項目並運行 joystick start

終端

cd app && joystick start

在此之後,您的應用應該可以運行了,我們可以開始了。

添加引導

深入研究代碼,首先,我們想將 Bootstrap CSS 框架添加到我們的應用程序中。雖然你沒有 為此,它將使我們的應用程序看起來更漂亮,並避免我們不得不為我們的 UI 拼湊 CSS。為此,我們將向 /index.html 添加 Bootstrap CDN 鏈接 項目根目錄下的文件:

/index.html

<!doctype html>
<html class="no-js" lang="en">
  <head>
    <meta charset="utf-8">
    <title>Joystick</title>
    <meta name="description" content="An awesome JavaScript app that's under development.">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="theme-color" content="#FFCC00">
    <link rel="apple-touch-icon" href="/apple-touch-icon-152x152.png">
    <link rel="stylesheet" href="/_joystick/index.css">
    <link rel="manifest" href="/manifest.json">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    ${css}
  </head>
  <body>
    ...
  </body>
</html>

在這裡,就在 ${css} 上方 在文件的一部分,我們已經粘貼在 <link></link> Bootstrap 文檔中的標籤,讓我們可以訪問框架的 CSS 部分。

而已。操縱桿會自動重啟並加載到瀏覽器中,這樣我們就可以開始使用了。

使用文本到語音連接操縱桿組件

在 Joystick 應用中,我們的 UI 是使用框架的內置 UI 庫 @joystick.js/ui 構建的 .當我們運行 joystick create app 上面,我們得到了一些可以使用的示例組件。我們將覆蓋 /ui/pages/index/index.js 包含一些 HTML 的文件,這些 HTML 將用作我們的翻譯器的 UI。

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  css: `
    h4 {
      border-bottom: 1px solid #eee;
      padding-bottom: 20px;
      margin-bottom: 40px;
    }

    textarea {
      margin-bottom: 40px;
    }
  `,
  render: () => {
    return `
      <div>
        <h4>Text to Speech Translator</h4>
        <form>
          <textarea class="form-control" name="textToTranslate" placeholder="Type the text to speak here and then press Speak below."></textarea>
          <button class="btn btn-primary">Speak</button>
        </form>
        <div class="players"></div>
      </div>
    `;
  },
});

export default Index;

首先,我們想用上面看到的替換這個文件中的組件。在這裡,我們定義了一個包含兩件事的簡單組件:一個 render 函數返回一個我們想要在瀏覽器和上面顯示的 HTML 字符串,一個 css 的字符串 我們想要應用到我們正在渲染的 HTML(Joystick 自動將我們在這里傳遞的 CSS 限定為由我們的 render 返回的 HTML 函數)。

如果我們加載 http://localhost:2600 在瀏覽器中(端口 2600 是我們運行 joystick start 時默認啟動 Joystick 的地方 ),我們應該看到上面的 HTML 的 Bootstrap 樣式版本。

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  events: {
    'submit form': (event, component) => {
      event.preventDefault();

      const text = event?.target?.textToTranslate?.value;
      const hasText = text.trim() !== '';

      if (!hasText) {
        return component.methods.speak('Well you have to say something!');
      }

      component.methods.speak(text);
    },
  },
  css: `...`,
  render: () => {
    return `
      <div>
        <h4>Text to Speech Translator</h4>
        <form>
          <textarea class="form-control" name="textToTranslate" placeholder="Type the text to speak here and then press Speak below."></textarea>
          <button class="btn btn-primary">Speak</button>
        </form>
        <div class="players"></div>
      </div>
    `;
  },
});

export default Index;

接下來,我們要添加一個 events 反對我們的組件。顧名思義,這是我們為組件定義事件監聽器的地方。在這裡,我們為 submit 定義了一個監聽器 <form></form> 上的事件 由我們的組件渲染的元素。就像我們的 CSS 一樣,Joystick 會自動將我們的事件範圍限定為正在呈現的 HTML。

分配給那個 submit form events 上的屬性 object 是一個函數,只要在我們的 <form></form> 上檢測到提交事件,就會調用該函數 .

在該函數內部,首先,我們接收 event (這是瀏覽器 DOM 事件)作為第一個參數並立即調用 event.preventDefault() 在上面。這可以防止瀏覽器嘗試執行 HTTP POSTaction 我們表單上的屬性。顧名思義,這是默認 瀏覽器的行為(我們沒有 action 屬性,因為我們想通過 JavaScript 控制提交)。

接下來,一旦停止,我們想要獲取輸入到 <textarea></textarea> 中的值 .為此,我們可以參考 textToTranslate event.target 上的屬性 目的。這裡,event.target<form></form> 在瀏覽器中呈現的元素(它在內存中的表示)。

我們可以訪問textToTranslate 因為瀏覽器會使用字段的 name 在內存中自動將表單中的所有字段分配給它 屬性作為屬性名稱。如果我們仔細觀察我們的 <textarea></textarea> ,我們可以看到它有 name 屬性 textToTranslate .如果我們將其更改為 pizza ,我們會寫 event?.target?.pizza?.value 而是。

將該值存儲在 text 變量,接下來,我們創建另一個變量hasText 其中包含檢查以確保我們的 text 變量不是空字符串(.trim() 此處部分“修剪”任何空白字符,以防用戶一遍又一遍地按空格鍵)。

如果我們在輸入中沒有任何文本,我們想“說”“你必須說點什麼!”這句話。假設我們做了 得到一些文本,我們只想“說”那個 text 價值。

請注意,這裡我們調用 component.methods.speak 我們還沒有定義。我們將利用操縱桿的 methods 功能(我們可以在我們的組件上定義其他功能)。

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  methods: {
    speak: (text = '') => {  
      window.speechSynthesis.cancel();

      const message = new SpeechSynthesisUtterance(text);

      speechSynthesis.speak(message);
    },
  },
  events: {
    'submit form': (event, component) => {
      event.preventDefault();

      const text = event?.target?.textToTranslate?.value;
      const hasText = text.trim() !== '';

      if (!hasText) {
        return component.methods.speak('Well you have to say something!');
      }

      component.methods.speak(text);
    },
  },
  css: `...`,
  render: () => {
    return `
      <div>
        <h4>Text to Speech Translator</h4>
        <form>
          <textarea class="form-control" name="textToTranslate" placeholder="Type the text to speak here and then press Speak below."></textarea>
          <button class="btn btn-primary">Speak</button>
        </form>
        <div class="players"></div>
      </div>
    `;
  },
});

export default Index;

現在是有趣的部分。因為語音合成 API 是在瀏覽器中實現的(請參閱此處的兼容性——它非常好),我們不必安裝或導入任何東西;整個 API 都可以在瀏覽器中全局訪問。

添加 methods events 上方的對象 ,我們分配 speak 我們從 submit form 調用的方法 事件處理程序。

裡面,沒什麼可做的:

  1. 如果我們更改輸入的文本並在播放過程中單擊“Speak”按鈕,我們希望調用 window.speechSynthesis.cancel() 方法告訴 API 清除其播放隊列。如果我們不這樣做,它只會將播放附加到其隊列中並繼續播放我們傳遞給它的內容(即使在瀏覽器刷新之後)。
  2. 創建一個 SpeechSynthesisUtterance() 的實例 這是一個接收我們要說的文本的類。
  3. 將該實例傳遞給 speechSynthesis.speak() 方法。

而已。只要我們在框中輸入一些文本並點擊“Speak”,您的瀏覽器(假設它支持 API)就會開始喋喋不休。

驚人的。但我們還沒有完成。信不信由你,語音合成 API 還包括使用不同聲音的選項。接下來,我們將更新 render 返回的 HTML 包含可供選擇的聲音列表並更新 methods.speak 的功能 接受不同的聲音。

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  state: {
    voices: [],
  },
  lifecycle: {
    onMount: (component) => {
      window.speechSynthesis.onvoiceschanged = () => {
        const voices = window.speechSynthesis.getVoices();
        component.setState({ voices });
      };
    },
  },
  methods: {
    getLanguageName: (language = '') => {
      if (language) {
        const regionNamesInEnglish = new Intl.DisplayNames(['en'], { type: 'region' });
        return regionNamesInEnglish.of(language?.split('-').pop());
      }

      return 'Unknown';
    },
    speak: (text = '', voice = '', component) => {  
      window.speechSynthesis.cancel();

      const message = new SpeechSynthesisUtterance(text);

      if (voice) {
        const selectedVoice = component?.state?.voices?.find((voiceOption) => voiceOption?.voiceURI === voice);
        message.voice = selectedVoice;
      }

      speechSynthesis.speak(message);
    },
  },
  events: {
    'submit form': (event, component) => {
      event.preventDefault();
      const text = event?.target?.textToTranslate?.value;
      const voice = event?.target?.voice?.value;
      const hasText = text.trim() !== '';

      if (!hasText) {
        return component.methods.speak('Well you have to say something!', voice);
      }

      component.methods.speak(text, voice);
    },
  },
  css: `
    h4 {
      border-bottom: 1px solid #eee;
      padding-bottom: 20px;
      margin-bottom: 40px;
    }

    select {
      margin-bottom: 20px;
    }

    textarea {
      margin-bottom: 40px;
    }
  `,
  render: ({ state, each, methods }) => {
    return `
      <div>
        <h4>Text to Speech Translator</h4>
        <form>
          <label class="form-label">Voice</label>
          <select class="form-control" name="voice">
            ${each(state?.voices, (voice) => {
              return `
                <option value="${voice.voiceURI}">${voice.name} (${methods.getLanguageName(voice.lang)})</option>
              `;
            })}
          </select>
          <textarea class="form-control" name="textToTranslate" placeholder="Type the text to speak here and then press Speak below."></textarea>
          <button class="btn btn-primary">Speak</button>
        </form>
        <div class="players"></div>
      </div>
    `;
  },
});

export default Index;

為了加快速度,我們在上面輸出了我們需要的其餘代碼——讓我們逐步完成它。

首先,為了訪問 API 提供的可用語音,我們需要等待它們在瀏覽器中加載。在我們的methods之上 選項,我們為組件 lifecycle 添加了另一個選項 我們為它分配了一個 onMount() 功能。

這個函數在我們的組件被掛載到 DOM 後立即被 Joystick 調用。這是一種運行依賴於 UI 的代碼的好方法,或者,在這種情況下,是一種偵聽和處理全局或瀏覽器級別事件的方法(與我們的組件呈現的 HTML 生成的事件相反)。

不過,在我們獲得聲音之前,我們需要監聽 window.speechSynthesis.onvoiceschanged 事件。加載聲音後立即觸發此事件(我們談論的是幾分之一秒,但速度足夠慢以至於我們想在代碼級別等待)。

onMount 內部 ,我們將該值分配給將在 window 上觸發事件時調用的函數 .在該函數內部,我們調用 window.speechSynthesis.getVoices() 函數返回一個描述所有可用聲音的對象列表。所以我們可以在我們的 UI 中使用它,我們採用 component 傳遞給 onMount 的參數 函數並調用它的 setState() 函數,傳遞一個具有 voices 屬性的對象 .

因為我們要分配一個狀態值voices 到變量 const voices 的內容 在這裡,我們可以跳過編寫 component.setState({ voices: voices }) 並且只使用簡寫版本。

重要 :高於 lifecycle 選項,請注意我們添加了另一個選項 state 設置為一個對象,並在該對像上,一個屬性 voices 設置為空數組。這是我們的 voices 的默認值 數組,它將在我們的 render 中發揮作用 功能。

在那裡,我們可以看到我們已經更新了 render 函數使用 JavaScript 解構,以便我們可以從它傳遞的參數(組件實例)中“提取”屬性,以便在我們返回 from 的 HTML 中使用 函數。

在這裡,我們引入 state , each , 和 methods . statemethods 是我們上面在組件中設置的值。 each 是所謂的“渲染函數”(不要與分配給 render 的函數混淆 我們組件上的選項)。

顧名思義,each() 用於循環或迭代列表並為該列表中的每個項目返回一些 HTML。

在這裡,我們可以看到 JavaScript 字符串插值的使用(用 ${} 表示 在 <select></select> 的打開和關閉之間 tag) 將我們的調用傳遞給 each() .到 each() ,我們傳遞列表或數組(在本例中為 state.voices ) 作為第一個參數,第二個參數是一個將被調用的函數,接收被迭代的當前值。

在這個函數內部,我們想要返回一些 HTML,這些 HTML 將會為 each 輸出 state.voices 中的項目 數組。

因為我們在 <select></select> 裡面 標記,我們想要為從語音合成 API 獲得的每個聲音呈現一個選擇選項。就像我們上面提到的,每個 voice 只是一個帶有一些屬性的 JavaScript 對象。我們在這里關心的是 voice.voiceURI (語音的唯一 ID/名稱)和 voice.name (說話人的字面意思)。

最後,我們還關心所使用的語言。這是作為 lang 傳遞的 在每個 voice 標準 ISO 語言代碼形式的對象。為了獲得“友好”的表示(例如,FranceGermany ),我們需要轉換 ISO 代碼。在這裡,我們調用了一個方法 getLanguageName() 在我們的 methods 中定義 接受 voice.lang 的對象 value 並將其轉換為人類友好的字符串。

從上面看這個函數,我們採用 language in 作為參數(我們從 each() 內部傳遞的字符串 ) 如果它不是空值,則創建 Intl.DisplayNames() 的實例 類(Intl 是瀏覽器中另一個可用的全局變量),傳遞給它一個我們想要支持的區域數組(因為作者是 yank,只是 en ) 並在第二個參數的選項中,設置名稱 type 到“地區”。

結果存儲在 regionNamesInEnglish ,我們調用該變量的 .of() 方法,傳入 language 參數傳遞給我們的函數。當我們通過它時,我們調用 .split('-') 它上面的方法說“在 - 處將此字符串一分為二 字符(意思是如果我們通過 en-US 我們會得到一個像 ['en', 'US'] 這樣的數組 ) 然後,在結果數組上,調用 .pop() 方法說“彈出最後一個項目並將其返回給我們。”在這種情況下,最後一項是 US 作為 .of() 預期格式的字符串 方法。

再邁一步。請注意,在我們的 submit form 事件處理程序,我們為 voice 添加了一個變量 選項(使用與 textToTranslate 相同的技術來檢索其值 ) 然後將其作為第二個參數傳遞給我們的 methods.speak() 功能。

回到那個函數,我們添加 voice 作為第二個參數以及 component 作為第三個(操縱桿自動通過 component 作為我們方法的最後一個參數——如果沒有傳遞參數,它會是第一個,或者,在這個例子中,如果傳遞了兩個參數,它會是第三個)。

在我們的函數內部,我們添加了一個 if (voice) 檢查並在其中運行一個 .find()state.voices 數組說“用 .voiceURI 找到我們的對象 值等於 voice 我們傳遞給 speak 的參數 函數(這是 en-US 字符串或 voice.lang )。這樣,我們只需設置 .voice 在我們的 messageSpeechSynthesisUtterance 類實例),API 從那裡接管。

完畢!如果一切都在正確的位置,我們應該有一個工作的文本到語音翻譯器。

總結

在本教程中,我們學習瞭如何使用 @joystick.js/ui 編寫組件 框架來幫助我們構建文本到語音的 API。我們學習瞭如何監聽 DOM 事件以及如何在瀏覽器中使用 Speech Synthesis API 為我們說話。我們還了解了 Intl 內置在瀏覽器中的庫,可幫助我們將日期字符串的 ISO 代碼轉換為易於理解的名稱。最後,我們學習瞭如何通過 Speech Synthesis API 動態切換語音以支持不同的音調和語言。


Tutorial JavaScript 教程
  1. react-router:如果它處於活動狀態,如何禁用它?

  2. 如何在 docker compose 內的 Node.js 服務之間發送 json 消息

  3. 探索 Netlify 拆分測試

  4. 使用 JavaScript 對字符串的一維數組進行排序

  5. 響應式導航菜單

  6. 使用 jQuery 製作大型下拉菜單

  7. REASONML - 按預期反應 (2020)

  1. 在您犯錯時發現錯誤。

  2. React:有狀態組件與無狀態組件

  3. 如何將字符串數組轉換為數字?

  4. 在低端設備中實施背壓以獲得更流暢的用戶體驗

  5. 如何使用 NodeJS、Express 和 MySQL 構建 Rest API

  6. 克隆對像或數組(淺克隆)

  7. JavaScript 練習:找出字符串中唯一字母的數量

  1. Web 可訪問性實用指南:第 1 部分:我的網站可以訪問嗎?

  2. 使用 Netlify 5 分鐘上線

  3. Reactjs material-ui 庫中的容器組件

  4. 天才之路:初學者#13