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

# Recording and composite layout

> Control server-side recording and stream mixing tasks from the SMeeting Swift SDK: build LayoutData and McuStartReq, start and stop tasks, change the composite layout during the meeting, query recording config and details, and handle recording state events. Read when adding a record button.

### Overview

Recording and compositing are both done on the server; the client only sends commands and shows the status. Task types are distinguished by `McuTaskType`:

| Type | Description |
| - | - |
| `.record` | Video recording, producing a recording file |
| `.mix` | Stream mixing, compositing multiple videos into one stream for viewers to pull |
| `.mixAndRecord` | Stream mixing plus recording |

You can also use `MeetingCreateReq.autoRecord` when creating the meeting so that recording starts automatically once the meeting begins.

***

### Build the layout and task parameters

`LayoutData`, `McuStartReq`, and the layout's `Watermark` / `Tag` / `Cell` / `DivList` can all be constructed directly; optional parameters have default values:

```swift theme={null}
// Simplest: 4-grid, everything else uses defaults
let layout = LayoutData(layout: .grids4)

// With a watermark and name tags
let layout = LayoutData(
    layout: .grids4,
    pollingDur: 0,
    watermark: Watermark(type: 2, text: "Internal meeting"),
    tag: Tag(type: "LB")
)

// Pin specified members to specified grid cells
let layout = LayoutData(
    layout: .grids4,
    divList: [
        DivList(
            cell: [Cell(idx: 0, bindShare: true, tag: Tag(type: "LB"))],
            uids: ["u1001"]
        )
    ]
)
```

These types are also `Codable`; the mapping between fields and JSON keys is in the tables below—if your layout configuration is JSON delivered by your backend, you can also decode it directly with `JSONDecoder`.

***

### Layout fields

`LayoutData`:

| Field | JSON key | Type | Description |
| - | - | - | - |
| `layout` | `layout` | `LayoutType` | Layout type, such as `auto`, `grids_4`, `right_4`, `full` |
| `pollingDur` | `polling_dur` | `Int?` | Polling interval; `0` means no polling |
| `watermark` | `watermark` | `Watermark?` | Watermark configuration |
| `tag` | `tag` | `Tag?` | Video label (name tag) configuration |
| `divList` | `div_list` | `[DivList]?` | Logical blocks that pin specified members to specified grid cells |

`Watermark`:

| Field | JSON key | Type | Description |
| - | - | - | - |
| `type` | `type` | `Int` | `0` default, `1` none, `2` single row, `3` multiple rows |
| `text` | `text` | `String` | Specified content; empty means the meeting title is used automatically |
| `size` | `size` | `Int?` | Font size; `0` is the default |
| `color` | `color` | `String?` | Font color |
| `olColor` | `ol_color` | `String?` | Outline color |
| `olWidth` | `ol_width` | `Int?` | Outline width |

`Tag`:

| Field | JSON key | Type | Description |
| - | - | - | - |
| `type` | `type` | `String` | Combination of position letters: `L` left, `R` right, `T` top, `B` bottom |
| `text` | `text` | `String` | Specified content; empty means the in-meeting display name is used automatically |
| `size` | `size` | `Int?` | Font size |
| `color` | `color` | `String?` | Font color |
| `bgColor` | `bg_color` | `String?` | Background color |

`DivList` and `Cell`:

| Field | JSON key | Type | Description |
| - | - | - | - |
| `DivList.cell` | `cell` | `[Cell]` | Grid cells in this block |
| `DivList.uids` | `uids` | `[String]` | Members pinned in this block |
| `Cell.idx` | `idx` | `Int` | Grid cell index |
| `Cell.bindShare` | `bind_share` | `Bool` | Whether to bind the shared video first |
| `Cell.tag` | `tag` | `Tag` | Label configuration for this cell |

For all `LayoutType` enum values, see [Types](/en/meeting/swift/types#layouttype).

***

### Start and stop tasks

```swift theme={null}
let req = McuStartReq(
    taskType: .mixAndRecord,
    title: "Weekly project sync",
    userName: "Alice",
    layoutData: LayoutData(layout: .grids4)
)

try await meeting.mcuStart(meetingId: meetingId, req: req)

// When stopping, specify which type of task to stop
try await meeting.mcuStop(meetingId: meetingId, taskType: .mixAndRecord)
```

`McuStartReq` fields:

| Field | JSON key | Type | Description |
| - | - | - | - |
| `taskType` | `task_type` | `McuTaskType` | Task type |
| `title` | `title` | `String` | Recording file title |
| `userName` | `user_name` | `String` | Operator name |
| `layoutData` | `layout_data` | `LayoutData` | Composite layout |

***

### Change the composite layout during the meeting

```swift theme={null}
try await meeting.adminUpdateLayout(layout)
```

Requires the host / co-host role. After the change, the server recomposites with the new layout; clients currently pulling the composite video don't need to resubscribe.

***

### Query recording configuration and details

```swift theme={null}
// Default recording configuration at the app level
let config = try await meeting.mcuRecordConfig()

// Recording details of a meeting
let detail = try await meeting.mcuRecordDetail(meetingId: meetingId)
```

Frequently used fields in `McuRecordDetail`:

| Field | Description |
| - | - |
| `taskStatus` | `McuTaskStatus`: `.running` in progress / `.normal` ended normally / `.exception` ended abnormally |
| `errDesc` | The reason when it ended abnormally |
| `vodKey` | Storage key of the recording file; use it with `presignedGetObject(resKey:)` to get a download URL |
| `vodSize` | File size |
| `mcuAt` / `mcuDur` | Recording start time and duration |

***

### Recording state events

When the task state changes, all members in the meeting receive:

```swift theme={null}
func meeting(_ meeting: SMeetingEngine, roomMcuTask data: RoomMcuTaskEventData) {
    // data.taskType   task type
    // data.taskStatus task status
    // data.errDesc    error description
}
```

You can also read the meeting's current recording status directly from `RoomInfo.recordStatus`, which suits initializing a "recording" badge when entering the meeting.

***

### Watch the composite video

Once a stream mixing task is running, clients can pull a single composite video instead of subscribing to members' video one by one; see [Video rendering](/en/meeting/swift/advanced/video-rendering).

***

### Related pages

* [Meeting materials](/en/meeting/swift/advanced/resources)
* [Video rendering](/en/meeting/swift/advanced/video-rendering)
* [Types](/en/meeting/swift/types)
