diff --git a/src/plugins/tts/WebTTSEngine.js b/src/plugins/tts/WebTTSEngine.js index b40a67279..708c4be22 100644 --- a/src/plugins/tts/WebTTSEngine.js +++ b/src/plugins/tts/WebTTSEngine.js @@ -16,6 +16,24 @@ export default class WebTTSEngine extends AbstractTTSEngine { return typeof(window.speechSynthesis) !== 'undefined'; } + /** + * @param {SpeechSynthesisVoice} voice + * @returns {boolean} + */ + static isGoodVoice(voice) { + const badVoicePrefixes = [ + // Exclude known novelty/undesired macOS voice families (e.g. joke/system variants) + 'com.apple.speech.synthesis.voice.', + 'com.apple.eloquence.', + 'com.apple.voice.super-compact.', + ]; + + return ( + voice.voiceURI + && !badVoicePrefixes.some(prefix => voice.voiceURI.startsWith(prefix)) + ); + } + /** @param {TTSEngineOptions} options */ constructor(options) { super(options); @@ -80,7 +98,7 @@ export default class WebTTSEngine extends AbstractTTSEngine { /** @override */ getVoices() { - const voices = speechSynthesis.getVoices(); + const voices = speechSynthesis.getVoices().filter(WebTTSEngine.isGoodVoice); if (voices.filter(v => v.default).length != 1) { // iOS bug where the default system voice is sometimes // missing from the list diff --git a/tests/jest/plugins/tts/WebTTSEngine.test.js b/tests/jest/plugins/tts/WebTTSEngine.test.js index 6eeb0fb59..621bec2bb 100644 --- a/tests/jest/plugins/tts/WebTTSEngine.test.js +++ b/tests/jest/plugins/tts/WebTTSEngine.test.js @@ -23,6 +23,24 @@ afterEach(() => { }); describe('WebTTSEngine', () => { + test('isGoodVoice excludes known macOS joke voices', () => { + const voices = [ + { voiceURI: 'com.apple.speech.synthesis.voice.Bad News', name: 'Bad News' }, + { voiceURI: 'com.apple.eloquence.Eddy', name: 'Eddy' }, + { voiceURI: 'com.apple.voice.super-compact.en-US.Samantha', name: 'Samantha (SC)' }, + { voiceURI: 'com.apple.voice.compact.en-US.Samantha', name: 'Samantha' }, + { voiceURI: 'urn:moz-tts:sapi:David', name: 'David' }, + { name: 'No URI Voice' }, + ]; + + const filteredVoices = voices.filter(WebTTSEngine.isGoodVoice); + + expect(filteredVoices).toEqual([ + { voiceURI: 'com.apple.voice.compact.en-US.Samantha', name: 'Samantha' }, + { voiceURI: 'urn:moz-tts:sapi:David', name: 'David' }, + ]); + }); + test('getVoices should include default voice when no actual default', () => { // iOS devices set all the voices to default -_- speechSynthesis.getVoices = () => [