-
Notifications
You must be signed in to change notification settings - Fork 319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix voiceLanguage NPE and add tests for NavigationSpeechPlayer #1054
Conversation
1441d10
to
6045f5e
Compare
6045f5e
to
9fb9d47
Compare
9fb9d47
to
536abf8
Compare
@Guardiola31337 @devotaaabel this is ready for 👀 |
|
||
/** | ||
* Creates an instance of {@link AndroidSpeechPlayer}. | ||
* | ||
* @param context used to create an instance of {@link TextToSpeech} | ||
* @param context used to create an instance of {@link TextToSpeech} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
@@ -267,7 +276,11 @@ public void userOffRoute(Location location) { | |||
@Override | |||
public void onMilestoneEvent(RouteProgress routeProgress, String instruction, Milestone milestone) { | |||
if (milestone instanceof VoiceInstructionMilestone) { | |||
instructionPlayer.play((VoiceInstructionMilestone) milestone); | |||
SpeechAnnouncement speechAnnouncement = SpeechAnnouncement.builder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if you pass in VoiceInstructionMilestone
into a constructor in SpeechAnnouncement
so this logic can be in SpeechAnnouncement
? Instead of using the builder pattern
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was thinking here in terms of usability outside of the turn-by-turn UI. NavigationSpeechPlayer
is a public
API, so tying it completely to the VoiceInstructionMilestone
in order to voice something may not make a lot of sense to someone that doesn't want to use the milestones to voice something.
I did update the builder though to also take a VoiceInstructionMilestone
, does that work? Any reasons you don't like the builder pattern here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense! And yea having a builder method that takes in a VoiceInstructionMilestone
for convenience is 👍
*/ | ||
@Override | ||
public void play(String instruction) { | ||
public void play(SpeechAnnouncement speechAnnouncement) { | ||
if (speechAnnouncement == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add this in the boolean canPlay
. i.e., boolean canPlay = languageSupported && !isMuted && !TextUtils.isEmpty(instruction) && speechAnnouncement != null;
|
||
/** | ||
* Construct an instance of {@link MapboxSpeechPlayer} | ||
* | ||
* @param context to setup the caches | ||
* @param language for which language | ||
* @param context to setup the caches |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should be consistent with javadoc param spacing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of the javadoc spacing was moved to align to the length of the longest @param
, in this case accessToken
:
* @param context to setup the caches
* @param language for which language
* @param accessToken a valid Mapbox access token
This is how I had always done it, but if we'd like to agree on a set style here I'm happy to conform / adjust cc @Guardiola31337
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s correct. But it’s true that we don’t have a checkstyle rule that enforces it. So if you reformat (as I do constantly 😅 ⌥⌘L) it gets formatted as @danesfeder mentioned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok with sticking with that. We should add this to checkstyle, or at least going forward start to update non-conforming javadoc as we run into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add this to checkstyle, or at least going forward start to update non-conforming javadoc as we run into it.
Yeah as I mentioned we don't have one and it'd be awesome to add it. I briefly looked into it and it's a tricky one though (more difficult than I initially thought). I'll keep digging.
} | ||
|
||
/** | ||
* Plays the specified text instruction using MapboxSpeech API | ||
* | ||
* @param instruction voice instruction to be synthesized and played | ||
* @param textType either "ssml" or "text" | ||
* @param textType either "ssml" or "text" |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
@@ -243,12 +253,12 @@ private void startNextInstruction() { | |||
|
|||
private void clearInstructionUrls() { | |||
while (!instructionQueue.isEmpty()) { | |||
instructionQueue.remove().delete(); | |||
instructionQueue.poll(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change? you still need to delete
the actual files, and you shouldn't have to worry about exceptions because of the while(!instructionQueue.isEmpty())
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The poll()
Javadoc states Retrieves and removes the head of this queue, or returns {@code null} if this queue is empty.
Does that not actually delete the file? I changed it back in case I was missing context here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope, it removes the File object from the queue and we lose track of it in memory but remember the audio file is still in the file system.
*/ | ||
public void setMuted(boolean isMuted) { | ||
this.isMuted = isMuted; | ||
retrieveSpeechPlayer().setMuted(isMuted); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so this is only going to set the currently retrieved speech player muted, not both if both are active
* @since 0.16.0 | ||
*/ | ||
public void onOffRoute() { | ||
retrieveSpeechPlayer().onOffRoute(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
* @since 0.16.0 | ||
*/ | ||
public void onDestroy() { | ||
retrieveSpeechPlayer().onDestroy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
@Override | ||
public void onError(String errorText, SpeechAnnouncement speechAnnouncement) { | ||
Timber.e(errorText); | ||
speechPlayerProvider.retrieveAndroidSpeechPlayer().play(speechAnnouncement); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this going to go into an infinite loop if AndroidSpeechPlayer
returns an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently not firing error listener for AndroidSpeechPlayer
https://github.com/mapbox/mapbox-navigation-android/blob/master/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/voice/UtteranceListener.java#L31
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, should we maybe print an error there? For the case where there's an error initializing native TTS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we are logging this currently in master
https://github.com/mapbox/mapbox-navigation-android/blob/master/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/voice/AndroidSpeechPlayer.java#L44
Does sticking with that work okay?
00ac03b
to
4736e74
Compare
@devotaaabel updated and addressed some of your comments - let me know if anything is unclear |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some minor things/questions - overall this looks great.
private void initVoiceInstructions(NavigationViewOptions options) { | ||
boolean languageSupported = options.directionsRoute().voiceLanguage() != null; | ||
instructionPlayer = new NavigationInstructionPlayer(getApplication(), language, languageSupported, accessToken); | ||
boolean voiceLanguageSupported = options.directionsRoute().voiceLanguage() != null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor thing - Per consistency with other boolean
s in the code base, what about calling it isVoiceLanguageSupported
instead?
|
||
/** | ||
* Construct an instance of {@link MapboxSpeechPlayer} | ||
* | ||
* @param context to setup the caches | ||
* @param language for which language | ||
* @param context to setup the caches |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s correct. But it’s true that we don’t have a checkstyle rule that enforces it. So if you reformat (as I do constantly 😅 ⌥⌘L) it gets formatted as @danesfeder mentioned.
private static final String MP3_POSTFIX = ".mp3"; | ||
private static final int END_OF_FILE_DENOTER = -1; | ||
private static int instructionNamingInt = 1; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT - unnecessary empty line
} | ||
|
||
void requestAudioFocus() { | ||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public void play(SpeechAnnouncement speechAnnouncement) { | ||
this.announcement = speechAnnouncement; | ||
String ssmlAnnouncement = announcement.ssmlAnnouncememnt(); | ||
if (ssmlAnnouncement != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we refactor this if else
somehow? Both branches are calling play
with different parameters.
|
||
void onStart(); | ||
|
||
void onDone(); | ||
|
||
void onError(boolean isMapboxPlayer, String errorText); | ||
void onError(String errorText, SpeechAnnouncement speechAnnouncement); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question - Could we refactor the code so we don't need to pass the announcement in the error callback?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's stick with the current setup here - this is an internal listener. No excuse, though, I know. We can look into re-wiring in the future.
2ff2383
to
be96983
Compare
* @since 0.16.0 | ||
*/ | ||
@Nullable | ||
public abstract String ssmlAnnouncememnt(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spelling
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After fixing @devotaaabel's commented typo, let's
Great job @danesfeder 🙇
be96983
to
99d5a66
Compare
99d5a66
to
e5b5583
Compare
Closes #1052 Closes #848 Closes #1060
Refactor speech classes to be more testable and add tests to core classes used. I aligned the naming "instruction" vs. "speech" because we were using both. Also created an immutable
AutoValue
classSpeechAnnouncement
to create from theVoiceInstructionMilestone
TODO tests for:
NavigationSpeechPlayer
NavigationSpeechListener
SpeechPlayerProvider