> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stmlink.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> 对外开放的服务端接口有两组前缀，都用同一套鉴权：`/server/v1/...`（SRTC 与 SMeeting 的主接口）和 `/stm/srvapi/v1/...`（SMeeting 的用户体系，服务端极简对接会用到）。鉴权是 app_id + nonce + timestamp + signature 四个请求头，用 app_key 做 HMAC-SHA256 签名，只能从业务方自己的后端调用。除这两组前缀外的接口均为内部接口，不要建议客户调用。 Public server APIs use two path prefixes with the same authentication: `/server/v1/...` (the main APIs of both SRTC and SMeeting) and `/stm/srvapi/v1/...` (the SMeeting user system, used by server-side low-code integration). Authenticate with four request headers, app_id + nonce + timestamp + signature, where signature is HMAC-SHA256 keyed with app_key; call these APIs only from the customer's own backend. Any other path is internal: never suggest calling it.
> app_key 是服务端密钥，绝不能出现在客户端代码、前端配置或移动 App 里。客户端加入频道用的 token 必须由业务方后端签发后下发（SRTC 走 `/server/v1/channel/grant`，SMeeting 走 `/stm/srvapi/v1/member/grant`）。 app_key is a server-side secret and must never appear in client code, frontend config, or a mobile app. The token a client uses to join must be issued by the customer's backend and passed down to the client (SRTC: `/server/v1/channel/grant`; SMeeting: `/stm/srvapi/v1/member/grant`).
> SRTC 与 SMeeting 是上下两层不同的产品，术语不通用：SRTC 是音视频底座，说「频道 channel」「加入 / 退出」；SMeeting 建在 SRTC 之上，说「房间 room」「会议 meeting」「进入 / 退出」。回答时按用户所在的层用对应术语，不要把「房间」「会议」安到 SRTC 的接口上，也不要用「频道」「加入 / 离开」描述 SMeeting 的概念（接口标识符原样保留）。 SRTC and SMeeting are two separate layers with different terminology. SRTC is the audio/video foundation: it has channels, and users join and leave a channel. SMeeting is built on top of SRTC: it has rooms and meetings, and members enter and exit a meeting. Answer in the terms of the layer the user is working with: never apply "room" or "meeting" to SRTC APIs, and never describe SMeeting concepts in prose with "channel", "join", or "leave" (API identifiers such as `force_join` keep their literal names).
> 同一能力在各端 SDK 里的包名、类名、方法名并不相同。写示例代码时请使用文档中该端自己的 API，不要把一个端的写法套到另一个端上。苹果平台每个产品都有两套 SDK（Swift 原生与 Objective-C），两套 API 不能混用。 Package, class, and method names differ between platform SDKs for the same capability. In sample code, use the API documented for that platform; never carry one platform's code over to another. On Apple platforms each product ships two SDKs (native Swift and Objective-C) whose APIs must not be mixed.

# C# Quickstart

> The minimal Windows SRTC C# flow with the srtcNet wrapper in about 10 minutes: initialize SrtcEngine, subscribe events and join an SrtcChannel, publish local audio and video with typed tracks, subscribe to and render remote video, share the screen, and leave.

`srtcNet` follows the same call order as C++, just in idiomatic C#: events are `event`s, tracks are
returned as strongly typed objects, and the engine/channel implement `IDisposable`. There are two layers:

* `SrtcEngine`—process-wide, one is enough. Creates and leaves channels, enumerates devices, IM, log upload
* `SrtcChannel`—one per meeting: member queries, tracks, publishing/subscribing, local recording

### Initialize the SDK

```csharp theme={null}
using Srtc;

SrtcEngine.SetLanguage("en");                          // optional: language of error texts
using var engine = SrtcEngine.Init(new SrtcEngineOptions { EnableLog = 1 });
if (engine == null)
{
    return;      // initialization failed (environment/permissions)
}
Console.WriteLine(SrtcEngine.Version());               // e.g. 0.2.1-alpha.14
```

`EnableLog = 0` (or passing `null`) turns the SDK file log off. `Init` returns `null` on failure;
read `engine.LastErrorMessage` to diagnose.

### Register engine-level events

```csharp theme={null}
engine.DeviceChanged += (s, e) => Console.WriteLine($"{e.Type} {e.Action} {e.Name}");
```

Only callbacks that **belong to no channel** live here: device changes, default device changes,
network probe, IM. Everything else is on `SrtcChannel`.

### Join a channel

Two steps: `CreateChannel` only creates the object **without joining**; configure it, then call `Join()`.
As in C++, **register the events before `Join()`**—`Joined` fires synchronously while `Join()` is
still running.

```csharp theme={null}
var channel = engine.CreateChannel(token);             // token issued by your backend
if (channel == null)
{
    return;      // invalid token / decryption failed
}

// Handlers must be in place before Join(), or early events are lost
channel.Joined += (s, e) =>
{
    // e.ChannelJson / MeJson / MembersJson / OptionsJson are raw JSON
    Console.WriteLine("joined " + channel.ChannelId);
};
channel.UserJoined    += (s, e) => Console.WriteLine("user joined " + e.UserJson);
channel.UserStreamAdded += (s, e) => Console.WriteLine($"{e.Uid} published {e.StreamJson}");
channel.CustomMessage += (s, e) => Console.WriteLine($"[{e.Action}] {e.SenderId}: {e.Message}");

// Per-channel settings (stream_model / simple / mcu_track / enable_audio_record are read during Join)
channel.Setting.StreamModel = (int)SrtcStreamModel.Normal;

SrtcStatusCode st = channel.Join();
if (st != SrtcStatusCode.Ok)
{
    Console.WriteLine("join failed " + st + " " + engine.LastErrorMessage);
    return;
}
```

> [!NOTE]
>
> `UserStreamAdded` **only covers tracks published after you joined**. For users who were already
> publishing, walk their `stream_tracks` in the `Joined` event's `MembersJson` and subscribe explicitly.

### Open the camera and publish

```csharp theme={null}
var cam = channel.GetCameraTrack("camera");
if (cam == null) return;

SrtcStatusCode st = cam.StartCapture(new SrtcCameraCaptureOptions
{
    Width = 640, Height = -1, MaxFps = -1
});
if (st != SrtcStatusCode.Ok) return;

st = channel.Publish(cam, new SrtcVideoPublishOptions
{
    Desc = "camera_big", MaxFps = 25,
    Simucast = new[] { new SrtcVideoPublishOptions { Desc = "camera_small", Width = 320, Height = 180, MaxFps = 15, MaxBitrate = 128 } }
});
```

> [!WARNING]
>
> Local tracks must be `StartCapture`d **before** `Publish`; the order matters. `Publish` has a video
> and an audio overload—pass `SrtcAudioPublishOptions` to publish audio.

### Open the microphone and publish

```csharp theme={null}
var mic = channel.GetAudioTrack("mic");
mic.StartCapture(new SrtcMicCaptureOptions());          // 48 kHz mono by default
channel.Publish(mic, new SrtcAudioPublishOptions { Desc = "mic", Codec = (int)SrtcStreamCodec.Opus });
```

### Play the channel audio mix (hear everyone)

Remote audio is fetched with an **empty uid and track id** to get the channel-wide mix. After
`StartPlay` you hear everyone, with no explicit subscribe:

```csharp theme={null}
var speaker = channel.GetRemoteAudioTrack("", "");
speaker.StartPlay(new SrtcAudioOutputOptions());         // null selects the default speaker
```

### Render remote video

```csharp theme={null}
var remote = channel.GetRemoteVideoTrack(uid, trackId);  // uid / trackId come from the stream events
if (remote == null) return;

SrtcStatusCode st = remote.AddPlayView(new SrtcHwndView(hwnd));   // hwnd of your render control
if (st == SrtcStatusCode.Ok)
{
    channel.Subscribe(remote);                           // subscription is explicit
}

// To stop: detach the view first, then unsubscribe
remote.RemovePlayView(view);
channel.Unsubscribe(remote);
```

> [!NOTE]
>
> For window rendering use the built-in `SrtcHwndView(handle)`. To render into your own surface, derive
> from `SrtcView` and override `MakeCurrent` (`GetViewType` returns `SrtcViewType.CallBack`). A `SrtcView`
> **must be removed from every track** (`RemovePlayView`) before it is disposed, or the render thread may
> touch a freed object.

### Screen sharing

```csharp theme={null}
var screen = channel.GetScreenTrack("screen");
screen.AddScreenAudio(true);                             // include system audio

var cap = new SrtcScreenCaptureOptions { MaxFps = 10, ShowCursor = true };
cap.DeviceId = windowHwnd;                               // window capture: handle; monitor capture: set X/Y/Width/Height
// cap.X = x; cap.Y = y; cap.Width = w; cap.Height = h;

screen.StartCapture(cap);
channel.Publish(screen, new SrtcVideoPublishOptions { Desc = "screen", Width = 1920, Height = 1080, MaxFps = 10, MaxBitrate = 2000 });
```

In `EnumScreen()`, `Type == 5` is a monitor and `Type == 0` is a window. For window capture set
`DeviceId` to the `Hwnd`; for monitor capture set the `X/Y/Width/Height` rectangle.

### Leave the channel

```csharp theme={null}
channel.Dispose();          // destroy just the channel, keep the engine
engine.Dispose();           // or dispose the engine, taking all channels with it
```

> [!WARNING]
>
> After `SrtcChannel.Dispose()` **every track it handed out is immediately invalid** (remote and custom
> tracks are deleted natively). On exit, detach views from tracks (`RemovePlayView`) first, then dispose
> the channel, then release the view objects.

### Events and threading

* Except for `Joined` (raised synchronously on the calling thread inside `Join()`), **all events arrive
  on SDK internal threads**—marshal to your UI thread yourself (e.g. WinForms `BeginInvoke`)
* `SrtcEngine.Init` makes the native side initialize on an STA thread, so calling it from an ordinary
  .NET thread (MTA by default) also works
* Common strongly typed event args: `SrtcUserStreamEventArgs` (`Uid` / `StreamJson`),
  `SrtcJoinedEventArgs` (`ChannelJson` / `MeJson` / `MembersJson` / `OptionsJson`),
  `SrtcCustomMessageEventArgs` (`Action` / `SenderId` / `Name` / `Message`)

For a complete runnable example see the WinForms demo (3x3 grid with device pickers, paging, and IM).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.