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

# Multi-channel

> Join multiple channels at once with the Web SDK: using the Channel object returned by join, isolating events per channel, the default-channel rule for single-channel code, publishing one captured track to several channels, and how capture is released on leave.

### Overview

The Web SDK lets one SRTC instance **join multiple channels at the same time**. A typical scenario: while sending and receiving audio and video in a main call channel, also join a voice intercom channel to send and receive intercom audio separately.

```typescript theme={null}
const meeting = await srtc.join(meetingToken);   // Main call channel
const intercom = await srtc.join(intercomToken); // Intercom channel, running in parallel

await meeting.publishLocalTrack(cameraTrack);
await intercom.publishLocalTrack(micTrack);

await intercom.leave();   // Leave the intercom; the main call is unaffected
```

The return value of `join` is the channel handle (the Channel object). Publishing, subscribing, querying users, listening for events, and leaving all happen on it, and multiple channels don't interfere with each other.

### Channel object

The channel-level methods on the Channel object have **exactly the same semantics** as the methods of the same name on the `srtc` instance (for parameters and return values, see the [SRTC API reference](/en/rtc/web/api-reference/SRTC)); the only difference is that the target changes from "the current channel" to this specific channel:

| Category | Methods |
| - | - |
| Info queries | `getInfo()`, `getUsersInfo(map)` |
| Publishing | `publishLocalTrack(track, opt?)`, `unpublishLocalTrack(track)`, `getPublishInfo(track)` |
| Subscribing | `subscribeRemoteAudioMixTrack` / `subscribeRemoteAudioTrack` / `subscribeRemoteVideoTrack` / `subscribeRemoteVideoMcuTrack` / `unsubscribeRemoteTrack` |
| Quality statistics | `getStreamMetric()`, `getNetworkStats()`, `getConnectionQuality()` |
| Events | `onNotifyEvent` callback property, `on()` / `once()` / `off()` typed listeners |
| Leaving | `leave()` |

The Channel object can only be created by `srtc.join()`; `new` isn't allowed.

### Per-channel event isolation

Each channel's events are emitted on its own Channel object. Use either approach:

```typescript theme={null}
// Option 1: callback property (the same discriminated union as srtc.onNotifyChannelEvent)
intercom.onNotifyEvent = (evt) => {
  switch (evt.type) {
    case ChannelEventType.USER_TRACK_ADD:
      // Only receives events from the intercom channel
      break;
  }
};

// Option 2: typed listener for a single event
meeting.on(ChannelEventType.CONNECTION_QUALITY_CHANGED, (evt) => { ... });
```

`srtc.onNotifyChannelEvent` still works: it receives channel events of the **default channel** (see the next section), plus device-level events unrelated to any channel, such as devices being plugged in or unplugged. In multi-channel apps, we recommend handling all channel events through `channel.onNotifyEvent` and leaving `srtc.onNotifyChannelEvent` for device-level events only.

### Single-channel compatibility: the default channel rule

The channel-level methods on the `srtc` instance (`publishLocalTrack`, `subscribe*`, `getChannelInfo`, `leave`, etc.) internally act on the **default channel**—the earliest-joined channel that you're still in; when you leave it, the next one takes over.

When you join only one channel, the default channel is always that channel, so all existing single-channel code behaves the same. Once you join multiple channels, we recommend no longer relying on these convenience methods and using each Channel object explicitly, to avoid ambiguity from the default channel moving to the next one.

### Publishing one captured track to multiple channels

The same local track can be published to multiple channels at once. There's only one capture, while encoding and publishing are independent per channel:

```typescript theme={null}
const mic = srtc.createLocalMicTrack();
await meeting.publishLocalTrack(mic);
await intercom.publishLocalTrack(mic);
```

Once published to multiple channels, the track has **separate track info in each channel** (the track id assigned by the server, encoding parameters, and so on all differ). In that case:

* `track.getInfo()` **throws**—it can't answer "the info in which channel";
* Use `channel.getPublishInfo(track)` instead to query the track description in a given channel;
* When published to only one channel, `track.getInfo()` behaves as before, so no changes are needed.

```typescript theme={null}
const infoInMeeting = meeting.getPublishInfo(mic);   // Track id etc. in the main call channel
const infoInIntercom = intercom.getPublishInfo(mic); // A separate copy in the intercom channel
```

### Releasing capture resources

When you leave, the SDK releases capture based on "whether any channel is still using it":

* `leave()` on one channel: only stops capture of local tracks that "were published in that channel and are no longer published in any channel"; tracks still published by other channels keep capturing and publishing.
* Leaving the last channel: releases all local tracks created by the SRTC instance (the same behavior as in the single-channel era).

So in the example above, `intercom.leave()` doesn't stop the mic capture (the main call is still using it); capture stops only after you leave both channels.

### Releasing the instance

When you no longer need the SRTC instance (the SPA route changes, the component unmounts, or you're about to recreate the instance), call `srtc.destroy()` to leave all channels, disable IM, and remove global listeners in one go. See [destroy](/en/rtc/web/api-reference/SRTC#destroy).
