Android
A Kotlin app with Jetpack Compose and Material 3 Expressive, on webrtc-sdk 150.7871.01. Android 7.0 (API 24) or newer.
File: docs/public/media/android-call.pngRun the app configuration from Android Studio, or ./gradlew :app:installDebug in android/.
Where things are
All paths are under android/app/src/main/java/com/example/myapplication.
| File | What it does |
|---|---|
MainActivity.kt | Hosts the Compose lobby, starts CallActivity |
CallActivity.kt | Hosts the call screen: permissions, pickers, MediaProjection, PiP |
call/ | BaseCallViewModel (shared state and media controls), CallViewModel (1:1), GroupCallViewModel (group) |
webrtc/LocalMedia.kt | Factory, capturers, sources, tracks, effects, the 300 ms rule. Shared by both engines. |
webrtc/PeerConnectionClient.kt | The 1:1 engine |
webrtc/WebRtcPeer.kt | One PeerConnection, its data channel and cryptors |
webrtc/SignalingSocket.kt | JSON over an OkHttp WebSocket, callbacks on the main thread. Used by both engines. |
webrtc/sfu/GroupCallClient.kt | The group engine: publish and subscribe connections |
webrtc/E2eeManager.kt | The FrameCryptor key provider |
webrtc/effects/ | GLES effects processor, MediaPipe segmenter and face tracker |
ui/video/TextureViewRenderer.kt | The video view |
How it does each part
- Switching sources. The camera and the screen each get a new track, put on the same sender with
RtpSender.setTrack(). A file reuses the camera'sVideoSourcewith another capturer. Switching video sources - Screen sharing.
MediaProjectionwith a foreground service,ScreenCaptureService.kt. It stays alive while you share another app, so it hears about rotation and resizes the virtual display. - E2EE.
FrameCryptorfrom webrtc-sdk with the shared options. End-to-end encryption - Effects. A
VideoProcessoron the camera'sVideoSource. Everything runs in GLES shaders on the camera texture, and only a small copy is read back for the models. Backgrounds and effects - Picture-in-picture. Enters by itself on the home gesture on Android 12+. Picture-in-picture
Video rendering
Video is drawn by TextureViewRenderer, a TextureView fed by an EglRenderer. Unlike SurfaceViewRenderer, a TextureView can be clipped, rounded and animated with the rest of the Compose tree, which the floating self view needs.
The renderer always fills its view. So the view is laid out at the frame's aspect ratio, just large enough to cover the screen, and "fit" scales it down with a graphicsLayer. Switching between fit and fill is then a GPU-only animation that never resizes the TextureView. iOS, macOS and Windows use the same idea.
Threads
| Thread | Work |
|---|---|
| Main | UI, every PeerConnection call, socket callbacks |
| WebRTC signaling thread | PeerConnection.Observer callbacks, which hop to main |
CaptureThread | Camera frames and the effects' GL work |
SegmenterInference, FaceTracker | The MediaPipe models |
Callbacks are dropped once a call has ended, because calling a disposed native PeerConnection crashes the process.
Notes
CallViewModelsurvives rotation and owns the WebRTC client and the sharedEglBase, so the call keeps running while the activity is recreated.CallActivityhandles size changes itself, so in practice it isn't recreated at all.Camera2Sessiontags every frame with the device rotation, so the other side sees an upright picture whichever way the phone is held.- Material 3 Expressive is only in the
material31.5 alphas, pinned to1.5.0-alpha18. See Troubleshooting before bumping Compose. - The MediaPipe models (
selfie_segmenter.tflite,face_landmarker.task) are stored uncompressed in the APK's assets.
