Chrome Speech Synthesis with longer texts

前端 未结 12 1177
难免孤独
难免孤独 2020-11-30 19:48

I am getting a problem when trying to use Speech Synthesis API in Chrome 33. It works perfectly with a shorter text, but if I try longer text, it just stops in the middle. A

相关标签:
12条回答
  • 2020-11-30 20:25

    Here is what i ended up with, it simply splits my sentences on the period "."

    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);
        }
    }
    
    0 讨论(0)
  • 2020-11-30 20:25

    I ended up chunking up the text and having some intelligence around handling of various punctucations like periods, commas, etc. For example, you don't want to break the text up on a comma if it's part of a number (i.e., $10,000).

    I have tested it and it seems to work on arbitrarily large sets of input and it also appears to work not just on the desktop but on android phones and iphones.

    Set up a github page for the synthesizer at: https://github.com/unk1911/speech

    You can see it live at: http://edeliverables.com/tts/

    0 讨论(0)
  • 2020-11-30 20:26

    2017 and this bug is still around. I happen to understand this problem quite well, being the developer of the award-winning Chrome extension Read Aloud. OK, just kidding about the award winning part.

    1. Your speech will get stuck if it's longer than 15 seconds.
    2. I discover that Chrome uses a 15 second idle timer to decide when to deactivate an extension's event/background page. I believe this is the culprit.

    The workaround I've used is a fairly complicated chunking algorithm that respects punctuation. For Latin languages, I set max chunk size at 36 words. The code is open-source, if you're inclined: https://github.com/ken107/read-aloud/blob/master/js/speech.js (line 144)

    The 36-word limit works well most of the time, staying within 15 seconds. But there'll be cases where it still gets stuck. To recover from that, I use a 16 second timer.

    0 讨论(0)
  • 2020-11-30 20:31

    I've had this issue for a while now with Google Chrome Speech Synthesis. After some investigation, I discovered the following:

    • The breaking of the utterances only happens when the voice is not a native voice,
    • The cutting out usually occurs between 200-300 characters,
    • When it does break you can un-freeze it by doing speechSynthesis.cancel();
    • The 'onend' event sometimes decides not to fire. A quirky work-around to this is to console.log() out the utterance object before speaking it. Also I found wrapping the speak invocation in a setTimeout callback helps smooth these issues out.

    In response to these problems, I have written a function that overcomes the character limit, by chunking the text up into smaller utterances, and playing them one after another. Obviously you'll get some odd sounds sometimes as sentences might be chunked into two separate utterances with a small time delay in between each, however the code will try and split these points at punctuation marks as to make the breaks in sound less obvious.

    Update

    I've made this work-around publicly available at https://gist.github.com/woollsta/2d146f13878a301b36d7#file-chunkify-js. Many thanks to Brett Zamir for his contributions.

    The function:

    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);
    };
    

    How to use it...

    //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');
    });
    

    Hope people find this as useful.

    0 讨论(0)
  • 2020-11-30 20:31

    As Michael proposed, Peter's solutions is really great except when your text is on different lines. Michael created demo to better illustrate the problem with it. - https://jsfiddle.net/1gzkja90/ and proposed another solution.

    To add one maybe simpler way to solve this is to remove line breaks from textarea in Peter's solution and it works just great.

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

    So in Peter's solution it might look the following way :

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

    But still there's problem with canceling the speech. It just goes to another sequence and won't stop.

    0 讨论(0)
  • 2020-11-30 20:31

    new Vue({
      el: "#app",
      data: {
        text: `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.`
      },
    
      methods:{
        stop_reading() {
          const synth = window.speechSynthesis;
          synth.cancel();
        },
    
        talk() {
          const synth = window.speechSynthesis;
          const textInput = this.text;
    
          const utterThis = new SpeechSynthesisUtterance(textInput);
          utterThis.pitch = 0;
          utterThis.rate = 1;
          synth.speak(utterThis);
    
          const resumeInfinity = () => {
            window.speechSynthesis.resume();
            const timeoutResumeInfinity = setTimeout(resumeInfinity, 1000);
          }
          
          utterThis.onstart = () => {
            resumeInfinity();
          };
        }
      }
    })
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    <div id="app">
      <button @click="talk">Speak</button>
      <button @click="stop_reading">Stop</button>
    </div>

    0 讨论(0)
提交回复
热议问题