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

# Quickstart

> The minimal Objective-C SRTC flow on iOS: initialize RTCEngineKit, implement RTCEngineDelegate and RTCEngineChannelDelegate, create a channel instance and join, preview and publish the camera, subscribe to remote video by track ID, then leave and destroy. Read after completing integration.

<Note>
  Starting with `3.0.0`, the SDK has two layers: `RTCEngineKit` is a process-level singleton that handles initialization, shared hardware (camera, audio routing, screen capture), and creating channel instances; `RTCEngineChannel` is a channel instance that handles joining the channel, publishing, and subscribing to streams. A single process can create multiple channel instances and join multiple channels at the same time.
</Note>

### Step 1: Initialize the SDK

#### Create the initialization parameters

You must initialize the SDK before calling any other SDK function. To initialize the SDK, create an instance of the `RTCEngineConfig` object.

```objectivec theme={null}
RTCEngineConfig *engineConfig = [[RTCEngineConfig alloc] init];
engineConfig.enableLocalLog = YES;
```

* **The following table describes all properties of the `RTCEngineConfig` object.**

| **Parameter** | **Required** | **Description** |
| - | :-: | - |
| logPath | No | Log file path; defaults to the sandbox Document directory |
| enableLocalLog | No | Whether to enable local logging; defaults to NO |

#### Initialize the RTC engine

After creating the `RTCEngineConfig` object, call the SDK's `initializeWithConfig` function to set the delegate and verify that initialization succeeded.

```objectivec theme={null}
RTCEngineError errorCode = [[RTCEngineKit sharedEngine] initializeWithConfig:self.engineConfig appGroup:@"Application Group Identifier" delegate:self];
if (errorCode != RTCEngineErrorOK) {
    NSLog(@"Failed to initialize the RTC service");
}
```

#### Set the delegates

The SDK has two event protocols; implement each according to which events it owns:

* `RTCEngineDelegate`: process-level events (audio route changes, network speed tests, app performance), passed in during initialization;
* `RTCEngineChannelDelegate`: in-channel events (connection, users, messages, streams, screen sharing), passed in when you create a channel instance.

```objectivec theme={null}
@interface YourClass : NSObject <RTCEngineDelegate, RTCEngineChannelDelegate>
/// Add any of the following callbacks here as needed.
```

#### Implement the callbacks

The first parameter of every `RTCEngineChannelDelegate` callback is the channel instance the event comes from. In multi-channel scenarios, use it to tell which channel an event belongs to; the channel name is available from `channel.channel`.

* **Join succeeded callback**

```objectivec theme={null}
/// Join succeeded callback
/// @param channel channel instance the event comes from
/// @param userId user ID
- (void)engineChannel:(RTCEngineChannel *)channel onJoinSucceed:(NSString *)userId {
    
    NSLog(@"Joined the channel: channel = %@, userId = %@", channel.channel, userId);
}
```

* **Local user data updated callback**

```objectivec theme={null}
/// Local user data updated callback
/// @param channel channel instance the event comes from
/// @param userId user ID
- (void)engineChannel:(RTCEngineChannel *)channel onUserUpdate:(NSString *)userId {
    
    NSLog(@"Local user data updated: channel = %@, userId = %@", channel.channel, userId);
}
```

* **Reconnecting callback**

```objectivec theme={null}
/// Reconnecting callback
/// @param channel channel instance the event comes from
- (void)engineChannelOnReconnecting:(RTCEngineChannel *)channel {
    
    NSLog(@"Connection lost, the SDK is trying to reconnect channel = %@", channel.channel);
}
```

* **Reconnected callback**

```objectivec theme={null}
/// Reconnected callback
/// @param channel channel instance the event comes from
- (void)engineChannelOnReconnected:(RTCEngineChannel *)channel {
    
    NSLog(@"Service connected/reconnected channel = %@", channel.channel);
}
```

* **Disconnected callback**

```objectivec theme={null}
/// Disconnected callback
/// Triggered by an unrecoverable error or when you leave the channel involuntarily; you need to get a new token after this event
/// @param channel channel instance the event comes from
/// @param reason leave reason
/// @param errCode error code
/// @param errMsg error message
- (void)engineChannel:(RTCEngineChannel *)channel onDisconnected:(RTCLeaveReason)reason errCode:(RTCEngineError)errCode errMsg:(nullable NSString *)errMsg {
    
    NSLog(@"Disconnected or removed from the channel, please log in again channel = %@, reason = %ld, errCode = %ld, errMsg = %@", channel.channel, (long)reason, (long)errCode, errMsg);
}
```

* **Custom message callback**

```objectivec theme={null}
/// Custom message callback
/// @param channel channel instance the event comes from
/// @param content message content
/// @param action message action
/// @param userId user ID
/// @param sessionId session ID
/// @param nickname user nickname
- (void)engineChannel:(RTCEngineChannel *)channel onCustomMessage:(NSString *)content action:(NSString *)action userId:(nullable NSString *)userId sessionId:(nullable NSString *)sessionId nickname:(nullable NSString *)nickname {
    
    NSLog(@"Received a custom message channel = %@, action = %@, content = %@", channel.channel, action, content);
}
```

* **Channel updated callback**

```objectivec theme={null}
/// Channel updated callback
/// @param channel channel instance the event comes from
/// @param props custom data
- (void)engineChannel:(RTCEngineChannel *)channel onChannelUpdate:(NSString *)props {
    
    NSLog(@"Channel data updated channel = %@, props = %@", channel.channel, props);
}
```

* **User joined callback**

```objectivec theme={null}
/// User joined callback
/// @param channel channel instance the event comes from
/// @param userId user ID
- (void)engineChannel:(RTCEngineChannel *)channel onRemoteUserJoinChannel:(NSString *)userId {
    
    NSLog(@"A user joined the channel channel = %@, userId = %@", channel.channel, userId);
}
```

* **User data updated callback**

```objectivec theme={null}
/// User data updated callback
/// @param channel channel instance the event comes from
/// @param userId user ID
- (void)engineChannel:(RTCEngineChannel *)channel onRemoteUserUpdate:(NSString *)userId {
    
    NSLog(@"User data updated channel = %@, userId = %@", channel.channel, userId);
}
```

* **User left callback**

```objectivec theme={null}
/// User left callback
/// @param channel channel instance the event comes from
/// @param userId user ID
/// @param reason leave reason
- (void)engineChannel:(RTCEngineChannel *)channel onRemoteUserLeaveChannel:(NSString *)userId reason:(RTCLeaveReason)reason {
    
    NSLog(@"A user left the channel channel = %@, userId = %@", channel.channel, userId);
}
```

* **User stream changed callback**

```objectivec theme={null}
/// User stream changed callback
/// @param channel channel instance the event comes from
/// @param userId user ID
/// @param streamTrackModel stream track data
/// @param changeType change type
- (void)engineChannel:(RTCEngineChannel *)channel onRemoteStreamTrackChange:(NSString *)userId streamTrackModel:(RTCEngineStreamTrackModel *)streamTrackModel changeType:(RTCChangeType)changeType {
    
    NSLog(@"User stream changed channel = %@, userId = %@, streamTrackModel = %@", channel.channel, userId, streamTrackModel);
}
```

### Step 2: Create a channel instance and join the channel

#### Create a channel instance

```objectivec theme={null}
RTCEngineChannel *channel = [[RTCEngineKit sharedEngine] createChannelWithDelegate:self];
/// The engine holds the channel instance; just keep your own reference to it
self.channel = channel;
```

To join multiple channels at the same time, call `createChannelWithDelegate:` multiple times and hold each instance separately. User data, stream statistics, and rendering don't interfere across instances.

#### Join the channel

```objectivec theme={null}
RTCEngineError errorCode = [self.channel joinChannelWithToken:@"Your Token"];
if (errorCode != RTCEngineErrorOK) {
    NSLog(@"Failed to join the channel");
}
```

#### Leave the channel

```objectivec theme={null}
[self.channel leaveChannel:^{
    /// TO DO...
}];
```

#### Destroy the channel instance

You must destroy the instance when you're done with it; otherwise the engine keeps holding the channel.

```objectivec theme={null}
[self.channel destroy];
self.channel = nil;
```

### Step 3: Publish video

The camera is process-level shared hardware, so capture and preview are controlled through the `RTCEngineKit` singleton; whether that video is published to a given channel is controlled separately by that channel instance's `publishLocalVideo:`.

#### Start the preview

```objectivec theme={null}
[[RTCEngineKit sharedEngine] startLocalPreview:YES view:self.localView];
```

#### Update the preview

```objectivec theme={null}
[[RTCEngineKit sharedEngine] updateLocalView:self.localView];
```

#### Stop the preview

```objectivec theme={null}
[[RTCEngineKit sharedEngine] stopLocalPreview];
```

#### Resume/pause publishing to the current channel

```objectivec theme={null}
[self.channel publishLocalVideo:YES];
```

### Step 4: Subscribe to and unsubscribe from remote video

#### Subscribe to a remote user's video

```objectivec theme={null}
[self.channel startRemoteView:userId trackId:trackId view:self.previewView];
```

* **The following table describes all values of the `RTCTrackIdentifierFlags` track ID enum.**

| **Enum name** | **Value** | **Description** |
| - | :-: | - |
| RTCTrackIdentifierFlags0 | `0` | Track 0 |
| RTCTrackIdentifierFlags1 | `1` | Track 1 |
| RTCTrackIdentifierFlags2 | `2` | Track 2 |
| RTCTrackIdentifierFlags3 | `3` | Track 3 |
| RTCTrackIdentifierFlags4 | `4` | Track 4 |
| RTCTrackIdentifierFlags5 | `5` | Track 5 |
| RTCTrackIdentifierFlags6 | `6` | Track 6 |

#### Update a remote user's video

```objectivec theme={null}
[self.channel updateRemoteView:userId trackId:trackId view:self.previewView];
```

#### Unsubscribe from a remote user's video

```objectivec theme={null}
[self.channel stopRemoteView:userId trackId:trackId];
```

#### Unsubscribe from all video streams of a specific remote user

```objectivec theme={null}
[self.channel stopAllRemoteViewWithUserId:userId];
```

### Step 5: Release resources

`destroy` first destroys all live channel instances and waits for them to finish leaving before releasing process-level resources, so you don't need to call `destroy` on each channel instance yourself.

```objectivec theme={null}
[[RTCEngineKit sharedEngine] destroy];
```
