hotfix: resolve compatibility issues with Android Text-to-Speech API (#1394)
This commit is contained in:
+5
@@ -1,3 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.TTS_SERVICE" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
+7
-2
@@ -3,6 +3,7 @@ package com.readest.native_tts
|
||||
import android.os.Bundle
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.provider.Settings
|
||||
import android.speech.tts.TextToSpeech
|
||||
import android.speech.tts.UtteranceProgressListener
|
||||
import android.speech.tts.Voice
|
||||
@@ -97,7 +98,11 @@ class NativeTTSPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
|
||||
private suspend fun initializeTTS(): Boolean = suspendCancellableCoroutine { continuation ->
|
||||
try {
|
||||
textToSpeech = TextToSpeech(activity) { status ->
|
||||
val preferredEngine = Settings.Secure.getString(
|
||||
activity.contentResolver,
|
||||
Settings.Secure.TTS_DEFAULT_SYNTH
|
||||
)
|
||||
textToSpeech = TextToSpeech(activity, { status ->
|
||||
when (status) {
|
||||
TextToSpeech.SUCCESS -> {
|
||||
setupTTSListener()
|
||||
@@ -111,7 +116,7 @@ class NativeTTSPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
continuation.resume(false) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, preferredEngine)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Exception during TTS initialization", e)
|
||||
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
||||
|
||||
@@ -264,7 +264,7 @@ export class EdgeTTSClient implements TTSClient {
|
||||
const voicesGroup: TTSVoicesGroup = {
|
||||
id: 'edge-tts',
|
||||
name: 'Edge TTS',
|
||||
voices: filteredVoices,
|
||||
voices: filteredVoices.sort(TTSUtils.sortVoicesFunc),
|
||||
disabled: !this.initialized || filteredVoices.length === 0,
|
||||
};
|
||||
|
||||
|
||||
@@ -14,12 +14,25 @@ type TTSEventPayload = {
|
||||
|
||||
const TTSEngines = {
|
||||
default: 'System TTS',
|
||||
msctts: 'Msc TTS',
|
||||
mstrans: 'MSTrans TTS',
|
||||
microsoft: 'Microsoft TTS',
|
||||
bytedance: 'ByteDance TTS',
|
||||
peiyinya: 'PeiYinYa TTS',
|
||||
huoshan: 'HuoShan TTS',
|
||||
sougou: 'Sougou TTS',
|
||||
xiaomi: 'XiaoMi TTS',
|
||||
bdetts: 'BDeTTS',
|
||||
bdotts: 'BDoTTS',
|
||||
vcstts: 'VcsTTS',
|
||||
isstts: 'IssTTS',
|
||||
xfpeiyin: 'XFPeiYin',
|
||||
azure: 'Azure TTS',
|
||||
edgetts: 'Edge TTS',
|
||||
google: 'Google TTS',
|
||||
gemini: 'Gemini TTS',
|
||||
weread: 'WeRead TTS',
|
||||
aispeech: 'Aispeech',
|
||||
} as Record<string, string>;
|
||||
|
||||
export class NativeTTSClient implements TTSClient {
|
||||
@@ -189,7 +202,7 @@ export class NativeTTSClient implements TTSClient {
|
||||
const defaultVoice = preferredVoice
|
||||
? preferredVoice
|
||||
: (await this.getVoices(lang))[0]?.voices[0] || null;
|
||||
return defaultVoice?.id || 'NOT_SET';
|
||||
return defaultVoice?.id || '';
|
||||
}
|
||||
|
||||
async pause() {
|
||||
@@ -244,7 +257,6 @@ export class NativeTTSClient implements TTSClient {
|
||||
const filteredVoices = voices.filter(
|
||||
(v) => v.lang.startsWith(locale) || (lang === 'en' && ['en-US', 'en-GB'].includes(v.lang)),
|
||||
);
|
||||
|
||||
const voiceGroups = new Map<string, TTSVoice[]>();
|
||||
filteredVoices.forEach((voice) => {
|
||||
const { name, lang } = voice;
|
||||
@@ -271,7 +283,7 @@ export class NativeTTSClient implements TTSClient {
|
||||
({
|
||||
id: groupId,
|
||||
name: TTSEngines[groupId] || groupId,
|
||||
voices: voices,
|
||||
voices: voices.sort(TTSUtils.sortVoicesFunc),
|
||||
disabled: !this.initialized || voices.length === 0,
|
||||
}) as TTSVoicesGroup,
|
||||
)
|
||||
|
||||
@@ -61,6 +61,7 @@ export class TTSController extends EventTarget {
|
||||
if (await this.ttsWebClient.init()) {
|
||||
availableClients.push(this.ttsWebClient);
|
||||
}
|
||||
this.ttsClient = availableClients[0] || this.ttsWebClient;
|
||||
const preferredClientName = TTSUtils.getPreferredClient();
|
||||
if (preferredClientName) {
|
||||
const preferredClient = availableClients.find(
|
||||
@@ -70,9 +71,6 @@ export class TTSController extends EventTarget {
|
||||
this.ttsClient = preferredClient;
|
||||
}
|
||||
}
|
||||
if (!this.ttsClient) {
|
||||
this.ttsClient = availableClients[0] || this.ttsWebClient;
|
||||
}
|
||||
this.ttsWebVoices = await this.ttsWebClient.getAllVoices();
|
||||
this.ttsEdgeVoices = await this.ttsEdgeClient.getAllVoices();
|
||||
}
|
||||
@@ -288,26 +286,6 @@ export class TTSController extends EventTarget {
|
||||
const ttsNativeVoices = (await this.ttsNativeClient?.getVoices(lang)) ?? [];
|
||||
|
||||
const voicesGroups = [...ttsNativeVoices, ...ttsEdgeVoices, ...ttsWebVoices];
|
||||
voicesGroups.forEach((group) => {
|
||||
group.voices = group.voices.sort((a, b) => {
|
||||
const aRegion = a.lang.split('-')[1] || '';
|
||||
const bRegion = b.lang.split('-')[1] || '';
|
||||
if (aRegion === bRegion) {
|
||||
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
||||
}
|
||||
if (aRegion === 'CN') return -1;
|
||||
if (bRegion === 'CN') return 1;
|
||||
if (aRegion === 'TW') return -1;
|
||||
if (bRegion === 'TW') return 1;
|
||||
if (aRegion === 'HK') return -1;
|
||||
if (bRegion === 'HK') return 1;
|
||||
if (aRegion === 'US') return -1;
|
||||
if (bRegion === 'US') return 1;
|
||||
if (aRegion === 'GB') return -1;
|
||||
if (bRegion === 'GB') return 1;
|
||||
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
||||
});
|
||||
});
|
||||
return voicesGroups;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { TTSVoice } from './types';
|
||||
|
||||
export class TTSUtils {
|
||||
private static readonly LOCAL_STORAGE_KEY = 'ttsPreferredVoices';
|
||||
private static readonly PREFERRED_CLIENT_KEY = 'preferredClient';
|
||||
@@ -37,4 +39,23 @@ export class TTSUtils {
|
||||
const storedPreferences = localStorage.getItem(this.LOCAL_STORAGE_KEY);
|
||||
return storedPreferences ? JSON.parse(storedPreferences) : {};
|
||||
}
|
||||
|
||||
static sortVoicesFunc(a: TTSVoice, b: TTSVoice): number {
|
||||
const aRegion = a.lang.split('-')[1] || '';
|
||||
const bRegion = b.lang.split('-')[1] || '';
|
||||
if (aRegion === bRegion) {
|
||||
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
||||
}
|
||||
if (aRegion === 'CN') return -1;
|
||||
if (bRegion === 'CN') return 1;
|
||||
if (aRegion === 'TW') return -1;
|
||||
if (bRegion === 'TW') return 1;
|
||||
if (aRegion === 'HK') return -1;
|
||||
if (bRegion === 'HK') return 1;
|
||||
if (aRegion === 'US') return -1;
|
||||
if (bRegion === 'US') return 1;
|
||||
if (aRegion === 'GB') return -1;
|
||||
if (bRegion === 'GB') return 1;
|
||||
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ export class WebSpeechClient implements TTSClient {
|
||||
const voicesGroup: TTSVoicesGroup = {
|
||||
id: 'web-speech-api',
|
||||
name: 'Web TTS',
|
||||
voices,
|
||||
voices: voices.sort(TTSUtils.sortVoicesFunc),
|
||||
disabled: !this.initialized || voices.length === 0,
|
||||
};
|
||||
return [voicesGroup];
|
||||
|
||||
Reference in New Issue
Block a user