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

# Audio routing

> Manage audio output routing (speaker, earpiece, wired headset, Bluetooth headset) in the SMeeting Android SDK: get AudioRouterManager from MeetingEngine, set the mode and auto-switch policy, handle device callbacks, switch routes manually, and release it.

Audio routing is managed by `AudioRouterManager`, and it behaves the same as in SRTC. In SMeeting you get and release it through `MeetingEngine`:

```kotlin theme={null}
var audioRouterManager = meetingEngine.getAudioRouterManager()

meetingEngine.releaseAudioRouterManager()
audioRouterManager = null
```

The `AudioRouterManager` type itself still comes from the RTC package: `cn.seastart.rtc.media.audioRouter.AudioRouterManager`.

***

## 1. Get `AudioRouterManager`

In SMeeting, get it through the current `MeetingEngine`:

```kotlin theme={null}
var audioRouterManager = meetingEngine.getAudioRouterManager()
```

The corresponding release call:

```kotlin theme={null}
meetingEngine.releaseAudioRouterManager()
audioRouterManager = null
```

### Notes

* `getAudioRouterManager()` returns a nullable type; handle null in your app.
* In the current `meetingSDK` implementation, this method passes through to the underlying RTC engine.
* We recommend holding it in one place on the **meeting screen / call screen**, rather than getting and initializing it again on every button tap.

***

## 2. Recommended initialization order

Based on the `meetingSDK` demo, we recommend initializing in the following order after entering the meeting screen:

1. Get the manager with `meetingEngine.getAudioRouterManager()`
2. Set the callback listener with `setAudioRouterCalllback(...)`
3. Set the audio mode with `setMode(...)`
4. Set the auto-switch policy with `setAutoChangeAudioRouter(...)`
5. Call `init()` to start route monitoring

Full example:

```kotlin theme={null}
// Imports are omitted in the following example

private var audioRouterManager: AudioRouterManager? = null

private fun initAudioRouterManager(meetingEngine: MeetingEngine) {
    audioRouterManager = meetingEngine.getAudioRouterManager()

    // Note: the API name is setAudioRouterCalllback (Calllback has 3 l's)
    audioRouterManager?.setAudioRouterCalllback(object :
        AudioRouterManager.AudioRouterCallback {

        override fun exitOutputDeviceChange(
            audioOutputDevices: HashMap<AudioRouterManager.AudioOutputDeviceType?, AudioDeviceInfo?>
        ) {
            // The list of currently available output devices changed
            // For example: a wired headset was plugged in, a Bluetooth headset connected, a headset was unplugged
        }

        override fun activeOutputDeviceChange(
            audioOutputDevice: Pair<AudioRouterManager.AudioOutputDeviceType, AudioDeviceInfo?>
        ) {
            // The output device actually in effect changed
        }

        override fun onAudioBecomingNoisy() {
            // When a headset disconnects and similar, the system may switch to the speaker
        }
    })

    // The demo's meeting scenario currently uses MODE_IN_COMMUNICATION
    audioRouterManager?.setMode(AudioManager.MODE_IN_COMMUNICATION)

    // The demo's actual policy: speaker over earpiece; Bluetooth over wired headset
    audioRouterManager?.setAutoChangeAudioRouter(true, true, false)

    // Start route monitoring
    audioRouterManager?.init()
}
```

This matches the current implementation in the demo's `MeetingActivity.kt`:

```kotlin theme={null}
audioRouterManager = MeetingEngineHelper.getInstance().session.getAudioRouterManager()
audioRouterManager?.setAudioRouterCalllback(/* callback */)
audioRouterManager?.setMode(AudioManager.MODE_IN_COMMUNICATION)
audioRouterManager?.setAutoChangeAudioRouter(true, true, false)
audioRouterManager?.init()
```

***

## 3. Using `setMode`

Recommended for meeting / call scenarios:

```kotlin theme={null}
audioRouterManager?.setMode(AudioManager.MODE_IN_COMMUNICATION)
```

For the common modes, refer to `AudioManager`, for example:

* `AudioManager.MODE_NORMAL`
* `AudioManager.MODE_RINGTONE`
* `AudioManager.MODE_IN_CALL`
* `AudioManager.MODE_IN_COMMUNICATION`

### Notes

* The current project's `minSdk = 24`.
* In the current integration, we recommend that your app **set the mode explicitly** rather than rely on the default.
* If your app has different audio scenarios such as meetings, playback, and voice chat, we recommend calling `setMode(...)` again when switching scenarios.

***

## 4. `setAutoChangeAudioRouter` auto-switch policy

### 4.1 Basic usage

```kotlin theme={null}
audioRouterManager?.setAutoChangeAudioRouter(true)
```

### 4.2 Full usage

```kotlin theme={null}
audioRouterManager?.setAutoChangeAudioRouter(
    isAutoChange = true,
    isPrioritySpeaker = true,
    isPriorityWiredEarphone = false
)
```

Parameters:

* `isAutoChange`
  * `true`: the route is switched automatically internally
  * `false`: device changes are only monitored; your app switches manually
* `isPrioritySpeaker`
  * `true`: the speaker has higher priority than the earpiece
  * `false`: the earpiece has higher priority than the speaker
* `isPriorityWiredEarphone`
  * `true`: a wired headset has higher priority than a Bluetooth headset
  * `false`: a Bluetooth headset has higher priority than a wired headset

### 4.3 Policy used in the current demo

`MeetingActivity.kt` uses:

```kotlin theme={null}
audioRouterManager?.setAutoChangeAudioRouter(true, true, false)
```

This means:

* Auto-switching is on
* The speaker takes priority over the earpiece
* A Bluetooth headset takes priority over a wired headset

This suits meeting scenarios that play audio out loud.

***

## 5. Callbacks

### 5.1 Available output devices changed

```kotlin theme={null}
override fun exitOutputDeviceChange(
    audioOutputDevices: HashMap<AudioRouterManager.AudioOutputDeviceType?, AudioDeviceInfo?>
) {
}
```

Indicates that the current "list of selectable devices" has changed, for example:

* A Bluetooth headset connected / disconnected
* A wired headset was plugged in / unplugged
* The system's output device capabilities changed

### 5.2 Active output device changed

```kotlin theme={null}
override fun activeOutputDeviceChange(
    audioOutputDevice: Pair<AudioRouterManager.AudioOutputDeviceType, AudioDeviceInfo?>
) {
}
```

Indicates that the audio output device actually in effect has changed.

If your UI shows "currently using the speaker / earpiece / Bluetooth headset," we recommend relying on this callback first.

### 5.3 A route change may cause noise

```kotlin theme={null}
override fun onAudioBecomingNoisy() {
}
```

Typical scenarios:

* A headset was unplugged
* A Bluetooth headset disconnected
* The system automatically switched from the headset to the speaker

At this point you can apply some safeguards in your app, such as:

* Lowering the volume
* Pausing playback
* Notifying the user

***

## 6. Get the available devices and the active device

### 6.1 Get the currently available output devices

```kotlin theme={null}
val devices = audioRouterManager?.getExitAudioOutputDevices()
devices?.forEach { (type, info) ->
    val name = info?.productName?.toString() ?: type?.name.orEmpty()
}
```

### 6.2 Get the currently active output device

```kotlin theme={null}
val activeDevice = audioRouterManager?.getActiveAudioOutputDevice()
val activeType = activeDevice?.first
val activeInfo = activeDevice?.second
```

### 6.3 Correct the Bluetooth name (optional)

```kotlin theme={null}
AudioRouterManager.getValidBluetoothName(
    curName = activeInfo?.productName?.toString().orEmpty(),
    context = this
) { validName ->
    // validName is the corrected Bluetooth name
}
```

> To get a more accurate Bluetooth name, handle Bluetooth permissions according to the OS version; on Android 12 and later you usually need to take care of `BLUETOOTH_CONNECT`.

***

## 7. Switch routes manually

Your app can switch actively when the user taps the UI:

```kotlin theme={null}
audioRouterManager?.switchAudioRouter(selectType)
```

Here `selectType` is an `AudioOutputDeviceType`, for example:

```kotlin theme={null}
audioRouterManager?.switchAudioRouter(AudioRouterManager.AudioOutputDeviceType.SPEAKER)
audioRouterManager?.switchAudioRouter(AudioRouterManager.AudioOutputDeviceType.EARPIECE)
audioRouterManager?.switchAudioRouter(AudioRouterManager.AudioOutputDeviceType.WIRED_EARPHONE)
audioRouterManager?.switchAudioRouter(AudioRouterManager.AudioOutputDeviceType.BLUETOOTH_HEADSET)
```

The current demo reads the list of available devices and then shows a dialog for the user to choose from:

```kotlin theme={null}
val audioOutputDevices = audioRouterManager?.exitAudioOutputDevices
// Build the options from the device list
audioRouterManager?.switchAudioRouter(selectType)
```

### Special value: `UN_KNOW`

```kotlin theme={null}
audioRouterManager?.switchAudioRouter(AudioRouterManager.AudioOutputDeviceType.UN_KNOW)
```

This doesn't mean switching to an "unknown device"; it means:

**Choose the most suitable output route again according to the current automatic routing policy.**

Suitable for scenarios such as:

* Restoring the default recommended route
* Explicitly asking the SDK to choose a route again after a device is plugged in or unplugged

***

## 8. Release resources

We recommend calling this when the meeting screen exits / is destroyed:

```kotlin theme={null}
meetingEngine.releaseAudioRouterManager()
audioRouterManager = null
```

What the current demo actually does:

```kotlin theme={null}
private fun releaseAudioRouterManager() {
    MeetingEngineHelper.getInstance().session.releaseAudioRouterManager()
    audioRouterManager = null
}

override fun onDestroy() {
    super.onDestroy()
    releaseAudioRouterManager()
}
```

***

## 9. Common device enums

```kotlin theme={null}
AudioRouterManager.AudioOutputDeviceType.UN_KNOW
AudioRouterManager.AudioOutputDeviceType.SPEAKER
AudioRouterManager.AudioOutputDeviceType.EARPIECE
AudioRouterManager.AudioOutputDeviceType.WIRED_EARPHONE
AudioRouterManager.AudioOutputDeviceType.BLUETOOTH_HEADSET
```

Meanings:

* `UN_KNOW`: unknown / triggers automatic re-routing
* `SPEAKER`: speaker
* `EARPIECE`: earpiece
* `WIRED_EARPHONE`: wired headset
* `BLUETOOTH_HEADSET`: Bluetooth headset

***

## 10. Recommended practices

1. **Integrate through the APIs exposed by `MeetingEngine`**; don't depend on the underlying `rtcEngine` directly in your app.
2. **Initialize when entering the meeting screen and release when leaving it**, to avoid repeatedly calling `init()` / `release()` from a single button tap.
3. **Set `setMode(...)` again every time you switch your app's audio scenario**.
4. When showing "the device currently in use," rely on `activeOutputDeviceChange(...)`.
5. When showing "the list of currently selectable devices," rely on `exitOutputDeviceChange(...)` or `getExitAudioOutputDevices()`.
6. We recommend fully verifying switching among Bluetooth / wired headset / speaker / earpiece on real devices.
7. `AudioRouterManager` is an Engine-level cached object; callers shouldn't call its `release()` directly—always use `meetingEngine.releaseAudioRouterManager()`.
