mapbox / mapbox-navigation-ios

Turn-by-turn navigation logic and UI in Swift on iOS
https://docs.mapbox.com/ios/navigation/
Other
860 stars 310 forks source link

Speech synthesizer locale is overwritten by route locale at every spoken instruction point #3799

Open 1ec5 opened 2 years ago

1ec5 commented 2 years ago

The speech synthesizer’s locale is overwritten by the route’s locale at every spoken instruction point, preventing the application from hard-coding a particular locale for selecting the text-to-speech voice.

Background

Normally, the navigation SDK is designed to speak spoken instructions in the same locale that is indicated in the route data. After all, if the guidance instructions are phrased as appropriate for French, they should be spoken by a French voice, not an American English one. 🙉 We want to do the right thing by default, without the developer needing to remember to set both locale settings.

However, the application should have the opportunity to hard-code the voice locale, for example, Hong Kong English instructions spoken by a British English voice. This is normally quite a contrived scenario, but a practical need for this mismatch can arise due to missing language support in either the Directions API or the Voice API.

Problem

Currently, developers get the impression that they can simply set SpeechSynthesizing.locale to the desired voice locale, such as in the process of configurating NavigationOptions. Unfortunately, RouteVoiceController.didPassSpokenInstructionPoint(notification:) overwrites this locale with the requested Directions API locale immediately before prefetching or saying an instruction. The prefetching and speaking is based on the resolved locale in the route response.

https://github.com/mapbox/mapbox-navigation-ios/blob/1b1ba5aaea85cb61b63ff65356e5d32324584a39/Sources/MapboxNavigation/RouteVoiceController.swift#L156-L157

This issue even affects any custom speech synthesizer that the application provides per #3747.

Proposed solution

We should move this locale-setting code so that it executes less frequently, like once at the beginning of the route, since we don’t support switching languages in the middle of a route anyways. We should only set overwrite the locale if it’s nil or equivalent to Locale.autoupdatingCurrent.

/ref #1649 #2348 /cc @mapbox/navigation-ios @Guardiola31337 @browndp08 @jinny-nam @dgearhart (FYI)

1ec5 commented 2 years ago

Ideally, the Directions API and Voice API would fully support the locale that the application wants to use. In the unfortunate event that some language support is missing, a partial client-side workaround may be possible as a last resort.

For an application based on a fork of the navigation SDK, the simplest workaround would be to remove the offending lines or hard-code them to use the desired locale.

If forking is not an option, an alternative workaround would be to shadow the voice locale when making requests to the Voice API:

import MapboxSpeech

/// Speaks in the Received Pronunciation of Commonwealth English regardless of what was requested.
class CommonwealthSpeechSynthesizer: SpeechSynthesizer {
    override func audioData(with options: SpeechOptions, completionHandler: @escaping SpeechSynthesizer.CompletionHandler) -> URLSessionDataTask {
        options.locale = Locale(identifier: "en-GB")
        return super.audioData(with: options, completionHandler: completionHandler)
    }
}

let commonwealthSpeechSynthesizer = CommonwealthSpeechSynthesizer(accessToken: nil)
let mapboxSpeechSynthesizer = MapboxSpeechSynthesizer(remoteSpeechSynthesizer: commonwealthSpeechSynthesizer)
let muxedSpeechSynthesizer = MultiplexedSpeechSynthesizer([mapboxSpeechSynthesizer, SystemSpeechSynthesizer()], accessToken: nil, host: nil)
let routeVoiceController = RouteVoiceController(navigationService: navigationService, speechSynthesizer: muxedSpeechSynthesizer, accessToken: nil, host: nil)
let navigationOptions = NavigationOptions(navigationService: navigationService, voiceController: routeVoiceController, …)

This workaround requires the changes in #3747. It’s especially involved because MapboxSpeech doesn’t expose a hook for custom URL query parameters: mapbox/mapbox-speech-swift#50. It also would not cover offline speech by VoiceOver, because SystemSpeechSynthesizer.locale is merely public, not open. (That might be worth addressing at the same time.)

mcrollin commented 2 years ago

I too am experiencing the issue. Mid-navigation, the instructions switch from German to English.

1ec5 commented 2 years ago

To be clear, the issue described here is only about the voice (i.e., the accent), not the instruction (the wording).

mcrollin commented 2 years ago

Well, we have users experiencing random language changes from an instruction to another (ie: German to English and back). I thought that it might be linked to this issue. If it isn't I'm happy to file new one.