From cf03e62f7aae1524d9f7d14b218d4396f2f3e00d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=BCster?= Date: Mon, 6 Dec 2021 13:52:10 +0100 Subject: [PATCH] fix(core): skip resumeInfinity on Android browsers --- docs/bundle.js | 21 +++++++++++++++++---- docs/main.js | 5 ++++- index.js | 21 +++++++++++++++++---- package.json | 2 +- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/docs/bundle.js b/docs/bundle.js index 189f99f..2015b00 100644 --- a/docs/bundle.js +++ b/docs/bundle.js @@ -55,6 +55,8 @@ const internal = { status: 'created' }; +const patches = {}; + /******************************************************************************* * * AVAILABLE WITHOUT INIT @@ -131,11 +133,21 @@ const detectFeatures = () => { features[name] = hasUtterance && hasProperty(features.speechSynthesisUtterance.prototype, name); }); + // not published to the outside + patches.skipInfinity = skipInfinity(); + return features }; +/** @private **/ const hasProperty = (target = {}, prop) => Object.hasOwnProperty.call(target, prop) || prop in target || !!target[prop]; +/** @private **/ +const skipInfinity = () => { + const ua = (scope.navigator || {}).userAgent || ''; + return /android/i.test(ua) +}; + /** * Common prefixes for browsers that tend to implement their custom names for * certain parts of their API. @@ -634,9 +646,9 @@ EasySpeech.speak = ({ text, voice, pitch, rate, volume, ...handlers }) => { // https://stackoverflow.com/questions/21947730/chrome-speech-synthesis-with-longer-texts // // XXX: this apparently works only on chrome desktop, while it breaks chrome - // mobile (android), which is why we skip this on these devices + // mobile (android), so we need to detect chrome desktop utterance.addEventListener('start', () => { - if (internal.speechSynthesisVoice) { + if (patches.skipInfinity !== true) { resumeInfinity(utterance); } }); @@ -680,14 +692,15 @@ let timeoutResumeInfinity; * @param target */ function resumeInfinity (target) { + // prevent memory-leak in case utterance is deleted, while this is ongoing if (!target && timeoutResumeInfinity) { debug('force-clear timeout'); - return clearTimeout(timeoutResumeInfinity) + return scope.clearTimeout(timeoutResumeInfinity) } internal.speechSynthesis.pause(); internal.speechSynthesis.resume(); - timeoutResumeInfinity = setTimeout(function () { + timeoutResumeInfinity = scope.setTimeout(function () { resumeInfinity(target); }, 5000); } diff --git a/docs/main.js b/docs/main.js index 43405df..9c0e9af 100644 --- a/docs/main.js +++ b/docs/main.js @@ -149,7 +149,10 @@ async function populateVoices (initialized) { .sort((a, b) => a.name.localeCompare(b.name)) filteredVoices.forEach((voice, index) => { - const option = textNode(voice.name, 'option') + const service = voice.localService ? 'local' : 'remote' + const isDefault = voice.default ? '[DEFAULT]' : '' + const voiceName = `${isDefault}${voice.name} - ${voice.voiceURI} (${service})` + const option = textNode(voiceName, 'option') option.setAttribute('value', index.toString(10)) inputs.voice.appendChild(option) }) diff --git a/index.js b/index.js index 2eeb51a..48cd440 100644 --- a/index.js +++ b/index.js @@ -55,6 +55,8 @@ const internal = { status: 'created' } +const patches = {} + /******************************************************************************* * * AVAILABLE WITHOUT INIT @@ -131,11 +133,21 @@ const detectFeatures = () => { features[name] = hasUtterance && hasProperty(features.speechSynthesisUtterance.prototype, name) }) + // not published to the outside + patches.skipInfinity = skipInfinity() + return features } +/** @private **/ const hasProperty = (target = {}, prop) => Object.hasOwnProperty.call(target, prop) || prop in target || !!target[prop] +/** @private **/ +const skipInfinity = () => { + const ua = (scope.navigator || {}).userAgent || '' + return /android/i.test(ua) +} + /** * Common prefixes for browsers that tend to implement their custom names for * certain parts of their API. @@ -634,9 +646,9 @@ EasySpeech.speak = ({ text, voice, pitch, rate, volume, ...handlers }) => { // https://stackoverflow.com/questions/21947730/chrome-speech-synthesis-with-longer-texts // // XXX: this apparently works only on chrome desktop, while it breaks chrome - // mobile (android), which is why we skip this on these devices + // mobile (android), so we need to detect chrome desktop utterance.addEventListener('start', () => { - if (internal.speechSynthesisVoice) { + if (patches.skipInfinity !== true) { resumeInfinity(utterance) } }) @@ -680,14 +692,15 @@ let timeoutResumeInfinity * @param target */ function resumeInfinity (target) { + // prevent memory-leak in case utterance is deleted, while this is ongoing if (!target && timeoutResumeInfinity) { debug('force-clear timeout') - return clearTimeout(timeoutResumeInfinity) + return scope.clearTimeout(timeoutResumeInfinity) } internal.speechSynthesis.pause() internal.speechSynthesis.resume() - timeoutResumeInfinity = setTimeout(function () { + timeoutResumeInfinity = scope.setTimeout(function () { resumeInfinity(target) }, 5000) } diff --git a/package.json b/package.json index 7741a25..fe77fe8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "easy-speech", - "version": "1.1.0", + "version": "1.1.1", "description": "Cross browser Speech Synthesis", "type": "module", "main": "index.js",