diff --git a/CHANGELOG.md b/CHANGELOG.md index ef09289e63d..a8d4a3af428 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ * MapboxMobileEvents dependency is replaced with CoreTelemetry (part of MapboxCommon). ([#4011](https://github.com/mapbox/mapbox-navigation-ios/pull/4011)) * Deprecated `NavigationEventsManager.init(activeNavigationDataSource:passiveNavigationDataSource:accessToken:mobileEventsManager:)` in favour of `NavigationEventsManager.init(activeNavigationDataSource:passiveNavigationDataSource:accessToken:)`. ([#4011](https://github.com/mapbox/mapbox-navigation-ios/pull/4011)) * Added `NavigationViewController.usesNightStyleInDarkMode` property to control whether night style is used in dark mode. ([#4143](https://github.com/mapbox/mapbox-navigation-ios/pull/4143)) +* Added `SpeechSynthesizing.forceSpeak(instruction:during: locale:)` to break through mute of speech synthesizer. ([#4195](https://github.com/mapbox/mapbox-navigation-ios/pull/4195)) ## v2.8.1 diff --git a/Sources/MapboxNavigation/MultiplexedSpeechSynthesizer.swift b/Sources/MapboxNavigation/MultiplexedSpeechSynthesizer.swift index 26b4bdc0435..1794d32e54c 100644 --- a/Sources/MapboxNavigation/MultiplexedSpeechSynthesizer.swift +++ b/Sources/MapboxNavigation/MultiplexedSpeechSynthesizer.swift @@ -108,6 +108,11 @@ open class MultiplexedSpeechSynthesizer: SpeechSynthesizing { currentLegProgress = legProgress speechSynthesizers.first?.speak(instruction, during: legProgress, locale: locale) } + + public func forceSpeak(_ instruction: SpokenInstruction, during legProgress: RouteLegProgress, locale: Locale? = nil) { + currentLegProgress = legProgress + speechSynthesizers.first?.forceSpeak(instruction, during: legProgress, locale: locale) + } public func stopSpeaking() { speechSynthesizers.forEach { $0.stopSpeaking() } diff --git a/Sources/MapboxNavigation/SpeechSynthesizing.swift b/Sources/MapboxNavigation/SpeechSynthesizing.swift index 5b884460f5c..be0084c2ba8 100644 --- a/Sources/MapboxNavigation/SpeechSynthesizing.swift +++ b/Sources/MapboxNavigation/SpeechSynthesizing.swift @@ -5,7 +5,7 @@ import MapboxCoreNavigation /** Protocol for implementing speech synthesizer to be used in `RouteVoiceController`. */ -public protocol SpeechSynthesizing: AnyObject { +public protocol SpeechSynthesizing: AnyObject, UnimplementedLogging { /// A delegate that will be notified about significant events related to spoken instructions. var delegate: SpeechSynthesizingDelegate? { get set } @@ -38,6 +38,14 @@ public protocol SpeechSynthesizing: AnyObject { /// /// This method is not guaranteed to be synchronous or asynchronous. When vocalizing is finished, `voiceController(_ voiceController: SpeechSynthesizing, didSpeak instruction: SpokenInstruction, with error: SpeechError?)` should be called. func speak(_ instruction: SpokenInstruction, during legProgress: RouteLegProgress, locale: Locale?) + + /// A request to vocalize the instruction, breaking through the mute of the speech synthesizer if muted + /// - parameter instruction: an instruction to be vocalized + /// - parameter legProgress: current leg progress, corresponding to the instruction + /// - parameter locale: A locale to be used for vocalizing the instruction. If `nil` is passed - `SpeechSynthesizing.locale` will be used as 'default'. + /// + /// This method is not guaranteed to be synchronous or asynchronous. When vocalizing is finished, `voiceController(_ voiceController: SpeechSynthesizing, didSpeak instruction: SpokenInstruction, with error: SpeechError?)` should be called. + func forceSpeak(_ instruction: SpokenInstruction, during legProgress: RouteLegProgress, locale: Locale?) /// Tells synthesizer to stop current vocalization in a graceful manner func stopSpeaking() @@ -45,6 +53,17 @@ public protocol SpeechSynthesizing: AnyObject { func interruptSpeaking() } +extension SpeechSynthesizing { + + /** + `UnimplementedLogging` prints a warning to standard output the first time this method is called. + */ + public func forceSpeak(_ instruction: SpokenInstruction, during legProgress: RouteLegProgress, locale: Locale?) { + logUnimplemented(protocolType: SpeechSynthesizing.self, level: .debug) + speak(instruction, during: legProgress, locale: locale) + } +} + /** The `SpeechSynthesizingDelegate` protocol defines methods that allow an object to respond to significant events related to spoken instructions. */ diff --git a/Sources/MapboxNavigation/SystemSpeechSynthesizer.swift b/Sources/MapboxNavigation/SystemSpeechSynthesizer.swift index 42c17524d24..43a884b3ce8 100644 --- a/Sources/MapboxNavigation/SystemSpeechSynthesizer.swift +++ b/Sources/MapboxNavigation/SystemSpeechSynthesizer.swift @@ -64,7 +64,10 @@ open class SystemSpeechSynthesizer: NSObject, SpeechSynthesizing { with: nil) return } - + forceSpeak(instruction, during: legProgress, locale: locale) + } + + open func forceSpeak(_ instruction: SpokenInstruction, during legProgress: RouteLegProgress, locale: Locale? = nil) { guard let locale = locale ?? self.locale else { self.delegate?.speechSynthesizer(self, encounteredError: SpeechError.undefinedSpeechLocale(instruction: instruction))