mapbox / mapbox-navigation-ios

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

RouteVoiceController is silent when starting navigation on CarPlay without using phone #3504

Open 1ec5 opened 3 years ago

1ec5 commented 3 years ago

A RouteVoiceController needs to be present for the lifetime of not only a NavigationViewController but also a CarPlay navigation activity.

Problem

If the user opens the application and starts navigation on the CarPlay screen while the application isn’t visible on the connected iPhone screen, then the application runs in background mode, and the NavigationViewController’s RouteVoiceController apparently doesn’t produce any audible sound.

Diagnosis

I’m not 100% clear on the reason the voice controller doesn’t work in this scenario, but we had been assuming that the phone’s UI would always drive voice instructions while connected to CarPlay. The audio would get routed through the car speakers, which the user would perceive as the CarPlay UI making those announcements. But starting navigation while the phone is locked seems to break that assumption.

Workaround

The workaround is to silence the built-in voice controller in favor of one managed by the application.

NavigationViewController.voiceController and RouteVoiceController.speechSynthesizer are non-optional, but the default speech synthesizer is multiplexed (to allow a fallback to VoiceOver when the Mapbox Voice API is unavailable). The multiplexed speech synthesizer can fall back to any number of component speech synthesizers or none at all. So you can effectively take away its ability to say anything by emptying the array of component speech synthesizers:

if let synthesizer = navigationViewController.voiceController.speechSynthesizer as? MultiplexedSpeechSynthesizer {
    synthesizer.speechSynthesizers = []
}

Then your application delegate can initialize and own a RouteVoiceController instance:

let credentials = navigationService.directions.credentials
voiceController = RouteVoiceController(navigationService: navigationService, accessToken: credentials.accessToken, host: credentials.host.absoluteString)

You’d be responsible for managing this voice controller and ensuring that it’s present as long as either CarPlayManager or NavigationViewController is around.

Proposed solution

This workaround suggests that we should allow CarPlayNavigationViewController (or CarPlayManager?) and NavigationViewController to share a single RouteVoiceController, just as they share a single NavigationService. I don’t think this necessarily means RouteVoiceController needs to be a singleton – though that would certainly prevent a chorus of voices when there’s a bug. Rather, we should consider the voice controller to be just one more resource shared between two screens within a given trip.

/cc @mapbox/navigation-ios @OttyLab

RL-William-Coates commented 8 months ago

Was this issues fixed? I'm experiencing the same problem, and the workaround is not working for me.

Udumft commented 8 months ago

Hi @William-Coates! Which SDK version do you use?

RL-William-Coates commented 8 months ago

Hi @William-Coates! Which SDK version do you use?

Hi, I'm using 2.17.0.

I would have expected the CarPlayNavigationViewController to have a NavigationOptions attribute, from which I can define a RouteVoiceController. I assume there's a reason this isn't the case?

Udumft commented 8 months ago

Well, this issue had no attention since then since it is quite rare and there is a workaround provided. Could you please expand more about how you've implemented it, because it is odd that it didn't work for you?

RL-William-Coates commented 8 months ago

It was implemented using the example provided in this repo. Going back to an early commit which is identical to the example, this issues is still occurring.

Is there just no way of defining the RouteVoiceController for CarPlay navigation? This is how it's defined for iOS navigation:

let voiceController = RouteVoiceController(
    navigationService: navigationService,
    accessToken: NavigationSettings.shared.directions.credentials.accessToken,
    host: NavigationSettings.shared.directions.credentials.host.absoluteString
)
voiceController.speechSynthesizer.managesAudioSession = false
voiceController.speechSynthesizer.delegate = self
RL-William-Coates commented 8 months ago

@1ec5 Where do you use the voiceController that you've initialized in the application delegate. I don't see any methods for defining a 'NavigationOptions' object for CarPlay. Could you expand on your workaround please?

Udumft commented 8 months ago

Hi @William-Coates! Sorry for the long reply. Here I've prepared a sample to demonstrate the proposed workaround. It seems to be working for me, and, you've mentioned that your implementation is based on this repo's Example, so you should be able to adapt the changes to your solution. Please check this workaround example to see if it works for you too and let me know the results. Thanks.

RL-William-Coates commented 8 months ago

Thanks for looking into this. I ran your branch and am still experiencing the same problem. Are you testing by force quitting the app on the phone, then setting a destination from CarPlay, and then starting navigation from CarPlay?

Udumft commented 8 months ago

Right. The app is shut down on the phone, then I open CarPlay play and start navigation from CarPlay screen. Nothing is done on the phone during that, and voice instructions are vocalized. I've tried with both locked and unlocked device. Did you try it with real Device+CarPlay? Unfortunately I don't have access to CarPlay-equipped vehicle...

RL-William-Coates commented 8 months ago

I'm using a real device + CarPlay Simulator, but I've tried it on a real CarPlay unit and get the same results. With these changes, these are results I experience: