package root.gast.playground.speech.tts;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import root.gast.playground.R;
import root.gast.playground.pref.PreferenceHelper;
import root.gast.playground.pref.SummarizingEditPreferences;
import root.gast.playground.util.DialogGenerator;
import root.gast.playground.util.FileUtil;
import root.gast.speech.tts.TextToSpeechInitializer;
import root.gast.speech.tts.TextToSpeechStartupListener;
import root.gast.speech.tts.TextToSpeechUtils;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
import android.speech.tts.UtteranceProgressListener;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
/**
* Demonstrates executing tts under various parameter settings
* @author Greg Milette <<a href="mailto:gregorym@gmail.com">gregorym@gmail.com</a>>
*/
public class TextToSpeechPlay extends Activity implements TextToSpeechStartupListener
{
private static final String TAG = "TextToSpeechPlay";
private static final String SILENCE_ROOT = "silence_";
private static final String SPEAK_ROOT = "speak_";
private static final String EARCON_ROOT = "earcon_";
private static final String OUTPUT_DIR = "textoutput";
private static String EXTERNAL_STORAGE_DIR;
/**
* how many characters to output in the log when
* outputting the text spoken
*/
private static final int CHARACTERS_IN_LOG = 30;
private TextView log;
private EditText whatToSay;
private Button speak;
private Button silence;
private Button earcon;
private Button stopSpeak;
private PreferenceHelper preferences;
private List<String> presets;
private TextToSpeechInitializer ttsInit;
private TextToSpeech tts;
private int queueMode;
/**
* keep track of the number of the spoken utterance
*/
private int utteranceCounter;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.texttospeech);
hookButtons();
init();
}
private void init()
{
try
{
EXTERNAL_STORAGE_DIR = getExternalFilesDir(OUTPUT_DIR).getAbsolutePath();
}
catch (Exception e)
{
Log.e(TAG, "the external storate isn't available, continue anyway", e);
}
preferences =
new PreferenceHelper(getResources().getString(
R.string.pref_tts_key), this.getApplicationContext());
deactivateUi();
ttsInit =
new TextToSpeechInitializer(this, Locale.getDefault(), this);
utteranceCounter = 0;
queueMode = TextToSpeech.QUEUE_FLUSH;
}
@Override
public void onFailedToInit()
{
DialogGenerator.createInfoDialog(this, getResources().getString(R.string.d_error),
getResources().getString(R.string.tts_init_failed), makeOnFailedToInitHandler()).show();
}
/**
* @see root.gast.speech.tts.TtsStartupListener#onRequireLanguageData()
*/
@Override
public void onRequireLanguageData()
{
Log.d(TAG, "REQUIRE LANGUAGE DATA");
DialogInterface.OnClickListener onClickInstall = makeOnClickInstallDialogListener();
DialogInterface.OnClickListener onClickCancel = makeOnFailedToInitHandler();
DialogGenerator.createConfirmDialog(this,
getResources().getString(R.string.tts_install_tts_data),
onClickCancel,
onClickInstall).show();
}
/**
* @see root.gast.speech.tts.TtsStartupListener#onWaitingForLanguageData()
*/
@Override
public void onWaitingForLanguageData()
{
Log.d(TAG, "waiting for language data");
//either wait for install
DialogInterface.OnClickListener onClickWait = makeOnFailedToInitHandler();
DialogInterface.OnClickListener onClickInstall = makeOnClickInstallDialogListener();
//or just do it again
AlertDialog d = DialogGenerator.createConfirmDialog(TextToSpeechPlay.this,
getResources().getString(R.string.d_info),
getResources().getString(R.string.tts_d_stilldownloading_message),
onClickWait,
getResources().getString(R.string.tts_d_stilldownloading_buttonwait),
onClickInstall,
getResources().getString(R.string.tts_d_stilldownloading_buttonretry));
d.show();
}
private DialogInterface.OnClickListener makeOnClickInstallDialogListener()
{
return new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
ttsInit.installLanguageData();
}
};
}
private DialogInterface.OnClickListener makeOnFailedToInitHandler()
{
return new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
finish();
}
};
}
@Override
public void onSuccessfulInit(TextToSpeech tts)
{
Log.d(TAG, "successful init");
this.tts = tts;
activateUi();
setTtsListener();
}
/**
* set the TTS listener to call {@link #onDone(String)} depending
* on the Build.Version
*/
private void setTtsListener()
{
final TextToSpeechPlay callWithResult = this;
if (Build.VERSION.SDK_INT >= 15)
{
int listenerResult = tts.setOnUtteranceProgressListener(new UtteranceProgressListener()
{
@Override
public void onDone(String utteranceId)
{
callWithResult.onDone(utteranceId);
}
@Override
public void onError(String utteranceId)
{
callWithResult.onError(utteranceId);
}
@Override
public void onStart(String utteranceId)
{
callWithResult.onStart(utteranceId);
}
});
if (listenerResult != TextToSpeech.SUCCESS)
{
Log.e(TAG, "failed to add utterance progress listener");
}
}
else
{
int listenerResult = tts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener()
{
@Override
public void onUtteranceCompleted(String utteranceId)
{
callWithResult.onDone(utteranceId);
}
});
if (listenerResult != TextToSpeech.SUCCESS)
{
Log.e(TAG, "failed to add utterance completed listener");
}
}
}
public void onDone(final String utteranceId)
{
runOnUiThread(new Runnable() {
@Override
public void run()
{
Log.d(TAG, "utterance completed");
stopSpeak.setEnabled(false);
//add to the "log"
appendToLog("completed: " + utteranceId);
}
});
}
public void onStart(final String utteranceId)
{
runOnUiThread(new Runnable() {
@Override
public void run()
{
Log.d(TAG, "TTS start");
appendToLog("started: " + utteranceId);
}
});
}
public void onError(final String utteranceId)
{
runOnUiThread(new Runnable() {
@Override
public void run()
{
Log.e(TAG, "TTS error");
appendToLog("error: " + utteranceId);
}
});
}
private void deactivateUi()
{
Log.d(TAG, "deactivate ui");
//don't enable until the initialization is complete
speak.setEnabled(false);
earcon.setEnabled(false);
silence.setEnabled(false);
}
private void activateUi()
{
Log.d(TAG, "activate ui");
speak.setEnabled(true);
earcon.setEnabled(true);
silence.setEnabled(true);
}
private void hookButtons()
{
speak = (Button)findViewById(R.id.btn_speak);
//speak something according to the preferences
speak.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
String speaking = getTextToSpeak();
String utteranceId = makeUtteranceId(SPEAK_ROOT);
startSpeaking(utteranceId, speaking);
setTtsSettingsFromPreferences();
boolean toFile = preferences.getBoolean(TextToSpeechPlay.this, R.string.pref_tts_tofile, R.string.pref_tts_tofile_default);
if (toFile)
{
String outputFile = getOutputFilePath(speaking);
HashMap<String, String> params = TextToSpeechUtils.makeParamsWith(utteranceId);
addFromPreferences(params);
tts.synthesizeToFile(speaking, params, outputFile);
}
else
{
HashMap<String, String> params = TextToSpeechUtils.makeParamsWith(utteranceId);
addFromPreferences(params);
tts.speak(speaking, queueMode, params);
}
}
});
stopSpeak = (Button)findViewById(R.id.btn_stop_speak);
stopSpeak.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
Log.d(TAG, "before stop is speaking: " + tts.isSpeaking());
tts.stop();
}
});
//play silence
silence = (Button)findViewById(R.id.btn_play_silence);
silence.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
String utteranceId = makeUtteranceId(SILENCE_ROOT);
startSpeaking(utteranceId, "");
setTtsSettingsFromPreferences();
int silenceLength = preferences.getInt(TextToSpeechPlay.this, R.string.pref_tts_silencelength, R.string.pref_tts_silencelength_default);
HashMap<String, String> params = TextToSpeechUtils.makeParamsWith(utteranceId);
addFromPreferences(params);
tts.playSilence(silenceLength, queueMode, params);
}
});
//play an earcon
earcon = (Button)findViewById(R.id.btn_play_earcon);
earcon.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
String earconName = "android";
String utteranceId = makeUtteranceId(EARCON_ROOT);
startSpeaking(utteranceId, "");
setTtsSettingsFromPreferences();
tts.addEarcon("android", "root.gast.playground", R.raw.tone);
HashMap<String, String> params = TextToSpeechUtils.makeParamsWith(utteranceId);
addFromPreferences(params);
tts.playEarcon(earconName, queueMode,params);
}
});
deactivateUi();
log = (TextView)findViewById(R.id.tv_resultlog);
whatToSay = (EditText)findViewById(R.id.et_tts_target);
//default things to say
String [] defaultTargets = getResources().getStringArray(R.array.default_tts_targets);
presets = new ArrayList<String>();
for (String target : defaultTargets)
{
presets.add(target);
}
//also add the files within the "tts" dir as possible
//tts things to say.
try
{
String[] files = getAssets().list("tts");
Log.d(TAG, "num long files: " + files);
for (String fileName : files)
{
presets.add("(" + fileName + ")");
}
} catch (IOException e)
{
Log.e(TAG, "can't file tts ", e);
}
//selecting a preset
ImageButton bt = (ImageButton) findViewById(R.id.bt_ttsTargets);
bt.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
DialogGenerator.makeSelectListDialog(
getResources().getString(
R.string.tts_textToSpeakHeading),
TextToSpeechPlay.this, presets,
new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog,
int which)
{
String whichPreset = presets.get(which);
whatToSay.setText(whichPreset);
}
}, DialogGenerator.DO_NOTHING).show();
}
});
}
/**
* get the filename to output when the user wants to write to file
* @param speaking what the user is speaking
*/
protected String getOutputFilePath(String speaking)
{
String appPath = EXTERNAL_STORAGE_DIR + File.pathSeparator + speaking.replaceAll("\\s", "_") + ".wav";
return appPath;
}
/**
* attach the utterance counter
*/
private String makeUtteranceId(String root)
{
String id = root + utteranceCounter;
utteranceCounter++;
return id;
}
/**
* either just get the text from the {@link TextView}
* or if it is a file, read the file in
*/
private String getTextToSpeak()
{
String toSay = whatToSay.getText().toString();
if (toSay.contains("("))
{
//load the whole file to and play that
String fileName = toSay.substring(1, toSay.length()-1);
try
{
InputStream in = getAssets().open("tts" + File.separator + fileName);
String contents = FileUtil.getContentsOfReader(new BufferedReader(new InputStreamReader(in)));
toSay = contents;
} catch (IOException e)
{
Log.e(TAG, "error reading file: " + fileName, e);
}
}
return toSay;
}
/**
* add parameters based on preferences
*/
private void addFromPreferences(Map<String,String> parameters)
{
float volume = preferences.getFloat(this, R.string.pref_tts_speechrate, R.string.pref_tts_speechrate_default);
float pan = preferences.getFloat(this, R.string.pref_tts_pan, R.string.pref_tts_pan_default);
if (pan > 1)
{
pan = 1;
}
if (pan < -1)
{
pan = -1;
}
if (volume > 1)
{
volume = 1;
}
if (volume < 0)
{
volume = 0;
}
parameters.put(TextToSpeech.Engine.KEY_PARAM_VOLUME, String.valueOf(volume));
parameters.put(TextToSpeech.Engine.KEY_PARAM_PAN, String.valueOf(pan));
int audioStream = preferences.getInteger(this, R.string.pref_tts_stream, R.integer.pref_tts_stream_default);
parameters.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(audioStream));
}
/**
* set the tts parameters based on preferences
*/
private void setTtsSettingsFromPreferences()
{
float speechRate = preferences.getFloat(this, R.string.pref_tts_speechrate, R.string.pref_tts_speechrate_default);
Log.d(TAG, "set speech rate: " + speechRate);
tts.setSpeechRate(speechRate);
float pitch = preferences.getFloat(this, R.string.pref_tts_pitch, R.string.pref_tts_pitch_default);
Log.d(TAG, "set pitch rate: " + pitch);
tts.setPitch(pitch);
boolean queueFlush = preferences.getBoolean(this, R.string.pref_tts_queueaction, R.string.pref_tts_queueaction_default);
Log.d(TAG, "set queueFlush: " + queueFlush);
if (queueFlush)
{
queueMode = TextToSpeech.QUEUE_FLUSH;
}
else
{
queueMode = TextToSpeech.QUEUE_ADD;
}
boolean customSpeech = preferences.getBoolean(this, R.string.pref_tts_customaudio, R.string.pref_tts_customaudio_default);
Log.d(TAG, "set customSpeech: " + customSpeech);
if (customSpeech)
{
tts.addSpeech("android", "root.gast.playground", R.raw.androidcalm);
}
else
{
//turn it off
tts.addSpeech("android", null, R.raw.androidcalm);
}
}
private void startSpeaking(String utteranceId, String text)
{
stopSpeak.setEnabled(true);
appendToLog("queued " + utteranceId + " " + text.substring(0, Math.min(CHARACTERS_IN_LOG, text.length())));
}
private void appendToLog(String appendThis)
{
String currentLog = log.getText().toString();
currentLog = appendThis + "\n" + currentLog;
log.setText(currentLog);
}
/**
* shutdown tts on destroy
*/
@Override
protected void onDestroy()
{
if (tts != null)
{
tts.shutdown();
}
super.onDestroy();
}
//menu handling
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.tts_menu, menu);
return true;
}
public boolean onOptionsItemSelected (MenuItem item)
{
switch (item.getItemId())
{
case R.id.m_ttsparam:
//launch preferences activity
Intent i = new Intent(this, SummarizingEditPreferences.class);
i.putExtra(SummarizingEditPreferences.WHICH_PREFERENCES_INTENT, R.xml.tts_preferences);
String preferenceName = getResources().getString(R.string.pref_tts_key);
i.putExtra(SummarizingEditPreferences.WHICH_PREFERENCES_NAME_INTENT, preferenceName);
startActivity(i);
break;
case R.id.m_setlanguage:
final List<Locale> localesUsed = TextToSpeechUtils.getLocalesSupported(this, tts);
DialogGenerator.makeSelectListDialog(getResources().getString(R.string.d_select_language),
this, localesUsed, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
Locale loc = localesUsed.get(which);
//Note: only supported locales appear in the
//list so this is safe
tts.setLanguage(loc);
//also set a preference
preferences.setLanguage(TextToSpeechPlay.this, loc);
}
}).show();
break;
case R.id.m_setaudiostream:
final Map<String, Integer> streamIdToName = new LinkedHashMap<String, Integer>();
streamIdToName.put("STREAM_ALARM", AudioManager.STREAM_ALARM);
streamIdToName.put("STREAM_DTMF", AudioManager.STREAM_DTMF);
streamIdToName.put("STREAM_MUSIC", AudioManager.STREAM_MUSIC);
streamIdToName.put("STREAM_NOTIFICATION", AudioManager.STREAM_NOTIFICATION);
streamIdToName.put("STREAM_RING", AudioManager.STREAM_RING);
streamIdToName.put("STREAM_SYSTEM", AudioManager.STREAM_SYSTEM);
streamIdToName.put("STREAM_VOICE_CALL", AudioManager.STREAM_VOICE_CALL);
final List<String> streamNames = new ArrayList<String>(streamIdToName.keySet());
DialogGenerator.makeSelectListDialog(getResources().getString(R.string.d_select_audio_stream),
this, streamNames, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
int audioStream = streamIdToName.get(streamNames.get(which));
preferences.setInt(getString(R.string.pref_tts_stream), audioStream);
}
}).show();
break;
default:
throw new RuntimeException("unknown menu selection");
}
return true;
}
}