DGJ 3 месяцев назад
Сommit
64dffa39e2
100 измененных файлов с 11654 добавлено и 0 удалено
  1. 41 0
      .gitignore
  2. 8 0
      Assets/Agora-RTC-Plugin.meta
  3. 8 0
      Assets/Agora-RTC-Plugin/API-Example.meta
  4. 8 0
      Assets/Agora-RTC-Plugin/API-Example/AppIdInput.meta
  5. 19 0
      Assets/Agora-RTC-Plugin/API-Example/AppIdInput/AppIdInput.cs
  6. 11 0
      Assets/Agora-RTC-Plugin/API-Example/AppIdInput/AppIdInput.cs.meta
  7. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Editor.meta
  8. 214 0
      Assets/Agora-RTC-Plugin/API-Example/Editor/CommandBuild.cs
  9. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Editor/CommandBuild.cs.meta
  10. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples.meta
  11. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced.meta
  12. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/AudioMixing.meta
  13. 268 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/AudioMixing/AudioMixing.cs
  14. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/AudioMixing/AudioMixing.cs.meta
  15. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/AudioSpectrum.meta
  16. 507 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/AudioSpectrum/AudioSpectrum.cs
  17. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/AudioSpectrum/AudioSpectrum.cs.meta
  18. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ChannelMediaRelay.meta
  19. 356 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ChannelMediaRelay/ChannelMediaRelay.cs
  20. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ChannelMediaRelay/ChannelMediaRelay.cs.meta
  21. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ContentInspect.meta
  22. 312 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ContentInspect/ContentInspect.cs
  23. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ContentInspect/ContentInspect.cs.meta
  24. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomCaptureAudio.meta
  25. 274 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomCaptureAudio/CustomCaptureAudio.cs
  26. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomCaptureAudio/CustomCaptureAudio.cs.meta
  27. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomCaptureVideo.meta
  28. 341 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomCaptureVideo/CustomCaptureVideo.cs
  29. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomCaptureVideo/CustomCaptureVideo.cs.meta
  30. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomRenderAudio.meta
  31. 273 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomRenderAudio/CustomRenderAudio.cs
  32. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomRenderAudio/CustomRenderAudio.cs.meta
  33. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/DualCamera.meta
  34. 452 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/DualCamera/DualCamera.cs
  35. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/DualCamera/DualCamera.cs.meta
  36. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/JoinChannelVideoToken.meta
  37. 319 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/JoinChannelVideoToken/JoinChannelVideoToken.cs
  38. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/JoinChannelVideoToken/JoinChannelVideoToken.cs.meta
  39. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/JoinChannelWithUserAccount.meta
  40. 272 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/JoinChannelWithUserAccount/JoinChannelWithUserAccount.cs
  41. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/JoinChannelWithUserAccount/JoinChannelWithUserAccount.cs.meta
  42. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaPlayer.meta
  43. 551 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaPlayer/MediaPlayerExample.cs
  44. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaPlayer/MediaPlayerExample.cs.meta
  45. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaPlayerWithCustomDataProvider.meta
  46. 561 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaPlayerWithCustomDataProvider/MediaPlayerWithCustomDataProviderExample.cs
  47. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaPlayerWithCustomDataProvider/MediaPlayerWithCustomDataProviderExample.cs.meta
  48. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaRecorder.meta
  49. 339 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaRecorder/MediaRecorder.cs
  50. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaRecorder/MediaRecorder.cs.meta
  51. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/Metadata.meta
  52. 354 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/Metadata/MetadataSample.cs
  53. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/Metadata/MetadataSample.cs.meta
  54. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ProcessAudioRawData.meta
  55. 308 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ProcessAudioRawData/ProcessAudioRawData.cs
  56. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ProcessAudioRawData/ProcessAudioRawData.cs.meta
  57. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ProcessVideoRawData.meta
  58. 292 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ProcessVideoRawData/ProcessVideoRawData.cs
  59. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ProcessVideoRawData/ProcessVideoRawData.cs.meta
  60. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/PushEncodedVideoImage.meta
  61. 520 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/PushEncodedVideoImage/PushEncodedVideoImage.cs
  62. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/PushEncodedVideoImage/PushEncodedVideoImage.cs.meta
  63. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ScreenShare.meta
  64. 415 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ScreenShare/ScreenShare.cs
  65. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ScreenShare/ScreenShare.cs.meta
  66. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ScreenShareWhileVideoCall.meta
  67. 426 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ScreenShareWhileVideoCall/ScreenShareWhileVideoCall.cs
  68. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ScreenShareWhileVideoCall/ScreenShareWhileVideoCall.cs.meta
  69. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetBeautyEffectOptions.meta
  70. 342 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetBeautyEffectOptions/SetBeautyEffectOptions.cs
  71. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetBeautyEffectOptions/SetBeautyEffectOptions.cs.meta
  72. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetEncryption.meta
  73. 290 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetEncryption/EncryptionSample.cs
  74. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetEncryption/EncryptionSample.cs.meta
  75. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetVideoEncodeConfiguration.meta
  76. 288 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetVideoEncodeConfiguration/SetVideoEncodeConfiguration.cs
  77. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetVideoEncodeConfiguration/SetVideoEncodeConfiguration.cs.meta
  78. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SpatialAudioWithMediaPlayer.meta
  79. 394 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SpatialAudioWithMediaPlayer/SpatialAudioWithMediaPlayer.cs
  80. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SpatialAudioWithMediaPlayer/SpatialAudioWithMediaPlayer.cs.meta
  81. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartDirectCdnStreaming.meta
  82. 298 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartDirectCdnStreaming/StartDirectCdnStreaming.cs
  83. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartDirectCdnStreaming/StartDirectCdnStreaming.cs.meta
  84. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartLocalVideoTranscoder.meta
  85. 563 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartLocalVideoTranscoder/StartLocalVideoTranscoder.cs
  86. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartLocalVideoTranscoder/StartLocalVideoTranscoder.cs.meta
  87. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartRhythmPlayer.meta
  88. 219 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartRhythmPlayer/StartRhythmPlayer.cs
  89. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartRhythmPlayer/StartRhythmPlayer.cs.meta
  90. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartRtmpStreamWithTranscoding.meta
  91. 369 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartRtmpStreamWithTranscoding/StartRtmpStreamWithTranscoding.cs
  92. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartRtmpStreamWithTranscoding/StartRtmpStreamWithTranscoding.cs.meta
  93. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StreamMessage.meta
  94. 222 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StreamMessage/StreamMessage.cs
  95. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StreamMessage/StreamMessage.cs.meta
  96. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/TakeSnapshot.meta
  97. 295 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/TakeSnapshot/TakeSnapshot.cs
  98. 11 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/TakeSnapshot/TakeSnapshot.cs.meta
  99. 8 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/VirtualBackground.meta
  100. 331 0
      Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/VirtualBackground/VirtualBackground.cs

+ 41 - 0
.gitignore

@@ -0,0 +1,41 @@
+/*.csproj
+/Library
+/Logs
+/obj
+/Temp
+/UserSettings
+/vrlauncher.sln
+/Assembly-CSharp.csproj
+/Assembly-CSharp-Editor.csproj
+/Assembly-CSharp-Editor-firstpass.csproj
+/Assembly-CSharp-firstpass.csproj
+/ControllerSample.csproj
+/Demos.StandardShader.Inspectors.csproj
+/Lenovo.XR.OSK.csproj
+/Lenovo.XR.OSK.Sample.csproj
+/Pico.Platform.csproj
+/Pico.Spatializer.csproj
+/Pico.Spatializer.Editor.csproj
+/Pico.Spatializer.Example.csproj
+/QCHT.Core.csproj
+/QCHT.Core.Editor.csproj
+/QCHT.Interactions.csproj
+/QCHT.Interactions.Editor.csproj
+/Snapdragon.Spaces.Editor.csproj
+/Snapdragon.Spaces.Runtime.csproj
+/Unity.XR.PICO.csproj
+/Unity.XR.PICO.Editor.csproj
+/.vsconfig
+/*.apk
+/libc++_shared.so
+/libc++_shared.so.meta
+/libopenxr_loader.so
+/libopenxr_loader.so.meta
+/QCAR
+/*.sln
+/.vs
+/.vscode
+/Assets/Samples/*
+/Assets/Samples.meta
+/Packages/*
+/Build/*

+ 8 - 0
Assets/Agora-RTC-Plugin.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a2fb2627347a94307aaff8ad64afcd73
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 432304418feee4a6fbc9a8fea4084f9c
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/AppIdInput.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 0c9562e6bbfaa40be845a370896502de
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 19 - 0
Assets/Agora-RTC-Plugin/API-Example/AppIdInput/AppIdInput.cs

@@ -0,0 +1,19 @@
+using UnityEngine;
+using UnityEngine.Events;
+using System.Collections;
+using System;
+using UnityEngine.Serialization;
+
+[CreateAssetMenu(menuName = "Agora/AppIdInput", fileName = "AppIdInput", order = 1)]
+[Serializable]
+public class AppIdInput : ScriptableObject
+{
+    [FormerlySerializedAs("APP_ID")] [SerializeField]
+    public string appID = "";
+
+    [FormerlySerializedAs("TOKEN")] [SerializeField]
+    public string token = "";
+
+    [FormerlySerializedAs("CHANNEL_NAME")] [SerializeField]
+    public string channelName = "YOUR_CHANNEL_NAME";
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/AppIdInput/AppIdInput.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 310468f085ef24732beac714c9bb64fd
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Editor.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e9abbe316aff44acd967104a06f35b46
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 214 - 0
Assets/Agora-RTC-Plugin/API-Example/Editor/CommandBuild.cs

@@ -0,0 +1,214 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEditor.Build;
+#if UNITY_2018_4_OR_NEWER
+using UnityEditor.Build.Reporting;
+#endif 
+using UnityEngine;
+
+public class CommandBuild : MonoBehaviour
+{
+
+
+    private static string[] GetAllScenes()
+    {
+        string[] scenes = new string[] {
+            "Assets/API-Example/HomeScene.unity",
+            "Assets/API-Example/Examples/Basic/JoinChannelVideo/BasicVideoCallScene.unity",
+            "Assets/API-Example/Examples/Basic/JoinChannelAudio/BasicAudioCallScene.unity",
+
+            "Assets/API-Example/Examples/Advanced/AudioMixing/AudioMixingScene.unity",
+            "Assets/API-Example/Examples/Advanced/AudioSpectrum/AudioSpectrumScene.unity",
+            "Assets/API-Example/Examples/Advanced/ChannelMediaRelay/ChannelMediaRelayScene.unity",
+            "Assets/API-Example/Examples/Advanced/ContentInspect/ContentInspectScene.unity",
+            "Assets/API-Example/Examples/Advanced/CustomCaptureAudio/CustomCaptureAudioScene.unity",
+            "Assets/API-Example/Examples/Advanced/CustomCaptureVideo/CustomCaptureVideoScene.unity",
+            "Assets/API-Example/Examples/Advanced/CustomRenderAudio/CustomRenderAudioScene.unity",
+            "Assets/API-Example/Examples/Advanced/DeviceManager/DeviceManagerScene.unity",
+            "Assets/API-Example/Examples/Advanced/DualCamera/DualCameraScene.unity",
+            "Assets/API-Example/Examples/Advanced/JoinChannelVideoToken/JoinChannelVideoTokenScene.unity",
+            "Assets/API-Example/Examples/Advanced/JoinChannelWithUserAccount/JoinChannelWithUserAccountScene.unity",
+            "Assets/API-Example/Examples/Advanced/MediaPlayer/MediaPlayerScene.unity",
+            "Assets/API-Example/Examples/Advanced/MediaRecorder/MediaRecorderScene.unity",
+            "Assets/API-Example/Examples/Advanced/Metadata/MetadataScene.unity",
+            "Assets/API-Example/Examples/Advanced/ProcessAudioRawData/ProcessAudioRawDataScene.unity",
+            "Assets/API-Example/Examples/Advanced/ProcessVideoRawData/ProcessVideoRawDataScene.unity",
+            "Assets/API-Example/Examples/Advanced/PushEncodedVideoImage/PushEncodedVideoImageScene.unity",
+            "Assets/API-Example/Examples/Advanced/RtmpStreaming/RtmpStreamingScene.unity",
+            "Assets/API-Example/Examples/Advanced/ScreenShare/ScreenShareScene.unity",
+            "Assets/API-Example/Examples/Advanced/ScreenShareWhileVideoCall/ScreenShareWhileVideoCallScene.unity",
+            "Assets/API-Example/Examples/Advanced/SetBeautyEffectOptions/SetBeautyEffectOptionsScene.unity",
+            "Assets/API-Example/Examples/Advanced/SetEncryption/SetEncryptionScene.unity",
+            "Assets/API-Example/Examples/Advanced/SetVideoEncodeConfiguration/SetVideoEncodeConfigurationScene.unity",
+            "Assets/API-Example/Examples/Advanced/SpatialAudioWithMediaPlayer/SpatialAudioWithMediaPlayerScene.unity",
+            "Assets/API-Example/Examples/Advanced/StartDirectCdnStreaming/StartDirectCdnStreamingScene.unity",
+            "Assets/API-Example/Examples/Advanced/StartLocalVideoTranscoder/StartLocalVideoTranscoderScene.unity",
+            "Assets/API-Example/Examples/Advanced/StartRhythmPlayer/StartRhythmPlayerScene.unity",
+            "Assets/API-Example/Examples/Advanced/StartRtmpStreamWithTranscoding/StartRtmpStreamWithTranscodingScene.unity",
+            "Assets/API-Example/Examples/Advanced/StreamMessage/StreamMessageScene.unity",
+            "Assets/API-Example/Examples/Advanced/TakeSnapshot/TakeSnapshotScene.unity",
+            "Assets/API-Example/Examples/Advanced/VirtualBackground/VirtualBackgroundScene.unity",
+            "Assets/API-Example/Examples/Advanced/VoiceChanger/VoiceChangerScene.unity"
+        };
+        return scenes;
+    }
+
+    [MenuItem("Build/Android")]
+    public static void BuildAndrod()
+    {
+
+        BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
+        buildPlayerOptions.scenes = GetAllScenes();
+        buildPlayerOptions.locationPathName = "../Build/Android.apk";
+        buildPlayerOptions.target = BuildTarget.Android;
+        buildPlayerOptions.options = BuildOptions.None;
+
+#if UNITY_2018_4_OR_NEWER
+        BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
+        BuildSummary summary = report.summary;
+
+        if (summary.result == BuildResult.Succeeded)
+        {
+            Debug.Log("Build Android succeeded: " + summary.totalSize + " bytes");
+        }
+
+        if (summary.result == BuildResult.Failed)
+        {
+            Debug.Log("Build Android failed");
+        }
+#else
+        string message = BuildPipeline.BuildPlayer(buildPlayerOptions);
+        Debug.Log("Build Android: " + message);
+#endif
+    }
+
+
+    [MenuItem("Build/IPhone")]
+    public static void BuildIPhone()
+    {
+        BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
+        buildPlayerOptions.scenes = GetAllScenes();
+        buildPlayerOptions.locationPathName = "../Build/IPhone";
+        buildPlayerOptions.target = BuildTarget.iOS;
+        buildPlayerOptions.options = BuildOptions.None;
+
+#if UNITY_2018_4_OR_NEWER
+        BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
+        BuildSummary summary = report.summary;
+
+        if (summary.result == BuildResult.Succeeded)
+        {
+            Debug.Log("Build IPhone succeeded: " + summary.totalSize + " bytes");
+        }
+
+        if (summary.result == BuildResult.Failed)
+        {
+            Debug.Log("Build IPhone failed");
+        }
+#else
+        string message = BuildPipeline.BuildPlayer(buildPlayerOptions);
+        Debug.Log("Build IPhone: " + message);
+#endif
+    }
+
+    [MenuItem("Build/Mac")]
+    public static void BuildMac()
+    {
+        BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
+        buildPlayerOptions.scenes = GetAllScenes();
+        buildPlayerOptions.locationPathName = "../Build/Mac.app";
+        buildPlayerOptions.target = BuildTarget.StandaloneOSX;
+        buildPlayerOptions.options = BuildOptions.None;
+
+#if UNITY_2018_4_OR_NEWER
+        BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
+        BuildSummary summary = report.summary;
+
+        if (summary.result == BuildResult.Succeeded)
+        {
+            Debug.Log("Build Mac succeeded: " + summary.totalSize + " bytes");
+        }
+
+        if (summary.result == BuildResult.Failed)
+        {
+            Debug.Log("Build Mac failed");
+        }
+#else
+        string message = BuildPipeline.BuildPlayer(buildPlayerOptions);
+        Debug.Log("Build Mac: " + message);
+#endif
+    }
+
+
+    [MenuItem("Build/x86")]
+    public static void BuildWin32()
+    {
+
+        BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
+        buildPlayerOptions.scenes = GetAllScenes();
+        buildPlayerOptions.locationPathName = "../Build/x86/x86.exe";
+        buildPlayerOptions.target = BuildTarget.StandaloneWindows;
+        buildPlayerOptions.options = BuildOptions.None;
+
+#if UNITY_2018_4_OR_NEWER
+        BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
+        BuildSummary summary = report.summary;
+
+        if (summary.result == BuildResult.Succeeded)
+        {
+            Debug.Log("Build x86 succeeded: " + summary.totalSize + " bytes");
+        }
+
+        if (summary.result == BuildResult.Failed)
+        {
+            Debug.Log("Build x86 failed");
+        }
+#else
+        string message = BuildPipeline.BuildPlayer(buildPlayerOptions);
+        Debug.Log("Build Win32: " + message);
+#endif
+
+    }
+
+    [MenuItem("Build/x86_64")]
+    public static void BuildWin64()
+    {
+        BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
+        buildPlayerOptions.scenes = GetAllScenes();
+        buildPlayerOptions.locationPathName = "../Build/x86_64/x86_64.exe";
+        buildPlayerOptions.target = BuildTarget.StandaloneWindows64;
+        buildPlayerOptions.options = BuildOptions.None;
+
+#if UNITY_2018_4_OR_NEWER
+        BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
+        BuildSummary summary = report.summary;
+
+        if (summary.result == BuildResult.Succeeded)
+        {
+            Debug.Log("Build x86_64 succeeded: " + summary.totalSize + " bytes");
+        }
+
+        if (summary.result == BuildResult.Failed)
+        {
+            Debug.Log("Build x86_64 failed");
+        }
+#else
+        string message = BuildPipeline.BuildPlayer(buildPlayerOptions);
+        Debug.Log("Build x86_64: " + message);
+#endif
+
+    }
+
+    [MenuItem("Build/All")]
+    public static void BuildAll()
+    {
+        BuildAndrod();
+        BuildIPhone();
+        BuildMac();
+        BuildWin32();
+        BuildWin64();
+    }
+
+
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Editor/CommandBuild.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: eb3b50c9e751a4456a735673fa3790b4
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: bfa5b5c7902844bfaada79d754ec6abf
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3471b5d11c96b463cb0cdefa61f1bafa
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/AudioMixing.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c7911fdd16738474c83026ed49c980c2
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 268 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/AudioMixing/AudioMixing.cs

@@ -0,0 +1,268 @@
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.AudioMixing
+{
+    public class AudioMixing : MonoBehaviour
+    {
+        [FormerlySerializedAs("AppIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        [SerializeField] public string Sound_URL = "";
+
+        private string _localPath = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+        // Start is called before the first frame update
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitRtcEngine();
+                SetupUI();
+                // enable it after joining
+                EnableUI(false);
+                JoinChannel();
+            }
+        }
+
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitRtcEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void SetupUI()
+        {
+            _mixingButton = GameObject.Find("MixButton").GetComponent<Button>();
+            _mixingButton.onClick.AddListener(HandleAudioMixingButton);
+            _effectButton = GameObject.Find("EffectButton").GetComponent<Button>();
+            _effectButton.onClick.AddListener(HandleEffectButton);
+            _urlToggle = GameObject.Find("Toggle").GetComponent<Toggle>();
+            _loopbackToggle = GameObject.Find("Loopback").GetComponent<Toggle>();
+
+
+#if UNITY_ANDROID && !UNITY_EDITOR
+            // On Android, the StreamingAssetPath is just accessed by /assets instead of Application.streamingAssetPath
+            _localPath = "/assets/audio/Agora.io-Interactions.mp3";
+#else
+            _localPath = Application.streamingAssetsPath + "/audio/" + "Agora.io-Interactions.mp3";
+#endif
+            Log.UpdateLog(string.Format("the audio file path: {0}", _localPath));
+
+        }
+
+        internal void EnableUI(bool enable)
+        {
+            _mixingButton.enabled = enable;
+            _effectButton.enabled = enable;
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.JoinChannel(_token, _channelName);
+        }
+
+        #region -- Test Control logic ---
+
+        private void StartAudioMixing()
+        {
+            Debug.Log("Playing with " + (_urlToggle.isOn ? "URL" : "local file"));
+
+            var ret = RtcEngine.StartAudioMixing(_urlToggle.isOn ? Sound_URL : _localPath, _loopbackToggle.isOn, 1);
+            Debug.Log("StartAudioMixing returns: " + ret);
+        }
+
+        private void PlayEffectTest()
+        {
+            Debug.Log("Playing with " + (_urlToggle.isOn ? "URL" : "local file"));
+            RtcEngine.PlayEffect(1, _urlToggle.isOn ? Sound_URL : _localPath, 1, 1.0, 0, 100, true);
+        }
+
+        private void StopEffectTest()
+        {
+            RtcEngine.StopAllEffects();
+        }
+
+        #endregion
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+
+        #region -- Application UI Logic ---
+
+        private bool _isMixing = false;
+        private Button _mixingButton { get; set; }
+
+        private void HandleAudioMixingButton()
+        {
+            if (_effectOn)
+            {
+                Log.UpdateLog("Testing Effect right now, can't play effect...");
+                return;
+            }
+
+            if (_isMixing)
+            {
+                RtcEngine.StopAudioMixing();
+            }
+            else
+            {
+                StartAudioMixing();
+            }
+
+            _isMixing = !_isMixing;
+            _mixingButton.GetComponentInChildren<Text>().text = (_isMixing ? "Stop Mixing" : "Start Mixing");
+        }
+
+
+        private bool _effectOn = false;
+        private Button _effectButton { get; set; }
+
+        private void HandleEffectButton()
+        {
+            if (_isMixing)
+            {
+                Log.UpdateLog("Testing Mixing right now, can't play effect...");
+                return;
+            }
+
+            if (_effectOn)
+            {
+                StopEffectTest();
+            }
+            else
+            {
+                PlayEffectTest();
+            }
+
+            _effectOn = !_effectOn;
+            _effectButton.GetComponentInChildren<Text>().text = (_effectOn ? "Stop Effect" : "Play Effect");
+        }
+
+       
+        private Toggle _urlToggle { get; set; }
+        private Toggle _loopbackToggle { get; set; }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly AudioMixing _audioMixing;
+
+        internal UserEventHandler(AudioMixing audioMixing)
+        {
+            _audioMixing = audioMixing;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _audioMixing.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _audioMixing.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _audioMixing.RtcEngine.GetVersion(ref build)));
+            _audioMixing.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                    connection.channelId, connection.localUid, elapsed));
+            _audioMixing.EnableUI(true);
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _audioMixing.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _audioMixing.Log.UpdateLog("OnLeaveChannel");
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _audioMixing.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _audioMixing.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _audioMixing.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+        }
+
+        public override void OnAudioMixingStateChanged(AUDIO_MIXING_STATE_TYPE state, AUDIO_MIXING_REASON_TYPE errorCode)
+        {
+            _audioMixing.Log.UpdateLog(string.Format("AUDIO_MIXING_STATE_TYPE: ${0}, AUDIO_MIXING_REASON_TYPE: ${1}",
+                state, errorCode));
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/AudioMixing/AudioMixing.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 57cd6320e47d7411ab4e5461a6d705db
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/AudioSpectrum.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9aa3c408eebd74effa7ea2d087d2d208
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 507 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/AudioSpectrum/AudioSpectrum.cs

@@ -0,0 +1,507 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.AudioSpectrum
+{
+    public class AudioSpectrum : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        public RectTransform spectrums;
+        public List<float> data = new List<float>();
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+        internal IMediaPlayer MediaPlayer = null;
+
+        private const string MPK_URL =
+            "https://agoracdn.s3.us-west-1.amazonaws.com/videos/Agora.io-Interactions.mp4";
+
+        private Button _button1;
+        private Button _button2;
+        private Button _button3;
+        private Button _button4;
+        private Button _button5;
+        private Toggle _urlToggle;
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                SetUpUI();
+                EnableUI(false);
+                InitEngine();
+                InitMediaPlayer();
+                JoinChannelWithMPK();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+
+            lock (data)
+            {
+                if (data.Count > 0)
+                { 
+                    for (var i = 0; i < this.data.Count; i++)
+                    {
+                        var height = (-data[i] + 1);
+                        if (height <= 1) height = 1;
+                        var child = (RectTransform)this.spectrums.GetChild(i);
+                        child.sizeDelta = new Vector2(15, height);
+                    }
+                }
+                data.Clear();
+            }
+        }
+
+        private void SetUpUI()
+        {
+            _button1 = GameObject.Find("Button1").GetComponent<Button>();
+            _button1.onClick.AddListener(OnPlayButtonPress);
+            _button2 = GameObject.Find("Button2").GetComponent<Button>();
+            _button2.onClick.AddListener(OnStopButtonPress);
+            _button3 = GameObject.Find("Button3").GetComponent<Button>();
+            _button3.onClick.AddListener(OnPauseButtonPress);
+            _button4 = GameObject.Find("Button4").GetComponent<Button>();
+            _button4.onClick.AddListener(OnResumeButtonPress);
+            _button5 = GameObject.Find("Button5").GetComponent<Button>();
+            _button5.onClick.AddListener(OnOpenButtonPress);
+            _urlToggle = GameObject.Find("UrlToggle").GetComponent<Toggle>();
+        }
+
+        public void EnableUI(bool val)
+        {
+            var obj = this.transform.Find("Button1").gameObject;
+            obj.SetActive(val);
+
+            obj = this.transform.Find("Button2").gameObject;
+            obj.SetActive(val);
+
+            obj = this.transform.Find("Button3").gameObject;
+            obj.SetActive(val);
+
+            obj = this.transform.Find("Button4").gameObject;
+            obj.SetActive(val);
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_GAME_STREAMING);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void InitMediaPlayer()
+        {
+            MediaPlayer = RtcEngine.CreateMediaPlayer();
+            if (MediaPlayer == null)
+            {
+                this.Log.UpdateLog("CreateMediaPlayer failed!");
+                return;
+            }
+
+            MpkEventHandler handler = new MpkEventHandler(this);
+            MediaPlayer.InitEventHandler(handler);
+            this.Log.UpdateLog("playerId id: " + MediaPlayer.GetId());
+
+            MediaPlayer.RegisterMediaPlayerAudioSpectrumObserver(new UserAudioSpectrumObserver(this), 16);
+        }
+
+        private void JoinChannelWithMPK()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+
+            ChannelMediaOptions options = new ChannelMediaOptions();
+            options.autoSubscribeAudio.SetValue(true);
+            options.autoSubscribeVideo.SetValue(true);
+            options.publishCustomAudioTrack.SetValue(false);
+            options.publishCameraTrack.SetValue(false);
+            options.publishMediaPlayerAudioTrack.SetValue(true);
+            options.publishMediaPlayerVideoTrack.SetValue(true);
+            options.publishMediaPlayerId.SetValue(MediaPlayer.GetId());
+            options.enableAudioRecordingOrPlayout.SetValue(true);
+            options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            var ret = RtcEngine.JoinChannel(_token, _channelName, 0, options);
+            this.Log.UpdateLog("RtcEngineController JoinChannel_MPK returns: " + ret);
+        }
+
+        private void OnPlayButtonPress()
+        {
+            var ret = MediaPlayer.Play();
+            this.Log.UpdateLog("Play return" + ret);
+            this.TestMediaPlayer();
+        }
+
+        private void OnStopButtonPress()
+        {
+            var ret = MediaPlayer.Stop();
+            this.Log.UpdateLog("Stop return" + ret);
+        }
+
+        private void OnPauseButtonPress()
+        {
+            var ret = MediaPlayer.Pause();
+            this.Log.UpdateLog("Pause return" + ret);
+        }
+
+        private void OnResumeButtonPress()
+        {
+            var ret = MediaPlayer.Resume();
+
+            this.Log.UpdateLog("Resume returns: " + ret);
+        }
+
+        private void OnOpenButtonPress()
+        {
+            string path = null;
+            if (this._urlToggle.isOn)
+            {
+                path = MPK_URL;
+            }
+            else
+            {
+#if UNITY_ANDROID && !UNITY_EDITOR
+                // On Android, the StreamingAssetPath is just accessed by /assets instead of Application.streamingAssetPath
+                path = "/assets/img/MPK.mov";
+#else
+                path = Path.Combine(Application.streamingAssetsPath, "img/MPK.mov");
+#endif
+            }
+            this.Log.UpdateLog("Is opening : " + path);
+            var ret = MediaPlayer.Open(path, 0);
+            this.Log.UpdateLog("Open returns: " + ret);
+        }
+
+        private void TestMediaPlayer()
+        {
+            long duration = 0;
+            var ret = MediaPlayer.GetDuration(ref duration);
+            Debug.Log("_mediaPlayer.GetDuration returns: " + ret + "duration: " + duration);
+
+            long pos = 0;
+            ret = MediaPlayer.GetPlayPosition(ref pos);
+            Debug.Log("_mediaPlayer.GetPlayPosition returns: " + ret + "position: " + pos);
+
+            Debug.Log("_mediaPlayer.GetState:" + MediaPlayer.GetState());
+
+            bool mute = true;
+            ret = MediaPlayer.GetMute(ref mute);
+            Debug.Log("_mediaPlayer.GetMute returns: " + ret + "mute: " + mute);
+
+            int volume = 0;
+            ret = MediaPlayer.GetPlayoutVolume(ref volume);
+            Debug.Log("_mediaPlayer.GetPlayoutVolume returns: " + ret + "volume: " + volume);
+
+            Debug.Log("SDK Version:" + MediaPlayer.GetPlayerSdkVersion());
+            Debug.Log("GetPlaySrc:" + MediaPlayer.GetPlaySrc());
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+
+            if (MediaPlayer != null)
+                RtcEngine.DestroyMediaPlayer(MediaPlayer);
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+            RtcEngine = null;
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "", VIDEO_SOURCE_TYPE videoSourceType = VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            videoSurface.SetForUser(uid, channelId, videoSourceType);
+            videoSurface.SetEnable(true);
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(1.0f, 1.333f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(4.5f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class MpkEventHandler : IMediaPlayerSourceObserver
+    {
+        private readonly AudioSpectrum _sample;
+
+        internal MpkEventHandler(AudioSpectrum sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnPlayerSourceStateChanged(MEDIA_PLAYER_STATE state, MEDIA_PLAYER_ERROR ec)
+        {
+            _sample.Log.UpdateLog(string.Format(
+                "OnPlayerSourceStateChanged state: {0}, ec: {1}, playId: {2}", state, ec, _sample.MediaPlayer.GetId()));
+            Debug.Log("OnPlayerSourceStateChanged");
+            if (state == MEDIA_PLAYER_STATE.PLAYER_STATE_OPEN_COMPLETED)
+            {
+                AudioSpectrum.MakeVideoView((uint)_sample.MediaPlayer.GetId(), "", VIDEO_SOURCE_TYPE.VIDEO_SOURCE_MEDIA_PLAYER);
+                _sample.EnableUI(true);
+                _sample.Log.UpdateLog("Open Complete. Click start to play media");
+            }
+            else if (state == MEDIA_PLAYER_STATE.PLAYER_STATE_STOPPED)
+            {
+                AudioSpectrum.DestroyVideoView((uint)_sample.MediaPlayer.GetId());
+                _sample.EnableUI(false);
+            }
+        }
+
+        public override void OnPlayerEvent(MEDIA_PLAYER_EVENT @event, Int64 elapsedTime, string message)
+        {
+            _sample.Log.UpdateLog(string.Format("OnPlayerEvent state: {0}", @event));
+        }
+    }
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly AudioSpectrum _sample;
+
+        internal UserEventHandler(AudioSpectrum sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _sample.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _sample.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _sample.RtcEngine.GetVersion(ref build)));
+            _sample.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                    connection.channelId, connection.localUid, elapsed));
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _sample.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _sample.Log.UpdateLog("OnLeaveChannel");
+            AudioSpectrum.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole,
+            CLIENT_ROLE_TYPE newRole)
+        {
+            _sample.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            AudioSpectrum.DestroyVideoView(uid);
+        }
+    }
+
+    internal class UserPlayerCustomDataProvider : IMediaPlayerCustomDataProvider
+    {
+        AudioSpectrum _sample;
+
+        internal UserPlayerCustomDataProvider(AudioSpectrum sample)
+        {
+            _sample = sample;
+        }
+
+        public override Int64 OnSeek(Int64 offset, int whence)
+        {
+            Debug.Log("UserPlayerCustomDataProvider OnSeek");
+            return 0;
+        }
+
+        public override int OnReadData(IntPtr bufferPtr, int bufferSize)
+        {
+            Debug.Log("UserPlayerCustomDataProvider OnReadData");
+            return 0;
+        }
+    }
+
+    internal class UserAudioSpectrumObserver : IAudioSpectrumObserver
+    {
+        AudioSpectrum _sample;
+        bool s = true;
+
+        internal UserAudioSpectrumObserver(AudioSpectrum sample)
+        {
+            this._sample = sample;
+        }
+
+        public override bool OnLocalAudioSpectrum(AudioSpectrumData data)
+        {
+            if (data.dataLength > 0)
+            {
+                lock (this._sample.data)
+                {
+                    this._sample.data.Clear();
+                    var interval = (int)(data.dataLength / 15);
+                    for (var i = 0; i < 15; i++)
+                    {
+                        this._sample.data.Add(data.audioSpectrumData[i * interval]);
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        public override bool OnRemoteAudioSpectrum(UserAudioSpectrumInfo[] spectrums, uint spectrumNumber)
+        {
+            return true;
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/AudioSpectrum/AudioSpectrum.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 31ecc6b1a291a4d09933603b5ef51581
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ChannelMediaRelay.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c84a753d319054ec3968a0ca12a35c0b
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 356 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ChannelMediaRelay/ChannelMediaRelay.cs

@@ -0,0 +1,356 @@
+using UnityEngine;
+using UnityEngine.UI;
+using Agora.Rtc;
+using Agora.Util;
+using UnityEngine.Serialization;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.ChannelMediaRelay
+{
+    public class ChannelMediaRelay : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        public string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        public string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        public string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitEngine();
+                SetupUI();
+                EnableUI(false);
+                JoinChannel();
+            }
+        }
+
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_GAME_STREAMING);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.JoinChannel(_token, _channelName, "");
+        }
+
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        private void SetupUI()
+        {
+            var ui = this.transform.Find("UI");
+
+            var btn = ui.Find("StartButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnStartButtonClick);
+
+            btn = ui.Find("UpdateButton").GetComponent<Button>();
+            btn.onClick.AddListener(onUpdateButtonClick);
+
+            btn = ui.Find("StopButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnStopButtonClick);
+
+            btn = ui.Find("PauseAllButton").GetComponent<Button>();
+            btn.onClick.AddListener(onPauseAllButtonClick);
+
+            btn = ui.Find("ResumAllButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnResumeAllButtonClick);
+        }
+
+        public void EnableUI(bool visible)
+        {
+            var ui = this.transform.Find("UI");
+            ui.gameObject.SetActive(visible);
+        }
+
+        private void OnStartButtonClick()
+        {
+            ChannelMediaRelayConfiguration config = new ChannelMediaRelayConfiguration();
+            config.srcInfo = new ChannelMediaInfo
+            {
+                channelName = this._appIdInput.channelName,
+                uid = 0,
+                token = this._appIdInput.token
+            };
+
+            //you can relay to another channels (limit max is 4)
+            config.destInfos = new ChannelMediaInfo[1];
+            config.destInfos[0] = new ChannelMediaInfo
+            {
+                channelName = this._appIdInput.channelName + "_2",
+                uid = 0,
+                token = this._appIdInput.token
+            };
+            config.destCount = 1;
+
+            var nRet = RtcEngine.StartChannelMediaRelay(config);
+            this.Log.UpdateLog("StartChannelMediaRelay nRet:" + nRet + " new ChannelName: " + this._appIdInput.channelName + "_2");
+        }
+
+        private void onUpdateButtonClick()
+        {
+            ChannelMediaRelayConfiguration config = new ChannelMediaRelayConfiguration();
+            config.srcInfo = new ChannelMediaInfo
+            {
+                channelName = this._appIdInput.channelName,
+                uid = 0,
+                token = this._appIdInput.token
+            };
+
+            config.destInfos = new ChannelMediaInfo[1];
+            config.destInfos[0] = new ChannelMediaInfo
+            {
+                channelName = this._appIdInput.channelName + "_3",
+                uid = 0,
+                token = this._appIdInput.token
+            };
+            config.destCount = 1;
+
+            //after StartChannelMediaRelay you can use StartChannelMediaRelay to remove or relay to anthoner channel
+            var nRet = RtcEngine.UpdateChannelMediaRelay(config);
+            this.Log.UpdateLog("UpdateChannelMediaRelay nRet:" + nRet + " new ChannelName: " + this._appIdInput.channelName + "_3");
+        }
+
+        private void onPauseAllButtonClick()
+        {
+            var nRet = RtcEngine.PauseAllChannelMediaRelay();
+            this.Log.UpdateLog("onPauseAllButtonClick nRet:" + nRet);
+        }
+
+        private void OnResumeAllButtonClick()
+        {
+            var nRet = RtcEngine.ResumeAllChannelMediaRelay();
+            this.Log.UpdateLog("OnResumeAllButtonClick nRet:" + nRet);
+        }
+
+        private void OnStopButtonClick()
+        {
+            var nRet = RtcEngine.StopChannelMediaRelay();
+            this.Log.UpdateLog("OnStopButtonClick nRet:" + nRet);
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+            RtcEngine = null;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "", VIDEO_SOURCE_TYPE type = VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            videoSurface.SetForUser(uid, channelId, type);
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+            videoSurface.SetEnable(true);
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+        
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly ChannelMediaRelay _channelMediaRelay;
+
+        internal UserEventHandler(ChannelMediaRelay videoSample)
+        {
+            _channelMediaRelay = videoSample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _channelMediaRelay.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            Debug.Log("Agora: OnJoinChannelSuccess ");
+            _channelMediaRelay.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _channelMediaRelay.RtcEngine.GetVersion(ref build)));
+            _channelMediaRelay.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+            _channelMediaRelay.EnableUI(true);
+            ChannelMediaRelay.MakeVideoView(0);
+
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _channelMediaRelay.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _channelMediaRelay.Log.UpdateLog("OnLeaveChannel");
+            _channelMediaRelay.EnableUI(false);
+            ChannelMediaRelay.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _channelMediaRelay.Log.UpdateLog(string.Format("OnClientRoleChanged {0}, {1}", oldRole, newRole));
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _channelMediaRelay.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            ChannelMediaRelay.MakeVideoView(uid, _channelMediaRelay._channelName, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _channelMediaRelay.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            ChannelMediaRelay.DestroyVideoView(uid);
+        }
+
+        public override void OnChannelMediaRelayEvent(int code)
+        {
+            _channelMediaRelay.Log.UpdateLog(string.Format("OnChannelMediaRelayEvent: {0}", code));
+
+        }
+
+        public override void OnChannelMediaRelayStateChanged(int state, int code)
+        {
+            _channelMediaRelay.Log.UpdateLog(string.Format("OnChannelMediaRelayStateChanged state: {0}, code: {1}", state, code));
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ChannelMediaRelay/ChannelMediaRelay.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1657ff169d52b409fbd5d4f29fa97852
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ContentInspect.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: fb9a487c180f7463bbc7b25c20ef3070
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 312 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ContentInspect/ContentInspect.cs

@@ -0,0 +1,312 @@
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.ContentInspect
+{
+    public class ContentInspect : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitEngine();
+                JoinChannel();
+                SetupUI();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                                        CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                                        AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.JoinChannel(_token, _channelName);
+        }
+
+        private void SetupUI()
+        {
+            var ui = this.transform.Find("UI");
+
+            var btn = ui.Find("StartButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnStartButtonClick);
+
+            btn = ui.Find("StopButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnStopButtonClick);
+        }
+
+        private void OnStartButtonClick()
+        {
+            var config = new ContentInspectConfig();
+            config.modules = new ContentInspectModule[1];
+            config.modules[0] = new ContentInspectModule
+            {
+                type = CONTENT_INSPECT_TYPE.CONTENT_INSPECT_MODERATION,
+                interval = 1
+            };
+            config.moduleCount = 1;
+
+            var nRet = RtcEngine.EnableContentInspect(true, config);
+            this.Log.UpdateLog("StartContentInspect: " + nRet);
+        }
+
+        private void OnStopButtonClick()
+        {
+            var config = new ContentInspectConfig();
+            config.modules = new ContentInspectModule[1];
+            config.modules[0] = new ContentInspectModule
+            {
+                type = CONTENT_INSPECT_TYPE.CONTENT_INSPECT_MODERATION,
+                interval = 1
+            };
+            config.moduleCount = 1;
+
+            var nRet = RtcEngine.EnableContentInspect(false, config);
+            this.Log.UpdateLog("StopContentInspect: " + nRet);
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "")
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            if (uid == 0)
+            {
+                videoSurface.SetForUser(uid, channelId);
+            }
+            else
+            {
+                videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+
+            videoSurface.SetEnable(true);
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            var yPos = Random.Range(3.0f, 5.0f);
+            var xPos = Random.Range(-2.0f, 2.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly ContentInspect _sample;
+
+        internal UserEventHandler(ContentInspect sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _sample.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            Debug.Log("Agora: OnJoinChannelSuccess ");
+            _sample.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _sample.RtcEngine.GetVersion(ref build)));
+            _sample.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+
+            ContentInspect.MakeVideoView(0);
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _sample.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _sample.Log.UpdateLog("OnLeaveChannel");
+            ContentInspect.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _sample.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            ContentInspect.MakeVideoView(uid, _sample.GetChannelName());
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            ContentInspect.DestroyVideoView(uid);
+        }
+
+        public override void OnContentInspectResult(CONTENT_INSPECT_RESULT result)
+        {
+            _sample.Log.UpdateLog("OnContentInspectResult :" + result);
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ContentInspect/ContentInspect.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3601d8801ddb2432d860bf3d1748c042
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomCaptureAudio.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5a34acf477f4447ab9577b51afc55b61
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 274 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomCaptureAudio/CustomCaptureAudio.cs

@@ -0,0 +1,274 @@
+using System;
+using System.Threading;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using System.Runtime.InteropServices;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+using RingBuffer;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.CustomCaptureAudio
+{
+    public class CustomCaptureAudio : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+        private const int CHANNEL = 1;
+        // Please do not change this value because Unity re-samples the sample rate to 48000.
+        private const int SAMPLE_RATE = 48000;
+        private const int PUSH_FREQ_PER_SEC = 100;
+
+        private RingBuffer<byte> _audioBuffer;
+        private bool _startConvertSignal = false;
+
+        private Thread _pushAudioFrameThread;
+        private System.Object _pushAudioFrameThreadSignal = new System.Object();
+        private int _count;
+        private bool _startSignal = false;
+
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitRtcEngine();
+                SetExternalAudioSource();
+                JoinChannel();
+                StartPushAudioFrame();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitRtcEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(new UserEventHandler(this));
+        }
+
+        private void SetExternalAudioSource()
+        {
+            var nRet = RtcEngine.SetExternalAudioSource(true, SAMPLE_RATE, CHANNEL, 1);
+            this.Log.UpdateLog("SetExternalAudioSource nRet:" + nRet);
+        }
+
+        private void StartPushAudioFrame()
+        {
+            // 1-sec-length buffer
+            var bufferLength = SAMPLE_RATE * CHANNEL;
+            _audioBuffer = new RingBuffer<byte>(bufferLength, true);
+            _startConvertSignal = true;
+
+            _pushAudioFrameThread = new Thread(PushAudioFrameThread);
+            _pushAudioFrameThread.Start();
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.SetAudioProfile(AUDIO_PROFILE_TYPE.AUDIO_PROFILE_MUSIC_HIGH_QUALITY,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.EnableAudio();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.JoinChannel(_token, _channelName, "");
+        }
+
+        private void OnLeaveBtnClick()
+        {
+            RtcEngine.LeaveChannel();
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            lock (_pushAudioFrameThreadSignal)
+            {
+                RtcEngine.InitEventHandler(null);
+                RtcEngine.LeaveChannel();
+                RtcEngine.Dispose();
+                RtcEngine = null;
+            }
+        }
+
+        private void PushAudioFrameThread()
+        {
+            var bytesPerSample = 2;
+            var type = AUDIO_FRAME_TYPE.FRAME_TYPE_PCM16;
+            var channels = CHANNEL;
+            var samples = SAMPLE_RATE / PUSH_FREQ_PER_SEC;
+            var samplesPerSec = SAMPLE_RATE;
+            var buffer = new byte[samples * bytesPerSample * CHANNEL];
+            var freq = 1000 / PUSH_FREQ_PER_SEC;
+
+            var tic = DateTime.Now;
+
+
+            IntPtr audioFrameBuffer = Marshal.AllocHGlobal(buffer.Length);
+            var audioFrame = new AudioFrame
+            {
+                bytesPerSample = BYTES_PER_SAMPLE.TWO_BYTES_PER_SAMPLE,
+                type = type,
+                samplesPerChannel = samples,
+                samplesPerSec = samplesPerSec,
+                channels = channels,
+                buffer = (UInt64)audioFrameBuffer,
+                bufferPtr = audioFrameBuffer,
+                RawBuffer = buffer,
+                renderTimeMs = freq
+            };
+
+            while (true)
+            {
+                lock (_pushAudioFrameThreadSignal)
+                {
+                    if (RtcEngine == null)
+                    {
+                        break;
+                    }
+                    var toc = DateTime.Now;
+
+                    if ((toc - tic).Milliseconds >= freq)
+                    {
+                        lock (_audioBuffer)
+                        {
+                            if (_audioBuffer.Size > samples * bytesPerSample * CHANNEL)
+                            {
+                                for (var j = 0; j < samples * bytesPerSample * CHANNEL; j++)
+                                {
+                                    buffer[j] = _audioBuffer.Get();
+                                }
+
+                                Marshal.Copy(buffer, 0, audioFrame.bufferPtr, buffer.Length);
+
+                                var ret = RtcEngine.PushAudioFrame(MEDIA_SOURCE_TYPE.AUDIO_PLAYOUT_SOURCE, audioFrame);
+                                Debug.Log("PushAudioFrame returns: " + ret);
+
+                                tic = toc;
+                            }
+                            else
+                            {
+                                tic = tic.AddMilliseconds(1);
+                            }
+                        }
+
+                    }
+                }
+                Thread.Sleep(1);
+            }
+
+
+            Marshal.FreeHGlobal(audioFrameBuffer);
+        }
+
+        private void OnAudioFilterRead(float[] data, int channels)
+        {
+            if (!_startConvertSignal) return;
+            var rescaleFactor = 32767;
+            foreach (var t in data)
+            {
+                var sample = t;
+                if (sample > 1) sample = 1;
+                else if (sample < -1) sample = -1;
+
+                var shortData = (short)(sample * rescaleFactor);
+                var byteArr = new byte[2];
+                byteArr = BitConverter.GetBytes(shortData);
+                lock (_audioBuffer)
+                {
+                    _audioBuffer.Put(byteArr[0]);
+                    _audioBuffer.Put(byteArr[1]);
+                }
+            }
+
+            //_count += 1;
+            //if (_count == 20) _startSignal = true;
+        }
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly CustomCaptureAudio _customAudioSource;
+
+        internal UserEventHandler(CustomCaptureAudio customAudioSource)
+        {
+            _customAudioSource = customAudioSource;
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _customAudioSource.Log.UpdateLog(string.Format("sdk version: {0}",
+                _customAudioSource.RtcEngine.GetVersion(ref build)));
+            _customAudioSource.Log.UpdateLog(string.Format(
+                "onJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}", connection.channelId,
+                connection.localUid, elapsed));
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _customAudioSource.Log.UpdateLog("OnLeaveChannelSuccess");
+        }
+
+        public override void OnError(int error, string msg)
+        {
+            _customAudioSource.Log.UpdateLog(string.Format("OnSDKError error: {0}, msg: {1}", error, msg));
+        }
+
+        public override void OnConnectionLost(RtcConnection connection)
+        {
+            _customAudioSource.Log.UpdateLog(string.Format("OnConnectionLost "));
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomCaptureAudio/CustomCaptureAudio.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ab653d0f03a8349cfb346755779d7f57
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomCaptureVideo.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2652585d787414ea8ab39bf8721d8a7b
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 341 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomCaptureVideo/CustomCaptureVideo.cs

@@ -0,0 +1,341 @@
+using System.Collections;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+#if UNITY_2018_1_OR_NEWER
+using Unity.Collections;
+#endif
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.CustomCaptureVideo
+{
+    public class CustomCaptureVideo : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+       
+        private Texture2D _texture;
+        private Rect _rect;
+        private int i = 0;
+        private WebCamTexture _webCameraTexture;
+        public RawImage RawImage;
+        public Vector2 CameraSize = new Vector2(640, 480);
+        public int CameraFPS = 15;
+        private byte[] _shareData;
+
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitCameraDevice();
+                InitTexture();
+                InitEngine();
+                SetExternalVideoSource();
+                JoinChannel();
+            }
+        }
+
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            StartCoroutine(ShareScreen());
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private IEnumerator ShareScreen()
+        {
+            yield return new WaitForEndOfFrame();
+            IRtcEngine rtc = Agora.Rtc.RtcEngine.Instance;
+            if (rtc != null)
+            {
+                _texture.ReadPixels(_rect, 0, 0);
+                _texture.Apply();
+
+#if UNITY_2018_1_OR_NEWER
+                NativeArray<byte> nativeByteArray = _texture.GetRawTextureData<byte>();
+                if (_shareData?.Length != nativeByteArray.Length)
+                {
+                    _shareData = new byte[nativeByteArray.Length];
+                }
+                nativeByteArray.CopyTo(_shareData);
+#else
+                _shareData = _texture.GetRawTextureData();
+#endif
+
+                ExternalVideoFrame externalVideoFrame = new ExternalVideoFrame();
+                externalVideoFrame.type = VIDEO_BUFFER_TYPE.VIDEO_BUFFER_RAW_DATA;
+                externalVideoFrame.format = VIDEO_PIXEL_FORMAT.VIDEO_PIXEL_RGBA;
+                externalVideoFrame.buffer = _shareData;
+                externalVideoFrame.stride = (int)_rect.width;
+                externalVideoFrame.height = (int)_rect.height;
+                externalVideoFrame.cropLeft = 10;
+                externalVideoFrame.cropTop = 10;
+                externalVideoFrame.cropRight = 10;
+                externalVideoFrame.cropBottom = 10;
+                externalVideoFrame.rotation = 180;
+                externalVideoFrame.timestamp = System.DateTime.Now.Ticks / 10000;
+                var ret = rtc.PushVideoFrame(externalVideoFrame);
+                Debug.Log("PushVideoFrame ret = " + ret + "time: " + System.DateTime.Now.Millisecond);
+            }
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void SetExternalVideoSource()
+        {
+            var ret = RtcEngine.SetExternalVideoSource(true, false, EXTERNAL_VIDEO_SOURCE_TYPE.VIDEO_FRAME, new SenderOptions());
+            this.Log.UpdateLog("SetExternalVideoSource returns:" + ret);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.JoinChannel(_token, _channelName);
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in Canvas!!!!");
+        }
+
+        private void InitTexture()
+        {
+            _rect = new UnityEngine.Rect(0, 0, Screen.width, Screen.height);
+            _texture = new Texture2D((int)_rect.width, (int)_rect.height, TextureFormat.RGBA32, false);
+        }
+
+        private void InitCameraDevice()
+        {
+            WebCamDevice[] devices = WebCamTexture.devices;
+            _webCameraTexture = new WebCamTexture(devices[0].name, (int)CameraSize.x, (int)CameraSize.y, CameraFPS);
+            RawImage.texture = _webCameraTexture;
+            _webCameraTexture.Play();
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (_webCameraTexture)
+            {
+                _webCameraTexture.Stop();
+            }
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "")
+        {
+            GameObject go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            VideoSurface videoSurface = makeImageSurface(uid.ToString());
+            if (!ReferenceEquals(videoSurface, null))
+            {
+                // configure videoSurface
+                if (uid == 0)
+                {
+                    videoSurface.SetForUser(uid, channelId);
+                }
+                else
+                {
+                    videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+                }
+
+                videoSurface.OnTextureSizeModify += (int width, int height) =>
+                {
+                    float scale = (float)height / (float)width;
+                    videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                    Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+                };
+
+                videoSurface.SetEnable(true);
+            }
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            GameObject go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, .5f);
+
+            // configure videoSurface
+            VideoSurface videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface makeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            GameObject canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(3f, 4f, 1f);
+
+            // configure videoSurface
+            VideoSurface videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            GameObject go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly CustomCaptureVideo _customCaptureVideo;
+
+        internal UserEventHandler(CustomCaptureVideo customCaptureVideo)
+        {
+            _customCaptureVideo = customCaptureVideo;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _customCaptureVideo.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _customCaptureVideo.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _customCaptureVideo.RtcEngine.GetVersion(ref build)));
+            _customCaptureVideo.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                    connection.channelId, connection.localUid, elapsed));
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _customCaptureVideo.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _customCaptureVideo.Log.UpdateLog("OnLeaveChannel");
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole,
+            CLIENT_ROLE_TYPE newRole)
+        {
+            _customCaptureVideo.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _customCaptureVideo.Log.UpdateLog(
+                string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            CustomCaptureVideo.MakeVideoView(uid, _customCaptureVideo.GetChannelName());
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _customCaptureVideo.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            CustomCaptureVideo.DestroyVideoView(uid);
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomCaptureVideo/CustomCaptureVideo.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d9ca5ff1572d84b0fafd27a976831922
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomRenderAudio.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 80a1da53c02684b69b3de73f03d0dafe
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 273 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomRenderAudio/CustomRenderAudio.cs

@@ -0,0 +1,273 @@
+using System;
+using System.Threading;
+using System.Runtime.InteropServices;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+using RingBuffer;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.CustomRenderAudio
+{
+    public class CustomRenderAudio : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine;
+
+
+        private const int CHANNEL = 1;
+        private const int SAMPLE_RATE = 44100;
+        private const int PULL_FREQ_PER_SEC = 100;
+
+
+        private RingBuffer<float> _audioBuffer;
+        private AudioClip _audioClip;
+
+
+        private Thread _pullAudioFrameThread;
+        private System.Object _pullAudioFrameThreadSignal = new System.Object();
+
+        private int _writeCount;
+        private int _readCount;
+
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitRtcEngine();
+                JoinChannel();
+                var aud = InitAudioSource();
+                StartPullAudioFrame(aud, "externalClip");
+            }
+        }
+
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private void InitRtcEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            //be care, enableAudioDevice need be false
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                                        CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                                        AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            var ret = RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            //no enableAudioDevice to set false? how this methond work?
+            var nRet = RtcEngine.SetExternalAudioSink(true,SAMPLE_RATE, CHANNEL);
+            this.Log.UpdateLog("SetExternalAudioSink ret:" + nRet);
+            RtcEngine.JoinChannel(_token, _channelName);
+        }
+
+        private AudioSource InitAudioSource()
+        {
+            var aud = GetComponent<AudioSource>();
+            if (aud == null)
+            {
+                aud = gameObject.AddComponent<AudioSource>();
+            }
+            return aud;
+        }
+
+        private void StartPullAudioFrame(AudioSource aud, string clipName)
+        {
+
+            // 1-sec-length buffer
+            var bufferLength = SAMPLE_RATE * CHANNEL;
+            _audioBuffer = new RingBuffer<float>(bufferLength, true);
+
+            _pullAudioFrameThread = new Thread(PullAudioFrameThread);
+            _pullAudioFrameThread.Start();
+
+            _audioClip = AudioClip.Create(clipName,
+                SAMPLE_RATE / PULL_FREQ_PER_SEC * CHANNEL, CHANNEL, SAMPLE_RATE, true,
+                OnAudioRead);
+            aud.clip = _audioClip;
+            aud.loop = true;
+            aud.Play();
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            lock (_pullAudioFrameThreadSignal)
+            {
+                if (RtcEngine == null) return;
+                RtcEngine.InitEventHandler(null);
+                RtcEngine.LeaveChannel();
+                RtcEngine.Dispose();
+                RtcEngine = null;
+            }
+        }
+
+        private void PullAudioFrameThread()
+        {
+            var avsync_type = 0;
+            var bytesPerSample = 2;
+            var type = AUDIO_FRAME_TYPE.FRAME_TYPE_PCM16;
+            var channels = CHANNEL;
+            var samples = SAMPLE_RATE / PULL_FREQ_PER_SEC * CHANNEL;
+            var samplesPerSec = SAMPLE_RATE;
+            var buffer = new byte[samples * bytesPerSample];
+            var freq = 1000 / PULL_FREQ_PER_SEC;
+
+            var tic = new TimeSpan(DateTime.Now.Ticks);
+
+            AudioFrame audioFrame = new AudioFrame(type, samples, BYTES_PER_SAMPLE.TWO_BYTES_PER_SAMPLE, channels, samplesPerSec, buffer, 0, avsync_type);
+            IntPtr audioFrameBuffer = Marshal.AllocHGlobal(samples * bytesPerSample * channels);
+            audioFrame.buffer = (UInt64)audioFrameBuffer;
+            audioFrame.bufferPtr = audioFrameBuffer;
+
+            while (true)
+            {
+                lock (_pullAudioFrameThreadSignal)
+                {
+                    if (RtcEngine == null)
+                    {
+                        break;
+                    }
+
+                    var toc = new TimeSpan(DateTime.Now.Ticks);
+                    if (toc.Subtract(tic).Duration().Milliseconds >= freq)
+                    {
+                        tic = new TimeSpan(DateTime.Now.Ticks);
+                        var ret = RtcEngine.PullAudioFrame(audioFrame);
+
+                        Debug.Log("PullAudioFrame returns: " + ret);
+
+                        if (ret == 0)
+                        {
+                            Marshal.Copy((IntPtr)audioFrame.buffer, audioFrame.RawBuffer, 0, audioFrame.RawBuffer.Length);
+                            var floatArray = ConvertByteToFloat16(audioFrame.RawBuffer);
+                            lock (_audioBuffer)
+                            {
+                                _audioBuffer.Put(floatArray);
+                            }
+
+                            _writeCount += floatArray.Length;
+                        }
+                    }
+                }
+                Thread.Sleep(1);
+            }
+
+            Marshal.FreeHGlobal(audioFrameBuffer);
+        }
+
+        private static float[] ConvertByteToFloat16(byte[] byteArray)
+        {
+            var floatArray = new float[byteArray.Length / 2];
+            for (var i = 0; i < floatArray.Length; i++)
+            {
+                floatArray[i] = BitConverter.ToInt16(byteArray, i * 2) / 32768f; // -Int16.MinValue
+            }
+
+            return floatArray;
+        }
+
+        private void OnAudioRead(float[] data)
+        {
+            //if (!_startSignal) return;
+            for (var i = 0; i < data.Length; i++)
+            {
+                lock (_audioBuffer)
+                {
+                    if (_audioBuffer.Count > 0)
+                    {
+                        data[i] = _audioBuffer.Get();
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+
+                //readCount += 1;
+            }
+
+            Debug.LogFormat("buffer length remains: {0}", _writeCount - _readCount);
+        }
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly CustomRenderAudio _customAudioSinkSample;
+
+        internal UserEventHandler(CustomRenderAudio customAudioSinkSample)
+        {
+            _customAudioSinkSample = customAudioSinkSample;
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _customAudioSinkSample.Log.UpdateLog(string.Format("sdk version: {0}", _customAudioSinkSample.RtcEngine.GetVersion(ref build)));
+            _customAudioSinkSample.Log.UpdateLog(string.Format("onJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}", connection.channelId,
+                connection.localUid, elapsed));
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _customAudioSinkSample.Log.UpdateLog("OnLeaveChannelSuccess");
+        }
+
+        public override void OnError(int error, string msg)
+        {
+            _customAudioSinkSample.Log.UpdateLog(string.Format("OnSDKError error: {0}, msg: {1}", error, msg));
+        }
+
+        public override void OnConnectionLost(RtcConnection connection)
+        {
+            _customAudioSinkSample.Log.UpdateLog(string.Format("OnConnectionLost "));
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/CustomRenderAudio/CustomRenderAudio.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5307a59e386ff48d0933377f2d714592
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/DualCamera.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 31a9f19e6c6e1429d8b85a75a69c6f10
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 452 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/DualCamera/DualCamera.cs

@@ -0,0 +1,452 @@
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.DualCamera
+{
+    public class DualCamera : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngineEx RtcEngine = null;
+
+        internal bool IsChannelJoined = false;
+
+        private IVideoDeviceManager _videoDeviceManager;
+        private Agora.Rtc.DeviceInfo[] _videoDeviceInfos;
+        private CameraCapturerConfiguration _config1;
+        private CameraCapturerConfiguration _config2;
+
+        public uint UID1 = 123;
+        public uint UID2 = 456;
+
+        public Button MainPublishButton;
+        public Button MainUnpublishButton;
+        public Button SecondPublishButton;
+        public Button SecondUnpublishButton;
+
+        // Use this for initialization
+        private void Start()
+        {
+#if UNITY_IPHONE || UNITY_ANDROID
+            this.LogText.text = "iOS/Android is not supported, but you could see how it works on the Editor for Windows/MacOS";
+
+#else
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitEngine();
+                GetVideoDeviceManager();
+            }
+#endif
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngineEx();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        public void MainCameraJoinChannel()
+        {
+            RtcEngine.StartPreview();
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+
+            var ret = RtcEngine.StartPrimaryCameraCapture(_config1);
+            Log.UpdateLog(
+                string.Format("StartPrimaryCameraCapture returns: {0}", ret));
+            ChannelMediaOptions options1 = new ChannelMediaOptions();
+            options1.publishCameraTrack.SetValue(true);
+            options1.autoSubscribeAudio.SetValue(true);
+            options1.autoSubscribeVideo.SetValue(true);
+            options1.publishScreenTrack.SetValue(false);
+            options1.enableAudioRecordingOrPlayout.SetValue(true);
+            options1.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            ret = RtcEngine.JoinChannel(_token, _channelName, UID1, options1);
+            Debug.Log("MainCameraJoinChannel returns: " + ret);
+        }
+
+        public void MainCameraLeaveChannel()
+        {
+            RtcEngine.StopPrimaryCameraCapture();
+            var ret = RtcEngine.LeaveChannel();
+            Debug.Log("MainCameraLeaveChannel returns: " + ret);
+        }
+
+        public void MainCameraPublish()
+        {
+            ChannelMediaOptions options1 = new ChannelMediaOptions();
+            options1.publishCameraTrack.SetValue(true);
+            options1.publishMicrophoneTrack.SetValue(true);
+            var connection = new RtcConnection();
+            connection.channelId = _channelName;
+            connection.localUid = UID1;
+            RtcEngine.UpdateChannelMediaOptionsEx(options1, connection);
+
+
+            MainPublishButton.gameObject.SetActive(false);
+            MainUnpublishButton.gameObject.SetActive(true);
+
+        }
+
+        public void MainCameraUnPublish()
+        {
+            ChannelMediaOptions options1 = new ChannelMediaOptions();
+            options1.publishCameraTrack.SetValue(false);
+            options1.publishMicrophoneTrack.SetValue(false);
+            var connection = new RtcConnection();
+            connection.channelId = _channelName;
+            connection.localUid = UID1;
+            RtcEngine.UpdateChannelMediaOptions(options1);
+
+            MainPublishButton.gameObject.SetActive(true);
+            MainUnpublishButton.gameObject.SetActive(false);
+        }
+
+
+        public void SecondCameraJoinChannel()
+        {
+            var ret = RtcEngine.StartSecondaryCameraCapture(_config2);
+            Log.UpdateLog(
+                string.Format("StartSecondaryCameraCapture returns: {0}", ret));
+            ChannelMediaOptions options2 = new ChannelMediaOptions();
+            options2.autoSubscribeAudio.SetValue(false);
+            options2.autoSubscribeVideo.SetValue(false);
+            options2.publishCustomAudioTrack.SetValue(false);
+            options2.publishCameraTrack.SetValue(false);
+            options2.publishSecondaryCameraTrack.SetValue(true);
+            options2.enableAudioRecordingOrPlayout.SetValue(false);
+            options2.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            ret = RtcEngine.JoinChannelEx(_token, new RtcConnection(_channelName, UID2), options2);
+            Debug.Log("JoinChannelEx returns: " + ret);
+        }
+
+        public void SecondCameraLeaveChannel()
+        {
+            RtcEngine.StopSecondaryCameraCapture();
+            var ret = RtcEngine.LeaveChannelEx(new RtcConnection(_channelName, 456));
+            Debug.Log("SecondCameraLeaveChannel returns: " + ret);
+        }
+
+        public void SecondCameraPublish()
+        {
+            ChannelMediaOptions options1 = new ChannelMediaOptions();
+            options1.publishSecondaryCameraTrack.SetValue(true);
+
+            var connection = new RtcConnection();
+            connection.channelId = _channelName;
+            connection.localUid = UID2;
+            RtcEngine.UpdateChannelMediaOptionsEx(options1, connection);
+
+            SecondPublishButton.gameObject.SetActive(false);
+            SecondUnpublishButton.gameObject.SetActive(true);
+
+        }
+
+        public void SecondCameraUnpublish()
+        {
+            ChannelMediaOptions options1 = new ChannelMediaOptions();
+            options1.publishSecondaryCameraTrack.SetValue(false);
+
+            var connection = new RtcConnection();
+            connection.channelId = _channelName;
+            connection.localUid = UID2;
+            RtcEngine.UpdateChannelMediaOptionsEx(options1, connection);
+
+            SecondPublishButton.gameObject.SetActive(true);
+            SecondUnpublishButton.gameObject.SetActive(false);
+        }
+
+
+        private void GetVideoDeviceManager()
+        {
+            _videoDeviceManager = RtcEngine.GetVideoDeviceManager();
+            _videoDeviceInfos = _videoDeviceManager.EnumerateVideoDevices();
+            Log.UpdateLog(string.Format("VideoDeviceManager count: {0}", _videoDeviceInfos.Length));
+            for (var i = 0; i < _videoDeviceInfos.Length; i++)
+            {
+                Log.UpdateLog(string.Format("VideoDeviceManager device index: {0}, name: {1}, id: {2}", i,
+                    _videoDeviceInfos[i].deviceName, _videoDeviceInfos[i].deviceId));
+            }
+
+            _config1 = new CameraCapturerConfiguration();
+            _config1.deviceId = _videoDeviceInfos[0].deviceId;
+            Debug.Log("PrimaryCamera: " + _config1.deviceId);
+            _config1.format = new VideoFormat();
+
+            if (_videoDeviceInfos.Length > 1)
+            {
+                _config2 = new CameraCapturerConfiguration();
+                _config2.deviceId = _videoDeviceInfos[1].deviceId;
+                Debug.Log("SecondaryCamera: " + _config2.deviceId);
+                _config2.format = new VideoFormat();
+            }
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.StopSecondaryCameraCapture();
+            RtcEngine.StopPrimaryCameraCapture();
+            RtcEngine.LeaveChannelEx(new RtcConnection(_channelName, 456));
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "", VIDEO_SOURCE_TYPE videoSourceType = VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            VideoSurface videoSurface = null;
+
+            if (videoSourceType == VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA)
+            {
+                videoSurface = MakeImageSurface("MainCameraView");
+            }
+            else if (videoSourceType == VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA_SECONDARY)
+            {
+                videoSurface = MakeImageSurface("SecondCameraView");
+            }
+            else
+            {
+                videoSurface = MakeImageSurface(uid.ToString());
+            }
+
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            videoSurface.SetForUser(uid, channelId, videoSourceType);
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+
+            videoSurface.SetEnable(true);
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.Find(goName);
+            if (!ReferenceEquals(go, null))
+            {
+                return null; // reuse
+            }
+
+            go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(string name)
+        {
+            var go = GameObject.Find(name);
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly DualCamera _videoSample;
+
+        internal UserEventHandler(DualCamera videoSample)
+        {
+            _videoSample = videoSample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _videoSample.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _videoSample.IsChannelJoined = true;
+            _videoSample.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _videoSample.RtcEngine.GetVersion(ref build)));
+            _videoSample.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                    connection.channelId, connection.localUid, elapsed));
+
+            if (connection.localUid == _videoSample.UID1)
+            {
+                DualCamera.MakeVideoView(0);
+            }
+
+            if (connection.localUid == _videoSample.UID2)
+            {
+                DualCamera.MakeVideoView(0, "", VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA_SECONDARY);
+            }
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _videoSample.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _videoSample.IsChannelJoined = false;
+            _videoSample.Log.UpdateLog("OnLeaveChannel");
+            if (connection.localUid == _videoSample.UID1)
+            {
+                DualCamera.DestroyVideoView("MainCameraView");
+            }
+
+            if (connection.localUid == _videoSample.UID2)
+            {
+                DualCamera.DestroyVideoView("SecondCameraView");
+            }
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole,
+            CLIENT_ROLE_TYPE newRole)
+        {
+            _videoSample.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _videoSample.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            if (uid != _videoSample.UID1 && uid != _videoSample.UID2)
+            {
+                DualCamera.MakeVideoView(uid, _videoSample.GetChannelName(), VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _videoSample.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            if (uid != _videoSample.UID1 && uid != _videoSample.UID2)
+            {
+                DualCamera.DestroyVideoView(uid.ToString());
+            }
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/DualCamera/DualCamera.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cb516eeea082d4300a7ed7579ed00480
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/JoinChannelVideoToken.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 44666835b1d13446aac71e2873aa7df3
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 319 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/JoinChannelVideoToken/JoinChannelVideoToken.cs

@@ -0,0 +1,319 @@
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.JoinChannelVideoToken
+{
+    public class JoinChannelVideoToken : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+        internal static string _channelToken = "";
+        internal static string _tokenBase = "http://localhost:8080";
+        internal CONNECTION_STATE_TYPE _state = CONNECTION_STATE_TYPE.CONNECTION_STATE_DISCONNECTED;
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitEngine();
+                JoinChannel();
+            }
+        }
+
+        internal void RenewOrJoinToken(string newToken)
+        {
+            JoinChannelVideoToken._channelToken = newToken;
+            if (_state == CONNECTION_STATE_TYPE.CONNECTION_STATE_DISCONNECTED
+                || _state == CONNECTION_STATE_TYPE.CONNECTION_STATE_DISCONNECTED
+                || _state == CONNECTION_STATE_TYPE.CONNECTION_STATE_FAILED
+            )
+            {
+                // If we are not connected yet, connect to the channel as normal
+                JoinChannel();
+            }
+            else
+            {
+                // If we are already connected, we should just update the token
+                UpdateToken();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        private void UpdateToken()
+        {
+            RtcEngine.RenewToken(JoinChannelVideoToken._channelToken);
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelToken = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+
+            if (_channelToken.Length == 0)
+            {
+                StartCoroutine(HelperClass.FetchToken(_tokenBase, _channelName, 0, this.RenewOrJoinToken));
+                return;
+            }
+
+            RtcEngine.JoinChannel(_channelToken, _channelName, "");
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "")
+        {
+            GameObject go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            VideoSurface videoSurface = MakeImageSurface(uid.ToString());
+            if (!ReferenceEquals(videoSurface, null))
+            {
+                // configure videoSurface
+                if (uid == 0)
+                {
+                    videoSurface.SetForUser(uid, channelId);
+                }
+                else
+                {
+                    videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+                }
+
+                videoSurface.OnTextureSizeModify += (int width, int height) =>
+                {
+                    float scale = (float)height / (float)width;
+                    videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                    Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+                };
+
+                videoSurface.SetEnable(true);
+            }
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            GameObject go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, .5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            GameObject canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(3f, 4f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            GameObject go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Object.Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly JoinChannelVideoToken _helloVideoTokenAgora;
+
+        internal UserEventHandler(JoinChannelVideoToken helloVideoTokenAgora)
+        {
+            _helloVideoTokenAgora = helloVideoTokenAgora;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _helloVideoTokenAgora.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _helloVideoTokenAgora.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _helloVideoTokenAgora.RtcEngine.GetVersion(ref build)));
+            _helloVideoTokenAgora.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                    connection.channelId, connection.localUid, elapsed));
+            _helloVideoTokenAgora.Log.UpdateLog(string.Format("New Token: {0}",
+                JoinChannelVideoToken._channelToken));
+            // HelperClass.FetchToken(tokenBase, channelName, 0, this.RenewOrJoinToken);
+            JoinChannelVideoToken.MakeVideoView(0);
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _helloVideoTokenAgora.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _helloVideoTokenAgora.Log.UpdateLog("OnLeaveChannel");
+            JoinChannelVideoToken.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole,
+            CLIENT_ROLE_TYPE newRole)
+        {
+            _helloVideoTokenAgora.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _helloVideoTokenAgora.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid,
+                elapsed));
+            JoinChannelVideoToken.MakeVideoView(uid, _helloVideoTokenAgora.GetChannelName());
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _helloVideoTokenAgora.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            JoinChannelVideoToken.DestroyVideoView(uid);
+        }
+
+        public override void OnTokenPrivilegeWillExpire(RtcConnection connection, string token)
+        {
+            _helloVideoTokenAgora.StartCoroutine(HelperClass.FetchToken(JoinChannelVideoToken._tokenBase,
+                _helloVideoTokenAgora.GetChannelName(), 0, _helloVideoTokenAgora.RenewOrJoinToken));
+        }
+
+        public override void OnConnectionStateChanged(RtcConnection connection, CONNECTION_STATE_TYPE state,
+            CONNECTION_CHANGED_REASON_TYPE reason)
+        {
+            _helloVideoTokenAgora._state = state;
+        }
+
+        public override void OnConnectionLost(RtcConnection connection)
+        {
+            _helloVideoTokenAgora.Log.UpdateLog(string.Format("OnConnectionLost "));
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/JoinChannelVideoToken/JoinChannelVideoToken.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 67b051f83b55948329bc204f42908378
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/JoinChannelWithUserAccount.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: cd876ff00de7b4130b164d1d0ede84fd
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 272 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/JoinChannelWithUserAccount/JoinChannelWithUserAccount.cs

@@ -0,0 +1,272 @@
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.JoinChannelWithUserAccount
+{
+    public class JoinChannelWithUserAccount : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+        private const string USER_ACCOUNT = "Unity";
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitEngine();
+                JoinChannel();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                                        CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                                        AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.JoinChannelWithUserAccount(_token, _channelName, USER_ACCOUNT);
+        }
+
+        public void GetUserInfoByUserAccount()
+        {
+            Agora.Rtc.UserInfo info = new Agora.Rtc.UserInfo();
+            RtcEngine.GetUserInfoByUserAccount(USER_ACCOUNT, ref info);
+            Log.UpdateLog(string.Format("GetUserInfoByUserAccount account: {0}, uid: {1}", info.userAccount, info.uid));
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "")
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            if (uid == 0)
+            {
+                videoSurface.SetForUser(uid, channelId);
+            }
+            else
+            {
+                videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+
+            videoSurface.SetEnable(true);
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly JoinChannelWithUserAccount _videoSample;
+
+        internal UserEventHandler(JoinChannelWithUserAccount videoSample)
+        {
+            _videoSample = videoSample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _videoSample.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            Debug.Log("Agora: OnJoinChannelSuccess ");
+            _videoSample.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _videoSample.RtcEngine.GetVersion(ref build)));
+            _videoSample.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+
+            JoinChannelWithUserAccount.MakeVideoView(0);
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _videoSample.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _videoSample.Log.UpdateLog("OnLeaveChannel");
+            JoinChannelWithUserAccount.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _videoSample.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _videoSample.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            JoinChannelWithUserAccount.MakeVideoView(uid, _videoSample.GetChannelName());
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _videoSample.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            JoinChannelWithUserAccount.DestroyVideoView(uid);
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/JoinChannelWithUserAccount/JoinChannelWithUserAccount.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 42dad12b5c0114b60902fdf27bee810f
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaPlayer.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 8394696a4099143b38f98641d47d2ef2
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 551 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaPlayer/MediaPlayerExample.cs

@@ -0,0 +1,551 @@
+using System;
+using System.IO;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.MediaPlayer
+{
+    public class MediaPlayerExample : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+        internal IMediaPlayer MediaPlayer = null;
+
+        private const string MPK_URL = "https://agoracdn.s3.us-west-1.amazonaws.com/videos/Agora.io-Interactions.mp4";
+        private const string PRELOAD_URL = "https://agoracdn.s3.us-west-1.amazonaws.com/videos/Agora+Quality+Comparison+Jellyfish.mp4";
+
+        private Button _button1;
+        private Button _button2;
+        private Button _button3;
+        private Button _button4;
+        private Button _button5;
+        private Button _button6;
+        private Button _button7;
+        private Button _button8;
+        private Button _button9;
+        private Toggle _urlToggle;
+        private Toggle _loopToggle;
+        private InputField _inputField;
+       
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                SetUpUI();
+                EnableUI(false);
+                InitEngine();
+                InitMediaPlayer();
+                JoinChannelWithMPK();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        private void SetUpUI()
+        {
+            _button1 = GameObject.Find("Button1").GetComponent<Button>();
+            _button1.onClick.AddListener(OnPlayButtonPress);
+            _button2 = GameObject.Find("Button2").GetComponent<Button>();
+            _button2.onClick.AddListener(OnStopButtonPress);
+            _button3 = GameObject.Find("Button3").GetComponent<Button>();
+            _button3.onClick.AddListener(OnPauseButtonPress);
+            _button4 = GameObject.Find("Button4").GetComponent<Button>();
+            _button4.onClick.AddListener(OnResumeButtonPress);
+            _button5 = GameObject.Find("Button5").GetComponent<Button>();
+            _button5.onClick.AddListener(OnOpenButtonPress);
+            _button6 = GameObject.Find("Button6").GetComponent<Button>();
+            _button6.onClick.AddListener(OnPreloadSrcButtonClick);
+            _button7 = GameObject.Find("Button7").GetComponent<Button>();
+            _button7.onClick.AddListener(OnPlayPreloadButtonClick);
+            _button8 = GameObject.Find("Button8").GetComponent<Button>();
+            _button8.onClick.AddListener(OnStartPublishButtonClick);
+            _button8.gameObject.SetActive(false);
+            _button9 = GameObject.Find("Button9").GetComponent<Button>();
+            _button9.onClick.AddListener(OnStopPublishButtonClick);
+         
+
+            _urlToggle = GameObject.Find("UrlToggle").GetComponent<Toggle>();
+            _loopToggle = GameObject.Find("LoopToggle").GetComponent<Toggle>();
+            _inputField = GameObject.Find("InputField").GetComponent<InputField>();
+        }
+
+        public void EnableUI(bool val)
+        {
+            var obj = this.transform.Find("Button1").gameObject;
+            obj.SetActive(val);
+
+            obj = this.transform.Find("Button2").gameObject;
+            obj.SetActive(val);
+
+            obj = this.transform.Find("Button3").gameObject;
+            obj.SetActive(val);
+
+            obj = this.transform.Find("Button4").gameObject;
+            obj.SetActive(val);
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_GAME_STREAMING);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+            var logFile = Application.persistentDataPath + "/rtc.log";
+            RtcEngine.SetLogFile(logFile);
+            this.Log.UpdateLog("logFile:" + logFile);
+        }
+
+        private void InitMediaPlayer()
+        {
+            MediaPlayer = RtcEngine.CreateMediaPlayer();
+            if (MediaPlayer == null)
+            {
+                this.Log.UpdateLog("CreateMediaPlayer failed!");
+                return;
+            }
+
+            MpkEventHandler handler = new MpkEventHandler(this);
+            MediaPlayer.InitEventHandler(handler);
+            this.Log.UpdateLog("playerId id: " + MediaPlayer.GetId());
+        }
+
+
+        private void JoinChannelWithMPK()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+
+            ChannelMediaOptions options = new ChannelMediaOptions();
+            options.autoSubscribeAudio.SetValue(true);
+            options.autoSubscribeVideo.SetValue(true);
+            options.publishCustomAudioTrack.SetValue(false);
+            options.publishCameraTrack.SetValue(false);
+            options.publishMediaPlayerAudioTrack.SetValue(true);
+            options.publishMediaPlayerVideoTrack.SetValue(true);
+            options.publishMediaPlayerId.SetValue(MediaPlayer.GetId());
+            options.enableAudioRecordingOrPlayout.SetValue(true);
+            options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            var ret = RtcEngine.JoinChannel(_token, _channelName, 0, options);
+            this.Log.UpdateLog("RtcEngineController JoinChannel_MPK returns: " + ret);
+        }
+
+        private void OnPlayButtonPress()
+        {
+            if (this.IsLoop())
+            {
+                MediaPlayer.SetLoopCount(-1);
+            }
+            else
+            {
+                MediaPlayer.SetLoopCount(0);
+            }
+            var ret = MediaPlayer.Play();
+            this.Log.UpdateLog("Play return" + ret);
+            this.TestMediaPlayer();
+        }
+
+        private void OnStopButtonPress()
+        {
+            var ret = MediaPlayer.Stop();
+            this.Log.UpdateLog("Stop return" + ret);
+        }
+
+        private void OnPauseButtonPress()
+        {
+            var ret = MediaPlayer.Pause();
+            this.Log.UpdateLog("Pause return" + ret);
+        }
+
+        private void OnResumeButtonPress()
+        {
+            var ret = MediaPlayer.Resume();
+
+            this.Log.UpdateLog("Resume returns: " + ret);
+        }
+
+        private void OnOpenButtonPress()
+        {
+            string path = null;
+            if (this._urlToggle.isOn)
+            {
+                if (this._inputField.text == "")
+                {
+                    path = MPK_URL;
+                }
+                else
+                {
+                    path = this._inputField.text;
+                }
+            }
+            else
+            {
+#if UNITY_ANDROID && !UNITY_EDITOR
+                // On Android, the StreamingAssetPath is just accessed by /assets instead of Application.streamingAssetPath
+                path = "/assets/img/MPK.mov";
+#else
+                path = Path.Combine(Application.streamingAssetsPath, "img/MPK.mov");
+#endif
+            }
+            this.Log.UpdateLog("Is opening : " + path);
+            var ret = MediaPlayer.Open(path, 0);
+            this.Log.UpdateLog("Open returns: " + ret);
+        }
+
+        private void OnOpenWithCustomSource()
+        {
+            var ret = MediaPlayer.OpenWithCustomSource(0, new UserPlayerCustomDataProvider(this));
+            this.Log.UpdateLog("OpenWithCustomSource" + ret);
+        }
+
+
+        private void OnPreloadSrcButtonClick()
+        {
+
+            var nRet = MediaPlayer.PreloadSrc(PRELOAD_URL, 0);
+            this.Log.UpdateLog("PreloadSrc: " + nRet);
+        }
+
+        private void OnPlayPreloadButtonClick()
+        {
+
+            var nRet = MediaPlayer.PlayPreloadedSrc(PRELOAD_URL);
+            this.Log.UpdateLog("PlayPreloadedSrc: " + nRet);
+        }
+
+        private void TestMediaPlayer()
+        {
+            long duration = 0;
+            var ret = MediaPlayer.GetDuration(ref duration);
+            Debug.Log("_mediaPlayer.GetDuration returns: " + ret + "duration: " + duration);
+
+            long pos = 0;
+            ret = MediaPlayer.GetPlayPosition(ref pos);
+            Debug.Log("_mediaPlayer.GetPlayPosition returns: " + ret + "position: " + pos);
+
+            Debug.Log("_mediaPlayer.GetState:" + MediaPlayer.GetState());
+
+            bool mute = true;
+            ret = MediaPlayer.GetMute(ref mute);
+            Debug.Log("_mediaPlayer.GetMute returns: " + ret + "mute: " + mute);
+
+            int volume = 0;
+            ret = MediaPlayer.GetPlayoutVolume(ref volume);
+            Debug.Log("_mediaPlayer.GetPlayoutVolume returns: " + ret + "volume: " + volume);
+
+            Debug.Log("SDK Version:" + MediaPlayer.GetPlayerSdkVersion());
+            Debug.Log("GetPlaySrc:" + MediaPlayer.GetPlaySrc());
+        }
+
+        private void OnStartPublishButtonClick()
+        {
+            var options = new ChannelMediaOptions();
+            options.publishMediaPlayerVideoTrack.SetValue(true);
+            options.publishMediaPlayerAudioTrack.SetValue(true);
+            options.publishMediaPlayerId.SetValue(MediaPlayer.GetId());
+            var nRet = RtcEngine.UpdateChannelMediaOptions(options);
+            this.Log.UpdateLog("UpdateChannelMediaOptions: " + nRet);
+
+            _button8.gameObject.SetActive(false);
+            _button9.gameObject.SetActive(true);
+        }
+
+        private void OnStopPublishButtonClick()
+        {
+            var options = new ChannelMediaOptions();
+            options.publishMediaPlayerVideoTrack.SetValue(false);
+            options.publishMediaPlayerAudioTrack.SetValue(false);
+            options.publishMediaPlayerId.SetValue(MediaPlayer.GetId());
+
+            options.publishCameraTrack.SetValue(false);
+            var nRet = RtcEngine.UpdateChannelMediaOptions(options);
+            this.Log.UpdateLog("UpdateChannelMediaOptions: " + nRet);
+
+            _button8.gameObject.SetActive(true);
+            _button9.gameObject.SetActive(false);
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+
+            if (MediaPlayer != null)
+                RtcEngine.DestroyMediaPlayer(MediaPlayer);
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+            RtcEngine = null;
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        internal bool IsLoop()
+        {
+            return this._loopToggle.isOn;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "", VIDEO_SOURCE_TYPE videoSourceType = VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            videoSurface.SetForUser(uid, channelId, videoSourceType);
+            videoSurface.SetEnable(true);
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(1.0f, 1.333f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(4.5f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class MpkEventHandler : IMediaPlayerSourceObserver
+    {
+        private readonly MediaPlayerExample _sample;
+
+        internal MpkEventHandler(MediaPlayerExample sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnPlayerSourceStateChanged(MEDIA_PLAYER_STATE state, MEDIA_PLAYER_ERROR ec)
+        {
+            _sample.Log.UpdateLog(string.Format(
+                "OnPlayerSourceStateChanged state: {0}, ec: {1}, playId: {2}", state, ec, _sample.MediaPlayer.GetId()));
+            Debug.Log("OnPlayerSourceStateChanged");
+            if (state == MEDIA_PLAYER_STATE.PLAYER_STATE_OPEN_COMPLETED)
+            {
+                MediaPlayerExample.MakeVideoView((uint)_sample.MediaPlayer.GetId(), "", VIDEO_SOURCE_TYPE.VIDEO_SOURCE_MEDIA_PLAYER);
+                _sample.EnableUI(true);
+                _sample.Log.UpdateLog("Open Complete. Click start to play media");
+            }
+            else if (state == MEDIA_PLAYER_STATE.PLAYER_STATE_STOPPED)
+            {
+                MediaPlayerExample.DestroyVideoView((uint)_sample.MediaPlayer.GetId());
+                _sample.EnableUI(false);
+            }
+        }
+
+        public override void OnPlayerEvent(MEDIA_PLAYER_EVENT @event, Int64 elapsedTime, string message)
+        {
+            _sample.Log.UpdateLog(string.Format("OnPlayerEvent state: {0}", @event));
+        }
+
+        public override void OnPreloadEvent(string src, PLAYER_PRELOAD_EVENT @event)
+        {
+            _sample.Log.UpdateLog(string.Format("OnPreloadEvent src: {0}, @event: {1}", src, @event));
+        }
+    }
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly MediaPlayerExample _sample;
+
+        internal UserEventHandler(MediaPlayerExample sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _sample.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _sample.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _sample.RtcEngine.GetVersion(ref build)));
+            _sample.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                    connection.channelId, connection.localUid, elapsed));
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _sample.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _sample.Log.UpdateLog("OnLeaveChannel");
+            MediaPlayerExample.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole,
+            CLIENT_ROLE_TYPE newRole)
+        {
+            _sample.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            MediaPlayerExample.DestroyVideoView(uid);
+        }
+    }
+
+    internal class UserPlayerCustomDataProvider : IMediaPlayerCustomDataProvider
+    {
+        MediaPlayerExample _sample;
+
+        internal UserPlayerCustomDataProvider(MediaPlayerExample sample)
+        {
+            _sample = sample;
+        }
+
+        public override Int64 OnSeek(Int64 offset, int whence)
+        {
+            Debug.Log("UserPlayerCustomDataProvider OnSeek");
+            return 0;
+        }
+
+        public override int OnReadData(IntPtr bufferPtr, int bufferSize)
+        {
+            Debug.Log("UserPlayerCustomDataProvider OnReadData");
+            return 0;
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaPlayer/MediaPlayerExample.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 85b4c122e7b7340b2b410b1d2a842d2f
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaPlayerWithCustomDataProvider.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 552a09dcc72b04946ae631c300f0f039
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 561 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaPlayerWithCustomDataProvider/MediaPlayerWithCustomDataProviderExample.cs

@@ -0,0 +1,561 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+using Random = UnityEngine.Random;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.MediaPlayerWithCustomDataProviderExample
+{
+    public class MediaPlayerWithCustomDataProviderExample : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+        internal IMediaPlayer MediaPlayer = null;
+        internal UserPlayerCustomDataProvider customDataProvider = null;
+
+
+        private Button _button1;
+        private Button _button2;
+        private Button _button3;
+        private Button _button4;
+        private Button _button5;
+
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                SetUpUI();
+                //EnableUI(false);
+                InitEngine();
+                InitMediaPlayer();
+                JoinChannelWithMPK();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        private void SetUpUI()
+        {
+            _button1 = GameObject.Find("Button1").GetComponent<Button>();
+            _button1.onClick.AddListener(OnPlayButtonPress);
+            _button2 = GameObject.Find("Button2").GetComponent<Button>();
+            _button2.onClick.AddListener(OnStopButtonPress);
+            _button3 = GameObject.Find("Button3").GetComponent<Button>();
+            _button3.onClick.AddListener(OnPauseButtonPress);
+            _button4 = GameObject.Find("Button4").GetComponent<Button>();
+            _button4.onClick.AddListener(OnResumeButtonPress);
+            _button5 = GameObject.Find("Button5").GetComponent<Button>();
+            _button5.onClick.AddListener(OnOpenButtonPress);
+        }
+
+        public void EnableUI(bool val)
+        {
+            var obj = this.transform.Find("Button1").gameObject;
+            obj.SetActive(val);
+
+            obj = this.transform.Find("Button2").gameObject;
+            obj.SetActive(val);
+
+            obj = this.transform.Find("Button3").gameObject;
+            obj.SetActive(val);
+
+            obj = this.transform.Find("Button4").gameObject;
+            obj.SetActive(val);
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_GAME_STREAMING);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+            var logFile = Application.persistentDataPath + "/rtc.log";
+            RtcEngine.SetLogFile(logFile);
+            this.Log.UpdateLog("logFile:" + logFile);
+        }
+
+        private void InitMediaPlayer()
+        {
+            MediaPlayer = RtcEngine.CreateMediaPlayer();
+            if (MediaPlayer == null)
+            {
+                this.Log.UpdateLog("CreateMediaPlayer failed!");
+                return;
+            }
+
+            MpkEventHandler handler = new MpkEventHandler(this);
+            MediaPlayer.InitEventHandler(handler);
+            this.Log.UpdateLog("playerId id: " + MediaPlayer.GetId());
+        }
+
+
+        private void JoinChannelWithMPK()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+
+            ChannelMediaOptions options = new ChannelMediaOptions();
+            options.autoSubscribeAudio.SetValue(true);
+            options.autoSubscribeVideo.SetValue(true);
+            options.publishCustomAudioTrack.SetValue(false);
+            options.publishCameraTrack.SetValue(false);
+            options.publishMediaPlayerAudioTrack.SetValue(true);
+            options.publishMediaPlayerVideoTrack.SetValue(true);
+            options.publishMediaPlayerId.SetValue(MediaPlayer.GetId());
+            options.enableAudioRecordingOrPlayout.SetValue(true);
+            options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            var ret = RtcEngine.JoinChannel(_token, _channelName, 0, options);
+            this.Log.UpdateLog("RtcEngineController JoinChannel_MPK returns: " + ret);
+        }
+
+        private void OnPlayButtonPress()
+        {
+            var ret = MediaPlayer.Play();
+            this.Log.UpdateLog("Play return" + ret);
+            //this.TestMediaPlayer();
+        }
+
+        private void OnStopButtonPress()
+        {
+            var ret = MediaPlayer.Stop();
+            this.Log.UpdateLog("Stop return" + ret);
+            this.customDataProvider.Close();
+        }
+
+        private void OnPauseButtonPress()
+        {
+            var ret = MediaPlayer.Pause();
+            this.Log.UpdateLog("Pause return" + ret);
+        }
+
+        private void OnResumeButtonPress()
+        {
+            var ret = MediaPlayer.Resume();
+
+            this.Log.UpdateLog("Resume returns: " + ret);
+        }
+
+        private void OnOpenButtonPress()
+        {
+            this.customDataProvider = new UserPlayerCustomDataProvider(this);
+            string file;
+#if UNITY_ANDROID && !UNITY_EDITOR
+        // On Android, the StreamingAssetPath is just accessed by /assets instead of Application.streamingAssetPath
+            file = "/assets/img/MPK.mp4";
+#else
+            file = Application.streamingAssetsPath + "/img/" + "MPK.mp4";
+#endif
+            this.customDataProvider.Open(file);
+
+            //var ret = MediaPlayer.OpenWithCustomSource(0, this.customDataProvider);
+            //this.Log.UpdateLog("OpenWithCustomSource: " + ret);
+
+
+            var source = new MediaSource();
+            source.url = null;
+            source.uri = null;
+            source.provider = this.customDataProvider;
+            source.autoPlay = false;
+            var ret = MediaPlayer.OpenWithMediaSource(source);
+            this.Log.UpdateLog("OpenWithMediaSource: " + ret);
+        }
+
+
+        private void TestMediaPlayer()
+        {
+            long duration = 0;
+            var ret = MediaPlayer.GetDuration(ref duration);
+            Debug.Log("_mediaPlayer.GetDuration returns: " + ret + "duration: " + duration);
+
+            long pos = 0;
+            ret = MediaPlayer.GetPlayPosition(ref pos);
+            Debug.Log("_mediaPlayer.GetPlayPosition returns: " + ret + "position: " + pos);
+
+            Debug.Log("_mediaPlayer.GetState:" + MediaPlayer.GetState());
+
+            bool mute = true;
+            ret = MediaPlayer.GetMute(ref mute);
+            Debug.Log("_mediaPlayer.GetMute returns: " + ret + "mute: " + mute);
+
+            int volume = 0;
+            ret = MediaPlayer.GetPlayoutVolume(ref volume);
+            Debug.Log("_mediaPlayer.GetPlayoutVolume returns: " + ret + "volume: " + volume);
+
+            Debug.Log("SDK Version:" + MediaPlayer.GetPlayerSdkVersion());
+            Debug.Log("GetPlaySrc:" + MediaPlayer.GetPlaySrc());
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+
+
+            if (MediaPlayer != null)
+            {
+                MediaPlayer.Stop();
+                RtcEngine.DestroyMediaPlayer(MediaPlayer);
+            }
+
+            if (customDataProvider != null)
+                customDataProvider.Close();
+
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+            RtcEngine = null;
+
+        }
+
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        internal static void MakeVideoView(uint uid, string channelId = "", VIDEO_SOURCE_TYPE videoSourceType = VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            videoSurface.SetForUser(uid, channelId, videoSourceType);
+            videoSurface.SetEnable(true);
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(1.0f, 1.333f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(4.5f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+    }
+
+    internal class MpkEventHandler : IMediaPlayerSourceObserver
+    {
+        private readonly MediaPlayerWithCustomDataProviderExample _sample;
+
+        internal MpkEventHandler(MediaPlayerWithCustomDataProviderExample sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnPlayerSourceStateChanged(MEDIA_PLAYER_STATE state, MEDIA_PLAYER_ERROR ec)
+        {
+            _sample.Log.UpdateLog(string.Format(
+                "OnPlayerSourceStateChanged state: {0}, ec: {1}, playId: {2}", state, ec, _sample.MediaPlayer.GetId()));
+            Debug.Log("OnPlayerSourceStateChanged");
+            if (state == MEDIA_PLAYER_STATE.PLAYER_STATE_OPEN_COMPLETED)
+            {
+                MediaPlayerWithCustomDataProviderExample.MakeVideoView((uint)_sample.MediaPlayer.GetId(), "", VIDEO_SOURCE_TYPE.VIDEO_SOURCE_MEDIA_PLAYER);
+                _sample.EnableUI(true);
+                _sample.Log.UpdateLog("Open Complete. Click start to play media");
+            }
+            else if (state == MEDIA_PLAYER_STATE.PLAYER_STATE_STOPPED)
+            {
+                MediaPlayerWithCustomDataProviderExample.DestroyVideoView((uint)_sample.MediaPlayer.GetId());
+                _sample.EnableUI(false);
+            }
+        }
+
+        public override void OnPlayerEvent(MEDIA_PLAYER_EVENT @event, Int64 elapsedTime, string message)
+        {
+            _sample.Log.UpdateLog(string.Format("OnPlayerEvent state: {0}", @event));
+        }
+    }
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly MediaPlayerWithCustomDataProviderExample _sample;
+
+        internal UserEventHandler(MediaPlayerWithCustomDataProviderExample sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _sample.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _sample.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _sample.RtcEngine.GetVersion(ref build)));
+            _sample.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                    connection.channelId, connection.localUid, elapsed));
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _sample.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _sample.Log.UpdateLog("OnLeaveChannel");
+            MediaPlayerWithCustomDataProviderExample.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole,
+            CLIENT_ROLE_TYPE newRole)
+        {
+            _sample.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            MediaPlayerWithCustomDataProviderExample.DestroyVideoView(uid);
+        }
+    }
+
+    internal class UserPlayerCustomDataProvider : IMediaPlayerCustomDataProvider
+    {
+
+        MediaPlayerWithCustomDataProviderExample _sample;
+        private FileStream fis = null;
+
+        private Int64 fileSize = 0;
+
+
+        internal UserPlayerCustomDataProvider(MediaPlayerWithCustomDataProviderExample sample)
+        {
+            _sample = sample;
+
+        }
+
+        public bool Open(string file)
+        {
+            try
+            {
+                if (File.Exists(file))
+                {
+                    fis = new FileStream(file, FileMode.Open, FileAccess.Read);
+                    fileSize = fis.Length;
+                    this._sample.Log.UpdateLog("open file sucess size: " + fileSize);
+                }
+
+            }
+            catch (Exception e)
+            {
+                this._sample.Log.UpdateLog("open catch exception " + e);
+                return false;
+            }
+            return true;
+
+        }
+
+        public void Close()
+        {
+            if (fis == null)
+            {
+                return;
+            }
+            try
+            {
+                fis.Close();
+            }
+            catch (Exception e)
+            {
+                this._sample.Log.UpdateLog("close catch exception " + e);
+            }
+            fis = null;
+        }
+
+        public override Int64 OnSeek(Int64 offset, int whence)
+        {
+
+            string str = String.Format("OnSeek offset:{0} whence:{1}", offset, whence);
+            Debug.Log(str);
+
+            if (whence == 0)
+            {
+                try
+                {
+                    if (fis == null)
+                    {
+                        return -1;
+                    }
+                    fis.Seek(offset, SeekOrigin.Begin);
+                }
+                catch (Exception e)
+                {
+                    Debug.Log("onseek catch exception " + e);
+                    return -1;
+                }
+                return offset;
+            }
+            else if (whence == 65536)
+            {
+                return fileSize;
+            }
+            return 0;
+        }
+
+        public override int OnReadData(IntPtr bufferPtr, int bufferSize)
+        {
+            string str = String.Format("OnReadData bufferPtr:{0} bufferSize:{1}", (System.Int64)bufferPtr, bufferSize);
+            Debug.Log(str);
+
+
+            if (fis == null)
+            {
+                return -1;
+            }
+            byte[] byte_buffer = new byte[bufferSize];
+            int read_count = -1;
+            try
+            {
+                read_count = fis.Read(byte_buffer, 0, bufferSize);
+                if (read_count == -1)
+                {
+                    return -1;
+                }
+                UnityEngine.Debug.Log("onReadData: " + read_count);
+                Marshal.Copy(byte_buffer, 0, bufferPtr, read_count);
+            }
+            catch (Exception e)
+            {
+                Debug.Log("onseek catch exception " + e);
+                return -1;
+            }
+
+            return read_count;
+        }
+
+    }
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaPlayerWithCustomDataProvider/MediaPlayerWithCustomDataProviderExample.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5c2783c9c13e6494ca1d73fa613c9910
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaRecorder.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 073414ad25c6946a196edc53d0ee332c
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 339 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaRecorder/MediaRecorder.cs

@@ -0,0 +1,339 @@
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.MediaRecorder
+{
+	public class MediaRecorder : MonoBehaviour
+	{
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+        internal IMediaPlayer MediaPlayer = null;
+        internal RtcConnection SelfConnection = null;
+        internal IMediaRecorder Recorder = null;
+
+        private Button _button1;
+        private Button _button2;
+   
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                SetUpUI();
+                EnableUI(false);
+                InitEngine();
+                JoinChannel();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        private void SetUpUI()
+        {
+            _button1 = GameObject.Find("Button1").GetComponent<Button>();
+            _button1.onClick.AddListener(OnStartButtonPress);
+            _button2 = GameObject.Find("Button2").GetComponent<Button>();
+            _button2.onClick.AddListener(OnStopButtonPress);
+            
+        }
+
+        public void EnableUI(bool val)
+        {
+            var obj = this.transform.Find("Button1").gameObject;
+            obj.SetActive(val);
+
+            obj = this.transform.Find("Button2").gameObject;
+            obj.SetActive(val);
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_GAME_STREAMING);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        internal void InitMediaRecorder()
+        {
+            Recorder = RtcEngine.GetMediaRecorder();
+            var nRet = Recorder.SetMediaRecorderObserver(SelfConnection, new MediaRecorderObserver(this));
+            this.Log.UpdateLog("SetMediaRecorderObserver:" + nRet);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+
+            ChannelMediaOptions options = new ChannelMediaOptions();
+            options.autoSubscribeAudio.SetValue(true);
+            options.autoSubscribeVideo.SetValue(true);
+            options.publishCameraTrack.SetValue(true);
+            options.publishMicrophoneTrack.SetValue(true);
+            options.enableAudioRecordingOrPlayout.SetValue(true);
+            options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            var ret = RtcEngine.JoinChannel(_token, _channelName, 0, options);
+
+            this.Log.UpdateLog("RtcEngineController JoinChannel_MPK returns: " + ret);
+        }
+
+        private void OnStartButtonPress()
+        {
+            var config = new MediaRecorderConfiguration();
+            config.storagePath = Application.persistentDataPath + "/record.mp4";
+            config.recorderInfoUpdateInterval = 5;
+            var nRet = Recorder.StartRecording(this.SelfConnection, config);
+            this.Log.UpdateLog("StartRecording:" + nRet);
+            this.Log.UpdateLog("storagePath: " + config.storagePath);
+        }
+
+        private void OnStopButtonPress()
+        {
+            var nRet = Recorder.StopRecording(this.SelfConnection);
+            this.Log.UpdateLog("StopRecording:" + nRet);
+        }
+
+    
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+            RtcEngine = null;
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "", VIDEO_SOURCE_TYPE videoSourceType = VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            videoSurface.SetForUser(uid, channelId, videoSourceType);
+            videoSurface.SetEnable(true);
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(1.0f, 1.333f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(4.5f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly MediaRecorder _sample;
+
+        internal UserEventHandler(MediaRecorder sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _sample.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _sample.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _sample.RtcEngine.GetVersion(ref build)));
+            _sample.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                    connection.channelId, connection.localUid, elapsed));
+            _sample.SelfConnection = connection;
+            _sample.EnableUI(true);
+            MediaRecorder.MakeVideoView(0);
+            _sample.InitMediaRecorder();
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _sample.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _sample.Log.UpdateLog("OnLeaveChannel");
+            MediaRecorder.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole,
+            CLIENT_ROLE_TYPE newRole)
+        {
+            _sample.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            MediaRecorder.DestroyVideoView(uid);
+        }
+    }
+
+    internal class MediaRecorderObserver : IMediaRecorderObserver
+    {
+        private readonly MediaRecorder _sample;
+
+        internal MediaRecorderObserver(MediaRecorder sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnRecorderInfoUpdated(RecorderInfo info)
+        {
+            _sample.Log.UpdateLog(string.Format("OnRecorderInfoUpdated fileName: {0}, durationMs: {1} fileSize:{2}", info.fileName, info.durationMs, info.fileSize));
+        }
+
+        public override void OnRecorderStateChanged(RecorderState state, RecorderErrorCode error)
+        {
+            _sample.Log.UpdateLog(string.Format("OnRecorderStateChanged state: {0}, error: {1}", state, error));
+
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/MediaRecorder/MediaRecorder.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b28122a26e8ac4d7bbcc2538039cb9ee
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/Metadata.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2de9e811bb9c14e4483de7985fb3942a
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 354 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/Metadata/MetadataSample.cs

@@ -0,0 +1,354 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.MetadataSample
+{
+    public class MetadataSample : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        public string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        public string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        public string _channelName = "";
+
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine;
+        internal bool Sending = false;
+        internal Queue<String> MetadataQueue = new Queue<string>();
+
+
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitEngine();
+                SetupUI();
+                JoinChannel();
+            }
+        }
+
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_GAME_STREAMING);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+
+            UserMetadataObserver metadataObserver = new UserMetadataObserver(this);
+            RtcEngine.RegisterMediaMetadataObserver(metadataObserver, METADATA_TYPE.VIDEO_METADATA);
+        }
+
+        private void SetupUI()
+        {
+            var ui = this.transform.Find("UI");
+
+            var btn = ui.Find("StartButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnStartButtonPress);
+
+            btn = ui.Find("StopButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnStopButtonPress);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.JoinChannel(_token, _channelName, "");
+        }
+
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            lock (MetadataQueue)
+            {
+                while(MetadataQueue.Count > 0)
+                {
+                    string metadataString = MetadataQueue.Dequeue();
+                    this.Log.UpdateLog(metadataString);
+                }
+            }
+        }
+
+
+        private void OnStartButtonPress()
+        {
+            this.Sending = true;
+            this.Log.UpdateLog("Sending: true");
+        }
+
+        private void OnStopButtonPress()
+        {
+            this.Sending = false;
+            this.Log.UpdateLog("Sending: false");
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.UnregisterMediaMetadataObserver();
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        public string GetChannelName()
+        {
+            return this._channelName;
+        }
+
+         #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "")
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            if (uid == 0)
+            {
+                videoSurface.SetForUser(uid, channelId);
+            }
+            else
+            {
+                videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+
+            videoSurface.SetEnable(true);
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly MetadataSample _sample;
+
+        internal UserEventHandler(MetadataSample sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _sample.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            Debug.Log("Agora: OnJoinChannelSuccess ");
+            _sample.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _sample.RtcEngine.GetVersion(ref build)));
+            _sample.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+
+
+            MetadataSample.MakeVideoView(0);
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _sample.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _sample.Log.UpdateLog("OnLeaveChannel");
+            MetadataSample.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _sample.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnStreamMessageError(RtcConnection connection, uint remoteUid, int streamId, int code, int missed, int cached)
+        {
+            _sample.Log.UpdateLog(string.Format("OnStreamMessageError remoteUid: {0}, streamId: {1}, code: {2}, missed: {3}, cached: {4}", remoteUid, streamId, code, missed, cached));
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            MetadataSample.MakeVideoView(uid, _sample.GetChannelName());
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            MetadataSample.DestroyVideoView(uid);
+        }
+
+    }
+
+    internal class UserMetadataObserver : IMetadataObserver
+    {
+        MetadataSample _sample;
+        private int tick = 0;
+
+        internal UserMetadataObserver(MetadataSample sample)
+        {
+            _sample = sample;
+        }
+
+        public override int GetMaxMetadataSize()
+        {
+            return 128;
+        }
+
+        public override bool OnReadyToSendMetadata(ref Metadata metadata, VIDEO_SOURCE_TYPE source_type)
+        {
+            if (this._sample.Sending)
+            {
+                this.tick++;
+                string str = "tick :" + tick;
+                byte[] strByte = System.Text.Encoding.Default.GetBytes(str);
+                Marshal.Copy(strByte, 0, metadata.buffer, strByte.Length);
+                metadata.size = (uint)strByte.Length;
+                Debug.Log("OnReadyToSendMetadata Sended metadatasize:" + metadata.size);
+
+            }
+
+            return this._sample.Sending;
+        }
+
+        public override void OnMetadataReceived(Metadata data)
+        {
+            //this callback not trigger in unity main thread
+            byte[] strByte = new byte[data.size];
+            Marshal.Copy(data.buffer, strByte, 0, (int)data.size);
+            string str = System.Text.Encoding.Default.GetString(strByte);
+            var str2 = string.Format("OnMetadataReceived uid:{0} buffer:{1}", data.uid, str);
+            lock (this._sample.MetadataQueue)
+            {
+                this._sample.MetadataQueue.Enqueue(str2);
+            }
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/Metadata/MetadataSample.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cf8e0b34a62df440b86d18693a0993c6
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ProcessAudioRawData.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 321014923cb5847f3b9b23733d945d35
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 308 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ProcessAudioRawData/ProcessAudioRawData.cs

@@ -0,0 +1,308 @@
+using System;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+using RingBuffer;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.ProcessAudioRawData
+{
+    public class ProcessAudioRawData : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine;
+
+        private const int CHANNEL = 1;
+        private const int PULL_FREQ_PER_SEC = 100;
+        public const int SAMPLE_RATE = 32000; // this should = CLIP_SAMPLES x PULL_FREQ_PER_SEC
+        public const int CLIP_SAMPLES = 320;
+
+        internal int _count;
+
+        internal int _writeCount;
+        internal int _readCount;
+
+        internal RingBuffer<float> _audioBuffer;
+        internal AudioClip _audioClip;
+
+        private bool _startSignal;
+
+
+        void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitEngine();
+                JoinChannel();
+
+                var aud = GetComponent<AudioSource>();
+                if (aud == null)
+                {
+                    gameObject.AddComponent<AudioSource>();
+                }
+                SetupAudio(aud, "externalClip");
+            }
+        }
+
+        // Update is called once per frame
+        void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        public void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+            RtcEngine.RegisterAudioFrameObserver(new AudioFrameObserver(this), OBSERVER_MODE.RAW_DATA);
+            RtcEngine.SetPlaybackAudioFrameParameters(SAMPLE_RATE, 1, RAW_AUDIO_FRAME_OP_MODE_TYPE.RAW_AUDIO_FRAME_OP_MODE_READ_ONLY, 1024);
+        }
+
+        void JoinChannel()
+        {
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.JoinChannel(_token, _channelName, "");
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine != null)
+            {
+                RtcEngine.UnRegisterAudioFrameObserver();
+                RtcEngine.InitEventHandler(null);
+                RtcEngine.LeaveChannel();
+                RtcEngine.Dispose();
+            }
+        }
+
+        void SetupAudio(AudioSource aud, string clipName)
+        {
+            // //The larger the buffer, the higher the delay
+            var bufferLength = SAMPLE_RATE / PULL_FREQ_PER_SEC * CHANNEL * 100; // 1-sec-length buffer
+            _audioBuffer = new RingBuffer<float>(bufferLength, true);
+
+            _audioClip = AudioClip.Create(clipName,
+                CLIP_SAMPLES,
+                CHANNEL, SAMPLE_RATE, true,
+                OnAudioRead);
+            aud.clip = _audioClip;
+            aud.loop = true;
+            aud.Play();
+        }
+
+        private void OnAudioRead(float[] data)
+        {
+
+            for (var i = 0; i < data.Length; i++)
+            {
+                lock (_audioBuffer)
+                {
+                    if (_audioBuffer.Count > 0)
+                    {
+                        data[i] = _audioBuffer.Get();
+                        _readCount += 1;
+                    }
+                }
+            }
+
+            Debug.LogFormat("buffer length remains: {0}", _writeCount - _readCount);
+        }
+
+        internal static float[] ConvertByteToFloat16(byte[] byteArray)
+        {
+            var floatArray = new float[byteArray.Length / 2];
+            for (var i = 0; i < floatArray.Length; i++)
+            {
+                floatArray[i] = BitConverter.ToInt16(byteArray, i * 2) / 32768f; // -Int16.MinValue
+            }
+
+            return floatArray;
+        }
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly ProcessAudioRawData _agoraVideoRawData;
+
+        internal UserEventHandler(ProcessAudioRawData agoraVideoRawData)
+        {
+            _agoraVideoRawData = agoraVideoRawData;
+        }
+        public override void OnError(int err, string msg)
+        {
+            _agoraVideoRawData.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _agoraVideoRawData.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _agoraVideoRawData.RtcEngine.GetVersion(ref build)));
+            _agoraVideoRawData.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                    connection.channelId, connection.localUid, elapsed));
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _agoraVideoRawData.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _agoraVideoRawData.Log.UpdateLog("OnLeaveChannel");
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole,
+            CLIENT_ROLE_TYPE newRole)
+        {
+            _agoraVideoRawData.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _agoraVideoRawData.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid,
+                elapsed));
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _agoraVideoRawData.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+        }
+    }
+
+    internal class AudioFrameObserver : IAudioFrameObserver
+    {
+        private readonly ProcessAudioRawData _agoraAudioRawData;
+        private AudioParams _audioParams;
+
+
+        internal AudioFrameObserver(ProcessAudioRawData agoraAudioRawData)
+        {
+            _agoraAudioRawData = agoraAudioRawData;
+            _audioParams = new AudioParams();
+            _audioParams.sample_rate = 16000;
+            _audioParams.channels = 2;
+            _audioParams.mode = RAW_AUDIO_FRAME_OP_MODE_TYPE.RAW_AUDIO_FRAME_OP_MODE_READ_ONLY;
+            _audioParams.samples_per_call = 1024;
+        }
+
+        public override bool OnRecordAudioFrame(string channelId, AudioFrame audioFrame)
+        {
+            Debug.Log("OnRecordAudioFrame-----------");
+            return true;
+        }
+
+        public override bool OnPlaybackAudioFrame(string channelId, AudioFrame audioFrame)
+        {
+            Debug.Log("OnPlaybackAudioFrame-----------");
+            if (_agoraAudioRawData._count == 1)
+            {
+                Debug.LogWarning("audioFrame = " + audioFrame);
+            }
+            var floatArray = ProcessAudioRawData.ConvertByteToFloat16(audioFrame.RawBuffer);
+
+            lock (_agoraAudioRawData._audioBuffer)
+            {
+                _agoraAudioRawData._audioBuffer.Put(floatArray);
+                _agoraAudioRawData._writeCount += floatArray.Length;
+                _agoraAudioRawData._count++;
+            }
+            return true;
+        }
+        
+        public override int GetObservedAudioFramePosition()
+        {
+            Debug.Log("GetObservedAudioFramePosition-----------");
+            return (int)(AUDIO_FRAME_POSITION.AUDIO_FRAME_POSITION_PLAYBACK |
+                AUDIO_FRAME_POSITION.AUDIO_FRAME_POSITION_RECORD |
+                AUDIO_FRAME_POSITION.AUDIO_FRAME_POSITION_BEFORE_MIXING |
+                AUDIO_FRAME_POSITION.AUDIO_FRAME_POSITION_MIXED);
+        }
+
+        public override AudioParams GetPlaybackAudioParams()
+        {
+            Debug.Log("GetPlaybackAudioParams-----------");
+            return this._audioParams;
+        }
+
+        public override AudioParams GetRecordAudioParams()
+        {
+            Debug.Log("GetRecordAudioParams-----------");
+            return this._audioParams;
+        }
+
+        public override AudioParams GetMixedAudioParams()
+        {
+            Debug.Log("GetMixedAudioParams-----------");
+            return this._audioParams;
+        }
+
+        public override bool OnPlaybackAudioFrameBeforeMixing(string channel_id,
+                                                        uint uid,
+                                                        AudioFrame audio_frame)
+        {
+            Debug.Log("OnPlaybackAudioFrameBeforeMixing-----------");
+            return false;
+        }
+
+        public override bool OnPlaybackAudioFrameBeforeMixing(string channel_id,
+                                                        string uid,
+                                                        AudioFrame audio_frame)
+        {
+            Debug.Log("OnPlaybackAudioFrameBeforeMixing2-----------");
+            return false;
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ProcessAudioRawData/ProcessAudioRawData.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 293ff77b0398c4188a1c70f24bfcfbb8
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ProcessVideoRawData.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 69ad88fc08c81418da3b3052ba4185b9
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 292 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ProcessVideoRawData/ProcessVideoRawData.cs

@@ -0,0 +1,292 @@
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.ProcessVideoRawData
+{
+    public class ProcessVideoRawData : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine;
+
+        internal int Count;
+
+        internal int WriteCount;
+        internal int ReadCount;
+
+        internal byte[] VideoBuffer = new byte[0];
+
+        private int _videoFrameWidth = 1080;
+        public int VideoFrameWidth
+        {
+            set
+            {
+                if (value != _videoFrameWidth)
+                {
+                    _videoFrameWidth = value;
+                    _needResize = true;
+                }
+
+            }
+        
+            get
+            {
+                return _videoFrameWidth;
+            }
+        }
+
+        private int _videoFrameHeight = 720;
+        public int VideoFrameHeight
+        {
+            set
+            {
+                if (value != _videoFrameHeight)
+                {
+                    _videoFrameHeight = value;
+                    _needResize = true;
+                }
+
+            }
+
+            get
+            {
+                return _videoFrameHeight;
+
+            }
+        }
+
+        private bool _needResize = false;
+        public GameObject VideoView;
+        private Texture2D _texture;
+        private bool _isTextureAttach = false;
+
+
+        void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                SetUpUI();
+                InitEngine();
+                JoinChannel();
+            }
+        }
+
+        // Update is called once per frame
+        void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+
+            if (!_isTextureAttach)
+            {
+                var rd = VideoView.GetComponent<RawImage>();
+                rd.texture = _texture;
+                _isTextureAttach = true;
+            }
+            else if (VideoBuffer != null && VideoBuffer.Length != 0 && !_needResize)
+            {
+                lock (VideoBuffer)
+                {
+                    _texture.LoadRawTextureData(VideoBuffer);
+                    _texture.Apply();
+                }
+            }
+            else if(_needResize)
+            {
+                _texture.Reinitialize(_videoFrameWidth, _videoFrameHeight);
+                _texture.Apply();
+                _needResize = false;
+            }
+        }
+
+        bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        public void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        void SetUpUI()
+        {
+            var bufferLength = _videoFrameHeight * _videoFrameWidth * 4;
+            _texture = new Texture2D(_videoFrameWidth, _videoFrameHeight, TextureFormat.RGBA32, false);
+            _texture.Apply();
+        }
+
+        void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+            RtcEngine.RegisterVideoFrameObserver(new VideoFrameObserver(this), OBSERVER_MODE.RAW_DATA);
+        }
+
+        void JoinChannel()
+        {
+            VideoEncoderConfiguration config = new VideoEncoderConfiguration();
+            config.dimensions = new VideoDimensions(_videoFrameWidth, _videoFrameHeight);
+            RtcEngine.SetVideoEncoderConfiguration(config);
+
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.JoinChannel(_token, _channelName);
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine != null)
+            {
+                if (_texture != null)
+                {
+                    GameObject.Destroy(_texture);
+                    _texture = null;
+                }
+
+                RtcEngine.UnRegisterVideoFrameObserver();
+                RtcEngine.InitEventHandler(null);
+                RtcEngine.LeaveChannel();
+                RtcEngine.Dispose();
+            }
+        }
+
+        private static float[] ConvertByteToFloat16(byte[] byteArray)
+        {
+            var floatArray = new float[byteArray.Length / 2];
+            for (var i = 0; i < floatArray.Length; i++)
+            {
+                floatArray[i] = System.BitConverter.ToInt16(byteArray, i * 2) / 32768f; // -Int16.MinValue
+            }
+
+            return floatArray;
+        }
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly ProcessVideoRawData _agoraVideoRawData;
+
+        internal UserEventHandler(ProcessVideoRawData agoraVideoRawData)
+        {
+            _agoraVideoRawData = agoraVideoRawData;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _agoraVideoRawData.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _agoraVideoRawData.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _agoraVideoRawData.RtcEngine.GetVersion(ref build)));
+            _agoraVideoRawData.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                    connection.channelId, connection.localUid, elapsed));
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _agoraVideoRawData.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _agoraVideoRawData.Log.UpdateLog("OnLeaveChannel");
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole,
+            CLIENT_ROLE_TYPE newRole)
+        {
+            _agoraVideoRawData.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _agoraVideoRawData.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid,
+                elapsed));
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _agoraVideoRawData.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+        }
+    }
+
+    internal class VideoFrameObserver : IVideoFrameObserver
+    {
+        private readonly ProcessVideoRawData _agoraVideoRawData;
+
+        internal VideoFrameObserver(ProcessVideoRawData agoraVideoRawData)
+        {
+            _agoraVideoRawData = agoraVideoRawData;
+        }
+
+        public override bool OnCaptureVideoFrame(VideoFrame videoFrame, VideoFrameBufferConfig config)
+        {
+            Debug.Log("OnCaptureVideoFrame-----------" + " width:" + videoFrame.width + " height:" +
+                        videoFrame.height);
+            _agoraVideoRawData.VideoFrameWidth = videoFrame.width;
+            _agoraVideoRawData.VideoFrameHeight = videoFrame.height;
+            lock (_agoraVideoRawData.VideoBuffer)
+            {
+                _agoraVideoRawData.VideoBuffer = videoFrame.yBuffer;
+            }
+            return true;
+        }
+
+        public override bool OnRenderVideoFrame(string channelId, uint uid, VideoFrame videoFrame)
+        {
+            Debug.Log("OnRenderVideoFrameHandler-----------" + " uid:" + uid + " width:" + videoFrame.width +
+                        " height:" + videoFrame.height);
+            return true;
+        }
+
+        public override VIDEO_OBSERVER_FRAME_TYPE GetVideoFormatPreference()
+        {
+            return VIDEO_OBSERVER_FRAME_TYPE.FRAME_TYPE_RGBA;
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ProcessVideoRawData/ProcessVideoRawData.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8d4a3cabb800d442580df439fec79c06
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/PushEncodedVideoImage.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9812fb8a1ba8b43fc8eab9f8e7d35d49
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 520 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/PushEncodedVideoImage/PushEncodedVideoImage.cs

@@ -0,0 +1,520 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using Agora.Rtc;
+using Agora.Util;
+using UnityEngine;
+using UnityEngine.Serialization;
+using UnityEngine.UI;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.PushEncodedVideoImage
+{
+    public class PushEncodedVideoImage : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        public string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        public string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        public string _channelName = "";
+
+        public GameObject RolePrefab;
+        private GameObject _roleLocal;
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngineEx RtcEngine = null;
+
+        public Dictionary<string, Vector3> RolePositionDic = new Dictionary<string, Vector3>();
+
+
+
+        internal uint Uid1;
+        internal uint Uid2;
+        private System.Random _random = new System.Random();
+
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                Uid1 = (uint)(_random.Next() % 1000);
+                Uid2 = (uint)(_random.Next() % 1000 + 1000);
+                InitEngine();
+                JoinChannel1();
+                JoinChannel2();
+            }
+        }
+
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngineEx();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_GAME_STREAMING);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+            RtcEngine.RegisterVideoEncodedFrameObserver(new VideoEncodedImageReceiver(this), OBSERVER_MODE.INTPTR);
+        }
+
+        private void JoinChannel1()
+        {
+
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.EnableVideo();
+
+            var option = new ChannelMediaOptions();
+            option.autoSubscribeVideo.SetValue(true);
+            option.autoSubscribeAudio.SetValue(true);
+            option.publishCameraTrack.SetValue(true);
+            option.publishMicrophoneTrack.SetValue(true);
+            option.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            option.channelProfile.SetValue(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING);
+
+
+            var connection = new RtcConnection();
+            connection.channelId = _channelName;
+            connection.localUid = Uid1;
+
+            var nRet = RtcEngine.JoinChannelEx(_token, connection, option);
+            this.Log.UpdateLog("joinChanne1: nRet" + nRet + " uid1:" + Uid1);
+        }
+
+        private void JoinChannel2()
+        {
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.EnableVideo();
+
+            SenderOptions senderOptions = new SenderOptions();
+            senderOptions.codecType = VIDEO_CODEC_TYPE.VIDEO_CODEC_GENERIC;
+            RtcEngine.SetExternalVideoSource(true, true, EXTERNAL_VIDEO_SOURCE_TYPE.ENCODED_VIDEO_FRAME, senderOptions);
+
+            var option = new ChannelMediaOptions();
+            option.autoSubscribeVideo.SetValue(true);
+            option.autoSubscribeAudio.SetValue(true);
+            option.publishCustomAudioTrack.SetValue(false);
+            option.publishCameraTrack.SetValue(false);
+            option.publishCustomVideoTrack.SetValue(false);
+            option.publishEncodedVideoTrack.SetValue(true);
+            option.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            option.channelProfile.SetValue(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING);
+
+
+            var connection = new RtcConnection();
+            connection.channelId = _channelName;
+            connection.localUid = Uid2;
+
+            var nRet = RtcEngine.JoinChannelEx(_token, connection, option);
+            this.Log.UpdateLog("joinChanne1: nRet" + nRet + " uid2:" + Uid2);
+        }
+
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+
+            lock (this.RolePositionDic)
+            {
+                foreach (var e in this.RolePositionDic)
+                {
+                    this.UpdateRolePositon(e.Key, e.Value);
+                }
+            }
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+
+            RtcEngine.UnRegisterVideoEncodedFrameObserver();
+
+            var connection = new RtcConnection();
+            connection.channelId = _channelName;
+
+            connection.localUid = Uid1;
+            RtcEngine.LeaveChannelEx(connection);
+
+            connection.localUid = Uid2;
+            RtcEngine.LeaveChannelEx(connection);
+
+            RtcEngine.Dispose();
+        }
+
+        public void CreateRole(string uid, bool isLocal)
+        {
+            if (GameObject.Find("Role" + uid) != null)
+            {
+                return;
+            }
+
+            var role = Instantiate(this.RolePrefab, this.transform);
+            role.name = "Role" + uid;
+            var text = role.transform.Find("Text").GetComponent<Text>();
+            text.text = uid;
+
+            if (isLocal)
+            {
+                text.text += "\n(Local)";
+                role.AddComponent<UIElementDrag>();
+                this._roleLocal = role;
+            }
+            else if (this._roleLocal != null)
+            {
+                var count = this.transform.childCount;
+                this._roleLocal.transform.SetSiblingIndex(count - 1);
+            }
+
+            role.GetComponent<RectTransform>().anchoredPosition = Vector3.zero;
+        }
+
+        public void DestroyRole(string uid, bool isLocal)
+        {
+            var name = "Role" + uid;
+            var role = this.gameObject.transform.Find(name).gameObject;
+            if (role)
+            {
+                Destroy(role);
+            }
+
+            if (isLocal)
+            {
+                this._roleLocal = null;
+            }
+        }
+
+        private void UpdateRolePositon(string uid, Vector3 pos)
+        {
+            var name = "Role" + uid;
+            var role = this.gameObject.transform.Find(name);
+            if (role)
+            {
+                role.transform.localPosition = pos;
+            }
+        }
+
+        public void StartPushEncodeVideoImage()
+        {
+            this.InvokeRepeating("UpdateForPushEncodeVideoImage", 0, 0.1f);
+            this.Log.UpdateLog("Start PushEncodeVideoImage in every frame");
+        }
+
+        public void StopPushEncodeVideoImage()
+        {
+            this.CancelInvoke("UpdateForPushEncodeVideoImage");
+            this.Log.UpdateLog("Stop PushEncodeVideoImage");
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        private void UpdateForPushEncodeVideoImage()
+        {
+            //you can send any data not just  video image byte
+            if (_roleLocal)
+            {
+                //in this case, we send pos byte 
+                string json = JsonUtility.ToJson(this._roleLocal.transform.localPosition);
+                byte[] data = System.Text.Encoding.Default.GetBytes(json);
+                EncodedVideoFrameInfo encodedVideoFrameInfo = new EncodedVideoFrameInfo()
+                {
+                    framesPerSecond = 60,
+                    codecType = VIDEO_CODEC_TYPE.VIDEO_CODEC_GENERIC,
+                    frameType = VIDEO_FRAME_TYPE_NATIVE.VIDEO_FRAME_TYPE_KEY_FRAME
+                };
+                int nRet = this.RtcEngine.PushEncodedVideoImage(data, Convert.ToUInt32(data.Length), encodedVideoFrameInfo);
+                Debug.Log("PushEncodedVideoImage: " + nRet);
+            }
+        }
+
+
+        internal static void MakeVideoView(uint uid, string channelId = "")
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            if (uid == 0)
+            {
+                videoSurface.SetForUser(uid, channelId);
+            }
+            else
+            {
+                videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+
+            videoSurface.SetEnable(true);
+        }
+
+
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            var yPos = UnityEngine.Random.Range(3.0f, 5.0f);
+            var xPos = UnityEngine.Random.Range(-2.0f, 2.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly PushEncodedVideoImage _pushEncodedVideoImage;
+
+        internal UserEventHandler(PushEncodedVideoImage videoSample)
+        {
+            _pushEncodedVideoImage = videoSample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _pushEncodedVideoImage.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            Debug.Log("Agora: OnJoinChannelSuccess ");
+            _pushEncodedVideoImage.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _pushEncodedVideoImage.RtcEngine.GetVersion(ref build)));
+            _pushEncodedVideoImage.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+
+
+
+            if (connection.localUid >= 1000)
+            {
+                _pushEncodedVideoImage.CreateRole(connection.localUid.ToString(), true);
+                _pushEncodedVideoImage.Log.UpdateLog("you can drag your role to every where");
+                _pushEncodedVideoImage.StartPushEncodeVideoImage();
+            }
+            else
+            {
+                PushEncodedVideoImage.MakeVideoView(0);
+            }
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _pushEncodedVideoImage.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _pushEncodedVideoImage.Log.UpdateLog("OnLeaveChannel");
+
+            if (connection.localUid >= 1000)
+            {
+                _pushEncodedVideoImage.DestroyRole(connection.localUid.ToString(), true);
+                _pushEncodedVideoImage.StopPushEncodeVideoImage();
+            }
+            else
+            {
+                PushEncodedVideoImage.DestroyVideoView(0);
+            }
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _pushEncodedVideoImage.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _pushEncodedVideoImage.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+
+
+            if (uid == _pushEncodedVideoImage.Uid1 || uid == _pushEncodedVideoImage.Uid2)
+                return;
+
+            if (uid >= 1000)
+            {
+                _pushEncodedVideoImage.CreateRole(uid.ToString(), false);
+                //you must set options.encodedFrameOnly = true when you receive other 
+                VideoSubscriptionOptions options = new VideoSubscriptionOptions();
+                options.encodedFrameOnly.SetValue(true);
+                int nRet = _pushEncodedVideoImage.RtcEngine.SetRemoteVideoSubscriptionOptionsEx(uid, options, connection);
+                _pushEncodedVideoImage.Log.UpdateLog("SetRemoteVideoSubscriptionOptions nRet:" + nRet);
+            }
+            else
+            {
+                PushEncodedVideoImage.MakeVideoView(uid, _pushEncodedVideoImage.GetChannelName());
+            }
+
+
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _pushEncodedVideoImage.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+
+            if (uid == _pushEncodedVideoImage.Uid1 || uid == _pushEncodedVideoImage.Uid2)
+                return;
+
+            if (uid >= 1000)
+            {
+                _pushEncodedVideoImage.DestroyRole(uid.ToString(), false);
+            }
+            else
+            {
+                PushEncodedVideoImage.DestroyVideoView(uid);
+            }
+        }
+
+        public override void OnChannelMediaRelayEvent(int code)
+        {
+            _pushEncodedVideoImage.Log.UpdateLog(string.Format("OnChannelMediaRelayEvent: {0}", code));
+
+        }
+
+        public override void OnChannelMediaRelayStateChanged(int state, int code)
+        {
+            _pushEncodedVideoImage.Log.UpdateLog(string.Format("OnChannelMediaRelayStateChanged state: {0}, code: {1}", state, code));
+        }
+    }
+
+
+    internal class VideoEncodedImageReceiver : IVideoEncodedFrameObserver
+    {
+        private readonly PushEncodedVideoImage _pushEncodedVideoImage;
+
+        internal VideoEncodedImageReceiver(PushEncodedVideoImage videoSample)
+        {
+            _pushEncodedVideoImage = videoSample;
+        }
+
+        public override bool OnEncodedVideoFrameReceived(uint uid, IntPtr imageBufferPtr, UInt64 length, EncodedVideoFrameInfo videoEncodedFrameInfo)
+        {
+            byte[] imageBuffer = new byte[length];
+            Marshal.Copy(imageBufferPtr, imageBuffer, 0, (int)length);
+            string str = System.Text.Encoding.Default.GetString(imageBuffer);
+            var pos = JsonUtility.FromJson<Vector3>(str);
+            var uidStr = uid.ToString();
+            Debug.Log("OnEncodedVideoImageReceived" + uid + " pos" + str);
+            //this called is not in Unity MainThread.we need push data in this dic. And read it in Update()
+            lock (_pushEncodedVideoImage.RolePositionDic)
+            {
+                if (_pushEncodedVideoImage.RolePositionDic.ContainsKey(uidStr))
+                {
+                    _pushEncodedVideoImage.RolePositionDic[uidStr] = pos;
+                }
+                else
+                {
+                    _pushEncodedVideoImage.RolePositionDic.Add(uidStr, pos);
+                }
+            }
+            return true;
+        }
+    }
+
+    #endregion
+}
+

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/PushEncodedVideoImage/PushEncodedVideoImage.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f0b155aeab44f48dfa41533c4fd66d04
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ScreenShare.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: eada5807f81ea4041b702c7e0e868f19
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 415 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ScreenShare/ScreenShare.cs

@@ -0,0 +1,415 @@
+using System;
+using System.Linq;
+using UnityEngine;
+using UnityEngine.UI;
+using Agora.Rtc;
+using Agora.Util;
+using UnityEngine.Serialization;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.ScreenShare
+{
+    public class ScreenShare : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+        private Dropdown _winIdSelect;
+        private Button _startShareBtn;
+        private Button _stopShareBtn;
+        private Button _updateShareBtn;
+        private Button _publishBtn;
+        private Button _unpublishBtn;
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                EnableUI();
+                InitEngine();
+#if UNITY_ANDROID || UNITY_IPHONE
+                GameObject.Find("winIdSelect").SetActive(false);
+                _updateShareBtn.gameObject.SetActive(true);
+#else
+                _updateShareBtn.gameObject.SetActive(false);
+                PrepareScreenCapture();
+#endif
+                JoinChannel();
+            }
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+
+            var ret = RtcEngine.JoinChannel(_token, _channelName);
+            Debug.Log("JoinChannel returns: " + ret);
+        }
+
+        private void OnPublishButtonClick()
+        {
+            ChannelMediaOptions options = new ChannelMediaOptions();
+            options.publishCameraTrack.SetValue(false);
+            options.publishScreenTrack.SetValue(true);
+
+#if UNITY_ANDROID || UNITY_IPHONE
+            options.publishScreenCaptureAudio.SetValue(true);
+            options.publishScreenCaptureVideo.SetValue(true);
+#endif
+            var ret = RtcEngine.UpdateChannelMediaOptions(options);
+            Debug.Log("UpdateChannelMediaOptions returns: " + ret);
+
+            _publishBtn.gameObject.SetActive(false);
+            _unpublishBtn.gameObject.SetActive(true);
+        }
+
+        private void OnUnplishButtonClick()
+        {
+            ChannelMediaOptions options = new ChannelMediaOptions();
+            options.publishCameraTrack.SetValue(true);
+            options.publishScreenTrack.SetValue(false);
+
+#if UNITY_ANDROID || UNITY_IPHONE
+            options.publishScreenCaptureAudio.SetValue(false);
+            options.publishScreenCaptureVideo.SetValue(false);
+#endif
+            var ret = RtcEngine.UpdateChannelMediaOptions(options);
+            Debug.Log("UpdateChannelMediaOptions returns: " + ret);
+
+            _publishBtn.gameObject.SetActive(true);
+            _unpublishBtn.gameObject.SetActive(false);
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                                        CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                                        AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(new UserEventHandler(this));
+        }
+
+        private void PrepareScreenCapture()
+        {
+            _winIdSelect = GameObject.Find("winIdSelect").GetComponent<Dropdown>();
+
+            if (_winIdSelect == null || RtcEngine == null) return;
+
+            _winIdSelect.ClearOptions();
+
+            SIZE t = new SIZE();
+            t.width = 360;
+            t.height = 240;
+            SIZE s = new SIZE();
+            s.width = 360;
+            s.height = 240;
+            var info = RtcEngine.GetScreenCaptureSources(t, s, true);
+
+            _winIdSelect.AddOptions(info.Select(w =>
+                    new Dropdown.OptionData(
+                        string.Format("{0}: {1}-{2} | {3}", w.type, w.sourceName, w.sourceTitle, w.sourceId)))
+                .ToList());
+        }
+
+
+        private void EnableUI()
+        {
+            _startShareBtn = GameObject.Find("startShareBtn").GetComponent<Button>();
+            _stopShareBtn = GameObject.Find("stopShareBtn").GetComponent<Button>();
+            if (_startShareBtn != null) _startShareBtn.onClick.AddListener(OnStartShareBtnClick);
+            if (_stopShareBtn != null)
+            {
+                _stopShareBtn.onClick.AddListener(OnStopShareBtnClick);
+                _stopShareBtn.gameObject.SetActive(false);
+            }
+
+            var gameObject = GameObject.Find("updateShareBtn");
+            if (gameObject != null)
+            {
+                _updateShareBtn = gameObject.GetComponent<Button>();
+                _updateShareBtn.onClick.AddListener(OnUpdateShareBtnClick);
+            }
+
+            _publishBtn = GameObject.Find("publishBtn").GetComponent<Button>();
+            _publishBtn.onClick.AddListener(OnPublishButtonClick);
+            _publishBtn.gameObject.SetActive(false);
+
+            _unpublishBtn = GameObject.Find("unpublishBtn").GetComponent<Button>();
+            _unpublishBtn.onClick.AddListener(OnUnplishButtonClick);
+            _unpublishBtn.gameObject.SetActive(false);
+        }
+
+        private void OnStartShareBtnClick()
+        {
+            if (RtcEngine == null) return;
+
+            if (_startShareBtn != null) _startShareBtn.gameObject.SetActive(false);
+            if (_stopShareBtn != null) _stopShareBtn.gameObject.SetActive(true);
+
+#if UNITY_ANDROID || UNITY_IPHONE
+            var parameters2 = new ScreenCaptureParameters2();
+            parameters2.captureAudio = true;
+            parameters2.captureVideo = true;
+            var nRet = RtcEngine.StartScreenCapture(parameters2);
+            this.Log.UpdateLog("StartScreenCapture :" + nRet);
+#else
+            RtcEngine.StopScreenCapture();
+            if (_winIdSelect == null) return;
+            var option = _winIdSelect.options[_winIdSelect.value].text;
+            if (string.IsNullOrEmpty(option)) return;
+
+            if (option.Contains("ScreenCaptureSourceType_Window"))
+            {
+                var windowId = option.Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[1];
+                Log.UpdateLog(string.Format(">>>>> Start sharing {0}", windowId));
+                var nRet = RtcEngine.StartScreenCaptureByWindowId(ulong.Parse(windowId), default(Rectangle),
+                        default(ScreenCaptureParameters));
+                this.Log.UpdateLog("StartScreenCaptureByWindowId:" + nRet);
+            }
+            else
+            {
+                var dispId = uint.Parse(option.Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[1]);
+                Log.UpdateLog(string.Format(">>>>> Start sharing display {0}", dispId));
+                var nRet = RtcEngine.StartScreenCaptureByDisplayId(dispId, default(Rectangle),
+                    new ScreenCaptureParameters { captureMouseCursor = true, frameRate = 30 });
+                this.Log.UpdateLog("StartScreenCaptureByDisplayId:" + nRet);
+            }
+
+#endif
+
+            OnPublishButtonClick();
+
+        }
+
+        private void OnStopShareBtnClick()
+        {
+            if (_startShareBtn != null) _startShareBtn.gameObject.SetActive(true);
+            if (_stopShareBtn != null) _stopShareBtn.gameObject.SetActive(false);
+
+
+            _publishBtn.gameObject.SetActive(false);
+            _unpublishBtn.gameObject.SetActive(false);
+
+            RtcEngine.StopScreenCapture();
+        }
+
+        private void OnUpdateShareBtnClick()
+        {
+            //only work in ios or android
+            var config = new ScreenCaptureParameters2();
+            config.captureAudio = true;
+            config.captureVideo = true;
+            config.videoParams.dimensions.width = 960;
+            config.videoParams.dimensions.height = 640;
+            var nRet = RtcEngine.UpdateScreenCapture(config);
+            this.Log.UpdateLog("UpdateScreenCapture: " + nRet);
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "", VIDEO_SOURCE_TYPE videoSourceType = VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            videoSurface.SetForUser(uid, channelId, videoSourceType);
+            videoSurface.SetEnable(true);
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, .5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            var go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(3f, 4f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly ScreenShare _desktopScreenShare;
+
+        internal UserEventHandler(ScreenShare desktopScreenShare)
+        {
+            _desktopScreenShare = desktopScreenShare;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _desktopScreenShare.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _desktopScreenShare.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _desktopScreenShare.RtcEngine.GetVersion(ref build)));
+            _desktopScreenShare.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+            ScreenShare.MakeVideoView(0, "", VIDEO_SOURCE_TYPE.VIDEO_SOURCE_SCREEN);
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _desktopScreenShare.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _desktopScreenShare.Log.UpdateLog("OnLeaveChannel");
+            ScreenShare.DestroyVideoView(connection.localUid);
+            ScreenShare.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _desktopScreenShare.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _desktopScreenShare.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            ScreenShare.MakeVideoView(uid, _desktopScreenShare.GetChannelName(), VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _desktopScreenShare.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            ScreenShare.DestroyVideoView(uid);
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ScreenShare/ScreenShare.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 30197f00664e846448e0e897bdda9e74
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ScreenShareWhileVideoCall.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 82b419e912cc34998a2dd20191f4290b
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 426 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ScreenShareWhileVideoCall/ScreenShareWhileVideoCall.cs

@@ -0,0 +1,426 @@
+using System;
+using System.Linq;
+using UnityEngine;
+using UnityEngine.UI;
+using Agora.Rtc;
+using Agora.Util;
+using UnityEngine.Serialization;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.ScreenShareWhileVideoCall
+{
+    public class ScreenShareWhileVideoCall : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngineEx RtcEngine = null;
+
+        public uint Uid1 = 123;
+        public uint Uid2 = 456;
+       
+        private Dropdown _winIdSelect;
+        private Button _startShareBtn;
+        private Button _stopShareBtn;
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitEngine();
+#if UNITY_ANDROID || UNITY_IPHONE
+                GameObject.Find("winIdSelect").SetActive(false);
+#else       
+                PrepareScreenCapture();
+#endif
+                EnableUI();
+                JoinChannel();
+            }
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            
+            ChannelMediaOptions options = new ChannelMediaOptions();
+            options.autoSubscribeAudio.SetValue(true);
+            options.autoSubscribeVideo.SetValue(true);
+
+            options.publishCameraTrack.SetValue(true);
+            options.publishScreenTrack.SetValue(false);
+            options.enableAudioRecordingOrPlayout.SetValue(true);
+            options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.JoinChannel(_token, _channelName, this.Uid1, options);
+        }
+
+        private void ScreenShareJoinChannel()
+        {
+            ChannelMediaOptions options = new ChannelMediaOptions();
+            options.autoSubscribeAudio.SetValue(false);
+            options.autoSubscribeVideo.SetValue(false);
+            options.publishCameraTrack.SetValue(false);
+            options.publishScreenTrack.SetValue(true);
+            options.enableAudioRecordingOrPlayout.SetValue(false);
+#if UNITY_ANDROID || UNITY_IPHONE
+            options.publishScreenCaptureAudio.SetValue(true);
+            options.publishScreenCaptureVideo.SetValue(true);
+#endif
+            options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            var ret = RtcEngine.JoinChannelEx(_token, new RtcConnection(_channelName, this.Uid2), options);
+            Debug.Log("JoinChannelEx returns: " + ret);
+        }
+
+        private void ScreenShareLeaveChannel()
+        {
+            RtcEngine.LeaveChannelEx(new RtcConnection(_channelName, Uid2));
+        }
+
+        private void UpdateChannelMediaOptions()
+        {
+            ChannelMediaOptions options = new ChannelMediaOptions();
+            options.autoSubscribeAudio.SetValue(false);
+            options.autoSubscribeVideo.SetValue(false);
+
+            options.publishCameraTrack.SetValue(false);
+            options.publishScreenTrack.SetValue(true);
+
+#if UNITY_ANDROID || UNITY_IPHONE
+            options.publishScreenCaptureAudio.SetValue(true);
+            options.publishScreenCaptureVideo.SetValue(true);
+#endif
+
+            options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+
+            var ret = RtcEngine.UpdateChannelMediaOptions(options);
+            Debug.Log("UpdateChannelMediaOptions returns: " + ret);
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngineEx();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                                        CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                                        AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(new UserEventHandler(this));
+        }
+
+        private void PrepareScreenCapture()
+        {
+            _winIdSelect = GameObject.Find("winIdSelect").GetComponent<Dropdown>();
+
+            if (_winIdSelect == null || RtcEngine == null) return;
+
+            _winIdSelect.ClearOptions();
+
+            SIZE t = new SIZE();
+            t.width = 360;
+            t.height = 240;
+            SIZE s = new SIZE();
+            s.width = 360;
+            s.height = 240;
+            var info = RtcEngine.GetScreenCaptureSources(t, s, true);
+
+            _winIdSelect.AddOptions(info.Select(w =>
+                    new Dropdown.OptionData(
+                        string.Format("{0}: {1}-{2} | {3}", w.type, w.sourceName, w.sourceTitle, w.sourceId)))
+                .ToList());
+        }
+
+        private void EnableUI()
+        {
+            _startShareBtn = GameObject.Find("startShareBtn").GetComponent<Button>();
+            _stopShareBtn = GameObject.Find("stopShareBtn").GetComponent<Button>();
+            if (_startShareBtn != null) _startShareBtn.onClick.AddListener(OnStartShareBtnClick);
+            if (_stopShareBtn != null)
+            {
+                _stopShareBtn.onClick.AddListener(OnStopShareBtnClick);
+                _stopShareBtn.gameObject.SetActive(false);
+            }
+        }
+
+        private void OnStartShareBtnClick()
+        {
+            if (RtcEngine == null) return;
+
+            if (_startShareBtn != null) _startShareBtn.gameObject.SetActive(false);
+            if (_stopShareBtn != null) _stopShareBtn.gameObject.SetActive(true);
+      
+#if UNITY_ANDROID || UNITY_IPHONE
+            var parameters2 = new ScreenCaptureParameters2();
+            parameters2.captureAudio = true;
+            parameters2.captureVideo = true;
+            var nRet = RtcEngine.StartScreenCapture(parameters2);
+            this.Log.UpdateLog("StartScreenCapture :" + nRet);
+#else
+            RtcEngine.StopScreenCapture();
+            if (_winIdSelect == null) return;
+            var option = _winIdSelect.options[_winIdSelect.value].text;
+            if (string.IsNullOrEmpty(option)) return;
+
+            if (option.Contains("ScreenCaptureSourceType_Window"))
+            {
+                var windowId = option.Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[1];
+                Log.UpdateLog(string.Format(">>>>> Start sharing {0}", windowId));
+                var nRet = RtcEngine.StartScreenCaptureByWindowId(ulong.Parse(windowId), default(Rectangle),
+                        default(ScreenCaptureParameters));
+                this.Log.UpdateLog("StartScreenCaptureByWindowId:" + nRet);
+            }
+            else
+            {
+                var dispId = uint.Parse(option.Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[1]);
+                Log.UpdateLog(string.Format(">>>>> Start sharing display {0}", dispId));
+                var nRet = RtcEngine.StartScreenCaptureByDisplayId(dispId, default(Rectangle),
+                    new ScreenCaptureParameters { captureMouseCursor = true, frameRate = 30 });
+                this.Log.UpdateLog("StartScreenCaptureByDisplayId:" + nRet);
+            }
+#endif
+
+            ScreenShareJoinChannel();
+        }
+
+        private void OnStopShareBtnClick()
+        {
+            ScreenShareLeaveChannel();
+            if (_startShareBtn != null) _startShareBtn.gameObject.SetActive(true);
+            if (_stopShareBtn != null) _stopShareBtn.gameObject.SetActive(false);
+            RtcEngine.StopScreenCapture();
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "", VIDEO_SOURCE_TYPE videoSourceType = VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            VideoSurface videoSurface = new VideoSurface();
+
+            if (videoSourceType == VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA)
+            {
+                videoSurface = MakeImageSurface("MainCameraView");
+            }
+            else if (videoSourceType == VIDEO_SOURCE_TYPE.VIDEO_SOURCE_SCREEN)
+            {
+                videoSurface = MakeImageSurface("ScreenShareView");
+            }
+            else
+            {
+                videoSurface = MakeImageSurface(uid.ToString());
+            }
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            videoSurface.SetForUser(uid, channelId, videoSourceType);
+            videoSurface.SetEnable(true);
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, .5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            var go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(3f, 4f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(string name)
+        {
+            var go = GameObject.Find(name);
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly ScreenShareWhileVideoCall _desktopScreenShare;
+
+        internal UserEventHandler(ScreenShareWhileVideoCall desktopScreenShare)
+        {
+            _desktopScreenShare = desktopScreenShare;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _desktopScreenShare.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _desktopScreenShare.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _desktopScreenShare.RtcEngine.GetVersion(ref build)));
+            _desktopScreenShare.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+            if (connection.localUid == _desktopScreenShare.Uid1)
+            {
+                ScreenShareWhileVideoCall.MakeVideoView(0);
+            }
+            else if (connection.localUid == _desktopScreenShare.Uid2)
+            {
+                ScreenShareWhileVideoCall.MakeVideoView(0, "", VIDEO_SOURCE_TYPE.VIDEO_SOURCE_SCREEN);
+            }
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _desktopScreenShare.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _desktopScreenShare.Log.UpdateLog("OnLeaveChannel");
+            if (connection.localUid == _desktopScreenShare.Uid1)
+            {
+                ScreenShareWhileVideoCall.DestroyVideoView("MainCameraView");
+            }
+            else if (connection.localUid == _desktopScreenShare.Uid2)
+            {
+                ScreenShareWhileVideoCall.DestroyVideoView("ScreenShareView");
+            }
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _desktopScreenShare.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _desktopScreenShare.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            if (uid != _desktopScreenShare.Uid1 && uid != _desktopScreenShare.Uid2)
+            {
+                ScreenShareWhileVideoCall.MakeVideoView(uid, _desktopScreenShare.GetChannelName(), VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _desktopScreenShare.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            if (uid != _desktopScreenShare.Uid1 && uid != _desktopScreenShare.Uid2)
+            {
+                ScreenShareWhileVideoCall.DestroyVideoView(uid.ToString());
+            }
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/ScreenShareWhileVideoCall/ScreenShareWhileVideoCall.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 66152178a0c484a70bc11063a14e7097
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetBeautyEffectOptions.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 75a737c750dd9474d89371535ae3baae
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 342 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetBeautyEffectOptions/SetBeautyEffectOptions.cs

@@ -0,0 +1,342 @@
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.SetBeautyEffectOptions
+{
+    public class SetBeautyEffectOptions : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+        public Text TextLighteningLevel;
+        public Text TextSmoothnessLevel;
+        public Text TextRednessLevel;
+        public Text TextSharpnessLevel;
+
+        public Slider SliderLighteningLevel;
+        public Slider SliderSmoothnessLevel;
+        public Slider SliderRednessLevel;
+        public Slider SliderSharpnessLevel;
+
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                SetUpUI();
+                InitEngine();
+                InitLogFilePath();
+                JoinChannel();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                                        CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                                        AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.JoinChannel(_token, _channelName);
+        }
+
+        private void InitLogFilePath()
+        {
+            var path = Application.persistentDataPath + "/rtc.log";
+#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+             path = path.Replace('/', '\\');
+#endif
+            var nRet = RtcEngine.SetLogFile(path);
+            this.Log.UpdateLog(string.Format("logPath:{0},nRet:{1}", path, nRet));
+        }
+
+        private void SetUpUI()
+        {
+            this.SliderLighteningLevel.onValueChanged.AddListener((float value) =>
+            {
+                this.TextLighteningLevel.text = "lighteningLevel:" + value;
+            });
+
+            this.SliderSmoothnessLevel.onValueChanged.AddListener((float value) =>
+            {
+                this.TextSmoothnessLevel.text = "smoothnessLevel:" + value;
+            });
+
+            this.SliderRednessLevel.onValueChanged.AddListener((float value) =>
+            {
+                this.TextRednessLevel.text = "rednessLevel:" + value;
+            });
+
+            this.SliderSharpnessLevel.onValueChanged.AddListener((float value) =>
+            {
+                this.TextSharpnessLevel.text = "sharpnessLevel:" + value;
+            });
+
+            var btn = this.transform.Find("UI/StartButton").GetComponent<Button>();
+            btn.onClick.AddListener(this.OnStartButtonPress);
+
+            btn = this.transform.Find("UI/StopButton").GetComponent<Button>();
+            btn.onClick.AddListener(this.OnStopButtonPress);
+        }
+
+        private void OnStartButtonPress()
+        {
+            var beautyOptions = new BeautyOptions();
+            beautyOptions.lighteningContrastLevel = LIGHTENING_CONTRAST_LEVEL.LIGHTENING_CONTRAST_HIGH;
+
+            beautyOptions.lighteningLevel = this.SliderLighteningLevel.value;
+            beautyOptions.smoothnessLevel = this.SliderSmoothnessLevel.value;
+            beautyOptions.rednessLevel = this.SliderRednessLevel.value;
+            beautyOptions.sharpnessLevel = this.SliderSharpnessLevel.value;
+
+            var nRet = RtcEngine.SetBeautyEffectOptions(true, beautyOptions/*, MEDIA_SOURCE_TYPE.PRIMARY_CAMERA_SOURCE*/);
+            this.Log.UpdateLog("Start SetBeautyEffectOptions:" + nRet);
+        }
+
+        private void OnStopButtonPress()
+        {
+            var beautyOptions = new BeautyOptions();
+            beautyOptions.lighteningContrastLevel = LIGHTENING_CONTRAST_LEVEL.LIGHTENING_CONTRAST_HIGH;
+
+            beautyOptions.lighteningLevel = this.SliderLighteningLevel.value;
+            beautyOptions.smoothnessLevel = this.SliderSmoothnessLevel.value;
+            beautyOptions.rednessLevel = this.SliderRednessLevel.value;
+            beautyOptions.sharpnessLevel = this.SliderSharpnessLevel.value;
+
+            var nRet = RtcEngine.SetBeautyEffectOptions(false, beautyOptions/*, MEDIA_SOURCE_TYPE.PRIMARY_CAMERA_SOURCE*/);
+            this.Log.UpdateLog("Stop SetBeautyEffectOptions:" + nRet);
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "")
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            if (uid == 0)
+            {
+                videoSurface.SetForUser(uid, channelId);
+            }
+            else
+            {
+                videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+
+            videoSurface.SetEnable(true);
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            var yPos = Random.Range(3.0f, 5.0f);
+            var xPos = Random.Range(-2.0f, 2.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly SetBeautyEffectOptions _sample;
+
+        internal UserEventHandler(SetBeautyEffectOptions sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _sample.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            Debug.Log("Agora: OnJoinChannelSuccess ");
+            _sample.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _sample.RtcEngine.GetVersion(ref build)));
+            _sample.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+
+            SetBeautyEffectOptions.MakeVideoView(0);
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _sample.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _sample.Log.UpdateLog("OnLeaveChannel");
+            SetBeautyEffectOptions.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _sample.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            SetBeautyEffectOptions.MakeVideoView(uid, _sample.GetChannelName());
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            SetBeautyEffectOptions.DestroyVideoView(uid);
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetBeautyEffectOptions/SetBeautyEffectOptions.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e3cec9445bcf246fa896bbe9db470ff3
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetEncryption.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 610ddfa875b7b4ae093f37b8835f8416
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 290 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetEncryption/EncryptionSample.cs

@@ -0,0 +1,290 @@
+using System.Text;
+using Agora.Rtc;
+using Agora.Util;
+using UnityEngine;
+using UnityEngine.Serialization;
+using UnityEngine.UI;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.SetEncryption
+{
+    public class EncryptionSample : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        [SerializeField]
+        public ENCRYPTION_MODE EncrytionMode = ENCRYPTION_MODE.AES_128_GCM2;
+
+        private string secret = "Hello_Unity";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine;
+
+        // Start is called before the first frame update
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitRtcEngine();
+                SetEncryption();
+                JoinChannel();
+            }
+        }
+
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private void InitRtcEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(new UserEventHandler(this));
+        }
+
+        private byte[] GetEncryptionSaltFromServer()
+        {
+            return Encoding.UTF8.GetBytes("EncryptionKdfSaltInBase64Strings");
+        }
+
+        private void SetEncryption()
+        {
+            byte[] kdfSal = this.GetEncryptionSaltFromServer(); 
+            var config = new EncryptionConfig
+            {
+                encryptionMode = EncrytionMode,
+                encryptionKey = secret,
+                encryptionKdfSalt = kdfSal
+            };
+            Log.UpdateLog(string.Format("encryption mode: {0} secret: {1}", EncrytionMode, secret));
+            var nRet= RtcEngine.EnableEncryption(true, config);
+            this.Log.UpdateLog("EnableEncryption: " + nRet);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.JoinChannel(_token, _channelName, "", 0);
+        }
+
+        private void OnLeaveBtnClick()
+        {
+            RtcEngine.LeaveChannel();
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "")
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            if (uid == 0)
+            {
+                videoSurface.SetForUser(uid, channelId);
+            }
+            else
+            {
+                videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+
+            videoSurface.SetEnable(true);
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            var yPos = Random.Range(3.0f, 5.0f);
+            var xPos = Random.Range(-2.0f, 2.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly EncryptionSample _encryptionSample;
+
+        internal UserEventHandler(EncryptionSample encryptionSample)
+        {
+            _encryptionSample = encryptionSample;
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _encryptionSample.Log.UpdateLog(string.Format("sdk version: {0}",
+                _encryptionSample.RtcEngine.GetVersion(ref build)));
+            _encryptionSample.Log.UpdateLog(string.Format(
+                "onJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}", connection.channelId,
+                connection.localUid, elapsed));
+            EncryptionSample.MakeVideoView(0);
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _encryptionSample.Log.UpdateLog("OnLeaveChannelSuccess");
+            EncryptionSample.MakeVideoView(0);
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _encryptionSample.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            EncryptionSample.MakeVideoView(uid, _encryptionSample.GetChannelName());
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _encryptionSample.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            EncryptionSample.DestroyVideoView(uid);
+        }
+
+        public override void OnError(int error, string msg)
+        {
+            _encryptionSample.Log.UpdateLog(string.Format("OnSDKError error: {0}, msg: {1}", error, msg));
+        }
+
+        public override void OnConnectionLost(RtcConnection connection)
+        {
+            _encryptionSample.Log.UpdateLog(string.Format("OnConnectionLost "));
+        }
+
+        public override void OnEncryptionError(RtcConnection connection, ENCRYPTION_ERROR_TYPE errorType)
+        {
+            _encryptionSample.Log.UpdateLog("OnEncryptionError: " + errorType);
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetEncryption/EncryptionSample.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: db9ab9f4a16b440d5a282ceeaa0bf19c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetVideoEncodeConfiguration.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e810ffe8dc00648f9ba40bf70916bab5
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 288 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetVideoEncodeConfiguration/SetVideoEncodeConfiguration.cs

@@ -0,0 +1,288 @@
+using UnityEngine;
+using UnityEngine.UI;
+using Agora.Rtc;
+using Agora.Util;
+using UnityEngine.Serialization;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.SetVideoEncodeConfiguration
+{
+    public class SetVideoEncodeConfiguration : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+
+        // A list of dimensions for swithching
+        private VideoDimensions[] _dimensions = new VideoDimensions[]
+        {
+            new VideoDimensions {width = 640, height = 480},
+            new VideoDimensions {width = 480, height = 480},
+            new VideoDimensions {width = 480, height = 240}
+        };
+
+        // Start is called before the first frame update
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitEngine();
+                JoinChannel();
+                SetVideoEncoderConfiguration();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.JoinChannel(_token, _channelName, "");
+        }
+
+        private void SetVideoEncoderConfiguration(int dim = 0)
+        {
+            if (dim >= _dimensions.Length)
+            {
+                Debug.LogError("Invalid dimension choice!");
+                return;
+            }
+
+            VideoEncoderConfiguration config = new VideoEncoderConfiguration
+            {
+                dimensions = _dimensions[dim],
+                frameRate = 15,
+                codecType = VIDEO_CODEC_TYPE.VIDEO_CODEC_H264,
+                bitrate = 0,
+                minBitrate = 1,
+                orientationMode = ORIENTATION_MODE.ORIENTATION_MODE_ADAPTIVE,
+                degradationPreference = DEGRADATION_PREFERENCE.MAINTAIN_FRAMERATE,
+                mirrorMode = VIDEO_MIRROR_MODE_TYPE.VIDEO_MIRROR_MODE_AUTO
+            };
+            RtcEngine.SetVideoEncoderConfiguration(config);
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "")
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            if (uid == 0)
+            {
+                videoSurface.SetForUser(uid, channelId);
+            }
+            else
+            {
+                videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+            videoSurface.SetEnable(true);
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly SetVideoEncodeConfiguration _videoEncoderConfiguration;
+
+        internal UserEventHandler(SetVideoEncodeConfiguration videoEncoderConfiguration)
+        {
+            _videoEncoderConfiguration = videoEncoderConfiguration;
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _videoEncoderConfiguration.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _videoEncoderConfiguration.RtcEngine.GetVersion(ref build)));
+            _videoEncoderConfiguration.Log.UpdateLog(string.Format(
+                "onJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}", connection.channelId,
+                connection.localUid, elapsed));
+            SetVideoEncodeConfiguration.MakeVideoView(0);
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _videoEncoderConfiguration.Log.UpdateLog("OnLeaveChannelSuccess");
+            SetVideoEncodeConfiguration.DestroyVideoView(0);
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _videoEncoderConfiguration.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid,
+                elapsed));
+            SetVideoEncodeConfiguration.MakeVideoView(uid, _videoEncoderConfiguration.GetChannelName());
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _videoEncoderConfiguration.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            SetVideoEncodeConfiguration.DestroyVideoView(uid);
+        }
+
+        public override void OnError(int error, string msg)
+        {
+            _videoEncoderConfiguration.Log.UpdateLog(
+                string.Format("OnSDKError error: {0}, msg: {1}", error, msg));
+        }
+
+        public override void OnConnectionLost(RtcConnection connection)
+        {
+            _videoEncoderConfiguration.Log.UpdateLog(string.Format("OnConnectionLost "));
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SetVideoEncodeConfiguration/SetVideoEncodeConfiguration.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9f9dbfee6c215400fbdc75b2bc7792f8
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SpatialAudioWithMediaPlayer.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4b450026420f14d85b35a9510aac11a2
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 394 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SpatialAudioWithMediaPlayer/SpatialAudioWithMediaPlayer.cs

@@ -0,0 +1,394 @@
+using System;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.SpatialAudioWithMediaPlayer
+{
+    public class SpatialAudioWithMediaPlayer : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngineEx RtcEngine = null;
+        internal IMediaPlayer MediaPlayer = null;
+
+
+        private const string MPK_URL =
+            "https://agoracdn.s3.us-west-1.amazonaws.com/videos/Agora.io-Interactions.mp4";
+
+        private Button _button1;
+        private Button _button2;
+        private Button _button3;
+
+        public uint UidUseInEx = 123;
+        public uint UidUseInMPK = 67890;
+
+        public ILocalSpatialAudioEngine SpatialAudioEngine;
+        public int x = 0;
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                SetUpUI();
+                InitEngine();
+                InitMediaPlayer();
+                InitSpatialAudioEngine();
+                JoinChannelEx(_channelName, UidUseInEx);
+                JoinChannelExWithMPK(_channelName, UidUseInMPK, MediaPlayer.GetId());
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngineEx();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                                        CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                                        AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_GAME_STREAMING);
+            var ret = RtcEngine.Initialize(context);
+            Debug.Log("Agora: Initialize " + ret);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void InitMediaPlayer()
+        {
+            MediaPlayer = RtcEngine.CreateMediaPlayer();
+            if (MediaPlayer == null)
+            {
+                Debug.Log("GetAgoraRtcMediaPlayer failed!");
+                return;
+            }
+
+            MpkEventHandler handler = new MpkEventHandler(this);
+            MediaPlayer.InitEventHandler(handler);
+            Debug.Log("playerId id: " + MediaPlayer.GetId());
+        }
+
+        private void InitSpatialAudioEngine()
+        {
+            SpatialAudioEngine = RtcEngine.GetLocalSpatialAudioEngine();
+            var ret = SpatialAudioEngine.Initialize();
+            Debug.Log("_spatialAudioEngine: Initialize " + ret);
+            SpatialAudioEngine.SetAudioRecvRange(30);
+        }
+
+        private void JoinChannelEx(string channelName, uint uid)
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.EnableSpatialAudio(true);
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+
+            RtcConnection connection = new RtcConnection();
+            connection.channelId = channelName;
+            connection.localUid = uid;
+            ChannelMediaOptions options = new ChannelMediaOptions();
+            options.autoSubscribeAudio.SetValue(true);
+            options.autoSubscribeVideo.SetValue(true);
+            options.publishMicrophoneTrack.SetValue(false);
+            options.publishCameraTrack.SetValue(true);
+            options.enableAudioRecordingOrPlayout.SetValue(true);
+            options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            var ret = RtcEngine.JoinChannelEx("", connection, options);
+            Debug.Log("RtcEngineController JoinChannelEx returns: " + ret);
+        }
+
+        private void JoinChannelExWithMPK(string channelName, uint uid, int playerId)
+        {
+            RtcConnection connection = new RtcConnection();
+            connection.channelId = channelName;
+            connection.localUid = uid;
+            ChannelMediaOptions options = new ChannelMediaOptions();
+            options.autoSubscribeAudio.SetValue(false);
+            options.autoSubscribeVideo.SetValue(true);
+            //options.publishAudioTrack.SetValue(false);
+            options.publishCameraTrack.SetValue(false);
+            options.publishMediaPlayerAudioTrack.SetValue(true);
+            options.publishMediaPlayerVideoTrack.SetValue(true);
+            options.publishMediaPlayerId.SetValue(playerId);
+            options.enableAudioRecordingOrPlayout.SetValue(false);
+            options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            var ret = RtcEngine.JoinChannelEx("", connection, options);
+            RtcEngine.UpdateChannelMediaOptionsEx(options, connection);
+            Debug.Log("RtcEngineController JoinChannelEx_MPK returns: " + ret);
+        }
+
+        private void SetUpUI()
+        {
+            _button1 = GameObject.Find("Button1").GetComponent<Button>();
+            _button1.onClick.AddListener(onLeftLocationPress);
+            _button2 = GameObject.Find("Button2").GetComponent<Button>();
+            _button2.onClick.AddListener(onRightLocationPress);
+            _button3 = GameObject.Find("Button3").GetComponent<Button>();
+            _button3.onClick.AddListener(onOpenButtonPress);
+        }
+
+        private void onLeftLocationPress()
+        {
+            float[] f1 = { 0.0f, 1.0f, 0.0f };
+            var ret = SpatialAudioEngine.UpdateRemotePositionEx(UidUseInMPK, f1, new float[] { 0, 0, 0 }, new RtcConnection(_channelName, UidUseInEx));
+            Debug.Log("_spatialAudio.UpdateRemotePosition returns: " + ret);
+        }
+
+        private void onRightLocationPress()
+        {
+            float[] f1 = { 0.0f, -1.0f, 0.0f };
+            var ret = SpatialAudioEngine.UpdateRemotePositionEx(UidUseInMPK, f1, new float[] { 0, 0, 0 }, new RtcConnection(_channelName, UidUseInEx));
+            Debug.Log("_spatialAudio.UpdateRemotePosition returns: " + ret);
+        }
+
+        private void onOpenButtonPress()
+        {
+            var ret = MediaPlayer.Open(MPK_URL, 0);
+            Debug.Log("_mediaPlayer.Open returns: " + ret);
+
+            MediaPlayer.AdjustPlayoutVolume(0);
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "", VIDEO_SOURCE_TYPE source = VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            videoSurface.SetForUser(uid, channelId, source);
+            videoSurface.SetEnable(true);
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class MpkEventHandler : IMediaPlayerSourceObserver
+    {
+        private readonly SpatialAudioWithMediaPlayer _spatialAudio;
+
+        internal MpkEventHandler(SpatialAudioWithMediaPlayer spatialAudio)
+        {
+            _spatialAudio = spatialAudio;
+        }
+
+        public override void OnPlayerSourceStateChanged(MEDIA_PLAYER_STATE state, MEDIA_PLAYER_ERROR ec)
+        {
+            _spatialAudio.Log.UpdateLog(string.Format(
+                "OnPlayerSourceStateChanged state: {0}, ec: {1}, playId: {2}", state, ec, _spatialAudio.MediaPlayer.GetId()));
+            Debug.Log("OnPlayerSourceStateChanged");
+            if (state == MEDIA_PLAYER_STATE.PLAYER_STATE_OPEN_COMPLETED)
+            {
+                _spatialAudio.x = 1;
+                var ret = _spatialAudio.MediaPlayer.Play();
+                Debug.Log("Play return" + ret);
+                SpatialAudioWithMediaPlayer.MakeVideoView(_spatialAudio.UidUseInMPK, _spatialAudio.GetChannelName(), VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+        }
+    }
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly SpatialAudioWithMediaPlayer _spatialAudio;
+
+        internal UserEventHandler(SpatialAudioWithMediaPlayer spatialAudio)
+        {
+            _spatialAudio = spatialAudio;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _spatialAudio.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            Debug.Log("Agora: OnJoinChannelSuccess ");
+            _spatialAudio.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+            float[] f1 = new float[] { 0.0f, 0.0f, 0.0f };
+            float[] f2 = new float[] { 1.0f, 0.0f, 0.0f };
+            float[] f3 = new float[] { 0.0f, 1.0f, 0.0f };
+            float[] f4 = new float[] { 0.0f, 0.0f, 1.0f };
+            var ret = _spatialAudio.SpatialAudioEngine.UpdateSelfPositionEx(f1, f2, f3, f4, connection);
+            Debug.Log("UpdateSelfPosition return: " + ret);
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _spatialAudio.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _spatialAudio.Log.UpdateLog("OnLeaveChannel");
+            SpatialAudioWithMediaPlayer.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _spatialAudio.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _spatialAudio.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            if (uid == _spatialAudio.UidUseInMPK)
+            {
+                _spatialAudio.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+
+            }
+            else if (uid == _spatialAudio.UidUseInEx)
+            {
+                _spatialAudio.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+                SpatialAudioWithMediaPlayer.MakeVideoView(uid, _spatialAudio.GetChannelName());
+            }
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _spatialAudio.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            SpatialAudioWithMediaPlayer.DestroyVideoView(uid);
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/SpatialAudioWithMediaPlayer/SpatialAudioWithMediaPlayer.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f3f046281ab464c22afb0035b3f2d15e
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartDirectCdnStreaming.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d2e762771c3ca4c158d1514f2de01f80
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 298 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartDirectCdnStreaming/StartDirectCdnStreaming.cs

@@ -0,0 +1,298 @@
+using Agora.Rtc;
+using Agora.Util;
+using UnityEngine;
+using UnityEngine.Serialization;
+using UnityEngine.UI;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.StartDirectCdnStreaming
+{
+    public class StartDirectCdnStreaming : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+
+        private const string PUBLISH_URL = "rtmp://push.alexmk.name/live/agora_rtc_unity";
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+        // Use this for initialization
+        void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitEngine();
+                SetProfile();
+                StartDirectCdnStreamingCamera();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(new UserEventHandler(this));
+        }
+
+        private void SetProfile()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetChannelProfile(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING);
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+        }
+
+        private void StartDirectCdnStreamingCamera()
+        {
+            DirectCdnStreamingMediaOptions options = new DirectCdnStreamingMediaOptions();
+            options.publishMicrophoneTrack.SetValue(true);
+            options.publishCameraTrack.SetValue(true);
+           
+            RtcEngine.SetDirectCdnStreamingVideoConfiguration(new VideoEncoderConfiguration
+            {
+                dimensions = new VideoDimensions { width = 1280, height = 720 },
+                frameRate = 15,
+                bitrate = 2260,
+                minBitrate = -1,
+                degradationPreference = DEGRADATION_PREFERENCE.MAINTAIN_QUALITY,
+                codecType = VIDEO_CODEC_TYPE.VIDEO_CODEC_H264,
+                mirrorMode = VIDEO_MIRROR_MODE_TYPE.VIDEO_MIRROR_MODE_DISABLED
+            });
+            RtcEngine.StartDirectCdnStreaming(PUBLISH_URL, options);
+            RtcEngine.StartPreview();
+            MakeVideoView(0);
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            DestroyVideoView(0);
+            RtcEngine.StopPreview();
+            RtcEngine.StopDirectCdnStreaming();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "")
+        {
+            GameObject go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            VideoSurface videoSurface = MakeImageSurface(uid.ToString());
+            if (!ReferenceEquals(videoSurface, null))
+            {
+                // configure videoSurface
+                if (uid == 0)
+                {
+                    videoSurface.SetForUser(uid, channelId);
+                }
+                else
+                {
+                    videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+                }
+
+                videoSurface.OnTextureSizeModify += (int width, int height) =>
+                {
+                    float scale = (float)height / (float)width;
+                    videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                    Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+                };
+
+                videoSurface.SetEnable(true);
+            }
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            GameObject go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            float yPos = Random.Range(3.0f, 5.0f);
+            float xPos = Random.Range(-2.0f, 2.0f);
+            go.transform.position = new Vector3(xPos, yPos, 0f);
+            go.transform.localScale = new Vector3(0.25f, 0.5f, .5f);
+
+            // configure videoSurface
+            VideoSurface videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            GameObject canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(3f, 4f, 1f);
+
+            // configure videoSurface
+            VideoSurface videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            GameObject go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Object.Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly StartDirectCdnStreaming _startDirectCdnStreaming;
+
+        internal UserEventHandler(StartDirectCdnStreaming startDirectCdnStreaming)
+        {
+            _startDirectCdnStreaming = startDirectCdnStreaming;
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            _startDirectCdnStreaming.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _startDirectCdnStreaming.RtcEngine.GetVersion(ref build)));
+            _startDirectCdnStreaming.Log.UpdateLog(string.Format(
+                "onJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}", connection.channelId,
+                connection.localUid, elapsed));
+            StartDirectCdnStreaming.MakeVideoView(0);
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _startDirectCdnStreaming.Log.UpdateLog("OnLeaveChannelSuccess");
+            StartDirectCdnStreaming.DestroyVideoView(0);
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint remoteUid, int elapsed)
+        {
+            _startDirectCdnStreaming.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}",
+                connection.localUid, elapsed));
+            StartDirectCdnStreaming.MakeVideoView(remoteUid, _startDirectCdnStreaming.GetChannelName());
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint remoteUid,
+            USER_OFFLINE_REASON_TYPE reason)
+        {
+            _startDirectCdnStreaming.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", remoteUid,
+                (int)reason));
+            StartDirectCdnStreaming.DestroyVideoView(remoteUid);
+        }
+
+        public override void OnError(int error, string msg)
+        {
+            _startDirectCdnStreaming.Log.UpdateLog(string.Format("OnSDKError error: {0}, msg: {1}", error, msg));
+        }
+
+        public override void OnConnectionLost(RtcConnection connection)
+        {
+            _startDirectCdnStreaming.Log.UpdateLog("OnConnectionLost ");
+        }
+
+        public override void OnDirectCdnStreamingStateChanged(DIRECT_CDN_STREAMING_STATE state, DIRECT_CDN_STREAMING_ERROR error, string message)
+        {
+            _startDirectCdnStreaming.Log.UpdateLog(string.Format("OnDirectCdnStreamingStateChanged state: {0}, error: {1}", state, error));
+        }
+
+        public override void OnDirectCdnStreamingStats(DirectCdnStreamingStats stats)
+        {
+            _startDirectCdnStreaming.Log.UpdateLog("OnDirectCdnStreamingStats videoHeight:" + stats.videoHeight + " videoWidth:" + stats.videoWidth);
+            _startDirectCdnStreaming.Log.UpdateLog("OnDirectCdnStreamingStats fps:" + stats.fps);
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartDirectCdnStreaming/StartDirectCdnStreaming.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3ea89cbadc7a54f848634047cec68208
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartLocalVideoTranscoder.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: bd5527e4f810d4c49b5ba392b0014656
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 563 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartLocalVideoTranscoder/StartLocalVideoTranscoder.cs

@@ -0,0 +1,563 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using Agora.Rtc;
+using Agora.Util;
+using UnityEngine;
+using UnityEngine.Serialization;
+using UnityEngine.UI;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.StartLocalVideoTranscoder
+{
+    public class StartLocalVideoTranscoder : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+        internal IMediaPlayer MediaPlayer = null;
+
+        internal List<uint> RemoteUserUids = new List<uint>();
+
+
+        public Toggle ToggleRecord;
+        public Toggle TogglePrimartCamera;
+        public Toggle ToggleSecondaryCamera;
+        public Toggle TogglePng;
+        public Toggle ToggleJpg;
+        public Toggle ToggleGif;
+        public Toggle ToggleRemote;
+        public Toggle ToggleScreenShare;
+        public Toggle ToggleMediaPlay;
+
+
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                SetUpUI();
+                InitEngine();
+                InitMediaPlayer();
+                JoinChannel();
+            }
+        }
+
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+
+        private void SetUpUI()
+        {
+            var ui = this.transform.Find("UI");
+
+            var btn = ui.Find("StartButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnStartButtonPress);
+
+            btn = ui.Find("UpdateButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnUpdateButtonPress);
+
+            btn = ui.Find("StopButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnStopButtonPress);
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                                        CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                                        AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            var ret = RtcEngine.Initialize(context);
+            Debug.Log("Agora: Initialize " + ret);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void InitMediaPlayer()
+        {
+            MediaPlayer = RtcEngine.CreateMediaPlayer();
+
+            if (MediaPlayer == null)
+            {
+                Debug.Log("GetAgoraRtcMediaPlayer failed!");
+            }
+            MpkEventHandler handler = new MpkEventHandler(this);
+            MediaPlayer.InitEventHandler(handler);
+            Debug.Log("playerId id: " + MediaPlayer.GetId());
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            MakeVideoView(0, "", VIDEO_SOURCE_TYPE.VIDEO_SOURCE_TRANSCODED);
+
+            var options = new ChannelMediaOptions();
+            options.publishCameraTrack.SetValue(false);
+            options.publishSecondaryCameraTrack.SetValue(false);
+            options.publishTrancodedVideoTrack.SetValue(true);
+            RtcEngine.JoinChannel(_token, _channelName, 0, options);
+        }
+
+        private LocalTranscoderConfiguration GenerateLocalTranscoderConfiguration()
+        {
+
+            List<TranscodingVideoStream> list = new List<TranscodingVideoStream>();
+
+            if (this.ToggleRecord.isOn)
+            {
+                list.Add(new TranscodingVideoStream(MEDIA_SOURCE_TYPE.AUDIO_RECORDING_SOURCE, 0, "", 0, 0, 0, 0, 1, 1, false));
+            }
+
+            if (this.TogglePrimartCamera.isOn)
+            {
+                var videoDeviceManager = RtcEngine.GetVideoDeviceManager();
+                var devices = videoDeviceManager.EnumerateVideoDevices();
+
+                if (devices.Length >= 1)
+                {
+                    var configuration = new CameraCapturerConfiguration()
+                    {
+                        format = new VideoFormat(640, 320, 30),
+                        deviceId = devices[0].deviceId
+                    };
+                    var nRet = this.RtcEngine.StartPrimaryCameraCapture(configuration);
+                    this.Log.UpdateLog("StartPrimaryCameraCapture :" + nRet);
+                    list.Add(new TranscodingVideoStream(MEDIA_SOURCE_TYPE.PRIMARY_CAMERA_SOURCE, 0, "", 0, 0, 640, 320, 1, 1, false));
+                }
+                else
+                {
+                    this.Log.UpdateLog("PRIMARY_CAMERA Not Found!");
+                }
+            }
+
+            if (this.ToggleSecondaryCamera.isOn)
+            {
+                var videoDeviceManager = RtcEngine.GetVideoDeviceManager();
+                var devices = videoDeviceManager.EnumerateVideoDevices();
+
+                if (devices.Length >= 2)
+                {
+                    var configuration = new CameraCapturerConfiguration()
+                    {
+                        format = new VideoFormat(640, 320, 30),
+                        deviceId = devices[0].deviceId
+                    };
+                    this.RtcEngine.StartSecondaryCameraCapture(configuration);
+
+                    list.Add(new TranscodingVideoStream(MEDIA_SOURCE_TYPE.SECONDARY_CAMERA_SOURCE, 0, "", 0, 0, 360, 240, 1, 1, false));
+                }
+                else
+                {
+                    this.Log.UpdateLog("SECONDARY_CAMERA Not Found!");
+                }
+            }
+
+            if (this.TogglePng.isOn)
+            {
+#if UNITY_ANDROID && !UNITY_EDITOR
+                // On Android, the StreamingAssetPath is just accessed by /assets instead of Application.streamingAssetPath
+                var filePath = "/assets/img/png.png";
+#else
+                var filePath = Path.Combine(Application.streamingAssetsPath, "img/png.png");
+#endif
+                list.Add(new TranscodingVideoStream(MEDIA_SOURCE_TYPE.RTC_IMAGE_PNG_SOURCE, 0, filePath, 320, 180, 640, 360, 1, 1, false));
+            }
+
+            if (this.ToggleJpg.isOn)
+            {
+
+#if UNITY_ANDROID && !UNITY_EDITOR
+                // On Android, the StreamingAssetPath is just accessed by /assets instead of Application.streamingAssetPath
+                var filePath = "/assets/img/jpg.jpg";
+#else
+                var filePath = Path.Combine(Application.streamingAssetsPath, "img/jpg.jpg");
+#endif
+                list.Add(new TranscodingVideoStream(MEDIA_SOURCE_TYPE.RTC_IMAGE_JPEG_SOURCE, 0, filePath, 360, 240, 360, 240, 1, 1, false));
+            }
+
+
+            if (this.ToggleGif.isOn)
+            {
+#if UNITY_ANDROID && !UNITY_EDITOR
+                // On Android, the StreamingAssetPath is just accessed by /assets instead of Application.streamingAssetPath
+                var filePath = "/assets/img/gif.gif";
+#else
+                var filePath = Path.Combine(Application.streamingAssetsPath, "img/gif.gif");
+#endif
+                list.Add(new TranscodingVideoStream(MEDIA_SOURCE_TYPE.RTC_IMAGE_GIF_SOURCE, 0, filePath, 0, 0, 476, 280, 1, 1, false));
+            }
+
+            if (this.ToggleRemote.isOn)
+            {
+                if (this.RemoteUserUids.Count >= 1)
+                {
+                    var remoteUserUid = this.RemoteUserUids[0];
+                    list.Add(new TranscodingVideoStream(MEDIA_SOURCE_TYPE.REMOTE_VIDEO_SOURCE, remoteUserUid, "", 200, 200, 100, 100, 1, 1, false));
+                }
+                else
+                {
+                    this.Log.UpdateLog("remote user not found");
+                }
+            }
+
+            if (this.ToggleScreenShare.isOn)
+            {
+                if (this.StartScreenShare())
+                {
+                    list.Add(new TranscodingVideoStream(MEDIA_SOURCE_TYPE.PRIMARY_SCREEN_SOURCE, 0, "", 480, 640, 640, 320, 1, 1, false));
+                }
+            }
+            else
+            {
+                this.StopScreenShare();
+            }
+
+            if (this.ToggleMediaPlay.isOn)
+            {
+                var ret = this.MediaPlayer.Open("https://big-class-test.oss-cn-hangzhou.aliyuncs.com/61102.1592987815092.mp4", 0);
+                this.Log.UpdateLog("Media palyer ret:" + ret);
+                var sourceId = this.MediaPlayer.GetId();
+                this.Log.UpdateLog("Media palyer ret:" + ret);
+                list.Add(new TranscodingVideoStream(MEDIA_SOURCE_TYPE.MEDIA_PLAYER_SOURCE, 0, sourceId.ToString(), 0, 0, 1080, 960, 1, 1, false));
+            }
+            else
+            {
+                this.MediaPlayer.Stop();
+            }
+
+            var conf = new LocalTranscoderConfiguration();
+            conf.streamCount = Convert.ToUInt32(list.Count);
+            conf.VideoInputStreams = new TranscodingVideoStream[list.Count];
+            for (int i = 0; i < list.Count; i++)
+            {
+                conf.VideoInputStreams[i] = list[i];
+            }
+            conf.videoOutputConfiguration.dimensions.width = 1080;
+            conf.videoOutputConfiguration.dimensions.height = 960;
+
+            return conf;
+        }
+
+
+        private bool StartScreenShare()
+        {
+#if UNITY_IPHONE || UNITY_ANDROID
+            this.Log.UpdateLog("Not Support Screen Share in this platform!");
+             return false;
+#else
+            SIZE t = new SIZE();
+            t.width = 360;
+            t.height = 240;
+            SIZE s = new SIZE();
+            s.width = 360;
+            s.height = 240;
+            var info = RtcEngine.GetScreenCaptureSources(t, s, true);
+
+            if (info.Length > 0)
+            {
+                ScreenCaptureSourceInfo item = info[0];
+                if (item.type == ScreenCaptureSourceType.ScreenCaptureSourceType_Window)
+                {
+                    RtcEngine.StartScreenCaptureByWindowId(item.sourceId, default(Rectangle),
+                       default(ScreenCaptureParameters));
+                }
+                else
+                {
+                    RtcEngine.StartScreenCaptureByDisplayId((uint)item.sourceId, default(Rectangle),
+                 new ScreenCaptureParameters { captureMouseCursor = true, frameRate = 30 });
+                }
+                return true;
+            }
+            else
+            {
+                this.Log.UpdateLog("Not Screen can share");
+                return false;
+            }
+#endif
+        }
+
+        private void StopScreenShare()
+        {
+#if UNITY_IPHONE || UNITY_ANDROID
+            this.Log.UpdateLog("Not Support Screen Share in this platform!");
+#else
+            RtcEngine.StopScreenCapture();
+#endif
+        }
+
+        private void OnStartButtonPress()
+        {
+            var conf = this.GenerateLocalTranscoderConfiguration();
+            var nRet = RtcEngine.StartLocalVideoTranscoder(conf);
+            this.Log.UpdateLog("StartLocalVideoTranscoder:" + nRet);
+
+            var options = new ChannelMediaOptions();
+            options.publishCameraTrack.SetValue(false);
+            options.publishSecondaryCameraTrack.SetValue(false);
+            options.publishTrancodedVideoTrack.SetValue(true);
+            RtcEngine.UpdateChannelMediaOptions(options);
+        }
+
+        private void OnUpdateButtonPress()
+        {
+            var conf = this.GenerateLocalTranscoderConfiguration();
+            var nRet = RtcEngine.UpdateLocalTranscoderConfiguration(conf);
+            this.Log.UpdateLog("UpdateLocalTranscoderConfiguration:" + nRet);
+        }
+
+        private void OnStopButtonPress()
+        {
+            var nRet = RtcEngine.StopLocalVideoTranscoder();
+            this.Log.UpdateLog("StopLocalVideoTranscoder:" + nRet);
+            MediaPlayer.Stop();
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static VideoSurface MakeVideoView(uint uid, string channelId = "", VIDEO_SOURCE_TYPE source = VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return null;
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return null;
+            // configure videoSurface
+            if (uid == 0)
+            {
+                videoSurface.SetForUser(uid, channelId, source);
+            }
+            else
+            {
+                videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+
+            videoSurface.SetEnable(true);
+            return videoSurface;
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly StartLocalVideoTranscoder _sample;
+
+        internal UserEventHandler(StartLocalVideoTranscoder sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _sample.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            Debug.Log("Agora: OnJoinChannelSuccess ");
+            _sample.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _sample.RtcEngine.GetVersion(ref build)));
+            _sample.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+
+            StartLocalVideoTranscoder.MakeVideoView(0);
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _sample.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _sample.Log.UpdateLog("OnLeaveChannel");
+            StartLocalVideoTranscoder.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _sample.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            StartLocalVideoTranscoder.MakeVideoView(uid, _sample.GetChannelName());
+
+            if (_sample.RemoteUserUids.Contains(uid) == false)
+                _sample.RemoteUserUids.Add(uid);
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            StartLocalVideoTranscoder.DestroyVideoView(uid);
+            _sample.RemoteUserUids.Remove(uid);
+        }
+    }
+
+    internal class MpkEventHandler : IMediaPlayerSourceObserver
+    {
+        private readonly StartLocalVideoTranscoder _sample;
+
+        internal MpkEventHandler(StartLocalVideoTranscoder sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnPlayerSourceStateChanged(MEDIA_PLAYER_STATE state, MEDIA_PLAYER_ERROR ec)
+        {
+            _sample.Log.UpdateLog(string.Format(
+                "OnPlayerSourceStateChanged state: {0}, ec: {1}, playId: {2}", state, ec, _sample.MediaPlayer.GetId()));
+            Debug.Log("OnPlayerSourceStateChanged");
+            if (state == MEDIA_PLAYER_STATE.PLAYER_STATE_OPEN_COMPLETED)
+            {
+                _sample.MediaPlayer.Play();
+            }
+        }
+
+        public override void OnPlayerEvent(MEDIA_PLAYER_EVENT @event, Int64 elapsedTime, string message)
+        {
+            _sample.Log.UpdateLog(string.Format("OnPlayerEvent state: {0}", @event));
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartLocalVideoTranscoder/StartLocalVideoTranscoder.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3e578ddb1099943bc8df22e851f97c99
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartRhythmPlayer.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 65b7c16eee72a483ebb23e78ae31440f
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 219 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartRhythmPlayer/StartRhythmPlayer.cs

@@ -0,0 +1,219 @@
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using UnityEngine.SceneManagement;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+using System.IO;
+using System;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.StartRhythmPlayer
+{
+    public class StartRhythmPlayer : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                SetupUI();
+                InitEngine();
+                JoinChannel();
+            }
+           
+        }
+
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void SetupUI()
+        {
+            var btn = this.gameObject.transform.Find("StartButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnStartButtonPress);
+
+            btn = this.gameObject.transform.Find("StopButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnStopButtonPress);
+
+            btn = this.gameObject.transform.Find("ConfigButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnConfigButtonPress);
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                                        CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                                        AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.JoinChannel(_token, _channelName);
+        }
+
+        private void OnStartButtonPress()
+        {
+#if UNITY_ANDROID && !UNITY_EDITOR
+            // On Android, the StreamingAssetPath is just accessed by /assets instead of Application.streamingAssetPath
+            string sound1 = "/assets/audio/ding.mp3";
+            string sound2 = "/assets/audio/dang.mp3";
+#else
+            string sound1 = Path.Combine(Application.streamingAssetsPath, "audio/ding.mp3");
+            string sound2 = Path.Combine(Application.streamingAssetsPath, "audio/dang.mp3");
+#endif 
+            AgoraRhythmPlayerConfig config = new AgoraRhythmPlayerConfig()
+            {
+                beatsPerMeasure = 4,
+                beatsPerMinute = 60
+            };
+
+            int nRet = RtcEngine.StartRhythmPlayer(sound1, sound2, config);
+            this.Log.UpdateLog("StartRhythmPlayer nRet:" + nRet);
+        }
+
+        private void OnStopButtonPress()
+        {
+            int nRet = RtcEngine.StopRhythmPlayer();
+            this.Log.UpdateLog("StopRhythmPlayer nRet:" + nRet);
+        }
+
+        private void OnConfigButtonPress()
+        {
+            AgoraRhythmPlayerConfig config = new AgoraRhythmPlayerConfig()
+            {
+                beatsPerMeasure = 6,
+                beatsPerMinute = 60
+            };
+            int nRet = RtcEngine.ConfigRhythmPlayer(config);
+            this.Log.UpdateLog("ConfigRhythmPlayer nRet:" + nRet);
+            this.Log.UpdateLog("beatsPerMeasure is config from 4 to 6");
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly StartRhythmPlayer _startRhythmPlayer;
+
+        internal UserEventHandler(StartRhythmPlayer videoSample)
+        {
+            _startRhythmPlayer = videoSample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _startRhythmPlayer.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            Debug.Log("Agora: OnJoinChannelSuccess ");
+            _startRhythmPlayer.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _startRhythmPlayer.RtcEngine.GetVersion(ref build)));
+            _startRhythmPlayer.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _startRhythmPlayer.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _startRhythmPlayer.Log.UpdateLog("OnLeaveChannel");
+
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _startRhythmPlayer.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _startRhythmPlayer.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _startRhythmPlayer.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+        }
+
+        public override void OnRhythmPlayerStateChanged(RHYTHM_PLAYER_STATE_TYPE state, RHYTHM_PLAYER_ERROR_TYPE errorCode)
+        {
+            _startRhythmPlayer.Log.UpdateLog(string.Format("OnRhythmPlayerStateChanged {0},{1}", state, errorCode));
+        }
+
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartRhythmPlayer/StartRhythmPlayer.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 750a7fb8de2994aca967d8d32a0da5b4
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartRtmpStreamWithTranscoding.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1b245afa0f26b44ea92337250ad6d938
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 369 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartRtmpStreamWithTranscoding/StartRtmpStreamWithTranscoding.cs

@@ -0,0 +1,369 @@
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.StartRtmpStreamWithTranscoding
+{
+    public class StartRtmpStreamWithTranscoding : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        public InputField rtmpUrl;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+        public uint Uid = 0;
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitEngine();
+                SetUpUI();
+                JoinChannel();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        public void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                                        CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                                        AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.JoinChannel(_token, _channelName);
+        }
+
+        private void SetUpUI()
+        {
+            var btn = this.transform.Find("StartButton").GetComponent<Button>();
+            btn.onClick.AddListener(this.OnStartButtonPress);
+
+            btn = this.transform.Find("UpdateButton").GetComponent<Button>();
+            btn.onClick.AddListener(this.OnUpdateButtonPress);
+
+            btn = this.transform.Find("StopButton").GetComponent<Button>();
+            btn.onClick.AddListener(this.OnStopButtonPress);
+        }
+
+        private void OnStartButtonPress()
+        {
+            if (this.Uid == 0)
+            {
+                this.Log.UpdateLog("your must join channel.");
+                return;
+            }
+
+            var liveTranscoding = new LiveTranscoding();
+            liveTranscoding.userCount = 1;
+            liveTranscoding.transcodingUsers = new TranscodingUser[1];
+            liveTranscoding.transcodingUsers[0] = new TranscodingUser()
+            {
+                uid = this.Uid,
+                x = 0,
+                y = 0,
+                width = 360,
+                height = 640,
+                alpha = 1,
+                zOrder = 1,
+                audioChannel = 0
+            };
+
+            var url = this.rtmpUrl.text;
+            if (url == "")
+            {
+                this.Log.UpdateLog("your must input your rtmpUrl in Inspector of VideoCanvas");
+                return;
+            }
+
+            var nRet = RtcEngine.StartRtmpStreamWithTranscoding(url, liveTranscoding);
+            this.Log.UpdateLog("StartRtmpStreamWithTranscoding:" + nRet);
+            if (nRet == 0)
+            {
+                this.Log.UpdateLog(url);
+                /* 
+                    Verify remote
+                    1.install ffmpeg(brew install ffmpeg)
+                    2.ffplay rtmp://play.xxxxx.xxx.xxxx
+                 */
+            }
+        }
+
+        private void OnUpdateButtonPress()
+        {
+            var liveTranscoding = new LiveTranscoding();
+            liveTranscoding.width = 640;
+            liveTranscoding.height = 960;
+            liveTranscoding.userCount = 1;
+            liveTranscoding.transcodingUsers = new TranscodingUser[1];
+            liveTranscoding.transcodingUsers[0] = new TranscodingUser()
+            {
+                uid = this.Uid,
+                x = 100,
+                y = 100,
+                width = 360,
+                height = 640,
+                alpha = 1,
+                zOrder = 1,
+                audioChannel = 0
+            };
+
+
+            var nRet = RtcEngine.UpdateRtmpTranscoding(liveTranscoding);
+            this.Log.UpdateLog("UpdateRtmpTranscoding:" + nRet);
+        }
+
+        private void OnStopButtonPress()
+        {
+            var url = this.rtmpUrl.text;
+            if (url == "")
+            {
+                this.Log.UpdateLog("your must input your rtmpUrl in Inspector of VideoCanvas");
+                return;
+            }
+            var nRet = RtcEngine.StopRtmpStream(url);
+            this.Log.UpdateLog("StopRtmpStream:" + nRet);
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "")
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            if (uid == 0)
+            {
+                videoSurface.SetForUser(uid, channelId);
+            }
+            else
+            {
+                videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+
+            videoSurface.SetEnable(true);
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            var yPos = Random.Range(3.0f, 5.0f);
+            var xPos = Random.Range(-2.0f, 2.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly StartRtmpStreamWithTranscoding _sample;
+
+        internal UserEventHandler(StartRtmpStreamWithTranscoding sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _sample.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            Debug.Log("Agora: OnJoinChannelSuccess ");
+            int build = 0;
+            _sample.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _sample.RtcEngine.GetVersion(ref build)));
+            _sample.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+            _sample.Uid = connection.localUid;
+            StartRtmpStreamWithTranscoding.MakeVideoView(0);
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _sample.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _sample.Log.UpdateLog("OnLeaveChannel");
+            StartRtmpStreamWithTranscoding.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _sample.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            StartRtmpStreamWithTranscoding.MakeVideoView(uid, _sample.GetChannelName());
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            StartRtmpStreamWithTranscoding.DestroyVideoView(uid);
+        }
+
+        public override void OnRtmpStreamingStateChanged(string url, RTMP_STREAM_PUBLISH_STATE state, RTMP_STREAM_PUBLISH_ERROR_TYPE errCode)
+        {
+            _sample.Log.UpdateLog(string.Format("OnRtmpStreamingStateChanged url:{0},  state:{1},  errCode:{2}", url, state, errCode));
+        }
+
+        public override void OnTranscodingUpdated()
+        {
+            _sample.Log.UpdateLog(string.Format("OnTranscodingUpdated"));
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StartRtmpStreamWithTranscoding/StartRtmpStreamWithTranscoding.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9ae1d7381b8c94def92e8980ef6b6971
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StreamMessage.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4e02e7c82500540d19bfc639be7d9b5a
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 222 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StreamMessage/StreamMessage.cs

@@ -0,0 +1,222 @@
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+using System;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.StreamMessage
+{
+    public class StreamMessage : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        public string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        public string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        public string _channelName = "";
+
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine;
+
+        private int _streamId = -1;
+
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitEngine();
+                SetupUI();
+                EnableUI(false);
+                JoinChannel();
+            }
+        }
+
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_GAME_STREAMING);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.EnableAudio();
+            RtcEngine.JoinChannel(_token, _channelName, "");
+        }
+
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+        }
+
+        private void SetupUI()
+        {
+            var ui = this.transform.Find("UI");
+
+            var btn = ui.Find("SendButton").GetComponent<Button>();
+            btn.onClick.AddListener(onSendButtonPress);
+        }
+
+        public void EnableUI(bool visible)
+        {
+            var ui = this.transform.Find("UI");
+            ui.gameObject.SetActive(visible);
+        }
+
+        private void onSendButtonPress()
+        {
+            var text = this.transform.Find("UI/InputField").GetComponent<InputField>();
+            if (text.text == "")
+            {
+                Log.UpdateLog("Dont send empty message!");
+            }
+
+            int streamId = this.CreateDataStreamId();
+            if (streamId < 0)
+            {
+                Log.UpdateLog("CreateDataStream failed!");
+                return;
+            }
+            else
+            {
+                SendStreamMessage(streamId, text.text);
+                text.text = "";
+            }
+        }
+
+        private int CreateDataStreamId()
+        {
+            if (this._streamId == -1)
+            {
+                var config = new DataStreamConfig();
+                config.syncWithAudio = false;
+                config.ordered = true;
+                var nRet = RtcEngine.CreateDataStream(ref this._streamId, config);
+                this.Log.UpdateLog(string.Format("CreateDataStream: nRet{0}, streamId{1}", nRet, _streamId));
+            }
+            return _streamId;
+        }
+
+        private void SendStreamMessage(int streamId, string message)
+        {
+            byte[] byteArray = System.Text.Encoding.Default.GetBytes(message);
+            var nRet = RtcEngine.SendStreamMessage(streamId, byteArray, Convert.ToUInt32(byteArray.Length));
+            this.Log.UpdateLog("SendStreamMessage :" + nRet);
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly StreamMessage _streamMessage;
+
+        internal UserEventHandler(StreamMessage videoSample)
+        {
+            _streamMessage = videoSample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _streamMessage.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            Debug.Log("Agora: OnJoinChannelSuccess ");
+            _streamMessage.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _streamMessage.RtcEngine.GetVersion(ref build)));
+            _streamMessage.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+            _streamMessage.EnableUI(true);
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _streamMessage.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _streamMessage.Log.UpdateLog("OnLeaveChannel");
+            _streamMessage.EnableUI(false);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _streamMessage.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _streamMessage.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _streamMessage.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+
+        }
+
+        public override void OnStreamMessage(RtcConnection connection, uint remoteUid, int streamId, byte[] data, uint length, ulong sentTs)
+        {
+            string streamMessage = System.Text.Encoding.Default.GetString(data);
+            _streamMessage.Log.UpdateLog(string.Format("OnStreamMessage remoteUid: {0}, stream message: {1}", remoteUid, streamMessage));
+        }
+
+        public override void OnStreamMessageError(RtcConnection connection, uint remoteUid, int streamId, int code, int missed, int cached)
+        {
+            _streamMessage.Log.UpdateLog(string.Format("OnStreamMessageError remoteUid: {0}, streamId: {1}, code: {2}, missed: {3}, cached: {4}", remoteUid, streamId, code, missed, cached));
+        }
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/StreamMessage/StreamMessage.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0ed7871e5e11a4837bdbb0bf22944f9f
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/TakeSnapshot.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e879b1b7f72434b548f9eb598cca4f00
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 295 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/TakeSnapshot/TakeSnapshot.cs

@@ -0,0 +1,295 @@
+using System.IO;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.TakeSnapshot
+{
+    public class TakeSnapshot : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        public string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+        public uint LocalUid = 0;
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                SetupUI();
+                EnableUI(false);
+                InitEngine();
+                JoinChannel();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        public void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                                        CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                                        AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.JoinChannel(_token, _channelName);
+        }
+
+        private void SetupUI()
+        {
+            var but = this.transform.Find("TakeSnapshotButton").GetComponent<Button>();
+            but.onClick.AddListener(OnTakeSnapshotButtonPress);
+
+        }
+
+        public void EnableUI(bool visible)
+        {
+            var button = this.transform.Find("TakeSnapshotButton").gameObject;
+            button.SetActive(visible);
+        }
+
+        private void OnTakeSnapshotButtonPress()
+        {
+            //uid 0 means self. you can get other user uid in OnUserJoined()
+            uint uid = 0;
+            string filePath = Path.Combine(Application.persistentDataPath, "takeSnapshot.jpg");
+            int nRet = RtcEngine.TakeSnapshot(uid, filePath);
+            this.Log.UpdateLog("TakeSnapshot nRet: " + nRet);
+            this.Log.UpdateLog("TakeSnapshot in " + filePath);
+        }
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "")
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            if (uid == 0)
+            {
+                videoSurface.SetForUser(uid, channelId);
+            }
+            else
+            {
+                videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+
+            videoSurface.SetEnable(true);
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly TakeSnapshot _takeSnapshot;
+
+        internal UserEventHandler(TakeSnapshot videoSample)
+        {
+            _takeSnapshot = videoSample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _takeSnapshot.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            Debug.Log("Agora: OnJoinChannelSuccess ");
+            _takeSnapshot.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _takeSnapshot.RtcEngine.GetVersion(ref build)));
+            _takeSnapshot.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+
+            _takeSnapshot.LocalUid = connection.localUid;
+            _takeSnapshot.EnableUI(true);
+            TakeSnapshot.MakeVideoView(0);
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _takeSnapshot.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _takeSnapshot.Log.UpdateLog("OnLeaveChannel");
+            TakeSnapshot.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _takeSnapshot.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _takeSnapshot.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            TakeSnapshot.MakeVideoView(uid, _takeSnapshot._channelName);
+            _takeSnapshot.EnableUI(true);
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _takeSnapshot.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            TakeSnapshot.DestroyVideoView(uid);
+        }
+
+        public override void OnSnapshotTaken( RtcConnection connection, uint remoteUid, string filePath, int width, int height, int errCode)
+        {
+            _takeSnapshot.Log.UpdateLog(string.Format("OnSnapshotTaken: {0},{1},{2},{3}", filePath, width, height, errCode));
+        }
+
+    }
+
+    #endregion
+}

+ 11 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/TakeSnapshot/TakeSnapshot.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 870a542894dd742da809b3a81cf9e1b8
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/VirtualBackground.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 066525371bac543da89ce22923b7c83f
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 331 - 0
Assets/Agora-RTC-Plugin/API-Example/Examples/Advanced/VirtualBackground/VirtualBackground.cs

@@ -0,0 +1,331 @@
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Serialization;
+using Agora.Rtc;
+using Agora.Util;
+using Logger = Agora.Util.Logger;
+using System.IO;
+
+namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.VirtualBackground
+{
+    public class VirtualBackground : MonoBehaviour
+    {
+        [FormerlySerializedAs("appIdInput")]
+        [SerializeField]
+        private AppIdInput _appIdInput;
+
+        [Header("_____________Basic Configuration_____________")]
+        [FormerlySerializedAs("APP_ID")]
+        [SerializeField]
+        private string _appID = "";
+
+        [FormerlySerializedAs("TOKEN")]
+        [SerializeField]
+        private string _token = "";
+
+        [FormerlySerializedAs("CHANNEL_NAME")]
+        [SerializeField]
+        private string _channelName = "";
+
+        public Text LogText;
+        internal Logger Log;
+        internal IRtcEngine RtcEngine = null;
+
+
+        // Use this for initialization
+        private void Start()
+        {
+            LoadAssetData();
+            if (CheckAppId())
+            {
+                InitEngine();
+                InitLogFilePath();
+                SetupUI();
+                JoinChannel();
+            }
+        }
+
+        // Update is called once per frame
+        private void Update()
+        {
+            PermissionHelper.RequestMicrophontPermission();
+            PermissionHelper.RequestCameraPermission();
+        }
+
+        //Show data in AgoraBasicProfile
+        [ContextMenu("ShowAgoraBasicProfileData")]
+        private void LoadAssetData()
+        {
+            if (_appIdInput == null) return;
+            _appID = _appIdInput.appID;
+            _token = _appIdInput.token;
+            _channelName = _appIdInput.channelName;
+        }
+
+        private bool CheckAppId()
+        {
+            Log = new Logger(LogText);
+            return Log.DebugAssert(_appID.Length > 10, "Please fill in your appId in API-Example/profile/appIdInput.asset");
+        }
+
+        private void InitEngine()
+        {
+            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
+            UserEventHandler handler = new UserEventHandler(this);
+            RtcEngineContext context = new RtcEngineContext(_appID, 0,
+                                        CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
+                                        AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
+            RtcEngine.Initialize(context);
+            RtcEngine.InitEventHandler(handler);
+        }
+
+        private void JoinChannel()
+        {
+            RtcEngine.EnableAudio();
+            RtcEngine.EnableVideo();
+            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
+            RtcEngine.JoinChannel(_token, _channelName);
+        }
+
+        private void InitLogFilePath()
+        {
+            var path = Application.persistentDataPath + "/rtc.log";
+#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
+             path = path.Replace('/', '\\');
+#endif
+            var nRet = RtcEngine.SetLogFile(path);
+            this.Log.UpdateLog(string.Format("logPath:{0},nRet:{1}", path, nRet));
+        }
+
+        private void SetupUI()
+        {
+            var ui = this.transform.Find("UI");
+
+            var btn = ui.Find("StartButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnStartButtonPress);
+
+            btn = ui.Find("StartButton2").GetComponent<Button>();
+            btn.onClick.AddListener(OnStartButtonPress2);
+
+            btn = ui.Find("StopButton").GetComponent<Button>();
+            btn.onClick.AddListener(OnStopButtonPress);
+        }
+
+
+        private void OnStartButtonPress()
+        {
+            var source = new VirtualBackgroundSource();
+            source.background_source_type = BACKGROUND_SOURCE_TYPE.BACKGROUND_COLOR;
+            source.color = 0xffffff;
+            var segproperty = new SegmentationProperty();
+            var nRet = RtcEngine.EnableVirtualBackground(true, source, segproperty, MEDIA_SOURCE_TYPE.PRIMARY_CAMERA_SOURCE);
+            this.Log.UpdateLog("EnableVirtualBackground true :" + nRet);
+        }
+
+        private void OnStartButtonPress2()
+        {
+            var source = new VirtualBackgroundSource();
+
+            source.background_source_type = BACKGROUND_SOURCE_TYPE.BACKGROUND_IMG;
+
+#if UNITY_ANDROID && !UNITY_EDITOR
+                // On Android, the StreamingAssetPath is just accessed by /assets instead of Application.streamingAssetPath
+                var filePath = "/assets/img/png.png";
+#else
+            var filePath = Path.Combine(Application.streamingAssetsPath, "img/png.png");
+#endif
+            source.source = filePath;
+
+            var segproperty = new SegmentationProperty();
+            var nRet = RtcEngine.EnableVirtualBackground(true, source, segproperty, MEDIA_SOURCE_TYPE.PRIMARY_CAMERA_SOURCE);
+            this.Log.UpdateLog("EnableVirtualBackground true :" + nRet);
+        }
+
+        private void OnStopButtonPress()
+        {
+            var source = new VirtualBackgroundSource();
+            var segproperty = new SegmentationProperty();
+            var nRet = RtcEngine.EnableVirtualBackground(false, source, segproperty, MEDIA_SOURCE_TYPE.PRIMARY_CAMERA_SOURCE);
+            this.Log.UpdateLog("EnableVirtualBackground false :" + nRet);
+        }
+
+
+        private void OnDestroy()
+        {
+            Debug.Log("OnDestroy");
+            if (RtcEngine == null) return;
+            RtcEngine.InitEventHandler(null);
+            RtcEngine.LeaveChannel();
+            RtcEngine.Dispose();
+        }
+
+        internal string GetChannelName()
+        {
+            return _channelName;
+        }
+
+        #region -- Video Render UI Logic ---
+
+        internal static void MakeVideoView(uint uid, string channelId = "")
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                return; // reuse
+            }
+
+            // create a GameObject and assign to this new user
+            var videoSurface = MakeImageSurface(uid.ToString());
+            if (ReferenceEquals(videoSurface, null)) return;
+            // configure videoSurface
+            if (uid == 0)
+            {
+                videoSurface.SetForUser(uid, channelId);
+            }
+            else
+            {
+                videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
+            }
+
+            videoSurface.OnTextureSizeModify += (int width, int height) =>
+            {
+                float scale = (float)height / (float)width;
+                videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
+                Debug.Log("OnTextureSizeModify: " + width + "  " + height);
+            };
+
+            videoSurface.SetEnable(true);
+        }
+
+        // VIDEO TYPE 1: 3D Object
+        private static VideoSurface MakePlaneSurface(string goName)
+        {
+            var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // set up transform
+            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
+            var yPos = Random.Range(3.0f, 5.0f);
+            var xPos = Random.Range(-2.0f, 2.0f);
+            go.transform.position = Vector3.zero;
+            go.transform.localScale = new Vector3(0.25f, 0.5f, 0.5f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        // Video TYPE 2: RawImage
+        private static VideoSurface MakeImageSurface(string goName)
+        {
+            GameObject go = new GameObject();
+
+            if (go == null)
+            {
+                return null;
+            }
+
+            go.name = goName;
+            // to be renderered onto
+            go.AddComponent<RawImage>();
+            // make the object draggable
+            go.AddComponent<UIElementDrag>();
+            var canvas = GameObject.Find("VideoCanvas");
+            if (canvas != null)
+            {
+                go.transform.parent = canvas.transform;
+                Debug.Log("add video view");
+            }
+            else
+            {
+                Debug.Log("Canvas is null video view");
+            }
+
+            // set up transform
+            go.transform.Rotate(0f, 0.0f, 180.0f);
+            go.transform.localPosition = Vector3.zero;
+            go.transform.localScale = new Vector3(2f, 3f, 1f);
+
+            // configure videoSurface
+            var videoSurface = go.AddComponent<VideoSurface>();
+            return videoSurface;
+        }
+
+        internal static void DestroyVideoView(uint uid)
+        {
+            var go = GameObject.Find(uid.ToString());
+            if (!ReferenceEquals(go, null))
+            {
+                Destroy(go);
+            }
+        }
+
+        #endregion
+    }
+
+    #region -- Agora Event ---
+
+    internal class UserEventHandler : IRtcEngineEventHandler
+    {
+        private readonly VirtualBackground _sample;
+
+        internal UserEventHandler(VirtualBackground sample)
+        {
+            _sample = sample;
+        }
+
+        public override void OnError(int err, string msg)
+        {
+            _sample.Log.UpdateLog(string.Format("OnError err: {0}, msg: {1}", err, msg));
+        }
+
+        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            int build = 0;
+            Debug.Log("Agora: OnJoinChannelSuccess ");
+            _sample.Log.UpdateLog(string.Format("sdk version: ${0}",
+                _sample.RtcEngine.GetVersion(ref build)));
+            _sample.Log.UpdateLog(
+                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
+                                connection.channelId, connection.localUid, elapsed));
+
+            VirtualBackground.MakeVideoView(0);
+        }
+
+        public override void OnRejoinChannelSuccess(RtcConnection connection, int elapsed)
+        {
+            _sample.Log.UpdateLog("OnRejoinChannelSuccess");
+        }
+
+        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
+        {
+            _sample.Log.UpdateLog("OnLeaveChannel");
+            VirtualBackground.DestroyVideoView(0);
+        }
+
+        public override void OnClientRoleChanged(RtcConnection connection, CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
+        {
+            _sample.Log.UpdateLog("OnClientRoleChanged");
+        }
+
+        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid, elapsed));
+            VirtualBackground.MakeVideoView(uid, _sample.GetChannelName());
+        }
+
+        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
+        {
+            _sample.Log.UpdateLog(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
+                (int)reason));
+            VirtualBackground.DestroyVideoView(uid);
+        }
+    }
+
+    #endregion
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов