> ## 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.

# SeaStart advanced features

> C SDK features available only with the SeaStart engine: simulcast layer switching callbacks and rtc_switch_layer, periodic and on-demand network quality reports, and active speaker snapshots. Read this when you need layer control, quality indicators, or speaking indicators.

The following features are only available when the channel uses the **SeaStart engine**. With other engines these callbacks never fire and the functions you call directly (such as `rtc_switch_layer`) return an error. The engine is determined by the channel configuration sent down by the server, so your code doesn't need to check it—just make sure it still works without these callbacks.

The three features map to three separate strongly typed callbacks, in the same style as `track_event` / `track_sample`, so there's no JSON to parse on the C side.

***

## Layer switching (simulcast)

When the publisher pushes a multi-layer simulcast stream, layer switching on the subscriber side is **fully automatic**: the SDK registers the available layers as candidates, and the SFU switches between them based on bandwidth estimation (BWE). You don't need to configure anything; usually you only listen for the switching results.

### Layer switched callback

```c theme={null}
typedef struct {
    char    sub_key[128];        // Stable subscription handle "pub_uid:track_id"
    char    from_track_id[64];   // Layer in use before the switch; empty string on first playback
    char    to_track_id[64];     // Layer in use after the switch
    char    reason[32];          // Reason for the switch, see the table below
    int64_t latency_ms;          // Time from initiating the switch to actually reaching the target layer
} rtc_layer_switched_t;

typedef void (*rtc_layer_switched_callback)(void* context, const rtc_layer_switched_t* data);
void rtc_set_layer_switched_callback(void* handle, rtc_layer_switched_callback callback, void* context);
```

`reason` values:

| Value | Meaning |
| - | - |
| `bwe_down` | Bandwidth dropped; the SFU switched down a layer automatically |
| `bwe_up` | Bandwidth recovered; the SFU switched up a layer automatically |
| `track_refresh` | Track refreshed |
| `track_upgrade` | Track upgraded |
| `track_ended` | The current layer ended; fell back to another layer |
| `client` | The client called `rtc_switch_layer` |

```c theme={null}
static void on_layer_switched(void* ctx, const rtc_layer_switched_t* d) {
    printf("layer %s -> %s (%s, %lldms)\n",
           d->from_track_id[0] ? d->from_track_id : "-",
           d->to_track_id, d->reason, (long long)d->latency_ms);
}

rtc_set_layer_switched_callback(rtc, on_layer_switched, NULL);
```

<Note>
  **Which layer is currently in use?** The subscription handle (the `track_id` in `sub_key`) stays stable for the whole subscription, but the layer actually in use is switched dynamically. The `to_track_id` you get from `layer_switched` is the current layer; if you need it, cache a `sub_key → current layer` mapping yourself.
</Note>

### rtc\_switch\_layer

```c theme={null}
int rtc_switch_layer(void* handle, const char* pub_uid,
                     const char* track_id, const char* target_track_id);
```

Asks the SFU to switch to a specific layer. Useful for switching between large and small windows, or proactively downgrading when a window goes to the background.

| Parameter | Description |
| - | - |
| `pub_uid` | Publisher uid |
| `track_id` | The stable handle trackId used when subscribing (usually the main layer's ID) |
| `target_track_id` | Target layer trackId; **must be in the candidate pool registered at subscription time** |

**Returns:** `RTC_OK` (request sent) / `RTC_INVALID_PARAM` (invalid handle or a parameter is `NULL`) / `RTC_NOT_CONNECTED` (not joined) / `RTC_ERROR` (rejected by the engine).

```c theme={null}
// Switch u1001's subscribed main layer to the smallest layer
rtc_switch_layer(rtc, "u1001", big_track_id, small_track_id);
// The result is reported through on_layer_switched, with reason = "client"
```

<Note>
  A successful return only means the request was sent; the switch to the target layer is complete when the `layer_switched` callback fires.

  This SDK only supports multi-layer switching on the subscriber side; it doesn't support **publishing** multi-layer simulcast streams.
</Note>

***

## Network quality

### Periodic report callback

The SFU periodically (typically at 1 Hz) reports uplink and downlink quality.

```c theme={null}
// Quality sample for one direction
typedef struct {
    double  score;      // Quality score 0–100
    char    level[16];  // "excellent" | "good" | "poor" | "lost"
    double  mos;        // 1.0-4.5
    double  loss;       // Packet loss rate 0–1
    double  rtt;        // Milliseconds
    double  jitter;     // Milliseconds
    int64_t packets;    // Number of packets counted in this round
    double  bitrate;    // Average bitrate in kbps (not used in the score)
    int64_t bytes;      // Bytes in this window
} rtc_quality_sample_t;

typedef struct {
    int64_t              ts;   // Unix timestamp in milliseconds when the SFU generated the report
    rtc_quality_sample_t pub;  // Uplink (local → SFU)
    rtc_quality_sample_t sub;  // Downlink (SFU → local)
} rtc_connection_quality_t;

typedef void (*rtc_connection_quality_callback)(void* context, const rtc_connection_quality_t* q);
void rtc_set_connection_quality_callback(void* handle, rtc_connection_quality_callback callback, void* context);
```

```c theme={null}
static void on_connection_quality(void* ctx, const rtc_connection_quality_t* q) {
    // For a signal-bars indicator, use q->pub.level / q->sub.level
    // For a numeric panel, use mos / loss / rtt / jitter / bitrate
    printf("pub=%s(%.1f) sub=%s(%.1f) rtt=%.0fms\n",
           q->pub.level, q->pub.score, q->sub.level, q->sub.score, q->sub.rtt);
}

rtc_set_connection_quality_callback(rtc, on_connection_quality, NULL);
```

<Warning>
  The callback fires for every report, so **throttling is up to you**. If you write logs or feed monitoring, downsample yourself.
</Warning>

### rtc\_get\_connection\_quality

```c theme={null}
int rtc_get_connection_quality(void* handle, rtc_connection_quality_t* out);
```

Fetches the most recent quality report on demand. Useful right after joining, to populate the UI / monitoring metrics before the first periodic report arrives.

`out` is provided by the caller; the SDK fills in the fields directly, and nothing needs to be freed.

**Returns**

| Return value | Meaning |
| - | - |
| `RTC_OK` | Written to `out` |
| `RTC_NOT_CONNECTED` | Not yet joined, or no quality report received yet |
| `RTC_INVALID_PARAM` | Invalid handle, or `out` is `NULL` |

```c theme={null}
rtc_connection_quality_t q;
int ret = rtc_get_connection_quality(rtc, &q);
if (ret == RTC_OK) {
    printf("pub=%s loss=%.2f sub=%s loss=%.2f rtt=%.0f\n",
           q.pub.level, q.pub.loss, q.sub.level, q.sub.loss, q.sub.rtt);
} else if (ret == RTC_NOT_CONNECTED) {
    // No data yet; just wait for the callback
}
```

***

## Active speaker

```c theme={null}
typedef struct {
    char   uid[64];
    char   track_id[64];  // Distinguishes tracks when a user has multiple audio tracks
    double level;         // 0.0–1.0, higher is louder
} rtc_active_speaker_t;

typedef void (*rtc_active_speakers_callback)(void* context, int64_t ts,
                                             const rtc_active_speaker_t* speakers,
                                             int speakers_count);
void rtc_set_active_speakers_callback(void* handle, rtc_active_speakers_callback callback, void* context);
```

The SDK merges the SFU's incremental events into a **full snapshot** before passing it up, sorted by `level` in descending order, so you can simply overwrite the whole UI without merging deltas yourself. When no one is speaking, `speakers_count = 0` and `speakers = NULL`.

```c theme={null}
static void on_active_speakers(void* ctx, int64_t ts,
                               const rtc_active_speaker_t* speakers, int count) {
    clear_speaking_indicators();
    for (int i = 0; i < count; i++) {
        update_ui(speakers[i].uid, speakers[i].level);
    }
}

rtc_set_active_speakers_callback(rtc, on_active_speakers, NULL);
```

<Warning>
  The `speakers` array is held by the SDK during the callback and **freed as soon as the callback returns**. To keep it beyond the callback, you must copy it yourself.
</Warning>
