Android SDK Guide
Install, join a channel, publish and subscribe, events and platform-specific notes.
The Android surface has the native engine shape: an RtcEngine singleton plus IRtcEngineEventHandler callbacks, rather than the Web track object model. Publishing and subscribing are controlled by ChannelMediaOptions on joinChannel and by the muteLocal* family of methods.
Prerequisites
- JDK 17, Android SDK 35, Gradle 8.x.
- Min SDK: see the library manifest (capture and screen sharing use MediaProjection, which needs API 21+).
- The host app must request
CAMERAandRECORD_AUDIOat runtime (the library only declares them; it does not request them for you). - You already have an
appIdand a token service (see Enable services and get credentials).
Install
dependencies {
implementation(project(":sdk:android")) // in this codebase
// planned Maven coordinate: implementation("com.nimeet.rtc:nimeet-rtc:0.1.0")
}Get a token
An Android client must not hold customerSecret. The recommended setup has the host app's backend issue tokens, and the client obtains them through CallbackTokenProvider (which delegates to your session endpoint) or EndpointTokenProvider (a direct connection during development; never put the secret in the APK):
val tokenProvider = FallbackTokenProvider(listOf(
CallbackTokenProvider { channel, uid, role -> yourBackend.getRtcToken(channel, uid, role) },
StaticTokenProvider(BuildConfig.NIMEET_TEST_TOKEN), // tests only
))Join a channel
class MeetingActivity : AppCompatActivity() {
private var rtc: RtcEngine? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
rtc = RtcEngine.create(this, BuildConfig.NIMEET_APP_ID, object : IRtcEngineEventHandler() {
override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) {
runOnUiThread { statusText.text = "Joined the channel" }
}
override fun onUserJoined(uid: Int, elapsed: Int) {
runOnUiThread { addTile(uid) }
}
override fun onUserOffline(uid: Int, reason: Int) {
runOnUiThread { removeTile(uid) }
}
override fun onTokenPrivilegeWillExpire(token: String?) {
rtc?.renewToken(fetchFreshToken())
}
})
rtc?.apply {
setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING)
enableVideo()
setupLocalVideo(VideoCanvas(localView))
startPreview()
joinChannel(BuildConfig.RTC_TOKEN, "weekly-sync", "", 10001)
}
}
}joinChannel has four overloads: (token, channelId, optionalInfo, uid), (token, channelId, uid, options), (token, channelId, userAccount, options) and so on; for the complete list and the parameter types see the API reference for this platform (generated at build time from the SDK interface definition).
Publish and subscribe
The native surface has no publish(track): after joining, the camera and microphone are published according to ChannelMediaOptions by default, and you switch them with methods.
// Mute / turn off the camera (keeps the ability to publish)
rtc?.muteLocalAudioStream(true)
rtc?.muteLocalVideoStream(true)
// Stop/resume local video capture
rtc?.enableLocalVideo(false)
// Subscribe to audio only (saves bandwidth on an audience client)
val options = ChannelMediaOptions().apply {
autoSubscribeAudio = true
autoSubscribeVideo = false
clientRoleType = Constants.CLIENT_ROLE_AUDIENCE
}
rtc?.updateChannelMediaOptions(options)Events
The callback interface is IRtcEngineEventHandler (84 on* methods). The most commonly used ones:
| Callback | When it fires | Typical handling |
|---|---|---|
onJoinChannelSuccess(channel, uid, elapsed) | Join succeeded | Switch the UI, start the timer |
onUserJoined(uid, elapsed) | A remote user joined | Allocate a video container |
onFirstRemoteVideoDecoded(uid, width, height, elapsed) | The first remote frame was decoded | Render the remote video |
onUserOffline(uid, reason) | A remote user left | Release the container |
onTokenPrivilegeWillExpire(token) | The privilege is about to expire | Renew, then renewToken |
onError(err) | A runtime error | Route by error number |
onConnectionStateChanged(state, reason) | The connection state changed | Do not release resources while RECONNECTING |
Error handling
- Synchronous methods return
int:0means success, and a negative value is an Agora error number (for example-2for an invalid parameter,-110for an invalid token). You must check the return value. - Asynchronous errors arrive through the
onErrorcallback. - Unimplemented capabilities return
-4(ERR_NOT_SUPPORTED) and never succeed silently.
val code = rtc?.joinChannel(token, channel, "", uid) ?: -3
if (code < 0) {
showError(RtcError.fromCode(code)) // RtcError in sdk/ exposes code/sdkCode/message/retryable
}Troubleshooting
| Symptom | Cause | Handling |
|---|---|---|
joinChannel returns -2 | Invalid channel name or out-of-range uid | Channel name [A-Za-z0-9_-], uid 1..2147483647 |
Returns -110 | Invalid token or channel mismatch | Self-check with GET /v1/tokens/verify |
| Audio works but there is no video | enableVideo() was not called, or setupLocalVideo was not set | Call them in the order above and make sure startPreview() runs |
| Screen sharing shows a black screen | The MediaProjection foreground service was not started | Use the host app's MediaProjection consent flow |
| Media stops after moving to the background | The app was frozen by the system | Use a foreground service; do not rely on background keep-alive |
UnsatisfiedLinkError | ABI mismatch | Check abiFilters and the packaged .so files |
Next steps
- Full interfaces and overloads: API reference for this platform
- Error codes and troubleshooting: Error code table, FAQ
- Issue tokens on your server: Token authentication
Platform information
| Item | value |
|---|---|
| Platform | Android |
| Language | Kotlin |
| SDK package | com.nimeet.rtc |
| Interface version | 4.7.0 |
| Compatible Agora API | Agora android API |
The public interfaces are aligned with the Agora android API; installation and runtime requirements are listed under Prerequisites above.
Availability
This platform's SDK: Generally available. The examples use the public API; runtime capability follows the published SDK package.
join-channel flow
The method names, parameter types and return values match the API reference for the same platform. Where a method has several overloads, the most common one is shown here; see the API reference for all overloads.
| Step | Interface | Method | Overload count |
|---|---|---|---|
| Create instance / initialize | IAgoraMusicContentCenter | initialize | 1 |
| Renew the Token | IAgoraMusicContentCenter | renewToken | 1 |
| subscribe to remote media | IBaseSpatialAudioEngine | muteRemoteAudioStream | 1 |
| enable video and local preview | RtcEngine | enableVideo | 1 |
| Join a channel | RtcEngine | joinChannel | 2 |
| Leave a channel | RtcEngine | leaveChannel | 2 |
| Channel scenarios | RtcEngine | setChannelProfile | 1 |
| switch roles | RtcEngine | setClientRole | 2 |
| bind the local video | RtcEngine | setupLocalVideo | 1 |
initialize(configuration: MusicContentCenterConfiguration): int
renewToken(token: String): int
muteRemoteAudioStream(uid: int, mute: boolean): int
enableVideo(): int
joinChannel(token: String, channelId: String, optionalInfo: String, uid: int): int
leaveChannel(): int
setChannelProfile(profile: int): int
setClientRole(role: int): int
setupLocalVideo(local: VideoCanvas): intEvents and callbacks
Callbacks are registered on IRtcEngineEventHandler; naming differs by platform, and the full list is in the API reference for the same platform.
| Callbacks | Signature | Purpose |
|---|---|---|
onConnectionStateChanged | onConnectionStateChanged(state: int, reason: int): void | join result / connection-state changes:update the UI accordingly and decide whether to reconnect. |
onUserJoined | onUserJoined(uid: int, elapsed: int): void | Remote user joined:allocate the frame container. |
onFirstRemoteVideoDecoded | onFirstRemoteVideoDecoded(uid: int, width: int, height: int, elapsed: int): void | Remote video status:it can render once the first frame is decoded. |
onUserOffline | onUserOffline(uid: int, reason: int): void | Remote user left:release the container and subscription. |
onRequestToken | onRequestToken(): void | Token expiring soon:call the server to renew, then `renewToken`. |
onError | onError(err: int): void | Runtime errors:`err` is an Agora error number, route by the error-code table. |
onNetworkQuality | onNetworkQuality(uid: int, txQuality: int, rxQuality: int): void | Network quality:report to Analytics or prompt the user. |
Error codes
| Constant | value |
|---|---|
AUTHENTICATION_FAILED | 5 |
INVALID_ARGUMENT | 2 |
INVALID_OPERATION | 3 |
NETWORK_ERROR | 4 |
OK | 0 |
STREAM_NOT_FOUND | 6 |
the platform REST layer uses REST error codes;the SDK layer maps connection/authentication failures to Agora error numbers, for the mapping table see SDK error identifiers per platform。