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

# Key concepts

> How the SMeeting Swift SDK is organized: the SMeetingEngine entry point, the meeting lifecycle, meeting ID vs. room number, RoomInfo / MeetingUserInfo snapshots, roles, TrackDesc, media control naming, audio vs. video subscription, and SMeetingDelegate events. Read before building meeting features.

### Overall model

The object model of the SMeeting Swift SDK is compact:

* `SMeetingEngine`: the only public class; login, meeting management, entering and exiting meetings, media control, and host operations are all on it
* `RoomInfo`: room-level state of the current meeting (title, mute all / camera off for everyone, lock, sharing status, etc.)
* `MeetingUserInfo`: the state of one member in the meeting (nickname, role, microphone, camera, sharing)
* `SMeetingDelegate`: the callback entry point for all meeting events
* `SMeetingRemoteVideoView` / `SRTCVideoView`: the entry points for video rendering

At its core, the conferencing SDK synchronizes three kinds of state: **meeting state**, **member state**, and **media state**. The SDK's APIs and events are organized around these three kinds of state; once you understand this, most of the APIs become intuitive.

***

### Terminology: meeting layer vs. RTC layer

SMeeting is built on top of SRTC, and the two layers use different terms; mixing them up will keep tripping you up when reading the APIs:

| Concept | Meeting layer (SMeeting) | RTC layer (SRTC) |
| - | - | - |
| Space | room / meeting | channel |
| Entry / exit | enter / exit | join / leave |
| Members | member | channel user |

In the SMeeting APIs, you only see meeting-semantic names such as `enterRoom` / `exitRoom` / `createRoom`.

***

### Meeting lifecycle

A complete integration unfolds in the following order:

```text theme={null}
login  →  Before the meeting (create / query / update meetings)  →  enterRoom  →  In the meeting  →  exitRoom  →  logout
```

| Stage | Typical APIs | Description |
| - | - | - |
| Log in | `login(token:)` | The token is issued by your backend; you can call the other APIs only after logging in |
| Before the meeting | `createRoom(_:)`, `updateRoom(meetingId:req:)`, `cancelRoom(meetingId:)`, `detailRoom(meetingId:roomNo:)`, `attendeeRoom(page:)`, `attendedRoom(page:)` | Requires only login, not being in a meeting |
| Enter | `enterRoom(_:)` | On success, the SDK sets up meeting state internally and starts reporting events |
| In the meeting | Media control, messages, host controls, and more | Calling these when not in a meeting throws `SMeetingError.notInMeeting` |
| Exit | `exitRoom()` | Exits only the current meeting; the login state is kept |
| Log out | `logout()` | If still in a meeting, exits the meeting first automatically |

To check whether you're currently in a meeting, use `meeting.isInRoom`.

***

### Meeting ID and room number

The two identifiers often appear together but mean different things:

| Identifier | Source | Purpose |
| - | - | - |
| `meetingId` | Return value of `createRoom`, meeting list / details | The unique ID of the meeting; used by most meeting management APIs |
| `roomNo` | Return value of `createRoom`, meeting details | The user-facing meeting number, used for sharing and inviting others to enter the meeting |

In `MeetingEnterReq`, provide either `meetingId` or `roomNo`.

***

### Meeting type and meeting mode

Creating a meeting involves two orthogonal dimensions:

* `MeetingType`: `.instant` instant meeting / `.appointment` scheduled meeting. A scheduled meeting also requires `planTime` (timestamp in seconds) and `planDur` (minutes)
* `MeetingMode`: `.normal` normal, `.mix` composite, `.voice` voice meeting, `.training` training, `.subMeeting` sub-meeting

Entry restrictions are controlled by `AttendType`: password-protected entry also requires `password`, and invitation-only entry also requires `conferee`.

***

### In-meeting state: RoomInfo and MeetingUserInfo

After you enter a meeting, the SDK continuously maintains a snapshot of the in-meeting state that you can read at any time:

```swift theme={null}
let roomInfo = meeting.getRoomInfo()          // Current room info; nil when not in a meeting
let users = meeting.getUsersInfoList()        // All members (array)
let usersMap = meeting.getUsersInfo()         // All members (uid → member)
let me = try meeting.getUserInfo(meeting.currentUserId ?? "")
```

This snapshot is **read-only**: state changes are notified through `SMeetingDelegate` events, and you just read it again in the event callback. Don't cache stale member objects.

Frequently used fields in `MeetingUserInfo`:

| Field | Type | Description |
| - | - | - |
| `micState` | `MicState` | `.on` / `.off` |
| `cameraState` | `CameraState` | `.on` / `.off` |
| `shareState` | `Int` | `0` no sharing, `1` screen sharing, `2` whiteboard; can be compared with the `rawValue` of `ShareType` |
| `role` | `Role` | `.member` / `.host` / `.coHost` |
| `chatDisabled` | `Bool` | Whether chat is disabled for this member individually |
| `drawDisabled` | `Bool` | Whether the member is prevented from drawing |

***

### Roles and permissions

| Role | Description |
| - | - |
| `.host` | Host, with full meeting control permissions |
| `.coHost` | Co-host, sharing most meeting control permissions with the host |
| `.member` | Regular member |

To check whether you have meeting control permissions, read your own `MeetingUserInfo.role`:

```swift theme={null}
let me = try? meeting.getUserInfo(meeting.currentUserId ?? "")
let isAdmin = me?.role == .host || me?.role == .coHost
```

APIs with the `admin` prefix succeed only when called by the host / a co-host; regular members who call them get a permission error from the server.

***

### Track descriptions: TrackDesc

Every media stream in a meeting has a fixed description, used to locate the target when subscribing to remote video:

| Enum value | Raw value | Description |
| - | - | - |
| `.mic` | `mic` | Microphone audio |
| `.cameraBig` | `camera_big` | Camera high stream |
| `.cameraSmall` | `camera_small` | Camera low stream |
| `.screen` | `screen` | Screen sharing |

To see which tracks a member is currently publishing, read `MeetingUserInfo.trackDescs`.

***

### Naming rules for media control

* **Turning on** uses `requestOpenMic` / `requestOpenCamera` / `requestShare`—they include `request` because these actions first ask the meeting for permission (subject to room policies such as mute all, camera off for everyone, and sharing disabled) and then start the local stream
* **Turning off** uses `closeMic` / `closeCamera` / `stopShare`—they stop the local stream directly, with no request step, and never throw

When you're responding to the host's invitation to turn on your microphone / camera, pass `byAdmin: true` and `adminUid` to the turn-on API; the SDK then takes the "confirm the host's request" path instead of "request on your own."

***

### Video rendering entry points

| Scenario | Recommended entry point |
| - | - |
| Local video in SwiftUI | `SRTCVideoView(track: meeting.cameraTrack)` |
| Remote video in SwiftUI | `SMeetingRemoteVideoView(meeting:uid:trackDesc:)` |
| Remote video in UIKit / AppKit | `startPlayRemoteVideo(view:uid:trackDesc:)` / `stopPlayRemoteVideo(view:uid:trackDesc:)` |
| Controlling subscription yourself | `subscribeRemoteVideoTrack(uid:trackDesc:)` / `unsubscribeRemoteVideoTrack(uid:trackDesc:)` |

For details, see [Video rendering](/zh/meeting/swift/advanced/video-rendering) (Chinese).

***

### Audio subscription semantics

Remote audio and remote video are handled differently:

* **Audio**: subscribed automatically after you enter the meeting; you don't need to subscribe member by member. The speaker (remote audio playback) switch is `toggleRemoteAudioMute(_:)`—it only toggles playback and doesn't touch subscriptions
* **Video**: subscribed on demand. In a large meeting, subscribing to everyone's video makes bandwidth and decoding costs uncontrollable, so you decide which streams the current layout needs to pull

***

### Event model

All meeting events are reported through `SMeetingDelegate`. To register:

```swift theme={null}
meeting.delegates.add(delegate: self)
// When no longer needed
meeting.delegates.remove(delegate: self)
```

Key points:

* `delegates` is a **weak-reference multicast**, so you can register multiple observers; registering doesn't extend your object's lifetime
* Every method in the protocol has a default empty implementation, so you only implement the events you care about
* Event callbacks are always dispatched on the **main thread**, so you can update the UI directly

If your observer is a `@MainActor` type (for example, a SwiftUI `ObservableObject`), declare the protocol methods as `nonisolated`, then hop back to the main actor context inside them:

```swift theme={null}
@MainActor
final class MeetingController: ObservableObject {
    @Published var users: [MeetingUserInfo] = []
}

extension MeetingController: SMeetingDelegate {
    nonisolated func meeting(_ meeting: SMeetingEngine, userDidEnter user: MeetingUserInfo) {
        DispatchQueue.main.async { self.users = meeting.getUsersInfoList() }
    }
}
```

Events fall roughly into these groups: connection events, member events, room state events, message events, raise hand and host commands, waiting room, sub-meetings, sign-in and roll call, device events, and out-of-meeting messages (IM). For the full list, see [Events](/zh/meeting/swift/events) (Chinese).

***

### Out-of-meeting messages (IM)

`enableIm()` sets up a notification path independent of the meeting, used to receive notifications such as calls and meeting reminders **when you haven't entered a meeting**. It is separate from in-meeting chat messages: in-meeting chat goes through `sendRoomChatMessage` and works only inside the meeting.

See [Out-of-meeting messages](/zh/meeting/swift/advanced/im) (Chinese).

***

### Underlying RTC capabilities

When the meeting layer's high-level APIs aren't enough (for example, you need raw frame processing or custom encoding parameters that only SRTC provides), you can access the underlying instance through `meeting.srtc`.

```swift theme={null}
meeting.srtc.logLevel = .debug
```

> Always use `meeting.srtc`; don't create another SRTCEngine instance yourself. The meeting and the underlying layer share the same instance, and creating another one leads to split state, duplicate message connections, and devices being taken over.

***

### Further reading

* [Media control](/zh/meeting/swift/advanced/media-control) (Chinese)
* [Video rendering](/zh/meeting/swift/advanced/video-rendering) (Chinese)
* [Screen sharing](/zh/meeting/swift/advanced/screen-sharing) (Chinese)
* [Host controls](/zh/meeting/swift/advanced/host-controls) (Chinese)
* [Types](/zh/meeting/swift/types) (Chinese)
