LevelChatLevelChatDocs
iOS

SDKs

iOS SDK

Swift Package mirroring the Web SDK API.

Preview. The Swift Package source is in private development — passes 21/21 unit tests — and a public Swift Package + CocoaPods release is on the v0.3 roadmap. The API surface below is stable; only the distribution channel is in flight. Need source access during the preview window? Email [email protected].

The iOS SDK is a Swift Package mirroring the @levelchat/web concepts — LevelChat, Room, tracks, events — adapted to Swift Concurrency (async/await, actor, AsyncStream). Naming follows Swift idiom: events are a RoomEvent enum iterated with for await, not Web's kebab-case event strings. See the SDK parity matrix for the full per-platform naming table.

MIT-licensed. Real WebRTC via stasel/WebRTC.

Install — preview

The public install commands below are the planned shapes for v0.3; they will resolve once the Swift Package Index publish lands.

SwiftSwift Package Manager (v0.3+)
// dependencies: [
//   .package(url: "https://swift.levelchat.io/level-vchat.git", from: "0.3.0"),
// ]
// TODO(v0.3): the canonical SPM URL above is the planned mirror; the
// public Git endpoint is set up alongside the v0.3 release.
rubyCocoaPods (v0.3+)
# pod 'LevelChat', '~> 0.3'

During the preview window the package is distributed under NDA — email

[email protected] with your use case and we'll send back the private mirror credentials + a Package.swift snippet you can drop into your project.

Permissions

Add to Info.plist:

xml
<key>NSCameraUsageDescription</key>
<string>Used for video calls</string>
<key>NSMicrophoneUsageDescription</key>
<string>Used for voice + video calls</string>

For screen sharing, also add a Broadcast Upload Extension target to your app. The SDK's ScreenShare module drives RPSystemBroadcastPickerView for the picker UI and bridges the extension's frames into room.publishScreen().

The end-to-end path

Every LevelChat SDK follows the same five steps: install → token → join → publish → handle events → leave. Here it is for Swift.

1. Mint a token

In production the token comes from your backend. Never ship an apiKey in the binary — your auth flow mints short-lived JWTs scoped to a single (room, user, role). The issueToken helper exists only for getting-started tutorials. See the Quickstart for the POST /v1/auth/tokens/room request shape.

Swift
let token = try await fetchTokenFromYourBackend()

2. Construct the client + join

LevelChat is an actor, so construction and joins are awaited. joinRoom returns the Room actor directly; joinLive wraps it in a LiveStream carrying the role hint:

Swift
import LevelChat

let client = try await LevelChat(config: LevelChatConfig(
  region: "eu-fsn",
  logLevel: .info
))

// Either join a room directly…
let room = try await client.joinRoom(JoinRoomOptions(token: token))

// …or join as a typed broadcaster / viewer:
let live = try await client.joinLive(
  JoinLiveOptions(token: token, role: .broadcaster)
)
let room = live.room

JoinRoomOptions mirrors the web SDK: token (required), device, preferredCodec, simulcast (default true), svc, e2eeKey, signalingUrl.

3. Handle events

room.events is an AsyncStream<RoomEvent> — Swift's idiomatic typed event bus. Iterate it with for await from any task; the RoomEvent enum carries the same names as the web SDK:

Swift
Task {
  for await event in await room.events {
    switch event {
    case .participantJoined(let p):
      print("joined: \(p.identity)")
    case .participantLeft(let p, _):
      print("left: \(p.identity)")
    case .trackSubscribed(let track):
      // A remote track is ready — bind track.nativeTrackId to a view.
      attachRenderer(track)
    case .trackUnsubscribed(let track):
      detachRenderer(track)
    case .connectionState(let state):
      // state ∈ .connecting | .connected | .reconnecting | .disconnected | .failed
      updateConnectionBanner(state)
    case .connectionQuality(let participant, let score):
      // score.label ∈ excellent | good | fair | poor | disconnected
      updateNetworkIndicator(participant, score)
    case .chatMessage(let m):
      appendChatMessage(m)
    default:
      break
    }
  }
}

4. Publish camera + mic

Both methods are async and return a TrackView whose nativeTrackId you bind to your renderer for the local preview:

Swift
let camera = try await room.publishCamera(PublishCameraOptions(resolution: "720p"))
let mic = try await room.publishMic()

PublishCameraOptions / PublishMicOptions mirror the web SDK — deviceId for device selection, encodings for simulcast layers, echoCancellation / noiseSuppression / autoGainControl on the mic.

5. Chat + recording

In-call chat rides the room's data channel — sendChat to send, .chatMessage events to receive (wired in step 3):

Swift
try await room.sendChat(text: "hello room")

Recording is host-only and server-side. Start it with room.startRecording(); it returns a RecordingView whose id you can surface in your UI:

Swift
let recording = try await room.startRecording(
  StartRecordingOptions(layout: .speaker)
)
// …
_ = try await room.stopRecording()

Recording is gated by the record capability on the token — a UI-only "host" flag is not enough. On Web the canonical method is room.record({ compose }); on iOS the startRecording name + layout option is current. See the Recordings guide.

6. Leave

leave() tears down the peer connection and signaling, and finishes the event stream:

Swift
await room.leave()

UI Kit

The package ships a second product, LevelChatUI, with SwiftUI views matching the React component kit's component set. It has no WebRTC dependency — safe to import from marketing apps and previews.

Swift
import LevelChatUI

VideoTileView(
  name: "Mert",
  quality: .good,
  micOn: true,
  isSpeaking: speaker == myId
)
.frame(width: 240, height: 180)

LevelChatUI ships VideoTileView, ControlBarView, PreJoinView, SpeakerStageView, ChatView, PollPanelView, QAPanelView, and WhiteboardView — every component the React web kit exports has a SwiftUI counterpart with the same name and brand tokens.

Public API

  • actor LevelChat — the entrypoint; construct with try await LevelChat(config:). joinRoom / joinLive / issueToken (dev only).
  • actor Roomjoin / leave / publishCamera / publishMic / publishScreen / stopPublishing / subscribe / unsubscribe / sendChat / startRecording / stopRecording.
  • room.events: AsyncStream<RoomEvent> — typed event surface (participantJoined, trackSubscribed, connectionState, connectionQuality, chatMessage, …); iterate it with for await.

Modules: Errors, Logger, Events, Constants, Region, Quality, Encryption, Devices, Signaling, Peer, Track, Publication, Subscription, Participant, Room, AudioSession, Lifecycle, ScreenShare.

Audio session

Auto-configured for voice calls — AVAudioSession.playAndRecord + .voiceChat + .allowBluetooth + .defaultToSpeaker. Override via AudioSession.applyMode(...) if your app needs a different routing mode.

Lifecycle

Built-in UIApplication/NSApplication background-foreground observer via the LifecycleObserver actor — the Room pauses local video when the app is backgrounded. Call observer.start() / observer.stop() if you need to drive it yourself.

License

The SDK itself is MIT-licensed — embed in any app, commercial or personal, no royalties. The LevelChat server (signaling, SFU, recording) is BUSL-licensed and requires a paid commercial license for production use beyond the trial threshold. See Licensing.

iOS SDK — LevelChat Docs