From bc0713bc0dac86b781c485ddbe3a4e05c3f21c4b Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Thu, 13 May 2021 00:13:37 +0900 Subject: [PATCH 01/15] New JavaScript api for TTS Since it is unlikely that a beginner will handle this api, I moved the functions on the android side to JavaScript with as little modification as possible. Since the function conversion rules have been simplified, the api can be extended with consistent rules even if other TTS functions are needed on the JavaScript side in the future. --- .../ichi2/anki/AbstractFlashcardViewer.java | 44 ++++++++ .../java/com/ichi2/anki/JavaScriptTTS.java | 103 ++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index b666b8d742e7..b4aa8409b7c8 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -4212,5 +4212,49 @@ public boolean ankiIsActiveNetworkMetered() { return true; } } + + //Voice reading + JavaScriptTTS mTalker = new JavaScriptTTS (AbstractFlashcardViewer.this); + + @JavascriptInterface + public int ankiTtsSpeak(String text, int queueMode) { + return mTalker.speak(text, queueMode); + } + + @JavascriptInterface + public int ankiTtsSpeak(String text) { + return mTalker.speak(text); + } + + @JavascriptInterface + public int ankiTtsSetLanguage(String loc) { + return mTalker.setLanguage(loc); + } + + @JavascriptInterface + public int ankiTtsSetPitch(float pitch) { + return mTalker.setPitch(pitch); + } + + @JavascriptInterface + public int ankiTtsSetPitch(double pitch) { + return mTalker.setPitch((float)pitch); + } + + @JavascriptInterface + public int ankiTtsSetSpeechRate(float speechRate) { + return mTalker.setSpeechRate(speechRate); + } + + @JavascriptInterface + public int ankiTtsSetSpeechRate(double speechRate) { + return mTalker.setSpeechRate((float)speechRate); + } + + @JavascriptInterface + public void ankiTtsStop() { + mTalker.stop(); + } + } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java new file mode 100644 index 000000000000..ffa377e741bb --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java @@ -0,0 +1,103 @@ +package com.ichi2.anki; + +import android.content.Context; +import android.os.Bundle; +import android.speech.tts.TextToSpeech; +import java.util.Locale; + +// Since it is assumed that only advanced users will use the JavaScript api, +// here, Android's TextToSpeech is converted for JavaScript almost as it is, giving priority to free behavior. +// https://developer.android.com/reference/android/speech/tts/TextToSpeech +// +// + +public class JavaScriptTTS implements TextToSpeech.OnInitListener { + + private TextToSpeech mTts; + private boolean mTtsOk; + private static final Bundle mTtsParams = new Bundle(); + + //The constructor will create a TextToSpeech instance. + JavaScriptTTS(Context context) { + mTts = new TextToSpeech(context, this); + } + + @Override + //OnInitListener method to receive the TTS engine status + public void onInit(int status) { + if (status == TextToSpeech.SUCCESS) { + mTtsOk = true; + } + else { + mTtsOk = false; + } + } + + // A method to speak something + // The QueMode value is 1 for QUEUE_ADD and 0 for QUEUE_FLUSH. + public int speak(String text, int queueMode) { + return mTts.speak(text, queueMode, mTtsParams, "stringId"); + } + + // If only a string is given, set QUEUE_FLUSH to the default behavior. + public int speak(String text) { + return mTts.speak(text, TextToSpeech.QUEUE_FLUSH, mTtsParams, "stringId"); + } + + // Sets the text-to-speech language. + //The TTS engine will try to use the closest match to the specified language as represented by the Locale, but there is no guarantee that the exact same Locale will be used. + public int setLanguage(String loc) { + // The Int values will be returned + // Code indicating the support status for the locale. See LANG_AVAILABLE, LANG_COUNTRY_AVAILABLE, LANG_COUNTRY_VAR_AVAILABLE, LANG_MISSING_DATA and LANG_NOT_SUPPORTED. + return mTts.setLanguage(localeFromStringIgnoringScriptAndExtensions(loc)); + } + + // Sets the speech pitch for the TextToSpeech engine. This has no effect on any pre-recorded speech. + // float: Speech pitch. 1.0 is the normal pitch, lower values lower the tone of the synthesized voice, greater values increase it. + public int setPitch(float pitch) { + // The following Int values will be returned + // ERROR(-1) SUCCESS(0) + return mTts.setPitch(pitch); + } + + // Sets the speech rate. This has no effect on any pre-recorded speech. + public int setSpeechRate(float speechRate) { + // The following Int values will be returned + // ERROR(-1) SUCCESS(0) + return mTts.setSpeechRate(speechRate); + } + + // Interrupts the current utterance (whether played or rendered to file) and discards other utterances in the queue. + public void stop() { + // The following Int values will be returned + // ERROR(-1) SUCCESS(0) + mTts.stop(); + } + + /** + * Convert a string representation of a locale, in the format returned by Locale.toString(), + * into a Locale object, disregarding any script and extensions fields (i.e. using solely the + * language, country and variant fields). + *

+ * Returns a Locale object constructed from an empty string if the input string is null, empty + * or contains more than 3 fields separated by underscores. + */ + private static Locale localeFromStringIgnoringScriptAndExtensions(String localeCode) { + if (localeCode == null) { + return new Locale(""); + } + + String[] fields = localeCode.split("_"); + switch (fields.length) { + case 1: + return new Locale(fields[0]); + case 2: + return new Locale(fields[0], fields[1]); + case 3: + return new Locale(fields[0], fields[1], fields[2]); + default: + return new Locale(""); + } + } + +} \ No newline at end of file From d9023e2de100f9501d349968b6e7fdb2a6830d48 Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Wed, 2 Jun 2021 15:42:02 +0900 Subject: [PATCH 02/15] Code style and comment fixes --- .../ichi2/anki/AbstractFlashcardViewer.java | 12 ++-- .../java/com/ichi2/anki/JavaScriptTTS.java | 60 +++++++++++++------ 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index b4aa8409b7c8..421f7fa64a43 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -4214,31 +4214,31 @@ public boolean ankiIsActiveNetworkMetered() { } //Voice reading - JavaScriptTTS mTalker = new JavaScriptTTS (AbstractFlashcardViewer.this); + JavaScriptTTS mTalker = new JavaScriptTTS(AbstractFlashcardViewer.this); @JavascriptInterface public int ankiTtsSpeak(String text, int queueMode) { - return mTalker.speak(text, queueMode); + return mTalker.speak(text, queueMode); } @JavascriptInterface public int ankiTtsSpeak(String text) { - return mTalker.speak(text); + return mTalker.speak(text); } @JavascriptInterface public int ankiTtsSetLanguage(String loc) { - return mTalker.setLanguage(loc); + return mTalker.setLanguage(loc); } @JavascriptInterface public int ankiTtsSetPitch(float pitch) { - return mTalker.setPitch(pitch); + return mTalker.setPitch(pitch); } @JavascriptInterface public int ankiTtsSetPitch(double pitch) { - return mTalker.setPitch((float)pitch); + return mTalker.setPitch((float)pitch); } @JavascriptInterface diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java index ffa377e741bb..b5ed36a3826e 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java @@ -5,25 +5,23 @@ import android.speech.tts.TextToSpeech; import java.util.Locale; -// Since it is assumed that only advanced users will use the JavaScript api, -// here, Android's TextToSpeech is converted for JavaScript almost as it is, giving priority to free behavior. -// https://developer.android.com/reference/android/speech/tts/TextToSpeech -// -// - +/** + * Since it is assumed that only advanced users will use the JavaScript api, + * here, Android's TextToSpeech is converted for JavaScript almost as it is, giving priority to free behavior. + * https://developer.android.com/reference/android/speech/tts/TextToSpeech + */ public class JavaScriptTTS implements TextToSpeech.OnInitListener { private TextToSpeech mTts; private boolean mTtsOk; private static final Bundle mTtsParams = new Bundle(); - //The constructor will create a TextToSpeech instance. JavaScriptTTS(Context context) { mTts = new TextToSpeech(context, this); } @Override - //OnInitListener method to receive the TTS engine status + /** OnInitListener method to receive the TTS engine status */ public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { mTtsOk = true; @@ -32,42 +30,68 @@ public void onInit(int status) { mTtsOk = false; } } - - // A method to speak something - // The QueMode value is 1 for QUEUE_ADD and 0 for QUEUE_FLUSH. + + /** + * A method to speak something + * @param text Content to speak + * @param queueMode 1 for QUEUE_ADD and 0 for QUEUE_FLUSH. + * @return ERROR(-1) SUCCESS(0) + */ public int speak(String text, int queueMode) { return mTts.speak(text, queueMode, mTtsParams, "stringId"); } - // If only a string is given, set QUEUE_FLUSH to the default behavior. + /** + * If only a string is given, set QUEUE_FLUSH to the default behavior. + * @param text Content to speak + * @return ERROR(-1) SUCCESS(0) + */ public int speak(String text) { return mTts.speak(text, TextToSpeech.QUEUE_FLUSH, mTtsParams, "stringId"); } - // Sets the text-to-speech language. - //The TTS engine will try to use the closest match to the specified language as represented by the Locale, but there is no guarantee that the exact same Locale will be used. + /** + * Sets the text-to-speech language. + * The TTS engine will try to use the closest match to the specified language as represented by the Locale, but there is no guarantee that the exact same Locale will be used. + * @param loc Specifying the language to speak + * @return 0 Denotes the language is available for the language by the locale, but not the country and variant. + *

  • 1 Denotes the language is available for the language and country specified by the locale, but not the variant. + *
  • 2 Denotes the language is available exactly as specified by the locale. + *
  • -1 Denotes the language data is missing. + *
  • -2 Denotes the language is not supported. + */ public int setLanguage(String loc) { // The Int values will be returned // Code indicating the support status for the locale. See LANG_AVAILABLE, LANG_COUNTRY_AVAILABLE, LANG_COUNTRY_VAR_AVAILABLE, LANG_MISSING_DATA and LANG_NOT_SUPPORTED. return mTts.setLanguage(localeFromStringIgnoringScriptAndExtensions(loc)); } - // Sets the speech pitch for the TextToSpeech engine. This has no effect on any pre-recorded speech. - // float: Speech pitch. 1.0 is the normal pitch, lower values lower the tone of the synthesized voice, greater values increase it. + + /** + * Sets the speech pitch for the TextToSpeech engine. This has no effect on any pre-recorded speech. + * @param pitch float: Speech pitch. 1.0 is the normal pitch, lower values lower the tone of the synthesized voice, greater values increase it. + * @return ERROR(-1) SUCCESS(0) + */ public int setPitch(float pitch) { // The following Int values will be returned // ERROR(-1) SUCCESS(0) return mTts.setPitch(pitch); } - // Sets the speech rate. This has no effect on any pre-recorded speech. + /** + * + * @param speechRate Sets the speech rate. 1.0 is the normal speech rate. This has no effect on any pre-recorded speech. + * @return ERROR(-1) SUCCESS(0) + */ public int setSpeechRate(float speechRate) { // The following Int values will be returned // ERROR(-1) SUCCESS(0) return mTts.setSpeechRate(speechRate); } - // Interrupts the current utterance (whether played or rendered to file) and discards other utterances in the queue. + /** + * Interrupts the current utterance (whether played or rendered to file) and discards other utterances in the queue. + */ public void stop() { // The following Int values will be returned // ERROR(-1) SUCCESS(0) From 65e84f961d007ad4548567e607d4f0b7c7459d0c Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Wed, 2 Jun 2021 16:12:59 +0900 Subject: [PATCH 03/15] Create LanguageUtils class Moved the localeFromStringIgnoringScriptAndExtensions function to the LanguageUtils class. --- .../java/com/ichi2/anki/JavaScriptTTS.java | 31 ++------------ .../java/com/ichi2/anki/LanguageUtils.java | 42 +++++++++++++++++++ .../main/java/com/ichi2/anki/ReadText.java | 41 ++---------------- 3 files changed, 48 insertions(+), 66 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/LanguageUtils.java diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java index b5ed36a3826e..dc7ef6bbebf4 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java @@ -3,7 +3,8 @@ import android.content.Context; import android.os.Bundle; import android.speech.tts.TextToSpeech; -import java.util.Locale; + +import com.ichi2.anki.LanguageUtils; /** * Since it is assumed that only advanced users will use the JavaScript api, @@ -63,7 +64,7 @@ public int speak(String text) { public int setLanguage(String loc) { // The Int values will be returned // Code indicating the support status for the locale. See LANG_AVAILABLE, LANG_COUNTRY_AVAILABLE, LANG_COUNTRY_VAR_AVAILABLE, LANG_MISSING_DATA and LANG_NOT_SUPPORTED. - return mTts.setLanguage(localeFromStringIgnoringScriptAndExtensions(loc)); + return mTts.setLanguage(LanguageUtils.localeFromStringIgnoringScriptAndExtensions(loc)); } @@ -98,30 +99,4 @@ public void stop() { mTts.stop(); } - /** - * Convert a string representation of a locale, in the format returned by Locale.toString(), - * into a Locale object, disregarding any script and extensions fields (i.e. using solely the - * language, country and variant fields). - *

    - * Returns a Locale object constructed from an empty string if the input string is null, empty - * or contains more than 3 fields separated by underscores. - */ - private static Locale localeFromStringIgnoringScriptAndExtensions(String localeCode) { - if (localeCode == null) { - return new Locale(""); - } - - String[] fields = localeCode.split("_"); - switch (fields.length) { - case 1: - return new Locale(fields[0]); - case 2: - return new Locale(fields[0], fields[1]); - case 3: - return new Locale(fields[0], fields[1], fields[2]); - default: - return new Locale(""); - } - } - } \ No newline at end of file diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/LanguageUtils.java b/AnkiDroid/src/main/java/com/ichi2/anki/LanguageUtils.java new file mode 100644 index 000000000000..b549211ff436 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/LanguageUtils.java @@ -0,0 +1,42 @@ +package com.ichi2.anki; + +import java.util.Locale; + +public class LanguageUtils { + + /** + * Convert a string representation of a locale, in the format returned by Locale.toString(), + * into a Locale object, disregarding any script and extensions fields (i.e. using solely the + * language, country and variant fields). + *

    + * Returns a Locale object constructed from an empty string if the input string is null, empty + * or contains more than 3 fields separated by underscores. + */ + public static Locale localeFromStringIgnoringScriptAndExtensions(String localeCode) { + if (localeCode == null) { + return new Locale(""); + } + + localeCode = stripScriptAndExtensions(localeCode); + + String[] fields = localeCode.split("_"); + switch (fields.length) { + case 1: + return new Locale(fields[0]); + case 2: + return new Locale(fields[0], fields[1]); + case 3: + return new Locale(fields[0], fields[1], fields[2]); + default: + return new Locale(""); + } + } + + private static String stripScriptAndExtensions(String localeCode) { + int hashPos = localeCode.indexOf('#'); + if (hashPos >= 0) { + localeCode = localeCode.substring(0, hashPos); + } + return localeCode; + } +} \ No newline at end of file diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.java b/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.java index fdc4db57a2f8..1ff15f5462ab 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.java @@ -29,6 +29,7 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.google.android.material.snackbar.Snackbar; import com.ichi2.libanki.Sound; +import com.ichi2.anki.LanguageUtils; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -56,7 +57,7 @@ public static Sound.SoundSide getmQuestionAnswer() { } public static void speak(String text, String loc, int queueMode) { - int result = mTts.setLanguage(localeFromStringIgnoringScriptAndExtensions(loc)); + int result = mTts.setLanguage(LanguageUtils.localeFromStringIgnoringScriptAndExtensions(loc)); if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { UIUtils.showThemedToast(mReviewer.get(), mReviewer.get().getString(R.string.no_tts_available_message) + " (" + loc + ")", false); @@ -218,48 +219,12 @@ private static void textToSpeech(String text, long did, int ord, Sound.SoundSide selectTts(mTextToSpeak, mDid, mOrd, mQuestionAnswer); } - /** - * Convert a string representation of a locale, in the format returned by Locale.toString(), - * into a Locale object, disregarding any script and extensions fields (i.e. using solely the - * language, country and variant fields). - *

    - * Returns a Locale object constructed from an empty string if the input string is null, empty - * or contains more than 3 fields separated by underscores. - */ - private static Locale localeFromStringIgnoringScriptAndExtensions(String localeCode) { - if (localeCode == null) { - return new Locale(""); - } - - localeCode = stripScriptAndExtensions(localeCode); - - String[] fields = localeCode.split("_"); - switch (fields.length) { - case 1: - return new Locale(fields[0]); - case 2: - return new Locale(fields[0], fields[1]); - case 3: - return new Locale(fields[0], fields[1], fields[2]); - default: - return new Locale(""); - } - } - - private static String stripScriptAndExtensions(String localeCode) { - int hashPos = localeCode.indexOf('#'); - if (hashPos >= 0) { - localeCode = localeCode.substring(0, hashPos); - } - return localeCode; - } - /** * Returns true if the TTS engine supports the language of the locale represented by localeCode * (which should be in the format returned by Locale.toString()), false otherwise. */ private static boolean isLanguageAvailable(String localeCode) { - return mTts.isLanguageAvailable(localeFromStringIgnoringScriptAndExtensions(localeCode)) >= + return mTts.isLanguageAvailable(LanguageUtils.localeFromStringIgnoringScriptAndExtensions(localeCode)) >= TextToSpeech.LANG_AVAILABLE; } From 7ae67152548a9b51d1fb7141724e34257f290b00 Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Wed, 2 Jun 2021 16:54:49 +0900 Subject: [PATCH 04/15] Fixed verbose code in JavaScript TTS class --- AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java index dc7ef6bbebf4..1f3e63689553 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java @@ -24,12 +24,7 @@ public class JavaScriptTTS implements TextToSpeech.OnInitListener { @Override /** OnInitListener method to receive the TTS engine status */ public void onInit(int status) { - if (status == TextToSpeech.SUCCESS) { - mTtsOk = true; - } - else { - mTtsOk = false; - } + mTtsOk = status == TextToSpeech.SUCCESS; } /** From 33feffac15b23a42345bd5b5ad9cc81dc5b3df68 Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Wed, 2 Jun 2021 17:03:11 +0900 Subject: [PATCH 05/15] Code style fixes --- AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java index 1f3e63689553..4857b166ddc5 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java @@ -21,12 +21,12 @@ public class JavaScriptTTS implements TextToSpeech.OnInitListener { mTts = new TextToSpeech(context, this); } - @Override /** OnInitListener method to receive the TTS engine status */ + @Override public void onInit(int status) { mTtsOk = status == TextToSpeech.SUCCESS; } - + /** * A method to speak something * @param text Content to speak From feafb9bbea66a8b2f53b75b7e1dd12d4199c1bcb Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Wed, 2 Jun 2021 19:39:32 +0900 Subject: [PATCH 06/15] Move JavaScript TTS to the top of the class --- .../main/java/com/ichi2/anki/AbstractFlashcardViewer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index 421f7fa64a43..6948ee0d5f06 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -392,6 +392,9 @@ public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity i /** Preference: Whether the user wants to focus "type in answer" */ private boolean mFocusTypeAnswer; + /** Text to speech */ + private JavaScriptTTS mTalker = new JavaScriptTTS(AbstractFlashcardViewer.this); + // ---------------------------------------------------------------------------- // LISTENERS // ---------------------------------------------------------------------------- @@ -4213,9 +4216,6 @@ public boolean ankiIsActiveNetworkMetered() { } } - //Voice reading - JavaScriptTTS mTalker = new JavaScriptTTS(AbstractFlashcardViewer.this); - @JavascriptInterface public int ankiTtsSpeak(String text, int queueMode) { return mTalker.speak(text, queueMode); From 14107a27602f479a986dff39b334bb91e3b66b0c Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Thu, 3 Jun 2021 07:40:04 +0900 Subject: [PATCH 07/15] Added NonNull annotation --- AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java index 4857b166ddc5..cc9ad42768c1 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java @@ -6,6 +6,8 @@ import com.ichi2.anki.LanguageUtils; +import androidx.annotation.NonNull; + /** * Since it is assumed that only advanced users will use the JavaScript api, * here, Android's TextToSpeech is converted for JavaScript almost as it is, giving priority to free behavior. @@ -13,8 +15,9 @@ */ public class JavaScriptTTS implements TextToSpeech.OnInitListener { - private TextToSpeech mTts; - private boolean mTtsOk; + @NonNull + private static TextToSpeech mTts; + private static boolean mTtsOk; private static final Bundle mTtsParams = new Bundle(); JavaScriptTTS(Context context) { From 8a095684277f93ec9a52bc1fb042d0d92aa2b0bb Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:55:37 +0900 Subject: [PATCH 08/15] Force int value through IntDef --- .../java/com/ichi2/anki/JavaScriptTTS.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java index cc9ad42768c1..99e07f336821 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java @@ -6,6 +6,7 @@ import com.ichi2.anki.LanguageUtils; +import androidx.annotation.IntDef; import androidx.annotation.NonNull; /** @@ -15,6 +16,25 @@ */ public class JavaScriptTTS implements TextToSpeech.OnInitListener { + private static final int TTS_SUCCESS = TextToSpeech.SUCCESS; + private static final int TTS_ERROR = TextToSpeech.ERROR; + private static final int TTS_QUEUE_ADD = TextToSpeech.QUEUE_ADD; + private static final int TTS_QUEUE_FLUSH = TextToSpeech.QUEUE_FLUSH; + private static final int TTS_LANG_AVAILABLE = TextToSpeech.LANG_AVAILABLE; + private static final int TTS_LANG_COUNTRY_AVAILABLE = TextToSpeech.LANG_COUNTRY_AVAILABLE; + private static final int TTS_LANG_COUNTRY_VAR_AVAILABLE = TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE; + private static final int TTS_LANG_MISSING_DATA = TextToSpeech.LANG_MISSING_DATA; + private static final int TTS_LANG_NOT_SUPPORTED = TextToSpeech.LANG_NOT_SUPPORTED; + + @IntDef({TTS_SUCCESS, TTS_ERROR}) + public @interface ErrorOrSuccess {} + + @IntDef({TTS_QUEUE_ADD, TTS_QUEUE_FLUSH}) + public @interface QueueMode {} + + @IntDef({TTS_LANG_AVAILABLE, TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE, TTS_LANG_MISSING_DATA, TTS_LANG_NOT_SUPPORTED}) + public @interface TTSLangResult {} + @NonNull private static TextToSpeech mTts; private static boolean mTtsOk; @@ -26,7 +46,7 @@ public class JavaScriptTTS implements TextToSpeech.OnInitListener { /** OnInitListener method to receive the TTS engine status */ @Override - public void onInit(int status) { + public void onInit(@ErrorOrSuccess int status) { mTtsOk = status == TextToSpeech.SUCCESS; } @@ -36,7 +56,8 @@ public void onInit(int status) { * @param queueMode 1 for QUEUE_ADD and 0 for QUEUE_FLUSH. * @return ERROR(-1) SUCCESS(0) */ - public int speak(String text, int queueMode) { + @ErrorOrSuccess + public int speak(String text, @QueueMode int queueMode) { return mTts.speak(text, queueMode, mTtsParams, "stringId"); } @@ -45,6 +66,7 @@ public int speak(String text, int queueMode) { * @param text Content to speak * @return ERROR(-1) SUCCESS(0) */ + @ErrorOrSuccess public int speak(String text) { return mTts.speak(text, TextToSpeech.QUEUE_FLUSH, mTtsParams, "stringId"); } @@ -59,6 +81,7 @@ public int speak(String text) { *

  • -1 Denotes the language data is missing. *
  • -2 Denotes the language is not supported. */ + @TTSLangResult public int setLanguage(String loc) { // The Int values will be returned // Code indicating the support status for the locale. See LANG_AVAILABLE, LANG_COUNTRY_AVAILABLE, LANG_COUNTRY_VAR_AVAILABLE, LANG_MISSING_DATA and LANG_NOT_SUPPORTED. @@ -71,6 +94,7 @@ public int setLanguage(String loc) { * @param pitch float: Speech pitch. 1.0 is the normal pitch, lower values lower the tone of the synthesized voice, greater values increase it. * @return ERROR(-1) SUCCESS(0) */ + @ErrorOrSuccess public int setPitch(float pitch) { // The following Int values will be returned // ERROR(-1) SUCCESS(0) @@ -82,6 +106,7 @@ public int setPitch(float pitch) { * @param speechRate Sets the speech rate. 1.0 is the normal speech rate. This has no effect on any pre-recorded speech. * @return ERROR(-1) SUCCESS(0) */ + @ErrorOrSuccess public int setSpeechRate(float speechRate) { // The following Int values will be returned // ERROR(-1) SUCCESS(0) From b79cb6b922f31c892d92cf8b46a6a1af7682af97 Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:57:21 +0900 Subject: [PATCH 09/15] Removed unnecessary imports --- .../src/main/java/com/ichi2/anki/JavaScriptTTS.java | 10 ++++------ AnkiDroid/src/main/java/com/ichi2/anki/ReadText.java | 1 - 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java index 99e07f336821..7832aa3a7a09 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java @@ -4,13 +4,11 @@ import android.os.Bundle; import android.speech.tts.TextToSpeech; -import com.ichi2.anki.LanguageUtils; - import androidx.annotation.IntDef; import androidx.annotation.NonNull; /** - * Since it is assumed that only advanced users will use the JavaScript api, + * Since it is assumed that only advanced users will use the JavaScript api, * here, Android's TextToSpeech is converted for JavaScript almost as it is, giving priority to free behavior. * https://developer.android.com/reference/android/speech/tts/TextToSpeech */ @@ -72,8 +70,8 @@ public int speak(String text) { } /** - * Sets the text-to-speech language. - * The TTS engine will try to use the closest match to the specified language as represented by the Locale, but there is no guarantee that the exact same Locale will be used. + * Sets the text-to-speech language. + * The TTS engine will try to use the closest match to the specified language as represented by the Locale, but there is no guarantee that the exact same Locale will be used. * @param loc Specifying the language to speak * @return 0 Denotes the language is available for the language by the locale, but not the country and variant. *
  • 1 Denotes the language is available for the language and country specified by the locale, but not the variant. @@ -102,7 +100,7 @@ public int setPitch(float pitch) { } /** - * + * * @param speechRate Sets the speech rate. 1.0 is the normal speech rate. This has no effect on any pre-recorded speech. * @return ERROR(-1) SUCCESS(0) */ diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.java b/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.java index 1ff15f5462ab..dfd12b066df8 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.java @@ -29,7 +29,6 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.google.android.material.snackbar.Snackbar; import com.ichi2.libanki.Sound; -import com.ichi2.anki.LanguageUtils; import java.lang.ref.WeakReference; import java.util.ArrayList; From bbc78fae02cc5fb8ccb603253ff049fb1e1e7beb Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Thu, 3 Jun 2021 11:09:46 +0900 Subject: [PATCH 10/15] Changed to end the end of the file with a newline --- AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java | 2 +- AnkiDroid/src/main/java/com/ichi2/anki/LanguageUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java index 7832aa3a7a09..5bcc70cbc63a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java @@ -120,4 +120,4 @@ public void stop() { mTts.stop(); } -} \ No newline at end of file +} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/LanguageUtils.java b/AnkiDroid/src/main/java/com/ichi2/anki/LanguageUtils.java index b549211ff436..f17335ad9efc 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/LanguageUtils.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/LanguageUtils.java @@ -39,4 +39,4 @@ private static String stripScriptAndExtensions(String localeCode) { } return localeCode; } -} \ No newline at end of file +} From cd7fbf828ae53c62548a13d069c4f24e9cf78abf Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Wed, 7 Jul 2021 21:06:43 +0900 Subject: [PATCH 11/15] Improved JavaScript TTS initialization process Fixed a problem that null may be passed as an argument when initializing JavaScriptTTS. Changed to refer to context from within the class instead of the argument when initializing JavaScriptTTS. --- .../main/java/com/ichi2/anki/AbstractFlashcardViewer.java | 2 +- AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index 6948ee0d5f06..04d717c8c8b7 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -393,7 +393,7 @@ public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity i private boolean mFocusTypeAnswer; /** Text to speech */ - private JavaScriptTTS mTalker = new JavaScriptTTS(AbstractFlashcardViewer.this); + private JavaScriptTTS mTalker = new JavaScriptTTS(); // ---------------------------------------------------------------------------- // LISTENERS diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java index 5bcc70cbc63a..0e8e1a7dffa2 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java @@ -38,8 +38,9 @@ public class JavaScriptTTS implements TextToSpeech.OnInitListener { private static boolean mTtsOk; private static final Bundle mTtsParams = new Bundle(); - JavaScriptTTS(Context context) { - mTts = new TextToSpeech(context, this); + JavaScriptTTS() { + Context mContext = AnkiDroidApp.getInstance().getApplicationContext(); + mTts = new TextToSpeech(mContext, this); } /** OnInitListener method to receive the TTS engine status */ From 4c8101f1a145ee8c0070c1cebaf56f23f53bc55c Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Tue, 3 Aug 2021 16:36:19 +0900 Subject: [PATCH 12/15] Added isSpeaking function --- .../java/com/ichi2/anki/AbstractFlashcardViewer.java | 9 +++++++-- .../src/main/java/com/ichi2/anki/JavaScriptTTS.java | 10 ++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index 04d717c8c8b7..4ac63aa11f91 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -4245,12 +4245,17 @@ public int ankiTtsSetPitch(double pitch) { public int ankiTtsSetSpeechRate(float speechRate) { return mTalker.setSpeechRate(speechRate); } - + @JavascriptInterface public int ankiTtsSetSpeechRate(double speechRate) { return mTalker.setSpeechRate((float)speechRate); } - + + @JavascriptInterface + public boolean ankiTtsIsSpeaking() { + return mTalker.isSpeaking(); + } + @JavascriptInterface public void ankiTtsStop() { mTalker.stop(); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java index 0e8e1a7dffa2..2e24c97d5915 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java @@ -112,6 +112,16 @@ public int setSpeechRate(float speechRate) { return mTts.setSpeechRate(speechRate); } + /** + * Checks whether the TTS engine is busy speaking. + * Note that a speech item is considered complete once it's audio data has + * been sent to the audio mixer, or written to a file. + * + */ + public boolean isSpeaking() { + return mTts.isSpeaking(); + } + /** * Interrupts the current utterance (whether played or rendered to file) and discards other utterances in the queue. */ From 16589e2641a97cccb9bf5146ac9ad2f12613f747 Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Tue, 3 Aug 2021 16:47:43 +0900 Subject: [PATCH 13/15] Changed the stop function to return ErrorOrSuccess --- .../main/java/com/ichi2/anki/AbstractFlashcardViewer.java | 4 ++-- AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index 4ac63aa11f91..eca0f1cfaa39 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -4257,8 +4257,8 @@ public boolean ankiTtsIsSpeaking() { } @JavascriptInterface - public void ankiTtsStop() { - mTalker.stop(); + public int ankiTtsStop() { + return mTalker.stop(); } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java index 2e24c97d5915..eb9181518a0f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java @@ -124,11 +124,11 @@ public boolean isSpeaking() { /** * Interrupts the current utterance (whether played or rendered to file) and discards other utterances in the queue. + * @return ERROR(-1) SUCCESS(0) */ - public void stop() { - // The following Int values will be returned - // ERROR(-1) SUCCESS(0) - mTts.stop(); + @ErrorOrSuccess + public int stop() { + return mTts.stop(); } } From 9bceb0ad205943b0d1442fbfe4dc7dc8167a2b89 Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Thu, 26 Aug 2021 14:59:41 +0900 Subject: [PATCH 14/15] Change the position of JavaScriptTS initialization --- .../main/java/com/ichi2/anki/AbstractFlashcardViewer.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index eca0f1cfaa39..50622bf6e266 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -392,9 +392,6 @@ public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity i /** Preference: Whether the user wants to focus "type in answer" */ private boolean mFocusTypeAnswer; - /** Text to speech */ - private JavaScriptTTS mTalker = new JavaScriptTTS(); - // ---------------------------------------------------------------------------- // LISTENERS // ---------------------------------------------------------------------------- @@ -4018,6 +4015,8 @@ public JavaScriptFunction javaScriptFunction() { } public class JavaScriptFunction { + // Text to speech + private JavaScriptTTS mTalker = new JavaScriptTTS(); // if supplied api version match then enable api private void enableJsApi() { From 059557ad74b9bd8ecb53fa9687b90e2c817c557a Mon Sep 17 00:00:00 2001 From: mikunimaru <43168745+mikunimaru@users.noreply.github.com> Date: Thu, 26 Aug 2021 15:00:28 +0900 Subject: [PATCH 15/15] Add GPL copyright header --- .../main/java/com/ichi2/anki/JavaScriptTTS.java | 16 ++++++++++++++++ .../main/java/com/ichi2/anki/LanguageUtils.java | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java index eb9181518a0f..46f98e66c23f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/JavaScriptTTS.java @@ -1,3 +1,19 @@ +/**************************************************************************************** + * Copyright (c) 2021 mikunimaru * + * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 3 of the License, or (at your option) any later * + * version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along with * + * this program. If not, see . * + ****************************************************************************************/ + package com.ichi2.anki; import android.content.Context; diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/LanguageUtils.java b/AnkiDroid/src/main/java/com/ichi2/anki/LanguageUtils.java index f17335ad9efc..7cd3c00bac4b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/LanguageUtils.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/LanguageUtils.java @@ -1,3 +1,19 @@ +/**************************************************************************************** + * Copyright (c) 2021 mikunimaru * + * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 3 of the License, or (at your option) any later * + * version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along with * + * this program. If not, see . * + ****************************************************************************************/ + package com.ichi2.anki; import java.util.Locale;