web-dev-qa-db-ja.com

Chrome長いテキストでの音声合成

Chrome 33でSpeech Synthesis APIを使用しようとすると問題が発生します。短いテキストでも問題なく動作しますが、長いテキストを試しても途中で停止します。一度停止すると、音声合成はブラウザが再起動されるまでChrome内で動作しません。

コード例( http://jsfiddle.net/Mdm47/1/ ):

function speak(text) {
    var msg = new SpeechSynthesisUtterance();
    var voices = speechSynthesis.getVoices();
    msg.voice = voices[10];
    msg.voiceURI = 'native';
    msg.volume = 1;
    msg.rate = 1;
    msg.pitch = 2;
    msg.text = text;
    msg.lang = 'en-US';

    speechSynthesis.speak(msg);
}

speak('Short text');
speak('Collaboratively administrate empowered markets via plug-and-play networks. Dynamically procrastinate B2C users after installed base benefits. Dramatically visualize customer directed convergence without revolutionary ROI. Efficiently unleash cross-media information without cross-media value. Quickly maximize timely deliverables for real-time schemas. Dramatically maintain clicks-and-mortar solutions without functional solutions.');
speak('Another short text');

2番目のテキストの途中で発言が停止し、その後、他のページで発言することはできません。

それはブラウザのバグですか、それとも何らかのセキュリティ上の制限ですか?

39
Andrey Shchekin

Google Chrome Speech Synthesisでこの問題が発生しました。調査を重ねた結果、次のことがわかりました。

  • 発話の中断は、音声がネイティブ音声ではない場合にのみ発生します
  • 切り取りは通常、200-300文字の間で行われ、
  • 壊れた場合は、speechSynthesis.cancel();を実行してフリーズ解除できます。
  • 'onend'イベントが発生しない場合があります。これに対する風変わりな回避策は、発話オブジェクトを話す前にconsole.log()することです。また、発言呼び出しをsetTimeoutコールバックでラップすると、これらの問題を解決できることがわかりました。

これらの問題に対応して、テキストを小さな発声に分割し、それらを次々に再生することにより、文字の制限を克服する関数を作成しました。文が2つの別々の発話に分割され、それぞれの間にわずかな時間遅延があるため、明らかに奇妙な音が聞こえることがあります。ただしコードは、音の切れが目立たない。

更新

この回避策を https://Gist.github.com/woollsta/2d146f13878a301b36d7#file-chunkify-js で公開しました。 Brett Zamir に感謝します。

関数:

var speechUtteranceChunker = function (utt, settings, callback) {
    settings = settings || {};
    var newUtt;
    var txt = (settings && settings.offset !== undefined ? utt.text.substring(settings.offset) : utt.text);
    if (utt.voice && utt.voice.voiceURI === 'native') { // Not part of the spec
        newUtt = utt;
        newUtt.text = txt;
        newUtt.addEventListener('end', function () {
            if (speechUtteranceChunker.cancel) {
                speechUtteranceChunker.cancel = false;
            }
            if (callback !== undefined) {
                callback();
            }
        });
    }
    else {
        var chunkLength = (settings && settings.chunkLength) || 160;
        var pattRegex = new RegExp('^[\\s\\S]{' + Math.floor(chunkLength / 2) + ',' + chunkLength + '}[.!?,]{1}|^[\\s\\S]{1,' + chunkLength + '}$|^[\\s\\S]{1,' + chunkLength + '} ');
        var chunkArr = txt.match(pattRegex);

        if (chunkArr[0] === undefined || chunkArr[0].length <= 2) {
            //call once all text has been spoken...
            if (callback !== undefined) {
                callback();
            }
            return;
        }
        var chunk = chunkArr[0];
        newUtt = new SpeechSynthesisUtterance(chunk);
        var x;
        for (x in utt) {
            if (utt.hasOwnProperty(x) && x !== 'text') {
                newUtt[x] = utt[x];
            }
        }
        newUtt.addEventListener('end', function () {
            if (speechUtteranceChunker.cancel) {
                speechUtteranceChunker.cancel = false;
                return;
            }
            settings.offset = settings.offset || 0;
            settings.offset += chunk.length - 1;
            speechUtteranceChunker(utt, settings, callback);
        });
    }

    if (settings.modifier) {
        settings.modifier(newUtt);
    }
    console.log(newUtt); //IMPORTANT!! Do not remove: Logging the object out fixes some onend firing issues.
    //placing the speak invocation inside a callback fixes ordering and onend issues.
    setTimeout(function () {
        speechSynthesis.speak(newUtt);
    }, 0);
};

どうやって使うのですか...

//create an utterance as you normally would...
var myLongText = "This is some long text, oh my goodness look how long I'm getting, wooooohooo!";

var utterance = new SpeechSynthesisUtterance(myLongText);

//modify it as you normally would
var voiceArr = speechSynthesis.getVoices();
utterance.voice = voiceArr[2];

//pass it into the chunking function to have it played out.
//you can set the max number of characters by changing the chunkLength property below.
//a callback function can also be added that will fire once the entire text has been spoken.
speechUtteranceChunker(utterance, {
    chunkLength: 120
}, function () {
    //some code to execute when done
    console.log('done');
});

人々がこれが役に立つと思うことを願っています。

53
Peter Woolley

Peterの答えの問題は、音声合成のキューが設定されていると機能しないことです。スクリプトは、新しいチャンクをキューの最後に配置するため、順序が乱れます。例: https://jsfiddle.net/1gzkja90/

<script type='text/javascript' src='http://code.jquery.com/jquery-2.1.0.js'></script>
<script type='text/javascript'>    
    u = new SpeechSynthesisUtterance();
    $(document).ready(function () {
        $('.t').each(function () {
            u = new SpeechSynthesisUtterance($(this).text());

            speechUtteranceChunker(u, {
                chunkLength: 120
            }, function () {
                console.log('end');
            });
        });
    });
     /**
     * Chunkify
     * Google Chrome Speech Synthesis Chunking Pattern
     * Fixes inconsistencies with speaking long texts in speechUtterance objects 
     * Licensed under the MIT License
     *
     * Peter Woolley and Brett Zamir
     */
    var speechUtteranceChunker = function (utt, settings, callback) {
        settings = settings || {};
        var newUtt;
        var txt = (settings && settings.offset !== undefined ? utt.text.substring(settings.offset) : utt.text);
        if (utt.voice && utt.voice.voiceURI === 'native') { // Not part of the spec
            newUtt = utt;
            newUtt.text = txt;
            newUtt.addEventListener('end', function () {
                if (speechUtteranceChunker.cancel) {
                    speechUtteranceChunker.cancel = false;
                }
                if (callback !== undefined) {
                    callback();
                }
            });
        }
        else {
            var chunkLength = (settings && settings.chunkLength) || 160;
            var pattRegex = new RegExp('^[\\s\\S]{' + Math.floor(chunkLength / 2) + ',' + chunkLength + '}[.!?,]{1}|^[\\s\\S]{1,' + chunkLength + '}$|^[\\s\\S]{1,' + chunkLength + '} ');
            var chunkArr = txt.match(pattRegex);

            if (chunkArr[0] === undefined || chunkArr[0].length <= 2) {
                //call once all text has been spoken...
                if (callback !== undefined) {
                    callback();
                }
                return;
            }
            var chunk = chunkArr[0];
            newUtt = new SpeechSynthesisUtterance(chunk);
            var x;
            for (x in utt) {
                if (utt.hasOwnProperty(x) && x !== 'text') {
                    newUtt[x] = utt[x];
                }
            }
            newUtt.addEventListener('end', function () {
                if (speechUtteranceChunker.cancel) {
                    speechUtteranceChunker.cancel = false;
                    return;
                }
                settings.offset = settings.offset || 0;
                settings.offset += chunk.length - 1;
                speechUtteranceChunker(utt, settings, callback);
            });
        }

        if (settings.modifier) {
            settings.modifier(newUtt);
        }
        console.log(newUtt); //IMPORTANT!! Do not remove: Logging the object out fixes some onend firing issues.
        //placing the speak invocation inside a callback fixes ordering and onend issues.
        setTimeout(function () {
            speechSynthesis.speak(newUtt);
        }, 0);
    };
</script>
<p class="t">MLA format follows the author-page method of in-text citation. This means that the author's last name and the page number(s) from which the quotation or paraphrase is taken must appear in the text, and a complete reference should appear on your Works Cited page. The author's name may appear either in the sentence itself or in parentheses following the quotation or paraphrase, but the page number(s) should always appear in the parentheses, not in the text of your sentence.</p>
<p class="t">Joe waited for the train.</p>
<p class="t">The train was late.</p>
<p class="t">Mary and Samantha took the bus.</p>

私の場合、答えはキューに追加する前に文字列を「チャンク」することでした。こちらをご覧ください: http://jsfiddle.net/vqvyjzq4/

アイデアと正規表現(まだ私はまだ征服していない)についてPeterに多くの小道具。JavaScriptをクリーンアップできると確信しています。これは概念実証の詳細です。

<script type='text/javascript' src='http://code.jquery.com/jquery-2.1.0.js'></script>
<script type='text/javascript'>    
    var chunkLength = 120;
    var pattRegex = new RegExp('^[\\s\\S]{' + Math.floor(chunkLength / 2) + ',' + chunkLength + '}[.!?,]{1}|^[\\s\\S]{1,' + chunkLength + '}$|^[\\s\\S]{1,' + chunkLength + '} ');

    $(document).ready(function () {
        var element = this;
        var arr = [];
        var txt = replaceBlank($(element).text());
        while (txt.length > 0) {
            arr.Push(txt.match(pattRegex)[0]);
            txt = txt.substring(arr[arr.length - 1].length);
        }
        $.each(arr, function () {
            var u = new SpeechSynthesisUtterance(this.trim());
            window.speechSynthesis.speak(u);
        });
    });
</script>
<p class="t">MLA format follows the author-page method of in-text citation. This means that the author's last name and the page number(s) from which the quotation or paraphrase is taken must appear in the text, and a complete reference should appear on your Works Cited page. The author's name may appear either in the sentence itself or in parentheses following the quotation or paraphrase, but the page number(s) should always appear in the parentheses, not in the text of your sentence.</p>
<p class="t">Joe waited for the train.</p>
<p class="t">The train was late.</p>
<p class="t">Mary and Samantha took the bus.</p>
8
Michael

シンプルで効果的なソリューションは、定期的に再開することです。

function resumeInfinity() {
    window.speechSynthesis.resume();
    timeoutResumeInfinity = setTimeout(resumeInfinity, 1000);
}

これをonendイベントとonstartイベントに関連付けることができるため、必要な場合にのみ再開を呼び出します。何かのようなもの:

var utterance = new SpeechSynthesisUtterance();

utterance.onstart = function(event) {
    resumeInfinity();
};

utterance.onend = function(event) {
    clearTimeout(timeoutResumeInfinity);
};

たまたまこれを発見しました!

この助けを願っています!

6
MhagnumDw

最終的にテキストを分割し、ピリオド、コンマなどのさまざまな句読点の処理に関するインテリジェンスを取得しました。たとえば、テキストが数字の一部(つまり、$ 10,000)の場合、テキストをコンマに分割したくない。

私はそれをテストしましたが、それは任意の大きな入力セットで動作するようであり、デスクトップだけでなくAndroid電話とiphoneでも動作するようです。

シンセサイザーのgithubページをセットアップします: https://github.com/unk1911/speech

ライブで見ることができます: http://edeliverables.com/tts/

5
user3892260

Pause()関数とresume()関数を呼び出し、タイマーを再度コールセットするタイマー関数を使用して、問題を解決しました。 onendイベントで、タイマーをクリアします。

    var myTimeout;
    function myTimer() {
        window.speechSynthesis.pause();
        window.speechSynthesis.resume();
        myTimeout = setTimeout(myTimer, 10000);
    }
    ...
        window.speechSynthesis.cancel();
        myTimeout = setTimeout(myTimer, 10000);
        var toSpeak = "some text";
        var utt = new SpeechSynthesisUtterance(toSpeak);
        ...
        utt.onend =  function() { clearTimeout(myTimeout); }
        window.speechSynthesis.speak(utt);
    ...

これはうまくいくようです。

4
jjsa

ここに私が終わったものがあります、それは単に期間「。」で私の文章を分割します

var voices = window.speechSynthesis.getVoices();

var sayit = function ()
{
    var msg = new SpeechSynthesisUtterance();

    msg.voice = voices[10]; // Note: some voices don't support altering params
    msg.voiceURI = 'native';
    msg.volume = 1; // 0 to 1
    msg.rate = 1; // 0.1 to 10
    msg.pitch = 2; //0 to 2
    msg.lang = 'en-GB';
    msg.onstart = function (event) {

        console.log("started");
    };
    msg.onend = function(event) {
        console.log('Finished in ' + event.elapsedTime + ' seconds.');
    };
    msg.onerror = function(event)
    {

        console.log('Errored ' + event);
    }
    msg.onpause = function (event)
    {
        console.log('paused ' + event);

    }
    msg.onboundary = function (event)
    {
        console.log('onboundary ' + event);
    }

    return msg;
}


var speekResponse = function (text)
{
    speechSynthesis.cancel(); // if it errors, this clears out the error.

    var sentences = text.split(".");
    for (var i=0;i< sentences.length;i++)
    {
        var toSay = sayit();
        toSay.text = sentences[i];
        speechSynthesis.speak(toSay);
    }
}
3
Emile

2017およびこのバグはまだ存在します。受賞歴のあるChrome拡張機能Read Aloud)の開発者であるこの問題をよく理解しています。 、賞を受賞した部分について冗談です。

  1. 15秒を超えると、音声が停止します。
  2. Chrome=は15秒のアイドルタイマーを使用して、拡張機能のイベント/バックグラウンドページを非アクティブにするタイミングを決定します。これが原因だと思います。

私が使用した回避策は、句読点を尊重するかなり複雑なチャンクアルゴリズムです。ラテン語の場合、最大チャンクサイズを36ワードに設定します。あなたが傾いている場合、コードはオープンソースです: https://github.com/ken107/read-aloud/blob/master/js/speech.js (行144)

ほとんどの場合、36ワードの制限は15秒以内に収まります。しかし、それでも動かなくなる場合があります。それから回復するには、16秒のタイマーを使用します。

3
Hai Phan

はい。Google合成APIは、長いテキストを話している間のある時点で停止します。

SpeechSynthesisUtteranceのonendイベント、onpauseイベント、onerrorイベントは、突然停止が発生すると正常に起動されないため、speechSynthesis onerrorイベントも発生します。

いくつかの試行の後、speechSynthesis.pausedが機能していることを確認し、speechSynthesis.resume()が発言の再開に役立ちます。

したがって、発言中に一時停止状態を確認し、speechSynthesis.resume()を呼び出して続行するためのタイマーが必要です。間隔は、発言を継続するときのグリッチを防ぐために十分小さくする必要があります。

let timer = null;
let reading = false;

let readText = function(text) {

    if (!reading) {
        speechSynthesis.cancel();
        if (timer) {
            clearInterval(timer);
        }
        let msg = new SpeechSynthesisUtterance();
        let voices = window.speechSynthesis.getVoices();
        msg.voice = voices[82];
        msg.voiceURI = 'native';
        msg.volume = 1; // 0 to 1
        msg.rate = 1.0; // 0.1 to 10
        msg.pitch = 1; //0 to 2
        msg.text = text;
        msg.lang = 'zh-TW';

        msg.onerror = function(e) {
            speechSynthesis.cancel();
            reading = false;
            clearInterval(timer);
        };

        msg.onpause = function(e) {
            console.log('onpause in ' + e.elapsedTime + ' seconds.');
        }            

        msg.onend = function(e) {
            console.log('onend in ' + e.elapsedTime + ' seconds.');
            reading = false;
            clearInterval(timer);
        };

        speechSynthesis.onerror = function(e) {
            console.log('speechSynthesis onerror in ' + e.elapsedTime + ' seconds.');
            speechSynthesis.cancel();
            reading = false;
            clearInterval(timer);
        };

        speechSynthesis.speak(msg);

        timer = setInterval(function(){
            if (speechSynthesis.paused) {
                console.log("#continue")
                speechSynthesis.resume();
            }

        }, 100);

        reading = true;

    }
}
0
Jackie Chao

他の提案は、ドットで奇妙なことをするか、DOTと言って、文の終わりに音声のイントネーションを尊重しません。

var CHARACTER_LIMIT = 200;
var lang = "en";

var text = "MLA format follows the author-page method of in-text citation. This means that the author's last name and the page number(s) from which the quotation or paraphrase is taken must appear in the text, and a complete reference should appear on your Works Cited page. The author's name may appear either in the sentence itself or in parentheses following the quotation or paraphrase, but the page number(s) should always appear in the parentheses, not in the text of your sentence. Joe waited for the train. The train was late. Mary and Samantha took the bus.";

    speak(text, lang)

    function speak(text, lang) {

      //Support for multipart text (there is a limit on characters)
      var multipartText = [];

      if (text.length > CHARACTER_LIMIT) {

        var tmptxt = text;

        while (tmptxt.length > CHARACTER_LIMIT) {

          //Split by common phrase delimiters
          var p = tmptxt.search(/[:!?.;]+/);
          var part = '';

          //Coludn't split by priority characters, try commas
          if (p == -1 || p >= CHARACTER_LIMIT) {
            p = tmptxt.search(/[,]+/);
          }

          //Couldn't split by normal characters, then we use spaces
          if (p == -1 || p >= CHARACTER_LIMIT) {

            var words = tmptxt.split(' ');

            for (var i = 0; i < words.length; i++) {

              if (part.length + words[i].length + 1 > CHARACTER_LIMIT)
                break;

              part += (i != 0 ? ' ' : '') + words[i];

            }

          } else {

            part = tmptxt.substr(0, p + 1);

          }

          tmptxt = tmptxt.substr(part.length, tmptxt.length - part.length);

          multipartText.Push(part);
          //console.log(part.length + " - " + part);

        }

        //Add the remaining text
        if (tmptxt.length > 0) {
          multipartText.Push(tmptxt);
        }

      } else {

        //Small text
        multipartText.Push(text);
      }


      //Play multipart text
      for (var i = 0; i < multipartText.length; i++) {

        //Use SpeechSynthesis
        //console.log(multipartText[i]);

        //Create msg object
        var msg = new SpeechSynthesisUtterance();
        //msg.voice = profile.systemvoice;
        //msg.voiceURI = profile.systemvoice.voiceURI;
        msg.volume = 1; // 0 to 1
        msg.rate = 1; // 0.1 to 10
        // msg.rate = usersetting || 1; // 0.1 to 10
        msg.pitch = 1; //0 to 2*/
        msg.text = multipartText[i];
        msg.speak = multipartText;
        msg.lang = lang;
        msg.onend = self.OnFinishedPlaying;
        msg.onerror = function (e) {
          console.log('Error');
          console.log(e);
        };
        /*GC*/
        msg.onstart = function (e) {
          var curenttxt = e.currentTarget.text;
          console.log(curenttxt);
          //highlight(e.currentTarget.text);
          //$('#showtxt').text(curenttxt);
          //console.log(e);
        };
        //console.log(msg);
        speechSynthesis.speak(msg);

      }

    }

https://jsfiddle.net/onigetoc/9r27Ltqz/

0
Gino

マイケルが提案したように、ピーターのソリューションは、テキストが異なる行にある場合を除いて、本当に素晴らしいです。マイケルは、問題をよりよく説明するデモを作成しました。 - https://jsfiddle.net/1gzkja90/ そして別の解決策を提案しました。

これを解決するためのより簡単な方法を1つ追加するには、Peterのソリューションでtextareaから改行を削除します。これはうまく機能します。

_//javascript
var noLineBreaks = document.getElementById('mytextarea').replace(/\n/g,'');

//jquery
var noLineBreaks = $('#mytextarea').val().replace(/\n/g,'');_

したがって、Peterのソリューションでは、次のようになります。

utterance.text = $('#mytextarea').val().replace(/\n/g,'');

しかし、それでもスピーチのキャンセルには問題があります。別のシーケンスに進むだけで停止しません。

0
mhlavacka

Chrome拡張機能とアプリケーションを介して、chrome.tts、以来chrome.ttsを使用すると、ウィンドウを閉じるときにトークを停止するウィンドウの代わりに、ブラウザを介して話すことができます。

以下のコードを使用して、大規模なスピーチに関する上記の問題を修正できます。

chrome.tts.speak("Abnormally large string, over 250 characters, etc...");
setInterval(() => { chrome.tts.resume(); }, 100);

私はそれがうまくいくと確信していますが、安全のためにこれをしました:

var largeData = "";
var smallChunks = largeData.match(/.{1,250}/g);
for (var chunk of smallChunks) {
  chrome.tts.speak(chunk, {'enqueue': true});
}

これが誰かを助けることを願っています! my application work morefunctionally、およびepicly

0
Jack Hales