Skip to content

Instantly share code, notes, and snippets.

@woollsta
Last active December 25, 2023 10:45
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 13 You must be signed in to fork a gist
  • Save woollsta/2d146f13878a301b36d7 to your computer and use it in GitHub Desktop.
Save woollsta/2d146f13878a301b36d7 to your computer and use it in GitHub Desktop.
Fixes an issue with Google Chrome Speech Synthesis where long texts pause mid-speaking. The function takes in a speechUtterance object and intelligently chunks it into smaller blocks of text that are stringed together one after the other. Basically, you can play any length of text. See http://stackoverflow.com/questions/21947730/chrome-speech-sy…
/**
* 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);
};
@Michael4824
Copy link

This works great if you only have one utterance, but if you already have a queue of utterances, it puts the new chunk at the end of the queue, and thus out of order. Example: https://jsfiddle.net/1gzkja90/

@sixman9
Copy link

sixman9 commented Mar 17, 2015

@
Thank you for you recent comments on StackOverflow regarding your breakdown of the out-of-order speech issue (@JSFiddle), I think in your modesty you forgot to list your own, correctly-ordering, implementation of this speech chunking utility, over at JSFiddle => http://jsfiddle.net/vqvyjzq4 .

Cheers

@riccardovittoria
Copy link

Hi! Is there a way to stop the backlog of previous speechsynthesisutterances when the speechUtteranceChunker function is called again?
Thank you very much

@000panther
Copy link

Hello! Good Job. We are using speechSynthesis via cordova on IOS, where logging some utterances causes the logger to die. We found out that just storing a reference on the utterance somewhere in your js (like in an array) also works, without spamming the Log! We clean up the array on speechSynthesis cancel - so no memory leak.

@citynorman
Copy link

Thanks guys! Btw I've had trouble setting the language. This edit did the job:
newUtt = new SpeechSynthesisUtterance(chunk);
newUtt.lang = utt.lang;

@citynorman
Copy link

Question though: why does it read out "dot"?

@nattatorn-dev
Copy link

Thank, It's helpful for me

@hsed
Copy link

hsed commented Mar 18, 2017

Thanks, nice helpful script but I found 2 issues (on chrome 56):

  • It was reading "DOT" as the start of the second sentence.
  • End event wasn't firing due to "...cannot read [0] of null.." error.

This has been fixed here: https://gist.github.com/hsed/ef4a2d17f76983588cb6d2a11d4566d6.

@carson-katri
Copy link

I modified it to use my selected voice which wasn't working by adding newUtt.voice = utt.voice; after initializing the newUtt from the chunk.

However, this caused timing issues. I have pauses in between chunks. It sounds un-natural.

@sharjeelfaiq
Copy link

Hello author! I'm putting an argument based on my fresh experience with speechSynthesis. I'm trying to understand your code but most of the things are not giving sense to me. I want to use this code to make my following code speak the given text up to the end. Can you guide me on how to do this?

const read = () => {
            const textArea = document.getElementById("textarea");
            const btn = document.getElementById("btn");
            const input = textArea.value;
            const msg = new SpeechSynthesisUtterance(input);
            window.speechSynthesis.speak(msg);
        }
const cancel = () => {
            window.speechSynthesis.cancel();
        }

THANKS IN ADVANCE!

@Rohit-Dhende-Minddeft
Copy link

This works perfectly on mobile as well, but how can i have control over resume and cancel methods? like on click the content is reading but i want some additional functionality also. I have created some states and based on that it should read my content. Like, isArticle = "playing" || "paused" || "resumed" || "canceled". playing will speak. paused will pause and canceled with stop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment