/* Copyright (C) 2012 Haowen Ning, Xinxin Wang 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.liberty.android.fantastischmemo.tts; import android.content.Context; import android.speech.tts.TextToSpeech; import android.util.Log; import android.widget.Toast; import com.google.common.base.Strings; import org.liberty.android.fantastischmemo.R; import org.liberty.android.fantastischmemo.tts.SpeakWord.OnCompletedListener; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; public class AnyMemoTTSImpl implements AnyMemoTTS, TextToSpeech.OnInitListener{ private volatile TextToSpeech myTTS; private SpeakWord speakWord; private final Locale myLocale; private Context context; private volatile ReentrantLock initLock = new ReentrantLock(); private volatile ReentrantLock speakLock = new ReentrantLock(); /* TTS Init lock's timeout in seconds. */ private static long INIT_LOCK_TIMEOUT = 10L; public final static String TAG = "org.liberty.android.fantastischmemo.tts.AnyMemoTTSPlatform"; public void onInit(int status){ try { if (initLock.tryLock() || initLock.tryLock(INIT_LOCK_TIMEOUT, TimeUnit.SECONDS)) { initLock.unlock(); } else { Log.e(TAG, "TTS init timed out"); return; } } catch (InterruptedException e) { Log.e(TAG, "TTS init lock waiting is interrupted."); } if (status == TextToSpeech.SUCCESS) { Log.v(TAG, "init!" + myLocale.toString()); assert myTTS != null; assert myLocale != null; int result = myTTS.setLanguage(myLocale); if (result == TextToSpeech.LANG_MISSING_DATA) { Toast.makeText(context, context.getString(R.string.tts_language_data_missing_text) + " " + myLocale, Toast.LENGTH_LONG) .show(); Log.e(TAG, "Missing language data"); } if (result == TextToSpeech.LANG_NOT_SUPPORTED) { Toast.makeText(context, context.getString(R.string.unsupported_audio_locale_text) + " " + myLocale, Toast.LENGTH_LONG) .show(); Log.e(TAG, "Language is not supported"); } } else { Log.e(TAG, "Can't initialize"); } } public AnyMemoTTSImpl(Context context, String locale, List<String> audioSearchPath){ // We must make sure the constructor happens before // the onInit callback. Unfortunately, this is not // always true. We have to use lock to ensure the happen before. // Or a null pointer for myTTS is waiting initLock.lock(); this.context = context; myLocale = getLocaleForTTS(locale); myTTS = new TextToSpeech(context, this); speakWord = new SpeakWord(audioSearchPath); initLock.unlock(); } public void destory(){ if(speakWord != null){ speakWord.destory(); } myTTS.shutdown(); } public void stop(){ if(speakWord != null){ speakWord.stop(); return; } myTTS.stop(); // We wait until the tts is not speaking. // This is because top is asynchronized call while (myTTS.isSpeaking()) { try { Thread.sleep(20); } catch (InterruptedException e) { // Do not need to handle this case. } } } // We need setOnUtteranceCompletedListener for compatibility with Android 2.x @SuppressWarnings("deprecation") public void sayText(final String s, final OnTextToSpeechCompletedListener onTextToSpeechCompletedListener){ /*if there is a user defined audio, speak it and return */ if (speakWord.speakWord(s)) { // This enables auto speak for user defined audio files. speakWord.setOnCompletedListener(new OnCompletedListener() { @Override public void onCompleted() { if (onTextToSpeechCompletedListener != null) { onTextToSpeechCompletedListener.onTextToSpeechCompleted(s); } } }); return; } /*otherwise, speak the content*/ Log.v(TAG, "say it!"); // This is slightly different from AMStringUtils.stripHTML since we replace the <br> with // a period to let it have a short pause. // Replace break with period String processed_str = s.replaceAll("\\<br\\>", ". " ); // Remove HTML processed_str = processed_str.replaceAll("\\<.*?>", ""); // Remove () [] and their content processed_str = processed_str.replaceAll("\\[.*?\\]", ""); // Remove the XML special character processed_str = processed_str.replaceAll("\\[.*?\\]", ""); processed_str = processed_str.replaceAll("&.*?;", ""); HashMap<String, String> params = new HashMap<String, String>(); params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "id"); myTTS.setOnUtteranceCompletedListener(new TextToSpeech.OnUtteranceCompletedListener() { @Override public void onUtteranceCompleted(String utteranceId) { if (onTextToSpeechCompletedListener != null) { onTextToSpeechCompletedListener.onTextToSpeechCompleted(s); } } }); speakLock.lock(); Log.i(TAG, "processed_str is \"" + processed_str + "\""); myTTS.speak(processed_str, 0, params); speakLock.unlock(); } private Locale getLocaleForTTS(String loc) { if (Strings.isNullOrEmpty(loc)) { return Locale.US; } if (loc.toLowerCase().equals("us")) { return Locale.US; } if (loc.toLowerCase().equals("uk")) { return Locale.UK; } return new Locale(loc); } }