Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
35bec870ee
3 changed files with 183 additions and 70 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,13 @@ import org.apache.commons.logging.Log;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
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
|
||||||
* them using media player
|
* them using media player
|
||||||
*/
|
*/
|
||||||
public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer {
|
public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implements MediaPlayer.OnCompletionListener {
|
||||||
|
|
||||||
private static final String CONFIG_FILE = "_config.p";
|
private static final String CONFIG_FILE = "_config.p";
|
||||||
private static final int[] MEDIA_VOICE_VERSION = new int[] { 0 }; // MUST BE SORTED, list of supported versions
|
private static final int[] MEDIA_VOICE_VERSION = new int[] { 0 }; // MUST BE SORTED, list of supported versions
|
||||||
|
@ -29,7 +30,6 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer {
|
||||||
// playing media
|
// playing media
|
||||||
private MediaPlayer mediaPlayer;
|
private MediaPlayer mediaPlayer;
|
||||||
// indicates that player is ready to play first file
|
// indicates that player is ready to play first file
|
||||||
private volatile boolean playNext = true;
|
|
||||||
private List<String> filesToPlay = Collections.synchronizedList(new ArrayList<String>());
|
private List<String> filesToPlay = Collections.synchronizedList(new ArrayList<String>());
|
||||||
private int streamType;
|
private int streamType;
|
||||||
|
|
||||||
|
@ -38,14 +38,6 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer {
|
||||||
throws CommandPlayerException
|
throws CommandPlayerException
|
||||||
{
|
{
|
||||||
super(ctx, settings, voiceProvider, CONFIG_FILE, MEDIA_VOICE_VERSION);
|
super(ctx, settings, voiceProvider, CONFIG_FILE, MEDIA_VOICE_VERSION);
|
||||||
mediaPlayer = new MediaPlayer();
|
|
||||||
this.streamType = settings.AUDIO_STREAM_GUIDANCE.get();
|
|
||||||
mediaPlayer.setAudioStreamType(streamType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateAudioStream(int streamType) {
|
|
||||||
this.streamType = streamType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -54,74 +46,104 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer {
|
||||||
mediaPlayer = null;
|
mediaPlayer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called from the calculating route thread.
|
||||||
@Override
|
@Override
|
||||||
public void playCommands(CommandBuilder builder){
|
public synchronized void playCommands(CommandBuilder builder) {
|
||||||
filesToPlay.addAll(builder.execute());
|
filesToPlay.addAll(builder.execute());
|
||||||
playQueue();
|
|
||||||
|
// If we have not already started to play audio, start.
|
||||||
|
if (mediaPlayer == null) {
|
||||||
|
requestAudioFocus();
|
||||||
|
playQueue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void playQueue() {
|
private synchronized void playQueue() {
|
||||||
while (!filesToPlay.isEmpty() && playNext) {
|
if (mediaPlayer == null) {
|
||||||
|
mediaPlayer = new MediaPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
performDelays();
|
||||||
|
File file = getNextFileToPlay();
|
||||||
|
if (file != null) {
|
||||||
|
playFile(file);
|
||||||
|
// Will continue with onCompletion()
|
||||||
|
} else {
|
||||||
|
// Release the media player only when we are done speaking.
|
||||||
|
if (mediaPlayer != null) {
|
||||||
|
mediaPlayer.release();
|
||||||
|
mediaPlayer = null;
|
||||||
|
|
||||||
|
abandonAudioFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the MediaPlayer is done. The call back is on the main thread.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onCompletion(MediaPlayer mp) {
|
||||||
|
// Work on the next file to play.
|
||||||
|
playQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performDelays() {
|
||||||
|
int sleep = 0;
|
||||||
|
while (!filesToPlay.isEmpty() && filesToPlay.get(0).startsWith(DELAY_CONST)) {
|
||||||
|
String s = filesToPlay.remove(0).substring(DELAY_CONST.length());
|
||||||
|
try {
|
||||||
|
sleep += Integer.parseInt(s);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (sleep != 0) {
|
||||||
|
log.debug("Delaying "+sleep);
|
||||||
|
Thread.sleep(sleep);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getNextFileToPlay() {
|
||||||
|
while (!filesToPlay.isEmpty()) {
|
||||||
String f = filesToPlay.remove(0);
|
String f = filesToPlay.remove(0);
|
||||||
if (f != null && voiceDir != null) {
|
if (f != null && voiceDir != null) {
|
||||||
boolean exists = false;
|
File file = new File(voiceDir, f);
|
||||||
// if(voiceZipFile != null){
|
return file;
|
||||||
// ZipEntry entry = voiceZipFile.getEntry(f);
|
|
||||||
// exists = entry != null;
|
|
||||||
// voiceZipFile.getInputStream(entry);
|
|
||||||
//
|
|
||||||
// } else {
|
|
||||||
File file = new File(voiceDir, f);
|
|
||||||
exists = file.exists();
|
|
||||||
// }
|
|
||||||
if (exists) {
|
|
||||||
log.debug("Playing file : " + f); //$NON-NLS-1$
|
|
||||||
playNext = false;
|
|
||||||
try {
|
|
||||||
// Can't play sound file from zip it seams to be impossible only unpack and play!!!
|
|
||||||
mediaPlayer.setDataSource(file.getAbsolutePath());
|
|
||||||
mediaPlayer.prepare();
|
|
||||||
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
|
||||||
@Override
|
|
||||||
public void onCompletion(MediaPlayer mp) {
|
|
||||||
mp.release();
|
|
||||||
mediaPlayer = new MediaPlayer();
|
|
||||||
mediaPlayer.setAudioStreamType(streamType);
|
|
||||||
int sleep = 60;
|
|
||||||
boolean delay = true;
|
|
||||||
while (!filesToPlay.isEmpty() && delay) {
|
|
||||||
delay = filesToPlay.get(0).startsWith(DELAY_CONST);
|
|
||||||
if (delay) {
|
|
||||||
String s = filesToPlay.remove(0).substring(DELAY_CONST.length());
|
|
||||||
try {
|
|
||||||
sleep += Integer.parseInt(s);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Thread.sleep(sleep);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
}
|
|
||||||
playNext = true;
|
|
||||||
playQueue();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.start();
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Error while playing voice command", e); //$NON-NLS-1$
|
|
||||||
playNext = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.info("Play file not found : " + f); //$NON-NLS-1$
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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) {
|
||||||
|
if (!file.exists()) {
|
||||||
|
log.error("Unable to play, does not exist: "+file);
|
||||||
|
playQueue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
log.debug("Playing file : " + file); //$NON-NLS-1$
|
||||||
|
mediaPlayer.reset();
|
||||||
|
mediaPlayer.setAudioStreamType(streamType);
|
||||||
|
mediaPlayer.setDataSource(file.getAbsolutePath());
|
||||||
|
mediaPlayer.prepare();
|
||||||
|
mediaPlayer.setOnCompletionListener(this);
|
||||||
|
mediaPlayer.start();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error while playing voice command", e); //$NON-NLS-1$
|
||||||
|
playQueue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isMyData(File voiceDir) {
|
public static boolean isMyData(File voiceDir) {
|
||||||
return new File(voiceDir, CONFIG_FILE).exists();
|
return new File(voiceDir, CONFIG_FILE).exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,15 +81,30 @@ 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;
|
||||||
|
|
||||||
|
// Called from the calculating route thread.
|
||||||
@Override
|
@Override
|
||||||
public void playCommands(CommandBuilder builder) {
|
public synchronized void playCommands(CommandBuilder builder) {
|
||||||
if (mTts != null) {
|
if (mTts != null) {
|
||||||
final List<String> execute = builder.execute(); //list of strings, the speech text, play it
|
final List<String> execute = builder.execute(); //list of strings, the speech text, play it
|
||||||
StringBuilder bld = new StringBuilder();
|
StringBuilder bld = new StringBuilder();
|
||||||
for (String s : execute) {
|
for (String s : execute) {
|
||||||
bld.append(s).append(' ');
|
bld.append(s).append(' ');
|
||||||
}
|
}
|
||||||
|
if (ttsRequests++ == 0)
|
||||||
|
requestAudioFocus();
|
||||||
|
log.debug("ttsRequests="+ttsRequests);
|
||||||
|
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,""+System.currentTimeMillis());
|
||||||
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 +161,15 @@ public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer {
|
||||||
return ctx instanceof SettingsActivity;
|
return ctx instanceof SettingsActivity;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
mTts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener() {
|
||||||
|
// The call back is on a binder thread.
|
||||||
|
@Override
|
||||||
|
public synchronized void onUtteranceCompleted(String utteranceId) {
|
||||||
|
if (--ttsRequests == 0)
|
||||||
|
abandonAudioFocus();
|
||||||
|
log.debug("ttsRequests="+ttsRequests);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,6 +204,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