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

> Code snippets for the SMeeting Web SDK: create an instance, check the environment, log in, create / enter / exit / end meetings, manage camera, mic, speaker, and screen sharing, send messages, host controls, device invitations, and recording. Read this to get a basic Web meeting running.

### Basic concepts

* SMeeting instance: create an instance with `new SMeeting`. Almost all APIs are exposed on this instance, and all events can be listened to through `SMeeting.onNotifyRoomEvent`. (If you want to enter multiple meetings at the same time, currently the only way is to create multiple instances.)
* On the home page, get a meeting token (from your backend API) and call smeeting.login. Only after login succeeds can you perform further operations such as creating a meeting or entering a meeting
* RoomInfo: the meeting information after entering a meeting with `smeeting.enterRoom`, available through `smeeting.getRoomInfo`
* `UserInfo`: information about users in the meeting (including yourself and other remote users). Get a single user's information with `smeeting.getUserInfo(uid: string)`, or get all online users in the meeting with `smeeting.getUsersInfo(true/false)`—true returns a map, false returns an array

### Initialize the SDK and check the environment

```typescript theme={null}
// Create an instance
const smeeting = new SMeeting({
  logLevel: LogLevel.DEBUG,
  logTarget: LogTarget.CONSOLE,
});

// Check environment information
const envinfo = smeeting.getEnvInfo();
console.log("environment", envinfo);
if(!envinfo.supported) {
  alert("SMeeting is not supported in this environment. Switch to the latest version of Chrome.");
} else {
  if(!envinfo.mediaDevices) {
    if(!envinfo.secure) {
      alert("This is not a secure context (https, localhost, or 127.0.0.1). Media devices such as the mic and camera cannot be accessed for audio and video capture and publishing.");
    } else {
      alert("This environment does not support accessing devices. Media devices such as the mic and camera cannot be accessed for audio and video capture and publishing.");
    }
  }
  if(!envinfo.h264Enc) {
    alert("This environment does not support H264 encoding. Publishing is unavailable.");
  }
  if(!envinfo.h264Dec) {
    alert("This environment does not support H264 decoding. Receiving streams is unavailable.");
  }
  if(!envinfo.screenshare) {
    alert("This environment does not support starting screen sharing.");
  }
} 

// Handle event callbacks
smeeting.onNotifyRoomEvent = (evt: RoomEvent) => {
  console.log('Meeting event received', evt);
  switch (evt.type) {
      // ...
  }
})
```

### Create a meeting

```typescript theme={null}
const createRoom = async () => {
    // Call your backend API to get a Meet grant
  
    let token = "Meet grant token returned by your backend";
    await smeeting.login(token);
    const { room_no, meeting_id } = await smeeting.createRoom({
        title: 'Weekly project sync',      // Meeting title
        meeting_mode: MeetingMode.Normal,  // Meeting mode (required): Normal / Mix (composite) / Voice / Training
    }); // Returns the meeting number and meeting ID
}
```

### Enter a meeting

```typescript theme={null}
const enterRoom = async () => {
   await smeeting.enterRoom({
        room_no: roomNo,  // Meeting number
        nickname: nickname, // Display name
    });
}
```

### Exit a meeting

```typescript theme={null}
await smeeting.exitRoom()
```

### End a meeting (dismiss)

```typescript theme={null}
await smeeting.adminDestroyRoom()
```

### Cancel a meeting

```typescript theme={null}
await smeeting.cancelRoom(meeting_id)
```

### Get room info / user info / user list

```typescript theme={null}
// Get room info
let roomInfo: RoomInfo = smeeting.getRoomInfo();
// Get a user's info
let userInfo: UserInfo = smeeting.getUserInfo(uid);
// Get the list of users in the room
let users: Record<string, UserInfo> = smeeting.getUsersInfo(true);
```

### Update the in-meeting display name

```typescript theme={null}
await smeeting.updateName(name)
```

### Get the device list

```typescript theme={null}
// kind: device category "audioinput" | "audiooutput" | "videoinput"
let freshDeviceList = async (kind?: MediaDeviceKind) => {
  if(!envinfo.mediaDevices) {
    return;
  }
  try {
    let devices = await smeeting.getDevices(kind);
    console.log("Got device list", devices);
    devices.forEach((device) => {
      // Later you can use deviceId to capture from a specific camera or mic, or play through a specific speaker (not supported in some environments)
      console.log(device.deviceId, device.kind, device.label);
      // Add to a select list in the UI
      // let opt = document.createElement('option');
      // opt.value = device.deviceId;
      // label can sometimes be an empty string; display label || deviceId in the UI
      // opt.text = device.label || device.deviceId;
      // select.append(opt);
    });
  } catch(err) {
    console.error("Failed to get device list", err);
  };
};
```

### Open, close, and switch the camera

```typescript theme={null}
/**
 * Open the camera
 * @param container Preview container
 * @param deviceId Camera device ID
 * @param preset Camera preset
 * @param byAdmin Whether this is a host operation
 * @param adminUid Host ID (which host requested opening)
 */
await smeeting.requestOpenCamera(container: HTMLElement, deviceId?: string, preset?: CameraPreset, byAdmin?: boolean, adminUid?: string)
/**
* Close the camera
*/
await smeeting.closeCamera()
 /**
 * Switch the camera
 * @param deviceId Camera device ID. On mobile, omit it to toggle between front and rear cameras; on desktop, you can specify a camera
 */
await smeeting.switchCamera(deviceId?: string)
```

### Start and stop playing a remote user's video

```typescript theme={null}
/**
 * Start playing a remote user's video
 * @param container Playback container
 * @param uid Remote user ID
 * @param trackDesc Video track description
 * @returns  RemoteVideoTrack 
 */
const track:RemoteVideoTrack = await smeeting.startPlayRemoteVideo(container: HTMLElement,uid: string, trackDesc: TrackDesc)
  /**
   * Stop playing a remote user's video
   * @param container Playback container
   * @param uid Remote user ID
   * @param trackDesc Video track description
   */
await smeeting.stopPlayRemoteVideo(container: HTMLElement, uid: string, trackDesc: TrackDesc)
```

### Open, close, and switch the mic

```typescript theme={null}
  /**
 * Open the mic
 * @param deviceId Mic device ID
 * @param preset Mic preset
  * @param admin_uid Host ID (which host requested opening)
 */
await smeeting.requestOpenMic(deviceId?: string, preset?: MicPreset, byAdmin?: boolean, adminUid?: string)
  /**
 * Close the mic
 */
await smeeting.closeMic()
/**
 * Switch the mic
 * @param deviceId Mic device ID
 */
await smeeting.switchMic(deviceId: string)
```

### Open, close, and switch the speaker

```typescript theme={null}
/**
 * Toggle mute / unmute for remote audio (a device can be specified)
 * @param mute Whether to mute
 * @param opt Speaker options
 */
await smeeting.toggleRemoteAudioMute(false,{deviceId?: string})  // Open and switch
await smeeting.toggleRemoteAudioMute(true)  // Close
```

### Request to start and stop sharing

```typescript theme={null}
/**
 * Start sharing
 * @param shareType Share type
 * @param container Preview container
 */
await smeeting.requestShare(shareType: ShareType = ShareType.Screen, preset?: ScreenPreset, container?: HTMLElement)
await smeeting.stopShare()
```

### Send a chat message

```typescript theme={null}
/**
     * Send a chat message, to one member or to everyone
     * @param msg_type Message type: 1 text, 2 file, 3 image, 4 voice
     * @param msg Message content
     * @param target_id Message recipient; empty means everyone in the room receives it
     */
await smeeting.sendRoomChatMessage(msg: string, target_id: string, msg_type: ChatMsgType = ChatMsgType.Text)
```

### Send a custom message

```typescript theme={null}
/**
 * Send a custom message, to one member or to everyone
 * @param content Message content
 * @param target_id Message recipient; empty means everyone in the room receives it
 */
await smeeting.sendRoomCustomMessage(content: string, target_id: string)
```

### Raise and lower a hand

```typescript theme={null}
enum HandupType {
    /**
     * Request to turn on the mic
     */
    Mic = 1,
    /**
     * Request to turn on the camera
     */
    Camera = 2,
    /**
     * Request to chat
     */
    Chat = 3
}
await smeeting.requestHandup(code:HandupType)
await smeeting.cancelHandup(code:HandupType)
```

### The host asks a remote user to open the camera, or closes a remote user's camera

```typescript theme={null}
    /**
     * Ask a remote user to open the camera
     * @param target_id User ID
     */
    await smeeting.adminRequestUserOpenCamera(target_id: string)
   /**
     * Close a remote user's camera
     * @param target_id User ID
     */
    await smeeting.adminCloseUserCamera(target_id: string)

```

### The host asks a remote user to open the mic, or closes a remote user's mic

```typescript theme={null}
 /**
 * Ask a remote user to open the mic
 * @param targetId User ID
 */
await smeeting.adminRequestUserOpenMic(target_id: string)
/**
 * Close a remote user's mic
 * @param target_id User ID
 */
await smeeting.adminCloseUserMic(target_id: string)
```

### The host removes a remote user from the room

```typescript theme={null}
 /**
     * Remove a remote user from the room
     * @param target_id User ID
     * @param join_disabled Whether to prevent the user from entering the meeting again
     */
   await smeeting.adminKickUserOut(target_id: string, join_disabled: boolean)
```

### The host updates the room's mic permission state (mute all and unmute all)

```typescript theme={null}
 /**
 * Update the room's mic permission state (mute all and unmute all)
 * @param self_unmute_mic_disabled Whether to disable self-unmute
 * @param mic_disabled Whether to disable the mic
 */
await smeeting.adminUpdateRoomMicState(self_unmute_mic_disabled: boolean, mic_disabled: boolean)
```

### The host updates whether members can unmute their own mic

```typescript theme={null}
/**
 * Update whether members can unmute their own mic
 * @param self_unmute_mic_disabled Whether to disable self-unmute
 */
await smeeting.adminUpdateRoomSelfUnmuteMicDisabled(self_unmute_mic_disabled: boolean)
```

### The host updates whether members can turn their own camera back on

```typescript theme={null}
/**
 * Update whether members can turn their own camera back on
 * @param self_unmute_camera_disabled Whether to disable self-unmute
 */
await smeeting.adminUpdateRoomSelfUnmuteCameraDisabled(self_unmute_camera_disabled: boolean)
```

### The host updates the room's camera permission state

```typescript theme={null}
/**
 * Update the room's camera permission state
 * @param self_unmute_camera_disabled Whether to disable self-unmute
 * @param camera_disabled Whether to disable the camera
 */
await smeeting.adminUpdateRoomCameraState(self_unmute_camera_disabled: boolean, camera_disabled: boolean)
```

### The host invites a device to the meeting

```typescript theme={null}
 /**
   * The host invites a device to the meeting
   * @param agents Devices to invite {type: device type, contact: device identifier}
   * @param no Room number
  */
await smeeting.adminInviteAgent(agents:{ type: AgentType, contact: string }[], no: string)
```

### The host updates the invitees

```typescript theme={null}
 /**
   * Update the invitees
   * @param conferee Array of invitee IDs
  */
await smeeting.adminUpdateConferee(conferee: string[])
```

### The host updates the layout of a composite-mode meeting

```typescript theme={null}
 /**
 * Update the layout of a composite-mode meeting
 * @param layoutData Layout data
 */
await smeeting.adminUpdateLayout(layoutData: LayoutData)
```

### Get the device list

```typescript theme={null}
 /**
 * Device list
 * @param type Device type
 * @param name Name
 * @param page Page number
 * @param perPage Items per page
 */
smeeting.agentList(type: AgentType[], name: string, page: number, perPage: number): Promise<{
      data: AgentInfo[];
      _meta: MetaRes;
  }>
```

### Recording

```typescript theme={null}
/**
 * Start recording
 * @param req Recording parameters
 */
await smeeting.mcuStart(req: McuStartReq)
/**
 * Stop recording
 */
await smeeting.mcuStop()
/**
 * Get the recording configuration
 */
const mcuRecordConfig:McuRecordConfig = await smeeting.mcuRecordConfig()
/**
 * Get recording details
 */
const mcuRecordDetail:McuRecordDetail = await smeeting.mcuRecordDetail()
```

### Remote composite video

```typescript theme={null}
/**
 * Start playing the remote composite video
 * @param container Playback container
 * @returns 
 */
const track:RemoteVideoTrack = await startPlayRemoteVideoMcu(container: HTMLElement)

 /**
 * Stop playing the remote composite video
 * @param container Playback container
 */
await stopPlayRemoteVideoMcu(container: HTMLElement)
```
