diff --git a/AGENTS.md b/AGENTS.md
index 126fcd80c..af39f90f7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -5,7 +5,7 @@ Read this file first, then navigate to the relevant platform directory.
## Repository Overview
-This repository contains sample projects demonstrating Agora RTC Native SDK APIs across five platforms. Each platform is fully independent — do not share source files or dependencies across platforms.
+This repository contains sample projects demonstrating Agora RTC Native SDK APIs across four independent platforms. Each platform is self-contained — do not share source files, build scripts, or dependencies across platforms.
| Platform | Language(s) | Directory | SDK |
|----------|-------------|-----------|-----|
diff --git a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/PlayAudioFiles.java b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/PlayAudioFiles.java
index 6a440245c..20ba3a2ef 100644
--- a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/PlayAudioFiles.java
+++ b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/PlayAudioFiles.java
@@ -330,7 +330,7 @@ private void joinChannel(String channelId) {
Constants.AudioScenario.getValue(Constants.AudioScenario.valueOf(audioScenario.getSelectedItem().toString()))
);
- /**Please configure accessToken in the string_config file.
+ /**
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/ProcessAudioRawData.java b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/ProcessAudioRawData.java
index 18276db68..489c417b4 100644
--- a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/ProcessAudioRawData.java
+++ b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/ProcessAudioRawData.java
@@ -268,7 +268,7 @@ private void joinChannel(String channelId) {
engine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);
engine.setDefaultAudioRoutetoSpeakerphone(true);
- /**Please configure accessToken in the string_config file.
+ /**
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/RhythmPlayer.java b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/RhythmPlayer.java
index bd6801bb4..bfc1ae9a4 100644
--- a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/RhythmPlayer.java
+++ b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/RhythmPlayer.java
@@ -227,7 +227,7 @@ private void joinChannel(String channelId) {
engine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);
engine.enableAudioVolumeIndication(1000, 3, true);
- /**Please configure accessToken in the string_config file.
+ /**
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/SpatialSound.java b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/SpatialSound.java
index a05becde0..2fabbf920 100644
--- a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/SpatialSound.java
+++ b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/SpatialSound.java
@@ -219,7 +219,7 @@ private void joinChannel() {
engine.setClientRole(io.agora.rtc2.Constants.CLIENT_ROLE_BROADCASTER);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/VoiceEffects.java b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/VoiceEffects.java
index 0cb03300b..585510a54 100644
--- a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/VoiceEffects.java
+++ b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/VoiceEffects.java
@@ -397,7 +397,7 @@ private void joinChannel(String channelId) {
Constants.AudioScenario.getValue(Constants.AudioScenario.valueOf(audioScenario.getSelectedItem().toString()))
);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioRender.java b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioRender.java
index e26c4fbda..c0cf18837 100755
--- a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioRender.java
+++ b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioRender.java
@@ -233,7 +233,7 @@ private void joinChannel(String channelId) {
engine.setExternalAudioSink(true, SAMPLE_RATE, SAMPLE_NUM_OF_CHANNEL);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioSource.java b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioSource.java
index 3df640efa..19a3eadb0 100755
--- a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioSource.java
+++ b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioSource.java
@@ -278,7 +278,7 @@ private void joinChannel(String channelId) {
config.enableLocalPlayback = false;
customAudioTrack = engine.createCustomAudioTrack(Constants.AudioTrackType.AUDIO_TRACK_MIXABLE, config);
- /**Please configure accessToken in the string_config file.
+ /**
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/audio/AudioWaveform.java b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/audio/AudioWaveform.java
index 1b35bfc66..81718f550 100644
--- a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/audio/AudioWaveform.java
+++ b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/audio/AudioWaveform.java
@@ -168,7 +168,7 @@ private void joinChannel(String channelId) {
option.autoSubscribeVideo = true;
option.publishMicrophoneTrack = true;
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelAudio.java b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelAudio.java
index 4e84a5b0d..5989df2ed 100755
--- a/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelAudio.java
+++ b/Android/APIExample-Audio/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelAudio.java
@@ -463,7 +463,7 @@ private void joinChannel(String channelId) {
option.autoSubscribeAudio = true;
option.autoSubscribeVideo = true;
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/AgoraBeauty.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/AgoraBeauty.java
index a48f07d71..1ded647db 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/AgoraBeauty.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/AgoraBeauty.java
@@ -394,7 +394,7 @@ private void joinChannel(String channelId) {
VideoEncoderConfiguration.ORIENTATION_MODE.valueOf(((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingOrientation())
));
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ChannelEncryption.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ChannelEncryption.java
index b61249795..c0e8f5590 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ChannelEncryption.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ChannelEncryption.java
@@ -287,7 +287,7 @@ private void joinChannel(String channelId) {
ORIENTATION_MODE_ADAPTIVE
));
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ContentInspect.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ContentInspect.java
index 4839435fb..ee783fe4a 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ContentInspect.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ContentInspect.java
@@ -231,7 +231,7 @@ private void joinChannel(String channelId) {
engine.enableContentInspect(true, contentInspectConfig);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/CustomRemoteVideoRender.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/CustomRemoteVideoRender.java
index 790f995d8..bdeb4df92 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/CustomRemoteVideoRender.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/CustomRemoteVideoRender.java
@@ -273,7 +273,7 @@ private void joinChannel(String channelId) {
engine.startPreview();
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/FaceCapture.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/FaceCapture.java
index 7b002db61..fea16cf35 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/FaceCapture.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/FaceCapture.java
@@ -239,7 +239,7 @@ private void joinChannel(String channelId) {
engine.startPreview();
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/HostAcrossChannel.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/HostAcrossChannel.java
index 770e0cdf5..0601d3e6d 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/HostAcrossChannel.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/HostAcrossChannel.java
@@ -261,7 +261,7 @@ private void joinChannel(String channelId) {
VideoEncoderConfiguration.ORIENTATION_MODE.valueOf(((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingOrientation())
));
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/InCallReport.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/InCallReport.java
deleted file mode 100644
index ed193ebf6..000000000
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/InCallReport.java
+++ /dev/null
@@ -1,486 +0,0 @@
-package io.agora.api.example.examples.advanced;
-
-import static io.agora.rtc2.video.VideoCanvas.RENDER_MODE_HIDDEN;
-import static io.agora.rtc2.video.VideoEncoderConfiguration.STANDARD_BITRATE;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.widget.AppCompatTextView;
-
-import io.agora.api.example.MainApplication;
-import io.agora.api.example.R;
-import io.agora.api.example.common.BaseFragment;
-import io.agora.api.example.common.model.StatisticsInfo;
-import io.agora.api.example.utils.CommonUtil;
-import io.agora.api.example.utils.PermissonUtils;
-import io.agora.api.example.utils.TokenUtils;
-import io.agora.rtc2.Constants;
-import io.agora.rtc2.IRtcEngineEventHandler;
-import io.agora.rtc2.RtcEngine;
-import io.agora.rtc2.RtcEngineConfig;
-import io.agora.rtc2.proxy.LocalAccessPointConfiguration;
-import io.agora.rtc2.video.VideoCanvas;
-import io.agora.rtc2.video.VideoEncoderConfiguration;
-
-// Disabled: UI entry removed. Code retained for reference only.
-//@Example(
-// index = 17,
-// group = ADVANCED,
-// name = R.string.item_incallreport,
-// actionId = R.id.action_mainFragment_to_InCallReport,
-// tipsId = R.string.incallstats
-//)
-
-/**
- * The type In call report.
- *
- * @deprecated The report has been moved to
- * {@link io.agora.api.example.common.widget.VideoReportLayout}.
- * You can refer to {@link LiveStreaming} or {@link io.agora.api.example.examples.basic.JoinChannelVideo} example.
- */
-@Deprecated
-public class InCallReport extends BaseFragment implements View.OnClickListener {
- private static final String TAG = InCallReport.class.getSimpleName();
-
- private FrameLayout fl_local, fl_remote;
- private Button join;
- private EditText et_channel;
- private AppCompatTextView localStats, remoteStats;
- private RtcEngine engine;
- private StatisticsInfo statisticsInfo;
- private int myUid;
- private boolean joined = false;
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_in_call_report, container, false);
- return view;
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- join = view.findViewById(R.id.btn_join);
- statisticsInfo = new StatisticsInfo();
- et_channel = view.findViewById(R.id.et_channel);
- localStats = view.findViewById(R.id.local_stats);
- localStats.bringToFront();
- remoteStats = view.findViewById(R.id.remote_stats);
- remoteStats.bringToFront();
- view.findViewById(R.id.btn_join).setOnClickListener(this);
- fl_local = view.findViewById(R.id.fl_local);
- fl_remote = view.findViewById(R.id.fl_remote);
- }
-
- private void updateLocalStats() {
- handler.post(new Runnable() {
- @Override
- public void run() {
- localStats.setText(statisticsInfo.getLocalVideoStats());
- }
- });
- }
-
- private void updateRemoteStats() {
- handler.post(new Runnable() {
- @Override
- public void run() {
- remoteStats.setText(statisticsInfo.getRemoteVideoStats());
- }
- });
- }
-
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- // Check if the context is valid
- Context context = getContext();
- if (context == null) {
- return;
- }
- try {
- RtcEngineConfig config = new RtcEngineConfig();
- /*
- * The context of Android Activity
- */
- config.mContext = context.getApplicationContext();
- /*
- * The App ID issued to you by Agora. See How to get the App ID
- */
- config.mAppId = getAgoraAppId();
- /* Sets the channel profile of the Agora RtcEngine.
- CHANNEL_PROFILE_COMMUNICATION(0): (Default) The Communication profile.
- Use this profile in one-on-one calls or group calls, where all users can talk freely.
- CHANNEL_PROFILE_LIVE_BROADCASTING(1): The Live-Broadcast profile. Users in a live-broadcast
- channel have a role as either broadcaster or audience. A broadcaster can both send and receive streams;
- an audience can only receive streams.*/
- config.mChannelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
- /*
- * IRtcEngineEventHandler is an abstract class providing default implementation.
- * The SDK uses this class to report to the app on SDK runtime events.
- */
- config.mEventHandler = iRtcEngineEventHandler;
- config.mAudioScenario = Constants.AudioScenario.getValue(Constants.AudioScenario.DEFAULT);
- config.mAreaCode = ((MainApplication) getActivity().getApplication()).getGlobalSettings().getAreaCode();
- engine = RtcEngine.create(config);
- /*
- * This parameter is for reporting the usages of APIExample to agora background.
- * Generally, it is not necessary for you to set this parameter.
- */
- engine.setParameters("{"
- + "\"rtc.report_app_scenario\":"
- + "{"
- + "\"appScenario\":" + 100 + ","
- + "\"serviceType\":" + 11 + ","
- + "\"appVersion\":\"" + RtcEngine.getSdkVersion() + "\""
- + "}"
- + "}");
- /* setting the local access point if the private cloud ip was set, otherwise the config will be invalid.*/
- LocalAccessPointConfiguration localAccessPointConfiguration = ((MainApplication) getActivity().getApplication()).getGlobalSettings().getPrivateCloudConfig();
- if (localAccessPointConfiguration != null) {
- // This api can only be used in the private media server scenario, otherwise some problems may occur.
- engine.setLocalAccessPoint(localAccessPointConfiguration);
- }
- }
- catch (Exception e) {
- e.printStackTrace();
- getActivity().onBackPressed();
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- /*leaveChannel and Destroy the RtcEngine instance*/
- if (engine != null) {
- engine.leaveChannel();
- engine.stopPreview();
- }
- handler.post(RtcEngine::destroy);
- engine = null;
- }
-
- @Override
- public void onClick(View v) {
- if (v.getId() == R.id.btn_join) {
- if (!joined) {
- CommonUtil.hideInputBoard(getActivity(), et_channel);
- // call when join button hit
- String channelId = et_channel.getText().toString();
- // Check permission
- checkOrRequestPermisson(new PermissonUtils.PermissionResultCallback() {
- @Override
- public void onPermissionsResult(boolean allPermissionsGranted, String[] permissions, int[] grantResults) {
- // Permissions Granted
- if (allPermissionsGranted) {
- joinChannel(channelId);
- }
- }
- });
- } else {
- joined = false;
- /*After joining a channel, the user must call the leaveChannel method to end the
- * call before joining another channel. This method returns 0 if the user leaves the
- * channel and releases all resources related to the call. This method call is
- * asynchronous, and the user has not exited the channel when the method call returns.
- * Once the user leaves the channel, the SDK triggers the onLeaveChannel callback.
- * A successful leaveChannel method call triggers the following callbacks:
- * 1:The local client: onLeaveChannel.
- * 2:The remote client: onUserOffline, if the user leaving the channel is in the
- * Communication channel, or is a BROADCASTER in the Live Broadcast profile.
- * @returns 0: Success.
- * < 0: Failure.
- * PS:
- * 1:If you call the destroy method immediately after calling the leaveChannel
- * method, the leaveChannel process interrupts, and the SDK does not trigger
- * the onLeaveChannel callback.
- * 2:If you call the leaveChannel method during CDN live streaming, the SDK
- * triggers the removeInjectStreamUrl method.*/
- engine.leaveChannel();
- join.setText(getString(R.string.join));
- }
- }
- }
-
- private void joinChannel(String channelId) {
- // Check if the context is valid
- Context context = getContext();
- if (context == null) {
- return;
- }
-
- // Create render view by RtcEngine
- SurfaceView surfaceView = new SurfaceView(context);
- if (fl_local.getChildCount() > 0) {
- fl_local.removeAllViews();
- }
- // Add to the local container
- fl_local.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
- // Setup local video to render your local camera preview
- engine.setupLocalVideo(new VideoCanvas(surfaceView, RENDER_MODE_HIDDEN, 0));
- // Set audio route to microPhone
- engine.setDefaultAudioRoutetoSpeakerphone(true);
-
- /*In the demo, the default is to enter as the anchor.*/
- engine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);
- // Enable video module
- engine.enableVideo();
- // start preview
- engine.startPreview();
- // Setup video encoding configs
- engine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(
- ((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingDimensionObject(),
- VideoEncoderConfiguration.FRAME_RATE.valueOf(((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingFrameRate()),
- STANDARD_BITRATE,
- VideoEncoderConfiguration.ORIENTATION_MODE.valueOf(((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingOrientation())
- ));
-
- /*Please configure accessToken in the string_config file.
- * A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
- * https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
- * A token generated at the server. This applies to scenarios with high-security requirements. For details, see
- * https://docs.agora.io/en/cloud-recording/token_server_java?platform=Java*/
- TokenUtils.gen(requireContext(), channelId, 0, ret -> {
- /* Allows a user to join a channel.
- if you do not specify the uid, we will generate the uid for you*/
- int res = engine.joinChannel(ret, channelId, "Extra Optional Data", 0);
- if (res != 0) {
- // Usually happens with invalid parameters
- // Error code description can be found at:
- // en: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html
- // cn: https://docs.agora.io/cn/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html
- showAlert(RtcEngine.getErrorDescription(Math.abs(res)));
- return;
- }
- // Prevent repeated entry
- join.setEnabled(false);
- });
- }
-
- /**
- * IRtcEngineEventHandler is an abstract class providing default implementation.
- * The SDK uses this class to report to the app on SDK runtime events.
- */
- private final IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler() {
- /**
- * Error code description can be found at:
- * en: https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onerror
- * cn: https://docs.agora.io/cn/video-call-4.x/API%20Reference/java_ng/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onerror
- */
- @Override
- public void onError(int err) {
- Log.w(TAG, String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err)));
- }
-
-
- /**Occurs when a user leaves the channel.
- * @param stats With this callback, the application retrieves the channel information,
- * such as the call duration and statistics.*/
- @Override
- public void onLeaveChannel(RtcStats stats) {
- super.onLeaveChannel(stats);
- Log.i(TAG, String.format("local user %d leaveChannel!", myUid));
- showLongToast(String.format("local user %d leaveChannel!", myUid));
- }
-
- /**Occurs when the local user joins a specified channel.
- * The channel name assignment is based on channelName specified in the joinChannel method.
- * If the uid is not specified when joinChannel is called, the server automatically assigns a uid.
- * @param channel Channel name
- * @param uid User ID
- * @param elapsed Time elapsed (ms) from the user calling joinChannel until this callback is triggered*/
- @Override
- public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
- Log.i(TAG, String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
- showLongToast(String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
- myUid = uid;
- joined = true;
- handler.post(new Runnable() {
- @Override
- public void run() {
- join.setEnabled(true);
- join.setText(getString(R.string.leave));
- }
- });
- }
-
- /**Since v2.9.0.
- * This callback indicates the state change of the remote audio stream.
- * PS: This callback does not work properly when the number of users (in the Communication profile) or
- * broadcasters (in the Live-broadcast profile) in the channel exceeds 17.
- * @param uid ID of the user whose audio state changes.
- * @param state State of the remote audio
- * REMOTE_AUDIO_STATE_STOPPED(0): The remote audio is in the default state, probably due
- * to REMOTE_AUDIO_REASON_LOCAL_MUTED(3), REMOTE_AUDIO_REASON_REMOTE_MUTED(5),
- * or REMOTE_AUDIO_REASON_REMOTE_OFFLINE(7).
- * REMOTE_AUDIO_STATE_STARTING(1): The first remote audio packet is received.
- * REMOTE_AUDIO_STATE_DECODING(2): The remote audio stream is decoded and plays normally,
- * probably due to REMOTE_AUDIO_REASON_NETWORK_RECOVERY(2),
- * REMOTE_AUDIO_REASON_LOCAL_UNMUTED(4) or REMOTE_AUDIO_REASON_REMOTE_UNMUTED(6).
- * REMOTE_AUDIO_STATE_FROZEN(3): The remote audio is frozen, probably due to
- * REMOTE_AUDIO_REASON_NETWORK_CONGESTION(1).
- * REMOTE_AUDIO_STATE_FAILED(4): The remote audio fails to start, probably due to
- * REMOTE_AUDIO_REASON_INTERNAL(0).
- * @param reason The reason of the remote audio state change.
- * REMOTE_AUDIO_REASON_INTERNAL(0): Internal reasons.
- * REMOTE_AUDIO_REASON_NETWORK_CONGESTION(1): Network congestion.
- * REMOTE_AUDIO_REASON_NETWORK_RECOVERY(2): Network recovery.
- * REMOTE_AUDIO_REASON_LOCAL_MUTED(3): The local user stops receiving the remote audio
- * stream or disables the audio module.
- * REMOTE_AUDIO_REASON_LOCAL_UNMUTED(4): The local user resumes receiving the remote audio
- * stream or enables the audio module.
- * REMOTE_AUDIO_REASON_REMOTE_MUTED(5): The remote user stops sending the audio stream or
- * disables the audio module.
- * REMOTE_AUDIO_REASON_REMOTE_UNMUTED(6): The remote user resumes sending the audio stream
- * or enables the audio module.
- * REMOTE_AUDIO_REASON_REMOTE_OFFLINE(7): The remote user leaves the channel.
- * @param elapsed Time elapsed (ms) from the local user calling the joinChannel method
- * until the SDK triggers this callback.*/
- @Override
- public void onRemoteAudioStateChanged(int uid, int state, int reason, int elapsed) {
- super.onRemoteAudioStateChanged(uid, state, reason, elapsed);
- Log.i(TAG, "onRemoteAudioStateChanged->" + uid + ", state->" + state + ", reason->" + reason);
- }
-
- /**Since v2.9.0.
- * Occurs when the remote video state changes.
- * PS: This callback does not work properly when the number of users (in the Communication
- * profile) or broadcasters (in the Live-broadcast profile) in the channel exceeds 17.
- * @param uid ID of the remote user whose video state changes.
- * @param state State of the remote video:
- * REMOTE_VIDEO_STATE_STOPPED(0): The remote video is in the default state, probably due
- * to REMOTE_VIDEO_STATE_REASON_LOCAL_MUTED(3), REMOTE_VIDEO_STATE_REASON_REMOTE_MUTED(5),
- * or REMOTE_VIDEO_STATE_REASON_REMOTE_OFFLINE(7).
- * REMOTE_VIDEO_STATE_STARTING(1): The first remote video packet is received.
- * REMOTE_VIDEO_STATE_DECODING(2): The remote video stream is decoded and plays normally,
- * probably due to REMOTE_VIDEO_STATE_REASON_NETWORK_RECOVERY (2),
- * REMOTE_VIDEO_STATE_REASON_LOCAL_UNMUTED(4), REMOTE_VIDEO_STATE_REASON_REMOTE_UNMUTED(6),
- * or REMOTE_VIDEO_STATE_REASON_AUDIO_FALLBACK_RECOVERY(9).
- * REMOTE_VIDEO_STATE_FROZEN(3): The remote video is frozen, probably due to
- * REMOTE_VIDEO_STATE_REASON_NETWORK_CONGESTION(1) or REMOTE_VIDEO_STATE_REASON_AUDIO_FALLBACK(8).
- * REMOTE_VIDEO_STATE_FAILED(4): The remote video fails to start, probably due to
- * REMOTE_VIDEO_STATE_REASON_INTERNAL(0).
- * @param reason The reason of the remote video state change:
- * REMOTE_VIDEO_STATE_REASON_INTERNAL(0): Internal reasons.
- * REMOTE_VIDEO_STATE_REASON_NETWORK_CONGESTION(1): Network congestion.
- * REMOTE_VIDEO_STATE_REASON_NETWORK_RECOVERY(2): Network recovery.
- * REMOTE_VIDEO_STATE_REASON_LOCAL_MUTED(3): The local user stops receiving the remote
- * video stream or disables the video module.
- * REMOTE_VIDEO_STATE_REASON_LOCAL_UNMUTED(4): The local user resumes receiving the remote
- * video stream or enables the video module.
- * REMOTE_VIDEO_STATE_REASON_REMOTE_MUTED(5): The remote user stops sending the video
- * stream or disables the video module.
- * REMOTE_VIDEO_STATE_REASON_REMOTE_UNMUTED(6): The remote user resumes sending the video
- * stream or enables the video module.
- * REMOTE_VIDEO_STATE_REASON_REMOTE_OFFLINE(7): The remote user leaves the channel.
- * REMOTE_VIDEO_STATE_REASON_AUDIO_FALLBACK(8): The remote media stream falls back to the
- * audio-only stream due to poor network conditions.
- * REMOTE_VIDEO_STATE_REASON_AUDIO_FALLBACK_RECOVERY(9): The remote media stream switches
- * back to the video stream after the network conditions improve.
- * @param elapsed Time elapsed (ms) from the local user calling the joinChannel method until
- * the SDK triggers this callback.*/
- @Override
- public void onRemoteVideoStateChanged(int uid, int state, int reason, int elapsed) {
- super.onRemoteVideoStateChanged(uid, state, reason, elapsed);
- Log.i(TAG, "onRemoteVideoStateChanged->" + uid + ", state->" + state + ", reason->" + reason);
- }
-
- /**Occurs when a remote user (Communication)/host (Live Broadcast) joins the channel.
- * @param uid ID of the user whose audio state changes.
- * @param elapsed Time delay (ms) from the local user calling joinChannel/setClientRole
- * until this callback is triggered.*/
- @Override
- public void onUserJoined(int uid, int elapsed) {
- super.onUserJoined(uid, elapsed);
- Log.i(TAG, "onUserJoined->" + uid);
- showLongToast(String.format("user %d joined!", uid));
- /*Check if the context is correct*/
- Context context = getContext();
- if (context == null) {
- return;
- }
- handler.post(() -> {
- /*Display remote video stream*/
- SurfaceView surfaceView = null;
- if (fl_remote.getChildCount() > 0) {
- fl_remote.removeAllViews();
- }
- // Create render view by RtcEngine
- surfaceView = new SurfaceView(context);
- surfaceView.setZOrderMediaOverlay(true);
- // Add to the remote container
- fl_remote.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
-
- // Setup remote video to render
- engine.setupRemoteVideo(new VideoCanvas(surfaceView, RENDER_MODE_HIDDEN, uid));
- });
- }
-
- /**Occurs when a remote user (Communication)/host (Live Broadcast) leaves the channel.
- * @param uid ID of the user whose audio state changes.
- * @param reason Reason why the user goes offline:
- * USER_OFFLINE_QUIT(0): The user left the current channel.
- * USER_OFFLINE_DROPPED(1): The SDK timed out and the user dropped offline because no data
- * packet was received within a certain period of time. If a user quits the
- * call and the message is not passed to the SDK (due to an unreliable channel),
- * the SDK assumes the user dropped offline.
- * USER_OFFLINE_BECOME_AUDIENCE(2): (Live broadcast only.) The client role switched from
- * the host to the audience.*/
- @Override
- public void onUserOffline(int uid, int reason) {
- Log.i(TAG, String.format("user %d offline! reason:%d", uid, reason));
- showLongToast(String.format("user %d offline! reason:%d", uid, reason));
- handler.post(new Runnable() {
- @Override
- public void run() {
- /*Clear render view
- Note: The video will stay at its last frame, to completely remove it you will need to
- remove the SurfaceView from its parent*/
- engine.setupRemoteVideo(new VideoCanvas(null, RENDER_MODE_HIDDEN, uid));
- }
- });
- }
-
- @Override
- public void onRemoteAudioStats(RemoteAudioStats remoteAudioStats) {
- statisticsInfo.setRemoteAudioStats(remoteAudioStats);
- updateRemoteStats();
- }
-
- @Override
- public void onLocalAudioStats(LocalAudioStats localAudioStats) {
- statisticsInfo.setLocalAudioStats(localAudioStats);
- updateLocalStats();
- }
-
- @Override
- public void onRemoteVideoStats(RemoteVideoStats remoteVideoStats) {
- statisticsInfo.setRemoteVideoStats(remoteVideoStats);
- updateRemoteStats();
- }
-
- @Override
- public void onLocalVideoStats(Constants.VideoSourceType source, LocalVideoStats stats) {
- super.onLocalVideoStats(source, stats);
- statisticsInfo.setLocalVideoStats(stats);
- updateLocalStats();
- }
-
- @Override
- public void onRtcStats(RtcStats rtcStats) {
- statisticsInfo.setRtcStats(rtcStats);
- }
- };
-}
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/JoinMultipleChannel.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/JoinMultipleChannel.java
index 17e40f8ff..191f48f65 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/JoinMultipleChannel.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/JoinMultipleChannel.java
@@ -269,7 +269,7 @@ private void joinChannel(String channelId) {
VideoEncoderConfiguration.ORIENTATION_MODE.valueOf(((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingOrientation())
));
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/LiveStreaming.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/LiveStreaming.java
index b82fa47d8..e8de67458 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/LiveStreaming.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/LiveStreaming.java
@@ -549,7 +549,6 @@ private void joinChannel(String channelId) {
/*
- * Please configure accessToken in the string_config file.
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/LocalVideoTranscoding.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/LocalVideoTranscoding.java
index 36e88262d..4c9a80f01 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/LocalVideoTranscoding.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/LocalVideoTranscoding.java
@@ -280,7 +280,7 @@ private void joinChannel(String channelId) {
option.publishMicrophoneTrack = true;
option.publishTranscodedVideoTrack = true;
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MediaMetadata.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MediaMetadata.java
index 8747dc500..deb16ac20 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MediaMetadata.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MediaMetadata.java
@@ -260,7 +260,7 @@ private void joinChannel(String channelId) {
int code = engine.registerMediaMetadataObserver(iMetadataObserver, IMetadataObserver.VIDEO_METADATA);
Log.e(TAG, code + "");
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MediaPlayer.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MediaPlayer.java
index 4278d7540..fbd593423 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MediaPlayer.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MediaPlayer.java
@@ -338,7 +338,7 @@ private void joinChannel(String channelId) {
options.publishMicrophoneTrack = false;
options.enableAudioRecordingOrPlayout = true;
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MediaRecorder.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MediaRecorder.java
index f93c4b8a9..8430977e8 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MediaRecorder.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MediaRecorder.java
@@ -265,7 +265,7 @@ private void joinChannel(String channelId) {
option.publishMicrophoneTrack = true;
option.publishCameraTrack = true;
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MultiVideoSourceTracks.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MultiVideoSourceTracks.java
index 5dcf0d621..a47fc677b 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MultiVideoSourceTracks.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MultiVideoSourceTracks.java
@@ -241,7 +241,7 @@ private void joinChannel(String channelId) {
VideoEncoderConfiguration.ORIENTATION_MODE.valueOf(((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingOrientation())
));
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/Multipath.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/Multipath.java
index 5b3139186..11111ae84 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/Multipath.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/Multipath.java
@@ -288,7 +288,7 @@ private void joinChannel(String channelId, boolean broadcast) {
Log.d(TAG, mediaOptions.toString());
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PictureInPicture.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PictureInPicture.java
index 7c73b7303..ae7e89aef 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PictureInPicture.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PictureInPicture.java
@@ -265,7 +265,7 @@ private void joinChannel(String channelId) {
option.publishMicrophoneTrack = true;
option.publishCameraTrack = true;
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PlayAudioFiles.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PlayAudioFiles.java
index 0363996b6..7cf455f82 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PlayAudioFiles.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PlayAudioFiles.java
@@ -333,7 +333,7 @@ private void joinChannel(String channelId) {
Constants.AudioScenario.getValue(Constants.AudioScenario.valueOf(audioScenario.getSelectedItem().toString()))
);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ProcessAudioRawData.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ProcessAudioRawData.java
index 5b6450bc5..84fcae813 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ProcessAudioRawData.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ProcessAudioRawData.java
@@ -268,7 +268,7 @@ private void joinChannel(String channelId) {
engine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);
engine.setDefaultAudioRoutetoSpeakerphone(true);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ProcessRawData.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ProcessRawData.java
index e051003d1..2515be2d8 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ProcessRawData.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ProcessRawData.java
@@ -244,7 +244,7 @@ private void joinChannel(String channelId) {
engine.startPreview();
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PushExternalVideo.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PushExternalVideo.java
deleted file mode 100644
index e31816c58..000000000
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PushExternalVideo.java
+++ /dev/null
@@ -1,555 +0,0 @@
-package io.agora.api.example.examples.advanced;
-
-import static io.agora.rtc2.video.VideoCanvas.RENDER_MODE_HIDDEN;
-import static io.agora.rtc2.video.VideoEncoderConfiguration.STANDARD_BITRATE;
-
-import android.content.Context;
-import android.graphics.SurfaceTexture;
-import android.hardware.Camera;
-import android.opengl.EGLSurface;
-import android.opengl.GLES11Ext;
-import android.opengl.GLES20;
-import android.opengl.Matrix;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.SurfaceView;
-import android.view.TextureView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.io.IOException;
-import java.util.concurrent.Callable;
-
-import io.agora.api.example.MainApplication;
-import io.agora.api.example.R;
-import io.agora.api.example.common.BaseFragment;
-import io.agora.api.example.common.gles.ProgramTextureOES;
-import io.agora.api.example.common.gles.core.EglCore;
-import io.agora.api.example.common.gles.core.GlUtil;
-import io.agora.api.example.utils.CommonUtil;
-import io.agora.api.example.utils.PermissonUtils;
-import io.agora.api.example.utils.TokenUtils;
-import io.agora.base.TextureBufferHelper;
-import io.agora.base.VideoFrame;
-import io.agora.base.internal.video.EglBase;
-import io.agora.base.internal.video.EglBase14;
-import io.agora.base.internal.video.RendererCommon;
-import io.agora.base.internal.video.YuvConverter;
-import io.agora.rtc2.ChannelMediaOptions;
-import io.agora.rtc2.Constants;
-import io.agora.rtc2.IRtcEngineEventHandler;
-import io.agora.rtc2.RtcEngine;
-import io.agora.rtc2.RtcEngineConfig;
-import io.agora.rtc2.proxy.LocalAccessPointConfiguration;
-import io.agora.rtc2.video.VideoCanvas;
-import io.agora.rtc2.video.VideoEncoderConfiguration;
-
-//@Example(
-// index = 7,
-// group = ADVANCED,
-// name = R.string.item_pushexternal,
-// actionId = R.id.action_mainFragment_to_PushExternalVideo,
-// tipsId = R.string.pushexternalvideo
-//)
-
-/**
- * The type Push external video.
- *
- * @deprecated The impletation of custom has been moved to {@link PushExternalVideoYUV}. You can refer to {@link PushExternalVideoYUV} example.
- */
-@Deprecated
-public class PushExternalVideo extends BaseFragment implements View.OnClickListener, TextureView.SurfaceTextureListener,
- SurfaceTexture.OnFrameAvailableListener {
- private static final String TAG = PushExternalVideo.class.getSimpleName();
- private final int DEFAULT_CAPTURE_WIDTH = 640;
- private final int DEFAULT_CAPTURE_HEIGHT = 480;
-
- private FrameLayout fl_local, fl_remote;
- private Button join;
- private EditText et_channel;
- private RtcEngine engine;
- private int myUid;
- private volatile boolean joined = false;
-
- private YuvConverter mYuvConverter = new YuvConverter();
- private Handler mHandler;
- private int mPreviewTexture;
- private SurfaceTexture mPreviewSurfaceTexture;
- private EglCore mEglCore;
- private EGLSurface mDummySurface;
- private EGLSurface mDrawSurface;
- private ProgramTextureOES mProgram;
- private float[] mTransform = new float[16];
- private float[] mMVPMatrix = new float[16];
- private boolean mMVPMatrixInit = false;
- private Camera mCamera;
- private int mFacing = Camera.CameraInfo.CAMERA_FACING_FRONT;
- private boolean mPreviewing = false;
- private int mSurfaceWidth;
- private int mSurfaceHeight;
- private boolean mTextureDestroyed;
- private volatile boolean glPrepared;
- private volatile TextureBufferHelper textureBufferHelper;
-
- private boolean prepareGl(EglBase.Context eglContext, final int width, final int height) {
- Log.d(TAG, "prepareGl");
- textureBufferHelper = TextureBufferHelper.create("STProcess", eglContext);
- if (textureBufferHelper == null) {
- return false;
- }
- Log.d(TAG, "prepareGl completed");
- return true;
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_push_externalvideo, container, false);
- return view;
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- join = view.findViewById(R.id.btn_join);
- et_channel = view.findViewById(R.id.et_channel);
- view.findViewById(R.id.btn_join).setOnClickListener(this);
- fl_local = view.findViewById(R.id.fl_local);
- fl_remote = view.findViewById(R.id.fl_remote);
- }
-
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- // Check if the context is valid
- Context context = getContext();
- if (context == null) {
- return;
- }
- try {
- RtcEngineConfig config = new RtcEngineConfig();
- /*
- * The context of Android Activity
- */
- config.mContext = context.getApplicationContext();
- /*
- * The App ID issued to you by Agora. See How to get the App ID
- */
- config.mAppId = getAgoraAppId();
- /* Sets the channel profile of the Agora RtcEngine.
- CHANNEL_PROFILE_COMMUNICATION(0): (Default) The Communication profile.
- Use this profile in one-on-one calls or group calls, where all users can talk freely.
- CHANNEL_PROFILE_LIVE_BROADCASTING(1): The Live-Broadcast profile. Users in a live-broadcast
- channel have a role as either broadcaster or audience. A broadcaster can both send and receive streams;
- an audience can only receive streams.*/
- config.mChannelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
- /*
- * IRtcEngineEventHandler is an abstract class providing default implementation.
- * The SDK uses this class to report to the app on SDK runtime events.
- */
- config.mEventHandler = iRtcEngineEventHandler;
- config.mAudioScenario = Constants.AudioScenario.getValue(Constants.AudioScenario.DEFAULT);
- config.mAreaCode = ((MainApplication) getActivity().getApplication()).getGlobalSettings().getAreaCode();
- engine = RtcEngine.create(config);
- /*
- * This parameter is for reporting the usages of APIExample to agora background.
- * Generally, it is not necessary for you to set this parameter.
- */
- engine.setParameters("{"
- + "\"rtc.report_app_scenario\":"
- + "{"
- + "\"appScenario\":" + 100 + ","
- + "\"serviceType\":" + 11 + ","
- + "\"appVersion\":\"" + RtcEngine.getSdkVersion() + "\""
- + "}"
- + "}");
- /* setting the local access point if the private cloud ip was set, otherwise the config will be invalid.*/
- LocalAccessPointConfiguration localAccessPointConfiguration = ((MainApplication) getActivity().getApplication()).getGlobalSettings().getPrivateCloudConfig();
- if (localAccessPointConfiguration != null) {
- // This api can only be used in the private media server scenario, otherwise some problems may occur.
- engine.setLocalAccessPoint(localAccessPointConfiguration);
- }
- }
- catch (Exception e) {
- e.printStackTrace();
- getActivity().onBackPressed();
- }
- }
-
-
- @Override
- public void onDestroy() {
- /*leaveChannel and Destroy the RtcEngine instance*/
- if (engine != null) {
- /*After joining a channel, the user must call the leaveChannel method to end the
- * call before joining another channel. This method returns 0 if the user leaves the
- * channel and releases all resources related to the call. This method call is
- * asynchronous, and the user has not exited the channel when the method call returns.
- * Once the user leaves the channel, the SDK triggers the onLeaveChannel callback.
- * A successful leaveChannel method call triggers the following callbacks:
- * 1:The local client: onLeaveChannel.
- * 2:The remote client: onUserOffline, if the user leaving the channel is in the
- * Communication channel, or is a BROADCASTER in the Live Broadcast profile.
- * @returns 0: Success.
- * < 0: Failure.
- * PS:
- * 1:If you call the destroy method immediately after calling the leaveChannel
- * method, the leaveChannel process interrupts, and the SDK does not trigger
- * the onLeaveChannel callback.
- * 2:If you call the leaveChannel method during CDN live streaming, the SDK
- * triggers the removeInjectStreamUrl method.*/
- engine.leaveChannel();
- engine.stopPreview();
- if (textureBufferHelper != null) {
- textureBufferHelper.dispose();
- textureBufferHelper = null;
- }
- }
- engine = null;
- super.onDestroy();
- handler.post(RtcEngine::destroy);
- }
-
- @Override
- public void onClick(View v) {
- if (v.getId() == R.id.btn_join) {
- if (!joined) {
- CommonUtil.hideInputBoard(getActivity(), et_channel);
- // call when join button hit
- String channelId = et_channel.getText().toString();
- // Check permission
- checkOrRequestPermisson(new PermissonUtils.PermissionResultCallback() {
- @Override
- public void onPermissionsResult(boolean allPermissionsGranted, String[] permissions, int[] grantResults) {
- // Permissions Granted
- if (allPermissionsGranted) {
- joinChannel(channelId);
- }
- }
- });
- } else {
- fl_local.setVisibility(View.GONE);
- getActivity().onBackPressed();
- }
- }
- }
-
- private void joinChannel(String channelId) {
-// engine.setParameters("{\"rtc.log_filter\":65535}");
- // Check if the context is valid
- Context context = getContext();
- if (context == null) {
- return;
- }
-
- // Create render view by RtcEngine
- TextureView textureView = new TextureView(getContext());
- //add SurfaceTextureListener
- textureView.setSurfaceTextureListener(this);
- // Add to the local container
- fl_local.addView(textureView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
- /*Set up to play remote sound with receiver*/
- engine.setDefaultAudioRoutetoSpeakerphone(true);
-
- /*In the demo, the default is to enter as the anchor.*/
- engine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);
- // Enables the video module.
- engine.enableVideo();
- // Setup video encoding configs
- engine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(
- ((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingDimensionObject(),
- VideoEncoderConfiguration.FRAME_RATE.valueOf(((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingFrameRate()),
- STANDARD_BITRATE,
- VideoEncoderConfiguration.ORIENTATION_MODE.valueOf(((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingOrientation())
- ));
- /*Configures the external video source.
- * @param enable Sets whether or not to use the external video source:
- * true: Use the external video source.
- * false: Do not use the external video source.
- * @param useTexture Sets whether or not to use texture as an input:
- * true: Use texture as an input.
- * false: (Default) Do not use texture as an input.
- * @param pushMode
- * VIDEO_FRAME: Use the ENCODED_VIDEO_FRAME.
- * ENCODED_VIDEO_FRAME: Use the ENCODED_VIDEO_FRAME*/
- engine.setExternalVideoSource(true, true, Constants.ExternalVideoSourceType.VIDEO_FRAME);
-
- /*Please configure accessToken in the string_config file.
- * A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
- * https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
- * A token generated at the server. This applies to scenarios with high-security requirements. For details, see
- * https://docs.agora.io/en/cloud-recording/token_server_java?platform=Java*/
- TokenUtils.gen(requireContext(), channelId, 0, token -> {
- /* Allows a user to join a channel.
- if you do not specify the uid, we will generate the uid for you*/
-
- ChannelMediaOptions option = new ChannelMediaOptions();
- option.autoSubscribeAudio = true;
- option.autoSubscribeVideo = true;
- int res = engine.joinChannel(token, channelId, 0, option);
- if (res != 0) {
- // Usually happens with invalid parameters
- // Error code description can be found at:
- // en: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html
- // cn: https://docs.agora.io/cn/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html
- showAlert(RtcEngine.getErrorDescription(Math.abs(res)));
- return;
- }
- // Prevent repeated entry
- join.setEnabled(false);
- });
-
- }
-
- @Override
- public void onFrameAvailable(SurfaceTexture surfaceTexture) {
- if (mTextureDestroyed) {
- return;
- }
-
- if (!mEglCore.isCurrent(mDrawSurface)) {
- mEglCore.makeCurrent(mDrawSurface);
- }
- try {
- surfaceTexture.updateTexImage();
- surfaceTexture.getTransformMatrix(mTransform);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- /*The rectangle ratio of frames and the screen surface may be different, so cropping may
- * happen when display frames to the screen.
- * The display transformation matrix does not change for the same camera when the screen
- * orientation remains the same.*/
- if (!mMVPMatrixInit) {
- /*For simplicity, we only consider the activity as portrait mode. In this case, the captured
- * images should be rotated 90 degrees (left or right).Thus the frame width and height
- * should be swapped.*/
- float frameRatio = DEFAULT_CAPTURE_HEIGHT / (float) DEFAULT_CAPTURE_WIDTH;
- float surfaceRatio = mSurfaceWidth / (float) mSurfaceHeight;
- Matrix.setIdentityM(mMVPMatrix, 0);
-
- if (frameRatio >= surfaceRatio) {
- float w = DEFAULT_CAPTURE_WIDTH * surfaceRatio;
- float scaleW = DEFAULT_CAPTURE_HEIGHT / w;
- Matrix.scaleM(mMVPMatrix, 0, scaleW, 1, 1);
- } else {
- float h = DEFAULT_CAPTURE_HEIGHT / surfaceRatio;
- float scaleH = DEFAULT_CAPTURE_WIDTH / h;
- Matrix.scaleM(mMVPMatrix, 0, 1, scaleH, 1);
- }
- mMVPMatrixInit = true;
- }
- GLES20.glViewport(0, 0, mSurfaceWidth, mSurfaceHeight);
- mProgram.drawFrame(mPreviewTexture, mTransform, mMVPMatrix);
- mEglCore.swapBuffers(mDrawSurface);
-
- if (joined) {
- VideoFrame.Buffer buffer = textureBufferHelper.invoke(new Callable() {
- @Override
- public VideoFrame.Buffer call() throws Exception {
- return textureBufferHelper.wrapTextureBuffer(DEFAULT_CAPTURE_HEIGHT,
- DEFAULT_CAPTURE_WIDTH, VideoFrame.TextureBuffer.Type.OES, mPreviewTexture,
- RendererCommon.convertMatrixToAndroidGraphicsMatrix(mTransform));
- }
- });
- VideoFrame frame = new VideoFrame(buffer, 0, 0);
- /*Pushes the video frame using the AgoraVideoFrame class and passes the video frame to the Agora SDK.
- * Call the setExternalVideoSource method and set pushMode as true before calling this
- * method. Otherwise, a failure returns after calling this method.
- * @param frame AgoraVideoFrame
- * @return
- * true: The frame is pushed successfully.
- * false: Failed to push the frame.
- * PS:
- * In the Communication profile, the SDK does not support textured video frames.*/
- boolean a = engine.pushExternalVideoFrame(frame);
- Log.d(TAG, "pushExternalVideoFrame:" + a);
- }
- }
-
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
- Log.i(TAG, "onSurfaceTextureAvailable");
- mTextureDestroyed = false;
- mSurfaceWidth = width;
- mSurfaceHeight = height;
- /* handler associate to the GL thread which creates the texture.
- * in some condition SDK need to convert from texture format to YUV format, in this case,
- * SDK will use this handler to switch into the GL thread to complete the conversion.
- * */
- mHandler = new Handler(Looper.myLooper());
- mEglCore = new EglCore();
- if (!glPrepared) {
- // setup egl context
- EglBase.Context eglContext = new EglBase14.Context(mEglCore.getEGLContext());
- glPrepared = prepareGl(eglContext, width, height);
- }
- mDummySurface = mEglCore.createOffscreenSurface(1, 1);
- mEglCore.makeCurrent(mDummySurface);
- mPreviewTexture = GlUtil.createTextureObject(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
- mPreviewSurfaceTexture = new SurfaceTexture(mPreviewTexture);
- mPreviewSurfaceTexture.setOnFrameAvailableListener(this);
- mDrawSurface = mEglCore.createWindowSurface(surface);
- mProgram = new ProgramTextureOES();
- if (mCamera != null || mPreviewing) {
- Log.e(TAG, "Camera preview has been started");
- return;
- }
- try {
- mCamera = Camera.open(mFacing);
- /*It is assumed to capture images of resolution 640x480. During development, it should
- * be the most suitable supported resolution that best fits the scenario.*/
- Camera.Parameters parameters = mCamera.getParameters();
- parameters.setPreviewSize(DEFAULT_CAPTURE_WIDTH, DEFAULT_CAPTURE_HEIGHT);
- mCamera.setParameters(parameters);
- mCamera.setPreviewTexture(mPreviewSurfaceTexture);
- /*The display orientation is 90 for both front and back facing cameras using a surface
- * texture for the preview when the screen is in portrait mode.*/
- mCamera.setDisplayOrientation(90);
- mCamera.startPreview();
- mPreviewing = true;
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
-
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
- Log.i(TAG, "onSurfaceTextureDestroyed");
- mTextureDestroyed = true;
- if (mCamera != null && mPreviewing) {
- mCamera.stopPreview();
- mPreviewing = false;
- mCamera.release();
- mCamera = null;
- }
- mProgram.release();
- mEglCore.releaseSurface(mDummySurface);
- mEglCore.releaseSurface(mDrawSurface);
- mEglCore.release();
- return true;
- }
-
- @Override
- public void onSurfaceTextureUpdated(SurfaceTexture surface) {
-
- }
-
- /**
- * IRtcEngineEventHandler is an abstract class providing default implementation.
- * The SDK uses this class to report to the app on SDK runtime events.
- */
- private final IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler() {
- /**
- * Error code description can be found at:
- * en: https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onerror
- * cn: https://docs.agora.io/cn/video-call-4.x/API%20Reference/java_ng/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onerror
- */
- @Override
- public void onError(int err) {
- Log.w(TAG, String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err)));
- }
-
- /**Occurs when a user leaves the channel.
- * @param stats With this callback, the application retrieves the channel information,
- * such as the call duration and statistics.*/
- @Override
- public void onLeaveChannel(RtcStats stats) {
- super.onLeaveChannel(stats);
- Log.i(TAG, String.format("local user %d leaveChannel!", myUid));
- showLongToast(String.format("local user %d leaveChannel!", myUid));
- }
-
- /**Occurs when the local user joins a specified channel.
- * The channel name assignment is based on channelName specified in the joinChannel method.
- * If the uid is not specified when joinChannel is called, the server automatically assigns a uid.
- * @param channel Channel name
- * @param uid User ID
- * @param elapsed Time elapsed (ms) from the user calling joinChannel until this callback is triggered*/
- @Override
- public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
- Log.i(TAG, String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
- showLongToast(String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
- myUid = uid;
- joined = true;
- handler.post(new Runnable() {
- @Override
- public void run() {
- join.setEnabled(true);
- join.setText(getString(R.string.leave));
- }
- });
- }
-
- /**Occurs when a remote user (Communication)/host (Live Broadcast) joins the channel.
- * @param uid ID of the user whose audio state changes.
- * @param elapsed Time delay (ms) from the local user calling joinChannel/setClientRole
- * until this callback is triggered.*/
- @Override
- public void onUserJoined(int uid, int elapsed) {
- super.onUserJoined(uid, elapsed);
- Log.i(TAG, "onUserJoined->" + uid);
- showLongToast(String.format("user %d joined!", uid));
- /*Check if the context is correct*/
- Context context = getContext();
- if (context == null) {
- return;
- }
- handler.post(() -> {
- /*Display remote video stream*/
- // Create render view by RtcEngine
- SurfaceView surfaceView = new SurfaceView(context);
- surfaceView.setZOrderMediaOverlay(true);
- if (fl_remote.getChildCount() > 0) {
- fl_remote.removeAllViews();
- }
- // Add to the remote container
- fl_remote.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
- // Setup remote video to render
- engine.setupRemoteVideo(new VideoCanvas(surfaceView, RENDER_MODE_HIDDEN, uid));
- });
- }
-
- /**Occurs when a remote user (Communication)/host (Live Broadcast) leaves the channel.
- * @param uid ID of the user whose audio state changes.
- * @param reason Reason why the user goes offline:
- * USER_OFFLINE_QUIT(0): The user left the current channel.
- * USER_OFFLINE_DROPPED(1): The SDK timed out and the user dropped offline because no data
- * packet was received within a certain period of time. If a user quits the
- * call and the message is not passed to the SDK (due to an unreliable channel),
- * the SDK assumes the user dropped offline.
- * USER_OFFLINE_BECOME_AUDIENCE(2): (Live broadcast only.) The client role switched from
- * the host to the audience.*/
- @Override
- public void onUserOffline(int uid, int reason) {
- Log.i(TAG, String.format("user %d offline! reason:%d", uid, reason));
- showLongToast(String.format("user %d offline! reason:%d", uid, reason));
- handler.post(new Runnable() {
- @Override
- public void run() {
- /*Clear render view
- Note: The video will stay at its last frame, to completely remove it you will need to
- remove the SurfaceView from its parent*/
- engine.setupRemoteVideo(new VideoCanvas(null, RENDER_MODE_HIDDEN, uid));
- }
- });
- }
- };
-}
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PushExternalVideoYUV.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PushExternalVideoYUV.java
index 17b3d7127..063c37f84 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PushExternalVideoYUV.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PushExternalVideoYUV.java
@@ -301,7 +301,7 @@ private void joinChannel(String channelId) {
fl_local.addView(textureView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
engine.startPreview(Constants.VideoSourceType.VIDEO_SOURCE_CUSTOM);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/RTMPStreaming.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/RTMPStreaming.java
index eea6a0007..af08f18be 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/RTMPStreaming.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/RTMPStreaming.java
@@ -240,7 +240,7 @@ private void joinChannel(String channelId) {
/*Set up to play remote sound with receiver*/
engine.setDefaultAudioRoutetoSpeakerphone(true);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/RhythmPlayer.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/RhythmPlayer.java
index 28386196f..194fdde17 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/RhythmPlayer.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/RhythmPlayer.java
@@ -229,7 +229,7 @@ private void joinChannel(String channelId) {
engine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);
engine.enableAudioVolumeIndication(1000, 3, true);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ScreenSharing.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ScreenSharing.java
index c7c548a66..d385c5dc8 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ScreenSharing.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/ScreenSharing.java
@@ -412,7 +412,7 @@ private void joinChannel() {
startScreenSharePreview();
}
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SendDataStream.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SendDataStream.java
index f438d3356..98d5f990c 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SendDataStream.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SendDataStream.java
@@ -240,7 +240,7 @@ private void joinChannel(String channelId) {
/*Set up to play remote sound with receiver*/
engine.setDefaultAudioRoutetoSpeakerphone(true);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SimpleExtension.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SimpleExtension.java
index b3430bc2e..ec68029b7 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SimpleExtension.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SimpleExtension.java
@@ -333,7 +333,7 @@ private void joinChannel(String channelId) {
engine.setClientRole(CLIENT_ROLE_BROADCASTER);
engine.enableAudioVolumeIndication(1000, 3, false);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/Simulcast.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/Simulcast.java
index 051164c35..e9db675a9 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/Simulcast.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/Simulcast.java
@@ -335,7 +335,7 @@ private void joinChannel(String channelId, boolean broadcast) {
engine.setSimulcastConfig(simulcastConfig);
}
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SpatialSound.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SpatialSound.java
index 6e796ce0f..1f6eb2c0b 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SpatialSound.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SpatialSound.java
@@ -219,7 +219,7 @@ private void joinChannel() {
engine.setClientRole(io.agora.rtc2.Constants.CLIENT_ROLE_BROADCASTER);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SwitchCameraScreenShare.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SwitchCameraScreenShare.java
index 213be52f7..4f9cceefb 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SwitchCameraScreenShare.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SwitchCameraScreenShare.java
@@ -350,7 +350,7 @@ private void joinChannel(String channelId) {
/*Set up to play remote sound with receiver*/
engine.setDefaultAudioRoutetoSpeakerphone(true);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/TransparentRendering.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/TransparentRendering.java
index f53e803e1..6c7b348e4 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/TransparentRendering.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/TransparentRendering.java
@@ -248,7 +248,7 @@ private void joinChannel(String channelId) {
option.publishCameraTrack = false;
option.publishCustomVideoTrack = true;
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VideoProcessExtension.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VideoProcessExtension.java
index a890e3ecf..04928919b 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VideoProcessExtension.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VideoProcessExtension.java
@@ -293,7 +293,7 @@ private void joinChannel(String channelId) {
VideoEncoderConfiguration.ORIENTATION_MODE.valueOf(((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingOrientation())
));
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VideoQuickSwitch.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VideoQuickSwitch.java
index 6a7cb8abc..2051fe5c6 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VideoQuickSwitch.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VideoQuickSwitch.java
@@ -288,7 +288,7 @@ private void joinChannel(String channelId) {
/*Set up to play remote sound with receiver*/
engine.setDefaultAudioRoutetoSpeakerphone(true);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VoiceEffects.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VoiceEffects.java
index 9a9439ec1..bb68f7dff 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VoiceEffects.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VoiceEffects.java
@@ -397,7 +397,7 @@ private void joinChannel(String channelId) {
Constants.AudioScenario.getValue(Constants.AudioScenario.valueOf(audioScenario.getSelectedItem().toString()))
);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioRender.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioRender.java
index f7cb510f8..09b7c8649 100755
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioRender.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioRender.java
@@ -233,7 +233,7 @@ private void joinChannel(String channelId) {
engine.setExternalAudioSink(true, SAMPLE_RATE, SAMPLE_NUM_OF_CHANNEL);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioSource.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioSource.java
index d1f79394b..df467373b 100755
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioSource.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioSource.java
@@ -275,7 +275,7 @@ private void joinChannel(String channelId) {
config.enableLocalPlayback = false;
customAudioTrack = engine.createCustomAudioTrack(Constants.AudioTrackType.AUDIO_TRACK_MIXABLE, config);
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioRouterPlayerExo.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioRouterPlayerExo.java
index ae75d7edc..aa64576e3 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioRouterPlayerExo.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioRouterPlayerExo.java
@@ -123,7 +123,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
option.publishMicrophoneTrack = true;
option.publishCameraTrack = true;
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioRouterPlayerIjk.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioRouterPlayerIjk.java
index 339a95ce4..c5a15375e 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioRouterPlayerIjk.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioRouterPlayerIjk.java
@@ -120,7 +120,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
option.publishMicrophoneTrack = true;
option.publishCameraTrack = true;
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioRouterPlayerNative.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioRouterPlayerNative.java
index c8876a924..e6563ea47 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioRouterPlayerNative.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioRouterPlayerNative.java
@@ -121,7 +121,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
option.publishMicrophoneTrack = true;
option.publishCameraTrack = true;
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioWaveform.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioWaveform.java
index e083f8479..57eb8ff15 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioWaveform.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/audio/AudioWaveform.java
@@ -171,7 +171,7 @@ private void joinChannel(String channelId) {
option.autoSubscribeVideo = true;
option.publishMicrophoneTrack = true;
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelAudio.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelAudio.java
index f2591d056..f83c52537 100755
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelAudio.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelAudio.java
@@ -552,7 +552,7 @@ private void joinChannel(String channelId) {
option.autoSubscribeAudio = true;
option.autoSubscribeVideo = true;
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelVideo.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelVideo.java
index 7d341f5f4..0e1ed1432 100644
--- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelVideo.java
+++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelVideo.java
@@ -239,7 +239,7 @@ private void joinChannel(String channelId) {
option.publishMicrophoneTrack = true;
option.publishCameraTrack = true;
- /*Please configure accessToken in the string_config file.
+ /*
* A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
* https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
* A token generated at the server. This applies to scenarios with high-security requirements. For details, see
diff --git a/Android/APIExample/app/src/main/res/layout/fragment_in_call_report.xml b/Android/APIExample/app/src/main/res/layout/fragment_in_call_report.xml
deleted file mode 100644
index f2ef0b03f..000000000
--- a/Android/APIExample/app/src/main/res/layout/fragment_in_call_report.xml
+++ /dev/null
@@ -1,93 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Android/APIExample/app/src/main/res/navigation/nav_graph.xml b/Android/APIExample/app/src/main/res/navigation/nav_graph.xml
index 4377ecd84..e2e3cab68 100755
--- a/Android/APIExample/app/src/main/res/navigation/nav_graph.xml
+++ b/Android/APIExample/app/src/main/res/navigation/nav_graph.xml
@@ -92,9 +92,6 @@
-
@@ -325,11 +322,6 @@
android:name="io.agora.api.example.examples.advanced.MediaRecorder"
android:label="@string/item_media_recorder"
tools:layout="@layout/fragment_media_recorder" />
-
Media Stream Encryption(Custom Encoder)
Media Stream Encryption
Set the Video Profile
- Report In-call Statistics
Super Resolution
RTC Live Streaming
Multi Process - Screen Sharing
@@ -166,7 +165,6 @@
This example shows the behavior of audio router while communicating with rtc.
This example demonstrates how to display waveform by rtc engine.
This example demonstrates how to use MediaRecorder to recorde local or remote video.
- This example demonstrates how to display in call statistics.
This example demonstrates how to transfer media streaming to another rtc channel.
This example demonstrates how to encrypt and decrypt audio and video frames during audio and video calls.
This example demonstrates how to use VideoEncoderConfiguration to adjust video configurations.
diff --git a/iOS/APIExample/.agent/skills/review-case/SKILL.md b/iOS/APIExample/.agent/skills/review-case/SKILL.md
index 403a8c84e..70e70d6eb 100644
--- a/iOS/APIExample/.agent/skills/review-case/SKILL.md
+++ b/iOS/APIExample/.agent/skills/review-case/SKILL.md
@@ -102,7 +102,7 @@ AgoraAudioSession.sharedInstance().requestRecordPermission { [weak self] granted
**Check:**
- Entry class inherits `UIViewController`, Main class inherits `BaseViewController`
-- Class names follow `Entry` / `Main` pattern
+- Entry class usually follows `Entry`; the main controller may be `Main` or an existing project-specific `*ViewController` name as long as storyboard wiring is correct
- `configs` dictionary used to pass data from Entry to Main (no direct property injection)
- File placed under `Examples/Basic/` or `Examples/Advanced/` matching the MenuItem section
- Storyboard ID of Main scene matches the `controller` field in `MenuItem`
diff --git a/iOS/APIExample/.agent/skills/upsert-case/SKILL.md b/iOS/APIExample/.agent/skills/upsert-case/SKILL.md
index eccfe1e6b..9e1c531d2 100644
--- a/iOS/APIExample/.agent/skills/upsert-case/SKILL.md
+++ b/iOS/APIExample/.agent/skills/upsert-case/SKILL.md
@@ -16,7 +16,7 @@ metadata:
## When to Use
- **Add**: the feature has no existing case in `Examples/Basic/` or `Examples/Advanced/`
-- **Modify**: the case already exists — skip Steps 1–3, go directly to Step 4+
+- **Modify**: the case already exists — update the existing `.swift` and storyboard first, then check registration and docs
Before adding, search the Case Index in `ARCHITECTURE.md` to confirm the case does not already exist.
@@ -24,11 +24,22 @@ Before adding, search the Case Index in `ARCHITECTURE.md` to confirm the case do
| Scenario | Files |
|----------|-------|
-| Add new case | New folder + `.swift` file + `.storyboard`, `ViewController.swift` (MenuItem), `ARCHITECTURE.md` (Case Index) |
-| Modify existing case | Existing `.swift` file(s), optionally `.storyboard`, `ARCHITECTURE.md` (Case Index) |
+| Add new case | New folder + `.swift` file + `Base.lproj/.storyboard`, `ViewController.swift` (MenuItem), `ARCHITECTURE.md` (Case Index) |
+| Modify existing case | Existing `.swift` file(s), optionally `Base.lproj/.storyboard`, `ViewController.swift` if registration/wiring changed, `ARCHITECTURE.md` (Case Index) |
---
+## Modify Existing Case
+
+When repairing or rebuilding an existing case, use this order instead of the new-case flow:
+
+1. Locate the existing `.swift` implementation and update the actual runtime logic first
+2. Update the existing storyboard if scene wiring, outlets, actions, or controller identifiers changed
+3. Check `APIExample/ViewController.swift` and fix the `MenuItem` only if registration or `controller` / `storyboard` wiring is wrong
+4. Update `ARCHITECTURE.md` last if the case path, APIs, or description changed
+
+Do not skip implementation edits just because the case folder already exists.
+
## Step 1 — Create the Example Folder
```
@@ -98,7 +109,7 @@ extension Main: AgoraRtcEngineDelegate {
## Step 3 — Create the Storyboard
-Create `APIExample/Base.lproj/.storyboard` with two scenes:
+Create `APIExample/Examples/[Basic|Advanced]//Base.lproj/.storyboard` with two scenes:
| Scene | Storyboard ID | Class |
|-------|--------------|-------|
@@ -107,6 +118,8 @@ Create `APIExample/Base.lproj/.storyboard` with two scenes:
Connect a `Show` segue or use the manual push in `onJoinPressed`.
+Keep the storyboard inside the example folder. Do not place new case storyboards in the shared `APIExample/Base.lproj/` directory.
+
## Default Entry UI Convention
Unless the user explicitly asks for a different flow, use this default Entry layout and interaction:
diff --git a/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo/SKILL.md b/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo/SKILL.md
deleted file mode 100644
index f3f7f3a51..000000000
--- a/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo/SKILL.md
+++ /dev/null
@@ -1,194 +0,0 @@
----
-name: join-channel-video-guide
-description: Guide for implementing video call functionality in business scenarios, including SDK initialization, joining channels, video encoding configuration, and event handling
-compatibility: [Cursor, Kiro, Windsurf, Claude, Copilot]
-license: MIT
-metadata:
- example: JoinChannelVideo
- category: Basic
- apis: [AgoraRtcEngineKit, joinChannel, enableVideo, setupLocalVideo, setupRemoteVideo]
----
-
-# Video Call Implementation Guide
-
-## Feature Description
-
-This example demonstrates how to use Agora RTC SDK to implement basic video call functionality, including:
-- Initialize SDK engine
-- Configure video encoding parameters (resolution, frame rate, orientation)
-- Join channel
-- Display local and remote video
-- Handle user join/leave events
-
-## Core API Call Flow
-
-### 1. Initialize SDK
-
-```swift
-let config = AgoraRtcEngineConfig()
-config.appId = KeyCenter.AppId
-config.areaCode = GlobalSettings.shared.area
-config.channelProfile = .liveBroadcasting
-agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
-```
-
-**Key Parameters:**
-- `appId`: App ID obtained from Agora Console
-- `areaCode`: Region code for specifying connection region
-- `channelProfile`: Channel profile, `.liveBroadcasting` supports host and audience roles
-
-### 2. Set User Role and Enable Audio/Video
-
-```swift
-agoraKit.setClientRole(.broadcaster) // or .audience
-agoraKit.enableVideo()
-agoraKit.enableAudio()
-```
-
-### 3. Configure Video Encoding Parameters
-
-```swift
-agoraKit.setVideoEncoderConfiguration(
- AgoraVideoEncoderConfiguration(
- size: CGSize(width: 960, height: 540),
- frameRate: .fps15,
- bitrate: AgoraVideoBitrateStandard,
- orientationMode: .adaptative,
- mirrorMode: .auto
- )
-)
-```
-
-**Configurable Parameters:**
-- `size`: Video resolution (90x90 ~ 1280x720)
-- `frameRate`: Frame rate (10/15/24/30/60 fps)
-- `orientationMode`: Video orientation mode
- - `.adaptative`: Adaptive
- - `.fixedLandscape`: Fixed landscape
- - `.fixedPortrait`: Fixed portrait
-
-### 4. Set Local Video Preview
-
-```swift
-let videoCanvas = AgoraRtcVideoCanvas()
-videoCanvas.uid = 0 // Local user uid is 0
-videoCanvas.view = localVideoView
-videoCanvas.renderMode = .hidden
-agoraKit.setupLocalVideo(videoCanvas)
-agoraKit.startPreview()
-```
-
-### 5. Join Channel
-
-```swift
-let option = AgoraRtcChannelMediaOptions()
-option.publishCameraTrack = true
-option.publishMicrophoneTrack = true
-option.clientRoleType = .broadcaster
-
-agoraKit.joinChannel(
- byToken: token,
- channelId: channelName,
- uid: 0, // 0 means SDK automatically assigns uid
- mediaOptions: option
-)
-```
-
-**Important Notes:**
-- If App Certificate is enabled in console, Token must be used
-- Token must be generated with the same channel name and uid
-- Return value of 0 indicates success
-
-### 6. Set Remote Video
-
-Set in `didJoinedOfUid` callback:
-
-```swift
-func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
- let videoCanvas = AgoraRtcVideoCanvas()
- videoCanvas.uid = uid
- videoCanvas.view = remoteVideoView
- videoCanvas.renderMode = .hidden
- agoraKit.setupRemoteVideo(videoCanvas)
-}
-```
-
-### 7. Leave Channel and Clean Up Resources
-
-```swift
-agoraKit.disableAudio()
-agoraKit.disableVideo()
-agoraKit.stopPreview()
-agoraKit.leaveChannel { stats in
- print("left channel, duration: \(stats.duration)")
-}
-```
-
-## Key Event Callbacks
-
-### didJoinChannel
-```swift
-func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int)
-```
-Triggered when local user successfully joins channel.
-
-### didJoinedOfUid
-```swift
-func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int)
-```
-Triggered when remote user joins channel (not triggered for audience role).
-
-### didOfflineOfUid
-```swift
-func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason)
-```
-Triggered when remote user leaves channel.
-
-### didOccurError
-```swift
-func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurError errorCode: AgoraErrorCode)
-```
-Triggered when SDK encounters an error, recommend displaying error message to user.
-
-## Common Questions
-
-### Q: Can't see local video?
-A: Check if `startPreview()` and `setupLocalVideo()` have been called
-
-### Q: Can't see remote video?
-A:
-1. Confirm remote user role is broadcaster
-2. Check if `setupRemoteVideo()` is correctly set in `didJoinedOfUid` callback
-3. Confirm `option.publishCameraTrack = true`
-
-### Q: joinChannel returns non-zero value?
-A:
-- Check if App ID is correct
-- If App Certificate is enabled, check if Token is valid
-- Check if channel name and uid match those used when generating Token
-
-### Q: How to switch camera?
-A: Call `agoraKit.switchCamera()`
-
-### Q: How to mute/unmute?
-A:
-- Local mute: `agoraKit.muteLocalAudioStream(true/false)`
-- Remote mute: `agoraKit.muteRemoteAudioStream(uid, mute: true/false)`
-
-### Q: How to disable/enable video?
-A:
-- Local video: `agoraKit.muteLocalVideoStream(true/false)`
-- Remote video: `agoraKit.muteRemoteVideoStream(uid, mute: true/false)`
-
-## Reference Documentation
-
-- [iOS API Reference (English)](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit)
-- [iOS API Reference (Chinese)](https://doc.shengwang.cn/api-ref/rtc/ios/API/toc_video_call)
-- [Error Code Description](https://doc.shengwang.cn/api-ref/rtc/ios/error-code)
-
-## Related Examples
-
-- `JoinChannelAudio` - Audio-only call
-- `JoinChannelVideoToken` - Join channel with Token
-- `VideoProcess` - Video processing (filters, watermarks)
-- `CustomVideoSourcePush` - Custom video source
diff --git a/macOS/.agent/skills/review-case/SKILL.md b/macOS/.agent/skills/review-case/SKILL.md
index b21c61176..92d813ec2 100644
--- a/macOS/.agent/skills/review-case/SKILL.md
+++ b/macOS/.agent/skills/review-case/SKILL.md
@@ -87,6 +87,7 @@ See `references/incorrect-thread-safety.swift` for common mistakes.
- [ ] Microphone permission requested before `enableAudio()`
- [ ] Camera permission requested before `enableVideo()`
- [ ] Permissions checked before accessing devices
+- [ ] Review guidance stays macOS-specific and does not suggest iOS-only APIs such as `AVAudioSession.sharedInstance().requestRecordPermission`
**Correct Pattern:**
```swift
@@ -98,7 +99,7 @@ func initializeAgoraEngine() {
}
}
- AVAudioSession.sharedInstance().requestRecordPermission { granted in
+ AVCaptureDevice.requestAccess(for: .audio) { granted in
if granted {
self.agoraKit.enableAudio()
}
diff --git a/macOS/.agent/skills/upsert-case/SKILL.md b/macOS/.agent/skills/upsert-case/SKILL.md
index c8827d048..4e7b61909 100644
--- a/macOS/.agent/skills/upsert-case/SKILL.md
+++ b/macOS/.agent/skills/upsert-case/SKILL.md
@@ -2,7 +2,7 @@
name: upsert-case
description: >
Add a new API example or modify an existing one. Covers both creation and modification scenarios,
- including file structure, registration, and ARCHITECTURE.md updates.
+ including file structure, per-example storyboard creation, registration, and ARCHITECTURE.md updates.
compatibility: [Cursor, Kiro, Windsurf, Claude, Copilot]
license: MIT
metadata:
@@ -30,9 +30,10 @@ Use this skill when you need to:
1. Determine if the example belongs in `Basic/` or `Advanced/`
2. Create the example folder with PascalCase name
3. Create the Swift implementation file
-4. Register the example in `ViewController.swift`
-5. Update `ARCHITECTURE.md` Case Index
-6. Verify compilation and functionality
+4. Create the example storyboard
+5. Register the example in `ViewController.swift`
+6. Update `ARCHITECTURE.md` Case Index
+7. Verify compilation and functionality
### Scenario 2: Modify an Existing Example
@@ -41,8 +42,9 @@ Use this skill when you need to:
**Steps:**
1. Locate the example in `APIExample/Examples/[Basic|Advanced]//`
2. Modify the Swift file
-3. Update `ARCHITECTURE.md` Case Index if APIs changed
-4. Verify compilation and functionality
+3. Modify the storyboard if outlets, actions, or controller identifiers changed
+4. Update `ARCHITECTURE.md` Case Index if APIs changed
+5. Verify compilation and functionality
---
@@ -53,6 +55,7 @@ Use this skill when you need to:
| File | Action | Notes |
|------|--------|-------|
| `APIExample/Examples/[Basic\|Advanced]//.swift` | Create | Main implementation file |
+| `APIExample/Examples/[Basic\|Advanced]//Base.lproj/.storyboard` | Create | Example UI and controller identifier |
| `APIExample/ViewController.swift` | Modify | Register example in menu/list |
| `ARCHITECTURE.md` | Modify | Add entry to Case Index |
@@ -61,6 +64,7 @@ Use this skill when you need to:
| File | Action | Notes |
|------|--------|-------|
| `APIExample/Examples/[Basic\|Advanced]//.swift` | Modify | Update implementation |
+| `APIExample/Examples/[Basic\|Advanced]//Base.lproj/.storyboard` | Modify if needed | Keep outlets and controller identifiers aligned |
| `ARCHITECTURE.md` | Modify | Update Case Index if APIs changed |
---
@@ -86,19 +90,26 @@ Create `APIExample/Examples/[Basic|Advanced]//.swift`
Use the template from `references/example-template.swift` as a starting point. Replace `` with your example name.
-### Step 4: Register in ViewController
+### Step 4: Create the Example Storyboard
+
+Create `APIExample/Examples/[Basic|Advanced]//Base.lproj/.storyboard`.
+
+The storyboard name must match the value passed to `NSStoryboard(name:bundle:)` from `ViewController.swift`, and the main controller identifier in the storyboard must match the `controller` field in `MenuItem`.
+
+### Step 5: Register in ViewController
Edit `APIExample/ViewController.swift` and add the example to the menu/list:
```swift
-// In the example list or menu setup
-let examples = [
- // ... existing examples
- ("ExampleName", Main.self),
-]
+MenuItem(name: "Example Name".localized,
+ identifier: "menuCell",
+ controller: "",
+ storyboard: "")
```
-### Step 5: Update ARCHITECTURE.md
+Place it in the correct section (Basic / Advanced).
+
+### Step 6: Update ARCHITECTURE.md
Add a new row to the Case Index table in `ARCHITECTURE.md`:
@@ -108,13 +119,14 @@ Add a new row to the Case Index table in `ARCHITECTURE.md`:
**Key APIs column:** List 2-5 core SDK methods used in this example.
-### Step 6: Verify
+### Step 7: Verify
- [ ] Code compiles without errors
- [ ] Example appears in the menu/list
- [ ] Example can join channel and receive callbacks
- [ ] `leaveChannel()` and `destroy()` are called on close
- [ ] UI updates happen on main thread
+- [ ] Storyboard loads with the expected controller identifier
- [ ] ARCHITECTURE.md Case Index is updated
---
@@ -139,6 +151,7 @@ See `references/` directory for code patterns:
- Forget to implement `AgoraRtcEngineDelegate` for event handling
- Leave the channel without calling `leaveChannel()` first
- Modify examples outside the `APIExample/Examples/[Basic|Advanced]/` structure
+- Register a new menu item without also creating the per-example storyboard under the same example folder
- Forget to update `ARCHITECTURE.md` Case Index after adding/modifying an example
---
@@ -150,6 +163,8 @@ After completing the upsert, verify:
- [ ] Example folder is in correct location (`APIExample/Examples/[Basic|Advanced]//`)
- [ ] Swift file is named `.swift` (PascalCase)
- [ ] Class name is `Main` and extends `BaseViewController`
+- [ ] Storyboard exists at `APIExample/Examples/[Basic|Advanced]//Base.lproj/.storyboard`
+- [ ] Storyboard identifier matches the `controller` value registered in `ViewController.swift`
- [ ] Example is registered in `ViewController.swift`
- [ ] `initializeAgoraEngine()` creates engine with correct config
- [ ] `joinChannel()` uses token from `KeyCenter`
diff --git a/macOS/AGENTS.md b/macOS/AGENTS.md
index 93c142e20..2116452eb 100644
--- a/macOS/AGENTS.md
+++ b/macOS/AGENTS.md
@@ -59,15 +59,6 @@ All work must conform to the rules defined in `ARCHITECTURE.md`:
- State management uses instance variables and delegate callbacks — do not introduce Combine or async/await patterns unless they already exist in the file being modified
- Match the code style, naming, and patterns of existing examples
-### Use Example-Level SKILLs
-
-Each example may contain a `SKILL.md` file in its folder. When working on or referencing a specific example:
-1. Check whether a `SKILL.md` exists in that example's directory
-2. If it exists, read it before making changes — it describes the API usage, call flow, and known constraints
-3. If it does not exist, one will be created in the future; proceed using the source code as the reference
-
-**SKILL.md location pattern:** `APIExample/Examples/[Basic|Advanced]//SKILL.md`
-
### Use Project-Level SKILLs
For broader tasks, use the skills in `.agent/skills/`:
diff --git a/macOS/ARCHITECTURE.md b/macOS/ARCHITECTURE.md
index 78299542f..61149da5b 100644
--- a/macOS/ARCHITECTURE.md
+++ b/macOS/ARCHITECTURE.md
@@ -47,7 +47,7 @@ macOS/
Each example lives in its own folder under `APIExample/Examples/Basic/` or `APIExample/Examples/Advanced/` and consists of:
- A Swift file containing the example implementation
-- Optional: A storyboard or XIB file for UI layout
+- A per-example storyboard, typically at `Base.lproj/.storyboard`
### Example Pattern
diff --git a/windows/.agent/skills/review-case/SKILL.md b/windows/.agent/skills/review-case/SKILL.md
index 95095e3c3..ead1ccdfa 100644
--- a/windows/.agent/skills/review-case/SKILL.md
+++ b/windows/.agent/skills/review-case/SKILL.md
@@ -29,10 +29,10 @@ Use this skill when you need to:
- [ ] Engine is created in `InitializeAgoraEngine()` or similar
- [ ] Engine is initialized with `RtcEngineContext`
- [ ] `leaveChannel()` is called before `release()`
-- [ ] `release()` is called in `PostNcDestroy()` or cleanup method
+- [ ] `release()` is called in the case's real cleanup path
- [ ] No engine leaks (engine not recreated on every join)
-**Correct Pattern:**
+**Correct Pattern A — standalone dialog teardown:**
```cpp
BOOL CExampleDlg::OnInitDialog() {
CDialogEx::OnInitDialog();
@@ -61,6 +61,33 @@ void CExampleDlg::LeaveChannel() {
}
```
+**Correct Pattern B — scene-switching dialog teardown:**
+```cpp
+bool CExampleDlg::InitAgora() {
+ m_rtcEngine = createAgoraRtcEngine();
+ // initialize once when the scene becomes active
+ return m_rtcEngine != nullptr;
+}
+
+void CExampleDlg::UnInitAgora() {
+ if (!m_rtcEngine) return;
+ if (m_joinChannel) {
+ m_rtcEngine->leaveChannel();
+ }
+ m_rtcEngine->release(nullptr);
+ m_rtcEngine = nullptr;
+}
+
+void CExampleDlg::OnShowWindow(BOOL bShow, UINT nStatus) {
+ CDialogEx::OnShowWindow(bShow, nStatus);
+ if (!bShow) {
+ UnInitAgora();
+ }
+}
+```
+
+Accept either pattern as long as the dialog follows one lifecycle consistently and does not leak the engine across scene switches.
+
**Incorrect Pattern:**
See `references/incorrect-lifecycle.cpp` for common mistakes.
diff --git a/windows/.agent/skills/upsert-case/SKILL.md b/windows/.agent/skills/upsert-case/SKILL.md
index 0f53149ef..f809c4b4a 100644
--- a/windows/.agent/skills/upsert-case/SKILL.md
+++ b/windows/.agent/skills/upsert-case/SKILL.md
@@ -2,7 +2,8 @@
name: upsert-case
description: >
Add a new API example or modify an existing one. Covers both creation and modification scenarios,
- including dialog class structure, message map registration, and ARCHITECTURE.md updates.
+ including dialog class structure, registration in APIExampleDlg, localization wiring, and
+ ARCHITECTURE.md updates.
compatibility: [Cursor, Kiro, Windsurf, Claude, Copilot]
license: MIT
metadata:
@@ -30,9 +31,10 @@ Use this skill when you need to:
1. Determine if the example belongs in `Basic/` or `Advanced/`
2. Create the example folder with PascalCase name
3. Create `.h` and `.cpp` files for the dialog class
-4. Register the example in `CSceneDialog`
-5. Update `ARCHITECTURE.md` Case Index
-6. Verify compilation and functionality
+4. Register the example in `APIExampleDlg.h` and `APIExampleDlg.cpp`
+5. Add scene label wiring in `Language.h`, `stdafx.cpp`, and language `.ini` files
+6. Update `ARCHITECTURE.md` Case Index
+7. Verify compilation and functionality
### Scenario 2: Modify an Existing Example
@@ -41,8 +43,9 @@ Use this skill when you need to:
**Steps:**
1. Locate the example in `APIExample/APIExample/[Basic|Advanced]//`
2. Modify the `.h` and `.cpp` files
-3. Update `ARCHITECTURE.md` Case Index if APIs changed
-4. Verify compilation and functionality
+3. Update `APIExampleDlg.cpp` if routing or lifecycle hooks changed
+4. Update `ARCHITECTURE.md` Case Index if APIs changed
+5. Verify compilation and functionality
---
@@ -54,7 +57,14 @@ Use this skill when you need to:
|------|--------|-------|
| `APIExample/APIExample/[Basic\|Advanced]//CDlg.h` | Create | Dialog class header |
| `APIExample/APIExample/[Basic\|Advanced]//CDlg.cpp` | Create | Dialog class implementation |
-| `APIExample/APIExample/CSceneDialog.cpp` | Modify | Register example in scene list |
+| `APIExample/APIExample/APIExampleDlg.h` | Modify | Add include and dialog member pointer |
+| `APIExample/APIExample/APIExampleDlg.cpp` | Modify | Register, create, show, and release the dialog |
+| `APIExample/APIExample/Language.h` | Modify | Declare the localized scene label |
+| `APIExample/APIExample/stdafx.cpp` | Modify | Initialize the localized scene label in `InitKeyInfomation()` |
+| `APIExample/APIExample/en.ini` | Modify | Add English display text |
+| `APIExample/APIExample/zh-cn.ini` | Modify | Add Chinese display text |
+| `APIExample/APIExample/APIExample.vcxproj` | Modify if needed | Add new source/header files when not using the Visual Studio UI |
+| `APIExample/APIExample/APIExample.vcxproj.filters` | Modify if needed | Keep Solution Explorer grouping correct |
| `ARCHITECTURE.md` | Modify | Add entry to Case Index |
### Modify Existing Example
@@ -63,6 +73,7 @@ Use this skill when you need to:
|------|--------|-------|
| `APIExample/APIExample/[Basic\|Advanced]//CDlg.h` | Modify | Update dialog class |
| `APIExample/APIExample/[Basic\|Advanced]//CDlg.cpp` | Modify | Update implementation |
+| `APIExample/APIExample/APIExampleDlg.cpp` | Modify if routing changes | Update show/hide or scene selection behavior if needed |
| `ARCHITECTURE.md` | Modify | Update Case Index if APIs changed |
---
@@ -94,19 +105,31 @@ Create `APIExample/APIExample/[Basic|Advanced]//CDlg.c
Use the template from `references/example-template.cpp` as a starting point. Replace `` with your example name.
-### Step 5: Register in CSceneDialog
+### Step 5: Register in APIExampleDlg
-Edit `APIExample/APIExample/CSceneDialog.cpp` and add the example to the scene list:
+Do not edit `CSceneDialog.cpp` for case registration. In this project, scene ownership lives in the main dialog:
-```cpp
-// In the scene list initialization
-m_sceneList.push_back({
- _T("ExampleName"),
- [](CWnd* pParent) { return new CDlg(pParent); }
-});
-```
+- `APIExample/APIExample/APIExampleDlg.h`
+ Add the example header include and a member pointer such as `CDlg* m_pDlg = nullptr;`
+- `APIExample/APIExample/APIExampleDlg.cpp`
+ Mirror an existing example across:
+ - `InitSceneDialog()` to push the localized label into `m_vecBasic` or `m_vecAdvanced`, create the dialog, and position it
+ - `CreateScene()` to call the dialog's init/show path when the tree item is selected
+ - `ReleaseScene()` to call the dialog's cleanup/hide path when the tree item is left
+
+`InitSceneList()` reads from `m_vecBasic` and `m_vecAdvanced`, so the tree updates automatically once the vectors are populated in `InitSceneDialog()`.
+
+### Step 6: Add localized scene labels
+
+Register the example name used by the tree view:
+
+- Add an `extern wchar_t ...[INFO_LEN];` declaration to `APIExample/APIExample/Language.h`
+- Initialize it in `APIExample/APIExample/stdafx.cpp` inside `InitKeyInfomation()`
+- Add matching keys to `APIExample/APIExample/en.ini` and `APIExample/APIExample/zh-cn.ini`
+
+Follow the existing naming pattern such as `Basic.JoinChannelVideoByToken` or `Advanced.ScreenCap`.
-### Step 6: Update ARCHITECTURE.md
+### Step 7: Update ARCHITECTURE.md
Add a new row to the Case Index table in `ARCHITECTURE.md`:
@@ -116,13 +139,14 @@ Add a new row to the Case Index table in `ARCHITECTURE.md`:
**Key APIs column:** List 2-5 core SDK methods used in this example.
-### Step 7: Verify
+### Step 8: Verify
- [ ] Code compiles without errors
- [ ] Example appears in the scene list
- [ ] Example can join channel and receive callbacks
- [ ] `leaveChannel()` and `release()` are called on close
- [ ] UI updates happen on main thread (via message map)
+- [ ] Localized labels resolve correctly in both `en.ini` and `zh-cn.ini`
- [ ] ARCHITECTURE.md Case Index is updated
---
@@ -148,6 +172,7 @@ See `references/` directory for code patterns:
- Hardcode App ID or token — use `CConfig`
- Forget to implement `IRtcEngineEventHandler` for event handling
- Leave the channel without calling `leaveChannel()` first
+- Register a new case in `CSceneDialog.cpp` — registration lives in `APIExampleDlg.h` and `APIExampleDlg.cpp`
- Modify examples outside the `APIExample/APIExample/[Basic|Advanced]/` structure
- Forget to update `ARCHITECTURE.md` Case Index after adding/modifying an example
- Use modern C++ patterns (lambdas, smart pointers) unless already in the file
@@ -164,11 +189,14 @@ After completing the upsert, verify:
- [ ] Dialog class inherits from `CDialogEx` or `CDialog`
- [ ] Event handler implements `IRtcEngineEventHandler`
- [ ] Message map properly defined with `BEGIN_MESSAGE_MAP` / `END_MESSAGE_MAP`
-- [ ] Example is registered in `CSceneDialog`
+- [ ] Example is registered in `APIExampleDlg.h` and `APIExampleDlg.cpp`
+- [ ] Scene label is declared in `Language.h` and initialized in `stdafx.cpp`
+- [ ] Scene label has entries in both `en.ini` and `zh-cn.ini`
- [ ] `InitializeAgoraEngine()` creates engine with correct config
- [ ] `JoinChannel()` uses token from `CConfig`
- [ ] `LeaveChannel()` and `release()` are called in `PostNcDestroy()`
- [ ] All engine events posted to main thread via `PostMessage()`
+- [ ] `APIExample.vcxproj` and `.filters` include the new files when they were added outside the IDE
- [ ] `ARCHITECTURE.md` Case Index includes new/updated example
- [ ] Code compiles without warnings or errors
- [ ] Example appears in the scene list
diff --git a/windows/AGENTS.md b/windows/AGENTS.md
index b8939152d..5e3601839 100644
--- a/windows/AGENTS.md
+++ b/windows/AGENTS.md
@@ -49,7 +49,7 @@ All work must conform to the rules defined in `ARCHITECTURE.md`:
- Each example implements `IAgoraRtcEngineEventHandler` interface
- Each example manages its own Agora engine lifecycle
- Message handlers are defined via `BEGIN_MESSAGE_MAP` / `END_MESSAGE_MAP`
-- All examples are registered in `CSceneDialog`
+- All examples are registered in `APIExampleDlg.h` and `APIExampleDlg.cpp`
- Configuration is managed centrally via `CConfig` class
### Follow the Existing Language and Framework
@@ -60,15 +60,6 @@ All work must conform to the rules defined in `ARCHITECTURE.md`:
- Use message map pattern for event handling — do not introduce modern C++ patterns unless they already exist in the file being modified
- Match the code style, naming, and patterns of existing examples
-### Use Example-Level SKILLs
-
-Each example may contain a `SKILL.md` file in its folder. When working on or referencing a specific example:
-1. Check whether a `SKILL.md` exists in that example's directory
-2. If it exists, read it before making changes — it describes the API usage, call flow, and known constraints
-3. If it does not exist, one will be created in the future; proceed using the source code as the reference
-
-**SKILL.md location pattern:** `APIExample/APIExample/[Basic|Advanced]//SKILL.md`
-
### Use Project-Level SKILLs
For broader tasks, use the skills in `.agent/skills/`:
diff --git a/windows/ARCHITECTURE.md b/windows/ARCHITECTURE.md
index 1655a93c9..e92efe599 100644
--- a/windows/ARCHITECTURE.md
+++ b/windows/ARCHITECTURE.md
@@ -30,11 +30,11 @@ windows/
│ │ ├── res/ # Resources (icons, dialogs, strings)
│ │ ├── APIExample.cpp
│ │ ├── APIExample.h
-│ │ ├── APIExampleDlg.cpp # Main dialog
+│ │ ├── APIExampleDlg.cpp # Main dialog and example registration
│ │ ├── APIExampleDlg.h
│ │ ├── CConfig.cpp # Configuration management
│ │ ├── CConfig.h
-│ │ ├── CSceneDialog.cpp # Scene selection dialog
+│ │ ├── CSceneDialog.cpp # Shared dialog helper, not the case registration source
│ │ └── CSceneDialog.h
│ ├── APIExample.sln # Visual Studio solution
│ ├── cicd/ # CI/CD scripts
@@ -73,7 +73,7 @@ Each example is a dialog class that:
### Menu Registration
-All examples are registered in `CSceneDialog` via a scene list. The example name must match the folder name.
+All examples are registered in `APIExampleDlg.h` and `APIExampleDlg.cpp`. The localized scene name is wired through `Language.h`, `stdafx.cpp`, `en.ini`, and `zh-cn.ini`, and the example name should still match the folder name.
### Configuration Management