Skip to content

Call someone from their Remote, as the web does - #137

Merged
simonhamp merged 1 commit into
NativePHP:mainfrom
rohitsasi720:feat/calls
Oct 30, 2026
Merged

simonhamp merged 1 commit into
NativePHP:mainfrom
rohitsasi720:feat/calls

Conversation

@rohitsasi720

Copy link
Copy Markdown
Contributor

On the web, a one-to-one Remote can make a call from its phone button. This brings calls to the app, and they work with the web in both directions: phone to browser, browser to phone, and phone to phone.

It needs the call endpoints on the signed API from dev-null-sh/dev-null.sh#1312.

What you can do

  • A one-to-one thread you can write in gets a phone button next to its menu. It opens a call screen that rings them for 45 seconds.
  • A call ringing for you opens the same screen with Accept and Decline, and buzzes unless their thread is muted. This works from the Log, Notifications, Remotes, your profile or any thread, while the app is open.
  • Once connected, the screen shows the timer and the web's fp code, made from both ends' keys, with mute, speaker and Hang up. It says how a call ended ("Call ended.", "No answer.", "Missed call from @…", "Couldn't connect the call.") for 4 seconds, as the web does. Android's back button hangs up.
  • Threads now log calls in the web's words ("you started a call", "missed call from @ada"). Before, these showed as a bare "* someone".

How it works

packages/calls is a new local NativePHP plugin around Google's WebRTC (io.getstream:stream-webrtc-android, stasel/WebRTC). It sets the call up exactly as the browser does (resources/js/lib/call.ts on the web):

  • the microphone, an empty video slot, and a call data channel whose closing means the other end hung up
  • the offer or answer sent only once candidate gathering is done
  • relay-only when devnull says so

The PHP side of the call lives in Call. The fp code is worked out in PHP from both descriptions, and a test checks it against the web's own function.

Limits for now

  • Audio only. A partner on the web can turn their camera on, but this end doesn't show it.
  • Calls only ring while the app is open, and Android may cut the microphone if the app goes to the background mid-call.
  • One-to-one only, since org calls aren't on the API yet.

Testing

  • tests/Feature/Screens/CallTest.php passes: 22 tests, 202 assertions. The thread, inbox and composer tests pass too.
  • In the full suite, LogTest › "shows the following log in tab chrome" fails on my machine. It stalls for 90 seconds and the clock moves on. It fails the same way without this change.
  • Tried on a Pixel 9 (Android 16) against a local instance of the API branch, with Chrome on macOS:
    • phone to web and web to phone, with audio both ways and the same fp code on both ends
    • decline, ring-out and missed call, hang up from either end
    • mute and speaker
  • iOS isn't built or tested. The Swift side is written, but I don't have Xcode on this machine.

Screenshots

Android only, because of the iOS limitation above. The call screen is new, so it has no before shots.

conversation

Android light Android dark
Before
After

call-ringing

Android light Android dark
After

Recordings of an incoming call sliding up: Android light, Android dark

call-connected

Android light Android dark
After
  • No visible change

A one-to-one thread gets the web's phone button. It opens a call screen
that rings them for 45 seconds, and a call ringing for you opens the same
screen with Accept and Decline from the Log, Notifications, Remotes, your
profile or any thread, while the app is open. Once connected it shows the
web's fp code, made from both ends' keys, with mute, speaker and hang up.

The two ends swap a WebRTC offer and answer through the signed API's new
call endpoints, then talk through the relay, so a call works whether the
other end is the web or the app. packages/calls is a NativePHP plugin
around Google's WebRTC (stream-webrtc-android, stasel/WebRTC) that sets
the call up exactly as the browser does: the microphone, room for video
and a "call" data channel whose closing means the other end hung up.

The thread now logs calls in the web's words ("you started a call",
"missed call from @ada"), naming the other person where devnull leaves
the author off, instead of a bare "* someone".

Audio only for now: a partner's camera isn't shown yet, and a call only
rings while the app is open.
@simonhamp
simonhamp merged commit 3fab6f5 into NativePHP:main Oct 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants