From 468d1597d3ddba3a300cd1482391793b1f0e68d1 Mon Sep 17 00:00:00 2001 From: Enzo MENEGALDO Date: Wed, 6 Nov 2024 15:33:55 +0100 Subject: [PATCH 1/6] Implement support of QUEUE_ADD strategy in android --- .../community/tts/SpeakResultCallback.java | 2 +- .../community/tts/TextToSpeech.java | 83 +++++++++++++------ .../community/tts/TextToSpeechPlugin.java | 8 +- 3 files changed, 63 insertions(+), 30 deletions(-) diff --git a/android/src/main/java/com/getcapacitor/community/tts/SpeakResultCallback.java b/android/src/main/java/com/getcapacitor/community/tts/SpeakResultCallback.java index d12fa7c..77f8ba4 100644 --- a/android/src/main/java/com/getcapacitor/community/tts/SpeakResultCallback.java +++ b/android/src/main/java/com/getcapacitor/community/tts/SpeakResultCallback.java @@ -3,5 +3,5 @@ public interface SpeakResultCallback { void onDone(); void onError(); - void onRangeStart(int start, int end, String spokenWord); + void onRangeStart(int start, int end); } diff --git a/android/src/main/java/com/getcapacitor/community/tts/TextToSpeech.java b/android/src/main/java/com/getcapacitor/community/tts/TextToSpeech.java index ee48c64..786f4b6 100644 --- a/android/src/main/java/com/getcapacitor/community/tts/TextToSpeech.java +++ b/android/src/main/java/com/getcapacitor/community/tts/TextToSpeech.java @@ -15,6 +15,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Locale; +import java.util.Map; import java.util.Set; public class TextToSpeech implements android.speech.tts.TextToSpeech.OnInitListener { @@ -25,11 +26,44 @@ public class TextToSpeech implements android.speech.tts.TextToSpeech.OnInitListe private android.speech.tts.TextToSpeech tts = null; private int initializationStatus; private JSObject[] supportedVoices = null; + private Map requests = new HashMap(); TextToSpeech(Context context) { this.context = context; try { tts = new android.speech.tts.TextToSpeech(context, this); + tts.setOnUtteranceProgressListener( + new UtteranceProgressListener() { + @Override + public void onStart(String utteranceId) {} + + @Override + public void onDone(String utteranceId) { + SpeakResultCallback callback = requests.get(utteranceId); + if(callback != null) { + callback.onDone(); + requests.remove(utteranceId); + } + } + + @Override + public void onError(String utteranceId) { + SpeakResultCallback callback = requests.get(utteranceId); + if(callback != null) { + callback.onError(); + requests.remove(utteranceId); + } + } + + @Override + public void onRangeStart(String utteranceId, int start, int end, int frame) { + SpeakResultCallback callback = requests.get(utteranceId); + if(callback != null) { + callback.onRangeStart(start, end); + } + } + } + ); } catch (Exception ex) { Log.d(LOG_TAG, ex.getLocalizedMessage()); } @@ -40,6 +74,19 @@ public void onInit(int status) { this.initializationStatus = status; } + public void speak( + String text, + String lang, + float rate, + float pitch, + float volume, + int voice, + String callbackId, + SpeakResultCallback resultCallback + ) { + speak(text, lang, rate, pitch, volume, voice, callbackId, resultCallback, android.speech.tts.TextToSpeech.QUEUE_FLUSH); + } + public void speak( String text, String lang, @@ -48,31 +95,13 @@ public void speak( float volume, int voice, String callbackId, - SpeakResultCallback resultCallback + SpeakResultCallback resultCallback, + int queueStrategy ) { - tts.stop(); - tts.setOnUtteranceProgressListener( - new UtteranceProgressListener() { - @Override - public void onStart(String utteranceId) {} - - @Override - public void onDone(String utteranceId) { - resultCallback.onDone(); - } - - @Override - public void onError(String utteranceId) { - resultCallback.onError(); - } - - @Override - public void onRangeStart(String utteranceId, int start, int end, int frame) { - String spokenWord = text.substring(start, end); - resultCallback.onRangeStart(start, end, spokenWord); - } - } - ); + if(queueStrategy != android.speech.tts.TextToSpeech.QUEUE_ADD) { + stop(); + } + requests.put(callbackId, resultCallback); Locale locale = Locale.forLanguageTag(lang); @@ -92,8 +121,7 @@ public void onRangeStart(String utteranceId, int start, int end, int frame) { int resultCode = tts.setVoice(newVoice); } } - - tts.speak(text, android.speech.tts.TextToSpeech.QUEUE_FLUSH, ttsParams, callbackId); + tts.speak(text, queueStrategy, ttsParams, callbackId); } else { HashMap ttsParams = new HashMap<>(); ttsParams.put(android.speech.tts.TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, callbackId); @@ -102,12 +130,13 @@ public void onRangeStart(String utteranceId, int start, int end, int frame) { tts.setLanguage(locale); tts.setSpeechRate(rate); tts.setPitch(pitch); - tts.speak(text, android.speech.tts.TextToSpeech.QUEUE_FLUSH, ttsParams); + tts.speak(text, queueStrategy, ttsParams); } } public void stop() { tts.stop(); + requests.clear(); } public JSArray getSupportedLanguages() { diff --git a/android/src/main/java/com/getcapacitor/community/tts/TextToSpeechPlugin.java b/android/src/main/java/com/getcapacitor/community/tts/TextToSpeechPlugin.java index f41c8c5..79e6314 100644 --- a/android/src/main/java/com/getcapacitor/community/tts/TextToSpeechPlugin.java +++ b/android/src/main/java/com/getcapacitor/community/tts/TextToSpeechPlugin.java @@ -1,6 +1,8 @@ package com.getcapacitor.community.tts; import android.util.Base64; +import android.util.Log; + import com.getcapacitor.JSArray; import com.getcapacitor.JSObject; import com.getcapacitor.Plugin; @@ -37,6 +39,7 @@ public void speak(PluginCall call) { float pitch = call.getFloat("pitch", 1.0f); float volume = call.getFloat("volume", 1.0f); int voice = call.getInt("voice", -1); + int queueStrategy = call.getInt("queueStrategy", 0); boolean isLanguageSupported = implementation.isLanguageSupported(lang); if (!isLanguageSupported) { @@ -56,17 +59,18 @@ public void onError() { } @Override - public void onRangeStart(int start, int end, String spokenWord) { + public void onRangeStart(int start, int end) { JSObject ret = new JSObject(); ret.put("start", start); ret.put("end", end); + String spokenWord = text.substring(start, end); ret.put("spokenWord", spokenWord); notifyListeners("onRangeStart", ret); } }; try { - implementation.speak(text, lang, rate, pitch, volume, voice, call.getCallbackId(), resultCallback); + implementation.speak(text, lang, rate, pitch, volume, voice, call.getCallbackId(), resultCallback, queueStrategy); } catch (Exception ex) { call.reject(ex.getLocalizedMessage()); } From 637469f2c9ad4318b13260ab890d96a9a1ab70ab Mon Sep 17 00:00:00 2001 From: "enzo.menegald" Date: Thu, 7 Nov 2024 11:13:22 +0100 Subject: [PATCH 2/6] Implement queue strategy in ios --- ios/Plugin/TextToSpeech.swift | 10 ++++++++-- ios/Plugin/TextToSpeechPlugin.swift | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ios/Plugin/TextToSpeech.swift b/ios/Plugin/TextToSpeech.swift index dc11b63..6702020 100644 --- a/ios/Plugin/TextToSpeech.swift +++ b/ios/Plugin/TextToSpeech.swift @@ -1,6 +1,10 @@ import AVFoundation import Capacitor +enum QUEUE_STRATEGY: Int { + case QUEUE_ADD = 1, QUEUE_FLUSH = 0 +} + @objc public class TextToSpeech: NSObject, AVSpeechSynthesizerDelegate { let synthesizer = AVSpeechSynthesizer() var calls: [CAPPluginCall] = [] @@ -29,8 +33,10 @@ import Capacitor self.resolveCurrentCall() } - @objc public func speak(_ text: String, _ lang: String, _ rate: Float, _ pitch: Float, _ category: String, _ volume: Float, _ voice: Int, _ call: CAPPluginCall) throws { - self.synthesizer.stopSpeaking(at: .immediate) + @objc public func speak(_ text: String, _ lang: String, _ rate: Float, _ pitch: Float, _ category: String, _ volume: Float, _ voice: Int, _ queueStrategy: Int, _ call: CAPPluginCall) throws { + if(queueStrategy == QUEUE_STRATEGY.QUEUE_FLUSH.rawValue) { + self.synthesizer.stopSpeaking(at: .immediate) + } self.calls.append(call) let utterance = AVSpeechUtterance(string: text) diff --git a/ios/Plugin/TextToSpeechPlugin.swift b/ios/Plugin/TextToSpeechPlugin.swift index 25109f1..903c7c6 100644 --- a/ios/Plugin/TextToSpeechPlugin.swift +++ b/ios/Plugin/TextToSpeechPlugin.swift @@ -20,6 +20,7 @@ public class TextToSpeechPlugin: CAPPlugin { let volume = call.getFloat("volume") ?? 1.0 let voice = call.getInt("voice") ?? -1 let category = call.getString("category") ?? "ambient" + let queueStrategy = call.getInt("queueStrategy") ?? 0 let isLanguageSupported = implementation.isLanguageSupported(lang) guard isLanguageSupported else { @@ -28,7 +29,7 @@ public class TextToSpeechPlugin: CAPPlugin { } do { - try implementation.speak(text, lang, rate, pitch, category, volume, voice, call) + try implementation.speak(text, lang, rate, pitch, category, volume, voice, queueStrategy, call) } catch { call.reject(error.localizedDescription) } From 5265fbf5351d3165b379281c01e0340824c908c8 Mon Sep 17 00:00:00 2001 From: Enzo MENEGALDO Date: Thu, 7 Nov 2024 11:22:29 +0100 Subject: [PATCH 3/6] Update doc --- README.md | 2 ++ src/definitions.ts | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/README.md b/README.md index f8f7897..4bb2a9f 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ const speak = async () => { pitch: 1.0, volume: 1.0, category: 'ambient', + queueStrategy: 1 }); }; @@ -201,6 +202,7 @@ addListener(eventName: 'onRangeStart', listenerFunc: (info: { start: number; end | **`volume`** | number | The volume that the utterance will be spoken at. | 1.0 | | **`voice`** | number | The index of the selected voice that will be used to speak the utterance. Possible voices can be queried using `getSupportedVoices`. | | | **`category`** | string | Select the iOS Audio session category. Possible values: `ambient` and `playback`. Use `playback` to play audio even when the app is in the background. Only available for iOS. | "ambient" | +| **`queueStrategy`** | number | Select the strategy to adopt when several requests to speak overlap. Possible values: `0` and `1`. Use `0` to stop the current request when a new request is sent. Use `1` to buffer the speech request. The request will be executed when all previous requests have been completed. | 0 | #### SpeechSynthesisVoice diff --git a/src/definitions.ts b/src/definitions.ts index 7e56a4e..a703f4e 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -87,6 +87,15 @@ export interface TTSOptions { * @default "ambient" */ category?: string; + /** + * Select the strategy to adopt when several requests to speak overlap. + * Possible values: `0` and `1`. + * Use `0` to stop the current request when a new request is sent. + * Use `1` to buffer the speech request. The request will be executed when all previous requests have been completed. + * + * @default 0 + */ + queueStrategy?: number; } /** From 39ad20bdbf11379379f45b1cf48d549a211449e2 Mon Sep 17 00:00:00 2001 From: Enzo MENEGALDO Date: Thu, 7 Nov 2024 14:35:54 +0100 Subject: [PATCH 4/6] Apply review: Update definitions.ts --- README.md | 14 +++++++++++++- src/definitions.ts | 19 ++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4bb2a9f..0f520eb 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ const isLanguageSupported = async (lang: string) => { * [`openInstall()`](#openinstall) * [`addListener('onRangeStart', ...)`](#addlisteneronrangestart) * [Interfaces](#interfaces) +* [Enums](#enums) @@ -202,7 +203,7 @@ addListener(eventName: 'onRangeStart', listenerFunc: (info: { start: number; end | **`volume`** | number | The volume that the utterance will be spoken at. | 1.0 | | **`voice`** | number | The index of the selected voice that will be used to speak the utterance. Possible voices can be queried using `getSupportedVoices`. | | | **`category`** | string | Select the iOS Audio session category. Possible values: `ambient` and `playback`. Use `playback` to play audio even when the app is in the background. Only available for iOS. | "ambient" | -| **`queueStrategy`** | number | Select the strategy to adopt when several requests to speak overlap. Possible values: `0` and `1`. Use `0` to stop the current request when a new request is sent. Use `1` to buffer the speech request. The request will be executed when all previous requests have been completed. | 0 | +| **`queueStrategy`** | QueueStrategy | Select the strategy to adopt when several requests to speak overlap. | QueueStrategy.Flush | 5.1.0 | #### SpeechSynthesisVoice @@ -224,6 +225,17 @@ The SpeechSynthesisVoice interface represent | ------------ | ----------------------------------------- | | **`remove`** | () => Promise<void> | + +### Enums + + +#### QueueStrategy + +| Members | Value | Description | +| ----------- | -------------- | ---------------------------------------------------------------------------------------------------------------------- | +| **`Add`** | 0 | Use `Add` to stop the current request when a new request is sent. | +| **`Flush`** | 1 | Use `Flush` to buffer the speech request. The request will be executed when all previous requests have been completed. | + ## Changelog diff --git a/src/definitions.ts b/src/definitions.ts index a703f4e..1323fe6 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -40,6 +40,17 @@ export interface TextToSpeechPlugin { ): Promise; } +export enum QueueStrategy { + /** + * Use `Add` to stop the current request when a new request is sent. + */ + Add = 0, + /** + * Use `Flush` to buffer the speech request. The request will be executed when all previous requests have been completed. + */ + Flush = 1 +} + export interface TTSOptions { /** * The text that will be synthesised when the utterance is spoken. @@ -89,13 +100,11 @@ export interface TTSOptions { category?: string; /** * Select the strategy to adopt when several requests to speak overlap. - * Possible values: `0` and `1`. - * Use `0` to stop the current request when a new request is sent. - * Use `1` to buffer the speech request. The request will be executed when all previous requests have been completed. * - * @default 0 + * @since 5.1.0 + * @default QueueStrategy.Flush */ - queueStrategy?: number; + queueStrategy?: QueueStrategy; } /** From 5123bfa5bf2ec92edd1d8825a5d526fda9fabea8 Mon Sep 17 00:00:00 2001 From: Enzo MENEGALDO Date: Tue, 12 Nov 2024 11:38:42 +0100 Subject: [PATCH 5/6] Fix the inversion between Add and Flush in the documentation --- README.md | 4 ++-- src/definitions.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0f520eb..35447ab 100644 --- a/README.md +++ b/README.md @@ -233,8 +233,8 @@ The SpeechSynthesisVoice interface represent | Members | Value | Description | | ----------- | -------------- | ---------------------------------------------------------------------------------------------------------------------- | -| **`Add`** | 0 | Use `Add` to stop the current request when a new request is sent. | -| **`Flush`** | 1 | Use `Flush` to buffer the speech request. The request will be executed when all previous requests have been completed. | +| **`Flush`** | 0 | Use `Flush` to stop the current request when a new request is sent. | +| **`Add`** | 1 | Use `Add` to buffer the speech request. The request will be executed when all previous requests have been completed. | diff --git a/src/definitions.ts b/src/definitions.ts index 1323fe6..24c68c9 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -42,13 +42,13 @@ export interface TextToSpeechPlugin { export enum QueueStrategy { /** - * Use `Add` to stop the current request when a new request is sent. + * Use `Flush` to stop the current request when a new request is sent. */ - Add = 0, + Flush = 0, /** - * Use `Flush` to buffer the speech request. The request will be executed when all previous requests have been completed. + * Use `Add` to buffer the speech request. The request will be executed when all previous requests have been completed. */ - Flush = 1 + Add = 1 } export interface TTSOptions { From dadb267a0590e88ef746501146c580cf6941746a Mon Sep 17 00:00:00 2001 From: "enzo.menegald" Date: Tue, 12 Nov 2024 14:16:09 +0100 Subject: [PATCH 6/6] Fix linting --- .../community/tts/TextToSpeech.java | 66 +++++++++---------- .../community/tts/TextToSpeechPlugin.java | 1 - ios/Plugin/TextToSpeech.swift | 10 +-- src/definitions.ts | 8 +-- 4 files changed, 42 insertions(+), 43 deletions(-) diff --git a/android/src/main/java/com/getcapacitor/community/tts/TextToSpeech.java b/android/src/main/java/com/getcapacitor/community/tts/TextToSpeech.java index 786f4b6..cd8d84a 100644 --- a/android/src/main/java/com/getcapacitor/community/tts/TextToSpeech.java +++ b/android/src/main/java/com/getcapacitor/community/tts/TextToSpeech.java @@ -33,36 +33,36 @@ public class TextToSpeech implements android.speech.tts.TextToSpeech.OnInitListe try { tts = new android.speech.tts.TextToSpeech(context, this); tts.setOnUtteranceProgressListener( - new UtteranceProgressListener() { - @Override - public void onStart(String utteranceId) {} - - @Override - public void onDone(String utteranceId) { - SpeakResultCallback callback = requests.get(utteranceId); - if(callback != null) { - callback.onDone(); - requests.remove(utteranceId); - } + new UtteranceProgressListener() { + @Override + public void onStart(String utteranceId) {} + + @Override + public void onDone(String utteranceId) { + SpeakResultCallback callback = requests.get(utteranceId); + if (callback != null) { + callback.onDone(); + requests.remove(utteranceId); } + } - @Override - public void onError(String utteranceId) { - SpeakResultCallback callback = requests.get(utteranceId); - if(callback != null) { - callback.onError(); - requests.remove(utteranceId); - } + @Override + public void onError(String utteranceId) { + SpeakResultCallback callback = requests.get(utteranceId); + if (callback != null) { + callback.onError(); + requests.remove(utteranceId); } + } - @Override - public void onRangeStart(String utteranceId, int start, int end, int frame) { - SpeakResultCallback callback = requests.get(utteranceId); - if(callback != null) { - callback.onRangeStart(start, end); - } + @Override + public void onRangeStart(String utteranceId, int start, int end, int frame) { + SpeakResultCallback callback = requests.get(utteranceId); + if (callback != null) { + callback.onRangeStart(start, end); } } + } ); } catch (Exception ex) { Log.d(LOG_TAG, ex.getLocalizedMessage()); @@ -75,14 +75,14 @@ public void onInit(int status) { } public void speak( - String text, - String lang, - float rate, - float pitch, - float volume, - int voice, - String callbackId, - SpeakResultCallback resultCallback + String text, + String lang, + float rate, + float pitch, + float volume, + int voice, + String callbackId, + SpeakResultCallback resultCallback ) { speak(text, lang, rate, pitch, volume, voice, callbackId, resultCallback, android.speech.tts.TextToSpeech.QUEUE_FLUSH); } @@ -98,7 +98,7 @@ public void speak( SpeakResultCallback resultCallback, int queueStrategy ) { - if(queueStrategy != android.speech.tts.TextToSpeech.QUEUE_ADD) { + if (queueStrategy != android.speech.tts.TextToSpeech.QUEUE_ADD) { stop(); } requests.put(callbackId, resultCallback); diff --git a/android/src/main/java/com/getcapacitor/community/tts/TextToSpeechPlugin.java b/android/src/main/java/com/getcapacitor/community/tts/TextToSpeechPlugin.java index 79e6314..62eaf01 100644 --- a/android/src/main/java/com/getcapacitor/community/tts/TextToSpeechPlugin.java +++ b/android/src/main/java/com/getcapacitor/community/tts/TextToSpeechPlugin.java @@ -2,7 +2,6 @@ import android.util.Base64; import android.util.Log; - import com.getcapacitor.JSArray; import com.getcapacitor.JSObject; import com.getcapacitor.Plugin; diff --git a/ios/Plugin/TextToSpeech.swift b/ios/Plugin/TextToSpeech.swift index 6702020..90444f4 100644 --- a/ios/Plugin/TextToSpeech.swift +++ b/ios/Plugin/TextToSpeech.swift @@ -34,7 +34,7 @@ enum QUEUE_STRATEGY: Int { } @objc public func speak(_ text: String, _ lang: String, _ rate: Float, _ pitch: Float, _ category: String, _ volume: Float, _ voice: Int, _ queueStrategy: Int, _ call: CAPPluginCall) throws { - if(queueStrategy == QUEUE_STRATEGY.QUEUE_FLUSH.rawValue) { + if queueStrategy == QUEUE_STRATEGY.QUEUE_FLUSH.rawValue { self.synthesizer.stopSpeaking(at: .immediate) } self.calls.append(call) @@ -74,10 +74,10 @@ enum QUEUE_STRATEGY: Int { // Adjust rate for a closer match to other platform. @objc private func adjustRate(_ rate: Float) -> Float { - let baseRate: Float = AVSpeechUtteranceDefaultSpeechRate - if (rate >= 1.0 ) { - return (0.1 * rate) + (baseRate - 0.1) - } + let baseRate: Float = AVSpeechUtteranceDefaultSpeechRate + if rate >= 1.0 { + return (0.1 * rate) + (baseRate - 0.1) + } return rate * baseRate } diff --git a/src/definitions.ts b/src/definitions.ts index 24c68c9..622df38 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -42,13 +42,13 @@ export interface TextToSpeechPlugin { export enum QueueStrategy { /** - * Use `Flush` to stop the current request when a new request is sent. + * Use `Flush` to stop the current request when a new request is sent. */ Flush = 0, /** * Use `Add` to buffer the speech request. The request will be executed when all previous requests have been completed. */ - Add = 1 + Add = 1, } export interface TTSOptions { @@ -99,8 +99,8 @@ export interface TTSOptions { */ category?: string; /** - * Select the strategy to adopt when several requests to speak overlap. - * + * Select the strategy to adopt when several requests to speak overlap. + * * @since 5.1.0 * @default QueueStrategy.Flush */