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,282 @@
1
+ # Agora RTC Flutter SDK
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](#event-handling)
11
+ - [Leaving and Cleanup](#leaving-and-cleanup)
12
+ - [Complete Example](#complete-example)
13
+
14
+ API Reference: <https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/rtc_api_overview.html>
15
+
16
+ ## Installation
17
+
18
+ Add to `pubspec.yaml`:
19
+
20
+ ```yaml
21
+ dependencies:
22
+ agora_rtc_engine: ^6.5.0
23
+ ```
24
+
25
+ Run `flutter pub get`.
26
+
27
+ Add permissions to `AndroidManifest.xml`:
28
+
29
+ ```xml
30
+ <uses-permission android:name="android.permission.INTERNET" />
31
+ <uses-permission android:name="android.permission.CAMERA" />
32
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
33
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
34
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
35
+ <uses-permission android:name="android.permission.BLUETOOTH" />
36
+ <!-- Android 12+ (API 31+): required for Bluetooth audio headsets -->
37
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
38
+ ```
39
+
40
+ Add to `Info.plist` (iOS):
41
+
42
+ ```xml
43
+ <key>NSCameraUsageDescription</key>
44
+ <string>Camera access for video calls</string>
45
+ <key>NSMicrophoneUsageDescription</key>
46
+ <string>Microphone access for audio calls</string>
47
+ ```
48
+
49
+ ## Engine Initialization
50
+
51
+ ```dart
52
+ import 'package:agora_rtc_engine/agora_rtc_engine.dart';
53
+
54
+ late RtcEngine _engine;
55
+
56
+ Future<void> initializeAgora() async {
57
+ _engine = createAgoraRtcEngine();
58
+ await _engine.initialize(const RtcEngineContext(
59
+ appId: 'your-app-id',
60
+ channelProfile: ChannelProfileType.channelProfileCommunication,
61
+ ));
62
+ await _engine.enableVideo();
63
+ }
64
+ ```
65
+
66
+ ## Joining a Channel
67
+
68
+ ```dart
69
+ Future<void> joinChannel() async {
70
+ await _engine.joinChannel(
71
+ token: null, // null for testing
72
+ channelId: 'channel-name',
73
+ uid: 0, // 0 for auto-assignment
74
+ options: const ChannelMediaOptions(
75
+ clientRoleType: ClientRoleType.clientRoleBroadcaster,
76
+ publishMicrophoneTrack: true,
77
+ publishCameraTrack: true,
78
+ autoSubscribeAudio: true,
79
+ autoSubscribeVideo: true,
80
+ ),
81
+ );
82
+ }
83
+ ```
84
+
85
+ ## Video Setup
86
+
87
+ ```dart
88
+ // Local preview — use uid=0
89
+ AgoraVideoView(
90
+ controller: VideoViewController(
91
+ rtcEngine: _engine,
92
+ canvas: const VideoCanvas(uid: 0),
93
+ ),
94
+ )
95
+
96
+ // Remote video — use the remote user's uid
97
+ AgoraVideoView(
98
+ controller: VideoViewController.remote(
99
+ rtcEngine: _engine,
100
+ canvas: VideoCanvas(uid: remoteUid),
101
+ connection: const RtcConnection(channelId: 'channel-name'),
102
+ ),
103
+ )
104
+ ```
105
+
106
+ ## Audio Setup
107
+
108
+ ```dart
109
+ // Mute/unmute local audio
110
+ await _engine.muteLocalAudioStream(true) // mute
111
+ await _engine.muteLocalAudioStream(false) // unmute
112
+
113
+ // Mute/unmute local video
114
+ await _engine.muteLocalVideoStream(true) // video off
115
+ await _engine.muteLocalVideoStream(false) // video on
116
+
117
+ // Speaker vs earpiece
118
+ await _engine.setEnableSpeakerphone(true) // speaker
119
+ await _engine.setEnableSpeakerphone(false) // earpiece
120
+ ```
121
+
122
+ ## Event Handling
123
+
124
+ Register handlers **before** joining the channel.
125
+
126
+ ```dart
127
+ _engine.registerEventHandler(
128
+ RtcEngineEventHandler(
129
+ onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
130
+ print('Joined: ${connection.channelId}, uid: ${connection.localUid}');
131
+ },
132
+ onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
133
+ print('Remote user joined: $remoteUid');
134
+ setState(() => _remoteUid = remoteUid);
135
+ },
136
+ onUserOffline: (RtcConnection connection, int remoteUid, UserOfflineReasonType reason) {
137
+ print('Remote user left: $remoteUid');
138
+ setState(() => _remoteUid = null);
139
+ },
140
+ onTokenPrivilegeWillExpire: (RtcConnection connection, String token) async {
141
+ final newToken = await fetchTokenFromServer();
142
+ await _engine.renewToken(newToken);
143
+ },
144
+ onError: (ErrorCodeType err, String msg) {
145
+ print('Error: $err $msg');
146
+ },
147
+ ),
148
+ );
149
+ ```
150
+
151
+ ## Leaving and Cleanup
152
+
153
+ ```dart
154
+ Future<void> leaveChannel() async {
155
+ await _engine.leaveChannel();
156
+ }
157
+
158
+ // Full cleanup when widget is disposed
159
+ @override
160
+ void dispose() {
161
+ _engine.leaveChannel();
162
+ _engine.release();
163
+ super.dispose();
164
+ }
165
+ ```
166
+
167
+ Always call `release()` when the engine is no longer needed.
168
+
169
+ ## Complete Example
170
+
171
+ ```dart
172
+ import 'package:flutter/material.dart';
173
+ import 'package:agora_rtc_engine/agora_rtc_engine.dart';
174
+
175
+ const appId = 'your-app-id';
176
+ const token = null; // null for testing
177
+ const channel = 'test';
178
+
179
+ class VideoCallPage extends StatefulWidget {
180
+ const VideoCallPage({super.key});
181
+ @override
182
+ State<VideoCallPage> createState() => _VideoCallPageState();
183
+ }
184
+
185
+ class _VideoCallPageState extends State<VideoCallPage> {
186
+ late RtcEngine _engine;
187
+ bool _joined = false;
188
+ int? _remoteUid;
189
+
190
+ @override
191
+ void initState() {
192
+ super.initState();
193
+ _initAgora();
194
+ }
195
+
196
+ Future<void> _initAgora() async {
197
+ _engine = createAgoraRtcEngine();
198
+ await _engine.initialize(const RtcEngineContext(appId: appId));
199
+ await _engine.enableVideo();
200
+
201
+ _engine.registerEventHandler(RtcEngineEventHandler(
202
+ onJoinChannelSuccess: (_, __) => setState(() => _joined = true),
203
+ onUserJoined: (_, uid, __) => setState(() => _remoteUid = uid),
204
+ onUserOffline: (_, uid, __) => setState(() => _remoteUid = null),
205
+ ));
206
+ }
207
+
208
+ Future<void> _join() async {
209
+ await _engine.startPreview();
210
+ await _engine.joinChannel(
211
+ token: token,
212
+ channelId: channel,
213
+ uid: 0,
214
+ options: const ChannelMediaOptions(
215
+ clientRoleType: ClientRoleType.clientRoleBroadcaster,
216
+ publishMicrophoneTrack: true,
217
+ publishCameraTrack: true,
218
+ ),
219
+ );
220
+ }
221
+
222
+ Future<void> _leave() async {
223
+ await _engine.leaveChannel();
224
+ setState(() {
225
+ _joined = false;
226
+ _remoteUid = null;
227
+ });
228
+ }
229
+
230
+ @override
231
+ void dispose() {
232
+ _engine.release();
233
+ super.dispose();
234
+ }
235
+
236
+ @override
237
+ Widget build(BuildContext context) {
238
+ return Scaffold(
239
+ body: Stack(
240
+ children: [
241
+ if (_joined)
242
+ AgoraVideoView(
243
+ controller: VideoViewController(
244
+ rtcEngine: _engine,
245
+ canvas: const VideoCanvas(uid: 0),
246
+ ),
247
+ ),
248
+ if (_remoteUid != null)
249
+ Positioned(
250
+ right: 16, top: 16, width: 120, height: 160,
251
+ child: AgoraVideoView(
252
+ controller: VideoViewController.remote(
253
+ rtcEngine: _engine,
254
+ canvas: VideoCanvas(uid: _remoteUid!),
255
+ connection: const RtcConnection(channelId: channel),
256
+ ),
257
+ ),
258
+ ),
259
+ Positioned(
260
+ bottom: 32, left: 0, right: 0,
261
+ child: Center(
262
+ child: ElevatedButton(
263
+ onPressed: _joined ? _leave : _join,
264
+ child: Text(_joined ? 'Leave' : 'Join'),
265
+ ),
266
+ ),
267
+ ),
268
+ ],
269
+ ),
270
+ );
271
+ }
272
+ }
273
+ ```
274
+
275
+ For test setup and mocking patterns, see [references/testing-guidance/SKILL.md](../testing-guidance/SKILL.md).
276
+
277
+ ## Official Documentation
278
+
279
+ For APIs or features not covered above:
280
+
281
+ - Quick-start guide: <https://docs-md.agora.io/en/video-calling/get-started/get-started-sdk.md?platform=flutter>
282
+ - API Reference: <https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/rtc_api_overview.html>
@@ -0,0 +1,306 @@
1
+ # Agora RTC iOS SDK (Swift)
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 (Delegate)](#event-handling-delegate)
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/ios/4.x/API/rtc_api_overview_ng.html>
16
+
17
+ ## Installation
18
+
19
+ ### Swift Package Manager
20
+
21
+ ```text
22
+ https://github.com/AgoraIO/AgoraRtcEngine_iOS
23
+ ```
24
+
25
+ ### CocoaPods
26
+
27
+ ```ruby
28
+ pod 'AgoraRtcEngine_iOS', '~> 4.3'
29
+ ```
30
+
31
+ Add to Info.plist:
32
+
33
+ ```xml
34
+ <key>NSCameraUsageDescription</key>
35
+ <string>For video calling</string>
36
+ <key>NSMicrophoneUsageDescription</key>
37
+ <string>For voice calling</string>
38
+ ```
39
+
40
+ ## Engine Initialization
41
+
42
+ ```swift
43
+ import AgoraRtcKit
44
+
45
+ let config = AgoraRtcEngineConfig()
46
+ config.appId = "your-app-id"
47
+ config.channelProfile = .communication // or .liveBroadcasting
48
+
49
+ let agoraEngine = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
50
+ ```
51
+
52
+ ## Joining a Channel
53
+
54
+ ```swift
55
+ // Communication mode
56
+ let option = AgoraRtcChannelMediaOptions()
57
+ option.clientRoleType = .broadcaster // .broadcaster or .audience
58
+ option.channelProfile = .communication
59
+
60
+ agoraEngine.joinChannel(
61
+ byToken: token, // nil for testing
62
+ channelId: "channel-name",
63
+ uid: 0, // 0 = auto-assign
64
+ mediaOptions: option
65
+ )
66
+ ```
67
+
68
+ ## Video Setup
69
+
70
+ ```swift
71
+ // Enable video module
72
+ agoraEngine.enableVideo()
73
+
74
+ // Configure video encoder
75
+ let videoConfig = AgoraVideoEncoderConfiguration(
76
+ size: CGSize(width: 640, height: 360),
77
+ frameRate: .fps24,
78
+ bitrate: AgoraVideoBitrateStandard,
79
+ orientationMode: .adaptative
80
+ )
81
+ agoraEngine.setVideoEncoderConfiguration(videoConfig)
82
+
83
+ // Local video preview
84
+ let localView = UIView(frame: localVideoContainer.bounds)
85
+ let canvas = AgoraRtcVideoCanvas()
86
+ canvas.uid = 0 // 0 = local user
87
+ canvas.view = localView
88
+ canvas.renderMode = .hidden
89
+ agoraEngine.setupLocalVideo(canvas)
90
+ agoraEngine.startPreview()
91
+
92
+ // Remote video (call in didJoinedOfUid delegate)
93
+ let remoteCanvas = AgoraRtcVideoCanvas()
94
+ remoteCanvas.uid = remoteUid
95
+ remoteCanvas.view = remoteVideoView
96
+ remoteCanvas.renderMode = .hidden
97
+ agoraEngine.setupRemoteVideo(remoteCanvas)
98
+ ```
99
+
100
+ ## Audio Setup
101
+
102
+ ```swift
103
+ // Enable audio (enabled by default)
104
+ agoraEngine.enableAudio()
105
+
106
+ // Audio profile
107
+ agoraEngine.setAudioProfile(.default)
108
+
109
+ // Mute/unmute local audio
110
+ agoraEngine.muteLocalAudioStream(true) // mute
111
+ agoraEngine.muteLocalAudioStream(false) // unmute
112
+
113
+ // Mute/unmute local video (stops sending video, camera stays active)
114
+ agoraEngine.muteLocalVideoStream(true) // video off
115
+ agoraEngine.muteLocalVideoStream(false) // video on
116
+
117
+ // Or disable video entirely (stops camera capture)
118
+ agoraEngine.enableLocalVideo(false) // camera off
119
+ agoraEngine.enableLocalVideo(true) // camera on
120
+
121
+ // Switch between speaker and earpiece
122
+ agoraEngine.setEnableSpeakerphone(true) // speaker
123
+ agoraEngine.setEnableSpeakerphone(false) // earpiece
124
+
125
+ // Mute remote user
126
+ agoraEngine.muteRemoteAudioStream(remoteUid, mute: true)
127
+ ```
128
+
129
+ ## Event Handling (Delegate)
130
+
131
+ ```swift
132
+ extension ViewController: AgoraRtcEngineDelegate {
133
+ // Successfully joined channel
134
+ func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) {
135
+ print("Joined channel: \(channel), uid: \(uid)")
136
+ }
137
+
138
+ // Remote user joined
139
+ func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
140
+ // Setup remote video view
141
+ let canvas = AgoraRtcVideoCanvas()
142
+ canvas.uid = uid
143
+ canvas.view = remoteVideoView
144
+ canvas.renderMode = .hidden
145
+ engine.setupRemoteVideo(canvas)
146
+ }
147
+
148
+ // Remote user left
149
+ func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
150
+ // Clean up remote view
151
+ let canvas = AgoraRtcVideoCanvas()
152
+ canvas.uid = uid
153
+ canvas.view = nil
154
+ engine.setupRemoteVideo(canvas)
155
+ }
156
+
157
+ // Token expiring
158
+ func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) {
159
+ // Fetch new token from server and renew
160
+ fetchNewToken { newToken in
161
+ engine.renewToken(newToken)
162
+ }
163
+ }
164
+
165
+ // Error
166
+ func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurError errorCode: AgoraErrorCode) {
167
+ print("Agora error: \(errorCode.rawValue)")
168
+ }
169
+
170
+ // Network quality
171
+ func rtcEngine(_ engine: AgoraRtcEngineKit, networkQuality uid: UInt,
172
+ txQuality: AgoraNetworkQuality, rxQuality: AgoraNetworkQuality) {
173
+ // 0=unknown, 1=excellent, 2=good, 3=poor, 4=bad, 5=very bad
174
+ }
175
+ }
176
+ ```
177
+
178
+ ## Leaving and Cleanup
179
+
180
+ ```swift
181
+ func leaveChannel() {
182
+ agoraEngine.stopPreview()
183
+ agoraEngine.leaveChannel(nil)
184
+ }
185
+
186
+ // Full cleanup (app termination)
187
+ func destroy() {
188
+ agoraEngine.stopPreview()
189
+ agoraEngine.leaveChannel(nil)
190
+ AgoraRtcEngineKit.destroy()
191
+ }
192
+ ```
193
+
194
+ ## Token Renewal
195
+
196
+ ```swift
197
+ func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) {
198
+ Task {
199
+ let newToken = await fetchTokenFromServer(channel: channelName, uid: localUid)
200
+ engine.renewToken(newToken)
201
+ }
202
+ }
203
+ ```
204
+
205
+ ## Complete Example: Video Call
206
+
207
+ ```swift
208
+ import UIKit
209
+ import AgoraRtcKit
210
+
211
+ class VideoCallViewController: UIViewController {
212
+ private var agoraEngine: AgoraRtcEngineKit!
213
+ private let appId = "your-app-id"
214
+ private var token: String? = nil
215
+ private let channelName = "test"
216
+
217
+ @IBOutlet weak var localVideoView: UIView!
218
+ @IBOutlet weak var remoteVideoView: UIView!
219
+
220
+ override func viewDidLoad() {
221
+ super.viewDidLoad()
222
+ initializeAgora()
223
+ }
224
+
225
+ private func initializeAgora() {
226
+ let config = AgoraRtcEngineConfig()
227
+ config.appId = appId
228
+ config.channelProfile = .communication
229
+
230
+ agoraEngine = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
231
+ agoraEngine.enableVideo()
232
+ agoraEngine.setVideoEncoderConfiguration(
233
+ AgoraVideoEncoderConfiguration(
234
+ size: CGSize(width: 640, height: 360),
235
+ frameRate: .fps24,
236
+ bitrate: AgoraVideoBitrateStandard,
237
+ orientationMode: .adaptative
238
+ )
239
+ )
240
+ }
241
+
242
+ @IBAction func joinChannel(_ sender: Any) {
243
+ // Setup local preview
244
+ let localCanvas = AgoraRtcVideoCanvas()
245
+ localCanvas.uid = 0
246
+ localCanvas.view = localVideoView
247
+ localCanvas.renderMode = .hidden
248
+ agoraEngine.setupLocalVideo(localCanvas)
249
+ agoraEngine.startPreview()
250
+
251
+ // Join
252
+ let option = AgoraRtcChannelMediaOptions()
253
+ option.clientRoleType = .broadcaster
254
+ option.channelProfile = .communication
255
+
256
+ agoraEngine.joinChannel(
257
+ byToken: token,
258
+ channelId: channelName,
259
+ uid: 0,
260
+ mediaOptions: option
261
+ )
262
+ }
263
+
264
+ @IBAction func leaveChannel(_ sender: Any) {
265
+ agoraEngine.stopPreview()
266
+ agoraEngine.leaveChannel(nil)
267
+ }
268
+
269
+ deinit {
270
+ agoraEngine.stopPreview()
271
+ agoraEngine.leaveChannel(nil)
272
+ AgoraRtcEngineKit.destroy()
273
+ }
274
+ }
275
+
276
+ extension VideoCallViewController: AgoraRtcEngineDelegate {
277
+ func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
278
+ let remoteCanvas = AgoraRtcVideoCanvas()
279
+ remoteCanvas.uid = uid
280
+ remoteCanvas.view = remoteVideoView
281
+ remoteCanvas.renderMode = .hidden
282
+ agoraEngine.setupRemoteVideo(remoteCanvas)
283
+ }
284
+
285
+ func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
286
+ let canvas = AgoraRtcVideoCanvas()
287
+ canvas.uid = uid
288
+ canvas.view = nil
289
+ agoraEngine.setupRemoteVideo(canvas)
290
+ }
291
+
292
+ func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) {
293
+ Task {
294
+ let newToken = await fetchTokenFromServer()
295
+ agoraEngine.renewToken(newToken)
296
+ }
297
+ }
298
+ }
299
+ ```
300
+
301
+ ## Official Documentation
302
+
303
+ For APIs or features not covered above:
304
+
305
+ - API Reference: <https://api-ref.agora.io/en/video-sdk/ios/4.x/API/rtc_api_overview_ng.html>
306
+ - Guides: <https://docs.agora.io/en/video-calling/overview/product-overview>
@@ -0,0 +1,87 @@
1
+ # Agora RTC — Next.js / SSR
2
+
3
+ The Agora Web SDK (`agora-rtc-sdk-ng`) and `agora-rtc-react` are browser-only. They cannot run during server-side rendering and will throw errors if imported at the module level in a Next.js Server Component or during SSR.
4
+
5
+ ## The Problem
6
+
7
+ `next/dynamic` with `ssr: false` works in Pages Router and in Client Components, but **does NOT work in Server Components** in Next.js 14+ App Router. A Server Component importing a dynamically-loaded client module that references Agora will still fail at build time if the import is not properly isolated.
8
+
9
+ ## Pattern: Wrap Both the Provider and Component
10
+
11
+ The correct pattern for Next.js App Router is to dynamically import both your Agora component **and** the `AgoraRTCProvider` together, inside a Client Component:
12
+
13
+ ```tsx
14
+ 'use client';
15
+
16
+ import { useMemo, Suspense } from 'react';
17
+ import dynamic from 'next/dynamic';
18
+
19
+ // Dynamically import your RTC component with ssr disabled
20
+ const ConversationComponent = dynamic(() => import('./ConversationComponent'), {
21
+ ssr: false,
22
+ });
23
+
24
+ // Dynamically import AgoraRTCProvider and create the client inside the same
25
+ // dynamic boundary — this keeps all Agora imports out of the SSR bundle
26
+ const AgoraProvider = dynamic(
27
+ async () => {
28
+ const { AgoraRTCProvider, default: AgoraRTC } =
29
+ await import('agora-rtc-react');
30
+ return {
31
+ default: ({ children }: { children: React.ReactNode }) => {
32
+ const client = useMemo(
33
+ () => AgoraRTC.createClient({ mode: 'rtc', codec: 'vp8' }),
34
+ [],
35
+ );
36
+ return <AgoraRTCProvider client={client}>{children}</AgoraRTCProvider>;
37
+ },
38
+ };
39
+ },
40
+ { ssr: false },
41
+ );
42
+
43
+ export default function Page() {
44
+ return (
45
+ <Suspense fallback={<div>Loading...</div>}>
46
+ <AgoraProvider>
47
+ <ConversationComponent />
48
+ </AgoraProvider>
49
+ </Suspense>
50
+ );
51
+ }
52
+ ```
53
+
54
+ **Why this works:** Both `AgoraRTCProvider` and `AgoraRTC.createClient` are inside the dynamic import callback, so they only execute in the browser. The `useMemo` ensures the client is created once per component mount, not on every render.
55
+
56
+ ## Pattern: Simple Component Lazy Load (Pages Router / Client Components)
57
+
58
+ If you're in a Pages Router context or inside an existing `"use client"` boundary, the simpler pattern works:
59
+
60
+ ```tsx
61
+ 'use client';
62
+
63
+ import { useState, useEffect } from 'react';
64
+
65
+ export default function Page() {
66
+ const [VideoCall, setVideoCall] = useState<React.ComponentType | null>(null);
67
+
68
+ useEffect(() => {
69
+ import('./VideoCall').then((m) => setVideoCall(() => m.default));
70
+ }, []);
71
+
72
+ if (!VideoCall) return <div>Loading...</div>;
73
+ return <VideoCall />;
74
+ }
75
+ ```
76
+
77
+ ## Key Rules
78
+
79
+ - Mark any component that imports Agora with `"use client"`.
80
+ - Never import `agora-rtc-sdk-ng` or `agora-rtc-react` at the top level of a Server Component.
81
+ - Create the `AgoraRTC.createClient` instance inside a `useMemo` (or outside the component tree) — never in the render function directly.
82
+ - Wrap dynamic-loaded Agora components in `<Suspense>` to handle the async load state.
83
+
84
+ ## Node.js Version Requirements
85
+
86
+ - Next.js 14 / 15: requires Node.js >= 18
87
+ - Next.js 16+: requires Node.js >= 20.9.0 (used in agent-samples clients)