I have developed a TTS based application. When playing it sound, if any other notification triggered, then TTS playing with sound breaking. (Ex: if SMS received TTS sound breaks)
What are condition under which such an issue can occur?
there is a bug in the Android API < 8. To restore the original sound, just say "nothing".
protected void speak(final String text, final int mode) {
speechParams.clear();
speechParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM,
String.valueOf(AudioManager.STREAM_NOTIFICATION));
engine.speak(text, TextToSpeech.QUEUE_ADD, speechParams);
//Bug with API level < 8: The original sound isn't restored automatically.
//So we do it by speaking nothing.
speechParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_MUSIC));
this.engine.speak("", mode, speechParams);
}
}
Hope that helps.
Related
I've been working on a navigation feature for a maps app which has voice instructions. The problem is that when announcing the instructions, the first 500 milliseconds of the instruction gets cut off. For eg, if the instruction is "In 200m turn right", in the bluetooth earphone it ends up sounding like "200m turn right". Or if the instruction is "Continue for 2 kilometers", then it sounds like "tinue for 2 kilometers".
This is the code I'm using for TTS -
//Initialisation happens only once
var textToSpeechEngine = TextToSpeech(this) { status ->
if (status == TextToSpeech.SUCCESS) {
textToSpeechEngine?.language = Locale.ENGLISH
textToSpeechEngine?.setSpeechRate(0.8f)
}
}
...
//When text to speak is ready, invoking the speak method
textToSpeechEngine?.speak(textToSpeak, TextToSpeech.QUEUE_FLUSH, null, "tts1")
Additionally, I'm also using AudioFocusRequest to request and abandon focus so that any music playing in the background lowers its volume while the instruction is being spoken. This is the code I'm using for that -
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
var focusRequest: AudioFocusRequest? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build())
.setAcceptsDelayedFocusGain(false)
.build()
audioManager.requestAudioFocus(focusRequest)
} else {
audioManager.requestAudioFocus(null, AudioManager.STREAM_NOTIFICATION, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
}
textToSpeechEngine?.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
fun abandonFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
focusRequest?.let { request -> audioManager.abandonAudioFocusRequest(request) }
} else {
audioManager.abandonAudioFocus(null)
}
}
override fun onStart(utteranceId: String?) {}
override fun onDone(utteranceId: String?) {
abandonFocus()
}
override fun onError(utteranceId: String?) {
abandonFocus()
}
})
Point to note here is that this issue is not happening in all the bluetooth devices I've tested with. The issue happened with 2 Chinese brands bluetooth earphones but is not happening with a bluetooth Sony over-the-head headset. Also, the issue does not happen if music is playing in the background while navigation is going on.
As I understand it, it seems that the cheaper bluetooth earphones seem to keep the "connection alive" only when audio is actively coming through else it stops the connection temporarily in order to save battery I guess? However, when music is playing in the background, the connection is kept alive constantly so the instruction speech does not get cut off.
What can I do to fix this or work around it?
note that requestAudioFocus may take a OnAudioFocusChangeListener as first param, you are passing null in there (also Builder have this param). switching focus may take some (short) time, so I would recomend to fire your TTS when you get this callback fired with AUDIOFOCUS_GAIN
Assuming that you're correct that it is purely hardware at fault (which is what it seems like):
You can use the playSilence() or playSilentUtterance() methods of the TextToSpeech class to play silence for 500ms prior to your main speak() command... which should fool the speakers/headphones.
It may help to also use QUEUE_ADD instead of QUEUE_FLUSH for your main speak() request to make sure that it it attached the the previous (silence) with no gap... and that it doesn't prematurely end the first (silent) utterance.
There are a lot of variables at play here. You could consider:
Can you find an app that uses TextToSpeech which DOES work correctly even on these problematic devices? If so, then the problem must be solvable in code, and maybe you could find the source code for the app you tested and look at how they are setting up the AudioManager.
The code for handling media buttons from headsets that I use in my Text-to-Speech app works great under Android API 22 through 25 (in older versions of Android they are handled by other, now depreciated means). However under Android 8 "Oreo", both public beta and final release, it does not work. Here is the relevant code:
When the service starts, I create MediaSessionCompact object:
mSession = new MediaSessionCompat(getApplicationContext(), "my.package.name._player_session");
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setActive(true);
mSession.setCallback(myMediaSessionCallback);
PlaybackStateCompat state = new PlaybackStateCompat.Builder()
.setActions(ACTION_PLAY_PAUSE | ACTION_PLAY | ACTION_PAUSE |
ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS |
ACTION_FAST_FORWARD | ACTION_REWIND
)
.setState(PlaybackStateCompat.STATE_PAUSED, 0 /*PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN*/, 1f)
.build();
mSession.setPlaybackState(state);
There is of course session media callback defined:
private MediaSessionCompat.Callback myMediaSessionCallback = new MediaSessionCompat.Callback() {
#Override
public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
// The log output below never appears on "Oreo", nothing comes here.
Log.d(TAG, "callback onMediaButtonEvent() Compat");
MediaButtonIntentReceiver.handleIntent(mediaButtonIntent.getAction(), (KeyEvent) mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT));
return true;
}
#Override
public void onSkipToNext() {
//...
}
// etc. other overrides
};
I also experimented with PendingIntent, using MediaButtonReceiver.buildMediaButtonPendingIntent() and set mSession.setMediaButtonReceiver(pendingIntent) for all the actions I'm interested in, then in my service onStartCommand() I call MediaButtonReceiver.handleIntent(mSession, intent):
// still in the same service:
mSession.setMediaButtonReceiver(
MediaButtonReceiver.buildMediaButtonPendingIntent(
this,
mMediaButtonReceiverComponentName,
ACTION_PLAY));
mSession.setMediaButtonReceiver(
MediaButtonReceiver.buildMediaButtonPendingIntent(
this,
mMediaButtonReceiverComponentName,
ACTION_PAUSE));
mSession.setMediaButtonReceiver(
MediaButtonReceiver.buildMediaButtonPendingIntent(
this,
mMediaButtonReceiverComponentName,
ACTION_PLAY_PAUSE));
and in the service onStartCommand():
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// ...
if (intent != null) {
MediaButtonReceiver.handleIntent(mSession, intent);
// ...
}
return START_NOT_STICKY;
}
Nothing, it's completely dumb to media buttons press events. What's wrong with "O" or my code there??? I'm completely baffled.
Update 8/32/2017
I also created a trivial but working app project that demonstrates the problem, please see: https://github.com/gregko/PlayerServiceSample. This project displays LogCat output when a media button is pressed on a headset under Android 5.x to 7.x, but fails completely under Android 8 "Oreo".
Update 9/1/2017
There is now an open issue on Android Issue Tracker about this, which I submitted, at https://issuetracker.google.com/issues/65175978. Still the media buttons work in several music player apps I tested on Oreo, I just can't figure out what do they do differently to make them work... The context of my app is not playing music, but reading aloud text with Text to Speech service, so a lot of code from Music Player examples does not apply.
Solved. On "Android 8.0 Behavior Changes" Google page we find this text:
In Android 8.0 (API level 26) the handling of media button events is different:
The handling of media buttons in a UI activity has not changed: foreground activities still get priority in handling media button events.
If the foreground activity does not handle the media button event, the system routes the event to the app that most recently played audio locally. The active status, flags, and playback state of a media session are not considered when determining which app receives media button events.
If the app's media session has been released, the system sends the media button event to the app's MediaButtonReceiver if it has one.
For every other case, the system discards the media button event.
All I had to do to make my trivial sample work was to play some sound with MediaPlayer. Apparently playing sound with Text-to-Speech API does not qualify, which in my opinion is a bug.
Here is the code I added to my trivial sample to make it work, playing a very brief and silent WAV file from Raw resources directory:
final MediaPlayer mMediaPlayer;
mMediaPlayer = MediaPlayer.create(this, R.raw.silent_sound);
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
mMediaPlayer.release();
}
});
mMediaPlayer.start();
Update
Submitted the bug report to Android issue tracker at https://issuetracker.google.com/issues/65344811
Update 2, Oct. 10, 2017
Google now says that Oreo behavior in this respect is "by design" and won't fix it. Read the reply near the end of the issue tracker post above. I must say I'm disappointed.
I'm developing a game. I need a button to turn on and off game sounds.
In my app, I play background music which I want to be muted upon clicking a button. Here's my code:
AudioManager aManager = (AudioManager)getSystemService(AUDIO_SERVICE);
if (aManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL) {
aManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
} else {
aManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
}
When I debug, the conditions are right but nothing happens! The sound is never turned off!
Check for method "isVolumeFixed()". If true, your device can't have it's sound changed. Some devices have a policy about that.
I'm developing a spam call filtering app and I'm trying to silence ringer for incoming (spam) call. The problem is none of AudioManager.setStreamMute nor AudioManager.setRingerMode is working in Lollipop. Here is my code snippet:
public class CallReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
if (stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
AudioManager audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
audioManager.setStreamMute(AudioManager.STREAM_RING, false);
Log.i(TAG, "unmute");
} else if (stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
AudioManager audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
audioManager.setStreamMute(AudioManager.STREAM_RING, true);
Log.i(TAG, "mute");
}
}
When there's an incoming call, the mute part always gets executed but it sometimes succeeds and sometimes fails to mute the ringer. I can't find any rule. And audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT) doesn't work either. This seems work fine when tested on emulators < 5, so I guess it's somehow related to Lollipop not having silent mode but interruptions filter. Commercial spam call filters are woking fine, so can somebody let me know how I could silence incoming calls with Lollipop?
I've got the same issue for android 5. AudioManager.setStreamMute with the value false to unmute is never working.
I tried AudioManager.setStreamSolo and it worked for me.
//to mute ringtone
Global.app().getAudioManager().setStreamSolo(AudioManager.STREAM_MUSIC, true);
//unmute ringtone
Global.app().getAudioManager().setStreamSolo(AudioManager.STREAM_MUSIC, false);
It mutes all other streams except one you want to play. In may case I needed to play my own audio instead of ringtone.
But you can try to play a silence audio to hack if you need absolute silence.
I have an Android application that makes use of TTS (Text to speech) API. Everything is working perfectly, but now i want to fade in/out or even stop music (in case user is playing music with prebuilt Music Player), when application speaks a text. Right now, i think both music and TTS messages are played on the same stream (MUSIC), and it can be difficult to understand the voice messages.
I've tried to play the text on a different stream, like AudioManager.STREAM_NOTIFICATIONS. It does stop the music, but doesn't come back when the text is spoken, so i didn't achieve the goal. Haven't found anything yet, so i hope someone can help here. Thanks!
I finally got something that is working. Not perfect though. A quite dirty trick. Just in case it can help to someone:
This is fixed on API 8 with requestAudioFocus and abandomAudioFocus methods of AudioManager.
But for former versions, you can try this. Play TTS through a different stream channel, let's say STREAM_NOTIFICATIONS. Then you just need to return audio focus to STREAM_MUSIC. How can you achieve that?. Sending a silence string (" ") to TTS but this time through STREAM_MUSIC. The effect will be: music is stopped, your TTS message gets spoken, and finally your music is back after the voice alert. Not nice or something to feel proud of, but... if someone knows of a different way, i will appreciate it
Here is a way of doing this in Dec-2021
TexToSpeech needs to be initialized and assigned to tts before calling this method
Method 1 (Recommended):
private void speak(String textToSay) {
AudioAttributes mPlaybackAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANT)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
//add this below flag if you need the TTS to speak in a louder volume or TTS volume be heard for sure at any cost
//.setFlags(FLAG_AUDIBILITY_ENFORCED)
.build();
tts.setAudioAttributes(mPlaybackAttributes);
AudioFocusRequest mFocusRequest =
new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(mPlaybackAttributes)
.setAcceptsDelayedFocusGain(false)
.setWillPauseWhenDucked(false)
.build();
AudioManager am = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
am.requestAudioFocus(mFocusRequest);
tts.speak(textToSay, TextToSpeech.QUEUE_FLUSH, null, textToSay);
Handler ttsSpeak = new Handler();
Runnable checkTTSRunning = new Runnable() {
#Override
public void run() {
if (tts.isSpeaking()) {
ttsSpeak.postDelayed(this, 1000);
} else am.abandonAudioFocusRequest(mFocusRequest);
}
};
ttsSpeak.postDelayed(checkTTSRunning, 3000);
}
Method 2: Use this only if you need the TTS to speak in a louder volume and/or TTS volume needs to be heard for sure at any cost
private void speak(String textToSay) {
AudioAttributes mPlaybackAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANT)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.setFlags(FLAG_AUDIBILITY_ENFORCED) //VERY IMPORTANT
.build();
tts.setAudioAttributes(mPlaybackAttributes);
tts.speak(textToSay, TextToSpeech.QUEUE_FLUSH, null, textToSay);
}
Could you use the TextToSpeech.OnUtteranceCompletedListener along with AudioManager.setStreamVolume to achieve this?