Low-Level Networking on watchOS for Duplex audio streaming

Dear Apple Team,

I am facing an issue with UDP networking in my watchOS app for duplex audio streaming using NWConnection. I have already added the necessary capabilities, including background mode for audio, to ensure smooth operation.

Issue Details:

The UDP connection works fine on the simulator since it uses macOS networking and allows low-level access. However, on a real Apple Watch (running watchOS 10), the connection remains in a "waiting" state and fails with Error 50. I am aware of Technical Note TN3135 regarding low-level networking on watchOS, but even after following these guidelines, the issue persists.

Questions:

Does watchOS impose additional restrictions on UDP networking compared to iOS/macOS?

Are there any specific entitlements or configurations required to allow UDP connections on a real Apple Watch?

Is there a workaround or debugging method to get more insights into why the connection fails?

I would appreciate any guidance or recommendations on resolving this issue.

Answered by DTS Engineer in 830379022

Did you watch WWDC 2019 Session 716 Streaming Audio on watchOS 6? I explains how the low-level networking capability is tied to the presence of an active audio session:

Once your application has an active audio session, all of the networking APIs are available to retrieve audio content.

I suspect that you’ve failed to set that up, and so low-level networking is blocked.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Did you watch WWDC 2019 Session 716 Streaming Audio on watchOS 6? I explains how the low-level networking capability is tied to the presence of an active audio session:

Once your application has an active audio session, all of the networking APIs are available to retrieve audio content.

I suspect that you’ve failed to set that up, and so low-level networking is blocked.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I did watch WWDC 2019 Session 716 and understand that an active audio session is key to unlocking low‑level networking on watchOS. I’m configuring my audio session and engine as follows:

private func configureAudioSession(completion: @escaping (Bool) -> Void) {
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [])
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
// Retrieve sample rate and configure the audio format.
let sampleRate = audioSession.sampleRate
print("Active hardware sample rate: \(sampleRate)")
audioFormat = AVAudioFormat(standardFormatWithSampleRate: sampleRate, channels: 1)
// Configure the audio engine.
audioInputNode = audioEngine.inputNode
audioEngine.attach(audioPlayerNode)
audioEngine.connect(audioPlayerNode, to: audioEngine.mainMixerNode, format: audioFormat)
try audioEngine.start()
completion(true)
} catch {
print("Error configuring audio session: \(error.localizedDescription)")
completion(false)
}
}
private func setupUDPConnection() {
let parameters = NWParameters.udp
parameters.includePeerToPeer = true
connection = NWConnection(host: "***.***.xxxxx.***", port: 0000, using: parameters)
setupNWConnectionHandlers()
}
private func setupTCPConnection() {
let parameters = NWParameters.tcp
connection = NWConnection(host: "***.***.xxxxx.***", port: 0000, using: parameters)
setupNWConnectionHandlers()
}
private func setupWebSocketConnection() {
guard let url = URL(string: "ws://***.***.xxxxx.***:0000") else {
print("Invalid WebSocket URL")
return
}
let session = URLSession(configuration: .default)
webSocketTask = session.webSocketTask(with: url)
webSocketTask?.resume()
print("WebSocket connection initiated")
sendAudioToServer()
receiveDataFromServer()
sendWebSocketPing(after: 0.6)
}
private func setupNWConnectionHandlers() {
connection?.stateUpdateHandler = { [weak self] state in
DispatchQueue.main.async {
switch state {
case .ready:
print("Connected (NWConnection)")
self?.isConnected = true
self?.failToConnect = false
self?.receiveDataFromServer()
self?.sendAudioToServer()
case .waiting(let error), .failed(let error):
print("Connection error: \(error.localizedDescription)")
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self?.setupNetwork()
}
case .cancelled:
print("NWConnection cancelled")
self?.isConnected = false
default:
break
}
}
}
connection?.start(queue: .main)
}

I’m using the .playAndRecord category with .voiceChat mode because my app requires duplex streaming—capturing audio to send to a server and playing back the incoming audio. Despite this setup and ensuring that the audio engine is running, my low-level connections (UDP, TCP, and WebSocket via NWConnection/URLSessionWebSocketTask) remain in a “waiting” state and eventually fail with Error 50 on a real Apple Watch.

I'm not using the simple activation snippet with an empty options array because it ask for audio route but i wanted to perform duplex audio streaming with default speaker and mic.

I am reaching out to seek further assistance regarding the challenges I've been experiencing with establishing a UDP, TCP & web socket connection on watchOS using NWConnection for duplex audio streaming. Despite implementing the recommendations provided earlier, I am still encountering difficulties. Or duplex audio streaming not possible on apple watch?

Low-Level Networking on watchOS for Duplex audio streaming
 
 
Q