dsh-agora 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/assets/agora/SKILL.md +113 -0
  4. package/assets/agora/references/cli/README.md +161 -0
  5. package/assets/agora/references/cli/automation.md +189 -0
  6. package/assets/agora/references/cli/doctor.md +129 -0
  7. package/assets/agora/references/cli/env.md +158 -0
  8. package/assets/agora/references/cli/install-auth.md +152 -0
  9. package/assets/agora/references/cli/projects.md +116 -0
  10. package/assets/agora/references/cli/quickstarts.md +117 -0
  11. package/assets/agora/references/cloud-recording/README.md +86 -0
  12. package/assets/agora/references/conversational-ai/README.md +285 -0
  13. package/assets/agora/references/conversational-ai/agent-client-toolkit-react.md +182 -0
  14. package/assets/agora/references/conversational-ai/agent-samples.md +101 -0
  15. package/assets/agora/references/conversational-ai/agent-toolkit-android.md +209 -0
  16. package/assets/agora/references/conversational-ai/agent-toolkit-ios.md +208 -0
  17. package/assets/agora/references/conversational-ai/agent-toolkit.md +201 -0
  18. package/assets/agora/references/conversational-ai/agent-ui-kit.md +63 -0
  19. package/assets/agora/references/conversational-ai/architecture.md +221 -0
  20. package/assets/agora/references/conversational-ai/auth-flow.md +154 -0
  21. package/assets/agora/references/conversational-ai/conversational-ai-studio.md +173 -0
  22. package/assets/agora/references/conversational-ai/go-sdk.md +184 -0
  23. package/assets/agora/references/conversational-ai/integration-from-quickstart.md +203 -0
  24. package/assets/agora/references/conversational-ai/python-sdk.md +122 -0
  25. package/assets/agora/references/conversational-ai/quickstarts.md +710 -0
  26. package/assets/agora/references/conversational-ai/server-custom-llm.md +45 -0
  27. package/assets/agora/references/conversational-ai/server-mcp.md +40 -0
  28. package/assets/agora/references/conversational-ai/server-sdk-rename.md +78 -0
  29. package/assets/agora/references/conversational-ai/server-sdks.md +128 -0
  30. package/assets/agora/references/doc-fetching.md +67 -0
  31. package/assets/agora/references/integration-patterns.md +201 -0
  32. package/assets/agora/references/mcp-tools.md +49 -0
  33. package/assets/agora/references/rtc/README.md +104 -0
  34. package/assets/agora/references/rtc/android.md +344 -0
  35. package/assets/agora/references/rtc/cross-platform-coordination.md +61 -0
  36. package/assets/agora/references/rtc/flutter.md +282 -0
  37. package/assets/agora/references/rtc/ios.md +306 -0
  38. package/assets/agora/references/rtc/nextjs.md +87 -0
  39. package/assets/agora/references/rtc/react-native.md +266 -0
  40. package/assets/agora/references/rtc/react.md +186 -0
  41. package/assets/agora/references/rtc/web.md +506 -0
  42. package/assets/agora/references/rtm/README.md +80 -0
  43. package/assets/agora/references/rtm/android.md +277 -0
  44. package/assets/agora/references/rtm/ios.md +231 -0
  45. package/assets/agora/references/rtm/web.md +348 -0
  46. package/assets/agora/references/server/README.md +22 -0
  47. package/assets/agora/references/server/tokens.md +74 -0
  48. package/assets/agora/references/server-gateway/README.md +80 -0
  49. package/assets/agora/references/server-gateway/linux-cpp.md +251 -0
  50. package/assets/agora/references/testing-guidance/SKILL.md +65 -0
  51. package/assets/agora/references/testing-guidance/completeness-gate.md +28 -0
  52. package/assets/agora/references/testing-guidance/convoai-rest.md +83 -0
  53. package/assets/agora/references/testing-guidance/mobile-rtm-and-renewal.md +109 -0
  54. package/assets/agora/references/testing-guidance/rtc-android.md +70 -0
  55. package/assets/agora/references/testing-guidance/rtc-ios.md +73 -0
  56. package/assets/agora/references/testing-guidance/rtc-react.md +51 -0
  57. package/assets/agora/references/testing-guidance/rtc-web.md +94 -0
  58. package/cordis.patch.yml +5 -0
  59. package/index.js +53 -0
  60. package/package.json +49 -0
@@ -0,0 +1,104 @@
1
+ # Agora RTC (Video/Voice SDK)
2
+
3
+ Real-time audio and video communication. Users join channels, publish local tracks, and subscribe to remote tracks.
4
+
5
+ ## Critical Rules
6
+
7
+ 1. **Register event handlers BEFORE joining** the channel, or you will miss events for users already present.
8
+ 2. **`user-published` fires separately** for audio and video. A user publishing both triggers two events — handle each.
9
+ 3. **Track cleanup**: Always `stop()` then `close()` local tracks before setting to null. Failure to clean up causes memory leaks and device locks. (React SDK hooks handle this automatically — see `react.md`.)
10
+ 4. **HTTPS required** for Web SDK (except `localhost`).
11
+ 5. **Token management is mandatory in production**. Handle `token-privilege-will-expire` (Web) / `onTokenPrivilegeWillExpire` (native) to renew tokens. UID in token must match UID used to join.
12
+ 6. **Stream bombing prevention**: In production, generate tokens with subscriber role (`kRoleSubscriber` / `RtcRole.SUBSCRIBER`) for audience-only users to prevent unauthorized publishing.
13
+ 7. **Audio autoplay (non-standard flows only)**: Browser autoplay policy is not an issue in typical RTC flows because the user has already clicked to join (satisfying the gesture requirement). However, if you auto-join on page load, trigger audio from a non-user event, or run in a headless/test environment, `audioTrack.play()` may be silently blocked. Wrap the join + publish sequence in a user gesture handler in these cases.
14
+
15
+ ## Channel Profiles
16
+
17
+ - `rtc` (communication): All peers are equal. Best for video calls, conferencing.
18
+ - `live` (live streaming): Host/audience roles. Higher bitrate, lower latency for hosts.
19
+
20
+ ## Video Encoder Profiles (Web)
21
+
22
+ ```text
23
+ "120p_1" → 160×120, 15fps, 65kbps
24
+ "180p_1" → 320×180, 15fps, 140kbps
25
+ "360p_1" → 640×360, 15fps, 400kbps
26
+ "480p_1" → 640×480, 15fps, 500kbps
27
+ "720p_1" → 1280×720, 15fps, 1130kbps
28
+ "720p_2" → 1280×720, 30fps, 2000kbps
29
+ "1080p_1" → 1920×1080, 15fps, 2080kbps
30
+ "1080p_2" → 1920×1080, 30fps, 3000kbps
31
+ ```
32
+
33
+ Or use custom config: `{ width: 640, height: 360, frameRate: 24, bitrateMin: 400, bitrateMax: 1000 }`
34
+
35
+ ## Audio Encoder Profiles (Web)
36
+
37
+ ```text
38
+ "speech_low_quality" → mono, 16kHz, 24kbps
39
+ "speech_standard" → mono, 32kHz, 24kbps
40
+ "high_quality" → mono, 48kHz, 40kbps
41
+ "high_quality_stereo" → stereo, 48kHz, 128kbps
42
+ ```
43
+
44
+ ## Dual Stream (Simulcast)
45
+
46
+ Send high+low quality streams simultaneously. Subscribers choose which to receive, enabling large-scale calls:
47
+
48
+ ```javascript
49
+ // Web: Enable after joining
50
+ await client.enableDualStream();
51
+ client.setLowStreamParameter({
52
+ width: 160,
53
+ height: 90,
54
+ framerate: 24,
55
+ bitrate: 200,
56
+ });
57
+ // Switch remote user to low stream
58
+ client.setRemoteVideoStreamType(uid, 1); // 0=high, 1=low
59
+ ```
60
+
61
+ ## Screen Sharing (Web)
62
+
63
+ ```javascript
64
+ const screenTrack = await AgoraRTC.createScreenVideoTrack(
65
+ {
66
+ optimizationMode: 'detail', // or "motion" for video content
67
+ encoderConfig: { width: 1280, height: 720, frameRate: 15 },
68
+ },
69
+ 'auto',
70
+ ); // "auto" returns [videoTrack, audioTrack] if audio available
71
+ ```
72
+
73
+ Screen share typically uses a separate client instance to avoid replacing the camera track.
74
+
75
+ ## Cross-Platform Interop Notes
76
+
77
+ When Web, iOS, and Android clients share the same channel:
78
+
79
+ - **Codec**: `"vp8"` and `"vp9"` scale better in multi-user calls — prefer these over `"h264"`, which does not scale well beyond small groups. If codecs differ between Web and native clients, Agora's server transcodes transparently (works but adds latency).
80
+ - **UID types**: iOS uses `UInt` (unsigned 32-bit), Android uses `Int` (signed 32-bit), Web uses `number`. UIDs > 2,147,483,647 wrap to negative on Android. RTM uses **string UIDs** — use `String(rtcUid)` as a mapping convention.
81
+ - **Audio profiles**: Align encoder settings across platforms to avoid one side sending stereo 128kbps while another expects mono. Use `"speech_standard"` (Web) / `AUDIO_PROFILE_DEFAULT` (native) for voice calls.
82
+ - **Orientation**: Mobile uses adaptive orientation (rotates with device). Web cameras are typically landscape. Handle aspect ratio changes on the viewer side.
83
+ - **Dual stream**: Enable on all platforms for large calls, not just Web.
84
+
85
+ ## Platform Reference Files
86
+
87
+ Read the file matching the user's platform:
88
+
89
+ - **[web.md](web.md)** — `agora-rtc-sdk-ng` (JS/TS): client creation, tracks, events, complete examples
90
+ - **[react.md](react.md)** — `agora-rtc-react` hooks and components
91
+ - **[nextjs.md](nextjs.md)** — Next.js / SSR dynamic import patterns (App Router + Pages Router)
92
+ - **[ios.md](ios.md)** — `AgoraRtcEngineKit` (Swift): engine setup, delegation, permissions
93
+ - **[android.md](android.md)** — `RtcEngine` (Kotlin/Java): engine setup, callbacks, permissions
94
+ - **[react-native.md](react-native.md)** — `react-native-agora`: engine init, events, video views, complete example
95
+ - **[flutter.md](flutter.md)** — `agora_rtc_engine` (Dart): engine init, events, AgoraVideoView, complete example
96
+ - **[cross-platform-coordination.md](cross-platform-coordination.md)** — UID strategy, codec interop, screen sharing across platforms, audio routing, common cross-platform bugs
97
+
98
+ For additional platforms and advanced features: <https://docs-md.agora.io/en/video-calling/get-started/get-started-sdk.md> — voice-only: <https://docs-md.agora.io/en/voice-calling/get-started/get-started-sdk.md>
99
+
100
+ For test setup and mocking patterns, see [references/testing-guidance/SKILL.md](../testing-guidance/SKILL.md).
101
+
102
+ ## When to Fetch More
103
+
104
+ Always use Level 2 fetch for: encoder profile parameter details, error code listings, release notes, Windows/Electron/Unity platform quick-starts. See [../doc-fetching.md](../doc-fetching.md).
@@ -0,0 +1,344 @@
1
+ # Agora RTC Android SDK (Kotlin)
2
+
3
+ ## Table of Contents
4
+
5
+ - [Installation](#installation)
6
+ - [Engine Initialization](#engine-initialization)
7
+ - [Joining a Channel](#joining-a-channel)
8
+ - [Video Setup](#video-setup)
9
+ - [Audio Setup](#audio-setup)
10
+ - [Event Handling (IRtcEngineEventHandler)](#event-handling)
11
+ - [Leaving and Cleanup](#leaving-and-cleanup)
12
+ - [Token Renewal](#token-renewal)
13
+ - [Complete Example: Video Call](#complete-example-video-call)
14
+
15
+ API Reference: <https://api-ref.agora.io/en/video-sdk/android/4.x/API/rtc_api_overview.html>
16
+
17
+ ## Installation
18
+
19
+ Add to `build.gradle`:
20
+
21
+ ```groovy
22
+ dependencies {
23
+ implementation 'io.agora.rtc:full-sdk:4.3.+'
24
+ // or for voice-only: implementation 'io.agora.rtc:voice-sdk:4.3.+'
25
+ }
26
+ ```
27
+
28
+ Add permissions to `AndroidManifest.xml`:
29
+
30
+ ```xml
31
+ <uses-permission android:name="android.permission.INTERNET" />
32
+ <uses-permission android:name="android.permission.CAMERA" />
33
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
34
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
35
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
36
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
37
+ <uses-permission android:name="android.permission.BLUETOOTH" />
38
+ <!-- Android 12+ (API 31+): required for Bluetooth audio headsets -->
39
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
40
+ ```
41
+
42
+ Request runtime permissions for `CAMERA` and `RECORD_AUDIO` before initializing.
43
+
44
+ ## Engine Initialization
45
+
46
+ ```kotlin
47
+ import io.agora.rtc2.RtcEngine
48
+ import io.agora.rtc2.RtcEngineConfig
49
+ import io.agora.rtc2.Constants
50
+
51
+ val config = RtcEngineConfig().apply {
52
+ mContext = applicationContext
53
+ mAppId = "your-app-id"
54
+ mEventHandler = rtcEventHandler
55
+ mChannelProfile = Constants.CHANNEL_PROFILE_COMMUNICATION
56
+ // or Constants.CHANNEL_PROFILE_LIVE_BROADCASTING
57
+ }
58
+
59
+ val agoraEngine = RtcEngine.create(config)
60
+ ```
61
+
62
+ ## Joining a Channel
63
+
64
+ ```kotlin
65
+ import io.agora.rtc2.ChannelMediaOptions
66
+
67
+ val options = ChannelMediaOptions().apply {
68
+ clientRoleType = Constants.CLIENT_ROLE_BROADCASTER
69
+ channelProfile = Constants.CHANNEL_PROFILE_COMMUNICATION
70
+ publishMicrophoneTrack = true
71
+ publishCameraTrack = true
72
+ autoSubscribeAudio = true
73
+ autoSubscribeVideo = true
74
+ }
75
+
76
+ agoraEngine.joinChannel(token, "channel-name", 0, options)
77
+ // token: null for testing, uid: 0 for auto-assignment
78
+ ```
79
+
80
+ ## Video Setup
81
+
82
+ ```kotlin
83
+ // Enable video module
84
+ agoraEngine.enableVideo()
85
+
86
+ // Configure encoder
87
+ val videoConfig = VideoEncoderConfiguration().apply {
88
+ dimensions = VideoEncoderConfiguration.VideoDimensions(640, 360)
89
+ frameRate = VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_24.value
90
+ bitrate = VideoEncoderConfiguration.STANDARD_BITRATE
91
+ orientationMode = VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_ADAPTIVE
92
+ }
93
+ agoraEngine.setVideoEncoderConfiguration(videoConfig)
94
+
95
+ // Local preview
96
+ val localSurfaceView = SurfaceView(context)
97
+ localVideoContainer.addView(localSurfaceView)
98
+ agoraEngine.setupLocalVideo(
99
+ VideoCanvas(localSurfaceView, VideoCanvas.RENDER_MODE_HIDDEN, 0)
100
+ )
101
+ agoraEngine.startPreview()
102
+
103
+ // Remote video (call in onUserJoined callback)
104
+ val remoteSurfaceView = SurfaceView(context)
105
+ remoteVideoContainer.addView(remoteSurfaceView)
106
+ agoraEngine.setupRemoteVideo(
107
+ VideoCanvas(remoteSurfaceView, VideoCanvas.RENDER_MODE_HIDDEN, remoteUid)
108
+ )
109
+ ```
110
+
111
+ ## Audio Setup
112
+
113
+ ```kotlin
114
+ // Enable audio (enabled by default)
115
+ agoraEngine.enableAudio()
116
+
117
+ // Audio profile (SDK 4.x: set profile and scenario separately)
118
+ agoraEngine.setAudioProfile(Constants.AUDIO_PROFILE_DEFAULT)
119
+ agoraEngine.setAudioScenario(Constants.AUDIO_SCENARIO_DEFAULT)
120
+
121
+ // Mute/unmute local audio
122
+ agoraEngine.muteLocalAudioStream(true) // mute
123
+ agoraEngine.muteLocalAudioStream(false) // unmute
124
+
125
+ // Mute/unmute local video (stops sending video, camera stays active)
126
+ agoraEngine.muteLocalVideoStream(true) // video off
127
+ agoraEngine.muteLocalVideoStream(false) // video on
128
+
129
+ // Or disable video entirely (stops camera capture)
130
+ agoraEngine.enableLocalVideo(false) // camera off
131
+ agoraEngine.enableLocalVideo(true) // camera on
132
+
133
+ // Switch between speaker and earpiece
134
+ agoraEngine.setEnableSpeakerphone(true) // speaker
135
+ agoraEngine.setEnableSpeakerphone(false) // earpiece
136
+
137
+ // Mute remote user
138
+ agoraEngine.muteRemoteAudioStream(remoteUid, true)
139
+ ```
140
+
141
+ ## Event Handling
142
+
143
+ ```kotlin
144
+ private val rtcEventHandler = object : IRtcEngineEventHandler() {
145
+ // Successfully joined channel
146
+ override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) {
147
+ runOnUiThread {
148
+ Log.d("Agora", "Joined channel: $channel, uid: $uid")
149
+ }
150
+ }
151
+
152
+ // Remote user joined
153
+ override fun onUserJoined(uid: Int, elapsed: Int) {
154
+ runOnUiThread {
155
+ val remoteSurfaceView = SurfaceView(context)
156
+ remoteVideoContainer.addView(remoteSurfaceView)
157
+ agoraEngine.setupRemoteVideo(
158
+ VideoCanvas(remoteSurfaceView, VideoCanvas.RENDER_MODE_HIDDEN, uid)
159
+ )
160
+ }
161
+ }
162
+
163
+ // Remote user left
164
+ override fun onUserOffline(uid: Int, reason: Int) {
165
+ runOnUiThread {
166
+ agoraEngine.setupRemoteVideo(VideoCanvas(null, VideoCanvas.RENDER_MODE_HIDDEN, uid))
167
+ // Remove remote view from container
168
+ }
169
+ }
170
+
171
+ // Token expiring
172
+ override fun onTokenPrivilegeWillExpire(token: String?) {
173
+ // Fetch new token and renew
174
+ fetchNewToken { newToken ->
175
+ agoraEngine.renewToken(newToken)
176
+ }
177
+ }
178
+
179
+ // Error
180
+ override fun onError(err: Int) {
181
+ Log.e("Agora", "Error code: $err")
182
+ }
183
+
184
+ // Network quality
185
+ override fun onNetworkQuality(uid: Int, txQuality: Int, rxQuality: Int) {
186
+ // 0=unknown, 1=excellent, 2=good, 3=poor, 4=bad, 5=very bad, 6=disconnected
187
+ }
188
+ }
189
+ ```
190
+
191
+ ## Leaving and Cleanup
192
+
193
+ ```kotlin
194
+ fun leaveChannel() {
195
+ agoraEngine.stopPreview()
196
+ agoraEngine.leaveChannel()
197
+ }
198
+
199
+ // Full cleanup (activity/app destruction)
200
+ fun destroy() {
201
+ agoraEngine.stopPreview()
202
+ agoraEngine.leaveChannel()
203
+ RtcEngine.destroy()
204
+ }
205
+ ```
206
+
207
+ ## Token Renewal
208
+
209
+ ```kotlin
210
+ override fun onTokenPrivilegeWillExpire(token: String?) {
211
+ lifecycleScope.launch {
212
+ val newToken = fetchTokenFromServer(channelName, localUid)
213
+ agoraEngine.renewToken(newToken)
214
+ }
215
+ }
216
+ ```
217
+
218
+ ## Complete Example: Video Call
219
+
220
+ ```kotlin
221
+ class VideoCallActivity : AppCompatActivity() {
222
+ private val appId = "your-app-id"
223
+ private var token: String? = null
224
+ private val channelName = "test"
225
+ private var agoraEngine: RtcEngine? = null
226
+ private var isJoined = false
227
+
228
+ override fun onCreate(savedInstanceState: Bundle?) {
229
+ super.onCreate(savedInstanceState)
230
+ setContentView(R.layout.activity_video_call)
231
+
232
+ // Request permissions first
233
+ if (checkPermissions()) {
234
+ initializeAgora()
235
+ } else {
236
+ requestPermissions()
237
+ }
238
+ }
239
+
240
+ private fun initializeAgora() {
241
+ val config = RtcEngineConfig().apply {
242
+ mContext = applicationContext
243
+ mAppId = appId
244
+ mEventHandler = rtcEventHandler
245
+ }
246
+ agoraEngine = RtcEngine.create(config)
247
+ agoraEngine?.enableVideo()
248
+
249
+ agoraEngine?.setVideoEncoderConfiguration(
250
+ VideoEncoderConfiguration().apply {
251
+ dimensions = VideoEncoderConfiguration.VideoDimensions(640, 360)
252
+ frameRate = VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_24.value
253
+ bitrate = VideoEncoderConfiguration.STANDARD_BITRATE
254
+ }
255
+ )
256
+ }
257
+
258
+ fun joinChannel() {
259
+ // Setup local preview
260
+ val localView = SurfaceView(this)
261
+ findViewById<FrameLayout>(R.id.local_video_container).addView(localView)
262
+ agoraEngine?.setupLocalVideo(VideoCanvas(localView, VideoCanvas.RENDER_MODE_HIDDEN, 0))
263
+ agoraEngine?.startPreview()
264
+
265
+ // Join channel
266
+ val options = ChannelMediaOptions().apply {
267
+ clientRoleType = Constants.CLIENT_ROLE_BROADCASTER
268
+ channelProfile = Constants.CHANNEL_PROFILE_COMMUNICATION
269
+ publishMicrophoneTrack = true
270
+ publishCameraTrack = true
271
+ }
272
+ agoraEngine?.joinChannel(token, channelName, 0, options)
273
+ }
274
+
275
+ fun leaveChannel() {
276
+ agoraEngine?.stopPreview()
277
+ agoraEngine?.leaveChannel()
278
+ isJoined = false
279
+ }
280
+
281
+ private val rtcEventHandler = object : IRtcEngineEventHandler() {
282
+ override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) {
283
+ runOnUiThread { isJoined = true }
284
+ }
285
+
286
+ override fun onUserJoined(uid: Int, elapsed: Int) {
287
+ runOnUiThread {
288
+ val remoteView = SurfaceView(this@VideoCallActivity)
289
+ findViewById<FrameLayout>(R.id.remote_video_container).addView(remoteView)
290
+ agoraEngine?.setupRemoteVideo(
291
+ VideoCanvas(remoteView, VideoCanvas.RENDER_MODE_HIDDEN, uid)
292
+ )
293
+ }
294
+ }
295
+
296
+ override fun onUserOffline(uid: Int, reason: Int) {
297
+ runOnUiThread {
298
+ agoraEngine?.setupRemoteVideo(VideoCanvas(null, VideoCanvas.RENDER_MODE_HIDDEN, uid))
299
+ }
300
+ }
301
+
302
+ override fun onTokenPrivilegeWillExpire(token: String?) {
303
+ lifecycleScope.launch {
304
+ val newToken = fetchTokenFromServer(channelName, 0)
305
+ agoraEngine?.renewToken(newToken)
306
+ }
307
+ }
308
+ }
309
+
310
+ override fun onDestroy() {
311
+ super.onDestroy()
312
+ agoraEngine?.stopPreview()
313
+ agoraEngine?.leaveChannel()
314
+ RtcEngine.destroy()
315
+ agoraEngine = null
316
+ }
317
+
318
+ private fun checkPermissions(): Boolean {
319
+ return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) ==
320
+ PackageManager.PERMISSION_GRANTED &&
321
+ ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) ==
322
+ PackageManager.PERMISSION_GRANTED
323
+ }
324
+
325
+ private fun requestPermissions() {
326
+ ActivityCompat.requestPermissions(
327
+ this,
328
+ arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO),
329
+ PERMISSION_REQUEST_CODE
330
+ )
331
+ }
332
+
333
+ companion object {
334
+ private const val PERMISSION_REQUEST_CODE = 22
335
+ }
336
+ }
337
+ ```
338
+
339
+ ## Official Documentation
340
+
341
+ For APIs or features not covered above:
342
+
343
+ - API Reference: <https://api-ref.agora.io/en/video-sdk/android/4.x/API/rtc_api_overview.html>
344
+ - Guides: <https://docs.agora.io/en/video-calling/overview/product-overview>
@@ -0,0 +1,61 @@
1
+ # RTC Cross-Platform Coordination
2
+
3
+ Patterns for apps where users on different platforms (Web, iOS, Android) join the same Agora RTC channel.
4
+
5
+ ## UID Strategy
6
+
7
+ Agora assigns UIDs per channel. For multi-platform apps:
8
+
9
+ - **Auto-assign on all clients**: Pass `null`/`0` to `join()` — each platform auto-receives a unique numeric UID. Clients subscribe to all remote users regardless of platform.
10
+ - **Fixed UIDs**: Assign specific UIDs per user role (e.g., `1001` for host, `1002` for co-host) when you need deterministic lookup. Must be unique per channel — duplicates cause undefined behavior.
11
+ - **UID limits**: Numeric UIDs must be `0` to `2^32 - 1`. String UIDs must be ASCII and at most `255` characters.
12
+ - **RTC + RTM coordination**: After RTC join, use `String(rtcUid)` as the RTM user ID to correlate users across both systems (see [../rtm/README.md](../rtm/README.md)).
13
+
14
+ ## Codec Interoperability
15
+
16
+ Agora handles codec negotiation automatically for most scenarios. What to know:
17
+
18
+ | Codec | Notes |
19
+ |-------|-------|
20
+ | VP8 | Web default. Scales well in multi-user calls. Supported on Safari 13+. Recommended. |
21
+ | VP9 | Better compression than VP8. Scales well on desktop. **iOS Safari: hardware-only** — requires iPhone 15 Pro / M3 Mac or newer; software fallback degrades battery significantly on older devices. |
22
+ | H.264 | Default on iOS and Android native SDKs. Does not scale well beyond small groups — avoid for multi-user Web calls. |
23
+ | H.265 (HEVC) | Not universally supported on Web; avoid for cross-platform channels. |
24
+
25
+ **Recommendation**: `'vp8'` is the safest default for multi-user Web calls — scales well and works on all modern Safari (13+). Use `'vp9'` only if you can ensure participants are on modern hardware. Avoid `'h264'` for multi-user Web scenarios. If codecs differ between Web and native clients, Agora's server transcodes transparently, which adds latency and is billed separately.
26
+
27
+ ## Screen Sharing (Cross-Platform)
28
+
29
+ Screen share is a separate track/stream, not a replacement for the camera track.
30
+
31
+ - **Web**: `AgoraRTC.createScreenVideoTrack()` — publishes as a second video track. See [web.md](web.md) for dual-stream setup.
32
+ - **iOS**: `AgoraRtcEngineKit.startScreenCapture(_:)` with broadcast extension — different lifecycle than camera.
33
+ - **Android**: `MediaProjection` API + `RtcEngine.startScreenCapture()`.
34
+
35
+ Remote users on any platform subscribe to the screen share UID as a normal remote user — the stream is just another video track from a different UID.
36
+
37
+ **Key rule**: Screen share uses a separate channel join with a different UID. Never publish camera and screen share from the same UID.
38
+
39
+ ## Audio Routing Differences
40
+
41
+ | Platform | Default audio output | Override |
42
+ |----------|---------------------|---------|
43
+ | Web | Speaker (browser-controlled) | Not configurable via SDK |
44
+ | iOS | Earpiece for `rtc` mode | `setDefaultAudioRouteToSpeakerphone(true)` for speaker |
45
+ | Android | Earpiece by default | `setEnableSpeakerphone(true)` |
46
+
47
+ When a user plugs in headphones, iOS/Android switch automatically. Web relies on the browser and OS audio routing — the SDK cannot override this.
48
+
49
+ ## Testing Multi-Platform Channels Locally
50
+
51
+ 1. **Web + Simulator/Emulator**: Connect both to the same channel; verify remote tracks appear on both sides.
52
+ 2. **Different UIDs on same machine**: Open two browser tabs or two simulator instances — each gets its own UID automatically.
53
+ 3. **Cross-device**: Use the Agora [Web Demo](https://webdemo.agora.io) to join from a browser while testing your native app in the same channel.
54
+ 4. **Codec check**: In the Agora Console → Real-time Monitoring, inspect active streams to confirm codec negotiation.
55
+
56
+ ## Common Cross-Platform Bugs
57
+
58
+ - **Remote user appears then immediately disappears on iOS** — usually a token expiry or UID collision. Check `connectionStateChanged` delegate for the reason code.
59
+ - **No video from Android on Web** — codec mismatch. Android may be sending H.265; Web can't decode it. Force H.264 on Android via `VideoEncoderConfiguration`.
60
+ - **Audio works, video doesn't on mobile** — camera permission not granted. Check permission before calling `startPreview()`.
61
+ - **Web user sees mobile user but not vice versa** — `user-published` event not registered before `client.join()`. Event handlers must be set up before joining — see RTC critical rules in [README.md](README.md).