diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml
index 367bcab4bc..6ef6f2f9ca 100644
--- a/OsmAnd/res/values/strings.xml
+++ b/OsmAnd/res/values/strings.xml
@@ -2913,4 +2913,6 @@
Searching for the corresponding wiki article
Article not found
How to open Wikipedia articles?
+ Use JS voice guidance
+ Use new voice guidance logic based on JavaScript
diff --git a/OsmAnd/src/net/osmand/plus/AppInitializer.java b/OsmAnd/src/net/osmand/plus/AppInitializer.java
index 02b129ced4..dd4dd2d1d8 100644
--- a/OsmAnd/src/net/osmand/plus/AppInitializer.java
+++ b/OsmAnd/src/net/osmand/plus/AppInitializer.java
@@ -46,6 +46,7 @@ import net.osmand.plus.search.QuickSearchHelper;
import net.osmand.plus.views.corenative.NativeCoreContext;
import net.osmand.plus.voice.CommandPlayer;
import net.osmand.plus.voice.CommandPlayerException;
+import net.osmand.plus.voice.JSTTSCommandPlayerImpl;
import net.osmand.plus.voice.MediaCommandPlayerImpl;
import net.osmand.plus.voice.TTSCommandPlayerImpl;
import net.osmand.plus.wikivoyage.data.TravelDbHelper;
@@ -577,7 +578,9 @@ public class AppInitializer implements IProgress {
if (!voiceDir.exists()) {
throw new CommandPlayerException(ctx.getString(R.string.voice_data_unavailable));
}
-
+ if (app.getSettings().USE_JS_VOICE_GUIDANCE.get()) {
+ return new JSTTSCommandPlayerImpl(osmandApplication, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider);
+ }
if (MediaCommandPlayerImpl.isMyData(voiceDir)) {
return new MediaCommandPlayerImpl(osmandApplication, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider);
} else if (TTSCommandPlayerImpl.isMyData(voiceDir)) {
diff --git a/OsmAnd/src/net/osmand/plus/OsmandSettings.java b/OsmAnd/src/net/osmand/plus/OsmandSettings.java
index a83a591968..949cdaf490 100644
--- a/OsmAnd/src/net/osmand/plus/OsmandSettings.java
+++ b/OsmAnd/src/net/osmand/plus/OsmandSettings.java
@@ -1340,6 +1340,8 @@ public class OsmandSettings {
public final OsmandPreference ANIMATE_MY_LOCATION = new BooleanPreference("animate_my_location", true).makeGlobal().cache();
+ public final OsmandPreference USE_JS_VOICE_GUIDANCE = new BooleanPreference("use_js_voice_guidance", false);
+
public final OsmandPreference ROUTE_MAP_MARKERS_START_MY_LOC = new BooleanPreference("route_map_markers_start_my_loc", false).makeGlobal().cache();
public final OsmandPreference ROUTE_MAP_MARKERS_ROUND_TRIP = new BooleanPreference("route_map_markers_round_trip", false).makeGlobal().cache();
diff --git a/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java b/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java
index bece3ea19a..be1ae15e49 100644
--- a/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java
+++ b/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java
@@ -63,6 +63,9 @@ public class SettingsDevelopmentActivity extends SettingsBaseActivity {
R.string.animate_my_location,
R.string.animate_my_location_desc));
+ cat.addPreference(createCheckBoxPreference(settings.USE_JS_VOICE_GUIDANCE, getString(R.string.use_js_voice_guidance),
+ getString(R.string.use_js_voice_guidance_description)));
+
final Preference firstRunPreference = new Preference(this);
firstRunPreference.setTitle(R.string.simulate_initial_startup);
firstRunPreference.setSummary(R.string.simulate_initial_startup_descr);
diff --git a/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java b/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java
index af27e575ea..cab1e395ff 100644
--- a/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java
+++ b/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java
@@ -69,6 +69,8 @@ public class VoiceRouter {
private static RouteDirectionInfo nextRouteDirection;
private Term empty;
+ private boolean useJS;
+
public interface VoiceMessageListener {
void onVoiceMessage();
}
@@ -78,6 +80,7 @@ public class VoiceRouter {
public VoiceRouter(RoutingHelper router, final OsmandSettings settings) {
this.router = router;
this.settings = settings;
+ useJS = settings.USE_JS_VOICE_GUIDANCE.get();
this.mute = settings.VOICE_MUTE.get();
empty = new Struct("");
voiceMessageListeners = new ConcurrentHashMap();
diff --git a/OsmAnd/src/net/osmand/plus/voice/AbstractJSCommandPlayer.java b/OsmAnd/src/net/osmand/plus/voice/AbstractJSCommandPlayer.java
new file mode 100644
index 0000000000..09db3cee5b
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/voice/AbstractJSCommandPlayer.java
@@ -0,0 +1,54 @@
+package net.osmand.plus.voice;
+
+import java.util.List;
+
+import alice.tuprolog.Struct;
+
+public class AbstractJSCommandPlayer implements CommandPlayer {
+ @Override
+ public String getCurrentVoice() {
+ return null;
+ }
+
+ @Override
+ public CommandBuilder newCommandBuilder() {
+ JSCommandBuilder commandBuilder = new JSCommandBuilder(this);
+ commandBuilder.setParameters("km-m", true);
+ return commandBuilder;
+ }
+
+ @Override
+ public void playCommands(CommandBuilder builder) {
+
+ }
+
+ @Override
+ public void clear() {
+
+ }
+
+ @Override
+ public List execute(List listStruct) {
+ return null;
+ }
+
+ @Override
+ public void updateAudioStream(int streamType) {
+
+ }
+
+ @Override
+ public String getLanguage() {
+ return null;
+ }
+
+ @Override
+ public boolean supportsStructuredStreetNames() {
+ return true;
+ }
+
+ @Override
+ public void stop() {
+
+ }
+}
diff --git a/OsmAnd/src/net/osmand/plus/voice/CommandBuilder.java b/OsmAnd/src/net/osmand/plus/voice/CommandBuilder.java
index 6e17ac0543..01640b1c34 100644
--- a/OsmAnd/src/net/osmand/plus/voice/CommandBuilder.java
+++ b/OsmAnd/src/net/osmand/plus/voice/CommandBuilder.java
@@ -53,8 +53,8 @@ public class CommandBuilder {
/**
*
*/
- private final CommandPlayer commandPlayer;
- private boolean alreadyExecuted = false;
+ protected final CommandPlayer commandPlayer;
+ protected boolean alreadyExecuted = false;
private List listStruct = new ArrayList();
public CommandBuilder(CommandPlayer commandPlayer){
diff --git a/OsmAnd/src/net/osmand/plus/voice/JSCommandBuilder.java b/OsmAnd/src/net/osmand/plus/voice/JSCommandBuilder.java
new file mode 100644
index 0000000000..e8bffb4912
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/voice/JSCommandBuilder.java
@@ -0,0 +1,220 @@
+package net.osmand.plus.voice;
+
+import net.osmand.PlatformUtil;
+
+import org.apache.commons.logging.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class JSCommandBuilder extends CommandBuilder {
+
+ private static final Log log = PlatformUtil.getLog(JSCommandBuilder.class);
+
+ protected static final String C_PREPARE_TURN = "prepare_turn"; //$NON-NLS-1$
+ protected static final String C_PREPARE_ROUNDABOUT = "prepare_roundabout"; //$NON-NLS-1$
+ protected static final String C_PREPARE_MAKE_UT = "prepare_make_ut"; //$NON-NLS-1$
+ protected static final String C_ROUNDABOUT = "roundabout"; //$NON-NLS-1$
+ protected static final String C_GO_AHEAD = "go_ahead"; //$NON-NLS-1$
+ protected static final String C_TURN = "turn"; //$NON-NLS-1$
+ protected static final String C_MAKE_UT = "make_ut"; //$NON-NLS-1$
+ protected static final String C_MAKE_UTWP = "make_ut_wp"; //$NON-NLS-1$
+ protected static final String C_AND_ARRIVE_DESTINATION = "and_arrive_destination"; //$NON-NLS-1$
+ protected static final String C_REACHED_DESTINATION = "reached_destination"; //$NON-NLS-1$
+ protected static final String C_AND_ARRIVE_INTERMEDIATE = "and_arrive_intermediate"; //$NON-NLS-1$
+ protected static final String C_REACHED_INTERMEDIATE = "reached_intermediate"; //$NON-NLS-1$
+ protected static final String C_AND_ARRIVE_WAYPOINT = "and_arrive_waypoint"; //$NON-NLS-1$
+ protected static final String C_AND_ARRIVE_FAVORITE = "and_arrive_favorite"; //$NON-NLS-1$
+ protected static final String C_AND_ARRIVE_POI_WAYPOINT = "and_arrive_poi"; //$NON-NLS-1$
+ protected static final String C_REACHED_WAYPOINT = "reached_waypoint"; //$NON-NLS-1$
+ protected static final String C_REACHED_FAVORITE = "reached_favorite"; //$NON-NLS-1$
+ protected static final String C_REACHED_POI = "reached_poi"; //$NON-NLS-1$
+ protected static final String C_THEN = "then"; //$NON-NLS-1$
+ protected static final String C_SPEAD_ALARM = "speed_alarm"; //$NON-NLS-1$
+ protected static final String C_ATTENTION = "attention"; //$NON-NLS-1$
+ protected static final String C_OFF_ROUTE = "off_route"; //$NON-NLS-1$
+ protected static final String C_BACK_ON_ROUTE ="back_on_route"; //$NON-NLS-1$
+
+
+ protected static final String C_BEAR_LEFT = "bear_left"; //$NON-NLS-1$
+ protected static final String C_BEAR_RIGHT = "bear_right"; //$NON-NLS-1$
+ protected static final String C_ROUTE_RECALC = "route_recalc"; //$NON-NLS-1$
+ protected static final String C_ROUTE_NEW_CALC = "route_new_calc"; //$NON-NLS-1$
+ protected static final String C_LOCATION_LOST = "location_lost"; //$NON-NLS-1$
+ protected static final String C_LOCATION_RECOVERED = "location_recovered"; //$NON-NLS-1$
+
+
+ private List listStruct = new ArrayList<>();
+
+ public JSCommandBuilder(CommandPlayer commandPlayer) {
+ super(commandPlayer);
+ }
+
+
+ public void setParameters(String metricCons, boolean tts) {
+ // TODO Set the parameters to js context
+ }
+
+ private JSCommandBuilder addCommand(String name, Object... args){
+ // TODO add JSCore
+ listStruct.add(name);
+ return this;
+ }
+
+ public JSCommandBuilder goAhead(){
+ return goAhead(-1, new HashMap());
+ }
+
+ public JSCommandBuilder goAhead(double dist, Map streetName){
+ return addCommand(C_GO_AHEAD, dist, streetName);
+ }
+
+ public JSCommandBuilder makeUTwp(){
+ return makeUT(new HashMap());
+ }
+
+ public JSCommandBuilder makeUT(Map streetName){
+ return addCommand(C_MAKE_UT, streetName);
+ }
+ @Override
+ public JSCommandBuilder speedAlarm(int maxSpeed, float speed){
+ return addCommand(C_SPEAD_ALARM, maxSpeed, speed);
+ }
+ @Override
+ public JSCommandBuilder attention(String type){
+ return addCommand(C_ATTENTION, type);
+ }
+ @Override
+ public JSCommandBuilder offRoute(double dist){
+ return addCommand(C_OFF_ROUTE, dist);
+ }
+ @Override
+ public CommandBuilder backOnRoute(){
+ return addCommand(C_BACK_ON_ROUTE);
+ }
+
+ public JSCommandBuilder makeUT(double dist, Map streetName){
+ return addCommand(C_MAKE_UT, dist, streetName);
+ }
+
+ public JSCommandBuilder prepareMakeUT(double dist, Map streetName){
+ return addCommand(C_PREPARE_MAKE_UT, dist, streetName);
+ }
+
+
+ public JSCommandBuilder turn(String param, Map streetName) {
+ return addCommand(C_TURN, param, streetName);
+ }
+
+ public JSCommandBuilder turn(String param, double dist, Map streetName){
+ return addCommand(C_TURN, param, dist, streetName);
+ }
+
+ /**
+ *
+ * @param param A_LEFT, A_RIGHT, ...
+ * @param dist
+ * @return
+ */
+ public JSCommandBuilder prepareTurn(String param, double dist, Map streetName){
+ return addCommand(C_PREPARE_TURN, param, dist, streetName);
+ }
+
+ public JSCommandBuilder prepareRoundAbout(double dist, int exit, Map streetName){
+ return addCommand(C_PREPARE_ROUNDABOUT, dist, exit, streetName);
+ }
+
+ public JSCommandBuilder roundAbout(double dist, double angle, int exit, Map streetName){
+ return addCommand(C_ROUNDABOUT, dist, angle, exit, streetName);
+ }
+
+ public JSCommandBuilder roundAbout(double angle, int exit, Map streetName) {
+ return roundAbout(-1, angle, exit, streetName);
+ }
+ @Override
+ public JSCommandBuilder andArriveAtDestination(String name){
+ return addCommand(C_AND_ARRIVE_DESTINATION, name);
+ }
+ @Override
+ public JSCommandBuilder arrivedAtDestination(String name){
+ return addCommand(C_REACHED_DESTINATION, name);
+ }
+ @Override
+ public JSCommandBuilder andArriveAtIntermediatePoint(String name){
+ return addCommand(C_AND_ARRIVE_INTERMEDIATE, name);
+ }
+ @Override
+ public JSCommandBuilder arrivedAtIntermediatePoint(String name) {
+ return addCommand(C_REACHED_INTERMEDIATE, name);
+ }
+ @Override
+ public JSCommandBuilder andArriveAtWayPoint(String name){
+ return addCommand(C_AND_ARRIVE_WAYPOINT, name);
+ }
+ @Override
+ public JSCommandBuilder arrivedAtWayPoint(String name) {
+ return addCommand(C_REACHED_WAYPOINT, name);
+ }
+ @Override
+ public JSCommandBuilder andArriveAtFavorite(String name) {
+ return addCommand(C_AND_ARRIVE_FAVORITE, name);
+ }
+ @Override
+ public JSCommandBuilder arrivedAtFavorite(String name) {
+ return addCommand(C_REACHED_FAVORITE, name);
+ }
+ @Override
+ public JSCommandBuilder andArriveAtPoi(String name) {
+ return addCommand(C_AND_ARRIVE_POI_WAYPOINT, name);
+ }
+ @Override
+ public JSCommandBuilder arrivedAtPoi(String name) {
+ return addCommand(C_REACHED_POI, name);
+ }
+
+ public JSCommandBuilder bearLeft(Map streetName){
+ return addCommand(C_BEAR_LEFT, streetName);
+ }
+
+ public JSCommandBuilder bearRight(Map streetName){
+ return addCommand(C_BEAR_RIGHT, streetName);
+ }
+
+ @Override
+ public JSCommandBuilder then(){
+ return addCommand(C_THEN);
+ }
+
+ @Override
+ public JSCommandBuilder gpsLocationLost() {
+ return addCommand(C_LOCATION_LOST);
+ }
+
+ @Override
+ public JSCommandBuilder gpsLocationRecover() {
+ return addCommand(C_LOCATION_RECOVERED);
+ }
+
+ @Override
+ public JSCommandBuilder newRouteCalculated(double dist, int time){
+ return addCommand(C_ROUTE_NEW_CALC, dist, time);
+ }
+
+ @Override
+ public JSCommandBuilder routeRecalculated(double dist, int time){
+ return addCommand(C_ROUTE_RECALC, dist, time);
+ }
+
+ @Override
+ public void play(){
+ this.commandPlayer.playCommands(this);
+ }
+
+ @Override
+ protected List execute(){
+ alreadyExecuted = true;
+ return listStruct;
+ }
+}
diff --git a/OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java b/OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java
new file mode 100644
index 0000000000..4caefad1f7
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java
@@ -0,0 +1,106 @@
+package net.osmand.plus.voice;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.speech.tts.TextToSpeech;
+import android.support.v7.app.AlertDialog;
+import android.util.Log;
+
+import net.osmand.plus.ApplicationMode;
+import net.osmand.plus.OsmandApplication;
+import net.osmand.plus.R;
+import net.osmand.plus.routing.VoiceRouter;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+public class JSTTSCommandPlayerImpl extends AbstractJSCommandPlayer {
+
+ private static final String TAG = JSTTSCommandPlayerImpl.class.getSimpleName();
+ private static TextToSpeech mTts;
+
+ private OsmandApplication app;
+ private ApplicationMode appMode;
+ private VoiceRouter vrt;
+ private String voiceProvider;
+
+ private HashMap params = new HashMap();
+
+ private static int ttsRequests = 0;
+
+ public JSTTSCommandPlayerImpl(OsmandApplication ctx, ApplicationMode applicationMode, VoiceRouter vrt, String voiceProvider) {
+ this.app = ctx;
+ this.appMode = applicationMode;
+ this.vrt = vrt;
+ this.voiceProvider = voiceProvider;
+ mTts = new TextToSpeech(ctx, null);
+ }
+
+ @Override
+ public String getCurrentVoice() {
+ return null;
+ }
+
+ @Override
+ public JSCommandBuilder newCommandBuilder() {
+ JSCommandBuilder commandBuilder = new JSCommandBuilder(this);
+ commandBuilder.setParameters(app.getSettings().METRIC_SYSTEM.get().toHumanString(app), true);
+ return commandBuilder;
+ }
+
+ @Override
+ public void playCommands(CommandBuilder builder) {
+ final List execute = builder.execute(); //list of strings, the speech text, play it
+ StringBuilder bld = new StringBuilder();
+ for (String s : execute) {
+ bld.append(s).append(' ');
+ }
+ if (mTts != null && !vrt.isMute()) {
+ if (ttsRequests++ == 0) {
+ // Delay first prompt of each batch to allow BT SCO connection being established
+ if (app.getSettings().AUDIO_STREAM_GUIDANCE.getModeValue(appMode) == 0) {
+ ttsRequests++;
+ if (android.os.Build.VERSION.SDK_INT < 21) {
+ params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,""+System.currentTimeMillis());
+ mTts.playSilence(app.getSettings().BT_SCO_DELAY.get(), TextToSpeech.QUEUE_ADD, params);
+ } else {
+ mTts.playSilentUtterance(app.getSettings().BT_SCO_DELAY.get(), TextToSpeech.QUEUE_ADD, ""+System.currentTimeMillis());
+ }
+ }
+ }
+ Log.d(TAG, "ttsRequests= "+ttsRequests);
+ params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,""+System.currentTimeMillis());
+ mTts.speak(bld.toString(), TextToSpeech.QUEUE_ADD, params);
+ // Audio focus will be released when onUtteranceCompleted() completed is called by the TTS engine.
+ } else if (app != null && vrt.isMute()) {
+ // sendAlertToAndroidWear(ctx, bld.toString());
+ }
+ }
+
+ @Override
+ public void clear() {
+
+ }
+
+ @Override
+ public void updateAudioStream(int streamType) {
+
+ }
+
+ @Override
+ public String getLanguage() {
+ return null;
+ }
+
+ @Override
+ public boolean supportsStructuredStreetNames() {
+ return false;
+ }
+
+ @Override
+ public void stop() {
+
+ }
+}