> ## 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 Android SRTC flow in Kotlin: create and initialize RTCEngine, register channel and media callbacks, join a channel, explicitly start camera/microphone/screen capture and publish, subscribe to remote video, then leave and release resources. Read after completing integration.

This page walks through the minimal working flow of the Android SRTC SDK in this order: create the Engine → bind callbacks → join a channel → start capture and publish → subscribe to remote media → leave and release.

Before you start, complete the following:

* Configure the Maven repository, SDK dependency, and basic environment as described in [Integration](/en/rtc/android/integration).
* Prepare a channel `token` issued by your server.
* Request camera and microphone runtime permissions in your app.
* To manage output devices such as the speaker, earpiece, or Bluetooth headsets, see [Audio routing](/zh/rtc/android/advanced/audio-routing) (Chinese).

Use `cn.seastart.rtc.media.original.render.VcsPlayerGlTextureView` or `VcsPlayerGlSurfaceView` for preview and remote display views, with this package path in both code imports and XML layouts.

## Step 1: Create and initialize `RTCEngine`

`RTCEngine.create(...)` requires an Engine-level error listener. It receives errors that don't belong to any channel callback, as well as blocking errors such as calling a channel that hasn't started; `channelId` is `null` when it can't be determined.

```kotlin theme={null}
private lateinit var rtcEngine: RTCEngine

fun initRtcSdk(application: Application) {
    rtcEngine = RTCEngine.create(
        app = application,
        enableLocalLog = true,
        engineEvent = object : RTCEngineSimpleEvent() {
            override fun onError(channelId: String?, errorCode: Int, message: String?) {
                // Log or display Engine errors in one place
            }
        },
        localLogPath = null,
        version = "app: ${BuildConfig.VERSION_NAME}"
    )
    rtcEngine.initSDK()
}
```

For full parameter descriptions, see [RTCEngine](/zh/rtc/android/api-reference/RTCEngine) (Chinese) and [RTCEngineEvent](/zh/rtc/android/api-reference/RTCEngineEvent) (Chinese).

## Step 2: Prepare channel and media callbacks

Every `join(...)` call takes its own `RTCClientEvent` for that channel. If you only override a few events, extend `RTCClientSimpleEvent` instead of implementing the full interface.

```kotlin theme={null}
private val clientEvent = object : RTCClientSimpleEvent() {
    override fun onJoinSucceed(channel: String, uid: String, whiteBoard: String?) {
        // Actually joined the channel; update the UI or start publishing here
    }

    override fun onJoinFailed(channel: String?, statusCode: Int) {
        // Failed to join; see the error codes page for statusCode
    }

    override fun onRemoteUserJoin(channel: String, uid: String) {
        // Maintain the user list for this channel
    }

    override fun onStreamTrackAdd(
        uid: String,
        channel: String,
        trackId: String,
        trackDesc: String
    ) {
        subscribeRemoteVideo(uid, trackId, trackDesc)
    }

    override fun onDisconnected(
        channel: String,
        leaveReason: LeaveReason,
        statusCode: Int,
        message: String
    ) {
        // An unrecoverable disconnect occurred in this channel
    }
}

rtcEngine.setRtcMediaEvent(object : RTCMediaSimpleEvent() {
    override fun onMediaConnected(channel: String) {
        // Connected to the media server of the default channel
    }

    override fun onVolumesReport(
        channel: String,
        volumes: MutableMap<UserTrackDesc, VolumeInfo>
    ) {
        // Channel volume info, useful for highlighting the active speaker
    }
})
```

Every channel-level callback explicitly carries `channel`. Even if a listener is bound to only one `RTCChannel`, use this parameter to keep logs and state separated. For more definitions, see [RTCClientEvent](/zh/rtc/android/api-reference/RTCClientEvent) (Chinese) and [RTCMediaEvent](/zh/rtc/android/api-reference/RTCMediaEvent) (Chinese).

## Step 3: Join a channel

`join(...)` synchronously returns `RTCChannel?`:

* A non-null return only means the SDK accepted the request and created a channel handle, not that the join succeeded.
* The actual result is reported by `onJoinSucceed(...)` or `onJoinFailed(...)`.
* If the SDK isn't initialized or has been released, it synchronously throws `SdkNotInitializedException`.

```kotlin theme={null}
private var defaultChannel: RTCChannel? = null

fun joinChannel(activity: Activity, token: String) {
    defaultChannel = rtcEngine.join(
        activity = activity,
        token = token,
        clientEvent = clientEvent,
        options = JoinOptions(
            autoSubscribeAudio = true,
            autoSubscribeVideo = false
        )
    )

    if (defaultChannel == null) {
        // The request was rejected before the channel session was created; the reason is still reported via onJoinFailed
    }
}
```

The first `join` creates the default channel, and the flat APIs on `RTCEngine`—publish, subscribe, query, `leave()`, and so on—all act on it. The SDK also supports joining multiple channels at the same time; this quickstart covers only the single-channel flow. For details, see [Multi-channel](/zh/rtc/android/advanced/multi-channel) (Chinese).

## Step 4: Start local capture and publish

Run the following publishing flow after `onJoinSucceed(...)`. Capture and publishing are two separate actions: explicitly start local capture first, then publish the same local track to the channel. Unpublishing doesn't automatically stop shared capture; when you no longer need the device, also call the track's `stopCapture()`.

### 4.1 Camera capture and publishing

```kotlin theme={null}
private lateinit var cameraTrack: LocalCameraTrack

fun startCamera(previewView: VcsPlayerGlTextureView) {
    cameraTrack = rtcEngine.getLocalCameraTrack(PreOptionCamera._720P)
    cameraTrack.addPlayView(previewView)
    cameraTrack.startCapture(object : RTCResultListener {
        override fun onSuccess() {
            rtcEngine.publishLocalVideo(
                track = cameraTrack,
                publishCustomOpt = PublishCustomOptions(
                    desc = TrackDesc.TRACK_MAIN.value,
                    props = null,
                    simulcasts = null
                ),
                listener = null
            )
        }

        override fun onFail(code: Int) {
            // For example, the CAMERA permission wasn't granted
        }
    })
}
```

For details, see [LocalCameraTrack](/zh/rtc/android/api-reference/LocalCameraTrack) (Chinese).

### 4.2 Microphone capture and publishing

The microphone capture module is decoupled from joining and publishing. `publishLocalAudio(...)` no longer opens the microphone; you must call `LocalMicTrack.startCapture(...)` first.

```kotlin theme={null}
private lateinit var micTrack: LocalMicTrack

fun startMicrophone() {
    micTrack = rtcEngine.getLocalMicTrack(PreOptionMic.def)
    micTrack.startCapture(object : RTCResultListener {
        override fun onSuccess() {
            rtcEngine.publishLocalAudio(
                track = micTrack,
                publishCustomOpt = PublishCustomOptions(
                    desc = TrackDesc.TRACK_AUDIO.value,
                    props = null,
                    simulcasts = null
                ),
                listener = null
            )
        }

        override fun onFail(code: Int) {
            // For example, the RECORD_AUDIO permission wasn't granted or the microphone failed to open
        }
    })
}
```

Explicit capture also works outside a channel. Set `setRtcLocalAudioFrameEvent(...)` first, then call `micTrack.startCapture(...)` to receive local PCM data for recording or processing; registering the callback alone doesn't open the microphone. See [LocalMicTrack](/zh/rtc/android/api-reference/LocalMicTrack) (Chinese) and [RTCEngine](/zh/rtc/android/api-reference/RTCEngine#setrtclocalaudioframeevent-e) (Chinese).

### 4.3 Screen sharing (optional)

```kotlin theme={null}
val screenTrack = rtcEngine.getLocalScreenTrack(this, PreOptionScreen.def)

screenTrack.setEvent(object : RTCScreenStateEvent {
    override fun onScreenCaptureStateChanged(state: ScreenCaptureState, args: String?) {
        when (state) {
            ScreenCaptureState.START -> Unit // Screen capture is established
            ScreenCaptureState.STOP -> Unit  // Screen capture has stopped
            ScreenCaptureState.ERROR -> Unit // args contains the error info
        }
    }
})

screenTrack.request { granted, intent ->
    if (granted && intent != null) {
        screenTrack.startCapture(intent, object : RTCResultListener {
            override fun onSuccess() {
                // This only means the SDK accepted the start request; the actual state is reported by RTCScreenStateEvent
                rtcEngine.publishLocalVideo(
                    track = screenTrack,
                    publishCustomOpt = PublishCustomOptions(
                        desc = TrackDesc.TRACK_SHARE.value,
                        props = null,
                        simulcasts = null
                    ),
                    listener = null
                )
            }

            override fun onFail(code: Int) {
                // For example, a duplicate start or the current lifecycle state doesn't allow starting
            }
        })
    }
}
```

For API details, see [LocalScreenTrack](/zh/rtc/android/api-reference/LocalScreenTrack) (Chinese).

## Step 5: Subscribe to and play remote media

After receiving `onStreamTrackAdd(...)`, get the remote track from the default channel, bind a render view, then subscribe:

```kotlin theme={null}
private fun subscribeRemoteVideo(uid: String, trackId: String, trackDesc: String) {
    val remoteTrack = rtcEngine.getRemoteVideoTrack(uid, trackDesc)
    remoteTrack?.addPlayView(remoteView)

    rtcEngine.subscribeRemoteTrack(
        uid = uid,
        trackId = trackId,
        preferTrackIds = null,
        result = object : RTCResultListener {
            override fun onSuccess() = Unit
            override fun onFail(code: Int) {
                // Subscription failed
            }
        }
    )
}

// Put this in the clientEvent implementation above
override fun onStreamTrackRemove(uid: String, channel: String, trackInfo: TrackInfo) {
    rtcEngine.unSubscribeRemoteTrack(uid, trackInfo.id)
    rtcEngine.getRemoteVideoTrack(uid, trackInfo.desc)?.removePlayView(remoteView)
}
```

For details, see [RemoteVideoTrack](/zh/rtc/android/api-reference/RemoteVideoTrack) (Chinese).

## Step 6: Leave the channel and release resources

```kotlin theme={null}
// First, unpublish from the default channel
rtcEngine.unPublishLocalAudio(micTrack, null)
rtcEngine.unPublishLocalVideo(cameraTrack, null)

// Then close the shared capture devices
micTrack.stopCapture()
cameraTrack.stopCapture()

// Leave the default channel; you can also call defaultChannel?.leave()
rtcEngine.leave()

// Release the Engine when the app no longer uses RTC
rtcEngine.releaseSDK()
```

`releaseSDK()` releases all channels and shared resources from the current initialization cycle; you can call `initSDK()` again afterward.

## More capabilities

* Joining multiple channels concurrently, per-channel publishing and subscription, and resource isolation: [Multi-channel](/zh/rtc/android/advanced/multi-channel) (Chinese)
* Microphone input device enumeration, switching, and PCM callbacks: [LocalMicTrack](/zh/rtc/android/api-reference/LocalMicTrack) (Chinese)
* Custom video publishing: [Custom tracks](/zh/rtc/android/advanced/custom-track) (Chinese)
* Whiteboard: [Whiteboard](/en/rtc/whiteboard)
* Audio output device management: [Audio routing](/zh/rtc/android/advanced/audio-routing) (Chinese)
* Full SDK API: [RTCEngine](/zh/rtc/android/api-reference/RTCEngine) (Chinese)
