# Use Nosmai Effects with WebRTC on iOS

> Publish Nosmai's processed iOS pixel buffers through a WebRTC video source while your application owns signalling and peers.

> For AI agents: the complete documentation index is at https://nosmai.com/llms/effects.txt

Product: Nosmai Effects
Group: integrations
Source: https://nosmai.com/docs/effects/integrations/webrtc/ios/

## Streaming path

![Nosmai processes iOS camera frames before WebRTC publishes them](/images/docs/webrtc-streaming-flow-v1.svg)

Nosmai owns camera capture and the local preview. Your WebRTC layer publishes
the processed `CVPixelBuffer` output and manages signalling, peers, ICE, audio,
and reconnect behaviour.

## Prerequisites

- iOS 15 or later on a physical arm64 device
- The `NosmaiCameraSDK` and `WebRTC-SDK` pods
- An authenticated production signalling service
- Camera and microphone permission

```ruby
pod 'NosmaiCameraSDK', '~> 3.0.4'
pod 'WebRTC-SDK', '~> 125.6422.07'
```

## Create a WebRTC video source

```swift
let source = peerConnectionFactory.videoSource()
let capturer = RTCVideoCapturer(delegate: source)
let track = peerConnectionFactory.videoTrack(
    with: source,
    trackId: "nosmai-video"
)

peerConnection.add(track, streamIds: ["nosmai-stream"])
```

Add microphone audio through a normal WebRTC audio source. Do not create a
WebRTC camera capturer.

## Push processed frames

Keep the Nosmai callback short and forward each buffer to the WebRTC source.

```swift
NosmaiCore.shared().liveFrameStreamCallback = {
    pixelBuffer, timestamp in

    let buffer = RTCCVPixelBuffer(pixelBuffer: pixelBuffer)
    let frame = RTCVideoFrame(
        buffer: buffer,
        rotation: ._0,
        timeStampNs: Int64(timestamp * 1_000_000_000)
    )

    source.capturer(capturer, didCapture: frame)
}
```

The IOSurface-backed buffer is wrapped without a CPU pixel copy. Use
`liveFrameStreamCallback`; replacing the SDK singleton's lower-level callback
can detach recording output.

## Connect through your signalling layer

Exchange SDP and trickle ICE through your own service. Configure STUN and TURN
for the networks your application supports. The example viewer server is only
for local verification.

## Stop in the correct order

```swift
NosmaiCore.shared().liveFrameStreamCallback = nil
peerConnection.close()
```

Then close signalling and release the WebRTC tracks, source, and capturer.

## Common errors

| Symptom | Cause | Fix |
| --- | --- | --- |
| Preview is live but the peer receives no video | `startProcessing` was not called or the callback was not armed | Start capture and processing, then assign `liveFrameStreamCallback` |
| Recording stops receiving frames | The lower-level SDK callback replaced `NosmaiCore` fan-out | Use `liveFrameStreamCallback` only |
| Stream works on Wi-Fi but not across networks | ICE cannot find a viable route | Configure authenticated TURN and inspect ICE state |

## Verify it worked

Use the example viewer to confirm visible effects, decoded resolution, frame
rate, orientation, and a clean reconnect.

## Complete example

The [native iOS WebRTC example](https://github.com/nosmai/nosmai-webrtc-ios-example)
contains the tested frame wrapper, peer connection, signalling client, and
browser viewer.

## Next steps

- [Compare live-streaming routes](/docs/effects/integrations/live-streaming/)
- [Render frames off-screen on iOS](/docs/effects/guides/streaming/render-frames-off-screen/ios/)
- [Review iOS requirements](/docs/effects/platforms/ios/)

## References

- [Nosmai WebRTC iOS example](https://github.com/nosmai/nosmai-webrtc-ios-example)
- [WebRTC native APIs](https://webrtc.googlesource.com/src/)
