59

次の HTML は、最初のクリックでコンソールに空の配列を表示します。

<!DOCTYPE html>
<html>
    <head>
        <script>
            function test(){
                console.log(window.speechSynthesis.getVoices())
            }
        </script>
    </head>
    <body>
        <a href="#" onclick="test()">Test</a>
    </body>
</html>

2 回目のクリックで、予想されるリストが表示されます。

onloadこの関数を呼び出すイベントを追加すると ( <body onload="test()">)、最初のクリックで正しい結果が得られます。最初の呼び出しがonloadまだ正しく機能しないことに注意してください。ページの読み込み時に空を返しますが、その後は機能します。

質問:

ベータ版のバグかもしれないので、 「なぜ」の質問は諦めました。

window.speechSynthesisここで問題は、ページの読み込み時にアクセスするかどうかです。

  • この問題に最適なハックは何ですか?
  • speechSynthesisページの読み込み時に読み込まれることを確認するにはどうすればよいですか?

背景とテスト:

Web Speech API の新機能をテストしていたところ、コードで次の問題が発生しました。

<script type="text/javascript">
$(document).ready(function(){
    // Browser support messages. (You might need Chrome 33.0 Beta)
    if (!('speechSynthesis' in window)) {
      alert("You don't have speechSynthesis");
    }

    var voices = window.speechSynthesis.getVoices();
    console.log(voices) // []

    $("#test").on('click', function(){
        var voices = window.speechSynthesis.getVoices();
        console.log(voices); // [SpeechSynthesisVoice, ...]
    });
});
</script>
<a id="test" href="#">click here if 'ready()' didn't work</a>

私の質問は、ページが読み込まれて関数がトリガーされた後、なぜwindow.speechSynthesis.getVoices()空の配列を返すのですか? onreadyリンクをクリックするとわかるように、同じ関数がトリガーによって Chrome の利用可能な音声の配列を返しますonclickか?

window.speechSynthesisページの読み込み後にChrome が読み込まれるようです。

問題はreadyイベントではありません。var voice=...関数から行を削除するreadyと、最初のクリックでコンソールに空のリストが表示されます。しかし、2回目のクリックはうまくいきます。

window.speechSynthesis最初の呼び出しの後、読み込みに時間がかかるようです。2 回呼び出す必要があります。ただし、 を 2 番目に呼び出す前に、待機してロードする必要もありますwindow.speechSynthesis。たとえば、次のコードを初めて実行すると、コンソールに 2 つの空の配列が表示されます。

// First speechSynthesis call
var voices = window.speechSynthesis.getVoices();
console.log(voices);

// Second speechSynthesis call
voices = window.speechSynthesis.getVoices();
console.log(voices);
4

11 に答える 11

108

Web Speech API Errata (E11 2013-10-17) によると、音声リストはページに非同期で読み込まれます。onvoiceschangedそれらが読み込まれると、イベントが発生します。

voiceschanged: getVoices メソッドが返す SpeechSynthesisVoiceList の内容が変更されたときに発生します。例としては、リストが非同期的に決定されるサーバー側の合成、またはクライアント側の音声がインストール/アンインストールされるときなどがあります。

そのため、そのイベント リスナーのコールバックから音声を設定するのがコツです。

// wait on voices to be loaded before fetching list
window.speechSynthesis.onvoiceschanged = function() {
    window.speechSynthesis.getVoices();
    ...
};
于 2014-04-10T04:42:22.123 に答える
8

setInterval を使用して、ボイスがロードされるまで待ってから必要に応じて使用し、setInterval をクリアすることができます。

var timer = setInterval(function() {
    var voices = speechSynthesis.getVoices();
    console.log(voices);
    if (voices.length !== 0) {
      var msg = new SpeechSynthesisUtterance(/*some string here*/);
      msg.voice = voices[/*some number here to choose from array*/];
      speechSynthesis.speak(msg);
      clearInterval(timer);
    }
}, 200);

$("#test").on('click', timer);
于 2016-02-10T20:42:51.153 に答える
6

Google Chrome と Firefox での動作を調べた結果、すべての声を取得できるのは次のとおりです。

これには非同期の処理が含まれるため、Promise を使用して実行するのが最適な場合があります。

const allVoicesObtained = new Promise(function(resolve, reject) {
  let voices = window.speechSynthesis.getVoices();
  if (voices.length !== 0) {
    resolve(voices);
  } else {
    window.speechSynthesis.addEventListener("voiceschanged", function() {
      voices = window.speechSynthesis.getVoices();
      resolve(voices);
    });
  }
});

allVoicesObtained.then(voices => console.log("All voices:", voices));

ノート:

  1. イベントが発生したら、再度voiceschanged呼び出す必要があります.getVoices()。元の配列にはコンテンツが取り込まれません。
  2. getVoices()Google Chrome では、最初に呼び出す必要はありません。イベントをリッスンするだけで、イベントが発生します。Firefox では、リッスンするだけでは十分ではありませんgetVoices()。イベントを呼び出してリッスンし、通知を受け取ったらvoiceschangedを使用して配列を設定する必要があります。getVoices()
  3. promise を使用すると、コードがよりクリーンになります。声の取得に関連するすべてがこのプロミス コードに含まれています。promise を使用せずに、代わりにこのコードを音声ルーチンに入れると、かなり面倒です。
  4. 必要な音声に解決する promise を記述しvoiceObtained、関数で何かを言うことができます。 voiceObtained.then(voice => { })そのハンドラー内で、window.speechSynthesis.speak()何かを話すために を呼び出します。speechReady("hello world").then(speech => { window.speechSynthesis.speak(speech) })または、何かを言う約束を書くこともできます。
于 2020-01-17T11:41:01.803 に答える
1

音声が必要になる前に確実に読み込まれるようにするもう 1 つの方法は、音声の読み込み状態を promise にバインドし、音声コマンドを からディスパッチすることthenです。

const awaitVoices = new Promise(done => speechSynthesis.onvoiceschanged = done);

function listVoices() {
    awaitVoices.then(()=> {
        let voices = speechSynthesis.getVoices();
        console.log(voices);
    });
}

を呼び出すとlistVoices、最初にボイスがロードされるのを待つか、次のティックで操作をディスパッチします。

于 2017-05-16T09:51:11.247 に答える
0

私はそれを正しく理解していることを確認するために独自の調査を行う必要があったため、共有するだけです(自由に編集してください).

私の目標は次のとおりです。

  • デバイスで利用可能な音声のリストを取得する
  • select 要素にこれらの音声を入力します (特定のページが読み込まれた後)。
  • わかりやすいコードを使う

基本的な機能は、MDN の公式ライブ デモで実証されています。

https://github.com/mdn/web-speech-api/tree/master/speak-easy-synthesis

しかし、私はそれをよりよく理解したかったのです。

トピックを分解するには...

音声合成

Web Speech APISpeechSynthesisインターフェースは、音声サービスのコントローラー インターフェースです。これは、デバイスで利用可能な合成音声に関する情報の取得、音声の開始と一時停止、およびその他のコマンドに使用できます。

ソース

声が変わった

インターフェイスのonvoiceschangedプロパティは、メソッドによって返されるオブジェクト のリストが変更されたとき (イベントが発生したとき)SpeechSynthesisに実行されるイベント ハンドラーを表し ます。SpeechSynthesisVoiceSpeechSynthesis.getVoices()voiceschanged

ソース

例A

私のアプリケーションが単に持っている場合:

var synth = window.speechSynthesis;
console.log(synth);
console.log(synth.onvoiceschanged);

Chrome デベロッパー ツール コンソールに次のように表示されます。

ここに画像の説明を入力

例 B

コードを次のように変更すると:

var synth = window.speechSynthesis;

console.log("BEFORE");
console.log(synth);
console.log(synth.onvoiceschanged);

console.log("AFTER");
var voices = synth.getVoices();

console.log(voices);
console.log(synth);
console.log(synth.onvoiceschanged);

before と after の状態は同じでvoices、空の配列です。

ここに画像の説明を入力

解決

Promisesを実装する自信はありませんが、次のことがうまくいきました。

関数の定義

var synth = window.speechSynthesis;
// declare so that values are accessible globally
var voices = [];


function set_up_speech() {

    return new Promise(function(resolve, reject) {

        // get the voices
        var voices = synth.getVoices();

        // get reference to select element
        var $select_topic_speaking_voice = $("#select_topic_speaking_voice");

        // for each voice, generate select option html and append to select
        for (var i = 0; i < voices.length; i++) {

            var option = $("<option></option>");

            var suffix = "";

            // if it is the default voice, add suffix text  
            if (voices[i].default) {
                suffix = " -- DEFAULT";
            }

            // create the option text
            var option_text = voices[i].name + " (" + voices[i].lang + suffix + ")";

            // add the option text
            option.text(option_text);

            // add option attributes
            option.attr("data-lang", voices[i].lang);
            option.attr("data-name", voices[i].name);

            // append option to select element
            $select_topic_speaking_voice.append(option);
        }

        // resolve the voices value
        resolve(voices)

    });

}

関数の呼び出し

// in your handler, populate the select element    
if (page_title === "something") {
set_up_speech()
}
于 2018-08-24T05:41:20.810 に答える