diff --git a/CHANGELOG.md b/CHANGELOG.md index e39a3de5..15c014c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changes to the Mapbox Navigation SDK for iOS ## Unreleased + - The `speak` method in `RouteVoiceController` can be used without a given `RouteProgress` or the `RouteProgress` can explicitly ignored so that it will not be added to the voice instruction. + - `RouteProgress` is now optional in `willSpeak` method of `VoiceControllerDelegate` if the `RouteProgress` in the `speak` method of the `RouteVoiceController is `nil`. - Uses the `Locale` given in `RouteOptions` to create the corresponding `AVSpeechSynthesisVoice`. - Removed setCamera() from updateCourseTracking() - Added setCamera() to progressDidChange() diff --git a/Examples/Swift/ViewController.swift b/Examples/Swift/ViewController.swift index 317ddd37..e34199e9 100644 --- a/Examples/Swift/ViewController.swift +++ b/Examples/Swift/ViewController.swift @@ -360,7 +360,7 @@ extension ViewController: VoiceControllerDelegate { print(interruptedInstruction.text, interruptingInstruction.text) } - func voiceController(_ voiceController: RouteVoiceController, willSpeak instruction: SpokenInstruction, routeProgress: RouteProgress) -> SpokenInstruction? { + func voiceController(_ voiceController: RouteVoiceController, willSpeak instruction: SpokenInstruction, routeProgress: RouteProgress?) -> SpokenInstruction? { return SpokenInstruction(distanceAlongStep: instruction.distanceAlongStep, text: "New Instruction!", ssmlText: "New Instruction!") } diff --git a/MapboxNavigation/RouteVoiceController.swift b/MapboxNavigation/RouteVoiceController.swift index 8ca3cc61..53e168e0 100644 --- a/MapboxNavigation/RouteVoiceController.swift +++ b/MapboxNavigation/RouteVoiceController.swift @@ -192,13 +192,9 @@ open class RouteVoiceController: NSObject, AVSpeechSynthesizerDelegate { - parameter instruction: The instruction to read aloud. - parameter locale: The `Locale` used to create the voice read aloud the given instruction. If `nil` the `Locale.preferredLocalLanguageCountryCode` is used for creating the voice. + - parameter ignoreProgress: A `Bool` that indicates if the routeProgress is added to the instruction. */ - open func speak(_ instruction: SpokenInstruction, with locale: Locale?) { - guard let routeProgress else { - assertionFailure("routeProgress should not be nil.") - return - } - + open func speak(_ instruction: SpokenInstruction, with locale: Locale?, ignoreProgress: Bool = false) { if speechSynth.isSpeaking, let lastSpokenInstruction = lastSpokenInstruction { voiceControllerDelegate?.voiceController?(self, didInterrupt: lastSpokenInstruction, with: instruction) } @@ -209,29 +205,26 @@ open class RouteVoiceController: NSObject, AVSpeechSynthesizerDelegate { voiceControllerDelegate?.voiceController?(self, spokenInstructionsDidFailWith: error) } - var utterance: AVSpeechUtterance? + let modifiedInstruction = voiceControllerDelegate?.voiceController?(self, willSpeak: instruction, routeProgress: routeProgress) ?? instruction + + let utterance: AVSpeechUtterance + if locale?.identifier == "en-US" { // Alex can’t handle attributed text. - utterance = AVSpeechUtterance(string: instruction.text) - utterance!.voice = AVSpeechSynthesisVoice(identifier: AVSpeechSynthesisVoiceIdentifierAlex) - } - - let modifiedInstruction = voiceControllerDelegate?.voiceController?(self, willSpeak: instruction, routeProgress: routeProgress) ?? instruction - - if #available(iOS 10.0, *), utterance?.voice == nil { - utterance = AVSpeechUtterance(attributedString: modifiedInstruction.attributedText(for: routeProgress.currentLegProgress)) - } else { utterance = AVSpeechUtterance(string: modifiedInstruction.text) + utterance.voice = AVSpeechSynthesisVoice(identifier: AVSpeechSynthesisVoiceIdentifierAlex) + } else { + if #available(iOS 10.0, *), !ignoreProgress, let routeProgress { + utterance = AVSpeechUtterance(attributedString: modifiedInstruction.attributedText(for: routeProgress.currentLegProgress)) + } else { + utterance = AVSpeechUtterance(string: modifiedInstruction.text) + } + + // Only localized languages will have a proper fallback voice + utterance.voice = AVSpeechSynthesisVoice(language: locale?.identifier ?? Locale.preferredLocalLanguageCountryCode) } - // Only localized languages will have a proper fallback voice - if utterance?.voice == nil { - utterance?.voice = AVSpeechSynthesisVoice(language: locale?.identifier ?? Locale.preferredLocalLanguageCountryCode) - } - - if let utterance = utterance { - speechSynth.speak(utterance) - } + speechSynth.speak(utterance) } } @@ -264,8 +257,8 @@ public protocol VoiceControllerDelegate { - parameter voiceController: The voice controller that will speak an instruction. - parameter instruction: The spoken instruction that will be said. - - parameter routeProgress: The `RouteProgress` just before when the instruction is scheduled to be spoken. + - parameter routeProgress: The `RouteProgress` just before when the instruction is scheduled to be spoken. Could be `nil` if no progress is available or if it should be ignored. **/ @objc(voiceController:willSpeakSpokenInstruction:routeProgress:) - optional func voiceController(_ voiceController: RouteVoiceController, willSpeak instruction: SpokenInstruction, routeProgress: RouteProgress) -> SpokenInstruction? + optional func voiceController(_ voiceController: RouteVoiceController, willSpeak instruction: SpokenInstruction, routeProgress: RouteProgress?) -> SpokenInstruction? } diff --git a/MapboxNavigationTests/NavigationViewControllerTests.swift b/MapboxNavigationTests/NavigationViewControllerTests.swift index 482857d4..71bf4925 100644 --- a/MapboxNavigationTests/NavigationViewControllerTests.swift +++ b/MapboxNavigationTests/NavigationViewControllerTests.swift @@ -286,7 +286,7 @@ class TestableDayStyle: DayStyle { class FakeVoiceController: RouteVoiceController { - override func speak(_ instruction: SpokenInstruction, with locale: Locale?) { + override func speak(_ instruction: SpokenInstruction, with locale: Locale?, ignoreProgress: Bool = false) { //no-op }