0

I am using AVSpeechSynthesizer inside a WatchKit App Extension.

The logic is simple, and can be summarized as the following:

let utterance = AVSpeechUtterance(string: "Hello, World")
synth.speak(utterance)

This works fine but the speech always gets relayed via the Apple Watch's onboard speakers.
I require the speech to come through my airpods which are connected to my iPhone.

Previously I had delegated the task to the iPhone via WatchConnectivity which worked well but due to delays in WatchConnectivity communication, I moved the control logic directly onto the Apple Watch.
I thought watchOS would internally hand over the audio to the BLE device but it's not going as planned.

Maybe I am missing something?
Do I need to specify the audio channel synth.outputChannels?
Do I need to show the AirPlay popup asking user to select an audio output source?
If so how do I go about this?

I am unable to find much information on this matter online so any help would be greatly appreciated.

I am just trying to find a way to get the speech over my AirPods.

staticVoidMan
  • 19,275
  • 6
  • 69
  • 98
  • I believe that you would need to pair the AirPods with your watch and select them as the audio output – Paulw11 Mar 23 '20 at 09:05
  • @Paulw11 That is one solution but `watchOS` does not ask which audio device to use. For example, in music player scenarios, it shows a dialog request and the option to pair a bluetooth device. I could work around this by playing a silent track and letting watchOS popup with it's dialog before I initiate the speech. But is hackish at best. Is there a cleaner approach you can suggest? – staticVoidMan Mar 23 '20 at 13:27
  • Don't you just use the audio device selection button when you swipe up from the watch face? I believe the user has to route all audio to the headset. `AVRoutePickerView` isn't available on watchOS – Paulw11 Mar 23 '20 at 20:58
  • @Paulw11 Ideally I would, but my client wants this assistance/prompt to come from within the application. – staticVoidMan Mar 24 '20 at 06:15

1 Answers1

2

You can use the following code to display an audio device picker and direct audio to the selected device:

let session = AVAudioSession.sharedInstance()
do {
    try session.setCategory(AVAudioSession.Category.playback,
                                mode: .default,
                                policy: .longFormAudio,
                                options: [])
    session.activate(options: []) { (success, error) in
            // Check for an error and play audio.
        if let err = error) {
            print(err)
        }
    }
} catch {
    print(error)
}
Paulw11
  • 108,386
  • 14
  • 159
  • 186
  • Interesting. I already had the following `AVAudioSession.sharedInstance().setCategory(.playback, options: .mixWithOthers)` and `AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)`. Maybe using `policy: .longFormAudio` and the empty `session.activate` might work out for me. Definitely looks better than my idea of playing an empty audio clip. Thanks, will give this a go and let you know :) – staticVoidMan Mar 24 '20 at 13:01
  • Yes, in my testing, only `.longFormAudio` displayed the output picker. – Paulw11 Mar 24 '20 at 19:25
  • That was good test then because as per the documentation on [`activate(options:completionHandler:)`](https://developer.apple.com/documentation/avfoundation/avaudiosession/2962797-activate) it states `Playback of long-form audio on watchOS requires a Bluetooth audio route. If necessary, the system presents an audio route picker to the user, letting them choose the Bluetooth route.`. Thanks alot for your help. – staticVoidMan Mar 26 '20 at 06:32