diff --git a/.github/ci/build/build_ios_ipa.sh b/.github/ci/build/build_ios_ipa.sh index cc71b098b..322d7660e 100755 --- a/.github/ci/build/build_ios_ipa.sh +++ b/.github/ci/build/build_ios_ipa.sh @@ -17,8 +17,10 @@ TARGET_NAME=${PROJECT_PATH##*/} KEYCENTER_PATH=${PROJECT_PATH}"/"${TARGET_NAME}"/Common/KeyCenter.swift" +METHOD_PATH=${PROJECT_PATH}"/ExportOptions.plist" + # 打包环境 -CONFIGURATION=Development +CONFIGURATION=$method #工程文件路径 APP_PATH="${PROJECT_PATH}/${TARGET_NAME}.xcworkspace" @@ -57,6 +59,16 @@ echo PBXPROJ_PATH: $PBXPROJ_PATH /usr/libexec/PlistBuddy -c "Set :objects:8B10BE1826AFFFA6002E1373:buildSettings:DEVELOPMENT_TEAM ''" $PBXPROJ_PATH /usr/libexec/PlistBuddy -c "Set :objects:8B10BE1826AFFFA6002E1373:buildSettings:PROVISIONING_PROFILE_SPECIFIER ''" $PBXPROJ_PATH +#修改build number +# Debug +/usr/libexec/PlistBuddy -c "Set :objects:03D13BF72448758C00B599B3:buildSettings:CURRENT_PROJECT_VERSION ${BUILD_NUMBER}" $PBXPROJ_PATH +# Release +/usr/libexec/PlistBuddy -c "Set :objects:03D13BF82448758C00B599B3:buildSettings:CURRENT_PROJECT_VERSION ${BUILD_NUMBER}" $PBXPROJ_PATH + +#修改打包方式 +/usr/libexec/PlistBuddy -c "Set :method $CONFIGURATION" $METHOD_PATH + + # 读取APPID环境变量 echo AGORA_APP_ID:$APP_ID echo $AGORA_APP_ID diff --git a/.github/ci/build/build_mac_ipa.sh b/.github/ci/build/build_mac_ipa.sh index c867f6705..3eb0c737e 100755 --- a/.github/ci/build/build_mac_ipa.sh +++ b/.github/ci/build/build_mac_ipa.sh @@ -38,15 +38,11 @@ echo PBXPROJ_PATH: $PBXPROJ_PATH /usr/libexec/PlistBuddy -c "Set :objects:03896D5424F8A011008593CD:buildSettings:DEVELOPMENT_TEAM 'YS397FG5PA'" $PBXPROJ_PATH /usr/libexec/PlistBuddy -c "Set :objects:03896D5424F8A011008593CD:buildSettings:PROVISIONING_PROFILE_SPECIFIER 'apiexamplemac'" $PBXPROJ_PATH -# SimpleFilter +#修改build number # Debug -# /usr/libexec/PlistBuddy -c "Set :objects:8BD4AE7E272518D600E95B87:buildSettings:CODE_SIGN_STYLE 'Manual'" $PBXPROJ_PATH -# /usr/libexec/PlistBuddy -c "Set :objects:8BD4AE7E272518D600E95B87:buildSettings:DEVELOPMENT_TEAM ''" $PBXPROJ_PATH -# /usr/libexec/PlistBuddy -c "Set :objects:8BD4AE7E272518D600E95B87:buildSettings:PROVISIONING_PROFILE_SPECIFIER ''" $PBXPROJ_PATH -# # Release -# /usr/libexec/PlistBuddy -c "Set :objects:8BD4AE7F272518D600E95B87:buildSettings:CODE_SIGN_STYLE 'Manual'" $PBXPROJ_PATH -# /usr/libexec/PlistBuddy -c "Set :objects:8BD4AE7F272518D600E95B87:buildSettings:DEVELOPMENT_TEAM ''" $PBXPROJ_PATH -# /usr/libexec/PlistBuddy -c "Set :objects:8BD4AE7F272518D600E95B87:buildSettings:PROVISIONING_PROFILE_SPECIFIER ''" $PBXPROJ_PATH +/usr/libexec/PlistBuddy -c "Set :objects:03896D5324F8A011008593CD:buildSettings:CURRENT_PROJECT_VERSION ${BUILD_NUMBER}" $PBXPROJ_PATH +# Release +/usr/libexec/PlistBuddy -c "Set :objects:03896D5424F8A011008593CD:buildSettings:CURRENT_PROJECT_VERSION ${BUILD_NUMBER}" $PBXPROJ_PATH # 读取APPID环境变量 echo AGORA_APP_ID:$APP_ID diff --git a/.github/ci/build/build_windows.bat b/.github/ci/build/build_windows.bat index bc533247b..7bdbb98f3 100644 --- a/.github/ci/build/build_windows.bat +++ b/.github/ci/build/build_windows.bat @@ -84,6 +84,8 @@ del /F /Q windows\APIExample\ci.py xcopy /Y /E windows\APIExample Agora_Native_SDK_for_Windows_FULL\samples\API-example xcopy /Y /E windows\README.md Agora_Native_SDK_for_Windows_FULL\samples\API-example xcopy /Y /E windows\README.zh.md Agora_Native_SDK_for_Windows_FULL\samples\API-example +rmdir /S /Q Agora_Native_SDK_for_Windows_FULL\samples\API-example\APIExample\APIExample +dir Agora_Native_SDK_for_Windows_FULL\samples\API-example\APIExample 7z a -tzip result.zip -r Agora_Native_SDK_for_Windows_FULL copy result.zip %WORKSPACE%\\withAPIExample_%date:~4,2%%date:~7,2%%time:~0,2%%time:~3,2%_%zip_name% del /F result.zip diff --git a/Android/APIExample-Audio/app/build.gradle b/Android/APIExample-Audio/app/build.gradle index 6a7b4c48e..334dd33a0 100644 --- a/Android/APIExample-Audio/app/build.gradle +++ b/Android/APIExample-Audio/app/build.gradle @@ -48,7 +48,7 @@ dependencies { implementation fileTree(dir: "${localSdkPath}", include: ['*.jar', '*.aar']) } else{ - def agora_sdk_version = "4.1.0-1" + def agora_sdk_version = "4.1.1" // case 1: full single lib with voice only implementation "io.agora.rtc:voice-sdk:${agora_sdk_version}" // case 2: partial libs with voice only 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 466057e11..e7788b41c 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 @@ -55,6 +55,7 @@ public class RhythmPlayer extends BaseFragment implements View.OnClickListener, private boolean isPlaying = false; private SeekBar beatPerMinute, beatPerMeasure; private AgoraRhythmPlayerConfig agoraRhythmPlayerConfig = new AgoraRhythmPlayerConfig(); + private ChannelMediaOptions mChannelMediaOptions; @Override public void onCreate(@Nullable Bundle savedInstanceState) @@ -214,6 +215,10 @@ public void onClick(View v) else if(v.getId() == R.id.play){ if(!isPlaying){ int ret = engine.startRhythmPlayer(URL_DOWNBEAT, URL_UPBEAT, agoraRhythmPlayerConfig); + if(joined){ + mChannelMediaOptions.publishRhythmPlayerTrack = true; + engine.updateChannelMediaOptions(mChannelMediaOptions); + } Log.i(TAG, "startRhythmPlayer result:" + ret); isPlaying = true; beatPerMeasure.setEnabled(false); @@ -222,6 +227,10 @@ else if(v.getId() == R.id.play){ } else if(v.getId() == R.id.stop){ engine.stopRhythmPlayer(); + if(joined){ + mChannelMediaOptions.publishRhythmPlayerTrack = false; + engine.updateChannelMediaOptions(mChannelMediaOptions); + } isPlaying = false; beatPerMeasure.setEnabled(true); beatPerMinute.setEnabled(true); @@ -246,15 +255,15 @@ private void joinChannel(String channelId) /** 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; - option.publishMicrophoneTrack = true; + mChannelMediaOptions = new ChannelMediaOptions(); + mChannelMediaOptions.autoSubscribeAudio = true; + mChannelMediaOptions.autoSubscribeVideo = true; + mChannelMediaOptions.publishMicrophoneTrack = true; /** * config this for whether need push rhythem player to remote */ - option.publishRhythmPlayerTrack = true; - int res = engine.joinChannel(accessToken, channelId, 0, option); + mChannelMediaOptions.publishRhythmPlayerTrack = isPlaying; + int res = engine.joinChannel(accessToken, channelId, 0, mChannelMediaOptions); if (res != 0) { // Usually happens with invalid parameters // Error code description can be found at: 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 fc3603462..b4abaa337 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 @@ -282,6 +282,8 @@ public void onClick(View v) { join.setText(getString(R.string.join)); mic.setEnabled(false); pcm.setEnabled(false); + pcm.setChecked(false); + mic.setChecked(true); if(pushingTask != null){ try { pushingTask.join(); diff --git a/Android/APIExample-Audio/app/src/main/res/layout/fragment_custom_audio_source.xml b/Android/APIExample-Audio/app/src/main/res/layout/fragment_custom_audio_source.xml index d337497be..044f6a45c 100644 --- a/Android/APIExample-Audio/app/src/main/res/layout/fragment_custom_audio_source.xml +++ b/Android/APIExample-Audio/app/src/main/res/layout/fragment_custom_audio_source.xml @@ -111,6 +111,7 @@ android:layout_alignParentEnd="true" android:layout_alignParentBottom="true" android:checked="true" + android:enabled="false" android:layout_marginEnd="16dp" android:layout_marginBottom="64dp"/> @@ -121,6 +122,7 @@ android:layout_alignParentEnd="true" android:layout_alignParentBottom="true" android:layout_marginEnd="16dp" + android:enabled="false" android:layout_marginBottom="110dp" android:text="@string/publish_local_audio" /> diff --git a/Android/APIExample/app/build.gradle b/Android/APIExample/app/build.gradle index 694c1b83c..a340a149a 100644 --- a/Android/APIExample/app/build.gradle +++ b/Android/APIExample/app/build.gradle @@ -60,7 +60,7 @@ dependencies { implementation fileTree(dir: "${localSdkPath}", include: ['*.jar', '*.aar']) } else{ - def agora_sdk_version = "4.1.0-1" + def agora_sdk_version = "4.1.1" // case 1: full libs implementation "io.agora.rtc:full-sdk:${agora_sdk_version}" implementation "io.agora.rtc:full-screen-sharing:${agora_sdk_version}" diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/common/model/GlobalSettings.java b/Android/APIExample/app/src/main/java/io/agora/api/example/common/model/GlobalSettings.java index cb534072e..ff2453e02 100644 --- a/Android/APIExample/app/src/main/java/io/agora/api/example/common/model/GlobalSettings.java +++ b/Android/APIExample/app/src/main/java/io/agora/api/example/common/model/GlobalSettings.java @@ -37,7 +37,7 @@ public class GlobalSettings { public String getVideoEncodingDimension() { if (videoEncodingDimension == null) - return "VD_640x360"; + return "VD_960x540"; else return videoEncodingDimension; } 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 d42439208..6a82f9263 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 @@ -148,7 +148,6 @@ public void onDestroy() if(engine != null) { engine.leaveChannel(); - engine.stopPreview(); } handler.post(RtcEngine::destroy); engine = null; @@ -211,7 +210,6 @@ public void onClick(View v) * 2:If you call the leaveChannel method during CDN live streaming, the SDK * triggers the removeInjectStreamUrl method.*/ engine.leaveChannel(); - engine.stopPreview(); join.setText(getString(R.string.join)); et_password.setEnabled(true); encry_mode.setEnabled(true); @@ -250,7 +248,6 @@ private void joinChannel(String channelId) engine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER); // Enable video module engine.enableVideo(); - engine.startPreview(); // Setup video encoding configs engine.setVideoEncoderConfiguration(new VideoEncoderConfiguration( VD_640x360, 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 e826aacca..8dabb51ec 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 @@ -74,7 +74,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat fl_local = view.findViewById(R.id.fl_local); contentInspectRetTv = view.findViewById(R.id.ret_content_inspect); view.findViewById(R.id.btn_switch_camera).setOnClickListener(v -> { - if(engine != null){ + if(engine != null && joined){ engine.switchCamera(); } }); @@ -140,7 +140,6 @@ public void onDestroy() { /**leaveChannel and Destroy the RtcEngine instance*/ if (engine != null) { engine.leaveChannel(); - engine.stopPreview(); } handler.post(RtcEngine::destroy); engine = null; @@ -234,7 +233,6 @@ private void joinChannel(String channelId) { contentInspectConfig.moduleCount = 1; engine.enableContentInspect(true, contentInspectConfig); - 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 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 756ec0e1c..9d2ab9201 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 @@ -152,7 +152,6 @@ public void onDestroy() if(engine != null) { engine.leaveChannel(); - engine.stopPreview(); engine.stopChannelMediaRelay(); mediaRelaying = false; } @@ -277,7 +276,6 @@ private void joinChannel(String channelId) engine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER); // Enable video module engine.enableVideo(); - engine.startPreview(); // Setup video encoding configs engine.setVideoEncoderConfiguration(new VideoEncoderConfiguration( ((MainApplication)getActivity().getApplication()).getGlobalSettings().getVideoEncodingDimensionObject(), 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 184d527ed..f3b26e76d 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 @@ -266,6 +266,7 @@ private void joinChannel(String channelId) { option.autoSubscribeVideo = true; int res = engine.joinChannel(ret, channelId, 0, option); if (res != 0) { + engine.stopPreview(); // 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 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 bf82f827e..98647fc1e 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 @@ -13,18 +13,17 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; -import android.widget.Button; -import android.widget.EditText; import android.widget.FrameLayout; -import android.widget.Spinner; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.widget.SwitchCompat; +import androidx.appcompat.app.AlertDialog; +import com.google.android.material.bottomsheet.BottomSheetDialog; import com.yanzhenjie.permission.AndPermission; import com.yanzhenjie.permission.runtime.Permission; +import java.io.File; import java.util.Locale; import io.agora.api.example.MainApplication; @@ -33,6 +32,9 @@ import io.agora.api.example.common.BaseFragment; import io.agora.api.example.common.Constant; import io.agora.api.example.common.widget.VideoReportLayout; +import io.agora.api.example.databinding.FragmentLiveStreamingBinding; +import io.agora.api.example.databinding.FragmentLiveStreamingSettingBinding; +import io.agora.api.example.databinding.FragmentLiveStreamingVideoTrackingBinding; import io.agora.api.example.utils.CommonUtil; import io.agora.api.example.utils.TokenUtils; import io.agora.rtc2.ChannelMediaOptions; @@ -62,93 +64,65 @@ public class LiveStreaming extends BaseFragment implements View.OnClickListener { private static final String TAG = LiveStreaming.class.getSimpleName(); + private FragmentLiveStreamingBinding mRootBinding; + private FragmentLiveStreamingSettingBinding mSettingBinding; + private BottomSheetDialog mSettingDialog; + private VideoReportLayout foreGroundVideo, backGroundVideo; - private Button join, publish, latency; - private EditText et_channel; + private boolean isLocalVideoForeground; + private RtcEngine engine; private int myUid; private int remoteUid; private boolean joined = false; private boolean isHost = false; - private boolean isLowLatency = false; - private boolean isLocalVideoForeground = true; - private SwitchCompat watermarkSwitch; - private SwitchCompat lowStreamSwitch; - private Spinner spEncoderType; - private SwitchCompat bFrame; - private final VideoEncoderConfiguration videoEncoderConfiguration = new VideoEncoderConfiguration(); @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_live_streaming, container, false); + mRootBinding = FragmentLiveStreamingBinding.inflate(inflater, container, false); + return mRootBinding.getRoot(); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - join = view.findViewById(R.id.btn_join); - publish = view.findViewById(R.id.btn_publish); - latency = view.findViewById(R.id.btn_latency); - et_channel = view.findViewById(R.id.et_channel); - spEncoderType = view.findViewById(R.id.sp_encoder_type); - bFrame = view.findViewById(R.id.switch_b_frame); - latency.setEnabled(false); - publish.setEnabled(false); - view.findViewById(R.id.btn_join).setOnClickListener(this); - view.findViewById(R.id.btn_publish).setOnClickListener(this); - view.findViewById(R.id.btn_latency).setOnClickListener(this); - view.findViewById(R.id.foreground_video).setOnClickListener(this); - foreGroundVideo = view.findViewById(R.id.background_video); - backGroundVideo = view.findViewById(R.id.foreground_video); - view.findViewById(R.id.btn_take_shot).setOnClickListener(this); - watermarkSwitch = view.findViewById(R.id.switch_watermark); - watermarkSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + foreGroundVideo = mRootBinding.foregroundLayout.foregroundVideo; + backGroundVideo = mRootBinding.backgroundVideo; + mRootBinding.btnSetting.setOnClickListener(this); + mRootBinding.btnJoin.setOnClickListener(this); + mRootBinding.btnPublish.setOnClickListener(this); + mRootBinding.btnRemoteScreenshot.setOnClickListener(this); + foreGroundVideo.setOnClickListener(this); + + mSettingBinding = FragmentLiveStreamingSettingBinding.inflate(LayoutInflater.from(getContext())); + mSettingBinding.switchWatermark.setOnCheckedChangeListener((buttonView, isChecked) -> enableWatermark(isChecked)); + mSettingBinding.switchBFrame.setOnCheckedChangeListener((buttonView, isChecked) -> enableBFrame(isChecked)); + mSettingBinding.switchLowLatency.setOnCheckedChangeListener((buttonView, isChecked) -> enableLowLegacy(isChecked)); + mSettingBinding.switchLowStream.setOnCheckedChangeListener((buttonView, isChecked) -> enableLowStream(isChecked)); + mSettingBinding.switchFirstFrame.setOnCheckedChangeListener((buttonView, isChecked) -> { if (isChecked) { - engine.enableVideo(); - WatermarkOptions watermarkOptions = new WatermarkOptions(); - int size = ((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingDimensionObject().width / 6; - int height = ((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingDimensionObject().height; - watermarkOptions.positionInPortraitMode = new WatermarkOptions.Rectangle(10, height / 2, size, size); - watermarkOptions.positionInLandscapeMode = new WatermarkOptions.Rectangle(10, height / 2, size, size); - watermarkOptions.visibleInPreview = true; - int ret = engine.addVideoWatermark(Constant.WATER_MARK_FILE_PATH, watermarkOptions); - if (ret != Constants.ERR_OK) { - Log.e(TAG, "addVideoWatermark error=" + ret + ", msg=" + RtcEngine.getErrorDescription(ret)); - } - } else { - engine.clearVideoWatermarks(); + new AlertDialog.Builder(requireContext()) + .setTitle(R.string.tip) + .setMessage(R.string.first_frame_optimization_tip) + .setNegativeButton(R.string.cancel, (dialog, which) -> { + buttonView.setChecked(false); + dialog.dismiss(); + }) + .setPositiveButton(R.string.confirm, (dialog, which) -> { + // Enable FirstFrame Optimization + engine.enableInstantMediaRendering(); + buttonView.setEnabled(false); + dialog.dismiss(); + }) + .show(); } }); - lowStreamSwitch = view.findViewById(R.id.switch_low_stream); - lowStreamSwitch.setEnabled(false); - lowStreamSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - if(remoteUid != 0){ - engine.setRemoteVideoStreamType(remoteUid, isChecked ? Constants.VIDEO_STREAM_LOW: Constants.VIDEO_STREAM_HIGH); - } - }); - - bFrame.setOnCheckedChangeListener((buttonView, isChecked) -> { - videoEncoderConfiguration.advanceOptions.compressionPreference = isChecked ? - VideoEncoderConfiguration.COMPRESSION_PREFERENCE.PREFER_QUALITY : - VideoEncoderConfiguration.COMPRESSION_PREFERENCE.PREFER_LOW_LATENCY; - engine.setVideoEncoderConfiguration(videoEncoderConfiguration); - }); - - spEncoderType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + mSettingBinding.spEncoderType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - VideoEncoderConfiguration.AdvanceOptions advanceOptions = new VideoEncoderConfiguration.AdvanceOptions(); - if(position == 1){ - advanceOptions.encodingPreference = VideoEncoderConfiguration.ENCODING_PREFERENCE.PREFER_HARDWARE; - }else if(position == 2){ - advanceOptions.encodingPreference = VideoEncoderConfiguration.ENCODING_PREFERENCE.PREFER_SOFTWARE; - }else{ - advanceOptions.encodingPreference = VideoEncoderConfiguration.ENCODING_PREFERENCE.PREFER_AUTO; - } - videoEncoderConfiguration.advanceOptions = advanceOptions; - engine.setVideoEncoderConfiguration(videoEncoderConfiguration); + setEncodingPreference(position); } @Override @@ -156,6 +130,8 @@ public void onNothingSelected(AdapterView parent) { } }); + mSettingDialog = new BottomSheetDialog(requireContext()); + mSettingDialog.setContentView(mSettingBinding.getRoot()); } @Override @@ -168,7 +144,8 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) { } try { - /**Creates an RtcEngine instance. + /* + * Creates an RtcEngine instance. * @param context The context of Android Activity * @param appId The App ID issued to you by Agora. See * How to get the App ID @@ -178,13 +155,12 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) { rtcEngineConfig.mAppId = getString(R.string.agora_app_id); rtcEngineConfig.mContext = context.getApplicationContext(); rtcEngineConfig.mEventHandler = iRtcEngineEventHandler; - /** Sets the channel profile of the Agora RtcEngine. - */ + /* Sets the channel profile of the Agora RtcEngine. */ rtcEngineConfig.mChannelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING; rtcEngineConfig.mAudioScenario = Constants.AudioScenario.getValue(Constants.AudioScenario.DEFAULT); rtcEngineConfig.mAreaCode = ((MainApplication)getActivity().getApplication()).getGlobalSettings().getAreaCode(); engine = RtcEngine.create(rtcEngineConfig); - /** + /* * This parameter is for reporting the usages of APIExample to agora background. * Generally, it is not necessary for you to set this parameter. */ @@ -222,9 +198,9 @@ public void onDestroy() { public void onClick(View v) { if (v.getId() == R.id.btn_join) { if (!joined) { - CommonUtil.hideInputBoard(requireActivity(), et_channel); + CommonUtil.hideInputBoard(requireActivity(), mRootBinding.etChannel); // call when join button hit - String channelId = et_channel.getText().toString(); + String channelId = mRootBinding.etChannel.getText().toString(); // Check permission if (AndPermission.hasPermissions(this, Permission.Group.STORAGE, Permission.Group.MICROPHONE, Permission.Group.CAMERA)) { joinChannel(channelId); @@ -242,6 +218,15 @@ public void onClick(View v) { }).start(); } else { joined = false; + isHost = false; + mRootBinding.btnJoin.setText(getString(R.string.join)); + mRootBinding.btnPublish.setEnabled(false); + mRootBinding.btnPublish.setText(getString(R.string.enable_publish)); + mRootBinding.videoTrackingLayout.getRoot().setVisibility(View.GONE); + remoteUid = 0; + foreGroundVideo.removeAllViews(); + backGroundVideo.removeAllViews(); + /**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 @@ -261,36 +246,22 @@ public void onClick(View v) { * triggers the removeInjectStreamUrl method.*/ engine.stopPreview(); engine.leaveChannel(); - join.setText(getString(R.string.join)); - watermarkSwitch.setEnabled(false); - publish.setEnabled(false); - latency.setEnabled(false); - lowStreamSwitch.setChecked(false); - lowStreamSwitch.setEnabled(false); - remoteUid = 0; + } - } else if (v.getId() == R.id.btn_publish) { + } + else if (v.getId() == R.id.btn_publish) { isHost = !isHost; - if(isHost){ + if (isHost) { engine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER); - latency.setEnabled(false); - } - else{ + } else { ClientRoleOptions clientRoleOptions = new ClientRoleOptions(); - clientRoleOptions.audienceLatencyLevel = isLowLatency ? Constants.AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY : Constants.AUDIENCE_LATENCY_LEVEL_LOW_LATENCY; + clientRoleOptions.audienceLatencyLevel = mSettingBinding.switchLowLatency.isChecked() ? Constants.AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY : Constants.AUDIENCE_LATENCY_LEVEL_LOW_LATENCY; engine.setClientRole(CLIENT_ROLE_AUDIENCE, clientRoleOptions); - latency.setEnabled(true); } - publish.setEnabled(false); - publish.setText(isHost ? getString(R.string.disnable_publish) : getString(R.string.enable_publish)); - - } else if (v.getId() == R.id.btn_latency) { - isLowLatency = !isLowLatency; - latency.setText(isLowLatency ? getString(R.string.disable_low_latency) : getString(R.string.enable_low_latency)); - ClientRoleOptions clientRoleOptions = new ClientRoleOptions(); - clientRoleOptions.audienceLatencyLevel = isLowLatency ? Constants.AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY : Constants.AUDIENCE_LATENCY_LEVEL_LOW_LATENCY; - engine.setClientRole(CLIENT_ROLE_AUDIENCE, clientRoleOptions); - } else if (v.getId() == R.id.foreground_video) { + mRootBinding.btnPublish.setEnabled(false); + mRootBinding.btnPublish.setText(isHost ? getString(R.string.disnable_publish) : getString(R.string.enable_publish)); + } + else if (v.getId() == R.id.foreground_video) { isLocalVideoForeground = !isLocalVideoForeground; int foreGroundReportId = foreGroundVideo.getReportUid(); foreGroundVideo.setReportUid(backGroundVideo.getReportUid()); @@ -303,8 +274,8 @@ public void onClick(View v) { backGroundVideo.removeAllViews(); } // Create render view by RtcEngine - SurfaceView localView = RtcEngine.CreateRendererView(getContext()); - SurfaceView remoteView = RtcEngine.CreateRendererView(getContext()); + SurfaceView localView = new SurfaceView(getContext()); + SurfaceView remoteView = new SurfaceView(getContext()); if (isLocalVideoForeground){ // Add to the local container foreGroundVideo.addView(localView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); @@ -314,8 +285,8 @@ public void onClick(View v) { engine.setupRemoteVideo(new VideoCanvas(remoteView, RENDER_MODE_HIDDEN, remoteUid)); // Setup local video to render your local camera preview engine.setupLocalVideo(new VideoCanvas(localView, RENDER_MODE_HIDDEN, 0)); - remoteView.setZOrderMediaOverlay(true); - remoteView.setZOrderOnTop(true); + localView.setZOrderMediaOverlay(true); + localView.setZOrderOnTop(true); } else{ // Add to the local container @@ -326,20 +297,16 @@ public void onClick(View v) { engine.setupLocalVideo(new VideoCanvas(localView, RENDER_MODE_HIDDEN, 0)); // Setup remote video to render engine.setupRemoteVideo(new VideoCanvas(remoteView, RENDER_MODE_HIDDEN, remoteUid)); - localView.setZOrderMediaOverlay(true); - localView.setZOrderOnTop(true); - } - } else if (v.getId() == R.id.btn_take_shot) { - if (remoteUid != 0) { - int ret = engine.takeSnapshot(remoteUid, "/sdcard/APIExample_snapshot_" + et_channel.getText().toString() + "_" + remoteUid + ".png"); - if(ret != Constants.ERR_OK){ - showLongToast("takeSnapshot error code=" + ret + ",msg=" + RtcEngine.getErrorDescription(ret)); - } - } else { - showLongToast(getString(R.string.remote_screenshot_tip)); + remoteView.setZOrderMediaOverlay(true); + remoteView.setZOrderOnTop(true); } } - + else if(v.getId() == R.id.btn_setting){ + mSettingDialog.show(); + } + else if(v.getId() == R.id.btn_remote_screenshot){ + takeSnapshot(remoteUid); + } } private void joinChannel(String channelId) { @@ -349,13 +316,14 @@ private void joinChannel(String channelId) { return; } + isLocalVideoForeground = false; // Create render view by RtcEngine SurfaceView surfaceView = new SurfaceView(context); - if (foreGroundVideo.getChildCount() > 0) { - foreGroundVideo.removeAllViews(); + if (backGroundVideo.getChildCount() > 0) { + backGroundVideo.removeAllViews(); } // Add to the local container - foreGroundVideo.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + backGroundVideo.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 @@ -364,7 +332,7 @@ private void joinChannel(String channelId) { // Set audio route to microPhone engine.setDefaultAudioRoutetoSpeakerphone(true); - /**In the demo, the default is to enter as the anchor.*/ + /*In the demo, the default is to enter as the anchor.*/ engine.setClientRole(IRtcEngineEventHandler.ClientRole.CLIENT_ROLE_AUDIENCE); // Enable video module engine.enableVideo(); @@ -376,13 +344,16 @@ private void joinChannel(String channelId) { VideoEncoderConfiguration.ORIENTATION_MODE.valueOf(((MainApplication)getActivity().getApplication()).getGlobalSettings().getVideoEncodingOrientation()) )); - /**Please configure accessToken in the string_config file. + engine.startMediaRenderingTracing(); + + /* + * 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. + /* 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(); @@ -390,6 +361,7 @@ private void joinChannel(String channelId) { option.autoSubscribeVideo = true; int res = engine.joinChannel(ret, channelId, 0, option); if (res != 0) { + engine.stopPreview(); // 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 @@ -398,10 +370,76 @@ private void joinChannel(String channelId) { return; } // Prevent repeated entry - join.setEnabled(false); + mRootBinding.btnJoin.setEnabled(false); }); } + private void enableWatermark(boolean enable){ + if (enable) { + WatermarkOptions watermarkOptions = new WatermarkOptions(); + int size = ((MainApplication) requireActivity().getApplication()).getGlobalSettings().getVideoEncodingDimensionObject().width / 6; + int height = ((MainApplication) requireActivity().getApplication()).getGlobalSettings().getVideoEncodingDimensionObject().height; + watermarkOptions.positionInPortraitMode = new WatermarkOptions.Rectangle(10, height / 2, size, size); + watermarkOptions.positionInLandscapeMode = new WatermarkOptions.Rectangle(10, height / 2, size, size); + watermarkOptions.visibleInPreview = true; + int ret = engine.addVideoWatermark(Constant.WATER_MARK_FILE_PATH, watermarkOptions); + if (ret != Constants.ERR_OK) { + Log.e(TAG, "addVideoWatermark error=" + ret + ", msg=" + RtcEngine.getErrorDescription(ret)); + } + } else { + engine.clearVideoWatermarks(); + } + } + + private void enableLowStream(boolean enable) { + engine.setRemoteDefaultVideoStreamType(enable ? Constants.VIDEO_STREAM_LOW : Constants.VIDEO_STREAM_HIGH); + if (remoteUid != 0) { + engine.setRemoteVideoStreamType(remoteUid, enable ? Constants.VIDEO_STREAM_LOW : Constants.VIDEO_STREAM_HIGH); + } + } + + private void setEncodingPreference(int index){ + VideoEncoderConfiguration.ENCODING_PREFERENCE[] preferences = new VideoEncoderConfiguration.ENCODING_PREFERENCE[]{ + VideoEncoderConfiguration.ENCODING_PREFERENCE.PREFER_AUTO, + VideoEncoderConfiguration.ENCODING_PREFERENCE.PREFER_HARDWARE, + VideoEncoderConfiguration.ENCODING_PREFERENCE.PREFER_SOFTWARE, + }; + + VideoEncoderConfiguration.AdvanceOptions advanceOptions = new VideoEncoderConfiguration.AdvanceOptions(); + advanceOptions.encodingPreference = preferences[index]; + videoEncoderConfiguration.advanceOptions = advanceOptions; + engine.setVideoEncoderConfiguration(videoEncoderConfiguration); + } + + private void enableBFrame(boolean enable){ + videoEncoderConfiguration.advanceOptions.compressionPreference = enable ? + VideoEncoderConfiguration.COMPRESSION_PREFERENCE.PREFER_QUALITY : + VideoEncoderConfiguration.COMPRESSION_PREFERENCE.PREFER_LOW_LATENCY; + engine.setVideoEncoderConfiguration(videoEncoderConfiguration); + } + + private void enableLowLegacy(boolean enable) { + if (isHost) { + return; + } + ClientRoleOptions clientRoleOptions = new ClientRoleOptions(); + clientRoleOptions.audienceLatencyLevel = enable ? Constants.AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY : Constants.AUDIENCE_LATENCY_LEVEL_LOW_LATENCY; + engine.setClientRole(CLIENT_ROLE_AUDIENCE, clientRoleOptions); + } + + private void takeSnapshot(int uid) { + if (uid != 0) { + String filePath = requireContext().getExternalCacheDir().getAbsolutePath() + File.separator + "livestreaming_snapshot.png"; + int ret = engine.takeSnapshot(uid, filePath); + if (ret != Constants.ERR_OK) { + showLongToast("takeSnapshot error code=" + ret + ",msg=" + RtcEngine.getErrorDescription(ret)); + } + } else { + showLongToast(getString(R.string.remote_screenshot_tip)); + } + } + + /** * IRtcEngineEventHandler is an abstract class providing default implementation. * The SDK uses this class to report to the app on SDK runtime events. @@ -426,7 +464,6 @@ 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)); - runOnUIThread(() -> lowStreamSwitch.setEnabled(false)); } /**Occurs when the local user joins a specified channel. @@ -444,12 +481,10 @@ public void onJoinChannelSuccess(String channel, int uid, int elapsed) { handler.post(new Runnable() { @Override public void run() { - join.setEnabled(true); - join.setText(getString(R.string.leave)); - publish.setEnabled(true); - latency.setEnabled(true); + mRootBinding.btnJoin.setEnabled(true); + mRootBinding.btnJoin.setText(getString(R.string.leave)); + mRootBinding.btnPublish.setEnabled(true); foreGroundVideo.setReportUid(uid); - lowStreamSwitch.setEnabled(true); } }); } @@ -519,18 +554,19 @@ public void onUserJoined(int uid, int elapsed) { } handler.post(() -> { + VideoReportLayout videoContainer = isLocalVideoForeground ? backGroundVideo: foreGroundVideo; /**Display remote video stream*/ SurfaceView surfaceView = null; - if (backGroundVideo.getChildCount() > 0) { - backGroundVideo.removeAllViews(); + if (videoContainer.getChildCount() > 0) { + videoContainer.removeAllViews(); } // Create render view by RtcEngine surfaceView = new SurfaceView(context); surfaceView.setZOrderMediaOverlay(true); // Add to the remote container - backGroundVideo.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + videoContainer.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - backGroundVideo.setReportUid(remoteUid); + videoContainer.setReportUid(remoteUid); // Setup remote video to render engine.setupRemoteVideo(new VideoCanvas(surfaceView, RENDER_MODE_HIDDEN, remoteUid)); }); @@ -559,8 +595,6 @@ public void run() { 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)); - lowStreamSwitch.setChecked(false); - lowStreamSwitch.setEnabled(false); } }); } @@ -578,8 +612,7 @@ public void onClientRoleChanged(int oldRole, int newRole, ClientRoleOptions newR super.onClientRoleChanged(oldRole, newRole, newRoleOptions); Log.i(TAG, String.format("client role changed from state %d to %d", oldRole, newRole)); runOnUIThread(() -> { - publish.setEnabled(true); - watermarkSwitch.setEnabled(newRole == Constants.CLIENT_ROLE_BROADCASTER); + mRootBinding.btnPublish.setEnabled(true); }); } @@ -633,5 +666,24 @@ public void onRemoteAudioStats(RemoteAudioStats stats) { backGroundVideo.setRemoteAudioStats(stats); } } + + @Override + public void onVideoRenderingTracingResult(int uid, Constants.MEDIA_TRACE_EVENT currentEvent, VideoRenderingTracingInfo tracingInfo) { + super.onVideoRenderingTracingResult(uid, currentEvent, tracingInfo); + runOnUIThread(() -> { + FragmentLiveStreamingVideoTrackingBinding videoTrackingLayout = mRootBinding.videoTrackingLayout; + videoTrackingLayout.getRoot().setVisibility(View.VISIBLE); + videoTrackingLayout.tvUid.setText( String.valueOf(uid)); + videoTrackingLayout.tvEvent.setText(String.valueOf(currentEvent.getValue())); + videoTrackingLayout.tvElapsedTime.setText(String.format(Locale.US, "%d ms", tracingInfo.elapsedTime)); + videoTrackingLayout.tvStart2JoinChannel.setText(String.format(Locale.US, "%d ms", tracingInfo.start2JoinChannel)); + videoTrackingLayout.tvJoin2JoinSuccess.setText(String.format(Locale.US, "%d ms", tracingInfo.join2JoinSuccess)); + videoTrackingLayout.tvJoinSuccess2RemoteJoined.setText(String.format(Locale.US, "%d ms", tracingInfo.joinSuccess2RemoteJoined)); + videoTrackingLayout.tvRemoteJoined2SetView.setText(String.format(Locale.US, "%d ms", tracingInfo.remoteJoined2SetView)); + videoTrackingLayout.tvRemoteJoined2UnmuteVideo.setText(String.format(Locale.US, "%d ms", tracingInfo.remoteJoined2UnmuteVideo)); + videoTrackingLayout.tvRemoteJoined2PacketReceived.setText(String.format(Locale.US, "%d ms", tracingInfo.remoteJoined2PacketReceived)); + }); + } + }; } \ No newline at end of file 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 61dc7cbc5..41bb4e4b6 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 @@ -147,7 +147,6 @@ public void onDestroy() { /**leaveChannel and Destroy the RtcEngine instance*/ if (engine != null) { engine.leaveChannel(); - engine.stopPreview(); } dismissFloatWindow(); handler.post(RtcEngine::destroy); @@ -207,7 +206,6 @@ public void onClick(View v) { * 2:If you call the leaveChannel method during CDN live streaming, the SDK * triggers the removeInjectStreamUrl method.*/ engine.leaveChannel(); - engine.stopPreview(); join.setText(getString(R.string.join)); fl_remote.removeAllViews(); } @@ -247,8 +245,6 @@ private void joinChannel(String channelId) { VideoEncoderConfiguration.ORIENTATION_MODE.valueOf(((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingOrientation()) )); - engine.startPreview(); - ChannelMediaOptions option = new ChannelMediaOptions(); option.autoSubscribeAudio = true; option.autoSubscribeVideo = true; 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 ed0db3271..0a21ca6ee 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 @@ -167,7 +167,6 @@ public void onDestroy() if(engine != null) { engine.leaveChannel(); - engine.stopPreview(); } if(retryTask != null){ retryTask.cancel(true); @@ -208,7 +207,6 @@ public void onClick(View v) { engine.leaveChannel(); transCodeSwitch.setEnabled(true); - engine.stopPreview(); joined = false; join.setText(getString(R.string.join)); publishing = false; @@ -257,7 +255,6 @@ private void joinChannel(String channelId) )); /**Set up to play remote sound with receiver*/ engine.setDefaultAudioRoutetoSpeakerphone(true); - 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 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 21e064b95..ed2fee325 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 @@ -55,6 +55,7 @@ public class RhythmPlayer extends BaseFragment implements View.OnClickListener, private boolean isPlaying = false; private SeekBar beatPerMinute, beatPerMeasure; private AgoraRhythmPlayerConfig agoraRhythmPlayerConfig = new AgoraRhythmPlayerConfig(); + private ChannelMediaOptions mChannelMediaOptions; @Override public void onCreate(@Nullable Bundle savedInstanceState) @@ -214,6 +215,11 @@ public void onClick(View v) else if(v.getId() == R.id.play){ if(!isPlaying){ int ret = engine.startRhythmPlayer(URL_DOWNBEAT, URL_UPBEAT, agoraRhythmPlayerConfig); + if (joined) { + mChannelMediaOptions.publishRhythmPlayerTrack = true; + engine.updateChannelMediaOptions(mChannelMediaOptions); + } + Log.i(TAG, "startRhythmPlayer result:" + ret); isPlaying = true; beatPerMeasure.setEnabled(false); @@ -222,6 +228,10 @@ else if(v.getId() == R.id.play){ } else if(v.getId() == R.id.stop){ engine.stopRhythmPlayer(); + if (joined) { + mChannelMediaOptions.publishRhythmPlayerTrack = false; + engine.updateChannelMediaOptions(mChannelMediaOptions); + } isPlaying = false; beatPerMeasure.setEnabled(true); beatPerMinute.setEnabled(true); @@ -246,15 +256,15 @@ private void joinChannel(String channelId) /** 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; - option.publishMicrophoneTrack = true; + mChannelMediaOptions = new ChannelMediaOptions(); + mChannelMediaOptions.autoSubscribeAudio = true; + mChannelMediaOptions.autoSubscribeVideo = true; + mChannelMediaOptions.publishMicrophoneTrack = true; /** * config this for whether need push rhythem player to remote */ - option.publishRhythmPlayerTrack = true; - int res = engine.joinChannel(accessToken, channelId, 0, option); + mChannelMediaOptions.publishRhythmPlayerTrack = isPlaying; + int res = engine.joinChannel(accessToken, channelId, 0, mChannelMediaOptions); if (res != 0) { // Usually happens with invalid parameters // Error code description can be found at: diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VideoMetadata.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VideoMetadata.java index 87b08dafa..e077bf21d 100644 --- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VideoMetadata.java +++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/VideoMetadata.java @@ -154,7 +154,6 @@ public void onDestroy() if (engine != null) { engine.leaveChannel(); - engine.stopPreview(); } handler.post(RtcEngine::destroy); engine = null; @@ -208,7 +207,6 @@ public void onClick(View v) * 2:If you call the leaveChannel method during CDN live streaming, the SDK * triggers the removeInjectStreamUrl method.*/ engine.leaveChannel(); - engine.stopPreview(); send.setEnabled(false); join.setText(getString(R.string.join)); } @@ -257,7 +255,6 @@ private void joinChannel(String channelId) * < 0:Failure*/ int code = engine.registerMediaMetadataObserver(iMetadataObserver, IMetadataObserver.VIDEO_METADATA); Log.e(TAG, code + ""); - 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 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 d5dbb6da5..c83325d87 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 @@ -216,7 +216,6 @@ public void onDestroy() if(engine != null) { engine.leaveChannel(); - engine.stopPreview(); } handler.post(RtcEngine::destroy); engine = null; @@ -257,7 +256,6 @@ private void joinChannel(String channelId) STANDARD_BITRATE, VideoEncoderConfiguration.ORIENTATION_MODE.valueOf(((MainApplication)getActivity().getApplication()).getGlobalSettings().getVideoEncodingOrientation()) )); - 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 @@ -334,7 +332,6 @@ public void onClick(View v) { * 2:If you call the leaveChannel method during CDN live streaming, the SDK * triggers the removeInjectStreamUrl method.*/ engine.leaveChannel(); - engine.stopPreview(); join.setText(getString(R.string.join)); controlPanel.setVisibility(View.INVISIBLE); } 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 56ba85d4f..a4403f77a 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 @@ -238,6 +238,8 @@ public void onClick(View v) { join.setText(getString(R.string.join)); mic.setEnabled(false); pcm.setEnabled(false); + pcm.setChecked(false); + mic.setChecked(true); if(audioPushingHelper != null){ audioPushingHelper.stop(); } 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 a2d874bdc..5d3ba1cd8 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 @@ -154,7 +154,6 @@ public void onDestroy() if(engine != null) { engine.leaveChannel(); - engine.stopPreview(); } handler.post(RtcEngine::destroy); engine = null; @@ -219,7 +218,6 @@ public void onClick(View v) * 2:If you call the leaveChannel method during CDN live streaming, the SDK * triggers the removeInjectStreamUrl method.*/ engine.leaveChannel(); - engine.stopPreview(); join.setText(getString(R.string.join)); for (ViewGroup value : remoteViews.values()) { value.removeAllViews(); @@ -227,7 +225,7 @@ public void onClick(View v) remoteViews.clear(); } }else if(v.getId() == switch_camera.getId()){ - if(engine != null){ + if(engine != null && joined){ engine.switchCamera(); } } @@ -267,8 +265,6 @@ private void joinChannel(String channelId) VideoEncoderConfiguration.ORIENTATION_MODE.valueOf(((MainApplication)getActivity().getApplication()).getGlobalSettings().getVideoEncodingOrientation()) )); - engine.startPreview(); - ChannelMediaOptions option = new ChannelMediaOptions(); option.autoSubscribeAudio = true; option.autoSubscribeVideo = true; @@ -317,7 +313,6 @@ public void onError(int err) { showLongToast("Error code:" + err + ", msg:" + RtcEngine.getErrorDescription(err)); if (err == Constants.ERR_INVALID_TOKEN || err == Constants.ERR_TOKEN_EXPIRED) { engine.leaveChannel(); - engine.stopPreview(); runOnUIThread(() -> join.setEnabled(true)); if (Constants.ERR_INVALID_TOKEN == err) { diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelVideoByToken.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelVideoByToken.java index a8fe50174..cca09f80f 100644 --- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelVideoByToken.java +++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelVideoByToken.java @@ -135,7 +135,6 @@ private void destroyRtcEngine(){ if (engine != null) { /**leaveChannel and Destroy the RtcEngine instance*/ engine.leaveChannel(); - engine.stopPreview(); RtcEngine.destroy(); engine = null; } @@ -177,7 +176,7 @@ public void onClick(View v) { destroyRtcEngine(); } } else if (v.getId() == switch_camera.getId()) { - if (engine != null) { + if (engine != null && joined) { engine.switchCamera(); } } @@ -214,8 +213,6 @@ private void joinChannel(String channelId, String token) { VideoEncoderConfiguration.ORIENTATION_MODE.valueOf(((MainApplication) getActivity().getApplication()).getGlobalSettings().getVideoEncodingOrientation()) )); - engine.startPreview(); - ChannelMediaOptions option = new ChannelMediaOptions(); option.autoSubscribeAudio = true; option.autoSubscribeVideo = true; @@ -226,8 +223,6 @@ private void joinChannel(String channelId, String token) { if you do not specify the uid, we will generate the uid for you*/ int res = engine.joinChannel(token, channelId, 0, option); if (res != 0) { - engine.leaveChannel(); - engine.stopPreview(); // 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 @@ -257,7 +252,6 @@ public void onError(int err) { showLongToast("Error code:" + err + ", msg:" + RtcEngine.getErrorDescription(err)); if (err == Constants.ERR_INVALID_TOKEN || err == Constants.ERR_TOKEN_EXPIRED) { engine.leaveChannel(); - engine.stopPreview(); runOnUIThread(() -> join.setEnabled(true)); if (Constants.ERR_INVALID_TOKEN == err) { diff --git a/Android/APIExample/app/src/main/res/layout/fragment_custom_audio_source.xml b/Android/APIExample/app/src/main/res/layout/fragment_custom_audio_source.xml index d337497be..044f6a45c 100644 --- a/Android/APIExample/app/src/main/res/layout/fragment_custom_audio_source.xml +++ b/Android/APIExample/app/src/main/res/layout/fragment_custom_audio_source.xml @@ -111,6 +111,7 @@ android:layout_alignParentEnd="true" android:layout_alignParentBottom="true" android:checked="true" + android:enabled="false" android:layout_marginEnd="16dp" android:layout_marginBottom="64dp"/> @@ -121,6 +122,7 @@ android:layout_alignParentEnd="true" android:layout_alignParentBottom="true" android:layout_marginEnd="16dp" + android:enabled="false" android:layout_marginBottom="110dp" android:text="@string/publish_local_audio" /> diff --git a/Android/APIExample/app/src/main/res/layout/fragment_live_streaming.xml b/Android/APIExample/app/src/main/res/layout/fragment_live_streaming.xml index 1e9a630be..9a1e4002f 100644 --- a/Android/APIExample/app/src/main/res/layout/fragment_live_streaming.xml +++ b/Android/APIExample/app/src/main/res/layout/fragment_live_streaming.xml @@ -1,5 +1,6 @@ - - - - - - - - - - - - - - + + - - + + - - + app:layout_constraintBottom_toTopOf="@id/btn_remote_screenshot" + app:layout_constraintStart_toStartOf="@id/btn_publish"/> + android:layout_marginBottom="4dp" - + android:text="@string/remote_screenshot" + app:layout_constraintBottom_toTopOf="@id/btn_publish" + app:layout_constraintStart_toStartOf="@id/btn_publish" /> + android:text="@string/enable_publish" + app:layout_constraintBottom_toTopOf="@id/ll_join" + app:layout_constraintStart_toStartOf="parent" /> + + android:orientation="horizontal" + app:layout_constraintBottom_toBottomOf="parent"> + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/Android/APIExample/app/src/main/res/layout/fragment_live_streaming_setting.xml b/Android/APIExample/app/src/main/res/layout/fragment_live_streaming_setting.xml new file mode 100644 index 000000000..d5bae5380 --- /dev/null +++ b/Android/APIExample/app/src/main/res/layout/fragment_live_streaming_setting.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Android/APIExample/app/src/main/res/layout/fragment_live_streaming_video_item.xml b/Android/APIExample/app/src/main/res/layout/fragment_live_streaming_video_item.xml new file mode 100644 index 000000000..966d1c4a5 --- /dev/null +++ b/Android/APIExample/app/src/main/res/layout/fragment_live_streaming_video_item.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/Android/APIExample/app/src/main/res/layout/fragment_live_streaming_video_tracking.xml b/Android/APIExample/app/src/main/res/layout/fragment_live_streaming_video_tracking.xml new file mode 100644 index 000000000..de9892946 --- /dev/null +++ b/Android/APIExample/app/src/main/res/layout/fragment_live_streaming_video_tracking.xml @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Android/APIExample/app/src/main/res/layout/fragment_rtmp_streaming.xml b/Android/APIExample/app/src/main/res/layout/fragment_rtmp_streaming.xml index 1b5cb5da4..53624a562 100644 --- a/Android/APIExample/app/src/main/res/layout/fragment_rtmp_streaming.xml +++ b/Android/APIExample/app/src/main/res/layout/fragment_rtmp_streaming.xml @@ -93,7 +93,7 @@ android:layout_weight="1" android:hint="@string/url" android:singleLine="true" - android:text="rtmp://mdetest.push.agoramde.agoraio.cn/live/xxx" /> + android:text="rtmp://push.webdemo.agoraio.cn/lbhd/test" /> 播放器错误: %s 播放器播放结束。 是否重新打开url? + 首帧出图优化 + 开启后无法关闭,并且需要主辅两端都开启时才生效 \ No newline at end of file diff --git a/Android/APIExample/app/src/main/res/values/arrays.xml b/Android/APIExample/app/src/main/res/values/arrays.xml index d0c930c33..6ba3bd2aa 100644 --- a/Android/APIExample/app/src/main/res/values/arrays.xml +++ b/Android/APIExample/app/src/main/res/values/arrays.xml @@ -188,6 +188,7 @@ VD_480x480 VD_640x480 VD_840x480 + VD_960x540 VD_960x720 VD_1280x720 diff --git a/Android/APIExample/app/src/main/res/values/strings.xml b/Android/APIExample/app/src/main/res/values/strings.xml index 6cc06f8a9..2f4d97bc5 100644 --- a/Android/APIExample/app/src/main/res/values/strings.xml +++ b/Android/APIExample/app/src/main/res/values/strings.xml @@ -312,4 +312,6 @@ Media player error: %s Media player completed. Reopen url again? + First Frame Optimization + It cannot be turned off after it is turned on, and it will take effect only when both the main and auxiliary ends are turned on diff --git a/iOS/APIExample-Audio/APIExample-Audio.xcodeproj/project.pbxproj b/iOS/APIExample-Audio/APIExample-Audio.xcodeproj/project.pbxproj index 7f8ce1533..261cd6ed1 100644 --- a/iOS/APIExample-Audio/APIExample-Audio.xcodeproj/project.pbxproj +++ b/iOS/APIExample-Audio/APIExample-Audio.xcodeproj/project.pbxproj @@ -986,7 +986,7 @@ CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = YS397FG5PA; - ENABLE_BITCODE = YES; + ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/APIExample", @@ -1056,7 +1056,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = YS397FG5PA; - ENABLE_BITCODE = YES; + ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/APIExample", diff --git a/iOS/APIExample-Audio/APIExample-Audio/Common/GlobalSettings.swift b/iOS/APIExample-Audio/APIExample-Audio/Common/GlobalSettings.swift index 533b2a2e6..658f2cfc7 100644 --- a/iOS/APIExample-Audio/APIExample-Audio/Common/GlobalSettings.swift +++ b/iOS/APIExample-Audio/APIExample-Audio/Common/GlobalSettings.swift @@ -49,10 +49,10 @@ class GlobalSettings { SettingItemOption(idx: 0, label: "90x90", value: CGSize(width: 90, height: 90)), SettingItemOption(idx: 1, label: "160x120", value: CGSize(width: 160, height: 120)), SettingItemOption(idx: 2, label: "320x240", value: CGSize(width: 320, height: 240)), - SettingItemOption(idx: 3, label: "640x360", value: CGSize(width: 640, height: 360)), + SettingItemOption(idx: 3, label: "960x540", value: CGSize(width: 960, height: 540)), SettingItemOption(idx: 4, label: "1280x720", value: CGSize(width: 1280, height: 720)) ]), - "fps": SettingItem(selected: 3, options: [ + "fps": SettingItem(selected: 1, options: [ SettingItemOption(idx: 0, label: "10fps", value: AgoraVideoFrameRate.fps10), SettingItemOption(idx: 1, label: "15fps", value: AgoraVideoFrameRate.fps15), SettingItemOption(idx: 2, label: "24fps", value: AgoraVideoFrameRate.fps24), diff --git a/iOS/APIExample-Audio/APIExample-Audio/Examples/Advanced/SpatialAudio/SpatialAudio.swift b/iOS/APIExample-Audio/APIExample-Audio/Examples/Advanced/SpatialAudio/SpatialAudio.swift index 916e8f025..3ee89b800 100644 --- a/iOS/APIExample-Audio/APIExample-Audio/Examples/Advanced/SpatialAudio/SpatialAudio.swift +++ b/iOS/APIExample-Audio/APIExample-Audio/Examples/Advanced/SpatialAudio/SpatialAudio.swift @@ -39,6 +39,7 @@ class SpatialAudioMain: BaseViewController { @IBOutlet weak var remoteUserButton1: UIButton! @IBOutlet weak var remoteUserButton2: UIButton! + private var isJoined: Bool = false private lazy var actionView1 = SpatialAudioActionSheet() private lazy var actionView2 = SpatialAudioActionSheet() var agoraKit: AgoraRtcEngineKit! @@ -76,14 +77,17 @@ class SpatialAudioMain: BaseViewController { localSpatial.setAudioRecvRange(Float(SCREENSIZE.height)) localSpatial.setMaxAudioRecvCount(2) localSpatial.setDistanceUnit(1) - + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + guard isJoined == false else { return } mediaPlayer1 = agoraKit.createMediaPlayer(with: self) mediaPlayer1.setLoopCount(10000) mediaPlayer1.open("https://webdemo.agora.io/audiomixing.mp3", startPos: 0) localSpatial.updatePlayerPositionInfo(Int(mediaPlayer1.getMediaPlayerId()), positionInfo: getPlayerPostion(view: voiceButton1)) localSpatial.setPlayerAttenuation(0.2, playerId: UInt(mediaPlayer1.getMediaPlayerId()), forceSet: false) - mediaPlayer2 = agoraKit.createMediaPlayer(with: self) mediaPlayer2.setLoopCount(10000) mediaPlayer2.open("https://webdemo.agora.io/dang.mp3", startPos: 0) @@ -95,12 +99,12 @@ class SpatialAudioMain: BaseViewController { joinChannel() } - override func willMove(toParent parent: UIViewController?) { - if parent == nil { - agoraKit = nil - AgoraLocalSpatialAudioKit.destroy() - AgoraRtcEngineKit.destroy() - } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + agoraKit = nil + AgoraLocalSpatialAudioKit.destroy() + AgoraRtcEngineKit.destroy() + isJoined = false } private func joinChannel() { @@ -113,6 +117,7 @@ class SpatialAudioMain: BaseViewController { if result != 0 { print("join channel fail") } + self.isJoined = true }) } @@ -169,7 +174,6 @@ class SpatialAudioMain: BaseViewController { audioZone.up = up audioZone.position = getViewCenterPostion(view: voiceContainerView1) localSpatial.setZones([audioZone]) - } else { let audioZone = AgoraSpatialAudioZone() audioZone.forwardLength = Float(SCREENSIZE.height) @@ -210,6 +214,7 @@ class SpatialAudioMain: BaseViewController { private func getPlayerPostion(view: UIView) -> AgoraRemoteVoicePositionInfo { let position = getViewCenterPostion(view: view) + print("player postion == \(position)") let positionInfo = AgoraRemoteVoicePositionInfo() positionInfo.position = position positionInfo.forward = forward diff --git a/iOS/APIExample-Audio/APIExample-Audio/Info.plist b/iOS/APIExample-Audio/APIExample-Audio/Info.plist index 3deee0512..3c3d2fc3a 100644 --- a/iOS/APIExample-Audio/APIExample-Audio/Info.plist +++ b/iOS/APIExample-Audio/APIExample-Audio/Info.plist @@ -2,7 +2,7 @@ - Application Supports itunes + UIFileSharingEnabled BGTaskSchedulerPermittedIdentifiers diff --git a/iOS/APIExample-Audio/Podfile b/iOS/APIExample-Audio/Podfile index 46d432e67..a105225a4 100644 --- a/iOS/APIExample-Audio/Podfile +++ b/iOS/APIExample-Audio/Podfile @@ -7,7 +7,7 @@ target 'APIExample-Audio' do pod 'Floaty', '~> 4.2.0' pod 'AGEVideoLayout', '~> 1.0.2' - pod 'AgoraAudio_iOS', '4.1.0' + pod 'AgoraAudio_iOS', '4.1.1' # pod 'sdk', :path => 'sdk.podspec' end diff --git a/iOS/APIExample/APIExample.xcodeproj/project.pbxproj b/iOS/APIExample/APIExample.xcodeproj/project.pbxproj index 2f582cb96..e9e1fa16d 100644 --- a/iOS/APIExample/APIExample.xcodeproj/project.pbxproj +++ b/iOS/APIExample/APIExample.xcodeproj/project.pbxproj @@ -134,6 +134,11 @@ A7CA48C424553CF700507435 /* Popover.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A7CA48C224553CF600507435 /* Popover.storyboard */; }; B9C914453E92C7F49C93E1F5 /* Pods_APIExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8551A4BC255CE49B383BC575 /* Pods_APIExample.framework */; }; CBCDE23FB64E60D6A79F3723 /* Pods_Agora_ScreenShare_Extension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09E72C5D1AABD812866E41A6 /* Pods_Agora_ScreenShare_Extension.framework */; }; + E7163F812964149800EBBD55 /* ARKit.strings in Resources */ = {isa = PBXBuildFile; fileRef = E7163F7C2964149800EBBD55 /* ARKit.strings */; }; + E7163F822964149800EBBD55 /* ARKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7163F7E2964149800EBBD55 /* ARKit.swift */; }; + E7163F832964149800EBBD55 /* ARKit.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E7163F7F2964149800EBBD55 /* ARKit.storyboard */; }; + E7163F88296414F700EBBD55 /* ARVideoRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7163F86296414F700EBBD55 /* ARVideoRenderer.swift */; }; + E7163F8A29651D8900EBBD55 /* AR.scnassets in Resources */ = {isa = PBXBuildFile; fileRef = E7163F8929651D8900EBBD55 /* AR.scnassets */; }; E72055EA28F943520030E6D1 /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = E72055E928F943520030E6D1 /* Util.swift */; }; E72055F32900F8780030E6D1 /* KtvCopyrightMusic.swift in Sources */ = {isa = PBXBuildFile; fileRef = E72055F12900F8780030E6D1 /* KtvCopyrightMusic.swift */; }; E721600F28D3314B006431BD /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E721600E28D3314B006431BD /* AlertManager.swift */; }; @@ -421,6 +426,11 @@ A7CA48C324553CF600507435 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Popover.storyboard; sourceTree = ""; }; BC25C1A6D9E6B8827D095985 /* Pods_SimpleFilter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SimpleFilter.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CC6D08A23527C200339E4FD6 /* Pods-SimpleAudioFilter.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SimpleAudioFilter.release.xcconfig"; path = "Target Support Files/Pods-SimpleAudioFilter/Pods-SimpleAudioFilter.release.xcconfig"; sourceTree = ""; }; + E7163F7D2964149800EBBD55 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/ARKit.strings"; sourceTree = ""; }; + E7163F7E2964149800EBBD55 /* ARKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARKit.swift; sourceTree = ""; }; + E7163F802964149800EBBD55 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/ARKit.storyboard; sourceTree = ""; }; + E7163F86296414F700EBBD55 /* ARVideoRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARVideoRenderer.swift; sourceTree = ""; }; + E7163F8929651D8900EBBD55 /* AR.scnassets */ = {isa = PBXFileReference; lastKnownFileType = wrapper.scnassets; path = AR.scnassets; sourceTree = ""; }; E72055E928F943520030E6D1 /* Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = ""; }; E72055F12900F8780030E6D1 /* KtvCopyrightMusic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KtvCopyrightMusic.swift; sourceTree = ""; }; E721600E28D3314B006431BD /* AlertManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertManager.swift; sourceTree = ""; }; @@ -835,6 +845,7 @@ 03D13BFF24488F1E00B599B3 /* Common */ = { isa = PBXGroup; children = ( + E7163F84296414F700EBBD55 /* ARKit */, E74877B528A23B8B00CA2F58 /* NetworkManager */, 67B8C7B22805753D00195106 /* Utils */, 036CBA4025198E9100D74FAD /* CustomEncryption */, @@ -1038,6 +1049,7 @@ A75A56D724A0603000D0089E /* Advanced */ = { isa = PBXGroup; children = ( + E7163F7B2964149800EBBD55 /* ARKit */, E7A49CE829029E0000F06DD4 /* ThirdBeautify */, E7A49CB229011E7500F06DD4 /* MutliCamera */, E72055F02900F8370030E6D1 /* KtvCopyrightMusic */, @@ -1077,6 +1089,7 @@ A7CA48BF2455315A00507435 /* Supporting Files */ = { isa = PBXGroup; children = ( + E7163F8929651D8900EBBD55 /* AR.scnassets */, 03D13BDD2448758B00B599B3 /* Info.plist */, 03D13BCF2448758900B599B3 /* AppDelegate.swift */, 03D13BD82448758B00B599B3 /* Assets.xcassets */, @@ -1086,6 +1099,24 @@ name = "Supporting Files"; sourceTree = ""; }; + E7163F7B2964149800EBBD55 /* ARKit */ = { + isa = PBXGroup; + children = ( + E7163F7C2964149800EBBD55 /* ARKit.strings */, + E7163F7E2964149800EBBD55 /* ARKit.swift */, + E7163F7F2964149800EBBD55 /* ARKit.storyboard */, + ); + path = ARKit; + sourceTree = ""; + }; + E7163F84296414F700EBBD55 /* ARKit */ = { + isa = PBXGroup; + children = ( + E7163F86296414F700EBBD55 /* ARVideoRenderer.swift */, + ); + path = ARKit; + sourceTree = ""; + }; E72055F02900F8370030E6D1 /* KtvCopyrightMusic */ = { isa = PBXGroup; children = ( @@ -1456,6 +1487,7 @@ 033A9F7A252D8B5000BC26E1 /* MediaPlayer.storyboard in Resources */, 8BE7ABC3279E065000DFBCEF /* FusionCDN.storyboard in Resources */, 0339D6D224E91B80008739CD /* QuickSwitchChannelVCItem.xib in Resources */, + E7163F8A29651D8900EBBD55 /* AR.scnassets in Resources */, E728B84928B5FFCB00674A4A /* PictureInPicture.storyboard in Resources */, 03BEED0D251CAB9C005E78F4 /* audioeffect.mp3 in Resources */, A7CA48C424553CF700507435 /* Popover.storyboard in Resources */, @@ -1477,7 +1509,9 @@ 576CA80A25A9CC3A0091520B /* output.raw in Resources */, E7A49D40290907E200F06DD4 /* BytedEffect.strings in Resources */, 03B12DAA251125B700E55818 /* VideoView.xib in Resources */, + E7163F832964149800EBBD55 /* ARKit.storyboard in Resources */, 8BA5459526AFEC8D00ED4295 /* SimpleFilter.storyboard in Resources */, + E7163F812964149800EBBD55 /* ARKit.strings in Resources */, 67CB2F0D27EB318200CB19D2 /* SpatialAudio.storyboard in Resources */, 8BA5459426AFEC8D00ED4295 /* SimpleFilter.strings in Resources */, 033A9F4D252D89DB00BC26E1 /* CustomAudioSource.storyboard in Resources */, @@ -1668,6 +1702,7 @@ E721600F28D3314B006431BD /* AlertManager.swift in Sources */, E7A49CFF29029E0000F06DD4 /* FUManager.m in Sources */, 0339D6D424E91BAA008739CD /* QuickSwitchChannelVCItem.swift in Sources */, + E7163F822964149800EBBD55 /* ARKit.swift in Sources */, 036C42B624D2A3C600A59000 /* AgoraMetalShader.metal in Sources */, 033A9EE5252D5C6900BC26E1 /* VideoMetadata.swift in Sources */, 67B8C7B028056B7600195106 /* RawVideoData.swift in Sources */, @@ -1698,6 +1733,7 @@ 0385768225224A88003C369A /* JoinChannelVideo.swift in Sources */, A7847F922458062900469187 /* StatisticsInfo.swift in Sources */, E7A49D5D2909105000F06DD4 /* BEPixelBufferInfo.m in Sources */, + E7163F88296414F700EBBD55 /* ARVideoRenderer.swift in Sources */, 0339BE72251EF075007D4FDD /* MediaPlayer.swift in Sources */, E7A49CFE29029E0000F06DD4 /* FUBeautifyVC.m in Sources */, 0385767E2521E5A0003C369A /* MediaChannelRelay.swift in Sources */, @@ -2049,6 +2085,22 @@ name = Popover.storyboard; sourceTree = ""; }; + E7163F7C2964149800EBBD55 /* ARKit.strings */ = { + isa = PBXVariantGroup; + children = ( + E7163F7D2964149800EBBD55 /* zh-Hans */, + ); + name = ARKit.strings; + sourceTree = ""; + }; + E7163F7F2964149800EBBD55 /* ARKit.storyboard */ = { + isa = PBXVariantGroup; + children = ( + E7163F802964149800EBBD55 /* Base */, + ); + name = ARKit.storyboard; + sourceTree = ""; + }; E728B84228B5FFCB00674A4A /* PictureInPicture.strings */ = { isa = PBXVariantGroup; children = ( @@ -2198,6 +2250,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = YS397FG5PA; + ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/APIExample", @@ -2234,6 +2287,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = YS397FG5PA; + ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/APIExample", diff --git a/iOS/APIExample/APIExample/AR.scnassets/displayer.scn b/iOS/APIExample/APIExample/AR.scnassets/displayer.scn new file mode 100755 index 000000000..e83d249b8 Binary files /dev/null and b/iOS/APIExample/APIExample/AR.scnassets/displayer.scn differ diff --git a/iOS/APIExample/APIExample/Common/ARKit/ARVideoRenderer.swift b/iOS/APIExample/APIExample/Common/ARKit/ARVideoRenderer.swift new file mode 100755 index 000000000..ae6781de8 --- /dev/null +++ b/iOS/APIExample/APIExample/Common/ARKit/ARVideoRenderer.swift @@ -0,0 +1,139 @@ +// +// ARVideoRenderer.swift +// Agora-Video-With-ARKit +// +// Created by GongYuhua on 2017/12/27. +// Copyright © 2017年 Agora.io All rights reserved. +// + +import Foundation +import MetalKit +import SceneKit +import AgoraRtcKit + +class ARVideoRenderer : NSObject { + fileprivate var yTexture: MTLTexture? + fileprivate var uTexture: MTLTexture? + fileprivate var vTexture: MTLTexture? + fileprivate var rgbTexture: MTLTexture? + + fileprivate let device = MTLCreateSystemDefaultDevice() + fileprivate var commandQueue: MTLCommandQueue? + + fileprivate var defaultLibrary: MTLLibrary? + + fileprivate var threadsPerThreadgroup = MTLSizeMake(16, 16, 1) + fileprivate var threadgroupsPerGrid = MTLSizeMake(128, 96, 1) + fileprivate var pipelineState: MTLComputePipelineState? + + var renderNode: SCNNode? + var pixelBuffer: CVPixelBuffer? + + override init() { + super.init() + + defaultLibrary = device?.makeDefaultLibrary() + + if let device = device, let function = defaultLibrary?.makeFunction(name: "writeRGBFromYUV") { + pipelineState = try? device.makeComputePipelineState(function: function) + } + + commandQueue = device?.makeCommandQueue() + } + + func shouldDispose() { + yTexture = nil + uTexture = nil + vTexture = nil + rgbTexture = nil + renderNode?.geometry?.firstMaterial?.diffuse.contents = createEmptyRGBTexture(width: 1, height: 1) + } +} + +extension ARVideoRenderer: AgoraVideoFrameDelegate { +// func onCapture(_ videoFrame: AgoraOutputVideoFrame) -> Bool { +// +// videoFrame.pixelBuffer = pixelBuffer +// +// return true +// } + + func getVideoFormatPreference() -> AgoraVideoFormat { + .cvPixelI420 + } + + func getVideoFrameProcessMode() -> AgoraVideoFrameProcessMode { + .readWrite + } + + func onRenderVideoFrame(_ videoFrame: AgoraOutputVideoFrame, uid: UInt, channelId: String) -> Bool { + guard let node = renderNode, let rawData = videoFrame.alphaBuffer else { + return true + } + + let width = Int(videoFrame.width) + let height = Int(videoFrame.height) + + yTexture = createTexture(withData: rawData, + width: width, + height: height) + uTexture = createTexture(withData: rawData + width * height, + width: width / 2, + height: height / 2) + vTexture = createTexture(withData: rawData + width * height * 5 / 4, + width: width / 2, + height: height / 2) + + rgbTexture = createEmptyRGBTexture(width: width, height: height) + + node.geometry?.firstMaterial?.diffuse.contents = rgbTexture + renderRGBTexture() + + return true + } +} + +private extension ARVideoRenderer { + func createTexture(withData data: UnsafeMutableRawPointer, width: Int, height: Int) -> MTLTexture? { + let descriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .r8Uint, + width: width, + height: height, + mipmapped: false) + let texture = device?.makeTexture(descriptor: descriptor) + texture?.replace(region: MTLRegionMake2D(0, 0, width, height), + mipmapLevel: 0, + withBytes: data, + bytesPerRow: width) + + return texture + } + + func createEmptyRGBTexture(width: Int, height: Int) -> MTLTexture? { + let rgbaDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba16Float, + width: width, + height: height, + mipmapped: false) + rgbaDescriptor.usage = [.shaderWrite, .shaderRead] + let rgbTexture = device?.makeTexture(descriptor: rgbaDescriptor) + return rgbTexture + } + + func renderRGBTexture() { + guard let state = pipelineState, + let buffer = commandQueue?.makeCommandBuffer(), + let encoder = buffer.makeComputeCommandEncoder() else { + return + } + + encoder.setComputePipelineState(state) + encoder.setTexture(yTexture, index: 0) + encoder.setTexture(uTexture, index: 1) + encoder.setTexture(vTexture, index: 2) + encoder.setTexture(rgbTexture, index: 3) + encoder.dispatchThreadgroups(threadgroupsPerGrid, + threadsPerThreadgroup: threadsPerThreadgroup) + encoder.endEncoding() + + buffer.commit() + } +} diff --git a/iOS/APIExample/APIExample/Common/ExternalVideo/AgoraSampleBufferRender.m b/iOS/APIExample/APIExample/Common/ExternalVideo/AgoraSampleBufferRender.m index 7ac29cfc4..a651521f2 100644 --- a/iOS/APIExample/APIExample/Common/ExternalVideo/AgoraSampleBufferRender.m +++ b/iOS/APIExample/APIExample/Common/ExternalVideo/AgoraSampleBufferRender.m @@ -152,6 +152,9 @@ - (void)renderVideoData:(AgoraOutputVideoFrame *_Nonnull)videoData { CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, pixelBuffer, videoInfo, &timingInfo, &sampleBuffer); [self.displayLayer enqueueSampleBuffer:sampleBuffer]; + if (self.displayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) { + [self.displayLayer flush]; + } CMSampleBufferInvalidate(sampleBuffer); CFRelease(sampleBuffer); diff --git a/iOS/APIExample/APIExample/Common/GlobalSettings.swift b/iOS/APIExample/APIExample/Common/GlobalSettings.swift index b1cd9aee9..9f6c20f15 100644 --- a/iOS/APIExample/APIExample/Common/GlobalSettings.swift +++ b/iOS/APIExample/APIExample/Common/GlobalSettings.swift @@ -49,10 +49,10 @@ class GlobalSettings { SettingItemOption(idx: 0, label: "90x90", value: CGSize(width: 90, height: 90)), SettingItemOption(idx: 1, label: "160x120", value: CGSize(width: 160, height: 120)), SettingItemOption(idx: 2, label: "320x240", value: CGSize(width: 320, height: 240)), - SettingItemOption(idx: 3, label: "640x360", value: CGSize(width: 640, height: 360)), + SettingItemOption(idx: 3, label: "960x540", value: CGSize(width: 960, height: 540)), SettingItemOption(idx: 4, label: "1280x720", value: CGSize(width: 1280, height: 720)) ]), - "fps": SettingItem(selected: 3, options: [ + "fps": SettingItem(selected: 1, options: [ SettingItemOption(idx: 0, label: "10fps", value: AgoraVideoFrameRate.fps10), SettingItemOption(idx: 1, label: "15fps", value: AgoraVideoFrameRate.fps15), SettingItemOption(idx: 2, label: "24fps", value: AgoraVideoFrameRate.fps24), diff --git a/iOS/APIExample/APIExample/Common/StatisticsInfo.swift b/iOS/APIExample/APIExample/Common/StatisticsInfo.swift index f8b2eea4a..da5b6a6b7 100755 --- a/iOS/APIExample/APIExample/Common/StatisticsInfo.swift +++ b/iOS/APIExample/APIExample/Common/StatisticsInfo.swift @@ -34,6 +34,7 @@ struct StatisticsInfo { var dimension = CGSize.zero var fps:UInt = 0 + var firstFrameElapsedTime: Double = 0 var type: StatisticsType @@ -108,6 +109,9 @@ struct StatisticsInfo { break } } + mutating func updateFirstFrameInfo(_ info: AgoraVideoRenderingTracingInfo) { + firstFrameElapsedTime = Double(info.elapsedTime) + } func description(audioOnly:Bool) -> String { var full: String @@ -132,10 +136,14 @@ struct StatisticsInfo { let vSendLoss = "VSend Loss: MISSING%" let aSendLoss = "ASend Loss: MISSING%" + let firstFrame = "firstFrameTime: \(firstFrameElapsedTime)" + if(audioOnly) { - return [lastmile,audioSend,cpu,aSendLoss].joined(separator: "\n") + let array = firstFrameElapsedTime > 0 ? [firstFrame, lastmile,audioSend,cpu,aSendLoss] : [lastmile,audioSend,cpu,aSendLoss] + return array.joined(separator: "\n") } - return [dimensionFps,lastmile,videoSend,audioSend,cpu,vSendLoss,aSendLoss].joined(separator: "\n") + let array = firstFrameElapsedTime > 0 ? [firstFrame, dimensionFps,lastmile,videoSend,audioSend,cpu,vSendLoss,aSendLoss] : [dimensionFps,lastmile,videoSend,audioSend,cpu,vSendLoss,aSendLoss] + return array.joined(separator: "\n") } func remoteDescription(info: RemoteInfo, audioOnly: Bool) -> String { @@ -148,6 +156,7 @@ struct StatisticsInfo { audioQuality = AgoraNetworkQuality.unknown } + let firstFrame = "firstFrameTime: \(firstFrameElapsedTime)" let videoRecv = "VRecv: \(info.videoStats.receivedBitrate)kbps" let audioRecv = "ARecv: \(info.audioStats.receivedBitrate)kbps" @@ -155,8 +164,10 @@ struct StatisticsInfo { let audioLoss = "ALoss: \(info.audioStats.audioLossRate)%" let aquality = "AQuality: \(audioQuality.description())" if(audioOnly) { - return [audioRecv,audioLoss,aquality].joined(separator: "\n") + let array = firstFrameElapsedTime > 0 ? [firstFrame, audioRecv,audioLoss,aquality] : [audioRecv,audioLoss,aquality] + return array.joined(separator: "\n") } - return [dimensionFpsBit,videoRecv,audioRecv,videoLoss,audioLoss,aquality].joined(separator: "\n") + let array = firstFrameElapsedTime > 0 ? [firstFrame, dimensionFpsBit,videoRecv,audioRecv,videoLoss,audioLoss,aquality] : [dimensionFpsBit,videoRecv,audioRecv,videoLoss,audioLoss,aquality] + return array.joined(separator: "\n") } } diff --git a/iOS/APIExample/APIExample/Examples/Advanced/ARKit/ARKit.swift b/iOS/APIExample/APIExample/Examples/Advanced/ARKit/ARKit.swift new file mode 100644 index 000000000..dd8212bf5 --- /dev/null +++ b/iOS/APIExample/APIExample/Examples/Advanced/ARKit/ARKit.swift @@ -0,0 +1,351 @@ +// +// JoinChannelVC.swift +// APIExample +// +// Created by 张乾泽 on 2020/4/17. +// Copyright © 2020 Agora Corp. All rights reserved. +// +import UIKit +import AgoraRtcKit +import ARKit + +class ARKitEntry : UIViewController +{ + @IBOutlet weak var joinButton: AGButton! + @IBOutlet weak var channelTextField: AGTextField! + @IBOutlet weak var firstFrameSwitch: UISwitch! + private var isFirstFrame: Bool = false + let identifier = "ARKit" + + override func viewDidLoad() { + super.viewDidLoad() + } + + @IBAction func doOptimizeFirstFrameSwitch(_ sender: UISwitch) { + if sender.isOn { + let alertVC = UIAlertController(title: "After this function is enabled, it cannot be disabled and takes effect only when both the primary and secondary ends are enabled".localized, + message: nil, + preferredStyle: .alert) + + let ok = UIAlertAction(title: "Sure".localized, style: .default, handler: nil) + let cancel = UIAlertAction(title: "Cancel".localized, style: .cancel) { _ in + sender.isOn = false + } + alertVC.addAction(ok) + alertVC.addAction(cancel) + present(alertVC, animated: true, completion: nil) + } + } + + @IBAction func doJoinPressed(sender: AGButton) { + guard let channelName = channelTextField.text else {return} + //resign channel text field + channelTextField.resignFirstResponder() + + let storyBoard: UIStoryboard = UIStoryboard(name: identifier, bundle: nil) + // create new view controller every time to ensure we get a clean vc + guard let newViewController = storyBoard.instantiateViewController(withIdentifier: identifier) as? BaseViewController else {return} + newViewController.title = channelName + newViewController.configs = ["channelName":channelName, "isFirstFrame": firstFrameSwitch.isOn] + navigationController?.pushViewController(newViewController, animated: true) + } +} + +class ARKitMain: BaseViewController { + @IBOutlet weak var sceneView: ARSCNView! + @IBOutlet weak var infoLabel: UILabel! + @IBOutlet weak var statsLabel: UILabel! + var agoraKit: AgoraRtcEngineKit! + + private var unusedScreenNodes = [SCNNode]() + private var undisplayedUsers = [UInt]() + private var activeScreens = [UInt: SCNNode]() + private var renderer: ARVideoRenderer! + + // indicate if current instance has joined channel + var isJoined: Bool = false + var planarDetected: Bool = false { + didSet { + if(planarDetected) { + infoLabel.text = "Tap to place remote video canvas".localized + } else { + infoLabel.text = "Move Camera to find a planar\n(Shown as Red Rectangle)".localized + } + } + } + + override func viewDidLoad() { + super.viewDidLoad() + + //set AR Scene delegate + sceneView.delegate = self + sceneView.session.delegate = self + sceneView.showsStatistics = true + + // set up agora instance when view loadedlet config = AgoraRtcEngineConfig() + let config = AgoraRtcEngineConfig() + config.appId = KeyCenter.AppId + config.areaCode = GlobalSettings.shared.area + // setup log file path + let logConfig = AgoraLogConfig() + logConfig.level = .info + config.logConfig = logConfig + + agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self) + + if let isFirstFrame = configs["isFirstFrame"] as? Bool, isFirstFrame == true { + agoraKit.enableInstantMediaRendering() + agoraKit.startMediaRenderingTracing() + } + + // get channel name from configs + guard let channelName = configs["channelName"] as? String else {return} + + // make myself a broadcaster + agoraKit.setChannelProfile(.liveBroadcasting) + agoraKit.setClientRole(.broadcaster) + + // set AR video source as custom video source + renderer = ARVideoRenderer() + agoraKit.setVideoFrameDelegate(renderer) + // start AR Session + startARSession() + + + // Set audio route to speaker + agoraKit.setDefaultAudioRouteToSpeakerphone(true) + + // start joining channel + // 1. Users can only see each other after they join the + // same channel successfully using the same app id. + // 2. If app certificate is turned on at dashboard, token is needed + // when joining channel. The channel name and uid used to calculate + // the token has to match the ones used for channel join + NetworkManager.shared.generateToken(channelName: channelName) { token in + let result = self.agoraKit.joinChannel(byToken: token, channelId: channelName, info: nil, uid: 0, joinSuccess: nil) + if result != 0 { + // Usually happens with invalid parameters + // Error code description can be found at: + // en: https://docs.agora.io/en/Voice/API%20Reference/oc/Constants/AgoraErrorCode.html + // cn: https://docs.agora.io/cn/Voice/API%20Reference/oc/Constants/AgoraErrorCode.html + self.showAlert(title: "Error", message: "joinChannel call failed: \(result), please check your params") + } + } + } + + // start AR World tracking + func startARSession() { + guard ARWorldTrackingConfiguration.isSupported else { + showAlert(title: "ARKit is not available on this device.".localized, + message: "This app requires world tracking, which is available only on iOS devices with the A9 processor or later.".localized) + return + } + + let configuration = ARWorldTrackingConfiguration() + configuration.planeDetection = .horizontal + // remember to set this to false, or ARKit may conflict with Agora SDK + configuration.providesAudioData = false + + // start session + sceneView.session.run(configuration) + } + + // stop AR Tracking + func stopARSession() { + sceneView.session.pause() + } + + @IBAction func doSceneViewTapped(_ recognizer: UITapGestureRecognizer) { + if(!planarDetected) { + LogUtils.log(message: "Planar not yet found", level: .warning) + return + } + + let location = recognizer.location(in: sceneView) + + if let node = sceneView.hitTest(location, options: nil).first?.node { + removeNode(node) + } else if let result = sceneView.hitTest(location, types: .existingPlane).first { + addNode(withTransform: result.worldTransform) + } + } + + override func willMove(toParent parent: UIViewController?) { + if parent == nil { + // leave channel when exiting the view + if isJoined { + agoraKit.leaveChannel { (stats) -> Void in + LogUtils.log(message: "left channel, duration: \(stats.duration)", level: .info) + } + } + stopARSession() + } + } +} + +private extension ARKitMain { + func renderRemoteUser(uid: UInt, toNode node: SCNNode) { + renderer.renderNode = node + activeScreens[uid] = node + } + + func addNode(withTransform transform: matrix_float4x4) { + let scene = SCNScene(named: "AR.scnassets/displayer.scn")! + let rootNode = scene.rootNode + + rootNode.position = SCNVector3( + transform.columns.3.x, + transform.columns.3.y, + transform.columns.3.z + ) + rootNode.rotation = SCNVector4(0, 1, 0, sceneView.session.currentFrame!.camera.eulerAngles.y) + + sceneView.scene.rootNode.addChildNode(rootNode) + + let displayer = rootNode.childNode(withName: "displayer", recursively: false)! + let screen = displayer.childNode(withName: "screen", recursively: false)! + + if let undisplayedUid = undisplayedUsers.first { + undisplayedUsers.removeFirst() + renderRemoteUser(uid: undisplayedUid, toNode: screen) + } else { + unusedScreenNodes.append(screen) + } + } + + func removeNode(_ node: SCNNode) { + let rootNode: SCNNode + let screen: SCNNode + + if node.name == "screen", let parent = node.parent?.parent { + rootNode = parent + screen = node + } else if node.name == "displayer", let parent = node.parent { + rootNode = parent + screen = parent.childNode(withName: "screen", recursively: false)! + } else { + rootNode = node + screen = node + } + + rootNode.removeFromParentNode() + + if let index = unusedScreenNodes.firstIndex(where: {$0 == screen}) { + unusedScreenNodes.remove(at: index) + } + + if let (uid, _) = activeScreens.first(where: {$1 == screen}) { + activeScreens.removeValue(forKey: uid) + if let screenNode = unusedScreenNodes.first { + unusedScreenNodes.removeFirst() + renderRemoteUser(uid: uid, toNode: screenNode) + } else { + undisplayedUsers.insert(uid, at: 0) + } + } + } +} + +/// agora rtc engine delegate events +extension ARKitMain: AgoraRtcEngineDelegate { + /// callback when warning occured for agora sdk, warning can usually be ignored, still it's nice to check out + /// what is happening + /// Warning code description can be found at: + /// en: https://docs.agora.io/en/Voice/API%20Reference/oc/Constants/AgoraWarningCode.html + /// cn: https://docs.agora.io/cn/Voice/API%20Reference/oc/Constants/AgoraWarningCode.html + /// @param warningCode warning code of the problem + func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurWarning warningCode: AgoraWarningCode) { + LogUtils.log(message: "warning: \(warningCode.description)", level: .warning) + } + + /// callback when error occured for agora sdk, you are recommended to display the error descriptions on demand + /// to let user know something wrong is happening + /// Error code description can be found at: + /// en: https://docs.agora.io/en/Voice/API%20Reference/oc/Constants/AgoraErrorCode.html + /// cn: https://docs.agora.io/cn/Voice/API%20Reference/oc/Constants/AgoraErrorCode.html + /// @param errorCode error code of the problem + func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurError errorCode: AgoraErrorCode) { + LogUtils.log(message: "error: \(errorCode.rawValue)", level: .error) +// self.showAlert(title: "Error", message: "Error \(errorCode.description) occur") + } + + /// callback when the local user joins a specified channel. + /// @param channel + /// @param uid uid of local user + /// @param elapsed time elapse since current sdk instance join the channel in ms + func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) { + isJoined = true + LogUtils.log(message: "Join \(channel) with uid \(uid) elapsed \(elapsed)ms", level: .info) + } + + /// callback when a remote user is joinning the channel, note audience in live broadcast mode will NOT trigger this event + /// @param uid uid of remote joined user + /// @param elapsed time elapse since current sdk instance join the channel in ms + func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) { + LogUtils.log(message: "remote user join: \(uid) \(elapsed)ms", level: .info) + + if let screenNode = unusedScreenNodes.first { + unusedScreenNodes.removeFirst() + renderRemoteUser(uid: uid, toNode: screenNode) + } else { + undisplayedUsers.append(uid) + } + } + + /// callback when a remote user is leaving the channel, note audience in live broadcast mode will NOT trigger this event + /// @param uid uid of remote joined user + /// @param reason reason why this user left, note this event may be triggered when the remote user + /// become an audience in live broadcasting profile + func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) { + LogUtils.log(message: "remote user left: \(uid) reason \(reason)", level: .info) + + if let screenNode = activeScreens[uid] { +// agoraKit.setRemoteVideoRenderer(nil, forUserId: uid) + unusedScreenNodes.insert(screenNode, at: 0) + activeScreens[uid] = nil + } else if let index = undisplayedUsers.firstIndex(of: uid) { + undisplayedUsers.remove(at: index) + } + } + + func rtcEngine(_ engine: AgoraRtcEngineKit, videoRenderingTracingResultOfUid uid: UInt, currentEvent: AgoraMediaTraceEvent, tracingInfo: AgoraVideoRenderingTracingInfo) { + statsLabel.isHidden = tracingInfo.elapsedTime <= 0 + statsLabel.text = "firstFrameTime: \(tracingInfo.elapsedTime)" + } +} + + +extension ARKitMain: ARSCNViewDelegate { + func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { + guard let planeAnchor = anchor as? ARPlaneAnchor else { + return + } + + let plane = SCNBox(width: CGFloat(planeAnchor.extent.x), + height: CGFloat(planeAnchor.extent.y), + length: CGFloat(planeAnchor.extent.z), + chamferRadius: 0) + plane.firstMaterial?.diffuse.contents = UIColor.red + + let planeNode = SCNNode(geometry: plane) + node.addChildNode(planeNode) + planeNode.runAction(SCNAction.fadeOut(duration: 3)) + + //found planar + if(!planarDetected) { + DispatchQueue.main.async {[weak self] in + guard let weakSelf = self else { + return + } + weakSelf.planarDetected = true + } + } + } +} + +extension ARKitMain: ARSessionDelegate { + func session(_ session: ARSession, didUpdate frame: ARFrame) { + // send captured image to remote device + renderer.pixelBuffer = frame.capturedImage + } +} diff --git a/iOS/APIExample/APIExample/Examples/Advanced/ARKit/Base.lproj/ARKit.storyboard b/iOS/APIExample/APIExample/Examples/Advanced/ARKit/Base.lproj/ARKit.storyboard new file mode 100644 index 000000000..119a080e2 --- /dev/null +++ b/iOS/APIExample/APIExample/Examples/Advanced/ARKit/Base.lproj/ARKit.storyboard @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/APIExample/APIExample/Examples/Advanced/ARKit/zh-Hans.lproj/ARKit.strings b/iOS/APIExample/APIExample/Examples/Advanced/ARKit/zh-Hans.lproj/ARKit.strings new file mode 100644 index 000000000..85fd0da38 --- /dev/null +++ b/iOS/APIExample/APIExample/Examples/Advanced/ARKit/zh-Hans.lproj/ARKit.strings @@ -0,0 +1,11 @@ + +/* Class = "UITextField"; placeholder = "Enter channel name"; ObjectID = "GWc-L5-fZV"; */ +"GWc-L5-fZV.placeholder" = "输入频道名"; + +/* Class = "UILabel"; text = "Move Camera to find a planar 
(Shown as Red Rectangle)"; ObjectID = "bEC-x6-7dT"; */ +"bEC-x6-7dT.text" = "移动相机以找到一个平面 
(以红色方块显示)"; + +/* Class = "UIButton"; normalTitle = "Join"; ObjectID = "kbN-ZR-nNn"; */ +"kbN-ZR-nNn.normalTitle" = "加入频道"; + +"2xt-aD-ydA.text" = "首帧出图"; diff --git a/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoRender/CustomVideoRender.swift b/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoRender/CustomVideoRender.swift index 25c823792..9b9ccd108 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoRender/CustomVideoRender.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoRender/CustomVideoRender.swift @@ -72,10 +72,14 @@ class CustomVideoRenderMain: BaseViewController { // enable video module and set up video encoding configs agoraKit.enableVideo() agoraKit.enableAudio() - agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: AgoraVideoDimension640x360, - frameRate: .fps30, + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, bitrate: AgoraVideoBitrateStandard, - orientationMode: .adaptative, mirrorMode: .auto)) + orientationMode: orientation, + mirrorMode: .auto)) diff --git a/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoSourcePush/CustomVideoSourcePush.swift b/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoSourcePush/CustomVideoSourcePush.swift index be6c7a8d9..3b207fad8 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoSourcePush/CustomVideoSourcePush.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoSourcePush/CustomVideoSourcePush.swift @@ -98,15 +98,14 @@ class CustomVideoSourcePushMain: BaseViewController { customCamera?.startSource() agoraKit.setExternalVideoSource(true, useTexture: true, sourceType: .videoFrame) - agoraKit.setVideoEncoderConfiguration( - AgoraVideoEncoderConfiguration( - size: AgoraVideoDimension640x360, - frameRate: .fps30, - bitrate: AgoraVideoBitrateStandard, - orientationMode: .adaptative, - mirrorMode: .auto - ) - ) + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, + bitrate: AgoraVideoBitrateStandard, + orientationMode: orientation, + mirrorMode: .auto)) // Set audio route to speaker agoraKit.setDefaultAudioRouteToSpeakerphone(true) diff --git a/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoSourcePushMulti/CustomVideoSourcePushMulti.swift b/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoSourcePushMulti/CustomVideoSourcePushMulti.swift index 16bc3783e..e51860c29 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoSourcePushMulti/CustomVideoSourcePushMulti.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoSourcePushMulti/CustomVideoSourcePushMulti.swift @@ -94,15 +94,14 @@ class CustomVideoSourcePushMultiMain: BaseViewController { customCamera?.startSource() agoraKit.setExternalVideoSource(true, useTexture: true, sourceType: .videoFrame) - agoraKit.setVideoEncoderConfiguration( - AgoraVideoEncoderConfiguration( - size: AgoraVideoDimension640x360, - frameRate: .fps30, - bitrate: AgoraVideoBitrateStandard, - orientationMode: .adaptative, - mirrorMode: .auto - ) - ) + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, + bitrate: AgoraVideoBitrateStandard, + orientationMode: orientation, + mirrorMode: .auto)) // Set audio route to speaker agoraKit.setDefaultAudioRouteToSpeakerphone(true) diff --git a/iOS/APIExample/APIExample/Examples/Advanced/JoinMultiChannel/JoinMultiChannel.swift b/iOS/APIExample/APIExample/Examples/Advanced/JoinMultiChannel/JoinMultiChannel.swift index aded090a8..b30f6a08b 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/JoinMultiChannel/JoinMultiChannel.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/JoinMultiChannel/JoinMultiChannel.swift @@ -88,10 +88,14 @@ class JoinMultiChannelMain: BaseViewController { // enable video module and set up video encoding configs agoraKit.enableVideo() agoraKit.enableAudio() - agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: AgoraVideoDimension640x360, - frameRate: .fps30, - bitrate: AgoraVideoBitrateStandard, - orientationMode: .adaptative, mirrorMode: .auto)) + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, + bitrate: AgoraVideoBitrateStandard, + orientationMode: orientation, + mirrorMode: .auto)) // Set audio route to speaker agoraKit.setDefaultAudioRouteToSpeakerphone(true) diff --git a/iOS/APIExample/APIExample/Examples/Advanced/LiveStreaming/Base.lproj/LiveStreaming.storyboard b/iOS/APIExample/APIExample/Examples/Advanced/LiveStreaming/Base.lproj/LiveStreaming.storyboard index 90847e072..e8ebb7bb8 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/LiveStreaming/Base.lproj/LiveStreaming.storyboard +++ b/iOS/APIExample/APIExample/Examples/Advanced/LiveStreaming/Base.lproj/LiveStreaming.storyboard @@ -19,15 +19,30 @@ - + - + + + + + + + + + + + - + @@ -300,7 +319,7 @@ - + diff --git a/iOS/APIExample/APIExample/Examples/Advanced/LiveStreaming/LiveStreaming.swift b/iOS/APIExample/APIExample/Examples/Advanced/LiveStreaming/LiveStreaming.swift index 0ddab97d8..6fefe4933 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/LiveStreaming/LiveStreaming.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/LiveStreaming/LiveStreaming.swift @@ -15,6 +15,7 @@ class LiveStreamingEntry : UIViewController @IBOutlet weak var channelTextField: UITextField! let identifier = "LiveStreaming" var role:AgoraClientRole = .broadcaster + private var isFirstFrame: Bool = false override func viewDidLoad() { super.viewDidLoad() @@ -27,6 +28,26 @@ class LiveStreamingEntry : UIViewController }) } + @IBAction func doOptimizeFirstFrameSwitch(_ sender: UISwitch) { + if sender.isOn { + let alertVC = UIAlertController(title: "After this function is enabled, it cannot be disabled and takes effect only when both the primary and secondary ends are enabled".localized, + message: nil, + preferredStyle: .alert) + + let ok = UIAlertAction(title: "Sure".localized, style: .default) { _ in + self.isFirstFrame = true + } + let cancel = UIAlertAction(title: "Cancel".localized, style: .cancel) { _ in + self.isFirstFrame = false + } + + alertVC.addAction(ok) + alertVC.addAction(cancel) + present(alertVC, animated: true, completion: nil) + } else { + isFirstFrame = false + } + } @IBAction func doJoinPressed(sender: UIButton) { guard let _ = channelTextField.text else {return} @@ -47,7 +68,7 @@ class LiveStreamingEntry : UIViewController // create new view controller every time to ensure we get a clean vc guard let newViewController = storyBoard.instantiateViewController(withIdentifier: identifier) as? BaseViewController else {return} newViewController.title = channelName - newViewController.configs = ["channelName":channelName, "role":self.role] + newViewController.configs = ["channelName":channelName, "role":self.role, "isFirstFrame": isFirstFrame] navigationController?.pushViewController(newViewController, animated: true) } } @@ -116,6 +137,11 @@ class LiveStreamingMain: BaseViewController { Util.configPrivatization(agoraKit: agoraKit) agoraKit.setLogFile(LogUtils.sdkLogPath()) + if let isFirstFrame = configs["isFirstFrame"] as? Bool, isFirstFrame == true { + agoraKit.enableInstantMediaRendering() + agoraKit.startMediaRenderingTracing() + } + // get channel name from configs guard let channelName = configs["channelName"] as? String, let clientRole = configs["role"] as? AgoraClientRole else {return} @@ -417,4 +443,8 @@ extension LiveStreamingMain: AgoraRtcEngineDelegate { func rtcEngine(_ engine: AgoraRtcEngineKit, remoteAudioStats stats: AgoraRtcRemoteAudioStats) { backgroundVideo.statsInfo?.updateAudioStats(stats) } + + func rtcEngine(_ engine: AgoraRtcEngineKit, videoRenderingTracingResultOfUid uid: UInt, currentEvent: AgoraMediaTraceEvent, tracingInfo: AgoraVideoRenderingTracingInfo) { + backgroundVideo.statsInfo?.updateFirstFrameInfo(tracingInfo) + } } diff --git a/iOS/APIExample/APIExample/Examples/Advanced/LiveStreaming/zh-Hans.lproj/LiveStreaming.strings b/iOS/APIExample/APIExample/Examples/Advanced/LiveStreaming/zh-Hans.lproj/LiveStreaming.strings index 5ac162e3c..d85b25daa 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/LiveStreaming/zh-Hans.lproj/LiveStreaming.strings +++ b/iOS/APIExample/APIExample/Examples/Advanced/LiveStreaming/zh-Hans.lproj/LiveStreaming.strings @@ -1,4 +1,3 @@ - /* Class = "UITextField"; placeholder = "Enter channel name"; ObjectID = "GWc-L5-fZV"; */ "GWc-L5-fZV.placeholder" = "输入频道名"; @@ -11,3 +10,5 @@ /* Class = "UIButton"; normalTitle = "Join"; ObjectID = "kbN-ZR-nNn"; */ "kbN-ZR-nNn.normalTitle" = "加入频道"; "w4q-aT-JBc.normalTitle" = "截图"; + +"ohV-am-Acd.text" = "首帧出图"; diff --git a/iOS/APIExample/APIExample/Examples/Advanced/MediaChannelRelay/MediaChannelRelay.swift b/iOS/APIExample/APIExample/Examples/Advanced/MediaChannelRelay/MediaChannelRelay.swift index 02d1b59bc..a7b81bdb4 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/MediaChannelRelay/MediaChannelRelay.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/MediaChannelRelay/MediaChannelRelay.swift @@ -85,10 +85,15 @@ class MediaChannelRelayMain: BaseViewController { // enable video module and set up video encoding configs agoraKit.enableVideo() agoraKit.enableAudio() - agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: AgoraVideoDimension640x360, - frameRate: .fps30, + + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, bitrate: AgoraVideoBitrateStandard, - orientationMode: .adaptative, mirrorMode: .auto)) + orientationMode: orientation, + mirrorMode: .auto)) // set up local video to render your local camera preview let videoCanvas = AgoraRtcVideoCanvas() diff --git a/iOS/APIExample/APIExample/Examples/Advanced/MediaPlayer/MediaPlayer.swift b/iOS/APIExample/APIExample/Examples/Advanced/MediaPlayer/MediaPlayer.swift index 9ebc0795f..976115122 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/MediaPlayer/MediaPlayer.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/MediaPlayer/MediaPlayer.swift @@ -119,10 +119,14 @@ class MediaPlayerMain: BaseViewController, UITextFieldDelegate { // enable video module and set up video encoding configs agoraKit.enableVideo() agoraKit.enableAudio() - agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: AgoraVideoDimension960x720, - frameRate: .fps30, + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, bitrate: AgoraVideoBitrateStandard, - orientationMode: .adaptative, mirrorMode: .auto)) + orientationMode: orientation, + mirrorMode: .auto)) // get channel name from configs guard let channelName = configs["channelName"] as? String else { return } diff --git a/iOS/APIExample/APIExample/Examples/Advanced/MutliCamera/MutliCamera.swift b/iOS/APIExample/APIExample/Examples/Advanced/MutliCamera/MutliCamera.swift index 06e90dc1b..cd00414d8 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/MutliCamera/MutliCamera.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/MutliCamera/MutliCamera.swift @@ -35,17 +35,7 @@ class MutliCameraEntry : UIViewController class MutliCameraMain: BaseViewController { var localVideo = Bundle.loadVideoView(type: .local, audioOnly: false) - lazy var remoteVideos: [VideoView] = [] { - didSet { - let videoViews = [localVideo] + remoteVideos - container.layoutStream(views: videoViews) - let height = videoViews.count > 2 ? SCREENSIZE.height - 100 : 250 - containerHeightCons.constant = height - UIView.animate(withDuration: 0.25) { - self.view.layoutIfNeeded() - } - } - } + var localVideo_2 = Bundle.loadVideoView(type: .local, audioOnly: false) lazy var uid: UInt = UInt.random(in: 1...9999) lazy var mutliCameraUid: UInt = UInt.random(in: 10000...9999999) @@ -62,8 +52,8 @@ class MutliCameraMain: BaseViewController { super.viewDidLoad() // layout render view localVideo.setPlaceholder(text: "Local Host".localized) -// let videoViews = [localVideo] + remoteVideos - container.layoutStream(views: [localVideo]) + localVideo_2.setPlaceholder(text: "第二路摄像头".localized) + container.layoutStream(views: [localVideo, localVideo_2]) // set up agora instance when view loaded let config = AgoraRtcEngineConfig() @@ -85,6 +75,15 @@ class MutliCameraMain: BaseViewController { agoraKit.enableVideo() agoraKit.enableAudio() + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, + bitrate: AgoraVideoBitrateStandard, + orientationMode: orientation, + mirrorMode: .auto)) + // open Multi Camera let capturerConfig = AgoraCameraCapturerConfiguration() capturerConfig.cameraDirection = .rear @@ -135,17 +134,36 @@ class MutliCameraMain: BaseViewController { connection.channelId = channelName connection.localUid = mutliCameraUid if isOpenCamera { - agoraKit.startSecondaryCameraCapture() - let option = AgoraRtcChannelMediaOptions() - option.publishSecondaryCameraTrack = true - option.publishMicrophoneTrack = true - option.clientRoleType = .broadcaster - NetworkManager.shared.generateToken(channelName: channelName, uid: mutliCameraUid) { token in - self.agoraKit.joinChannelEx(byToken: token, connection: connection, delegate: self, mediaOptions: option, joinSuccess: nil) - } + let videoCanvas = AgoraRtcVideoCanvas() + videoCanvas.uid = mutliCameraUid + videoCanvas.view = localVideo_2.videoView + videoCanvas.renderMode = .hidden + videoCanvas.sourceType = .cameraSecondary + videoCanvas.mirrorMode = .disabled + agoraKit.setupLocalVideo(videoCanvas) + + agoraKit.startSecondaryCameraCapture() + let option = AgoraRtcChannelMediaOptions() + option.publishSecondaryCameraTrack = true + option.publishMicrophoneTrack = true + option.clientRoleType = .broadcaster + option.autoSubscribeAudio = false + option.autoSubscribeVideo = false + NetworkManager.shared.generateToken(channelName: channelName, uid: mutliCameraUid) { token in + self.agoraKit.joinChannelEx(byToken: token, connection: connection, delegate: self, mediaOptions: option, joinSuccess: nil) + self.agoraKit.muteRemoteAudioStream(self.mutliCameraUid, mute: true) + self.agoraKit.muteRemoteVideoStream(self.mutliCameraUid, mute: true) + } } else { - agoraKit.stopSecondaryCameraCapture() - agoraKit.leaveChannelEx(connection, leaveChannelBlock: nil) + let videoCanvas = AgoraRtcVideoCanvas() + videoCanvas.uid = mutliCameraUid + videoCanvas.view = nil + videoCanvas.renderMode = .hidden + videoCanvas.sourceType = .cameraSecondary + agoraKit.setupLocalVideo(videoCanvas) + + agoraKit.stopSecondaryCameraCapture() + agoraKit.leaveChannelEx(connection, leaveChannelBlock: nil) } } override func viewDidDisappear(_ animated: Bool) { @@ -203,25 +221,6 @@ extension MutliCameraMain: AgoraRtcEngineDelegate { /// @param elapsed time elapse since current sdk instance join the channel in ms func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) { LogUtils.log(message: "remote user join: \(uid) \(elapsed)ms", level: .info) - - guard uid != self.uid else { return } - // Only one remote video view is available for this - // tutorial. Here we check if there exists a surface - // view tagged as this uid. - guard let channelName = configs["channelName"] as? String else {return} - let connection = AgoraRtcConnection(channelId: channelName, localUid: Int(self.uid)) - let videoCanvas = AgoraRtcVideoCanvas() - videoCanvas.uid = uid - // the view to be binded - var videoView = remoteVideos.first(where: { $0.uid == uid }) - if videoView == nil { - videoView = Bundle.loadVideoView(type: .remote, audioOnly: false) - remoteVideos.append(videoView ?? VideoView()) - } - videoView?.uid = uid - videoCanvas.view = videoView?.videoView - videoCanvas.renderMode = .hidden - agoraKit.setupRemoteVideoEx(videoCanvas, connection: connection) } /// callback when a remote user is leaving the channel, note audience in live broadcast mode will NOT trigger this event @@ -240,8 +239,6 @@ extension MutliCameraMain: AgoraRtcEngineDelegate { videoCanvas.view = nil videoCanvas.renderMode = .hidden agoraKit.setupRemoteVideo(videoCanvas) - guard let index = remoteVideos.firstIndex(where: { $0.uid == uid }) else { return } - remoteVideos.remove(at: index) } /// Reports the statistics of the current call. The SDK triggers this callback once every two seconds after the user joins the channel. @@ -255,16 +252,4 @@ extension MutliCameraMain: AgoraRtcEngineDelegate { func rtcEngine(_ engine: AgoraRtcEngineKit, localAudioStats stats: AgoraRtcLocalAudioStats) { localVideo.statsInfo?.updateLocalAudioStats(stats) } - - /// Reports the statistics of the video stream from each remote user/host. - /// @param stats stats struct - func rtcEngine(_ engine: AgoraRtcEngineKit, remoteVideoStats stats: AgoraRtcRemoteVideoStats) { - remoteVideos.first?.statsInfo?.updateVideoStats(stats) - } - - /// Reports the statistics of the audio stream from each remote user/host. - /// @param stats stats struct for current call statistics - func rtcEngine(_ engine: AgoraRtcEngineKit, remoteAudioStats stats: AgoraRtcRemoteAudioStats) { - remoteVideos.first?.statsInfo?.updateAudioStats(stats) - } } diff --git a/iOS/APIExample/APIExample/Examples/Advanced/RTMPStreaming/RTMPStreaming.swift b/iOS/APIExample/APIExample/Examples/Advanced/RTMPStreaming/RTMPStreaming.swift index 47c78f64d..dca37e338 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/RTMPStreaming/RTMPStreaming.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/RTMPStreaming/RTMPStreaming.swift @@ -97,10 +97,14 @@ class RTMPStreamingMain: BaseViewController { // enable video module and set up video encoding configs agoraKit.enableVideo() agoraKit.enableAudio() - agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: AgoraVideoDimension640x480, - frameRate: .fps30, + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, bitrate: AgoraVideoBitrateStandard, - orientationMode: .adaptative, mirrorMode: .auto)) + orientationMode: orientation, + mirrorMode: .auto)) // set up local video to render your local camera preview let videoCanvas = AgoraRtcVideoCanvas() diff --git a/iOS/APIExample/APIExample/Examples/Advanced/ScreenShare/ScreenShare.swift b/iOS/APIExample/APIExample/Examples/Advanced/ScreenShare/ScreenShare.swift index 48a883212..7ed59d5e7 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/ScreenShare/ScreenShare.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/ScreenShare/ScreenShare.swift @@ -100,10 +100,14 @@ class ScreenShareMain: BaseViewController { // enable video module and set up video encoding configs agoraKit.enableVideo() agoraKit.enableAudio() - agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: AgoraVideoDimension640x360, - frameRate: .fps30, + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, bitrate: AgoraVideoBitrateStandard, - orientationMode: .adaptative, mirrorMode: .auto)) + orientationMode: orientation, + mirrorMode: .auto)) // set up local video to render your local camera preview let videoCanvas = AgoraRtcVideoCanvas() videoCanvas.uid = 0 diff --git a/iOS/APIExample/APIExample/Examples/Advanced/SimpleFilter/SimpleFilter.swift b/iOS/APIExample/APIExample/Examples/Advanced/SimpleFilter/SimpleFilter.swift index 990fca3ef..4e748b26c 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/SimpleFilter/SimpleFilter.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/SimpleFilter/SimpleFilter.swift @@ -82,6 +82,15 @@ class SimpleFilterMain: BaseViewController { agoraKit.enableVideo() agoraKit.enableAudio() + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, + bitrate: AgoraVideoBitrateStandard, + orientationMode: orientation, + mirrorMode: .auto)) + agoraKit.enableExtension(withVendor: SimpleFilterManager.vendorName(), extension: VIDEO_FILTER_NAME, enabled: true) agoraKit.enableExtension(withVendor: SimpleFilterManager.vendorName(), extension: AUDIO_FILTER_NAME, enabled: true) agoraKit.setExtensionPropertyWithVendor(SimpleFilterManager.vendorName(), extension: VIDEO_FILTER_NAME, key: "grey", value: "1") diff --git a/iOS/APIExample/APIExample/Examples/Advanced/SpatialAudio/SpatialAudio.swift b/iOS/APIExample/APIExample/Examples/Advanced/SpatialAudio/SpatialAudio.swift index 26d408f27..3ee89b800 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/SpatialAudio/SpatialAudio.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/SpatialAudio/SpatialAudio.swift @@ -39,6 +39,7 @@ class SpatialAudioMain: BaseViewController { @IBOutlet weak var remoteUserButton1: UIButton! @IBOutlet weak var remoteUserButton2: UIButton! + private var isJoined: Bool = false private lazy var actionView1 = SpatialAudioActionSheet() private lazy var actionView2 = SpatialAudioActionSheet() var agoraKit: AgoraRtcEngineKit! @@ -76,14 +77,17 @@ class SpatialAudioMain: BaseViewController { localSpatial.setAudioRecvRange(Float(SCREENSIZE.height)) localSpatial.setMaxAudioRecvCount(2) localSpatial.setDistanceUnit(1) - + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + guard isJoined == false else { return } mediaPlayer1 = agoraKit.createMediaPlayer(with: self) mediaPlayer1.setLoopCount(10000) mediaPlayer1.open("https://webdemo.agora.io/audiomixing.mp3", startPos: 0) localSpatial.updatePlayerPositionInfo(Int(mediaPlayer1.getMediaPlayerId()), positionInfo: getPlayerPostion(view: voiceButton1)) localSpatial.setPlayerAttenuation(0.2, playerId: UInt(mediaPlayer1.getMediaPlayerId()), forceSet: false) - mediaPlayer2 = agoraKit.createMediaPlayer(with: self) mediaPlayer2.setLoopCount(10000) mediaPlayer2.open("https://webdemo.agora.io/dang.mp3", startPos: 0) @@ -95,12 +99,12 @@ class SpatialAudioMain: BaseViewController { joinChannel() } - override func willMove(toParent parent: UIViewController?) { - if parent == nil { - agoraKit = nil - AgoraLocalSpatialAudioKit.destroy() - AgoraRtcEngineKit.destroy() - } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + agoraKit = nil + AgoraLocalSpatialAudioKit.destroy() + AgoraRtcEngineKit.destroy() + isJoined = false } private func joinChannel() { @@ -113,6 +117,7 @@ class SpatialAudioMain: BaseViewController { if result != 0 { print("join channel fail") } + self.isJoined = true }) } @@ -209,6 +214,7 @@ class SpatialAudioMain: BaseViewController { private func getPlayerPostion(view: UIView) -> AgoraRemoteVoicePositionInfo { let position = getViewCenterPostion(view: view) + print("player postion == \(position)") let positionInfo = AgoraRemoteVoicePositionInfo() positionInfo.position = position positionInfo.forward = forward diff --git a/iOS/APIExample/APIExample/Examples/Advanced/StreamEncryption/StreamEncryption.swift b/iOS/APIExample/APIExample/Examples/Advanced/StreamEncryption/StreamEncryption.swift index 5096f00a6..5f4486259 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/StreamEncryption/StreamEncryption.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/StreamEncryption/StreamEncryption.swift @@ -121,10 +121,14 @@ class StreamEncryptionMain: BaseViewController { // enable video module and set up video encoding configs agoraKit.enableVideo() agoraKit.enableAudio() - agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: AgoraVideoDimension640x360, - frameRate: .fps30, + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, bitrate: AgoraVideoBitrateStandard, - orientationMode: .adaptative, mirrorMode: .auto)) + orientationMode: orientation, + mirrorMode: .auto)) // set up local video to render your local camera preview let videoCanvas = AgoraRtcVideoCanvas() diff --git a/iOS/APIExample/APIExample/Examples/Advanced/VideoChat/VideoChat.swift b/iOS/APIExample/APIExample/Examples/Advanced/VideoChat/VideoChat.swift index 84842cfac..a9fd84226 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/VideoChat/VideoChat.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/VideoChat/VideoChat.swift @@ -17,7 +17,7 @@ class VideoChatEntry: UIViewController { @IBOutlet var resolutionBtn: UIButton! @IBOutlet var fpsBtn: UIButton! @IBOutlet var orientationBtn: UIButton! - var width:Int = 640, height:Int = 360, orientation:AgoraVideoOutputOrientationMode = .adaptative, fps = 30 + var width:Int = 960, height:Int = 540, orientation:AgoraVideoOutputOrientationMode = .adaptative, fps = 15 override func viewDidLoad() { super.viewDidLoad() @@ -53,7 +53,7 @@ class VideoChatEntry: UIViewController { alert.addAction(getResolutionAction(width: 90, height: 90)) alert.addAction(getResolutionAction(width: 160, height: 120)) alert.addAction(getResolutionAction(width: 320, height: 240)) - alert.addAction(getResolutionAction(width: 640, height: 360)) + alert.addAction(getResolutionAction(width: 960, height: 540)) alert.addAction(getResolutionAction(width: 1280, height: 720)) alert.addAction(getResolutionAction(width: 1920, height: 1080)) alert.addCancelAction() diff --git a/iOS/APIExample/APIExample/Examples/Advanced/VideoMetadata/VideoMetadata.swift b/iOS/APIExample/APIExample/Examples/Advanced/VideoMetadata/VideoMetadata.swift index 6af13638e..6f482b2cb 100644 --- a/iOS/APIExample/APIExample/Examples/Advanced/VideoMetadata/VideoMetadata.swift +++ b/iOS/APIExample/APIExample/Examples/Advanced/VideoMetadata/VideoMetadata.swift @@ -85,10 +85,14 @@ class VideoMetadataMain: BaseViewController { // enable video module and set up video encoding configs agoraKit.enableVideo() agoraKit.enableAudio() - agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: AgoraVideoDimension640x360, - frameRate: .fps30, + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, bitrate: AgoraVideoBitrateStandard, - orientationMode: .adaptative, mirrorMode: .auto)) + orientationMode: orientation, + mirrorMode: .auto)) // set up local video to render your local camera preview let videoCanvas = AgoraRtcVideoCanvas() diff --git a/iOS/APIExample/APIExample/Examples/Basic/JoinChannelAudio/JoinChannelAudio.swift b/iOS/APIExample/APIExample/Examples/Basic/JoinChannelAudio/JoinChannelAudio.swift index 8659e3aa3..52c563b9d 100644 --- a/iOS/APIExample/APIExample/Examples/Basic/JoinChannelAudio/JoinChannelAudio.swift +++ b/iOS/APIExample/APIExample/Examples/Basic/JoinChannelAudio/JoinChannelAudio.swift @@ -118,6 +118,15 @@ class JoinChannelAudioMain: BaseViewController { agoraKit.enableAudio() + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, + bitrate: AgoraVideoBitrateStandard, + orientationMode: orientation, + mirrorMode: .auto)) + // set audio profile agoraKit.setAudioProfile(audioProfile) diff --git a/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo(Token)/JoinChannelVideoToken.swift b/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo(Token)/JoinChannelVideoToken.swift index b05890b74..d384c4fb1 100644 --- a/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo(Token)/JoinChannelVideoToken.swift +++ b/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo(Token)/JoinChannelVideoToken.swift @@ -130,6 +130,15 @@ class JoinChannelVideoToken: BaseViewController { // enable video module and set up video encoding configs agoraKit.enableVideo() agoraKit.enableAudio() + + let resolution = (GlobalSettings.shared.getSetting(key: "resolution")?.selectedOption().value as? CGSize) ?? .zero + let fps = (GlobalSettings.shared.getSetting(key: "fps")?.selectedOption().value as? AgoraVideoFrameRate) ?? .fps15 + let orientation = (GlobalSettings.shared.getSetting(key: "orientation")?.selectedOption().value as? AgoraVideoOutputOrientationMode) ?? .fixedPortrait + agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, + frameRate: fps, + bitrate: AgoraVideoBitrateStandard, + orientationMode: orientation, + mirrorMode: .auto)) // set up local video to render your local camera preview let videoCanvas = AgoraRtcVideoCanvas() diff --git a/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo/JoinChannelVideo.swift b/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo/JoinChannelVideo.swift index c5a1a4029..cba6062a8 100644 --- a/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo/JoinChannelVideo.swift +++ b/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo/JoinChannelVideo.swift @@ -17,7 +17,7 @@ class JoinChannelVideoEntry : UIViewController @IBOutlet var resolutionBtn: UIButton! @IBOutlet var fpsBtn: UIButton! @IBOutlet var orientationBtn: UIButton! - var width:Int = 640, height:Int = 360, orientation:AgoraVideoOutputOrientationMode = .adaptative, fps = 30 + var width:Int = 960, height:Int = 540, orientation:AgoraVideoOutputOrientationMode = .adaptative, fps = 15 override func viewDidLoad() { @@ -52,7 +52,7 @@ class JoinChannelVideoEntry : UIViewController alert.addAction(getResolutionAction(width: 90, height: 90)) alert.addAction(getResolutionAction(width: 160, height: 120)) alert.addAction(getResolutionAction(width: 320, height: 240)) - alert.addAction(getResolutionAction(width: 640, height: 360)) + alert.addAction(getResolutionAction(width: 960, height: 540)) alert.addAction(getResolutionAction(width: 1280, height: 720)) alert.addCancelAction() present(alert, animated: true, completion: nil) @@ -132,7 +132,7 @@ class JoinChannelVideoMain: BaseViewController { agoraKit.enableVideo() agoraKit.enableAudio() agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: resolution, - frameRate: AgoraVideoFrameRate(rawValue: fps) ?? .fps30, + frameRate: AgoraVideoFrameRate(rawValue: fps) ?? .fps15, bitrate: AgoraVideoBitrateStandard, orientationMode: orientation, mirrorMode: .auto)) diff --git a/iOS/APIExample/APIExample/Info.plist b/iOS/APIExample/APIExample/Info.plist index 3deee0512..f356acca7 100644 --- a/iOS/APIExample/APIExample/Info.plist +++ b/iOS/APIExample/APIExample/Info.plist @@ -2,6 +2,8 @@ + UIFileSharingEnabled + Application Supports itunes BGTaskSchedulerPermittedIdentifiers diff --git a/iOS/APIExample/APIExample/ViewController.swift b/iOS/APIExample/APIExample/ViewController.swift index 4fd701a41..86011f514 100644 --- a/iOS/APIExample/APIExample/ViewController.swift +++ b/iOS/APIExample/APIExample/ViewController.swift @@ -61,7 +61,8 @@ class ViewController: AGViewController { MenuItem(name: "Content Inspect".localized, storyboard: "ContentInspect", controller: ""), MenuItem(name: "Mutli Camera(iOS13.0+)".localized, storyboard: "MutliCamera", controller: "MutliCamera"), MenuItem(name: "Ktv copyright music".localized, entry: "KtvCopyrightMusic", storyboard: "KtvCopyrightMusic", controller: "KtvCopyrightMusic"), - MenuItem(name: "Third Beautify".localized, storyboard: "ThirdBeautify", controller: "") + MenuItem(name: "Third Beautify".localized, storyboard: "ThirdBeautify", controller: ""), + MenuItem(name: "ARKit".localized, storyboard: "ARKit", controller: ""), ]), ] override func viewDidLoad() { diff --git a/iOS/APIExample/APIExample/zh-Hans.lproj/Localizable.strings b/iOS/APIExample/APIExample/zh-Hans.lproj/Localizable.strings index 2059e0dba..2961855f6 100644 --- a/iOS/APIExample/APIExample/zh-Hans.lproj/Localizable.strings +++ b/iOS/APIExample/APIExample/zh-Hans.lproj/Localizable.strings @@ -64,6 +64,7 @@ "Chat Room Entertainment" = "游戏开黑"; "Show Room" = "秀场"; "Cancel" = "取消"; +"Sure" = "确定"; "Stop" = "停止"; "Off" = "原声"; "FemaleFresh" = "语聊美声: 清新(女)"; @@ -146,3 +147,9 @@ "license authorization failed, please check whether the license file is correct" = "license授权失败, 请检查license文件是否正确"; "Quick input APPID and Token methods" = "快捷输入APPID和Token方法"; "I: the mobile phone and Mac log in to the same Apple account. After copying the Mac, it will automatically synchronize other terminals with the same account. The mobile phone can directly click the input box to paste.\n\n II: use https://cl1p.net/ online clipboard:\n\n1.Enter in a URL that starts with cl1p.net. Example cl1p.net/uqztgjnqcalmd\n\n2.Paste in anything you want.\n\n3.On another computer enter the same URL and get your stuff." = "I: 如果手机和Mac登录的同一个Apple账号, Mac复制后会自动同步到其它相同账号终端, 手机可直接点击输入框粘贴即可.\n\n II: 使用https://cl1p.net/在线剪切板:\n\n1.输入以cl1p.net开头的URL。示例cl1p.net/uqztgjnqcalmd。\n\n2.粘贴任何你想要的东西。\n\n3.在另一台计算机上,输入相同的URL并获取您的东西。"; + +"After this function is enabled, it cannot be disabled and takes effect only when both the primary and secondary ends are enabled" = "开启后无法关闭,并且需要主辅两端都开启时才生效"; +"ARKit is not available on this device." = "当前设备不支持ARKit"; +"This app requires world tracking, which is available only on iOS devices with the A9 processor or later." = "AR功能仅在内置A9处理器后的iOS机型支持"; +"Move Camera to find a planar\n(Shown as Red Rectangle)" = "移动相机以找到一个平面\n(以红色方块显示)"; +"Tap to place remote video canvas" = "点击屏幕以放置视频画布"; diff --git a/iOS/APIExample/Podfile b/iOS/APIExample/Podfile index fc4a56add..19bc7cb1f 100644 --- a/iOS/APIExample/Podfile +++ b/iOS/APIExample/Podfile @@ -8,7 +8,7 @@ target 'APIExample' do pod 'Floaty', '~> 4.2.0' pod 'AGEVideoLayout', '~> 1.0.2' pod 'CocoaAsyncSocket', '7.6.5' - pod 'AgoraRtcEngine_iOS', '4.1.0' + pod 'AgoraRtcEngine_iOS', '4.1.1' # pod 'sdk', :path => 'sdk.podspec' # pod 'senseLib', :path => 'sense.podspec' # pod 'bytedEffect', :path => 'bytedEffect.podspec' @@ -18,11 +18,11 @@ end target 'Agora-ScreenShare-Extension' do use_frameworks! # pod 'sdk', :path => 'sdk.podspec' - pod 'AgoraRtcEngine_iOS', '4.1.0' + pod 'AgoraRtcEngine_iOS', '4.1.1' end target 'SimpleFilter' do use_frameworks! # pod 'sdk', :path => 'sdk.podspec' - pod 'AgoraRtcEngine_iOS', '4.1.0' + pod 'AgoraRtcEngine_iOS', '4.1.1' end diff --git a/macOS/APIExample/Common/Configs.swift b/macOS/APIExample/Common/Configs.swift index 9f3a53594..61f458f0f 100644 --- a/macOS/APIExample/Common/Configs.swift +++ b/macOS/APIExample/Common/Configs.swift @@ -31,14 +31,14 @@ struct Layout { class Configs { static var defaultProxySettingIdx: Int = 1 - static var defaultResolutionIdx: Int = 2 + static var defaultResolutionIdx: Int = 1 static var Resolutions:[Resolution] = [ Resolution(width: 320, height: 240), - Resolution(width: 640, height: 480), + Resolution(width: 960, height: 540), Resolution(width: 960, height: 720), Resolution(width: 1920, height: 1080) ] - static var defaultFpsIdx: Int = 1 + static var defaultFpsIdx: Int = 0 static var Fps:[Int] = [ 15, 30 diff --git a/macOS/APIExample/Common/StatisticsInfo.swift b/macOS/APIExample/Common/StatisticsInfo.swift index 576b972d9..cf2b61ba1 100755 --- a/macOS/APIExample/Common/StatisticsInfo.swift +++ b/macOS/APIExample/Common/StatisticsInfo.swift @@ -35,6 +35,7 @@ struct StatisticsInfo { } var type: StatisticsType + var firstFrameElapsedTime: Double = 0 init(type: StatisticsType) { self.type = type @@ -119,6 +120,10 @@ struct StatisticsInfo { } } + mutating func updateFirstFrameInfo(_ info: AgoraVideoRenderingTracingInfo) { + firstFrameElapsedTime = Double(info.elapsedTime) + } + func description(audioOnly:Bool) -> String { var full: String switch type { @@ -131,6 +136,7 @@ struct StatisticsInfo { func localDescription(info: LocalInfo, audioOnly: Bool) -> String { var results:[String] = [] + let firstFrame = "firstFrameTime: \(firstFrameElapsedTime)" if(!audioOnly) { if let volume = info.audioVolume { results.append("Volume: \(volume)") @@ -156,14 +162,16 @@ struct StatisticsInfo { results.append("Send Loss: \(channelStats.txPacketLossRate)%") } } - + if firstFrameElapsedTime > 0 { + results.append(firstFrame) + } return results.joined(separator: "\n") } func remoteDescription(info: RemoteInfo, audioOnly: Bool) -> String { var results:[String] = [] - + let firstFrame = "firstFrameTime: \(firstFrameElapsedTime)" if(!audioOnly) { if let volume = info.audioVolume { results.append("Volume: \(volume)") @@ -190,7 +198,9 @@ struct StatisticsInfo { results.append("AQuality: \(audioQuality.description())") } } - + if firstFrameElapsedTime > 0 { + results.append(firstFrame) + } return results.joined(separator: "\n") } } diff --git a/macOS/APIExample/Examples/Advanced/LiveStreaming/Base.lproj/LiveStreaming.storyboard b/macOS/APIExample/Examples/Advanced/LiveStreaming/Base.lproj/LiveStreaming.storyboard index a4b51556c..c9edf9184 100644 --- a/macOS/APIExample/Examples/Advanced/LiveStreaming/Base.lproj/LiveStreaming.storyboard +++ b/macOS/APIExample/Examples/Advanced/LiveStreaming/Base.lproj/LiveStreaming.storyboard @@ -182,6 +182,22 @@ + + + + + + + + + + + + + + + + @@ -266,6 +282,7 @@ + diff --git a/macOS/APIExample/Examples/Advanced/LiveStreaming/LiveStreaming.swift b/macOS/APIExample/Examples/Advanced/LiveStreaming/LiveStreaming.swift index 9aa126a1d..ec81bae31 100644 --- a/macOS/APIExample/Examples/Advanced/LiveStreaming/LiveStreaming.swift +++ b/macOS/APIExample/Examples/Advanced/LiveStreaming/LiveStreaming.swift @@ -258,6 +258,26 @@ class LiveStreamingMain: BaseViewController { agoraKit.enableDualStreamMode(sender.state == .on) } + @IBOutlet weak var firstFrameSwitch: NSSwitch! + @IBAction func onFirstFrameSwitch(_ sender: NSSwitch) { + if sender.state == .on { + let alertVC = NSAlert() + alertVC.alertStyle = .critical + alertVC.addButton(withTitle: "Sure".localized) + alertVC.addButton(withTitle: "Cancel".localized) + alertVC.messageText = "After this function is enabled, it cannot be disabled and takes effect only when both the primary and secondary ends are enabled".localized + let response = alertVC.runModal() + if response == .alertFirstButtonReturn { + sender.isEnabled = false + agoraKit.enableInstantMediaRendering() + agoraKit.startMediaRenderingTracing() + } + if response == .alertSecondButtonReturn { + sender.state = .off + } + } + } + @IBOutlet weak var bFrameContainer: NSView! @IBAction func bFrameSwitch(_ sender: NSSwitch) { let encoderConfig = AgoraVideoEncoderConfiguration() @@ -310,6 +330,7 @@ class LiveStreamingMain: BaseViewController { didSet { channelField.isEnabled = !isJoined selectLayoutPicker.isEnabled = !isJoined + firstFrameSwitch.isEnabled = !isJoined initJoinChannelButton() } } @@ -552,7 +573,7 @@ extension LiveStreamingMain: AgoraRtcEngineDelegate { /// Reports the statistics of the uploading local video streams once every two seconds. /// @param stats stats struct - func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStats stats: AgoraRtcLocalVideoStats) { + func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStats stats: AgoraRtcLocalVideoStats, sourceType:AgoraVideoSourceType) { videos[0].statsInfo?.updateLocalVideoStats(stats) } @@ -574,7 +595,11 @@ extension LiveStreamingMain: AgoraRtcEngineDelegate { videos.first(where: { $0.uid == stats.uid })?.statsInfo?.updateAudioStats(stats) } - func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStateChangedOf state: AgoraVideoLocalState, error: AgoraLocalVideoStreamError) { + func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStateChangedOf state: AgoraVideoLocalState, error: AgoraLocalVideoStreamError, sourceType:AgoraVideoSourceType) { LogUtils.log(message: "AgoraRtcEngineKit state: \(state), error \(error)", level: .info) } + + func rtcEngine(_ engine: AgoraRtcEngineKit, videoRenderingTracingResultOfUid uid: UInt, currentEvent: AgoraMediaTraceEvent, tracingInfo: AgoraVideoRenderingTracingInfo) { + videos.first(where: { $0.uid == uid })?.statsInfo?.updateFirstFrameInfo(tracingInfo) + } } diff --git a/macOS/APIExample/Examples/Advanced/SimpleFilter/SimpleFilter.swift b/macOS/APIExample/Examples/Advanced/SimpleFilter/SimpleFilter.swift index 97843a4fe..bcc705d8c 100644 --- a/macOS/APIExample/Examples/Advanced/SimpleFilter/SimpleFilter.swift +++ b/macOS/APIExample/Examples/Advanced/SimpleFilter/SimpleFilter.swift @@ -508,7 +508,7 @@ extension SimpleFilterMain: AgoraRtcEngineDelegate { /// Reports the statistics of the uploading local video streams once every two seconds. /// @param stats stats struct - func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStats stats: AgoraRtcLocalVideoStats) { + private func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStats stats: AgoraRtcLocalVideoStats, sourceType: AgoraMediaSourceType) { videos[0].statsInfo?.updateLocalVideoStats(stats) } diff --git a/macOS/APIExample/Examples/Advanced/VideoProcess/VideoProcess.swift b/macOS/APIExample/Examples/Advanced/VideoProcess/VideoProcess.swift index 48f7b03d1..08b168f9a 100644 --- a/macOS/APIExample/Examples/Advanced/VideoProcess/VideoProcess.swift +++ b/macOS/APIExample/Examples/Advanced/VideoProcess/VideoProcess.swift @@ -475,7 +475,7 @@ extension VideoProcess: AgoraRtcEngineDelegate { /// Reports the statistics of the uploading local video streams once every two seconds. /// @param stats stats struct - func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStats stats: AgoraRtcLocalVideoStats) { + func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStats stats: AgoraRtcLocalVideoStats, sourceType: AgoraVideoSourceType) { videos[0].statsInfo?.updateLocalVideoStats(stats) } diff --git a/macOS/APIExample/Examples/Basic/JoinChannelVideo(Token)/JoinChannelVideoToken.swift b/macOS/APIExample/Examples/Basic/JoinChannelVideo(Token)/JoinChannelVideoToken.swift index 966d4d9bb..19555282c 100644 --- a/macOS/APIExample/Examples/Basic/JoinChannelVideo(Token)/JoinChannelVideoToken.swift +++ b/macOS/APIExample/Examples/Basic/JoinChannelVideo(Token)/JoinChannelVideoToken.swift @@ -503,7 +503,7 @@ extension JoinChannelVideoToken: AgoraRtcEngineDelegate { /// Reports the statistics of the uploading local video streams once every two seconds. /// @param stats stats struct - func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStats stats: AgoraRtcLocalVideoStats) { + func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStats stats: AgoraRtcLocalVideoStats, sourceType:AgoraVideoSourceType) { videos[0].statsInfo?.updateLocalVideoStats(stats) } @@ -525,7 +525,7 @@ extension JoinChannelVideoToken: AgoraRtcEngineDelegate { videos.first(where: { $0.uid == stats.uid })?.statsInfo?.updateAudioStats(stats) } - func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStateChangedOf state: AgoraVideoLocalState, error: AgoraLocalVideoStreamError) { + func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStateChangedOf state: AgoraVideoLocalState, error: AgoraLocalVideoStreamError, sourceType:AgoraVideoSourceType) { LogUtils.log(message: "AgoraRtcEngineKit state: \(state), error \(error)", level: .info) } } diff --git a/macOS/APIExample/Examples/Basic/JoinChannelVideo/JoinChannelVideo.swift b/macOS/APIExample/Examples/Basic/JoinChannelVideo/JoinChannelVideo.swift index 41a0d2930..eb52a0113 100644 --- a/macOS/APIExample/Examples/Basic/JoinChannelVideo/JoinChannelVideo.swift +++ b/macOS/APIExample/Examples/Basic/JoinChannelVideo/JoinChannelVideo.swift @@ -485,7 +485,7 @@ extension JoinChannelVideoMain: AgoraRtcEngineDelegate { /// Reports the statistics of the uploading local video streams once every two seconds. /// @param stats stats struct - func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStats stats: AgoraRtcLocalVideoStats) { + func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStats stats: AgoraRtcLocalVideoStats, sourceType:AgoraVideoSourceType) { videos[0].statsInfo?.updateLocalVideoStats(stats) } @@ -507,7 +507,7 @@ extension JoinChannelVideoMain: AgoraRtcEngineDelegate { videos.first(where: { $0.uid == stats.uid })?.statsInfo?.updateAudioStats(stats) } - func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStateChangedOf state: AgoraVideoLocalState, error: AgoraLocalVideoStreamError) { + func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStateChangedOf state: AgoraVideoLocalState, error: AgoraLocalVideoStreamError, sourceType:AgoraVideoSourceType) { LogUtils.log(message: "AgoraRtcEngineKit state: \(state), error \(error)", level: .info) } } diff --git a/macOS/APIExample/Localizable.strings b/macOS/APIExample/Localizable.strings index 074182ca6..5634c60ae 100644 --- a/macOS/APIExample/Localizable.strings +++ b/macOS/APIExample/Localizable.strings @@ -60,6 +60,7 @@ "Chat Room Entertainment" = "游戏开黑"; "Show Room" = "秀场"; "Cancel" = "取消"; +"Sure" = "确定"; "Off" = "原声"; "FemaleFresh" = "语聊美声: 清新(女)"; "FemaleVitality" = "语聊美声: 活力(女)"; @@ -189,3 +190,4 @@ "Save failed" = "保存失败"; "Please the input AppId" = "请输入AppID"; "Please the input token" = "请输入Token"; +"After this function is enabled, it cannot be disabled and takes effect only when both the primary and secondary ends are enabled" = "开启后无法关闭,并且需要主辅两端都开启时才生效"; diff --git a/macOS/Podfile b/macOS/Podfile index 2490fa170..d04475104 100644 --- a/macOS/Podfile +++ b/macOS/Podfile @@ -4,12 +4,12 @@ target 'APIExample' do use_frameworks! pod 'AGEVideoLayout', '1.0.2' - pod 'AgoraRtcEngine_macOS', '4.1.0' + pod 'AgoraRtcEngine_macOS', '4.1.1' # pod 'sdk', :path => 'sdk.podspec' end target 'SimpleFilter' do use_frameworks! # pod 'sdk', :path => 'sdk.podspec' - pod 'AgoraRtcEngine_macOS', '4.1.0' + pod 'AgoraRtcEngine_macOS', '4.1.1' end diff --git a/windows/APIExample/APIExample/APIExample.rc b/windows/APIExample/APIExample/APIExample.rc index 8d3af6308..5f15d1320 100755 --- a/windows/APIExample/APIExample/APIExample.rc +++ b/windows/APIExample/APIExample/APIExample.rc @@ -97,12 +97,14 @@ BEGIN CONTROL "Report",IDC_CHECK_REPORT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,346,59,10 CONTROL "Moderation",IDC_CHECK_MODERATION,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,362,52,10 PUSHBUTTON "Snapshot",IDC_BUTTON_SNAPSHOT,537,346,50,14 - GROUPBOX "Encoder",IDC_STATIC_ENCODE_GROUP,84,346,72,53 - CONTROL "Auto",IDC_RADIO_ENCODE_AUTO,"Button",BS_AUTORADIOBUTTON | WS_GROUP,90,358,60,10 - CONTROL "Hardward",IDC_RADIO_ENCODE_HARD,"Button",BS_AUTORADIOBUTTON,90,372,60,10 - CONTROL "Softward",IDC_RADIO_ENCODE_SOFT,"Button",BS_AUTORADIOBUTTON,90,386,60,10 + GROUPBOX "Encoder",IDC_STATIC_ENCODE_GROUP,100,346,60,53 + CONTROL "Auto",IDC_RADIO_ENCODE_AUTO,"Button",BS_AUTORADIOBUTTON | WS_GROUP,105,358,49,10 + CONTROL "Hardward",IDC_RADIO_ENCODE_HARD,"Button",BS_AUTORADIOBUTTON,105,372,49,10 + CONTROL "Softward",IDC_RADIO_ENCODE_SOFT,"Button",BS_AUTORADIOBUTTON,105,386,49,10 CONTROL "B Frame",IDC_CHECK_B_FRAME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,169,346,42,10 EDITTEXT IDC_EDIT_DETAIL_INFO,339,346,183,53,ES_MULTILINE | ES_READONLY | NOT WS_BORDER | WS_VSCROLL + CONTROL "FirstFrameOptimization",IDC_CHECK_FIRST_FRAME_OPT, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,379,89,10 END IDD_DIALOG_RTMPINJECT DIALOGEX 0, 0, 632, 400 @@ -545,7 +547,7 @@ BEGIN PUSHBUTTON "JoinChannel",IDC_BUTTON_JOINCHANNEL,307,325,60,14 PUSHBUTTON "LeaveChannel",IDC_BUTTON_EX_CHANNEL,308,350,60,14 LTEXT "",IDC_STATIC_DETAIL,442,325,181,58 - CONTROL "StopMic",IDC_BUTTON_STOP_MIC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,256,352,41,10 + CONTROL "StopMicWhenLeavingExChannel",IDC_BUTTON_STOP_MIC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,161,352,136,10 END IDD_DIALOG_LOCAL_VIDEO_TRANSCODING DIALOGEX 0, 0, 632, 400 @@ -648,10 +650,11 @@ BEGIN BEGIN RIGHTMARGIN, 630 VERTGUIDE, 7 - VERTGUIDE, 84 - VERTGUIDE, 90 - VERTGUIDE, 150 - VERTGUIDE, 156 + VERTGUIDE, 100 + VERTGUIDE, 105 + VERTGUIDE, 154 + VERTGUIDE, 160 + VERTGUIDE, 169 VERTGUIDE, 339 VERTGUIDE, 522 HORZGUIDE, 309 diff --git a/windows/APIExample/APIExample/Advanced/MediaEncrypt/CAgoraMediaEncryptDlg.cpp b/windows/APIExample/APIExample/Advanced/MediaEncrypt/CAgoraMediaEncryptDlg.cpp old mode 100644 new mode 100755 index cf3b878b0..9037a33e0 --- a/windows/APIExample/APIExample/Advanced/MediaEncrypt/CAgoraMediaEncryptDlg.cpp +++ b/windows/APIExample/APIExample/Advanced/MediaEncrypt/CAgoraMediaEncryptDlg.cpp @@ -144,6 +144,7 @@ void CAgoraMediaEncryptDlg::ResumeStatus() m_lstInfo.ResetContent(); m_edtEncryptKey.SetWindowText(_T("")); m_staDetails.SetWindowText(_T("")); + m_btnSetEncrypt.EnableWindow(TRUE); m_joinChannel = false; m_initialize = false; m_setEncrypt = false; @@ -156,10 +157,18 @@ BOOL CAgoraMediaEncryptDlg::OnInitDialog() m_localVideoWnd.Create(NULL, NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, CRect(0, 0, 1, 1), this, ID_BASEWND_VIDEO + 100); RECT rcArea; m_staVideoArea.GetClientRect(&rcArea); + int halfWidth = (rcArea.right - rcArea.left) / 2; + rcArea.right = rcArea.left + halfWidth; m_localVideoWnd.MoveWindow(&rcArea); m_localVideoWnd.ShowWindow(SW_SHOW); - int nIndex = 0; + m_remoteVideoWnd.Create(NULL, NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, CRect(0, 0, 1, 1), this, ID_BASEWND_VIDEO + 101); + rcArea.left = rcArea.right; + rcArea.right = rcArea.left + halfWidth; + m_remoteVideoWnd.MoveWindow(&rcArea); + m_remoteVideoWnd.ShowWindow(SW_SHOW); + m_remoteVideoWnd.SetUID(0); + int nIndex = 0; //m_cmbEncryptMode.InsertString(nIndex++, _T("aes-128-xts")); //m_cmbEncryptMode.InsertString(nIndex++, _T("aes-128-ecb")); //m_cmbEncryptMode.InsertString(nIndex++, _T("aes-256-xts")); @@ -278,6 +287,7 @@ LRESULT CAgoraMediaEncryptDlg::OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lPa strInfo.Format(_T("%s:join success, uid=%u"), getCurrentTime(), wParam); m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo); m_localVideoWnd.SetUID(wParam); + m_btnSetEncrypt.EnableWindow(FALSE); //notify parent window ::PostMessage(GetParent()->GetSafeHwnd(), WM_MSGID(EID_JOINCHANNEL_SUCCESS), TRUE, 0); return 0; @@ -293,6 +303,13 @@ LRESULT CAgoraMediaEncryptDlg::OnEIDLeaveChannel(WPARAM wParam, LPARAM lParam) CString strInfo; strInfo.Format(_T("leave channel success %s"), getCurrentTime()); m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo); + m_btnSetEncrypt.EnableWindow(TRUE); + + if (m_remoteVideoWnd.GetUID() != 0) { + m_remoteVideoWnd.SetUID(0); + m_remoteVideoWnd.Reset(); + } + ::PostMessage(GetParent()->GetSafeHwnd(), WM_MSGID(EID_JOINCHANNEL_SUCCESS), FALSE, 0); return 0; } @@ -301,8 +318,19 @@ LRESULT CAgoraMediaEncryptDlg::OnEIDLeaveChannel(WPARAM wParam, LPARAM lParam) LRESULT CAgoraMediaEncryptDlg::OnEIDUserJoined(WPARAM wParam, LPARAM lParam) { CString strInfo; - strInfo.Format(_T("%u joined"), wParam); + uid_t remoteUid = wParam; + strInfo.Format(_T("%u joined"), remoteUid); m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo); + + if (m_remoteVideoWnd.GetUID() == 0) { + m_remoteVideoWnd.SetUID(remoteUid); + + VideoCanvas canvas; + canvas.view = m_remoteVideoWnd.GetSafeHwnd(); + canvas.renderMode = agora::media::base::RENDER_MODE_HIDDEN; + canvas.uid = remoteUid; + m_rtcEngine->setupRemoteVideo(canvas); + } return 0; } @@ -316,6 +344,16 @@ LRESULT CAgoraMediaEncryptDlg::OnEIDUserOffline(WPARAM wParam, LPARAM lParam) strInfo.Format(_T("%u offline, reason:%d"), remoteUid, lParam); m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo); + if (m_remoteVideoWnd.GetUID() == remoteUid) { + m_remoteVideoWnd.SetUID(0); + m_remoteVideoWnd.Reset(); + + VideoCanvas canvas; + canvas.view = nullptr; + canvas.uid = remoteUid; + m_rtcEngine->setupRemoteVideo(canvas); + } + return 0; } diff --git a/windows/APIExample/APIExample/Advanced/MediaEncrypt/CAgoraMediaEncryptDlg.h b/windows/APIExample/APIExample/Advanced/MediaEncrypt/CAgoraMediaEncryptDlg.h old mode 100644 new mode 100755 index a15cd902c..d51fc0c89 --- a/windows/APIExample/APIExample/Advanced/MediaEncrypt/CAgoraMediaEncryptDlg.h +++ b/windows/APIExample/APIExample/Advanced/MediaEncrypt/CAgoraMediaEncryptDlg.h @@ -115,6 +115,7 @@ class CAgoraMediaEncryptDlg : public CDialogEx bool m_setEncrypt = false; IRtcEngine* m_rtcEngine = nullptr; CAGVideoWnd m_localVideoWnd; + CAGVideoWnd m_remoteVideoWnd; CAgoraMediaEncryptHandler m_eventHandler; // agora sdk message window handler LRESULT OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam); diff --git a/windows/APIExample/APIExample/Advanced/OriginalAudio/CAgoraOriginalAudioDlg.cpp b/windows/APIExample/APIExample/Advanced/OriginalAudio/CAgoraOriginalAudioDlg.cpp index ea8611e08..f13d6f077 100755 --- a/windows/APIExample/APIExample/Advanced/OriginalAudio/CAgoraOriginalAudioDlg.cpp +++ b/windows/APIExample/APIExample/Advanced/OriginalAudio/CAgoraOriginalAudioDlg.cpp @@ -127,17 +127,17 @@ int COriginalAudioProcFrameObserver::getObservedAudioFramePosition() } agora::media::IAudioFrameObserverBase::AudioParams COriginalAudioProcFrameObserver::getPlaybackAudioParams() { - agora::media::IAudioFrameObserverBase::AudioParams params(16000, 2, agora::rtc::RAW_AUDIO_FRAME_OP_MODE_READ_ONLY, 1024); + agora::media::IAudioFrameObserverBase::AudioParams params(16000, 2, agora::rtc::RAW_AUDIO_FRAME_OP_MODE_READ_ONLY, 320); return params; } agora::media::IAudioFrameObserverBase::AudioParams COriginalAudioProcFrameObserver::getRecordAudioParams() { - agora::media::IAudioFrameObserverBase::AudioParams params(16000, 2, agora::rtc::RAW_AUDIO_FRAME_OP_MODE_READ_ONLY, 1024); + agora::media::IAudioFrameObserverBase::AudioParams params(16000, 2, agora::rtc::RAW_AUDIO_FRAME_OP_MODE_READ_ONLY, 320); return params; } agora::media::IAudioFrameObserverBase::AudioParams COriginalAudioProcFrameObserver::getMixedAudioParams() { - agora::media::IAudioFrameObserverBase::AudioParams params(16000, 2, agora::rtc::RAW_AUDIO_FRAME_OP_MODE_READ_ONLY, 1024); + agora::media::IAudioFrameObserverBase::AudioParams params(16000, 2, agora::rtc::RAW_AUDIO_FRAME_OP_MODE_READ_ONLY, 320); return params; } diff --git a/windows/APIExample/APIExample/Advanced/VideoMetadata/CAgoraMetaDataDlg.cpp b/windows/APIExample/APIExample/Advanced/VideoMetadata/CAgoraMetaDataDlg.cpp index 8cc5c0cbf..fdd9dad4e 100755 --- a/windows/APIExample/APIExample/Advanced/VideoMetadata/CAgoraMetaDataDlg.cpp +++ b/windows/APIExample/APIExample/Advanced/VideoMetadata/CAgoraMetaDataDlg.cpp @@ -461,7 +461,7 @@ LRESULT CAgoraMetaDataDlg::OnEIDMetadataReceived(WPARAM wParam, LPARAM lParam) if (metaData->size > 0) { CString str; - str.Format(_T("Info: %S"), metaData->buffer); + str.Format(_T("Info: %s"), utf82cs(std::string((char*)metaData->buffer))); strInfo += str; } m_edtRecvSEI.SetWindowText(strInfo); diff --git a/windows/APIExample/APIExample/Basic/LiveBroadcasting/CLiveBroadcastingDlg.cpp b/windows/APIExample/APIExample/Basic/LiveBroadcasting/CLiveBroadcastingDlg.cpp index dc42de160..2dbf595e5 100755 --- a/windows/APIExample/APIExample/Basic/LiveBroadcasting/CLiveBroadcastingDlg.cpp +++ b/windows/APIExample/APIExample/Basic/LiveBroadcasting/CLiveBroadcastingDlg.cpp @@ -82,6 +82,7 @@ void CLiveBroadcastingRtcEngineEventHandler::onLeaveChannel(const RtcStats& stat } } + void CLiveBroadcastingRtcEngineEventHandler::onLocalVideoStats(VIDEO_SOURCE_TYPE source, const LocalVideoStats& stats) { if (m_hMsgHanlder && report) { @@ -91,6 +92,15 @@ void CLiveBroadcastingRtcEngineEventHandler::onLocalVideoStats(VIDEO_SOURCE_TYPE } } +void CLiveBroadcastingRtcEngineEventHandler::onVideoRenderingTracingResult(uid_t uid, MEDIA_TRACE_EVENT currentEvent, VideoRenderingTracingInfo info) +{ + if (m_hMsgHanlder) { + VideoRenderingTracingInfo* i = new VideoRenderingTracingInfo; + *i = info; + ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_VIDEO_RENDERING_TRACING_RESULT), (WPARAM)i, uid); + } +} + // CLiveBroadcastingDlg dialog IMPLEMENT_DYNAMIC(CLiveBroadcastingDlg, CDialogEx) @@ -121,6 +131,7 @@ void CLiveBroadcastingDlg::DoDataExchange(CDataExchange* pDX) DDX_Control(pDX, IDC_CHECK_MODERATION, m_chkModeration); DDX_Control(pDX, IDC_BUTTON_SNAPSHOT, m_chkSnapshot); DDX_Control(pDX, IDC_CHECK_B_FRAME, m_chkBFrame); + DDX_Control(pDX, IDC_CHECK_FIRST_FRAME_OPT, m_chkFirstFrameOpt); DDX_Control(pDX, IDC_RADIO_ENCODE_AUTO, m_rdiEncodeAuto); DDX_Control(pDX, IDC_RADIO_ENCODE_HARD, m_rdiEncodeHard); DDX_Control(pDX, IDC_RADIO_ENCODE_SOFT, m_rdiEncodeSoft); @@ -151,6 +162,7 @@ BEGIN_MESSAGE_MAP(CLiveBroadcastingDlg, CDialogEx) ON_MESSAGE(WM_MSGID(EID_REMOTE_VIDEO_STATE_CHANGED), &CLiveBroadcastingDlg::onEIDRemoteVideoStateChanged) ON_MESSAGE(WM_MSGID(EID_CONTENT_INSPECT_RESULT), &CLiveBroadcastingDlg::onEIDContentInspectResult) ON_MESSAGE(WM_MSGID(EID_SNAPSHOT_TAKEN), &CLiveBroadcastingDlg::onEIDSnapshotTaken) + ON_MESSAGE(WM_MSGID(EID_VIDEO_RENDERING_TRACING_RESULT), &CLiveBroadcastingDlg::onEIDVideoRenderingTracingResult) ON_WM_SHOWWINDOW() ON_LBN_SELCHANGE(IDC_LIST_INFO_BROADCASTING, &CLiveBroadcastingDlg::OnSelchangeListInfoBroadcasting) @@ -160,6 +172,7 @@ BEGIN_MESSAGE_MAP(CLiveBroadcastingDlg, CDialogEx) ON_BN_CLICKED(IDC_BUTTON_SNAPSHOT, &CLiveBroadcastingDlg::OnBnClickedSnapshot) ON_CONTROL_RANGE(BN_CLICKED, IDC_RADIO_ENCODE_AUTO, IDC_RADIO_ENCODE_SOFT, &CLiveBroadcastingDlg::OnBnClickedRadioEncoder) ON_BN_CLICKED(IDC_CHECK_B_FRAME, &CLiveBroadcastingDlg::OnBnClickedCheckBFrame) + ON_BN_CLICKED(IDC_CHECK_FIRST_FRAME_OPT, &CLiveBroadcastingDlg::OnBnClickedCheckFirstFrameOpt) END_MESSAGE_MAP() @@ -206,6 +219,7 @@ void CLiveBroadcastingDlg::InitCtrlText() m_rdiEncodeSoft.SetWindowText(liveBraodcastingSoftEncode); m_chkBFrame.SetWindowText(liveBraodcastingBFrame); m_staEncode.SetWindowText(liveBraodcastingEncode); + m_chkFirstFrameOpt.SetWindowText(liveBraodcastingFristFrameOpt); } //create all video window to save m_videoWnds. @@ -362,6 +376,8 @@ void CLiveBroadcastingDlg::ResumeStatus() m_rdiEncodeAuto.SetCheck(TRUE); m_btnJoinChannel.EnableWindow(TRUE); m_cmbRole.EnableWindow(TRUE); + m_chkFirstFrameOpt.EnableWindow(TRUE); + m_chkFirstFrameOpt.SetCheck(FALSE); m_edtChannelName.SetWindowText(_T("")); m_joinChannel = false; m_initialize = false; @@ -445,6 +461,8 @@ void CLiveBroadcastingDlg::OnBnClickedButtonJoinchannel() m_rtcEngine->setVideoEncoderConfiguration(config); std::string szChannelId = cs2utf8(strChannelName); + m_rtcEngine->startMediaRenderingTracing(); + ChannelMediaOptions options; options.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING; options.clientRoleType = CLIENT_ROLE_TYPE(m_cmbRole.GetCurSel() + 1); @@ -961,6 +979,43 @@ LRESULT CLiveBroadcastingDlg::onEIDSnapshotTaken(WPARAM wParam, LPARAM lParam) { return 0; } +LRESULT CLiveBroadcastingDlg::onEIDVideoRenderingTracingResult(WPARAM wParam, LPARAM lParam) +{ + VideoRenderingTracingInfo* info = (VideoRenderingTracingInfo*)wParam; + uid_t uid = lParam; + + CString strInfo; + strInfo.Format(_T("===VideoRenderingTracingResult===")); + m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo); + + strInfo.Format(_T("uid=%d"), uid); + m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo); + + strInfo.Format(_T("elapsedTime=%dms"), info->elapsedTime); + m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo); + + strInfo.Format(_T("start2JoinChannel=%dms"), info->start2JoinChannel); + m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo); + + strInfo.Format(_T("join2JoinSuccess=%dms"), info->join2JoinSuccess); + m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo); + + strInfo.Format(_T("joinSuccess2RemoteJoined=%dms"), info->joinSuccess2RemoteJoined); + m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo); + + strInfo.Format(_T("remoteJoined2SetView=%dms"), info->remoteJoined2SetView); + m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo); + + strInfo.Format(_T("remoteJoined2UnmuteVideo=%dms"), info->remoteJoined2UnmuteVideo); + m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo); + + strInfo.Format(_T("remoteJoined2PacketReceived=%dms"), info->remoteJoined2PacketReceived); + m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo); + + delete info; + return 0; +} + void CLiveBroadcastingDlg::OnBnClickedCheckReport() { bool isCheck = m_chkReport.GetCheck() != 0; @@ -1019,3 +1074,19 @@ void CLiveBroadcastingDlg::OnBnClickedCheckBFrame() m_videoEncoderConfig.advanceOptions.compressionPreference = m_chkBFrame.GetCheck() ? PREFER_QUALITY : PREFER_LOW_LATENCY; m_rtcEngine->setVideoEncoderConfiguration(m_videoEncoderConfig); } + + +void CLiveBroadcastingDlg::OnBnClickedCheckFirstFrameOpt() +{ + if (m_chkFirstFrameOpt.GetCheck()) { + + UINT ret = MessageBox(liveBraodcastingFristFrameTipContent, liveBraodcastingFristFrameTip, MB_YESNO | MB_ICONQUESTION); + if (ret == IDYES) { + m_rtcEngine->enableInstantMediaRendering(); + m_chkFirstFrameOpt.EnableWindow(FALSE); + } + else { + m_chkFirstFrameOpt.SetCheck(FALSE); + } + } +} diff --git a/windows/APIExample/APIExample/Basic/LiveBroadcasting/CLiveBroadcastingDlg.h b/windows/APIExample/APIExample/Basic/LiveBroadcasting/CLiveBroadcastingDlg.h index 9ea33aaf7..8cb813409 100755 --- a/windows/APIExample/APIExample/Basic/LiveBroadcasting/CLiveBroadcastingDlg.h +++ b/windows/APIExample/APIExample/Basic/LiveBroadcasting/CLiveBroadcastingDlg.h @@ -171,6 +171,9 @@ class CLiveBroadcastingRtcEngineEventHandler void onLocalVideoStats(VIDEO_SOURCE_TYPE source, const LocalVideoStats& stats) override; + + void onVideoRenderingTracingResult(uid_t uid, MEDIA_TRACE_EVENT currentEvent, VideoRenderingTracingInfo info) override; + private: HWND m_hMsgHanlder; bool report = false; @@ -227,6 +230,7 @@ class CLiveBroadcastingDlg : public CDialogEx afx_msg LRESULT onEIDRemoteVideoStateChanged(WPARAM wParam, LPARAM lParam); afx_msg LRESULT onEIDContentInspectResult(WPARAM wParam, LPARAM lParam); afx_msg LRESULT onEIDSnapshotTaken(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT onEIDVideoRenderingTracingResult(WPARAM wParam, LPARAM lParam); private: //set control text from config. void InitCtrlText(); @@ -273,6 +277,7 @@ class CLiveBroadcastingDlg : public CDialogEx CButton m_chkModeration; CButton m_chkSnapshot; CButton m_chkBFrame; + CButton m_chkFirstFrameOpt; CButton m_rdiEncodeAuto; CButton m_rdiEncodeSoft; CButton m_rdiEncodeHard; @@ -282,4 +287,5 @@ class CLiveBroadcastingDlg : public CDialogEx afx_msg void OnBnClickedSnapshot(); afx_msg void OnBnClickedRadioEncoder(UINT idCtl); afx_msg void OnBnClickedCheckBFrame(); + afx_msg void OnBnClickedCheckFirstFrameOpt(); }; diff --git a/windows/APIExample/APIExample/Language.h b/windows/APIExample/APIExample/Language.h index 96237cd76..c65f419bc 100755 --- a/windows/APIExample/APIExample/Language.h +++ b/windows/APIExample/APIExample/Language.h @@ -320,6 +320,10 @@ extern wchar_t liveBraodcastingHardEncode[INFO_LEN]; extern wchar_t liveBraodcastingAutoEncode[INFO_LEN]; extern wchar_t liveBraodcastingSoftEncode[INFO_LEN]; extern wchar_t liveBraodcastingBFrame[INFO_LEN]; +extern wchar_t liveBraodcastingFristFrameOpt[INFO_LEN]; +extern wchar_t liveBraodcastingFristFrameTip[INFO_LEN]; +extern wchar_t liveBraodcastingFristFrameTipContent[INFO_LEN]; + extern wchar_t SpatialAudio[INFO_LEN]; extern wchar_t SpatialAudioInitInfo[INFO_LEN]; diff --git a/windows/APIExample/APIExample/en.ini b/windows/APIExample/APIExample/en.ini index 731311618..6ae55f53c 100755 --- a/windows/APIExample/APIExample/en.ini +++ b/windows/APIExample/APIExample/en.ini @@ -186,7 +186,7 @@ MeidaPlayer.Ctrl.UnPublishAudio=UnPublishAudio MultiChannel.Ctrl.ChannelList=Channel List MultiChannel.Ctrl.JoinExChannel=JoinExChannel MultiChannel.Ctrl.LeaveExChannel=LeaveExChannel -MultiChannel.Ctrl.StopMic=Stop Mic +MultiChannel.Ctrl.StopMic=StopMicWhenLeavingExChannel @@ -248,6 +248,9 @@ LiveBroadcasting.Ctrl.AutoEncode = Auto LiveBroadcasting.Ctrl.HardEncode = Hardward LiveBroadcasting.Ctrl.SoftEncode = Software LiveBroadcasting.Ctrl.BFrame = Open BFrame +LiveBroadcasting.Ctrl.FirstFrameOpt = FirstFrameOptimization +LiveBroadcasting.Ctrl.FristFrameTip = Tip +LiveBroadcasting.Ctrl.FristFrameTipContent = It cannot be turned off after it is turned on, and it takes effect only when both the main and auxiliary ends are turned on. Is it open? Beauty.Face = BeautyFace BeautyAudio.Ctrl.ReverbPreSet=Preset diff --git a/windows/APIExample/APIExample/resource.h b/windows/APIExample/APIExample/resource.h index 76a6214a5..102746064 100755 --- a/windows/APIExample/APIExample/resource.h +++ b/windows/APIExample/APIExample/resource.h @@ -342,6 +342,7 @@ #define IDC_STATIC_PARAM2 1163 #define IDC_CHECK_MODERATION 1164 #define IDC_CHECK_B_FRAME 1165 +#define IDC_CHECK_FIRST_FRAME_OPT 1166 // Next default values for new objects // diff --git a/windows/APIExample/APIExample/stdafx.cpp b/windows/APIExample/APIExample/stdafx.cpp index 677d44ca0..dfc736f17 100755 --- a/windows/APIExample/APIExample/stdafx.cpp +++ b/windows/APIExample/APIExample/stdafx.cpp @@ -288,6 +288,9 @@ wchar_t liveBraodcastingHardEncode[INFO_LEN] = { 0 }; wchar_t liveBraodcastingAutoEncode[INFO_LEN] = { 0 }; wchar_t liveBraodcastingSoftEncode[INFO_LEN] = { 0 }; wchar_t liveBraodcastingBFrame[INFO_LEN] = { 0 }; +wchar_t liveBraodcastingFristFrameOpt[INFO_LEN] = { 0 }; +wchar_t liveBraodcastingFristFrameTip[INFO_LEN] = { 0 }; +wchar_t liveBraodcastingFristFrameTipContent[INFO_LEN] = { 0 }; wchar_t beautyFace[INFO_LEN] = { 0 }; wchar_t SpatialAudio[INFO_LEN] = { 0 }; @@ -708,6 +711,9 @@ void InitKeyInfomation() _tcscpy_s(liveBraodcastingHardEncode, INFO_LEN, Str(_T("LiveBroadcasting.Ctrl.HardEncode"))); _tcscpy_s(liveBraodcastingSoftEncode, INFO_LEN, Str(_T("LiveBroadcasting.Ctrl.SoftEncode"))); _tcscpy_s(liveBraodcastingBFrame, INFO_LEN, Str(_T("LiveBroadcasting.Ctrl.BFrame"))); + _tcscpy_s(liveBraodcastingFristFrameOpt, INFO_LEN, Str(_T("LiveBroadcasting.Ctrl.FirstFrameOpt"))); + _tcscpy_s(liveBraodcastingFristFrameTip, INFO_LEN, Str(_T("LiveBroadcasting.Ctrl.FristFrameTip"))); + _tcscpy_s(liveBraodcastingFristFrameTipContent, INFO_LEN, Str(_T("LiveBroadcasting.Ctrl.FristFrameTipContent"))); _tcscpy_s(beautyFace, INFO_LEN, Str(_T("Beauty.Face"))); diff --git a/windows/APIExample/APIExample/stdafx.h b/windows/APIExample/APIExample/stdafx.h index e72fe8be7..f616900be 100644 --- a/windows/APIExample/APIExample/stdafx.h +++ b/windows/APIExample/APIExample/stdafx.h @@ -113,6 +113,8 @@ using namespace agora::media; #define MAX_VIDEO_COUNT 16 #define EID_SNAPSHOT_TAKEN 0x00000032 +#define EID_VIDEO_RENDERING_TRACING_RESULT 0x00000033 + typedef struct _tagRtmpStreamStateChanged { char* url; int state; diff --git a/windows/APIExample/APIExample/zh-cn.ini b/windows/APIExample/APIExample/zh-cn.ini index 94c79b387..be1ca3b43 100755 --- a/windows/APIExample/APIExample/zh-cn.ini +++ b/windows/APIExample/APIExample/zh-cn.ini @@ -176,7 +176,7 @@ MeidaPlayer.Ctrl.UnPublishAudio=ȡ MultiChannel.Ctrl.ChannelList=Ƶб MultiChannel.Ctrl.JoinExChannel=ExƵ MultiChannel.Ctrl.LeaveExChannel=뿪ExƵ -MultiChannel.Ctrl.StopMic=ر˷ +MultiChannel.Ctrl.StopMic=뿪ExƵʱرձ˷ MultiVideoSource.Publish=ʼ MultiVideoSource.StopPublish=ֹͣ @@ -267,6 +267,9 @@ LiveBroadcasting.Ctrl.AutoEncode = LiveBroadcasting.Ctrl.HardEncode = Ӳ LiveBroadcasting.Ctrl.SoftEncode = LiveBroadcasting.Ctrl.BFrame = B֡ +LiveBroadcasting.Ctrl.FirstFrameOpt = ֡ͼŻ +LiveBroadcasting.Ctrl.FristFrameTip = ʾ +LiveBroadcasting.Ctrl.FristFrameTipContent = ޷رգҪ˶ʱЧǷ򿪣 Beauty.Face = BeautyAudio.Ctrl.ReverbPreSet= diff --git a/windows/APIExample/install.ps1 b/windows/APIExample/install.ps1 index 4934ab1d1..f6eaf0773 100644 --- a/windows/APIExample/install.ps1 +++ b/windows/APIExample/install.ps1 @@ -1,6 +1,6 @@ $ThirdPartysrc = 'https://agora-adc-artifacts.oss-cn-beijing.aliyuncs.com/libs/ThirdParty.zip' $ThirdPartydes = 'ThirdParty.zip' -$agora_sdk = 'https://download.agora.io/sdk/release/Agora_Native_SDK_for_Windows_v4.1.0_FULL.zip' +$agora_sdk = 'https://download.agora.io/sdk/release/Agora_Native_SDK_for_Windows_v4.1.1_FULL.zip' $agora_des = 'AgoraSdk.zip' $agora_local_sdk = '../../sdk'