Cellular call failure when two phones use cell network with talk app
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 2.2k
- Forks
- 586
- Avg merge
- 18h 27m
- Merged PRs (30d)
- 333
Description
Call freezes after ~3 seconds when BOTH participants use the native mobile apps on a direct P2P connection
Summary
In a 1:1 call between two mobile devices on cellular, media flows correctly in both
directions for ~2–4 seconds and then stops permanently. The call is never torn down by
the server; signalling stays healthy throughout.
The failure occurs only when both participants use the native Talk apps. Replacing
either endpoint with the Talk web client — same devices, same networks, same
call topology, same selected ICE candidate pair type — makes the problem disappear
completely.
The negotiated media path in the failing case is a direct peer-to-peer IPv6
host-to-host candidate pair. A TURN server is configured and reachable, but it is not
in the media path, and I have verified server-side that it never carries the media.
Environment
| Component | Version |
|---|---|
| Nextcloud Server | 34.0.3 |
| Talk (spreed) | 24.0.4 |
| Talk iOS | 24.0.2 (current on the App Store; no update offered) |
| Talk Android | 24.0.4 |
| Signalling | Internal (no High Performance Backend) — signaling_mode and signaling_servers both empty |
| TURN | coturn 4.6.3, IPv4-only |
| Devices | iPhone (iOS) and Samsung Galaxy S22 Ultra (Android) |
| Network | Both devices on the same MVNO cellular plan, Wi-Fi disabled on both. Both receive public IPv6. |
Steps to reproduce
- Two mobile devices, both on cellular only (Wi-Fi off), both with public IPv6.
- Place a 1:1 Talk call between them using the native Talk apps on both ends.
- Call connects; audio and video work.
- After ~2–4 seconds one side's outbound media stops. ~5 s later the other side stops too (consistent with ICE consent-freshness expiry).
Reproduced on every attempt — audio-only and video calls alike.
Expected vs actual
Expected: media continues, or ICE fails over to the configured TURN relay.
Actual: media stops permanently. No recovery, and no failover to the relay occurs.
The decisive evidence: same path, different client
| Android side | iOS side | Media path | Result |
|---|---|---|---|
| Talk app | Talk app | direct P2P | ❌ freezes ~3 s |
| Chrome (desktop, tethered to the Android) | Talk app | direct IPv6 host↔host | ✅ works |
| Chrome (on the Android itself) | Talk app | direct IPv6 host↔host | ✅ works |
| Talk app | Safari (web client) | direct P2P | ✅ works |
| Talk app | Talk app | IPv4 TURN relay | ✅ works |
Neither app is broken in isolation: the Android app works against Safari, the iOS app
works against Chrome, and both work against each other when the path is the TURN relay.
Only the app-to-app + direct-P2P combination fails.
Two details tighten this considerably:
- In the "Chrome on the Android" test, the Android's own IPv6 address is in the same /64 as in the failing app-to-app tests.
- The iOS device presented the identical IPv6 address (same interface identifier) in both the working and failing runs.
So the same IPv6 candidate pair, between the same two devices on the same carrier, works
under a browser and fails under the app.
Client-side data (rtcstats, working browser calls)
Nominated pair, Chrome on Android → Talk iOS app:
PAIR nominated=true
local host/udp 2600:xxxx:xxxx:xxxx:....:51849
remote host/udp 2600:xxxx:xxxx:xxxx:....:51052
bytesSent = 7,846,357 bytesReceived = 313,354 rtt = 0.051
Nominated pair, Chrome on desktop → Talk iOS app:
PAIR nominated=true
local host/udp 2600:xxxx:xxxx:xxxx:....:52084
remote host/udp 2600:xxxx:xxxx:xxxx:....:60775
bytesSent = 2,671,406 bytesReceived = 1,468,851 rtt = 0.087
In both cases the IPv4 TURN relay pair is present and succeeded, but carries
bytesSent=0, bytesReceived=0 — the relay is available and simply loses candidate
selection to the direct IPv6 pair, as expected.
I have no equivalent client-side data for the failing case, because the native mobile
apps do not expose a WebRTC stats dump. **That is the main gap in this report, and a
built-in way to export **rtcstats from the mobile apps would have made this far easier
Server-side observations during failing calls
Captured with tcpdump on the TURN host, covering UDP/TCP 3478 and the relay port range:
- No media ever traverses the TURN server. Peak throughput ~1.3 KB/s, largest packet 396 bytes — STUN connectivity checks and TURN control only, no RTP.
- coturn logs zero errors across every call: no auth failures, no quota, no refused allocations. Permissions are correctly created for the peers.
- Every call ends with a client-side
DELETE .../call/<token>; the server never terminates a call. - Talk signalling requests are all HTTP 200/304 with no 5xx, and continue normally on both devices after media has stopped.
Two observations that may help pinpoint the client behaviour:
-
The iOS app stops communicating with the TURN server ~0.87 s into the call, on
both its UDP and its TCP socket, 82 ms apart, and never resumes — while continuing
Talk signalling normally. This is consistent with it nominating the direct pair and
ceasing to service its relay candidates. -
The abandoned TURN-over-TCP connection is not closed — no
FIN, noREFRESH
withlifetime=0. It is simply left. The server then retransmits into a black hole
for minutes. Across two consecutive calls the socket accepted a byte-identical
396 acknowledged bytes before going silent:call 1 call 2. bytes_sent 4752 4488 bytes_acked 396 396 bytes_retrans 3960 3696 RTO backoff 10 9 Leaving the TCP allocation dangling rather than closing it looks like a resource leak
on the server side, independent of the main issue.
Ruled out
Server load; bandwidth, packet size and path MTU (the stalled segment is only 396 bytes,
and audio-only calls at ~40 kbps fail identically); mesh size (genuine 1:1, room
type=1); NAT traversal and the TURN port-forward (verified end to end with a
hand-rolled TURN client); carrier CGNAT filtering; iOS Lockdown Mode (off); iCloud
Private Relay (not subscribed); iOS Low Data Mode (not present on this plan); VPN or DNS
profiles (none installed); Talk app versions (an iPad on the identical iOS build 24.0.2
holds calls fine).
Workaround
Have either participant join from the web client instead of the native app. Only one
side needs to change.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
No source file or test entry point is identified in the report. Start by locating the native iOS and Android WebRTC call and ICE candidate-handling entry points, then reproduce the direct IPv6 app-to-app case alongside the working browser case. Done means native app calls keep media flowing or correctly fail over to the configured TURN relay, with a regression test or captured diagnostic evidence.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, ios
- Domain
- audio-video-rtc, mobile-dev
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100