Changes to use audio focus for TTS engine.
This commit is contained in:
parent
efff2c2d49
commit
59bea7f741
3 changed files with 102 additions and 52 deletions
|
@ -28,12 +28,13 @@ import alice.tuprolog.Struct;
|
||||||
import alice.tuprolog.Term;
|
import alice.tuprolog.Term;
|
||||||
import alice.tuprolog.Theory;
|
import alice.tuprolog.Theory;
|
||||||
import alice.tuprolog.Var;
|
import alice.tuprolog.Var;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
|
||||||
public abstract class AbstractPrologCommandPlayer implements CommandPlayer {
|
public abstract class AbstractPrologCommandPlayer implements CommandPlayer {
|
||||||
|
|
||||||
private static final Log log = LogUtil
|
private static final Log log = LogUtil.getLog(AbstractPrologCommandPlayer.class);
|
||||||
.getLog(AbstractPrologCommandPlayer.class);
|
|
||||||
|
|
||||||
protected Context ctx;
|
protected Context ctx;
|
||||||
protected File voiceDir;
|
protected File voiceDir;
|
||||||
|
@ -51,10 +52,14 @@ public abstract class AbstractPrologCommandPlayer implements CommandPlayer {
|
||||||
protected static final String DELAY_CONST = "delay_";
|
protected static final String DELAY_CONST = "delay_";
|
||||||
/** Must be sorted array! */
|
/** Must be sorted array! */
|
||||||
private final int[] sortedVoiceVersions;
|
private final int[] sortedVoiceVersions;
|
||||||
|
private AudioFocusHelper mAudioFocusHelper;
|
||||||
|
|
||||||
|
private int streamType;
|
||||||
|
|
||||||
protected AbstractPrologCommandPlayer(Context ctx, OsmandSettings settings, String voiceProvider, String configFile, int[] sortedVoiceVersions)
|
protected AbstractPrologCommandPlayer(Context ctx, OsmandSettings settings, String voiceProvider, String configFile, int[] sortedVoiceVersions)
|
||||||
throws CommandPlayerException
|
throws CommandPlayerException
|
||||||
{
|
{
|
||||||
|
this.ctx = ctx;
|
||||||
this.sortedVoiceVersions = sortedVoiceVersions;
|
this.sortedVoiceVersions = sortedVoiceVersions;
|
||||||
long time = System.currentTimeMillis();
|
long time = System.currentTimeMillis();
|
||||||
try {
|
try {
|
||||||
|
@ -67,6 +72,7 @@ public abstract class AbstractPrologCommandPlayer implements CommandPlayer {
|
||||||
if (log.isInfoEnabled()) {
|
if (log.isInfoEnabled()) {
|
||||||
log.info("Initializing prolog system : " + (System.currentTimeMillis() - time)); //$NON-NLS-1$
|
log.info("Initializing prolog system : " + (System.currentTimeMillis() - time)); //$NON-NLS-1$
|
||||||
}
|
}
|
||||||
|
this.streamType = settings.AUDIO_STREAM_GUIDANCE.get();
|
||||||
init(voiceProvider, settings, configFile);
|
init(voiceProvider, settings, configFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,4 +195,59 @@ public abstract class AbstractPrologCommandPlayer implements CommandPlayer {
|
||||||
prologSystem = null;
|
prologSystem = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateAudioStream(int streamType) {
|
||||||
|
this.streamType = streamType;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void requestAudioFocus() {
|
||||||
|
log.debug("requestAudioFocus");
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= 8) {
|
||||||
|
mAudioFocusHelper = new AudioFocusHelper(ctx);
|
||||||
|
}
|
||||||
|
if (mAudioFocusHelper != null)
|
||||||
|
mAudioFocusHelper.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void abandonAudioFocus() {
|
||||||
|
log.debug("abandonAudioFocus");
|
||||||
|
if (mAudioFocusHelper != null) {
|
||||||
|
mAudioFocusHelper.abandonFocus();
|
||||||
|
mAudioFocusHelper = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This helper class allows API level 8 calls to be isolated from the rest of the app.
|
||||||
|
* This class is only be instantiated on OS versions which support it.
|
||||||
|
* @author genly
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
// We Use API level 8 calls here, suppress warnings.
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
|
||||||
|
private Context mContext;
|
||||||
|
private AudioManager mAudioManager;
|
||||||
|
|
||||||
|
public AudioFocusHelper(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean requestFocus() {
|
||||||
|
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
|
||||||
|
mAudioManager.requestAudioFocus(this, streamType, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean abandonFocus() {
|
||||||
|
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.abandonAudioFocus(this);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onAudioFocusChange(int focusChange) {
|
||||||
|
// Basically we ignore audio focus changes. There's not much we can do when we have interrupted audio
|
||||||
|
// for our speech, and we in turn get interrupted. Ignore it until a scenario comes up which gives us
|
||||||
|
// reason to change this strategy.
|
||||||
|
log.error("MediaCommandPlayerImpl.onAudioFocusChange(): Unexpected audio focus change: "+focusChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,10 @@ import net.osmand.plus.OsmandSettings;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.media.MediaPlayer;
|
import android.media.MediaPlayer;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* That class represents command player.
|
* That class represents command player.
|
||||||
* It gets commands from input, analyze what files should be played and play
|
* It gets commands from input, analyze what files should be played and play
|
||||||
|
@ -33,21 +32,12 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen
|
||||||
// indicates that player is ready to play first file
|
// indicates that player is ready to play first file
|
||||||
private List<String> filesToPlay = Collections.synchronizedList(new ArrayList<String>());
|
private List<String> filesToPlay = Collections.synchronizedList(new ArrayList<String>());
|
||||||
private int streamType;
|
private int streamType;
|
||||||
private final Context mCtx;
|
|
||||||
private AudioFocusHelper mAudioFocusHelper;
|
|
||||||
|
|
||||||
|
|
||||||
public MediaCommandPlayerImpl(Context ctx, OsmandSettings settings, String voiceProvider)
|
public MediaCommandPlayerImpl(Context ctx, OsmandSettings settings, String voiceProvider)
|
||||||
throws CommandPlayerException
|
throws CommandPlayerException
|
||||||
{
|
{
|
||||||
super(ctx, settings, voiceProvider, CONFIG_FILE, MEDIA_VOICE_VERSION);
|
super(ctx, settings, voiceProvider, CONFIG_FILE, MEDIA_VOICE_VERSION);
|
||||||
mCtx = ctx;
|
|
||||||
this.streamType = settings.AUDIO_STREAM_GUIDANCE.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateAudioStream(int streamType) {
|
|
||||||
this.streamType = streamType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -62,13 +52,7 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen
|
||||||
|
|
||||||
// If we have not already started to play audio, start.
|
// If we have not already started to play audio, start.
|
||||||
if (mediaPlayer == null) {
|
if (mediaPlayer == null) {
|
||||||
if (android.os.Build.VERSION.SDK_INT >= 8) {
|
requestAudioFocus();
|
||||||
mAudioFocusHelper = new AudioFocusHelper(mCtx);
|
|
||||||
} else {
|
|
||||||
mAudioFocusHelper = null;
|
|
||||||
}
|
|
||||||
if (mAudioFocusHelper != null)
|
|
||||||
mAudioFocusHelper.requestFocus();
|
|
||||||
playQueue();
|
playQueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,14 +73,17 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen
|
||||||
mediaPlayer.release();
|
mediaPlayer.release();
|
||||||
mediaPlayer = null;
|
mediaPlayer = null;
|
||||||
|
|
||||||
if (mAudioFocusHelper != null)
|
abandonAudioFocus();
|
||||||
mAudioFocusHelper.abandonFocus();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the MediaPlayer is done.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCompletion(MediaPlayer mp) {
|
public void onCompletion(MediaPlayer mp) {
|
||||||
|
// Work on the next file to play.
|
||||||
playQueue();
|
playQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +118,11 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the MediaPlayer playing a file. This method will return immediately.
|
||||||
|
* OnCompletionListener() will be called when the MediaPlayer is done.
|
||||||
|
* @param file
|
||||||
|
*/
|
||||||
private void playFile(File file) {
|
private void playFile(File file) {
|
||||||
log.debug("Playing file : " + file); //$NON-NLS-1$
|
log.debug("Playing file : " + file); //$NON-NLS-1$
|
||||||
try {
|
try {
|
||||||
|
@ -150,34 +142,4 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen
|
||||||
return new File(voiceDir, CONFIG_FILE).exists();
|
return new File(voiceDir, CONFIG_FILE).exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This helper class allows API level 8 calls to be isolated from the rest of the app.
|
|
||||||
* This class is only be instantiated on OS versions which support it.
|
|
||||||
* @author genly
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
// We Use API level 8 calls here, suppress warnings.
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
|
|
||||||
private Context mContext;
|
|
||||||
private AudioManager mAudioManager;
|
|
||||||
|
|
||||||
public AudioFocusHelper(Context context) {
|
|
||||||
mContext = context;
|
|
||||||
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean requestFocus() {
|
|
||||||
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
|
|
||||||
mAudioManager.requestAudioFocus(this, streamType, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean abandonFocus() {
|
|
||||||
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.abandonAudioFocus(this);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onAudioFocusChange(int focusChange) {
|
|
||||||
log.error("MediaCommandPlayerImpl.onAudioFocusChange(): Unexpected audio focus change: "+focusChange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,10 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
|
||||||
import net.osmand.Algoritms;
|
import net.osmand.Algoritms;
|
||||||
|
import net.osmand.LogUtil;
|
||||||
import net.osmand.plus.OsmandApplication;
|
import net.osmand.plus.OsmandApplication;
|
||||||
import net.osmand.plus.OsmandSettings;
|
import net.osmand.plus.OsmandSettings;
|
||||||
import net.osmand.plus.R;
|
import net.osmand.plus.R;
|
||||||
|
@ -21,6 +24,7 @@ import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.speech.tts.TextToSpeech;
|
import android.speech.tts.TextToSpeech;
|
||||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
import android.speech.tts.TextToSpeech.OnInitListener;
|
||||||
|
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
|
||||||
|
|
||||||
public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer {
|
public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer {
|
||||||
|
|
||||||
|
@ -53,6 +57,7 @@ public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer {
|
||||||
|
|
||||||
private static final String CONFIG_FILE = "_ttsconfig.p";
|
private static final String CONFIG_FILE = "_ttsconfig.p";
|
||||||
private static final int[] TTS_VOICE_VERSION = new int[] { 100, 101 }; // !! MUST BE SORTED
|
private static final int[] TTS_VOICE_VERSION = new int[] { 100, 101 }; // !! MUST BE SORTED
|
||||||
|
private static final Log log = LogUtil.getLog(TTSCommandPlayerImpl.class);
|
||||||
private TextToSpeech mTts;
|
private TextToSpeech mTts;
|
||||||
private Context mTtsContext;
|
private Context mTtsContext;
|
||||||
private String language;
|
private String language;
|
||||||
|
@ -76,6 +81,15 @@ public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since TTS requests are asynchronous, playCommands() can be called before
|
||||||
|
* the TTS engine is done. We use this field to keep track of concurrent tts
|
||||||
|
* activity. Where tts activity is defined as the time between tts.speak()
|
||||||
|
* and the call back to onUtteranceCompletedListener(). This allows us to
|
||||||
|
* optimize use of requesting and abandoning audio focus.
|
||||||
|
*/
|
||||||
|
private int ttsRequests;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void playCommands(CommandBuilder builder) {
|
public void playCommands(CommandBuilder builder) {
|
||||||
if (mTts != null) {
|
if (mTts != null) {
|
||||||
|
@ -84,7 +98,11 @@ public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer {
|
||||||
for (String s : execute) {
|
for (String s : execute) {
|
||||||
bld.append(s).append(' ');
|
bld.append(s).append(' ');
|
||||||
}
|
}
|
||||||
|
if (ttsRequests++ == 0)
|
||||||
|
requestAudioFocus();
|
||||||
|
log.debug("ttsRequests="+ttsRequests);
|
||||||
mTts.speak(bld.toString(), TextToSpeech.QUEUE_ADD, params);
|
mTts.speak(bld.toString(), TextToSpeech.QUEUE_ADD, params);
|
||||||
|
// Audio focus will be released when onUtteranceCompleted() completed is called by the TTS engine.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,6 +159,14 @@ public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer {
|
||||||
return ctx instanceof SettingsActivity;
|
return ctx instanceof SettingsActivity;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
mTts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener() {
|
||||||
|
@Override
|
||||||
|
public void onUtteranceCompleted(String utteranceId) {
|
||||||
|
if (--ttsRequests == 0)
|
||||||
|
abandonAudioFocus();
|
||||||
|
log.debug("ttsRequests="+ttsRequests);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,6 +201,7 @@ public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateAudioStream(int streamType) {
|
public void updateAudioStream(int streamType) {
|
||||||
|
super.updateAudioStream(streamType);
|
||||||
params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, streamType+"");
|
params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, streamType+"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue