Changes to use audio focus for TTS engine.

This commit is contained in:
chgenly 2012-09-10 22:30:22 -07:00
parent efff2c2d49
commit 59bea7f741
3 changed files with 102 additions and 52 deletions

View file

@ -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);
}
}
} }

View file

@ -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);
}
}
} }

View file

@ -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+"");
} }