Skip to content

Repository files navigation

LiveKitNative

Tiny native Swift client SDK for LiveKit, packaged as LiveKitNative.

This repository is an independent, unofficial SDK that aims to mirror the shape of LiveKit Swift SDK v2 while implementing the client logic directly in Swift. The media engine is an internal LiveKitNativeWebRTC target, designed around Apple-native media frameworks and a small LiveKit-focused WebRTC profile.

Why We Needed This Project

iOS and macOS applications that use LiveKit usually inherit a large WebRTC stack through mostly opaque binary dependencies maintained outside the Apple platform toolchain. This project exists to split LiveKit client behavior into smaller Swift-native pieces that can be audited, tested, and reasoned about directly.

The goal is not to build a general-purpose browser WebRTC engine. The goal is to implement the narrow LiveKit-focused profile needed for signaling, publishing and subscribing to media, data channels, and reconnect behavior on top of Apple-native frameworks. That keeps package shape, release size, security surface, debugging experience, and production-readiness boundaries under explicit project control.

This approach is valuable for applications that need a SwiftPM-distributed, LiveKit-focused client SDK without Rust/UniFFI, a heavyweight WebRTC xcframework, or broad media dependencies that are difficult to inspect and ship.

Performance Expectations

This project's core promise is not to be faster than official WebRTC stacks in every situation. Its purpose is to provide a smaller, inspectable, LiveKit focused client surface. Low-level pieces such as protobuf, SDP, STUN, RTP, SRTP, and data-channel framing can run with very low latency in microbenchmarks, but those numbers alone do not define real-time media quality.

Production behavior is shaped more by video encode/decode cost, audio capture and playout, packet loss, jitter, TURN usage, reconnect handling, battery usage, thermal behavior, and multi-participant room load. Performance is therefore not irrelevant; it is just not the only decision point. A real evaluation should measure join latency, CPU, memory, FPS, audio drops, packet loss recovery, reconnect time, battery usage, and thermal behavior on real devices.

Performance issues may be manageable in small, controlled LiveKit scenarios. Applications with larger rooms, weak network conditions, long-running mobile sessions, or strict media-quality expectations should benchmark this package side by side with the official SDK and validate it with end-to-end tests before making a production decision.

Codec Acceleration and Swift Scope

The H.264 and opt-in H.265/HEVC encoders and decoders are intentionally not expected to be pure Swift implementations. Video codec work is one of the few places where "all Swift" would be the wrong production tradeoff: a Swift video codec would be large, hard to validate, difficult to keep power-efficient, and unlikely to match the hardware acceleration, battery behavior, and thermal characteristics provided by Apple platforms.

The intended production model is to keep LiveKit client logic, signaling, state, RTP/SRTP framing, SDP, ICE/STUN, and media pipeline orchestration in Swift, while using Apple-native system frameworks for heavy media primitives. For H.264 and opt-in H.265/HEVC, that means real VideoToolbox-backed encode and decode paths through VTCompressionSession and VTDecompressionSession, with hardware acceleration used when the platform supports it.

Using VideoToolbox does not add a vendored WebRTC binary or a third-party codec library to the package. It links against Apple system frameworks that are already part of the target OS. This preserves the goal of a small, inspectable, Swift-native LiveKit client while avoiding an impractical and power-hungry pure Swift codec implementation.

Production readiness must require the H.264 and H.265 media paths to use VideoToolbox for real frame encode/decode, verify whether the hardware path is active where the OS exposes that signal, and define explicit fallback behavior for devices or profiles where hardware acceleration is unavailable.

H.265/HEVC is implemented as an optional Apple-focused codec profile because Apple platforms expose HEVC through VideoToolbox and can benefit from its better compression efficiency. H.264 remains the default WebRTC compatibility baseline and the simulcast publish path. H.265 publish is opt-in through TrackPublishOptions(videoCodec: .h265), advertises payload type 104 by default, and currently publishes a single video layer; subscriber negotiation and receive/decode paths can accept negotiated H.265 alongside the default H.264 and VP8 receive codecs.

VP9 and AV1 now have opt-in receive-side SDP/RTP scaffolding for advanced profiles, including payload negotiation, frame assembly, and RTCP loss/keyframe feedback. AV1 receive can also decode and render through VideoToolbox when the current device reports hardware AV1 decode support; unsupported devices omit AV1 from subscriber SDP answers and keep the H.265/H.264/VP8 fallback path. VP9 remains receive scaffolding only and should not be assumed to have public VideoToolbox hardware acceleration. Neither advanced codec has a publish encoder in this package.

Production Meeting Bar

This package should not be considered competitive with LiveKitWebRTC for general production video meetings until the hardening bar covers the full call experience, not only the signaling and media packet paths.

Production readiness must include meeting-grade audio capture and playout, echo cancellation, real-device audio-session route changes, Bluetooth behavior, interruptions, background/foreground transitions, jitter buffering, packet-loss recovery, RTCP feedback, live bandwidth estimation, congestion control, applied adaptive quality, bounded frame dropping across real-time media queues, reconnect media recovery, TURN-only operation, and multi-participant behavior.

The package now includes deterministic RTCP receiver-report bandwidth estimation, low/medium/high adaptive video recommendations, and bounded camera publish frame backpressure primitives. Publisher RTCP receiver reports now feed that estimator, and matching H.264/H.265 camera pipelines can apply recommended bitrate/FPS caps to VideoToolbox. Subscriber-side recommendations can also be planned and sent as LiveKit UpdateTrackSettings requests, while observed subscriber RTP/Sender Report state can generate scheduled RTCP Receiver Reports with DLSR timing and REMB bitrate feedback over the secure subscriber RTCP transport, and the RTCP layer can encode/decode TWCC feedback with bounded arrival-time feedback planning. The subscriber RTP path can parse one-byte RTP header extensions, observe the default TWCC transport sequence extension, preserve encrypted SRTCP feedback bytes until decrypt, and dispatch bounded TWCC feedback over the secure subscriber RTCP transport. RoomOptions can opt into automatically applying subscriber adaptive track settings from those estimates, and can set default LiveKit adaptive_stream, subscriber pause, and data-track auto-subscribe signaling preferences, with per-connection ConnectOptions overrides. H.264 camera publishes now advertise deterministic simulcast layer metadata when TrackPublishOptions.simulcast is enabled: low/medium/high layers carry separate SSRC/RID values and no layer is larger than the capture source. The publisher SDP offer mirrors those RID values with a=rid and a=simulcast attributes and advertises video transport-cc for TWCC feedback. The congestion-control gate is now covered by a required strict release proof artifact for live RR/REMB/TWCC and opt-in subscriber adaptive track settings. End-to-end LiveKit validation and real-device validation remain production blockers.

Those requirements are intentionally production blockers. A release can only be called production-ready after automated LiveKit integration tests, weak-network tests, TURN-only tests, long-running soak tests, battery/thermal measurements, and real-device iOS validation pass.

Status

Detailed project status is tracked in docs/STATUS.md.

The 0.7.0 developer-preview scope is complete and main now reports SDK version 0.7.0 while hardening continues. The package contains the SwiftPM layout, single public LiveKitNative product, public API surface, actor-backed room state, signaling URL builder, CI workflow, privacy manifest, DocC landing page, and tests for the pieces that already have behavior.

Signaling and tiny WebRTC groundwork now includes generated LiveKit protobuf signal messages, binary protobuf frame encode/decode, a WebSocket transport abstraction, ping/close lifecycle hooks, a SignalConnection actor, Room.connect JoinResponse handling, remote track publication state updates, participant disconnect and track-unpublish reducers, a post-join signal receive loop for participant updates, refresh tokens, leave messages, subscriber offers, subscriber and publisher trickle candidates, publisher answer routing, speaker updates, connection quality events, stream state events, room updates, subscription permission/response events, subscribed quality events, track-subscribed events, media section requirements, subscribed audio codec updates, data-track publish/unpublish responses, data-track subscriber handle updates, room-moved events, SDP parsing/writing, minimal subscriber answer generation, STUN packet encode/decode, STUN XOR-MAPPED-ADDRESS handling, RFC-vector-tested STUN MESSAGE-INTEGRITY and FINGERPRINT signing/validation, authenticated ICE connectivity-check request sending and authenticated response validation, ICE priority helpers, host candidate construction, UDP STUN transport, connectivity-check request/response handling with bounded transport retries, candidate checklist nomination, SDP ICE candidate parsing, dynamic trickle candidate checklist updates, SDP ICE credential extraction, coordinator-created ICE agents, STUN-backed candidate-pair checking, use-candidate nomination, validate-only nomination handoff, DTLS fingerprint material, DTLS-SRTP protection profile and exporter key/salt splitting, use_srtp extension encode/decode and profile selection, SDP DTLS fingerprint/setup extraction, case-normalized DTLS fingerprint comparison, media-level SDP DTLS fingerprint/setup fallback, peer-connection handshake configuration, typed DTLS-SRTP handshake results, remote fingerprint, role, and protection-profile validation for media sessions, RFC 3711 AES-CM SRTP/SRTCP session key derivation, client/server DTLS-SRTP packet-protection context wiring, RTP packet encode/decode, RTP sequence rollover tracking, SRTP replay-window protection groundwork, SRTP AES-CM payload encryption/decryption using RFC 3711 IV construction, SRTP authentication-tag framing with ROC-aware HMAC-SHA1 validation, full SRTP packet protect/unprotect APIs with replay rejection, SRTCP AES-CM payload protection, SRTCP index/authentication-tag framing, HMAC-SHA1 auth-tag validation, full SRTCP packet protect/unprotect APIs with replay rejection, actor-backed secure RTP/RTCP datagram send/receive wiring with RTCP mux demux, RTP rollover tracking, and nominated ICE-pair guarded construction, exporter-backed secure media session construction, UDP media datagram socket transport with loopback coverage, handshaker-backed media session binder coverage, coordinator-level secure media transport startup from negotiated SDP and nominated ICE pairs, coordinator-run ICE checks that select a pair before secure transport binding, Room-level publisher/subscriber media startup triggering after negotiated SDP and final ICE trickle when a media binder is injected, local publisher/subscriber ICE candidate JSON encoding and trickle/final-trickle signaling, bound local ICE UDP sockets that can gather host candidates and reuse the candidate port for STUN checks and media datagrams, server-provided JoinResponse and ReconnectResponse ICE server configuration mapping onto subscriber and publisher peer connections, STUN UDP server-reflexive candidate discovery from supported stun: ICE server URLs for bound-socket startup, default public Room subscriber/publisher media startup configurations that gather and trickle socket-backed local ICE candidates and use the shared DTLS/SRTP media-data binder, deterministic ICE consent freshness planning, injectable executor primitive, Room-level consent loop for selected pairs after secure-media startup succeeds, deterministic ICE connectivity-check pacing/timeout scheduling with triggered-check priority, STUN 487 role-conflict parsing plus tie-breaker resolution, and ICEAgent integration for queued triggered checks, paced scheduling, role switching, and candidate-pair priority recompute, bounded RTP jitter buffering with gap skip, duplicate/old packet drops, missing-sequence accounting, and sequence-wrap ordering, TURN endpoint parsing from turn:/turns: ICE server URLs with UDP/TCP/TLS intent, default UDP TURN relay allocation from credentialed turn: entries, TURN Allocate request primitives for requested transport, lifetime, realm, nonce, ERROR-CODE, and relayed-address decoding, TURN allocation client request/response validation with one long-term credential 401 challenge retry over the STUN datagram transport abstraction, TURN Refresh request/response validation and deallocation lifetime support, CreatePermission request/response validation with IPv4 XOR-PEER-ADDRESS, ChannelBind request/response validation with TURN channel range checks, and one-shot stale nonce retry for authenticated TURN Allocate/Refresh/CreatePermission/ChannelBind flows, TURN ChannelData frame encode/decode and stream parsing with 4-byte padding, TURN ChannelData relay send/receive over an abstract media datagram transport, deterministic TURN allocation/permission refresh planning, maintenance execution, due action scheduling, relay ICE candidate planning, default UDP TURN relay allocation through the bound Room ICE socket, ChannelData relay bindings for relayed ICE checks/media datagrams, and TURN relay session configuration selection plus UDP/TCP/TLS fallback planning from parsed ICE server endpoints, plus bounded TURN relay session orchestration, deterministic setup-plan execution over abstract transports, TCP/TLS stream TURN setup execution, and relay-only ICE candidate policy for TURN-only validation, peer negotiation state reset across fresh join/reconnect/disconnect boundaries, RTCP sender/receiver report and bounded PLI/NACK subscriber feedback planning, H.264 single-NAL/STAP-A/FU-A packetization, H.265 single-NAL/AP/FU packetization, subscribe-side H.264/H.265 access-unit assembly, native camera track scaffolding, VideoToolbox H.264 and opt-in H.265 encode output, VideoToolbox H.264/H.265 subscriber decode-to-pixel-buffer smoke coverage, H.264/H.265 publish RTP packetization, LiveKit AddTrackRequest construction, TrackPublishedResponse correlation, local video publication state, default camera publish pipeline startup, opt-in subscriber video decode wiring, application-provided subscriber video renderer handoff, UIKit/AppKit sample-buffer VideoView rendering, and mock transport tests.

The audio groundwork now includes native microphone track scaffolding, AVAudioEngine capture and playout adapters, AudioToolbox Opus encode/decode adapters, Opus voice profile defaults, Opus TOC parsing, Opus RTP packetization/depacketization, subscribe-side packet loss accounting, opt-in subscriber Opus decode-to-playout scheduling, LiveKit AddTrackRequest construction for microphone publishes, TrackPublishedResponse correlation, default microphone publish pipeline startup, and local audio publication state.

VP8 subscribe groundwork now includes RTP payload descriptor parsing, PictureID and layer metadata parsing, VP8 frame assembly from single-packet and fragmented RTP frames, keyframe width/height extraction, sequence-gap validation, and a decode-only frame inspector for future renderer integration.

Data channel groundwork now includes WebRTC DCEP open/ack encode/decode, reliable/lossy LiveKit data-channel labels, SCTP stream routing for local data channels, binary PPID envelopes, LiveKit DataPacket user-packet mapping, publish(data:options:) local publish planning, data-track publish/unpublish request/response signaling, data subscription update signaling, queued local data publish flushing through an injected SCTP packet transport after reliable/lossy DCEP acknowledgement, inbound remote DCEP acknowledgement, inbound DataPacket decode to RoomEvent.dataReceived, publisher SDP m=application data-channel negotiation, subscriber-side data-channel receive loop plumbing, and Apple-native DTLS application-data packet transport coverage with deterministic packet fragmentation/reassembly and DTLS-backed fragmented-packet retransmission scheduling. Standards-shaped SCTP packet/chunk coverage now includes INIT, INIT_ACK, COOKIE_ECHO, COOKIE_ACK, DATA, SACK, parameter padding, and CRC32C checksum validation, with a unit-tested Apple-native DTLS application-data association bootstrap that can exchange SCTP DATA chunks, SACK responses, and reassemble fragmented SCTP DATA messages, with duplicate DATA TSN suppression and contiguous cumulative SACK tracking with gap/duplicate reporting, while holding out-of-order DATA until contiguous TSNs can be delivered and tracking outbound DATA TSNs until peer SACK acknowledgement, including gap-triggered retransmission planning for unacknowledged outbound DATA TSN holes. That association sender now also applies deterministic cwnd/rwnd DATA send gating, queues DATA chunks that do not fit the current window, flushes them after peer SACKs open capacity, grows the window during slow start, and reduces the window on gap-SACK fast recovery. The shared media/data binder and Room live-media startup helper can select that association transport in package-internal opt-in tests, and the LiveKit integration harness now validates a default-gated two-client reliable DataPacket publish/receive path over that standards-shaped SCTP association transport. The public default Room path continues to use the existing packet-envelope transport until the remaining standards-compliant SCTP state and complete RFC congestion-control hardening are complete. Data channel recovery now resets LiveKit channels after association restart, reopens DCEP on the next publish, and Room reconnect responses reset injected publisher data channels and receive loops before post-reconnect publish. A shared WebRTC DTLS/SRTP datagram demux and media/data session binder can now keep the persistent Apple-native DTLS application-data transport and SRTP media transport on the same selected ICE datagram path in unit tests, and the public default Room initializer now selects that shared startup binder for live media/data transport construction. Media section requirements and data-track subscriber handles are retained as latest-value Room state and emitted as typed room events.

The active implementation focus is now 1.0.0 hardening: expanding the new opt-in LiveKit-validated Apple-native DTLS-SRTP publisher/subscriber media startup coverage, including selected ICE pair and default media/data session assertions, into full secure RTP/RTCP send/receive validation, live quality-control wiring, real-device video display hardening, default-path DTLS-SCTP association receive-pump integration, LiveKit-validated data-channel recovery, complete RFC SCTP congestion-control hardening, hardening the default-gated live data-packet publish/receive smoke once standards SCTP is fully complete, real-device audio session hardening, integration apps, and size gates.

Current builds expose LiveKitNativeSDK.productionReadiness and LiveKitNativeSDK.assertProductionReady() so applications and release automation can fail fast while the remaining production blockers are still open. Room.mediaDiagnostics() exposes a public runtime snapshot for selected ICE candidate pair, DTLS-SRTP state/profile, data-channel state, codec path, RTCP-derived counters, and active published video layers without enabling decode, playout, audio-session, or adaptive behavior by default. LocalParticipant.setMetadata, setName, and setAttributes now send LiveKit UpdateParticipantMetadata requests and map server RequestResponse failures to typed SDK errors. Room.disconnect() also clears remote participant state, sends a client-initiated LeaveRequest, and emits cleanup lifecycle events. Refreshed signal tokens are retained for subsequent resume reconnects. Basic signal resume/full-reconnect and JoinResponse.alternative_url retry are unit-tested. JoinResponse and ReconnectResponse ICE server lists are applied to both subscriber and publisher peer connection configurations, injected bound-socket media startup can use supported stun: UDP URLs to add server-reflexive local candidates while reusing the same local UDP socket, allocate supported UDP TURN relay candidates through the same bound socket, and route selected relay pairs through TURN ChannelData bindings for checks and media datagrams. Fresh joins/reconnect responses clear stale peer negotiation state and regenerate local ICE credentials before applying new signaling configuration, resume reconnects rebuild retained subscriber answer / publisher offer SDP with fresh local ICE credentials, send local ICE trickle/final-trickle when media startup is configured, and send LiveKit SyncState for current media subscription preferences, disabled subscribed tracks, local media/data publications, and the latest negotiated subscriber answer / publisher offer state in unit tests, preserve publisher offer track state so later publishes after resume do not drop existing local media, and room-connected publish(videoTrack:) / publish(audioTrack:) calls send LiveKit AddTrackRequest messages and wait for matching TrackPublishedResponse acknowledgements before recording local publications, while matching RequestResponse failures are mapped to typed SDK errors instead of timing out. LocalParticipant.setTrackMuted(publication:muted:) now sends LiveKit MuteTrackRequest messages for local publications and waits for matching RequestResponse acknowledgements before applying local mute state. Room-connected local track unpublish and camera/microphone disable flows also send a muted MuteTrackRequest and wait for matching RequestResponse acknowledgements before removing the local publication, and multi-track unpublish sends a refreshed publisher offer for the remaining local media. Server-initiated TrackUnpublishedResponse messages for local media publications clear local publication and cached publisher offer reconnect state so resume reconnects and later publisher offers do not replay removed tracks. When the last local media track is unpublished, the injected publisher media transport is closed and its startup state is cleared so stale SRTP transports cannot keep sending. Room can also send publisher RTP packets through the started injected secure media transport in tests, establishing the handoff point now used by the default camera/microphone capture and encode pipelines. A stateful publisher RTP bridge keeps Opus, H.264, and opt-in H.265 packetizer state across packets/frames before handing RTP packets to that sink, and Room stores publisher audio/video RTP sender state by published SID and local CID so unpublish removes only the matching sender while preserving remaining local sender state for resume reconnect. Native camera-backed tracks can encode H.264 by default or opt into single-layer H.265 through VideoToolbox, native microphone-backed tracks can encode Opus through AudioToolbox without a vendored libopus, and those packets flow into the stored publisher RTP/SRTP send path after publisher media startup. Registered publisher and subscriber RTCP handlers can also receive decoded inbound RTCP from the injected secure media transport, and the default subscriber RTP receive loop now feeds protected RTP through jitter buffering, H.264/H.265/Opus packet assembly, and bounded NACK/PLI feedback dispatch. RoomOptions can opt into subscriber Opus decode-to-AVAudioPlayerNode playout scheduling from that receive loop. A deterministic RTCP receiver-report bandwidth estimator now maps loss into adaptive video quality recommendations, publisher RTCP receiver reports feed that estimator even without an external RTCP handler, matching H.264/H.265 camera pipelines can apply recommended bitrate/FPS caps to VideoToolbox, and the camera publish pipeline applies bounded frame backpressure/drop/restart control before VideoToolbox encode work is queued. Subscriber-side recommendations can be turned into LiveKit UpdateTrackSettings requests for low/medium/high/off reception, public subscriber video-quality presets cover manual selection, and publisher active layer availability can be signaled with UpdateVideoLayers. The subscriber RTP receive pipeline now tracks RTP/Sender Report state to emit cadenced RTCP Receiver Reports with DLSR timing and REMB bitrate feedback through the subscriber secure RTCP path; RoomOptions can also opt into deduplicated automatic UpdateTrackSettings dispatch for subscribed remote video tracks from those estimates. Connection setup can now advertise LiveKit adaptive stream, subscriber pause, and data-track auto-subscribe preferences through RoomOptions defaults or per-call ConnectOptions. WebRTC power presets are opt-in through RoomOptions.webRTCPowerMode; leaving the mode nil preserves existing SDK behavior. .batterySaver starts camera helpers at 640x360@15, disables camera simulcast, uses 24 kbps Opus with DTX, requests low subscriber video, and can be paired with automatic Low Power Mode and thermal adaptation. Custom policies can also cap the H.264 publisher bitrate for low-resolution runs:

let room = Room(
    options: RoomOptions(
        webRTCPowerMode: .batterySaver,
        webRTCPowerAdaptation: .automatic
    )
)
try await room.localParticipant.setCamera(enabled: true)
try await room.localParticipant.setMicrophone(enabled: true)

Use a custom policy when an app needs a different mix of publisher and subscriber power tradeoffs:

let customPolicy = WebRTCPowerPolicy(
    cameraCaptureOptions: CameraCaptureOptions(width: 960, height: 540, framesPerSecond: 24),
    cameraSimulcast: false,
    h264Bitrate: 500_000,
    opusBitrate: 28_000,
    initialSubscribedVideoQuality: .medium,
    decodeSubscriberVideo: false
)
let room = Room(options: RoomOptions(webRTCPowerMode: .custom(customPolicy)))

Server-initiated mute messages update local/remote track publication state and emit RoomEvent.trackMuteChanged. Room-level media subscription and subscribed track settings requests are available through Room.updateSubscription and Room.updateTrackSettings; publisher track subscription permissions are available through LocalParticipant.setTrackSubscriptionPermissions. Local publisher audio/video track update signaling is exposed through LocalParticipant.updateAudioTrack and LocalParticipant.updateVideoTrack, with matching RequestResponse acknowledgement handling. After TrackPublishedResponse, publisher publish flows now generate a send-only SDP offer and send it as SignalRequest.offer for the publisher negotiation path. Publisher answer routing, data-track control event mapping, data-track publish/unpublish request flows, and server/SFU media/data-track unpublish cleanup for reconnect state, injected publisher transport teardown, consent-freshness execution primitives plus the media-startup consent loop, RTP jitter-buffer primitives, default socket-backed Room ICE trickle and UDP TURN relay startup, queued data publish flush after data-channel DCEP ack, inbound data-channel event plumbing, DTLS application-data packet transport, data-channel fragment/reassembly and retransmission scheduling, data-channel recovery reset after reconnect, ICEAgent triggered-check pacing/role-conflict integration, shared DTLS/SRTP media-data demux coverage, VideoToolbox H.264 encode smoke coverage, AudioToolbox Opus encode/decode smoke coverage, subscriber RTP jitter-buffer/feedback behavior, deterministic receiver-report bandwidth estimation, publisher RTCP report ingestion, adaptive video quality recommendations, VideoToolbox bitrate/FPS recommendation application, camera publish backpressure/drop/restart control, subscriber adaptive track-settings planning, manual subscriber video-quality preset signaling, publisher active video-layer UpdateVideoLayers signaling, and opt-in automatic signaling, subscriber Receiver Report generation/cadence/sending, REMB packet/planner/sending, RTP header-extension parsing, TWCC observation/packet/planner/send-loop primitives, opt-in subscriber Opus playout scheduling, opt-in subscriber H.264/H.265 VideoToolbox decode-to-pixel-buffer scheduling, public SubscriberVideoFrameRenderer handoff, UIKit/AppKit VideoView sample-buffer display, opt-in voice-chat audio-session configuration and deterministic interruption/route recovery through RoomOptions.automaticallyConfigureAudioSession, and matching RequestResponse failure mapping, and default Room TURN relay maintenance over selected relay pairs are unit-tested, while standards-compliant live SCTP association behavior, media recovery, real-device soak validation, and end-to-end LiveKit hardening are still open. H.264 simulcast media production, TURN-only relay fallback, device-validation, and device-audio-video are closed and remain covered by required strict release proof artifacts.

Benchmarks

Release-mode microbenchmarks are available through:

swift run -c release LiveKitNativeBenchmarks

The current local run measures low-level signaling, SDP, STUN, RTP, SRTP/SRTCP replay/authentication tracking, SRTP/SRTCP AES-CM payload protection, full SRTP/SRTCP packet protect/unprotect paths, DTLS-SRTP exporter splitting and session-protection context, RTCP feedback, H.264, H.265, VP8, Opus RTP scaffolding, and SCTP data-channel message paths. On this machine, the latest release-readiness smoke medians include protobuf signal roundtrip at 5.767 us/op, subscriber SDP answer generation at 66.515 us/op, STUN binding roundtrip at 1.664 us/op, RTP encode/decode at 0.582 us/op, SRTP replay protection at 0.045 us/op, SRTP authenticated roundtrip at 9.519 us/op, SRTP AES-CM payload roundtrip at 56.026 us/op, full SRTP packet protect/unprotect at 62.460 us/op, RTCP feedback roundtrip at 1.702 us/op, SRTCP packet/replay roundtrip at 0.766 us/op, SRTCP authenticated roundtrip at 6.293 us/op, full SRTCP packet protect/unprotect at 8.432 us/op, DTLS-SRTP exporter split at 0.295 us/op, DTLS-SRTP session protect/unprotect at 71.291 us/op, H.264 packetize/depacketize at 2.317 us/op, H.265 packetize/depacketize at 2.527 us/op, VP8 payload depacketize at 0.132 us/op, Opus RTP packetize/depacketize at 0.034 us/op, and SCTP DCEP open/ack roundtrip at 0.714 us/op.

Official LiveKit Swift SDK/WebRTC baseline numbers are accepted as an external CSV so this package does not reintroduce the forbidden binary WebRTC dependency. See docs/BENCHMARKS.md for the full methodology, current results, and comparison workflow.

Release Gates

The repository now has a non-strict release-readiness gate for CI and a strict gate for real 1.0.0 tagging:

scripts/check_release_readiness.sh
scripts/check_production_release_gate.sh

The current production closure sequence is tracked in docs/PRODUCTION_READINESS_PLAN.md.

The manual GitHub Actions workflow at .github/workflows/production-release.yml is the CI entry point for that strict gate. It is pinned to a self-hosted macOS/iOS-device runner and expects a proof directory containing one <gate>.proof manifest per release gate. The workflow uses scripts/write_release_proof_env.sh to validate those manifests and generate the environment file exporting every LIVEKIT_NATIVE_*_PROOF path from the real LiveKit, TURN, impairment, device, soak, and Apple-native crypto jobs. Physical-device jobs can use scripts/write_device_harness_gate_report.sh to derive the device gate reports from the Examples/DeviceHarness diagnostics log before writing proof manifests.

The default gate checks package shape, forbidden runtime dependencies, local external crypto wrapper/package/link validation, device harness shape, unit/integration opt-in tests, benchmark smoke, and the compressed release binary size proxy. The production gate first validates the full proof bundle with scripts/check_release_proof_bundle.sh, then runs the strict release-readiness gate with REQUIRE_PRODUCTION_READY=1. The strict gate additionally requires LiveKitNativeSDK.productionReadiness.status == .productionReady, no blockers, tests, benchmarks, the size gate, Apple platform builds, and configured LiveKit integration variables. It also requires the general meeting v1 validation flags for media E2E, H.264 simulcast, TURN-only, weak-network, TWCC/REMB congestion control, reconnect, data-channel, multi-participant, real-device validation, real-device audio/video, soak, and Apple-native crypto/DTLS-SRTP validation, plus matching non-empty proof artifact files for those external validations, so a production tag cannot pass with any release gate disabled or only papered over with environment flags. Strict Apple platform builds include macOS and iOS Simulator builds, macOS docbuild, and an iOS generic archive. The archive path defaults to .build/release/LiveKitNative-iOS.xcarchive and can be overridden with LIVEKIT_NATIVE_IOS_ARCHIVE_PATH. Strict external proof artifacts are supplied through LIVEKIT_NATIVE_MEDIA_E2E_PROOF, LIVEKIT_NATIVE_SIMULCAST_PROOF, LIVEKIT_NATIVE_TURN_ONLY_PROOF, LIVEKIT_NATIVE_WEAK_NETWORK_PROOF, LIVEKIT_NATIVE_CONGESTION_CONTROL_PROOF, LIVEKIT_NATIVE_RECONNECT_PROOF, LIVEKIT_NATIVE_DATA_CHANNEL_PROOF, LIVEKIT_NATIVE_MULTI_PARTICIPANT_PROOF, LIVEKIT_NATIVE_DEVICE_VALIDATION_PROOF, LIVEKIT_NATIVE_DEVICE_AUDIO_VIDEO_PROOF, and LIVEKIT_NATIVE_DEVICE_SOAK_PROOF, and LIVEKIT_NATIVE_APPLE_NATIVE_CRYPTO_DTLS_SRTP_PROOF; each proof file must include matching gate: ..., status: passed, date: ..., runner: ..., command: ..., and artifact: ... lines, and its command must include that gate's strict opt-in token such as LIVEKIT_NATIVE_RUN_SIMULCAST=1 or LIVEKIT_NATIVE_VALIDATE_APPLE_NATIVE_CRYPTO_DTLS_SRTP=1. See docs/RELEASE_PROOFS.md for the proof manifest format and the standalone scripts/check_release_proof_bundle.sh validator. That strict gate intentionally fails today while device soak remains open. Completed gates still require their matching proof artifacts on every production release run.

Opt-in LiveKit integration tests are disabled unless a local or cloud LiveKit server is explicitly configured:

When running the local Docker server for the loopback harness, make LiveKit advertise the host loopback address for ICE:

docker run --rm --name livekit-native-dev \
  -p 7880:7880 -p 7881:7881 -p 7882:7882/udp \
  livekit/livekit-server:v1.12.0 \
  --dev --bind 0.0.0.0 --node-ip 127.0.0.1 --udp-port 7882
LIVEKIT_NATIVE_RUN_INTEGRATION=1 \
LIVEKIT_NATIVE_LIVEKIT_URL=ws://127.0.0.1:7880 \
LIVEKIT_NATIVE_API_KEY=devkey \
LIVEKIT_NATIVE_API_SECRET=secret \
swift test --filter LiveKitNativeIntegrationTests --jobs 1

The harness generates per-run room names with an lknative- prefix and short-lived room-scoped participant tokens. It currently covers one-client connect/disconnect, two-client participant join/leave with media subscription state and cleanup, and live Apple-native DTLS-SRTP publisher/subscriber media startup on the socket-backed Room media path with one publisher H.264 RTP send attempt. The live data-track subscriber-handle and standards-shaped SCTP data-packet publish/receive tests are included in the default integration gate whenever LIVEKIT_NATIVE_RUN_INTEGRATION=1 is set. Strict production release mode now requires those integration variables so the future productionReady marker cannot pass while live tests are silently skipped. TURN-only media startup validation is additionally gated by LIVEKIT_NATIVE_RUN_TURN_ONLY=1 and requires a LiveKit server that advertises credentialed TURN relay ICE servers; the test uses RoomOptions.iceTransportPolicy = .relay to suppress host and server-reflexive candidates.

Requirements

  • iOS 14+
  • macOS 11+
  • Swift 6
  • Xcode 16+

Installation

.package(
    url: "https://github.com/MarlonJD/livekit-webrtc-native-swift.git",
    from: "1.0.0"
)

Use the 1.0.0 requirement only after the readiness gate reports productionReady. Until then, main should be treated as a developer preview.

.product(name: "LiveKitNative", package: "livekit-webrtc-native-swift")

License

LiveKitNative is licensed under the GNU Lesser General Public License version 3.0 or later. See LICENSE, COPYING, and COPYING.LESSER.

Usage Shape

import LiveKitNative

let room = Room()

Task {
    for await event in room.events {
        print(event)
    }
}

try await room.connect(
    url: URL(string: "wss://example.livekit.cloud")!,
    token: token
)

let cameraTrack = try LocalVideoTrack.createCameraTrack()
try await room.localParticipant.publish(
    videoTrack: cameraTrack,
    options: TrackPublishOptions(videoCodec: .h265)
)

Design Notes

  • WebSocket signaling uses /rtc and binary protobuf frames.
  • The client protocol target is protocol=9.
  • LiveKitNativeWebRTC is an internal package target, not a public SwiftPM product and not a separate semver surface.
  • The default Room media path publishes H.264 video unless the caller opts into H.265 with TrackPublishOptions(videoCodec: .h265), receives H.264, H.265, and VP8 video, and uses Opus for audio.
  • H.265/HEVC is an optional Apple-focused codec profile; it is currently single-layer for publish, while H.264 remains the simulcast baseline.
  • VP9 and AV1 are opt-in advanced receive codecs; AV1 can decode/render through hardware VideoToolbox when available, while VP9 pixel decode/render and advanced codec publish encode remain future work.
  • The WebRTC engine is planned around AVFoundation, AudioToolbox, VideoToolbox, CoreMedia, Security, Network, and CryptoKit.
  • LiveKit protocol sources are pinned to 765a80e4298e376593859c3f11cf748c725f68f9. Run scripts/generate_protocol_sources.sh to regenerate vendored Swift protobuf files when the pin changes. The current vendored set is the client signaling subset needed for /rtc room join and media control.
  • Room owns a RoomActor for core state updates.
  • RoomEvent is available as an AsyncStream, with a delegate hook for UIKit and AppKit style integrations.
  • Production readiness is explicit through LiveKitNativeSDK.productionReadiness and LiveKitNativeSDK.assertProductionReady().
  • Logging can be configured through LiveKitNativeLogging.configure.
  • UIKit and AppKit VideoView classes can render SubscriberVideoFrame CVPixelBuffer output through AVSampleBufferDisplayLayer, with an opt-in runtime debug overlay for soak diagnostics. SwiftUI components are out of scope for v1 in this package.
  • The repository intentionally contains no Rust toolchain, no .rs sources, no UniFFI bridge dependency, no LiveKitWebRTC.xcframework, no BoringSSL, no OpenSSL C wrapper or XCFramework, no libopus, and no libvpx. DTLS-SRTP uses the Apple-native backend under production hardening instead of the forbidden WebRTC/BoringSSL/OpenSSL runtime paths.

Milestones

Tag Scope
0.1.0 Package cleanup, signaling, SDP parser/writer, room join
0.2.0 ICE/STUN, DTLS-SRTP, H.264 subscribe, video render
0.3.0 H.264 camera publish through VideoToolbox
0.4.0 Swift Opus audio publish/subscribe
0.5.0 Swift VP8 decode-only subscribe path
0.6.0 SCTP data channel and LiveKit data packets
0.7.0 Hardware-gated AV1 receive decode/render
1.0.0 Reconnect, TURN hardening, live quality control, sample apps, size gates

Compatibility Matrix

LiveKitNative LiveKit protocol Swift Xcode Platforms
0.7.0 9 6 16+ iOS 14+, macOS 11+
main 9 6 16+ iOS 14+, macOS 11+

About

Swift-native LiveKit WebRTC SDK for iOS and macOS with SwiftPM, RTP/SRTP, ICE/STUN, H.264, Opus and Apple VideoToolbox support.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages