Skip to content
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

Merged
merged 1 commit into from
Jun 29, 2018

Conversation

danesfeder
Copy link
Contributor

@danesfeder danesfeder commented Jun 22, 2018

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 class SpeechAnnouncement to create from the VoiceInstructionMilestone

TODO tests for:

  • NavigationSpeechPlayer
  • NavigationSpeechListener
  • SpeechPlayerProvider

@danesfeder danesfeder added bug Defect to be fixed. navigation-ui labels Jun 22, 2018
@danesfeder danesfeder self-assigned this Jun 22, 2018
@danesfeder danesfeder force-pushed the dan-null-instruction-player branch 2 times, most recently from 1441d10 to 6045f5e Compare June 22, 2018 16:08
@danesfeder danesfeder added this to the 0.16.0 milestone Jun 25, 2018
@danesfeder
Copy link
Contributor Author

@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.

@@ -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()
Copy link
Contributor

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

Copy link
Contributor Author

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?

Copy link
Contributor

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) {
Copy link
Contributor

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
Copy link
Contributor

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

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

@@ -243,12 +253,12 @@ private void startNextInstruction() {

private void clearInstructionUrls() {
while (!instructionQueue.isEmpty()) {
instructionQueue.remove().delete();
instructionQueue.poll();
Copy link
Contributor

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())

Copy link
Contributor Author

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.

Copy link
Contributor

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);
Copy link
Contributor

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();
Copy link
Contributor

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();
Copy link
Contributor

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);
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danesfeder danesfeder force-pushed the dan-null-instruction-player branch 2 times, most recently from 00ac03b to 4736e74 Compare June 26, 2018 13:58
@danesfeder
Copy link
Contributor Author

@devotaaabel updated and addressed some of your comments - let me know if anything is unclear

Copy link
Contributor

@Guardiola31337 Guardiola31337 left a 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;
Copy link
Contributor

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 booleans 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
Copy link
Contributor

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;

Copy link
Contributor

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we remove/abstract these kind of checks somehow? I'm thinking on implementing like a mix of Factory and Strategy patterns.

public void play(SpeechAnnouncement speechAnnouncement) {
this.announcement = speechAnnouncement;
String ssmlAnnouncement = announcement.ssmlAnnouncememnt();
if (ssmlAnnouncement != null) {
Copy link
Contributor

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@danesfeder danesfeder force-pushed the dan-null-instruction-player branch 2 times, most recently from 2ff2383 to be96983 Compare June 28, 2018 19:29
* @since 0.16.0
*/
@Nullable
public abstract String ssmlAnnouncememnt();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spelling

Copy link
Contributor

@Guardiola31337 Guardiola31337 left a 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 :shipit:

Great job @danesfeder 🙇

@danesfeder danesfeder merged commit 6e8fc57 into master Jun 29, 2018
@danesfeder danesfeder deleted the dan-null-instruction-player branch June 29, 2018 18:49
@danesfeder danesfeder mentioned this pull request Jul 20, 2018
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Defect to be fixed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants