LevelChatDocs
Docs
SDK parity matrix

SDKs

SDK parity matrix

The method, event, and error-code surface across all five LevelChat SDKs — and the documented naming divergences.

All five LevelChat SDKs — Web, iOS, Android, React Native, and Flutter — expose the same capabilities. The Web SDK is the canonical reference; every other platform mirrors its method names, parameters, and event shape unless a platform constraint forces a documented divergence.

This page is the table you want open when you port integration code from one stack to another.

Method surface

CapabilityWebiOSAndroidReact NativeFlutterNotes / divergence
new LevelChat({ apiKey?, region? })yesyesyesyesyesiOS/Android use a factory: try await LevelChat(config:) / LevelChat.create(context, config).
client.issueToken({ userId, roomId, ... })yesyesyesyesyesDev only — mint tokens server-side in production. See Quickstart.
client.joinRoom(...)yesyesyesyesyesWeb/iOS/Android take a single options object; RN + Flutter take (roomId, options). Returns Room.
client.joinLive({ token, role })yesyesyesyesyesReturns a LiveStream { room, role } on every stack.
room.publishCamera({ resolution, ... })yesyesyesyesyesSimulcast on by default.
room.publishMic({ ... })yesyesyespublishMicrophonepublishMicrophoneWeb/iOS/Android: publishMic. RN + Flutter spell it publishMicrophone — see Known divergences.
room.publishScreen({ audio })yesyesyesyesyesiOS needs a Broadcast Upload Extension; Android needs a mediaProjection foreground service.
room.stopPublishing(trackId)yesyesyesunpublish(source)unpublish(source)Web/iOS/Android: stopPublishing(trackId). RN + Flutter: unpublish(source) — see Known divergences.
Leave the roomleave()leave()leave()close([reason])close([reason])RN + Flutter spell it close([reason]) — see Known divergences.
Device enumerationDeviceManager.list*()DeviceManager.list*()DeviceManager.list*()enumerateDevices()mediaDevicesWeb/iOS/Android: a DeviceManager with listCameras / listMicrophones / listSpeakers. RN exposes one combined enumerateDevices(); Flutter delegates to mediaDevices. facing: front/back is normalised.
Audio session (voice-call mode)yesyesyesyesyesiOS: AVAudioSession; Android: AudioManager + audio focus.
Lifecycle observer (auto-pause camera in background)yesyesyesyesyesWeb uses the Page Visibility API.
Network quality scorer (5-band)yesyesyesyesyesSame thresholds on every platform.
Room reconnect (capped exponential backoff)yesyesyesyesyes
AES-GCM SFrame primitives (out-of-band E2EE)yesyesyesyesyesCross-platform 9-byte wire format.
Frame-transformer integration (in-band E2EE)yesnoyesnonoSee Known divergences — iOS, RN, and Flutter fall back to SRTP + the SFrame primitives.

Event surface

All SDKs deliver the same logical events with an identical semantic payload. Both the carrier and the wire name differ by platform idiom:

  • Carrierroom.on('…', cb) on Web and React Native, AsyncStream<RoomEvent> on iOS, Flow<RoomEvent> on Android, Stream<RoomEvent> on Flutter.
  • Wire name — Web emits kebab-case strings ('participant-joined'), React Native emits camelCase strings ('participantJoined'), and iOS/Android model events as enum cases (.participantJoined / RoomEvent.ParticipantJoined). Apps never hand-type these — they subscribe through typed helpers — so the spelling divergence is cosmetic, but it is a divergence and is recorded here rather than papered over.
Logical eventWebiOSAndroidReact NativeFlutter
connected / disconnectedyesyesyesyesyes
reconnecting / reconnectedyesyesyesyesyes
closedyesyesyesyesyes
participantJoinedyesyesyesyesyes
participantLeftyesyesyesyesyes
trackPublishedyesyesyesyesyes
trackUnpublishedyesyesyesyesyes
trackSubscribedyesyesyesyesyes
trackUnsubscribedyesyesyesyesyes
qualityChangedyesyesyesyesyes

Error-code namespace

Every platform uses the same stable string codes. Apps switch on error.code, never on error.message.

NamespaceExamples
config/*config/invalid, config/missing-api-key
token/*token/invalid, token/expired, token/unauthorized
signaling/*signaling/connect-failed, signaling/closed, signaling/timeout, signaling/protocol
transport/*transport/ice-failed, transport/dtls-failed, transport/unsupported
media/*media/permission-denied, media/device-not-found, media/unsupported, media/constraint-not-satisfied
room/*room/full, room/ended, room/kicked, room/cap-denied
codec/*codec/unsupported
encryption/*encryption/key-missing, encryption/unsupported
recording/*recording/not-permitted, recording/backend-failed
network/*network/unreachable, network/rate-limited
internal/*internal

The Web SDK page lists the full code enum with the matching LevelChatError subclasses.

Known divergences

React Native / Flutter naming dialect

Web, iOS, and Android share one method-naming dialect; React Native and Flutter share another. The capabilities are behaviourally identical — only the public spelling differs. An integrator porting code between stacks needs this table:

CapabilityWeb / iOS / AndroidReact Native / Flutter
Leave the roomroom.leave()room.close([reason])
Stop a publishroom.stopPublishing(id)room.unpublish(source)
Publish microom.publishMic(...)room.publishMicrophone(...)
Device enumerationDeviceManager.list*()enumerateDevices() (RN); mediaDevices (Flutter)

This split predates the parity matrix — RN and Flutter shipped first with the DOM-flavoured names and we kept them for backwards compatibility rather than break early adopters.

Track handles — React Native / Flutter only

React Native and Flutter return mutable LocalTrack / RemoteTrack handles with instance methods (pause(), resume(), close(), RemoteTrack.setPreferredLayer(...)). Web, iOS, and Android instead model a published track as an immutable TrackView value and do track lifecycle through room-level methods (room.stopPublishing(id)) and the SFU's signaling-driven layer selection — there is no track object to call methods on. This is an RN/Flutter-only convenience layer, not a parity gap.

End-to-end encryption — two layers

SFrame primitives (out-of-band) ship on all five SDKs: AES-GCM key wrapping plus an SFrame-style 9-byte header (magic | keyId(2) | counter(6)), with the same wire format across every platform. Apps that want to pre-encrypt frames before publishing call the public EncryptionContext + SFrame.encrypt/decrypt directly.

Frame-transformer integration (in-band) is where platforms diverge:

  • Web — supported on Chromium via RTCRtpSender.transform.
  • Android — shipped via libwebrtc's FrameCryptor. Room.setEncryptionKey(...) rotates a shared key provider that every per-sender and per-receiver cryptor references. AES-GCM, SFrame-compatible wire format with the Web SDK.
  • iOS — upstream-blocked. The public WebRTC.framework distribution only exposes RTCCryptoOptions for SRTP cipher selection; the FrameCryptor API isn't bridged.
  • React Nativereact-native-webrtc does not expose insertable streams. The SDK accepts the e2ee: flag for API parity, logs a warning, and falls back to SRTP plus the AES-GCM SFrame primitives for out-of-band encryption.
  • Flutter — same as React Native.

Native render path

  • Web<video srcObject={track.mediaStreamTrack}>.
  • iOSRTCMTLVideoView from WebRTC.framework, attached via track.nativeTrack.
  • AndroidSurfaceViewRenderer from libwebrtc.
  • React Native<RTCView streamURL={track.nativeTrack.id}>.
  • Flutter<RTCVideoView> fed via an RTCVideoRenderer initialised with the track's parent stream.

How parity is enforced

  1. The Web SDK is the authoritative spec.
  2. Every other SDK mirrors its method names, parameters, and event shape.
  3. The shared error-code namespace lives in @levelchat/shared-types, so a typo on any platform fails to compile against the canonical enum.
  4. Each SDK ships its own unit-test suite, and a shared integration suite runs one fixture against all five SDKs.

If you hit a behavioural difference that isn't documented on this page, it's a bug — email support@levelchat.io.