Hi I've read some post with same question but can't find the exact or I must say the answer I've been looking for. Well I just want to know how I can get the playback level of the audio file that is set on the mediaplayer. I already tried the int volume_level = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); but from what I see. I only get the current volume set on my device. Well what I want to achive is to add an animation that follows with level of my audio being played. Here's my code so far:
before the call of play audio method:
audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
the playback method:
public void playAudio(String record_path) throws IOException{
if(audioPlayer!=null && mpStatus == State.Paused){
/*play from paused state*/
audioPlayer.start();
mpStatus = State.Playing;
}
else
{
/*play from start of recording*/
setMediaPlayer(record_path);
audioPlayer.start();
mpStatus = State.Playing;
}
}
and the thread:
private class playBackRunnable extends Thread {
final long start_time = System.currentTimeMillis();
public void run() {
while(chk_play.isChecked()){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
return;
} catch (Exception e) {
return;
}
final long elapsed = System.currentTimeMillis() - start_time;
final String elapsed_time = util.getAsTime((int) elapsed);
runOnUiThread(new Runnable() {
#Override
public void run() {
int volume_level = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
int amp = (int)(volume_level * 100.f)/100;
Log.v("Volume Level", String.valueOf(amp));
if(chk_play.isChecked()){
prog_volume.setProgress(amp);
//txt_rectime.setText(elapsed_time);
if(amp <= 40 ){
prog_volume.setProgressDrawable(getResources().getDrawable(R.drawable.progress_green));
}else if(amp <= 60){
prog_volume.setProgressDrawable(getResources().getDrawable(R.drawable.progress_yellow));
}else if(amp <= 80){
prog_volume.setProgressDrawable(getResources().getDrawable(R.drawable.progress_orange));
}else {
prog_volume.setProgressDrawable(getResources().getDrawable(R.drawable.progress_red));
}
}
}
});
}
}
}
Hope someone can help me with this. Thanks in advance.
EDIT:
Added audioPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); before audioPlayer.prepare() still not working.
The only solution I know uses the Visualizer class. For convenience reason, I suggest using AudioCapture.java from KitKat live wallpaper sources, which add a data processing layer over Visualizer. The project linked above also gives some examples of uses, and here is how I use it in myself for JUnits tests :
private int getAudioOutputAmplitude(int durationInSeconds) throws InterruptedException {
AudioCapture mAudioCapture = new AudioCapture(AudioCapture.TYPE_PCM, 1024);
mAudioCapture.start();
Thread.sleep(durationInSeconds * 1000);
int [] mVizData;
mVizData = mAudioCapture.getFormattedData(1, 1);
mAudioCapture.release();
int minValue = 0;
int maxValue = 0;
for (int value:mVizData){
if (value<minValue){
minValue = value;
} else if (value>maxValue){
maxValue = value;
}
}
return maxValue-minValue;
}
Related
I am creating a class witch loads up a few sounds. However, isPlaying keeps on throwing an exception after a while and then stops playing that particular sound permanently, while other sounds keep playing OK.
public class MySound {
int m_IdMyId;
int m_ResId;
boolean m_IsLoaded;
MediaPlayer m_Media;
public MySound(int idMyId, int resId){
m_IdMyId = idMyId;
m_ResId = resId;
m_IsLoaded = false;
m_Media = null;
}
}
In this m_IdMyId is just an id for my game. m_ResId is something like R.raw.mysound1. m_IsLoaded I think is automatically set to true as I am loading synconously. m_Media is the MediaPlayer object.
I am calling stop() very regularly, as it is a game and I need to check every second or so to make sure certain sounds are stopped. It is here that it throws an exception when snd.m_Media.isPlaying() is called.
I cannot seem to access e to see what the error is.
Also I would like to know how I can set m_IsLoaded correctly. How do I know when the sound is fully loaded and ready to use?
Here is my management class:
public class MySoundManager {
MainActivity m_Context;
ArrayList<MySound> mySounds;
public MySoundManager(MainActivity context) {
m_Context = context;
mySounds = new ArrayList<MySound>();
mySounds.add(new MySound(8, R.raw.mysound1));
mySounds.add(new MySound(10, R.raw.mysound2));
mySounds.add(new MySound(22, R.raw.mysound3));
mySounds.add(new MySound(100, R.raw.click));
mySounds.add(new MySound(101, R.raw.error));
for(MySound mysound : mySounds) {
mysound.m_Media = MediaPlayer.create(m_Context, mysound.m_ResId); // no need to call prepare(); create() does that for you
mysound.m_IsLoaded = true;
}
}
// I call this when the main thread calls onResume
public void onResume(){
for(MySound mysound : mySounds) {
if(mysound.m_Media == null) {
mysound.m_Media = MediaPlayer.create(m_Context, mysound.m_ResId); // no need to call prepare(); create() does that for you
mysound.m_IsLoaded = true;
}
}
}
// I call this when the main thread calls onPause
public void onPause(){
for(MySound mysound : mySounds) {
if(mysound.m_Media != null) {
mysound.m_Media.stop();
mysound.m_Media.release();
mysound.m_Media = null;
}
}
}
public boolean IsAllLoaded(){
for(MySound mysound : mySounds) {
if(!mysound.m_IsLoaded) return false;
}
return true;
}
public MySound FindMySoundByIdMyId(int idMyId){
try {
for(MySound mysound : mySounds) {
if (mysound.m_IdMyId == idMyId) return mysound;
}
}catch(Exception e) {
MySound snd;
snd = null; // ToDo
}
return null;
}
public void play(int idMyId){
MySound snd;
try{
if((snd = FindMySoundByIdMyId(idMyId)) != null)
snd.m_Media.start();
}catch(IllegalStateException e) {
snd = null; // ToDo
}
}
public void pause(int idMyId){
MySound snd;
try{
if((snd = FindMySoundByIdMyId(idMyId)) != null &&
snd.m_Media.isPlaying())
snd.m_Media.pause();
}catch(IllegalStateException e) {
snd = null; // ToDo
}
}
public void pauseAll(){
try{
for (MySound mysound : mySounds) {
if(mysound.m_Media.isPlaying())
mysound.m_Media.pause();
}
}catch(IllegalStateException e) {
MySound snd;
snd = null; // ToDo
}
}
public boolean isPlaying(int idMyId, MySound[] fill){
MySound snd;
fill[0] = null;
try{
if((snd = FindMySoundByIdMyId(idMyId)) != null){
fill[0] = snd;
return snd.m_Media.isPlaying();
}
}catch(IllegalStateException e) {
snd = null; // ToDo
}
return false;
}
public void stop(int idMyId){
MySound snd;
try{
if((snd = FindMySoundByIdMyId(idMyId)) != null &&
snd.m_Media.isPlaying())
snd.m_Media.stop();
}catch(IllegalStateException e) {
snd = null; // ToDo
}
}
// The str is in the format
// number id, 1 = on 0 = off,dont play if this id playing;
public void PlaySound(String str) {
boolean isplaying;
int i, len, id, idDontPlay, milliNow;
String[] strARR = str.split(";");
String[] strARR2;
Integer[] tmpIntARR;
ArrayList<Integer[]> onARR = new ArrayList<Integer[]>();
ArrayList<Integer> offARR = new ArrayList<Integer>();
MySound snd;
for (i = 0, len = strARR.length; i < len; i++) {
if(strARR[i].length() <= 0) continue;
if((strARR2 = strARR[i].split(",")) != null &&
strARR2.length >= 3 &&
strARR2[0].length() > 0 &&
strARR2[1].length() > 0 &&
strARR2[2].length() > 0){
id = Integer.parseInt(strARR2[0]);
idDontPlay = Integer.parseInt(strARR2[2]);
tmpIntARR = new Integer[2];
tmpIntARR[0] = id;
tmpIntARR[1] = idDontPlay;
if(Integer.parseInt(strARR2[1]) == 1){
onARR.add(tmpIntARR);
} else offARR.add(id);
}
}
// Turn off all sounds that need to be turned off
for (i=0,len=offARR.size();i<len;i++) {
id = offARR.get(i);
stop(id);
}
// Turn all sounds that need to be turned on,
// but only if the sound that blocks a new sound is not playing
for (i=0,len=onARR.size();i<len;i++) {
tmpIntARR = onARR.get(i);
id = tmpIntARR[0];
idDontPlay = tmpIntARR[1];
// We dont play if the idDontPlay sound is already playing
if((snd = FindMySoundByIdMyId(idDontPlay)) != null &&
snd.m_Media.isPlaying())
continue;
if((snd = FindMySoundByIdMyId(id)) != null){
isplaying = snd.m_Media.isPlaying();
milliNow = snd.m_Media.getCurrentPosition();
if(milliNow > (snd.m_Media.getDuration() - 1000) ||
(!isplaying && milliNow > 0)){
snd.m_Media.seekTo(0); // Half a second inside
}
if(!isplaying) snd.m_Media.start();
}
}
}
}
Creating a MediaPlayer instance for every sound is not a good practice to get low latency, especially for short clips. MediaPlayer is for longer clips such as Music files it uses large buffer so, larger buffer means high latency. Also, there is AudioFocus mechanism on Android that may interfere your sound playing session. So, I strongly recommend you to use SoundPool to play short clips like game sounds.
After a particular button is clicked, I want to have it so that my audio would play fifteen times, and have the progress bar increment each time. I had also envisioned having a delay in between each time the audio plays.
What's currently happening is that all of the beeps play back to back without delay, and after that, the progress bar gets incremented right to max straight away. Using Handler somehow doesn't manage to delay the audio playing.
I'm a beginner in app development, so excuse the shoddy code:
public void click1(View view) throws IOException {
int i;
// ProgressBar1.setProgress(0);
for (i = 1; i < 16; i = i + 1)
{
int secs = 2; // Delay in seconds
Utils.delay(secs, new Utils.DelayCallback() {
#Override
public void afterDelay() throws IOException {
// Do something after delay
PlayShortAudioFileViaAudioTrack(500, 1);
ProgressBar1.incrementProgressBy(1);
}
});
}
}
Here's the delay code:
public class Utils {
public interface DelayCallback{
void afterDelay() throws IOException;
}
public static void delay(int secs, final DelayCallback delayCallback){
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
try {
delayCallback.afterDelay();
} catch (IOException e) {
e.printStackTrace();
}
}
}, secs * 1000); // afterDelay will be executed after (secs*1000) milliseconds.
}
}
The function that plays audio is
public void PlayShortAudioFileViaAudioTrack(int f, double duration) throws IOException
{ int sampleRate = 48000; // Samples per second
//double duration = 2.0;
long numFrames = (long)(duration * sampleRate);
long frameCounter = 0;
int intSize = android.media.AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_FLOAT);
AudioTrack at = new AudioTrack(AudioManager.STREAM_MUSIC, 48000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_FLOAT, intSize, AudioTrack.MODE_STREAM);
float[] buffer = new float[intSize];
while (frameCounter < numFrames)
{
long remaining = numFrames - frameCounter;
int toWrite = (remaining > intSize) ? intSize : (int) remaining;
for (int s=0 ; s<toWrite ; s++, frameCounter++)
{
buffer[s] = (float)Math.sin(2.0 * Math.PI * f * frameCounter / sampleRate);
// buffer[1][s] = Math.sin(2.0 * Math.PI * 500 * frameCounter / sampleRate);
}
if (at!=null) {
// Write the byte array to the track
at.play();
at.write(buffer, 0, intSize, AudioTrack.WRITE_BLOCKING);
}
else
Log.d("TCAudio", "audio track is not initialised ");
}
at.stop();
at.release();
}
Changing the audiotrack mode to NON-BLOCKING from BLOCKING results in the audio just playing once, and the progress bar still shooting up to full immediately.
To solve your problem, you can use AsynkTask<> like this:
Create a subclass of AsynkTask<> in your Activity to handle the delayed action and the updates of the progressbar.
Then in your click1()-method you just have to create a new instance of your AsyncTask subclass and execute it. You can give it the number of cycles on the call of execute(). The following code should work:
...
ProgressBar1.setMax(16); // to get 16 cycles like in your example
...
public void click1(View view) throws IOException {
int max = ProgressBar1.getMax();
new MyTask().execute(max);
}
class MyTask extends AsyncTask<Integer, Integer, Void> {
#Override
protected Void doInBackground(Integer... params) {
for (int i=0 ; i <= params[0]; i++) {
try {
Thread.sleep(secs * 1000);
publishProgress(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onPreExecute() {
//do something before execution
}
#Override
protected void onProgressUpdate(Integer... values) {
//do your delayed stuff
PlayShortAudioFileViaAudioTrack(500, 1);
ProgressBar1.incrementProgressBy(1);
}
}
I am playing MIDI notes using this library as follows:
handler.postDelayed(new Runnable() {
ShortMessage message;
#Override
public void run() {
{
MidiEvent event = midiEvents.get(index[0]);
if (index[0] < midiEvents.size() - 1) {
delta = midiEvents.get(index[0] + 1).getDelta();
time[0] = timeFactor * midiEvents.get(index[0] + 1).getDelta();
mTotalMidiTime += time[0];
int noteValue;
int NOTE_STATUS;
if (event instanceof NoteOn) {
noteValue = ((NoteOn) event).getNoteValue();
NOTE_STATUS = NOTE_ON;
if (index[0] != 0) {
if (delta != 0) {
Intent intent = new Intent();
intent.setAction(Constants.SCROLL_RECYCLERVIEW);
localBroadcastManager.sendBroadcast(intent);
}
}
} else {
noteValue = ((NoteOff) event).getNoteValue();
NOTE_STATUS = NOTE_OFF;
}
try {
message = new ShortMessage(NOTE_STATUS, 2, noteValue,
127);
} catch (Exception e) {
e.printStackTrace();
}
Intent intent = new Intent();
intent.setAction(Constants.ACTION_SEEK);
localBroadcastManager.sendBroadcast(intent);
if (message != null)
recv.send(message, -1);
index[0]++;
} else {
index[0] = 0;
time[0] = 1;
mTotalMidiTime = mMinimumTime;
delta = 0;
}
handler.postDelayed(this, time[0]);
}
}
}, 0);
With each NoteOn event I am smooth scrolling a RecyclerView and updating a Seekbar using a LocalBroadcastManager
My problem is that playback is fine when UI operations are not performed but playback and UI get completely out of sync as soon as multiple MIDI notes (chords) are played in very quick succession. It would be appreciated if any performance improvements are suggested for the same. I have already tried performing the UI operations in runOnUiThread and also launching a new Handler for UI operations.
My BroadcastReceiver is as follows:
BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String actionType = intent.getAction();
switch (actionType) {
case Constants.SCROLL_RECYCLERVIEW:
Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
mNotesRecycler.smoothScrollBy(pixels, 0);
}
});
break;
case Constants.ACTION_SEEK:
Handler seekHandler = new Handler();
seekHandler.post(new Runnable() {
#Override
public void run() {
mPinchSeekBar.setSelectedCentreValue(mTotalMidiTime);
mCurrentTime.setText(timeInMinutes((int) mTotalMidiTime));
}
});
break;
}
}
};
Besides the optimizations you can do, I think the problem itself is RecyclerView's smoothScroll is more a method you call sometimes to do a fancy scroll rather than bomb it with requests that cause it to constantly recompute the running animation.
One thing you can try is to write yourself a simple scroll handler that calls mRecyclerView.scrollTo() that does the scroll without animation but I guess it's going to be more reliable.
Try something like this
Handler mHandler = new Handler();
int mPosition, mTargetPosition;
#IntRange(from = 20, to = 100)
final int INTERVAL = 50; // try with 50 ms or a little lower
Runnable mTimerScroll = new Runnable() {
public void run() {
if (mPosition != mTargetPosition) {
if (mPosition < mTargetPosition) {
mPosition += mDelta;
if (mPosition > mTargetPosition) mPosition = mTargetPosition;
}
if (mPosition > mTargetPosition) {
mPosition -= mDelta;
if (mPosition < mTargetPosition) mPosition = mTargetPosition;
}
mRecyclerView.scrollTo(mPosition, 0);
}
// repeat every 50ms
mTimerScroll.post(mRunnable, INTERVAL);
}
}
And then you start it
void startTimer() {
stop(); // prevent double start
mHandler.post(mTimerScroll);
}
void stopTimer() {
mHandler.removeCallbacks(mTimerScroll);
}
void scroll(int target) {
mTargetPosition = target;
}
void scrollBy(int pixels) {
scroll(mPosition + pixels);
}
I don't know the specifics of your app but it might work.
About optimization, you are using a lot of stuff there that can or cannot be necessary depending on the specifics of your library, etc, but if there are no different threads or services involved you could avoid the LocalBroadcast thing and all the handler.post() as everything is already on the UI thread (handler.post just posts a runnable to the thread where the handler was created, that in your case is the UI thread, so it doesn't do anything)
I created an object that continuously reads the input from the mic but I'm having some troubles with it. I need to be able to pause and start it, so I've implemented the methods, but the problem is that, on an older phone (Galaxy Y) it works very well the first time I start it, but after I pause it and start it again it quickly crashes due to OutOfMemoryException.
#Override
public void run() {
while (mIsRunning) {
synchronized (mThread) {
while (mIsPaused) {
try {
mThread.wait();
} catch (InterruptedException e) {
}
}
}
double sum = 0;
int readSize = mRecorder.read(mBuffer, 0, mBuffer.length);
if (readSize > 0) {
short[] samples = new short[readSize];
for (int i = 0; i < readSize; i++) {
sum += mBuffer[i] * mBuffer[i];
samples[i] = mBuffer[i];
}
final int amplitude = (int) Math.sqrt((sum / readSize));
Message message = new Message();
message.arg1 = amplitude;
message.arg2 = readSize;
message.obj = samples;
message.what = 0;
if (mHandler != null)
mHandler.sendMessage(message);
}
}
}
public void start() {
if (!mIsRunning) {
mIsRunning = true;
mThread.start();
} else synchronized (mThread) {
mIsPaused = false;
mThread.notifyAll();
}
mRecorder.startRecording();
}
public void pause() {
synchronized (mThread) {
mIsPaused = true;
}
mRecorder.stop();
}
So, the first time all works well, but after I call stop() on the AudioRecord and start reading the input again I quickly run out of memory. Any ideas on how to fix this? I was thinking of just not stopping the recording and just not sending the data through the handler but I don't know. Thanks.
The solution was destroying (and releasing) the AudioRecord object on every pause and just recreating it on each start.
I'm playing text with android TTS - android.speech.tts.TextToSpeech
I use: TextToSpeech.speak to speak and .stop to stop. Is there a way to pause the text also?
The TTS SDK doesn't have any pause functionality that I know of. But you could use synthesizeToFile() to create an audio file that contains the TTS output. Then, you would use a MediaPlayer object to play, pause, and stop playing the file. Depending on how long the text string is, it might take a little longer for audio to be produced because the synthesizeToFile() function would have to complete the entire file before you could play it, but this delay should be acceptable for most applications.
I used splitting of string and used playsilence() like below:
public void speakSpeech(String speech) {
HashMap<String, String> myHash = new HashMap<String, String>();
myHash.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "done");
String[] splitspeech = speech.split("\\.");
for (int i = 0; i < splitspeech.length; i++) {
if (i == 0) { // Use for the first splited text to flush on audio stream
textToSpeech.speak(splitspeech[i].toString().trim(),TextToSpeech.QUEUE_FLUSH, myHash);
} else { // add the new test on previous then play the TTS
textToSpeech.speak(splitspeech[i].toString().trim(), TextToSpeech.QUEUE_ADD,myHash);
}
textToSpeech.playSilence(750, TextToSpeech.QUEUE_ADD, null);
}
}
You can make the TTS pause between sentences, or anywhere you want by adding up to three periods (".") all followed by a single space " ". The example below has a long pause at the beginning, and again before the message body. I'm not sure that is what you are after though.
private final BroadcastReceiver SMScatcher = new BroadcastReceiver() {
#Override
public void onReceive(final Context context, final Intent intent) {
if (intent.getAction().equals(
"android.provider.Telephony.SMS_RECEIVED")) {
// if(message starts with SMStretcher recognize BYTE)
StringBuilder sb = new StringBuilder();
/*
* The SMS-Messages are 'hiding' within the extras of the
* Intent.
*/
Bundle bundle = intent.getExtras();
if (bundle != null) {
/* Get all messages contained in the Intent */
Object[] pdusObj = (Object[]) bundle.get("pdus");
SmsMessage[] messages = new SmsMessage[pdusObj.length];
for (int i = 0; i < pdusObj.length; i++) {
messages[i] = SmsMessage
.createFromPdu((byte[]) pdusObj[i]);
}
/* Feed the StringBuilder with all Messages found. */
for (SmsMessage currentMessage : messages) {
// periods are to pause
sb.append("... Message From: ");
/* Sender-Number */
sb.append(currentMessage.getDisplayOriginatingAddress());
sb.append(".. ");
/* Actual Message-Content */
sb.append(currentMessage.getDisplayMessageBody());
}
// Toast.makeText(application, sb.toString(),
// Toast.LENGTH_LONG).show();
if (mTtsReady) {
try {
mTts.speak(sb.toString(), TextToSpeech.QUEUE_ADD,
null);
} catch (Exception e) {
Toast.makeText(application, "TTS Not ready",
Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
}
}
}
};
If you omit the space after the last period it will (or may) not work as expected.
In the absence of a pause option, you can add silence for the duration of when you want to delay the TTS Engine speaking. This of course would have to be a predetermined 'pause' and wouldn't help to include functionality of a pause button, for example.
For API < 21 : public int playSilence (long durationInMs, int queueMode, HashMap params)
For > 21 : public int playSilentUtterance (long durationInMs, int queueMode, String utteranceId)
Remember to use TextToSpeech.QUEUE_ADD rather than TextToSpeech.QUEUE_FLUSH otherwise it will clear the previously started speech.
I used a different approach.
Seperate your text into sentences
Speak every sentence one by one and keep track of the spoken sentence
pause will stop the text instantly
resume will start at the beginning of the last spoken sentence
Kotlin code:
class VoiceService {
private lateinit var textToSpeech: TextToSpeech
var sentenceCounter: Int = 0
var myList: List<String> = ArrayList()
fun resume() {
sentenceCounter -= 1
speakText()
}
fun pause() {
textToSpeech.stop()
}
fun stop() {
sentenceCounter = 0
textToSpeech.stop()
}
fun speakText() {
var myText = "This is some text to speak. This is more text to speak."
myList =myText.split(".")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
textToSpeech.speak(myList[sentenceCounter], TextToSpeech.QUEUE_FLUSH, null, utteranceId)
sentenceCounter++
} else {
var map: HashMap<String, String> = LinkedHashMap<String, String>()
map[TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID] = utteranceId
textToSpeech.speak(myList[sentenceCounter], TextToSpeech.QUEUE_FLUSH, map)
sentenceCounter++
}
}
override fun onDone(p0: String?) {
if (sentenceCounter < myList.size) {
speakText()
} else {
speakNextText()
}
}
}
I haven't yet tried this, but I need to do the same thing. My thinking is to first split your speech text into an array of words.
Then create a recursive function that plays the next word after the current word is finished, while keeping a counter of the current word.
divide the messages into parts and listen for last utterance by using onutteranceprogress listener
tts.playSilence(1250, TextToSpeech.QUEUE_ADD, null);
It seems that if you put a period after a word AND start the next word with a capital letter, just like a new sentence, like this:
after we came home. We ate dinner.
the "home. We" will then have a pause in it.
This becomes a grammatically strange way of writing it.
So far I have only tested this in my own language, Swedish.
It might be important that the space is there.
Also, an escaped quote (\") seems to have it pause somewhat as well - at least, if you put it around a word it adds space around the word.
This solution is not perfect, but an alternative to #Aaron C's solution may be to create a custom text to speech class like the below. This solution may work well enough if your text is relatively short and spoken words per minute is accurate enough for the language you are using.
private class CustomTextToSpeech extends TextToSpeech {
private static final double WORDS_PER_MS = (double)190/60/1000;
long startTimestamp = 0;
long pauseTimestamp = 0;
private Handler handler;
private Runnable speakRunnable;
StringBuilder textToSpeechBuilder;
private boolean isPaused = false;
public CustomTextToSpeech(Context context, OnInitListener initListener){
super(context, initListener);
setOnUtteranceProgressListener(new UtteranceProgressListener() {
#Override
public void onDone(String arg0) {
Log.d(TAG, "tts done. " + arg0);
startTimestamp = 0;
pauseTimestamp = 0;
handler.postDelayed(speakRunnable, TTS_INTERVAL_MS);
}
#Override
public void onError(String arg0) {
Log.e(TAG, "tts error. " + arg0);
}
#Override
public void onStart(String arg0) {
Log.d(TAG, "tts start. " + arg0);
setStartTimestamp(System.currentTimeMillis());
}
});
handler = new Handler();
speakRunnable = new Runnable() {
#Override
public void run() {
speak();
}
};
textToSpeechBuilder = new StringBuilder(getResources().getString(R.string.talkback_tips));
}
public void setStartTimestamp(long timestamp) {
startTimestamp = timestamp;
}
public void setPauseTimestamp(long timestamp) {
pauseTimestamp = timestamp;
}
public boolean isPaused(){
return (startTimestamp > 0 && pauseTimestamp > 0);
}
public void resume(){
if(handler != null && isPaused){
if(startTimestamp > 0 && pauseTimestamp > 0){
handler.postDelayed(speakRunnable, TTS_SETUP_TIME_MS);
} else {
handler.postDelayed(speakRunnable, TTS_INTERVAL_MS);
}
}
isPaused = false;
}
public void pause(){
isPaused = true;
if (handler != null) {
handler.removeCallbacks(speakRunnable);
handler.removeMessages(1);
}
if(isSpeaking()){
setPauseTimestamp(System.currentTimeMillis());
}
stop();
}
public void utter(){
if(handler != null){
handler.postDelayed(speakRunnable, TTS_INTERVAL_MS);
}
}
public void speak(){
Log.d(TAG, "textToSpeechBuilder: " + textToSpeechBuilder.toString());
if(isPaused()){
String[] words = textToSpeechBuilder.toString().split(" ");
int wordsAlreadySpoken = (int)Math.round((pauseTimestamp - startTimestamp)*WORDS_PER_MS);
words = Arrays.copyOfRange(words, wordsAlreadySpoken-1, words.length);
textToSpeechBuilder = new StringBuilder();
for(String s : words){
textToSpeechBuilder.append(s);
textToSpeechBuilder.append(" ");
}
} else {
textToSpeechBuilder = new StringBuilder(getResources().getString(R.string.talkback_tips));
}
if (tts != null && languageAvailable)
speak(textToSpeechBuilder.toString(), TextToSpeech.QUEUE_FLUSH, new Bundle(), "utter");
}
}