Web speech API: Consistently get the supported speech synthesis voices on iOS safari

2024/2/27 9:25:46

I'm trying to get the list of supported speech synthesis voices on iOS Safari.

As per the API, I should be able to get an array of voices by calling:

window.speechSynthesis.getVoices();

Sometimes this gives me list of voices, other times it doesn't. See the following jsfiddle: https://jsfiddle.net/sq7xf327/

If I open this on my iPhone 5 (iOS 8.1.3), I don't get back consistent results. Sometimes I get back all 37 voices, and other times I get returned 0 voices. If you keep on refreshing it sporadically displays either 37 or 0.

I know that in Chrome you can add an event listener to the

window.speechSynthesis.voiceschanged 

event to know when the voices have loaded, but this event is not supported in Safari.

A trick I've tried is to check periodically:

var timer = setInterval(function () {window.voices_ = window.speechSynthesis.getVoices();if (window.voices_.length > 0) {clearInterval(timer);}
}, 1000);

This has also not given me consistent results.

Any idea how I can reliably and consistently get the supported speech synthesis voices on iOS Safari?

Answer

I also encountered this and reported it as a bug to Apple. As of today the bug report is still open.

What I ended up doing as workaround was to hard-code an array of the 37 voices. If speechSynthesis.getVoices() returns an empty array then use the hard-coded array instead.

var _voices = [];// iOS 8
var _iOSvoices = [{name: "pt-BR", voiceURI: "pt-BR", lang: "pt-BR", localService: true, default: true},{name: "fr-CA", voiceURI: "fr-CA", lang: "fr-CA", localService: true, default: true},{name: "sk-SK", voiceURI: "sk-SK", lang: "sk-SK", localService: true, default: true},{name: "th-TH", voiceURI: "th-TH", lang: "th-TH", localService: true, default: true},{name: "ro-RO", voiceURI: "ro-RO", lang: "ro-RO", localService: true, default: true},{name: "no-NO", voiceURI: "no-NO", lang: "no-NO", localService: true, default: true},{name: "fi-FI", voiceURI: "fi-FI", lang: "fi-FI", localService: true, default: true},{name: "pl-PL", voiceURI: "pl-PL", lang: "pl-PL", localService: true, default: true},{name: "de-DE", voiceURI: "de-DE", lang: "de-DE", localService: true, default: true},{name: "nl-NL", voiceURI: "nl-NL", lang: "nl-NL", localService: true, default: true},{name: "id-ID", voiceURI: "id-ID", lang: "id-ID", localService: true, default: true},{name: "tr-TR", voiceURI: "tr-TR", lang: "tr-TR", localService: true, default: true},{name: "it-IT", voiceURI: "it-IT", lang: "it-IT", localService: true, default: true},{name: "pt-PT", voiceURI: "pt-PT", lang: "pt-PT", localService: true, default: true},{name: "fr-FR", voiceURI: "fr-FR", lang: "fr-FR", localService: true, default: true},{name: "ru-RU", voiceURI: "ru-RU", lang: "ru-RU", localService: true, default: true},{name: "es-MX", voiceURI: "es-MX", lang: "es-MX", localService: true, default: true},{name: "zh-HK", voiceURI: "zh-HK", lang: "zh-HK", localService: true, default: true},{name: "sv-SE", voiceURI: "sv-SE", lang: "sv-SE", localService: true, default: true},{name: "hu-HU", voiceURI: "hu-HU", lang: "hu-HU", localService: true, default: true},{name: "zh-TW", voiceURI: "zh-TW", lang: "zh-TW", localService: true, default: true},{name: "es-ES", voiceURI: "es-ES", lang: "es-ES", localService: true, default: true},{name: "zh-CN", voiceURI: "zh-CN", lang: "zh-CN", localService: true, default: true},{name: "nl-BE", voiceURI: "nl-BE", lang: "nl-BE", localService: true, default: true},{name: "en-GB", voiceURI: "en-GB", lang: "en-GB", localService: true, default: true},{name: "ar-SA", voiceURI: "ar-SA", lang: "ar-SA", localService: true, default: true},{name: "ko-KR", voiceURI: "ko-KR", lang: "ko-KR", localService: true, default: true},{name: "cs-CZ", voiceURI: "cs-CZ", lang: "cs-CZ", localService: true, default: true},{name: "en-ZA", voiceURI: "en-ZA", lang: "en-ZA", localService: true, default: true},{name: "en-AU", voiceURI: "en-AU", lang: "en-AU", localService: true, default: true},{name: "da-DK", voiceURI: "da-DK", lang: "da-DK", localService: true, default: true},{name: "en-US", voiceURI: "en-US", lang: "en-US", localService: true, default: true},{name: "en-IE", voiceURI: "en-IE", lang: "en-IE", localService: true, default: true},{name: "he-IL", voiceURI: "he-IL", lang: "he-IL", localService: true, default: true},{name: "hi-IN", voiceURI: "hi-IN", lang: "hi-IN", localService: true, default: true},{name: "el-GR", voiceURI: "el-GR", lang: "el-GR", localService: true, default: true},{name: "ja-JP", voiceURI: "ja-JP", lang: "ja-JP", localService: true, default: true}
];function populateVoices() {// wait firstvar watch = setTimeout(function() {_voices = speechSynthesis.getVoices();if (_voices.length === 0) {// use hard-coded list because speechSynthesis.getVoices() didn't work_voices = _iOSvoices;}clearTimeout(watch);}, 100);
}

UPDATE

iOS 9 is somewhat better at this if you remove the delay.

function _populateVoices() {_voices = speechSynthesis.getVoices();if (_voices.length === 0) {// use hard-coded list because speechSynthesis.getVoices() didn't work_voices = _iOS9voices;}
}var _iOS9voices = [{ name: "Maged", voiceURI: "com.apple.ttsbundle.Maged-compact", lang: "ar-SA", localService: true, "default": true },{ name: "Zuzana", voiceURI: "com.apple.ttsbundle.Zuzana-compact", lang: "cs-CZ", localService: true, "default": true },{ name: "Sara", voiceURI: "com.apple.ttsbundle.Sara-compact", lang: "da-DK", localService: true, "default": true },{ name: "Anna", voiceURI: "com.apple.ttsbundle.Anna-compact", lang: "de-DE", localService: true, "default": true },{ name: "Melina", voiceURI: "com.apple.ttsbundle.Melina-compact", lang: "el-GR", localService: true, "default": true },{ name: "Karen", voiceURI: "com.apple.ttsbundle.Karen-compact", lang: "en-AU", localService: true, "default": true },{ name: "Daniel", voiceURI: "com.apple.ttsbundle.Daniel-compact", lang: "en-GB", localService: true, "default": true },{ name: "Moira", voiceURI: "com.apple.ttsbundle.Moira-compact", lang: "en-IE", localService: true, "default": true },{ name: "Samantha (Enhanced)", voiceURI: "com.apple.ttsbundle.Samantha-premium", lang: "en-US", localService: true, "default": true },{ name: "Samantha", voiceURI: "com.apple.ttsbundle.Samantha-compact", lang: "en-US", localService: true, "default": true },{ name: "Tessa", voiceURI: "com.apple.ttsbundle.Tessa-compact", lang: "en-ZA", localService: true, "default": true },{ name: "Monica", voiceURI: "com.apple.ttsbundle.Monica-compact", lang: "es-ES", localService: true, "default": true },{ name: "Paulina", voiceURI: "com.apple.ttsbundle.Paulina-compact", lang: "es-MX", localService: true, "default": true },{ name: "Satu", voiceURI: "com.apple.ttsbundle.Satu-compact", lang: "fi-FI", localService: true, "default": true },{ name: "Amelie", voiceURI: "com.apple.ttsbundle.Amelie-compact", lang: "fr-CA", localService: true, "default": true },{ name: "Thomas", voiceURI: "com.apple.ttsbundle.Thomas-compact", lang: "fr-FR", localService: true, "default": true },{ name: "Carmit", voiceURI: "com.apple.ttsbundle.Carmit-compact", lang: "he-IL", localService: true, "default": true },{ name: "Lekha", voiceURI: "com.apple.ttsbundle.Lekha-compact", lang: "hi-IN", localService: true, "default": true },{ name: "Mariska", voiceURI: "com.apple.ttsbundle.Mariska-compact", lang: "hu-HU", localService: true, "default": true },{ name: "Damayanti", voiceURI: "com.apple.ttsbundle.Damayanti-compact", lang: "id-ID", localService: true, "default": true },{ name: "Alice", voiceURI: "com.apple.ttsbundle.Alice-compact", lang: "it-IT", localService: true, "default": true },{ name: "Kyoko", voiceURI: "com.apple.ttsbundle.Kyoko-compact", lang: "ja-JP", localService: true, "default": true },{ name: "Yuna", voiceURI: "com.apple.ttsbundle.Yuna-compact", lang: "ko-KR", localService: true, "default": true },{ name: "Ellen", voiceURI: "com.apple.ttsbundle.Ellen-compact", lang: "nl-BE", localService: true, "default": true },{ name: "Xander", voiceURI: "com.apple.ttsbundle.Xander-compact", lang: "nl-NL", localService: true, "default": true },{ name: "Nora", voiceURI: "com.apple.ttsbundle.Nora-compact", lang: "no-NO", localService: true, "default": true },{ name: "Zosia", voiceURI: "com.apple.ttsbundle.Zosia-compact", lang: "pl-PL", localService: true, "default": true },{ name: "Luciana", voiceURI: "com.apple.ttsbundle.Luciana-compact", lang: "pt-BR", localService: true, "default": true },{ name: "Joana", voiceURI: "com.apple.ttsbundle.Joana-compact", lang: "pt-PT", localService: true, "default": true },{ name: "Ioana", voiceURI: "com.apple.ttsbundle.Ioana-compact", lang: "ro-RO", localService: true, "default": true },{ name: "Milena", voiceURI: "com.apple.ttsbundle.Milena-compact", lang: "ru-RU", localService: true, "default": true },{ name: "Laura", voiceURI: "com.apple.ttsbundle.Laura-compact", lang: "sk-SK", localService: true, "default": true },{ name: "Alva", voiceURI: "com.apple.ttsbundle.Alva-compact", lang: "sv-SE", localService: true, "default": true },{ name: "Kanya", voiceURI: "com.apple.ttsbundle.Kanya-compact", lang: "th-TH", localService: true, "default": true },{ name: "Yelda", voiceURI: "com.apple.ttsbundle.Yelda-compact", lang: "tr-TR", localService: true, "default": true },{ name: "Ting-Ting", voiceURI: "com.apple.ttsbundle.Ting-Ting-compact", lang: "zh-CN", localService: true, "default": true },{ name: "Sin-Ji", voiceURI: "com.apple.ttsbundle.Sin-Ji-compact", lang: "zh-HK", localService: true, "default": true },{ name: "Mei-Jia", voiceURI: "com.apple.ttsbundle.Mei-Jia-compact", lang: "zh-TW", localService: true, "default": true }
];
http://en.ppmy.cn/q/40360.html

Related Q&A

js while(true){} blocks event loop

setInterval(function(){console.log("hello")},2000); while(true){}"hello" never gets printed. I think event loop runs in a different thread, but here it seems like while loop is prev…

Google maps circle: how to trigger an event when moved and how to obtain the new center

I was able to make a circle object as an overlay on my google map v3. I set its editable property to true. The next thing I wanted to do was get the coordinates of the center if the user moves the circ…

Bootstrap 4 - Link to specific tab

I have a Bootstrap 4 tabs set in a Wordpress website and want to link to a specific tab from another page link:Index.php:<a href="<?php echo get_site_url(); ?>/services/#innovation"…

Why is a spread element unsuitable for copying multidimensional arrays?

From mdn: Spread Syntax Note: Typically the spread operators in ES2015 goes one level deep while copying an array. Therefore, they are unsuitable for copying multidimensional arrays. Its the same case …

jQuery hover not triggered when element is programatically moved under the mouse

I have an image with a hover effect (higher opacity when mouse is over it). It works as desired when the mouse moves in and out.However, the image itself is moving (Im periodically changing the css att…

How do I simulate user clicking a link in JQuery?

Now theres quite a lot of similar-looking questions here, but I was wondering, what should I do, if I want not to just change the location of the window, but trigger whatever functions may be bound to …

Discord.js setGame() not working anymore

I have been coding my Discord bot using Discord.JS for about 2 months now and Ive just recently noticed that my bot isnt saying that its playing what Im telling it. When I first coded the bot up until …

How to prevent body from scrolling with push-side menu

I am trying to properly implement a push-side menu plugin (Responsive Menu) into a wordpress theme. Based on SO @Congrim answer, Ive managed to achieve a way to lock the body at scroll when push-menu i…

How to detect if mouse button is held down for a certain amount of time after click

Hi I am quite new to javascript. I want to be able to fire an event when a user clicks on point in canvas(short press), and if holds that click down for 1500 ms, i should have another functionality. I…

JQuery/Javascript or other way Silent Print once only

Anyone knows how to print a file without showing print preview in cross browsers, API or libraries like jQuery or javascript or else.Print a content without print preview (just silent print) Print once…