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

# Device management

> Enumerate and switch cameras, microphones, and speakers in the SMeeting Swift SDK, choose audio output on macOS or toggle the speakerphone on iOS, and handle device plug and unplug events. Read when building a device picker or a pre-meeting device check page.

### Overview

The device APIs are all on `SMeetingEngine`, so you don't need to handle the platform differences between iOS and macOS yourself:

| Capability | API | Platform |
| - | - | - |
| Enumerate devices | `getDevices(kind:)` | All |
| Switch cameras | `switchCamera(deviceId:)` | All |
| Switch microphones | `switchMic(deviceId:)` | All |
| Choose the audio output device | `setAudioOutput(deviceId:)` | macOS only |
| Toggle the speakerphone | `setSpeakerOutputEnabled(_:)` | iOS only |

Device capabilities **don't depend on meeting state**: after logging in (even before entering a meeting), you can enumerate devices and listen for plug and unplug events, which makes it easy to build a "pre-meeting device check" page.

***

### Enumerate devices

```swift theme={null}
let cameras = meeting.getDevices(kind: .videoInput)
let microphones = meeting.getDevices(kind: .audioInput)
let speakers = meeting.getDevices(kind: .audioOutput)

let all = meeting.getDevices()   // Returns all devices when kind is omitted
```

The returned `DeviceInfo` contains `deviceId`, `name`, `kind`, and `isDefault`.

Watch out for platform differences:

* **macOS**: the audio lists include a virtual device that points to the system default input / output
* **iOS audio input**: returns the available audio input routes (Bluetooth, wired headset, built-in microphone, and so on)
* **iOS audio output**: the system doesn't provide enumeration, so `kind: .audioOutput` returns an empty array on iOS—output control on iOS is a Boolean "speakerphone or not" switch; see below

***

### Switch cameras

```swift theme={null}
// Specify a device
try await meeting.switchCamera(deviceId: deviceId)

// iOS: switch between front and back cameras
try await meeting.switchCamera()
```

Calling it while the camera is off throws `SMeetingError.deviceError`. If the user picks a device in a dropdown **before turning on the camera**, we recommend storing the choice in your app state first and passing it in when you call `requestOpenCamera(deviceId:)`.

***

### Switch microphones

```swift theme={null}
try meeting.switchMic(deviceId: deviceId)
```

This method doesn't throw a "microphone not on" error; it adapts to the current state:

* **Microphone on**: switches the input device being captured
* **Microphone off (iOS)**: preselects the audio input route, for example choosing Bluetooth headphones before entering the meeting
* **Microphone off (macOS)**: the recording pipeline hasn't started yet, so the call returns directly without doing anything or reporting an error

***

### Audio output

The two platforms have different capability models on the output side, so the APIs are split as well.

#### macOS: choose the output device

```swift theme={null}
try meeting.setAudioOutput(deviceId: deviceId)
```

Use it to choose among hardware such as the built-in speakers, USB sound cards, and Bluetooth speakers.

#### iOS: speakerphone switch

```swift theme={null}
try meeting.setSpeakerOutputEnabled(true)   // Force the speakerphone
try meeting.setSpeakerOutputEnabled(false)  // Go back to the system default output (earpiece / Bluetooth / wired headset)
```

On iOS, specific output routes such as Bluetooth and AirPlay are chosen by the user through Control Center or `AVRoutePickerView`; apps can't specify them directly.

To set "use the speakerphone by default long-term," or to read the actual current route and listen for route changes, use the APIs in [Audio routing](/en/meeting/swift/advanced/audio-routing)—`setSpeakerOutputEnabled(_:)` and `setAudioRoute(_:)` there are two ways of writing the same mechanism.

#### How it differs from "speaker mute"

These two things are orthogonal; don't mix them up:

| Purpose | API |
| - | - |
| Whether to hear remote audio | `toggleRemoteAudioMute(_:)` |
| Which hardware plays the sound | `setAudioOutput(deviceId:)` / `setSpeakerOutputEnabled(_:)` |

***

### Listen for device plug and unplug events

Device changes are reported through `SMeetingDelegate`:

```swift theme={null}
func meeting(_ meeting: SMeetingEngine, didAddDevice data: DeviceChangeEventData) {
    refreshDeviceList()
}

func meeting(_ meeting: SMeetingEngine, didRemoveDevice data: DeviceChangeEventData) {
    refreshDeviceList()
}
```

On iOS, audio route changes such as plugging in or unplugging a headset and connecting / disconnecting Bluetooth are also reported as "devices" through these two events.

Recommended practice:

* In the callbacks, only refresh the device list state, and let the UI layer update the pickers from the new list automatically
* If the currently selected `deviceId` has disappeared from the list, fall back to the default device

```swift theme={null}
func refreshDeviceList() {
    cameras = meeting.getDevices(kind: .videoInput)
    if let id = selectedCameraId, !cameras.contains(where: { $0.deviceId == id }) {
        selectedCameraId = nil    // Fall back to the default
    }
}
```

> When a device in use is unplugged, the SDK falls back to an available device on its own, so you don't need to build a whole fault-tolerance layer. You should still refresh the UI, though, so it doesn't keep showing a device that no longer exists.

***

### Related pages

* [Media control](/en/meeting/swift/advanced/media-control)
* [API reference - Devices](/en/meeting/swift/api-reference/devices)
