Livekit.Rtc.Dotnet 0.1.7

dotnet add package Livekit.Rtc.Dotnet --version 0.1.7
                    
NuGet\Install-Package Livekit.Rtc.Dotnet -Version 0.1.7
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Livekit.Rtc.Dotnet" Version="0.1.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Livekit.Rtc.Dotnet" Version="0.1.7" />
                    
Directory.Packages.props
<PackageReference Include="Livekit.Rtc.Dotnet" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Livekit.Rtc.Dotnet --version 0.1.7
                    
#r "nuget: Livekit.Rtc.Dotnet, 0.1.7"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Livekit.Rtc.Dotnet@0.1.7
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Livekit.Rtc.Dotnet&version=0.1.7
                    
Install as a Cake Addin
#tool nuget:?package=Livekit.Rtc.Dotnet&version=0.1.7
                    
Install as a Cake Tool

NuGet Version NuGet Downloads GitHub Actions Workflow Status License badge

Livekit.Rtc.Dotnet

.NET SDK to integrate LiveKit's real-time video, audio, and data capabilities into your .NET applications using WebRTC. Access the same powerful API offered by client SDKs, but directly from your .NET server: connect to rooms as a participant, publish and subscribe to audio/video tracks, send and receive data messages, perform RPC calls, and more.

This SDK does not provide Server APIs to manage LiveKit resources such as Rooms, Egress, Ingress or SIP. If you want to manage LiveKit APIs from your .NET backend, please check Livekit.Server.Sdk.Dotnet instead.

Design principles

This library is crafted based on three core principles:

  1. Livekit.Rtc.Dotnet uses the C# auto-generated protobuf implementation of LiveKit RTC protocol. This ensures compatibility and ease of maintenance as the API evolves.
  2. Livekit.Rtc.Dotnet uses C# FFI bindings to the LiveKit Rust SDK. This allows us to leverage existing, well-tested code for WebRTC functionality that is already in use in other LiveKit SDKs. It brings the benefits of native performance for any OS platform while providing a common .NET interface. The library includes precompiled native binaries for Windows, Linux, and macOS, for both x64 and ARM64 architectures.
  3. Livekit.Rtc.Dotnet maintains full feature parity with other LiveKit RTC SDKs for the server side, especially the two more mature ones: Node.js and Python.

Features

Real-Time Media

Publish and subscribe to audio/video tracks with full programmatic control. Process frames in real-time, mix audio sources, publish pre-encoded video without re-encoding it, and build custom media pipelines. Perfect for server-side processing.

Data & Communication

Send reliable or lossy data messages, stream application frames over data tracks, implement RPC request-response patterns, and build real-time chat with topic-based channels. Full control over data flow between participants.

Room & Participant Management

Active speaker detection, transcription support, connection quality monitoring, permission changes, and 30+ event types for comprehensive room state management.

Security & Encryption

Built-in end-to-end encryption (E2EE) with AES-GCM, key rotation, and per-participant key management. Keep your sensitive communications secure, also in your .NET server.

Production-Ready

Automatic reconnection, multi-room support, isolated contexts, and battle-tested WebRTC implementation from the LiveKit Rust SDK.

Installation

dotnet add package Livekit.Rtc.Dotnet

Usage

Basic Connection

using LiveKit.Rtc;

var room = new Room();
await room.ConnectAsync("wss://your-livekit-server.com", "your-access-token",
    new RoomOptions { AutoSubscribe = true });

Console.WriteLine($"Connected to {room.Name}");
await room.DisconnectAsync();

Publishing Audio

var audioSource = new AudioSource(48000, 1); // 48kHz, mono
var audioTrack = LocalAudioTrack.Create("my-audio", audioSource);
var publication = await room.LocalParticipant!.PublishTrackAsync(audioTrack);

// Capture audio frames
var audioData = new short[480]; // 10ms at 48kHz
var audioFrame = new AudioFrame(audioData, 48000, 1, 480);
await audioSource.CaptureFrameAsync(audioFrame);

Publishing Video

var videoSource = new VideoSource(1920, 1080);
var videoTrack = LocalVideoTrack.Create("my-video", videoSource);
await room.LocalParticipant!.PublishTrackAsync(videoTrack, new TrackPublishOptions
{
    VideoCodec = Proto.VideoCodec.H264, // VP8 (default), H264, AV1, VP9...
    Simulcast = true,
    Source = Proto.TrackSource.SourceCamera,
});

// Capture video frames
var videoData = new byte[1920 * 1080 * 4]; // RGBA
var videoFrame = new VideoFrame(1920, 1080, Proto.VideoBufferType.Rgba, videoData);
videoSource.CaptureFrame(videoFrame);

Receiving Media

room.TrackSubscribed += async (sender, e) => {
    if (e.Track is RemoteVideoTrack videoTrack) {
        using var videoStream = new VideoStream(videoTrack);
        await foreach (var frame in videoStream.WithCancellation(cts.Token)) {
            // Process video frame
            Console.WriteLine($"Frame: {frame.Frame.Width}x{frame.Frame.Height}");
        }
    }
};

Event Handling

room.ParticipantConnected += (sender, participant) => {
    Console.WriteLine($"{participant.Identity} joined");
};

room.ActiveSpeakersChanged += (sender, e) => {
    Console.WriteLine($"Active speakers: {e.Speakers.Count}");
};

room.DataReceived += (sender, e) => {
    var message = Encoding.UTF8.GetString(e.Data);
    Console.WriteLine($"Data from {e.Participant.Identity}: {message}");
};

room.ParticipantPermissionsChanged += (sender, e) => {
    Console.WriteLine($"{e.Participant.Identity} can publish: {e.Permissions?.CanPublish}");
};

room.LocalTrackRepublished += (sender, e) => {
    // After a full reconnect the publication keeps its object but gets a new SID
    Console.WriteLine($"{e.PreviousSid} is now {e.Publication.Sid}");
};

room.ParticipantDisconnected += (sender, participant) => {
    Console.WriteLine($"{participant.Identity} left: {participant.DisconnectReason}");
};

room.Disconnected += (sender, reason) => {
    // ClientInitiated, RoomDeleted, DuplicateIdentity, ParticipantRemoved...
    // LocalParticipant and RemoteParticipants are still readable here
    Console.WriteLine($"Disconnected: {reason}");
};

End-to-End Encryption

var key = new byte[32]; // AES-256 key
var options = new RoomOptions {
    E2EE = new E2EEOptions {
        KeyProviderOptions = new KeyProviderOptions { SharedKey = key }
    }
};

await room.ConnectAsync(url, token, options);
// All tracks automatically encrypted/decrypted

RPC Communication

// Register RPC method handler
room.LocalParticipant!.RegisterRpcMethod("greet", async (data) => {
    return $"Hello, {data.CallerIdentity}!";
});

// Call RPC method on another participant
var response = await room.LocalParticipant!.PerformRpcAsync(
    destinationIdentity: "other-participant",
    method: "greet",
    payload: "{}",
    responseTimeout: 5
);

Text and Byte Streams

// Receiver: handlers run on the thread pool, so they may also read synchronously
room.RegisterTextStreamHandler("chat", async (reader, senderIdentity) => {
    try {
        var text = await reader.ReadAllAsync();
        Console.WriteLine($"{senderIdentity}: {text}");
    } catch (StreamException) {
        // The sender or the room disconnected before the stream was closed
    }
});

// Sender: multi-byte characters are never split across chunks
await using var writer = await room.LocalParticipant!.StreamTextAsync(topic: "chat");
await writer.WriteAsync("Hello 🎉"); // closed (trailer sent) when disposed, or with CloseAsync()

Data Tracks

Data tracks stream application frames (telemetry, sensor data, game state...) from one publisher to any number of subscribers. Unlike data messages they are a named, subscribable track; frames are not buffered for subscribers that join later.

// Publisher
var dataTrack = await room.LocalParticipant!.PublishDataTrackAsync("telemetry");
dataTrack.TryPush(new DataTrackFrame(payload, userTimestamp: 42)); // PushFrameException if it cannot be sent
dataTrack.Unpublish();

// Subscriber
room.DataTrackPublished += async (sender, remoteTrack) => {
    Console.WriteLine($"{remoteTrack.PublisherIdentity} published {remoteTrack.Info.Name}");
    using var stream = remoteTrack.Subscribe();
    await foreach (var frame in stream) { // ends when the track is unpublished
        Console.WriteLine($"{frame.Payload.Length} bytes, timestamp {frame.UserTimestamp}");
    }
};
room.DataTrackUnpublished += (sender, e) => Console.WriteLine($"{e.Sid} unpublished");

Publishing Pre-encoded Video

Publish video that is already encoded (for example H.264 from a hardware encoder or a demuxed file) without decoding and re-encoding it:

using var source = new EncodedVideoSource(1280, 720);
var track = LocalVideoTrack.Create("camera", source);
await room.LocalParticipant!.PublishTrackAsync(track, new TrackPublishOptions {
    VideoCodec = Proto.VideoCodec.H264,
    VideoEncoder = Proto.VideoEncoderBackend.EncoderBackendPreEncoded,
    Simulcast = false,
});

// Feed complete access units at the playback rate (Annex B for H.264/H.265)
source.CaptureFrame(new EncodedVideoFrame(
    accessUnit, 1280, 720, Proto.VideoCodec.H264, Proto.EncodedFrameType.EncodedFrameKey, timestampUs));

// Forward encoder feedback (key frame requests, bitrate targets) to your encoder
var feedback = source.TakeFeedback();
if (feedback.KeyframeRequested) encoder.RequestKeyFrame();

Controlling Remote Tracks

room.TrackSubscribed += (sender, e) => {
    var publication = e.Publication;
    publication.SetVideoQuality(Proto.VideoQuality.Low); // simulcast layer (video only)
    publication.SetVideoDimensions(640, 360);            // let the server pick the layer for this size
    publication.SetEnabled(false);                       // pause receiving without unsubscribing
};

Simulating Reconnections

Room.SimulateScenarioAsync forces, from a test, the reconnection paths that are otherwise only reachable through network failures. The task completes once the SDK has started the scenario; the reconnection itself is observed through the room events.

// Full reconnect: the SDK rebuilds its session and republishes the local tracks.
// Raises Reconnecting, then one LocalTrackRepublished per local track, then Reconnected.
await room.SimulateScenarioAsync(Proto.SimulateScenarioKind.SimulateFullReconnect);

// Resume: only the signaling connection is re-established and the publications are kept.
await room.SimulateScenarioAsync(Proto.SimulateScenarioKind.SimulateSignalReconnect);

See Proto.SimulateScenarioKind for the other variants (server-initiated leave, node failure, migration...).

Example apps

At LivekitRtc.Example you can find several example applications demonstrating the usage of LiveKit.Rtc.Dotnet SDK. See the examples README for more details.

For developers of the SDK

Clone repository

Make sure to clone with submodule option:

git clone --recurse-submodules https://github.com/pabloFuente/livekit-server-sdk-dotnet.git

Run tests

All E2E tests automatically launch necessary services as Docker containers with Testcontainers.

dotnet test LivekitRtc.Tests

Building from source

Prerequisites:

  • .NET 10.0 SDK (global.json pins it; the package itself targets netstandard2.1)
  • protoc
  • Rust (only if also building native libraries from source)

1. Get native FFI libraries

Option 1: download pre-compiled binaries from LiveKit Rust SDK releases

The easiest way to get the latest FFI binaries without building from source:

# From LivekitRtc directory
./download_ffi.sh          # Downloads latest version
./download_ffi.sh 0.12.44  # Or specify a version

This downloads pre-compiled binaries from LiveKit Rust SDK releases for all supported platforms (Windows, Linux, macOS on x64 and ARM64).

Option 2: building native libraries from source

If you want to modify the Rust/FFI code or use unreleased features. You will need:

  • .NET 10.0 SDK (global.json pins it; the package itself targets netstandard2.1)
  • Rust installed with rustup. No specific version needs to be installed beforehand: rust-sdks pins its toolchain in file rust-toolchain.toml (e.g. Rust 1.97.1 for livekit-ffi/v0.13.4), and rustup installs it automatically the first time cargo runs inside rust-sdks.
  • protoc
  • The platform-specific build dependencies listed in the rust-sdks README (e.g. clang 21+ and some system libraries on Linux).

The Rust build needs the nested submodules of rust-sdks (livekit-protocol/protocol and yuv-sys/libyuv). Option --checkout makes sure they are checked out, even if they have been disabled locally (see the note below):

# From the root directory (livekit-server-sdk-dotnet/.)
git submodule update --init --recursive --checkout LivekitRtc/rust-sdks
cd LivekitRtc/rust-sdks/livekit-ffi
cargo build --release

The native library is generated in the workspace target folder LivekitRtc/rust-sdks/target/release/ (liblivekit_ffi.so on Linux, liblivekit_ffi.dylib on macOS, livekit_ffi.dll on Windows). Copy it to the appropriate runtimes/{rid}/native/ folder. For example, on Linux x64:

# From LivekitRtc directory
cp rust-sdks/target/release/liblivekit_ffi.so runtimes/linux-x64/native/

Take into account that Rust builds for the host platform by default, so you may need to cross-compile for other platforms.

2. Generate Protocol Buffers

# From the root directory (livekit-server-sdk-dotnet/.)
./generate_proto.sh

3. Build the .NET SDK

# From LivekitRtc directory
dotnet build

Upgrade version of rust-sdks

The /sync-rust-sdks skill runs this whole procedure, including exposing the new FFI features, and the sync-rust-sdks workflow runs it on every new GA livekit-ffi release and opens a pull request. See AI coding agents.

To upgrade the version of the rust-sdks Git submodule:

cd LivekitRtc/rust-sdks
git fetch --all
git checkout <VERSION_TAG> # e.g. git checkout livekit-ffi/v0.12.76
cd ../..
./generate_proto.sh
dotnet pack -c Debug -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
./download_ffi.sh <VERSION_TAG> # e.g. ./download_ffi.sh livekit-ffi/v0.12.76

Then commit:

git commit -m "Update rust-sdks to <VERSION>"
git push

Perform release

  1. Create a commit like this updating the version.
  2. Make sure to run script ./build_local.sh to properly format all files with csharpier. Commit any changes in the repo after the script finishes.
  3. Create a new release in GitHub with:
    • Release title rtc-X.Y.Z
    • A new tag rtc-X.Y.Z
    • Description with relevant changes (e.g., Update to LiveKit Rust SDK vA.B.C)

The rtc- prefix in the tag is mandatory for the publish workflow to identify that it is a release for package Livekit.Rtc.Dotnet specifically.

After creating the release, workflow publish.yml will automatically publish the new version to NuGet and will perform the necessary post-release tasks.

AI coding agents

CLAUDE.md briefs AI coding agents on this repository, including how the FFI layer works. For this package the relevant tool is the /sync-rust-sdks Claude Code skill (SKILL.md), which carries out the rust-sdks upgrade end to end:

  1. Start from a clean working tree with protoc 36.2, protoc-gen-twirpcs v1.1.0 (generate_proto.sh also regenerates the server package), gh, curl and unzip installed, and Docker running for the e2e tests.

  2. Open Claude Code at the repository root and run the skill with the target livekit-ffi version, or without one to take the latest release:

    /sync-rust-sdks 0.13.4
    
  3. The skill checks that the GitHub release carries the six native binaries, moves the submodule, reads the upstream changelog and the FFI proto diff, regenerates LivekitRtc/Proto with ./generate_proto.sh, downloads the binaries with download_ffi.sh (it asks before overwriting them), fixes the build and runs the parity guards. It then stops at a checkpoint listing the relevant upstream changes, the proto members that forced fixes, and every new FFI request, callback, room event and option field with a proposal to expose it (with its Node and Python name) or to ignore it. Strike what you do not want and confirm.

  4. It exposes the confirmed features following the Node and Python RTC SDKs, with their mapping or e2e tests, README sections and changelog bullets under ## Unreleased, then runs the no-Docker tests and the e2e classes for the areas the upstream diff touched, and builds and checks the API reference.

  5. It ends with the test results (any FFI Panic or [WARN] RoomEvent line means the binaries and the protos disagree), the open decisions and the suggested commits, and never commits. Review the diff, commit as Update "rust-sdks" to livekit-ffi/vX.Y.Z (the submodule pointer, LivekitRtc/Proto, the six binaries and any generate_proto.sh change) and Adapt LivekitRtc to livekit-ffi/vX.Y.Z (everything else), and follow Perform release when ready.

The sync-rust-sdks workflow runs the same skill unattended: every day it looks for a GA livekit-ffi release newer than the submodule whose six binaries are published and, when it finds one, downloads the binaries, runs the skill and opens a pull request with those two commits and the skill's report as description. Instead of stopping at the checkpoint, it exposes the features that the Node or Python SDK exposes and lists the rest as open decisions. Its header lists the secrets and repository settings it needs, and it can be started by hand from the Actions tab to choose the Claude model and effort level.

A hook keeps Proto/*.g.cs, runtimes/ and the submodule out of reach of agent edits. The coverage script also works on its own, from the repository root. It lists the FfiRequest, FfiEvent and RoomEvent cases that the hand-written sources do not send, read or handle yet:

python3 .claude/skills/sync-rust-sdks/scripts/ffi_coverage.py
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Livekit.Rtc.Dotnet:

Package Downloads
Fluxer.Net.Voice

Voice channel support for Fluxer.Net bots: joining voice channels and sending audio.

Fluxify.Voice

Fluxify is a .NET Library for building chat bots for the fluxer platform.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.7 89 11/5/2026
0.1.6 517 11/2/2026
0.1.5 99 11/1/2026
0.1.4 6,891 8/28/2026
0.1.3 4,582 5/24/2026
0.1.2 1,545 3/29/2026
0.1.1 156 3/29/2026
0.1.0 2,035 1/26/2026

# Changelog (Release Notes)

## 0.1.7

- Update rust-sdks to [livekit-ffi/v0.13.4](https://github.com/livekit/rust-sdks/releases/tag/livekit-ffi%2Fv0.13.4) [PR #148](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/148)
- Fix the process aborting when a remote participant publishes a transcription whose track SID
 is not a valid track SID (an empty string, for example): the native client panicked while
 handling it. The transcription is now dropped with a native warning, and
 `Room.TranscriptionReceived` is not raised for it. [PR #148](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/148)
- A failed native request no longer prints the whole request to stderr in its
 `failed to handle request` error line, so the key passed to a failing
 `KeyProvider.SetSharedKey` or `KeyProvider.SetKey` call stays out of the console and the
 logs. [PR #148](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/148)
- Publishing a video track with a `TrackPublishOptions.VideoCodec` that no encoder on the host
 can produce (H.265 without an H.265 encoder, for example) now publishes VP8 and logs the
 native warning `no video encoder on this host can produce H265; publishing VP8 instead`,
 instead of announcing a codec the host cannot encode. Pre-encoded publishing
 (`EncodedVideoSource` with `VideoEncoder = EncoderBackendPreEncoded`) no longer needs a local
 encoder for its codec, so pre-encoded H.265 works on any host. [PR #148](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/148)
- Fix `EncodedVideoFrame.Metadata` (sent when `TrackPublishOptions.FrameMetadataFeatures` is
 set) reaching subscribers on the wrong frame when frames share a timestamp or are captured
 within the same millisecond: the sender looked the metadata up by capture time truncated to
 the millisecond, so such frames carried another frame's metadata. [PR #148](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/148)
- Reconnection now spaces its ten attempts like the JavaScript client (waits of 0.3 s, 1.2 s,
 2.7 s, 4.8 s, then 7 s, plus up to 1 s of jitter), so a `Room` rides out a network outage of
 about 45 s before it gives up and raises `Disconnected`; the previous random waits could spend
 every attempt within a few seconds. No new attempt starts once reconnection has run for 60 s,
 so a room no longer stays in `Reconnecting` for minutes, and a join or reconnect whose
 response times out now closes its WebSocket instead of leaving it open. [PR #148](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/148)
- XML documentation comments for every public member, with parameter and return value
 descriptions on every method, for IntelliSense and for the new
 [API reference](https://pablofuente.github.io/livekit-server-sdk-dotnet/) generated with DocFX.
 [commit f5660c2](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f5660c2f0225936951568c0c3ab403175521063a)
- Fix wrong XML documentation: `LocalParticipant.PerformRpcAsync` waits 15 seconds for the response
 by default, not 10; `Room.E2EEStateChanged` fires on every encryption state change, not only on
 errors; this version never calls `FrameProcessor.OnStreamInfoUpdated` and `OnCredentialsUpdated`,
 and never throws `FfiException` or `TrackException` (a failed native request raises
 `InvalidOperationException`, a server error `RoomException`). The defaults of `RoomOptions`,
 `TrackPublishOptions.Simulcast`, `DataPublishOptions.Reliable`, `E2EEOptions` and
 `KeyProviderOptions` are now documented, as is what each `Dispose` releases, and `Room` gains a
 usage example. The API reference lists only the `LiveKit.Proto` types the public API uses.

## 0.1.6

- Fix the process terminating (`FFI Panic` followed by exit code 1) in two situations: calling
 `LocalParticipant.UnpublishTrackAsync` with something that is not a track SID (a track name,
 an empty string, a remote track's SID, which was always empty), which now throws
 `ArgumentException`; and cancelling or timing out `Room.ConnectAsync` once the FFI had
 connected, which left a handshake unanswered that the FFI waits 15 s for before panicking.
 The SDK now completes that handshake and leaves the room in the background. In the same
 spirit, a `PublishTrackAsync` cancelled after the track was published now unpublishes it
 instead of leaving a track on the server the SDK does not know about, and an FFI callback
 that races with a cancelled wait is kept for the reconciliation instead of being dropped. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)
- Fix `Room.Disconnected` and `ConnectionStateChanged` (with `ConnDisconnected`) being raised
 twice on every `DisconnectAsync` (once from the FFI's own disconnected event, once from the
 method), always with `UnknownReason`. They are raised once, with the real reason
 (`ClientInitiated`, `RoomDeleted`, `DuplicateIdentity`, `ParticipantRemoved`...), and
 `Room.LocalParticipant` and `Room.RemoteParticipants` stay readable inside the `Disconnected`
 handlers (they were cleared before the handlers ran). `ConnectAsync` no longer reports a
 spurious `ConnDisconnected` first, and `ConnectionStateChanged` carries the state at the time
 of the change: a slow handler used to make `ConnReconnecting` vanish from the sequence. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)
- Fix `Room.TrackUnsubscribed`, `TrackMuted` and `TrackUnmuted` being dropped (logged as
 `[WARN] RoomEvent.TrackUnsubscribed: Participant ... not found`) when the remote participant
 leaves the room: the lookup ran on the event chain after `ParticipantDisconnected` had already
 removed the participant. Remote track events are now resolved on arrival. `LocalTrackPublished`
 and `LocalTrackSubscribed` no longer poll for the publication inside the event chain (up to
 500 ms of stall per miss, and a dropped event under thread-pool starvation): they wait for
 `PublishTrackAsync` to register it. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)
- Fix `RemoteTrack.Sid`, `Name` and `IsMuted` never being set (the `TrackSubscribed` track
 always reported an empty SID), and local tracks reporting `TR_unknown` after
 `PublishTrackAsync` instead of their publication's SID. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)
- Fix text data streams corrupting multi-byte characters (`€`, accented letters, CJK, emoji)
 that fell on a chunk boundary: chunks were cut every 15000 bytes regardless of UTF-8
 boundaries and decoded one by one, so a replacement character appeared at every boundary.
 Chunks are now split on character boundaries, and the reader joins a character split by
 another sender. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)
- Fix `TextStreamReader` and `ByteStreamReader` reads (`ReadAllAsync`, `await foreach`) waiting
 forever when the sender or the room disconnected before the stream was closed: they now throw
 `StreamException` once the buffered chunks are consumed. Text and byte stream handlers run on
 the thread pool instead of the FFI callback thread: a handler that blocked on `ReadAllAsync`
 deadlocked, and stalled every FFI event of the process. `TextStreamWriter.Dispose()` and
 `ByteStreamWriter.Dispose()` now send the trailer (`using var writer = ...` used to leave the
 remote reader open forever; only `DisposeAsync` closed it); new
 `CloseAsync(reason, attributes, CancellationToken)` overloads; `StreamInfo.Size` is reported
 for an announced size of 0. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)
- Fix `FrameCryptor.SetEnabled` and `SetKeyIndex` failing with
 `InvalidOperationException: FFI request failed: invalid handle`: the requests did not carry
 the track SID the FFI addresses the cryptor by. `FrameCryptor` now exposes `TrackSid` (set by
 `E2EEManager.GetFrameCryptors`) and has a constructor overload taking it. New
 `KeyProviderOptions.KeyDerivationFunction` (defaults to PBKDF2, as before); a null
 `RatchetSalt` now falls back to the default salt instead of throwing. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)
- Fix `AudioSource.CaptureFrameAsync` throwing `TimeoutException` for frames longer than about
 10 s (a TTS clip, a file): the FFI only acknowledges a frame once it fits its queue, so the
 wait is now proportional to the frame. Fix `WaitForPlayoutAsync` never completing when another
 frame was captured meanwhile, and hanging after `Dispose`; cancelling one wait no longer
 cancels the others. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)
- Fix `AudioResampler.Flush()` returning a frame with zero samples before any audio was pushed,
 and ignoring resampling errors. `Push(AudioFrame)` now rejects frames with another channel
 count or sample rate (the output used to be garbage of half the duration), and push and flush
 are safe to call from several threads. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)
- Fix `DataTrackStream.ReadAsync` throwing `ChannelClosedException` when the stream is disposed
 while a read is pending: it returns null, like at the end of the stream. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)
- Fix RPC: `RpcError.RpcData` was `""` instead of null when the remote error carried no data;
 the message and the full stack trace of an exception thrown by an RPC handler were sent to
 the remote caller, now only the built-in `Application error in method handler` crosses the
 wire and the exception is logged locally with `[ERROR]`, as the Node and Python SDKs do; and
 `PerformRpcAsync` surfaces the FFI's `RpcError` with `ResponseTimeout` instead of a generic
 `TimeoutException` when `responseTimeout` elapses. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)
- Media delivery: `AudioStream` bounded buffers (`capacity > 0`) drop the oldest frame instead
 of the newest when the consumer falls behind, like `VideoStream` and the Python SDK; a frame
 processor that throws is logged with `[WARN]` (`Debug.WriteLine` was silent in Release
 builds); audio and video frames and data packets are block-copied out of native memory (a
 byte loop on the FFI callback thread delayed every other event, 3 MB per 1080p frame); the
 frames the native side still emits right after a stream is disposed are released instead of
 leaked; and `AudioMixer` reads its sources concurrently (N stalled sources cost one timeout,
 not N) and keeps a source that stalls for one round instead of removing it for good. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)
- Participants: new `Participant.Attributes` (kept current by `ParticipantAttributesChanged`,
 which left it stale), `Participant.State` and `Participant.DisconnectReason` (set before
 `ParticipantDisconnected` is raised). `Participant.TrackPublications` returns a snapshot:
 enumerating it while a track event was handled could throw `Collection was modified`.
 `LocalTrackPublication.WaitForSubscriptionAsync` continuations no longer run inline on the
 room's event chain (delaying the following events), and the wait is cancelled when the room
 disconnects instead of hanging forever. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)
- Rooms and the FFI client: a connected `Room` that is never disposed no longer stays
 referenced by the process-wide FFI client (it listens from `ConnectAsync` to the disconnect);
 `Room.Dispose()` now delivers the teardown events like `DisconnectAsync` does; the FFI event
 queue no longer fills up with media frames, which made every request wait scan a thousand
 stale events and could evict a pending callback (spurious `TimeoutException` under media
 load); one throwing event subscriber no longer hides the event from the other rooms and
 streams; and handler exceptions are logged with their type and stack trace, not only the
 message. `FfiClient.CopyBuffer` threw `EntryPointNotFoundException` (the native library has
 no such export) and is now implemented in managed code; `FfiClient.RetrievePtr` has the same
 problem and cannot be fixed (a pointer to a managed array is only valid while pinned), so it
 is marked `[Obsolete]`. [commit f2765fc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f2765fc14a0a5325be47fae3501a8fc866d53970)

## 0.1.5

- Update rust-sdks to [livekit-ffi/v0.13.3](https://github.com/livekit/rust-sdks/releases/tag/livekit-ffi%2Fv0.13.3) ([commit 1001476](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/10014763a5212503352f2a39477ae86acf8add87))
- Fix process crash (native panic `index out of bounds: the len is 0 but the index is 0`) when
 converting or capturing video frames in the `I420A`, `I422`, `I444` and `I010` formats.
 `VideoFrame` only described the planes of `I420` and `NV12` buffers to the FFI, so the native
 side found no planes for the other planar formats. All planar formats are now described from a
 single plane table that is shared with `VideoFrame.GetPlane`, which also makes `GetPlane` work for
 `I010`. [commit 026961e](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/026961e5fa1367fdac9d1954db456cd2f374b4e3)
- Fix a possible native access violation (process crash) when capturing audio or video frames.
 The frame's managed sample/pixel array was only pinned while the pointer handed to the FFI was
 built, not while the capture request was serialized and sent, so a compacting garbage
 collection in between could move the array and leave the native side reading a stale pointer.
 `AudioSource.CaptureFrameAsync`, `VideoSource.CaptureFrame` and `VideoFrame.Convert` now keep
 the buffer (and, for planar video formats, every plane pointer) pinned through request
 serialization and the native call. [PR #143](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/143) (audio) and
 [PR #144](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/144) (video).
- Fix `Room.LocalTrackUnpublished` being dropped (logged as
 `[WARN] RoomEvent.LocalTrackUnpublished: Publication ... not found`) when the FFI
 `UnpublishTrack` callback was processed before the matching `LocalTrackUnpublished` room
 event. `LocalParticipant.UnpublishTrackAsync` now hands the removed publication over to the
 room event instead of dropping it, and the event handler no longer retries the lookup, which
 used to stall the room's FIFO event chain for up to 500ms per miss and delay every following
 event. Most visible when unpublishing many tracks at once. [commit 18d3966](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/18d3966a9c742e0f99a659b0d40ec0b538dd43f2)
- The SDK version that the FFI sends to the LiveKit server as the client SDK version now
 follows the package version in `LivekitRtc.csproj`. It was hard-coded to `0.1.0`.
 [commit 8ce5332](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/8ce53323aedfa79d35091d3824e5ea982c7c3a7c)
- The NuGet package now embeds this changelog as its release notes, as `Livekit.Server.Sdk.Dotnet`
 already did. [commit 880a38e](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/880a38e82783fa5a797e80f82af94f0286f0cf66)
- Data tracks, reaching parity with the Python RTC SDK: `LocalParticipant.PublishDataTrackAsync`
 returns a `LocalDataTrack` to push `DataTrackFrame`s on. Remote tracks arrive through the new
 `Room.DataTrackPublished` and `Room.DataTrackUnpublished` events (and are listed in
 `Room.RemoteDataTracks`); `RemoteDataTrack.Subscribe()` returns a `DataTrackStream` that can
 be read with `await foreach` and ends when the track is unpublished. [commit a3147e8](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/a3147e8923ed76611550bd38050ed18588f6164b)
- New `EncodedVideoSource` to publish pre-encoded video (H.264, VP8, ...) without re-encoding it:
 `LocalVideoTrack.Create(name, encodedSource)`, `CaptureFrame(EncodedVideoFrame)` and
 `TakeFeedback()` for the key-frame and bitrate requests to forward to the encoder. [commit a3147e8](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/a3147e8923ed76611550bd38050ed18588f6164b)
- New subscriber-side controls on `RemoteTrackPublication`: `SetVideoQuality` (simulcast layer),
 `SetEnabled` (pause receiving without unsubscribing) and `SetVideoDimensions`. [commit a3147e8](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/a3147e8923ed76611550bd38050ed18588f6164b)
- New room events `ParticipantPermissionsChanged` (with the new `Participant.Permissions`
 property), `ParticipantActive`, `LocalTrackRepublished` (local publications are now updated in
 place and re-keyed after a full reconnect instead of going stale) and
 `DataChannelBufferedAmountLowThresholdChanged`, raised after the new
 `LocalParticipant.SetDataChannelBufferedAmountLowThreshold`. Bulk participant updates sent by
 the server are now applied to the participant objects. [commit a3147e8](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/a3147e8923ed76611550bd38050ed18588f6164b)
- New `Room.SimulateScenarioAsync(Proto.SimulateScenarioKind)` to force the reconnection paths
 from tests without touching the network: a full reconnect (the SDK rebuilds its session and
 republishes its local tracks, raising `LocalTrackRepublished`), a resume, a server-initiated
 leave... It mirrors `Room.simulate_scenario` in the Python SDK and `Room.simulateScenario` in
 the Node SDK. See
 [Simulating Reconnections](https://github.com/pabloFuente/livekit-server-sdk-dotnet/blob/main/LivekitRtc/README.md#simulating-reconnections). [commit a3147e8](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/a3147e8923ed76611550bd38050ed18588f6164b)

## 0.1.4

- Update rust-sdks to [livekit-ffi/v0.12.76](https://github.com/livekit/rust-sdks/releases/tag/livekit-ffi%2Fv0.12.76) ([commit 36077cc](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/36077cc273bd2a8c0952aafc818ca65c77e2af53))
- Add all the missing `TrackPublishOptions` fields, reaching parity with the Node/Python/Rust
 RTC SDKs: `VideoCodec` (select the codec of published video tracks: VP8, H264, AV1, VP9...),
 `Dtx`, `Red`, `Stream`, `PreconnectBuffer`, `FrameMetadataFeatures`, `ScalabilityMode`
 (SVC publishing for VP9/AV1), `VideoEncoder` (preferred encoder backend) and
 `DegradationPreference`. All new options are nullable and unset keeps the previous
 behavior (the SDK defaults). The `TrackPublishOptions` → `Proto.TrackPublishOptions`
 mapping is now covered by unit tests, including a parity guard that fails when a future
 FFI proto regeneration introduces fields not exposed by the SDK. [commit 3e3cfb4](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/3e3cfb459794d2b031d69bc58933bc833c2cb8d5)

## 0.1.3

- Update rust-sdks to [livekit-ffi/v0.12.60](https://github.com/livekit/rust-sdks/releases/tag/livekit-ffi%2Fv0.12.60) ([commit 65e5fc4](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/65e5fc4accdd8060cc8994c8e672161a9c4d9bd2))

## 0.1.2

- Update rust-sdks to [livekit-ffi/v0.12.50](https://github.com/livekit/rust-sdks/releases/tag/livekit-ffi%2Fv0.12.50) ([commit 5225cc6](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/5225cc60c9acaf98fbced1f11193b50f9cc2a228))

## 0.1.1

- Update livekit/protocol to [v1.45.1](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.45.1) ([commit b6f0054](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/b6f00545ef4f9c87f4b6e1915f140d7e0f6e512d))

## 0.1.0

- Initial release of Livekit.Rtc.Dotnet SDK. [commit d110d6c](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/d110d6c21d40b58f4af7f7f68f12e4d80d5dde93)