Closed Razorholt closed 2 years ago
Hey @Razorholt, can you elaborate on what is being returned by the method? An empty array or nothing? Also, are you testing this on a physical device or an emulator?
I created a new project and I'm seeing the expected behavior and results.
I tested it in several physical android devices from Samsung to Pixels. Same results:
const GetVoices = async()=>{
let availableVoices = await Speech.getAvailableVoicesAsync();
if (availableVoices){
console.log('avail voices: ', availableVoices)
}
}
returns:
Running application on LM-G710VM.
avail voices: Array []
Is there a permission involved on android maybe?
https://snack.expo.io/@mobshed/e76230
Are you getting anything? I tried on several android devices and got nothing at all. Now, none of them were activated with any carrier, I'm using WIFI only. Would it make a difference?
For me, voices is populated only on the second call. It's always empty on the first call. And this is across all platforms.
For me, voices is populated only on the second call. It's always empty on the first call. And this is across all platforms.
Exactly same here. First call returning an empty array. Next call returns all voices.
Any suggestions, code wise?
Any suggestions, code wise?
Yes. Ugly but worked for me. I have created a 1000ms, 10x loop which tries to get available voices. It resolves at the first try 100% of the times I've tried. So you could just wait 1000ms, but for testing purposes I used the loop. Code following:
const availableVoices = await getAvailableVoicesAsync();
if (availableVoices.length) {
// Everything OK
} else {
// Try again
console.log("No Voices Available. Trying again to get voices...");
console.log("Waiting 1000ms");
let tryAgainResult = [];
for (let x = 0; x <= 10; x++) {
await new Promise((resolve) => {
setTimeout(() => {
console.log("Trying Again. Retried ", x + 1, " times.");
resolve(null);
}, 1000);
});
const _availableVoices = await getAvailableVoicesAsync();
if (_availableVoices.length) {
tryAgainResult = _availableVoices;
console.log("Apparently Had Success Trying. Voices: ", tryAgainResult);
break;
}
}
if (tryAgainResult.length) {
// Voices OK at tryAgainResult
} else {
throw new Error('Impossible to get Available Voices');
}
Wow! Yeah, pretty ugly but whatever as long as it works. Thanks, man!
Ok, I think I got something:
let availableVoices = await Speech.getAvailableVoicesAsync();
availableVoices.length
returns the proper number of characters
availableVoices[0]
returns json details of the first voice
availableVoices
returns nothing
And here is my solution, tested on iOS and Android (physical devices):
useEffect(()=>{
const GetVoices = async()=>{
let availableVoices;
availableVoices = await Speech.getAvailableVoicesAsync();
for (let x = 0; x <= availableVoices.length; x++) {
if (availableVoices[x]) {
console.log(x, ' - ', availableVoices[x])
} else {
break
}
}
}
},[])
Snack here: https://snack.expo.io/@mobshed/expo-speech-getvoices
It's been a while since we've had any activity on this issue, and seeing as it needs more info before we can properly address it, we will be closing it in one month. If you've found a fix, please share it! Otherwise, please provide the info we asked for, especially a reproducible example. Thanks!
Here is my solution that works
const loadVoices = (counter?: number) => {
setTimeout(async () => {
var voices = await Speech.getAvailableVoicesAsync();
if (voices.length > 0)
setTextVoices(voices);
else {
console.log("voices not found")
if (!counter || counter < 10)
loadVoices((counter ?? 0) + 1);
}
}, (counter ?? 1) * 300);
}
I am getting a new issue now. I implemented now a background service. When the app first start the voices get loaded. then I close the app but not the service and start the app again the voices do not load no matter how many time I try.
Dose anyone have the same problem ?
We are having the same issue with our React-native application. Calling Speech.getAvailableVoicesAsync(); in a web build is fine and provides a list of voices. However, calling in an emulator or on a phone running the expo live bundle results in an empty array. I've tried all of the methods listed above to trick the call into populating the list, but none worked.
We are having the same issue with our React-native application. Calling Speech.getAvailableVoicesAsync(); in a web build is fine and provides a list of voices. However, calling in an emulator or on a phone running the expo live bundle results in an empty array. I've tried all of the methods listed above to trick the call into populating the list, but none worked.
You won't get any voices from an emulator. It has to be a real device
We are having the same issue with our React-native application. Calling Speech.getAvailableVoicesAsync(); in a web build is fine and provides a list of voices. However, calling in an emulator or on a phone running the expo live bundle results in an empty array. I've tried all of the methods listed above to trick the call into populating the list, but none worked.
You won't get any voices from an emulator. It has to be a real device
I also ran this on a real phone from the expo QR code. Would this in effect be the same as an emulator?
useEffect(() => { const GetVoices = async () => { let availableVoices; try { availableVoices = await Speech.getAvailableVoicesAsync().then((all) => { if (all.length === 0) { GetVoices(); } else { console.log(all); } }); } catch (error) { console.log("error is : ", error); } }; GetVoices(); }, []);
This issue is stale because it has been open for 60 days with no activity. If there is no activity in the next 7 days, the issue will be closed.
The issue is still there friend, you cant close it.
Yes issue persists
This issue is stale because it has been open for 60 days with no activity. If there is no activity in the next 7 days, the issue will be closed.
This issue was closed because it has been inactive for 7 days since being marked as stale. Please open a new issue if you believe you are encountering a related problem.
Here's my workaround:
If the first call to Speech.getAvailableVoicesAsync
returns an empty array, the function will wait 1 second and try again, up to 10 times. This addresses the issue where the first call to getAvailableVoicesAsync
often returns an empty array on Android.
// LanguageSelectComponent.tsx
import React, { useEffect } from "react";
import { Select, CheckIcon, HStack, Text } from "native-base";
import * as Speech from "expo-speech";
import { LanguageInfo, getLanguageNames, sortLanguages } from "../lib/language";
interface LanguageSelectComponentProps {
initialLanguage: string;
onLanguageChange: (language: string) => void;
}
const LanguageSelectComponent: React.FC<LanguageSelectComponentProps> = ({
initialLanguage,
onLanguageChange,
}) => {
const [languages, setLanguages] = React.useState<LanguageInfo[]>([]);
const getAvailableVoices = async () => {
let voices = await Speech.getAvailableVoicesAsync();
if (voices.length === 0) {
for (let i = 0; i < 10; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
voices = await Speech.getAvailableVoicesAsync();
if (voices.length > 0) break;
}
}
const uniqueLanguages = Array.from(
new Set(voices.map((voice) => voice.language)),
);
return uniqueLanguages;
};
useEffect(() => {
getAvailableVoices()
.then((langs) => {
const languageNames = getLanguageNames(langs);
const preferredLanguages = [
"en-US",
"en",
"sr-RS",
"sr",
"hr-HR",
"bs",
];
const sortedLanguages = sortLanguages(
languageNames,
preferredLanguages,
);
setLanguages(sortedLanguages);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
<HStack space={2} alignItems="center">
<Text w="30%" textAlign="center">
Language:
</Text>
<Select
selectedValue={initialLanguage}
minWidth="200"
accessibilityLabel="Choose Language"
placeholder="Choose Language"
_selectedItem={{
bg: "teal.600",
endIcon: <CheckIcon size="5" />,
}}
mt={1}
onValueChange={onLanguageChange}
>
{languages.map((lang) => (
<Select.Item label={lang.label} value={lang.code} key={lang.code} />
))}
</Select>
</HStack>
);
};
export default LanguageSelectComponent;
// language.ts
import languageTags from 'language-tags';
interface LanguageInfo {
code: string;
label: string;
}
const getLanguageNames = (languages: string[]): LanguageInfo[] => {
return languages
.map((code) => ({
code,
language: languageTags(code).language(),
region: languageTags(code).region(),
}))
.filter(({ language }) => language)
.map(({ code, language, region }) => {
const regionName = region?.descriptions()[0];
return {
code,
label: region
? `${language!.descriptions()[0]} (${regionName})`
: `${language!.descriptions()[0]}`,
};
});
};
function sortLanguages(languageNames: LanguageInfo[], prefferedLanguages: string[]) {
return languageNames
// make a copy of the array
.concat()
// first sort alphabetically by language name
.sort((a, b) => {
if (a.label < b.label) {
return -1;
} else if (a.label > b.label) {
return 1;
} else {
return 0;
}
})
// next sort by preffered languages
.sort((a, b) => {
const aIndex = prefferedLanguages.indexOf(a.code);
const bIndex = prefferedLanguages.indexOf(b.code);
if (aIndex === -1 && bIndex === -1) {
return 0;
} else if (aIndex === -1) {
return 1;
} else if (bIndex === -1) {
return -1;
} else {
return aIndex - bIndex;
}
});
}
export { getLanguageNames, sortLanguages, LanguageInfo };
No solution yet? This loop to search voices 10 times sometimes does not work.
I encountered a similar situation. I found that it was because I turned on the foreground service. That is to say, once I turned on a foreground service, I would no longer be able to get the available list after reopening the app. It was always an empty list. BTW, I start the foreground service via react-native-background-actions
. Can anyone help?
Does getAvailableVoicesAsync() work on Android? I get nothing when querying the list of voices. Works fine on iOS.
const availableVoices = await Speech.getAvailableVoicesAsync();