Library / SDK
stasel/WebRTC-iOS avatar
stasel/WebRTC-iOS

WebRTC-iOS: the minimum path to a peer connection in a Swift app

A simple native WebRTC demo iOS app using swift

1,273 stars258 forksSwiftApache-2.0

At a glance

What is it?
WebRTC-iOS is a demo iOS app that establishes a WebRTC peer to peer connection with the signalling reduced to a broadcast server, written in Swift. It suits a developer who wants to see the offer, answer and candidate exchange as concrete code, and the project says plainly that it is not production ready and lists what a real VoIP app would still need.
Who is it for?
WebRTC-iOS fits a developer who wants the WebRTC flow made concrete on iOS, because it shows the offer, the answer and the candidate exchange in a small app rather than a framework, and because the CallKit section documents the audio session dance that is otherwise the least obvious part of wiring WebRTC into a system call UI.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 23 days ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The scope is the point: the bare minimum

WebRTC-iOS is a simple native WebRTC demo iOS app using Swift, and the disclaimer at the top sets the scope better than any feature list could.

This demo app's purpose is to demonstrate the bare minimum required to establish a peer to peer connection with WebRTC. This is not a production ready code, and the project says what a production VoIP app would need on top: a real signaling server, not a simple broadcast server like in this example, your own TURN servers, and probably integration of CallKit and push notifications.

That last sentence is the most useful thing in the documentation, because it names the three things a demo omits and a product cannot. The signalling server is the biggest difference. In this example the server is a broadcast, which means every connected client receives every message, and since the app has exactly two participants that happens to work. With more clients, each one would receive the other parties offers and answers, and the app would have to filter them, and with real users you also need authentication on the signalling channel, session management, and reconnection logic. TURN is the other one: the peer to peer connection works directly between two devices when a direct path is possible, and a TURN server is the relay for when it is not, which in practice means behind restrictive NATs.

So the value of the project is not the app. It is that the app is small enough to read, and that it makes the shape of the thing visible, including the parts you will have to build yourself.

The licence is Apache-2.0, the language is Swift, and the last push was on 2026-09-30.

The signalling server comes in two flavours, and you need Node or macOS

The signalling is implemented twice, and that is not redundancy so much as a demonstration of the point that signalling is just a message relay.

The NodeJS version lives in the `signaling/NodeJS` folder. You navigate there, run `npm install` to install all dependencies, and run `node app.js` to start the server.

The Swift version lives in the `signaling/Swift` folder. You run `make`, then run `./server` to start the server. There is an alternative method too, opening the `WebRTC-Demo.xcworkspace` and running the `SignalingServer` scheme, which is convenient if you already have the workspace open in Xcode.

Two implementations of the same trivial thing is a fair bit of work for a demo, and the reason it is there is instructive. Signalling is the part of WebRTC that is not standardised, and it is the part you have to write for your own application. Seeing the same server written in a language you already have a toolchain for, in about fifty lines, is a way of showing that it is not a hard problem. The hard parts are ICE, DTLS and the media path, and those are in the WebRTC framework itself.

The requirements list makes the toolchain explicit: Xcode 26 or later, iOS 15 or later, and then either Node.js with npm for the NodeJS signalling server, or macOS 12 for the Swift signalling server.

And there is a maintenance detail worth knowing about early. The project keeps branches for older toolchains, an Xcode 10 branch and an Xcode 11 branch, listed under legacy xcode support. So if you are on an older Xcode you are not stuck, but you are on a branch that receives nothing new, and you should know that before you start.

The run sequence, and the two mistakes that block people

The setup instructions are five steps, and two of them are where people get stuck.

Start the signalling server, either the NodeJS or the Swift one. Then navigate to the `WebRTC-Demo-app` folder and open `WebRTC-Demo.xcworkspace`. Note the workspace and not the project, which is the usual cause of a build that cannot find the WebRTC framework. Then open `Config.swift` and set the `defaultSignalingServerUrl` variable to your signalling server IP or host, with the explicit warning not to use `localhost` or `127.0.0.1` if you plan to connect other devices in your network to your Mac. Then build and run on devices or on a simulator, with the note that video capture is not supported on a simulator.

The localhost warning is the one worth internalising. The address `localhost` means the device itself, so if you set it and then run the app on your phone, the phone looks for the signalling server on the phone. The server is on your Mac, so you need the Mac's address on the local network, and that is also the address the other device needs to reach.

The simulator note is about the video capture path rather than about the peer connection. The audio and the data path work in the simulator, but there is no camera to capture, so the video button will not do anything useful there. For a demo involving two devices you need two real devices.

The run instructions then walk the offer and answer exchange. Run the app on two devices with the signalling server running, make sure both are connected to it, and on the first device click Send offer, which generates a local offer SDP and sends it to the other client using the signalling server. Wait until the second device receives the offer, where you should see that a remote SDP has arrived. Click Send answer on the second device, and when the answer arrives at the first device both devices should now be connected to each other using WebRTC, at which point you can talk or click the video button to start capturing video.

And to restart the process, kill both apps and repeat steps 1 to 6. That last step is not a throwaway remark. The peer connection in this demo is set up once and not renegotiated, so the flow does not survive a backgrounding or a reconnect, and knowing that early saves a long debugging session.

CallKit needs the WebRTC audio session to be told what CallKit did

The CallKit section is the most practically useful part of the README, and it comes with an honest disclaimer that I am not sure if this is the best way of doing it but this has worked for me so far.

The problem it solves is that WebRTC and CallKit both want to own the audio session, and if they do not agree, the call has no audio or the audio is routed wrong. The fix is a handshake in both directions.

First, configure the WebRTC audio session to use manual audio and disable audio:

swift
RTCAudioSession.sharedInstance().useManualAudio = true
RTCAudioSession.sharedInstance().isAudioEnabled = false

That tells WebRTC not to touch the audio hardware on its own. You are taking that responsibility.

Then, on your `CXProvider` delegate's `provider(CXProvider, didActivate: AVAudioSession)` method, call `RTCAudioSession.sharedInstance().audioSessionDidActivate` with the `AVAudioSession` from the `CXProvider`, and then enable audio with `RTCAudioSession.sharedInstance().isAudioEnabled = true`.

And symmetrically, on the `provider(CXProvider, didDeactivate: AVAudioSession)` call, call `RTCAudioSession.sharedInstance().audioSessionDidDeactivate` with the `AVAudioSession` from the `CXProvider`.

The order matters and is the whole point. CallKit activates the audio session, the app tells WebRTC that it happened, and only then does WebRTC start using the audio. On deactivation the same handshake runs in reverse, and if it does not, WebRTC keeps a reference to a session that CallKit has taken back, which is the kind of bug that appears as audio that works on the first call and not on the second.

The section also links a 2016 video on the subject, at a timestamp six minutes and twenty three seconds in, which given the age of the linked video is likely to show an older API, but the shape of the handshake is the same.

The references list points to the same places a reader would otherwise have to search for: the WebRTC website, the WebRTC source code, the WebRTC iOS compile guide, an appear.in developer blog post that is noted as using an old WebRTC API but still very informative, the AppRTC example which is described as a more detailed app to demonstrate WebRTC, useful information from pexip, and a post on building a video chat with WebRTC and Firestore.

Editorial conclusion

WebRTC-iOS fits a developer who wants the WebRTC flow made concrete on iOS, because it shows the offer, the answer and the candidate exchange in a small app rather than a framework, and because the CallKit section documents the audio session dance that is otherwise the least obvious part of wiring WebRTC into a system call UI. Skip it if you are shipping a calling app, since the project is explicit that it is not production ready and that you would need a real signalling server, your own TURN servers and probably CallKit and push notifications. Before you build on it, read the disclaimer, and note the toolchain, since it needs Xcode 26 or later and iOS 15 or later, with the previous Xcode versions on separate branches. The last push was on 2026-09-30.

Frequently asked questions

What is the WebRTC-iOS demo app?

It is a simple native WebRTC demo iOS app written in Swift, whose purpose is to demonstrate the bare minimum required to establish a peer to peer connection with WebRTC. The project states it is not production ready.

How do I run the WebRTC-iOS demo?

Start the signalling server, either the NodeJS or the Swift one, open the WebRTC-Demo.xcworkspace, set `defaultSignalingServerUrl` in Config.swift to your server address, and build to a device. Video capture is not supported on a simulator.

Why does the WebRTC-iOS setup warn against localhost?

Because the app would look for the signalling server on the device itself. If you are connecting another device on your network to your Mac, you need your Mac's IP or host address instead of localhost or 127.0.0.1.

What does integrating CallKit with WebRTC on iOS require?

Set the RTCAudioSession to use manual audio and disable audio, then on didActivate call audioSessionDidActivate with the AVAudioSession from the CXProvider and enable audio, and on didDeactivate call audioSessionDidDeactivate with the same session.

What would a production VoIP app need that this demo does not have?

A real signalling server rather than a simple broadcast one, your own TURN servers, and probably integration of CallKit and push notifications. The requirements are Xcode 26 or later, iOS 15 or later, and either Node.js with npm or macOS 12.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. stasel/WebRTC-iOS on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/stasel-webrtc-ios.svg)](https://hysenlabs.com/projects/stasel-webrtc-ios)

Was this useful?