├── Config ├── DefaultEditor.ini ├── DefaultGameUserSettings.ini ├── DefaultGame.ini └── DefaultEngine.ini ├── .DS_Store ├── Plugins └── .DS_Store ├── GuideImages ├── Anchor.png ├── New_C_Class.png ├── AddWidget_View.png ├── CreateClass_Menu.png ├── Default_Settings.png ├── FileMenu_Windows.png ├── NewLevel_Windows.png ├── NewProject_View.png ├── AddCppClass_Window.png ├── WorldSettings_View.png ├── 2020-03-12_13-26-59.png ├── AddCppWidget_Window.png ├── CreateWidgetBlueprint.png ├── NameCppClass_Window.png ├── NameCppWidget_Window.png ├── BP_VideoCallWidget_Asset.png ├── BP_VideoViewWidget_Asset.png ├── Name Inctroduction Widget.png ├── BP_VideoCallViewWidget_Asset.png ├── BP_EnterChannelWidgetBlueprint.png ├── UE4Editor_2020-03-11_11-35-02.png ├── UE4Editor_2020-03-11_12-21-56.png └── BP_VideoCallPlayerController_settings.png ├── ReadMeImages ├── iOSSettings.png ├── 2020-03-12_13-26-59.png ├── HowToPackagePlugin.png ├── HowToPackageProject.png └── HowToPackageProjectMac.png ├── Content ├── Audio │ ├── ID_TEST_AUDIO.wav │ └── ID_TEST_AUDIO.uasset ├── ButtonTextures │ ├── mute.png │ ├── hangup.png │ ├── mute.uasset │ ├── unmute.png │ ├── cameraoff.png │ ├── cameraon.png │ ├── hangup.uasset │ ├── unmute.uasset │ ├── cameraoff.uasset │ ├── cameraon.uasset │ ├── screen_sharing.png │ ├── unscreen_share.png │ ├── device_selection.png │ ├── screen_sharing.uasset │ ├── unscreen_share.uasset │ └── device_selection.uasset ├── Levels │ └── VideoCallLevel.umap ├── Textures │ ├── cameraoff_mainVideo.png │ └── cameraoff_mainVideo.uasset └── Widgets │ ├── BP_DeviceTestWidget.uasset │ ├── BP_VideoCallWidget.uasset │ ├── BP_VideoViewWidget.uasset │ ├── BP_EnterChannelWidget.uasset │ ├── BP_VideoCallViewWidget.uasset │ ├── BP_VideoSettingsWidget.uasset │ ├── BP_AgoraVideoCallGameModeBase.uasset │ └── BP_VideoCallPlayerController.uasset ├── Source ├── AgoraVideoCall │ ├── AgoraVideoCall.h │ ├── AgoraVideoCallGameModeBase.cpp │ ├── AgoraVideoCall.cpp │ ├── AgoraVideoCallGameModeBase.h │ ├── AgoraVideoCall.Build.cs │ ├── VideoFrameObserver.h │ ├── VideoFrameObserver.cpp │ ├── UI │ │ ├── VideoViewWidget.h │ │ ├── VideoCallViewWidget.h │ │ ├── VideoCallPlayerController.h │ │ ├── VideoSettingsWidget.h │ │ ├── VideoCallViewWidget.cpp │ │ ├── VideoCallWidget.h │ │ ├── EnterChannelWidget.h │ │ ├── VideoViewWidget.cpp │ │ ├── VideoSettingsWidget.cpp │ │ ├── EnterChannelWidget.cpp │ │ ├── VideoCallPlayerController.cpp │ │ ├── DeviceTestWidget.h │ │ ├── VideoCallWidget.cpp │ │ └── DeviceTestWidget.cpp │ ├── CameraManager.h │ ├── VideoCall.h │ ├── AudioInOutDeviceManager.h │ ├── CameraManager.cpp │ ├── AudioInOutDeviceManager.cpp │ └── VideoCall.cpp ├── AgoraVideoCall.Target.cs └── AgoraVideoCallEditor.Target.cs ├── AgoraVideoCall.uproject ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── README.md └── GUIDE.md /Config/DefaultEditor.ini: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/.DS_Store -------------------------------------------------------------------------------- /Plugins/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Plugins/.DS_Store -------------------------------------------------------------------------------- /GuideImages/Anchor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/Anchor.png -------------------------------------------------------------------------------- /GuideImages/New_C_Class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/New_C_Class.png -------------------------------------------------------------------------------- /ReadMeImages/iOSSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/ReadMeImages/iOSSettings.png -------------------------------------------------------------------------------- /Content/Audio/ID_TEST_AUDIO.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/Audio/ID_TEST_AUDIO.wav -------------------------------------------------------------------------------- /Content/ButtonTextures/mute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/mute.png -------------------------------------------------------------------------------- /GuideImages/AddWidget_View.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/AddWidget_View.png -------------------------------------------------------------------------------- /GuideImages/CreateClass_Menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/CreateClass_Menu.png -------------------------------------------------------------------------------- /GuideImages/Default_Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/Default_Settings.png -------------------------------------------------------------------------------- /GuideImages/FileMenu_Windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/FileMenu_Windows.png -------------------------------------------------------------------------------- /GuideImages/NewLevel_Windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/NewLevel_Windows.png -------------------------------------------------------------------------------- /GuideImages/NewProject_View.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/NewProject_View.png -------------------------------------------------------------------------------- /Content/Audio/ID_TEST_AUDIO.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/Audio/ID_TEST_AUDIO.uasset -------------------------------------------------------------------------------- /Content/ButtonTextures/hangup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/hangup.png -------------------------------------------------------------------------------- /Content/ButtonTextures/mute.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/mute.uasset -------------------------------------------------------------------------------- /Content/ButtonTextures/unmute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/unmute.png -------------------------------------------------------------------------------- /Content/Levels/VideoCallLevel.umap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/Levels/VideoCallLevel.umap -------------------------------------------------------------------------------- /GuideImages/AddCppClass_Window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/AddCppClass_Window.png -------------------------------------------------------------------------------- /GuideImages/WorldSettings_View.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/WorldSettings_View.png -------------------------------------------------------------------------------- /Content/ButtonTextures/cameraoff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/cameraoff.png -------------------------------------------------------------------------------- /Content/ButtonTextures/cameraon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/cameraon.png -------------------------------------------------------------------------------- /Content/ButtonTextures/hangup.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/hangup.uasset -------------------------------------------------------------------------------- /Content/ButtonTextures/unmute.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/unmute.uasset -------------------------------------------------------------------------------- /GuideImages/2020-03-12_13-26-59.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/2020-03-12_13-26-59.png -------------------------------------------------------------------------------- /GuideImages/AddCppWidget_Window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/AddCppWidget_Window.png -------------------------------------------------------------------------------- /GuideImages/CreateWidgetBlueprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/CreateWidgetBlueprint.png -------------------------------------------------------------------------------- /GuideImages/NameCppClass_Window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/NameCppClass_Window.png -------------------------------------------------------------------------------- /GuideImages/NameCppWidget_Window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/NameCppWidget_Window.png -------------------------------------------------------------------------------- /ReadMeImages/2020-03-12_13-26-59.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/ReadMeImages/2020-03-12_13-26-59.png -------------------------------------------------------------------------------- /ReadMeImages/HowToPackagePlugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/ReadMeImages/HowToPackagePlugin.png -------------------------------------------------------------------------------- /ReadMeImages/HowToPackageProject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/ReadMeImages/HowToPackageProject.png -------------------------------------------------------------------------------- /Content/ButtonTextures/cameraoff.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/cameraoff.uasset -------------------------------------------------------------------------------- /Content/ButtonTextures/cameraon.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/cameraon.uasset -------------------------------------------------------------------------------- /ReadMeImages/HowToPackageProjectMac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/ReadMeImages/HowToPackageProjectMac.png -------------------------------------------------------------------------------- /Content/ButtonTextures/screen_sharing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/screen_sharing.png -------------------------------------------------------------------------------- /Content/ButtonTextures/unscreen_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/unscreen_share.png -------------------------------------------------------------------------------- /Content/Textures/cameraoff_mainVideo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/Textures/cameraoff_mainVideo.png -------------------------------------------------------------------------------- /Content/Widgets/BP_DeviceTestWidget.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/Widgets/BP_DeviceTestWidget.uasset -------------------------------------------------------------------------------- /Content/Widgets/BP_VideoCallWidget.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/Widgets/BP_VideoCallWidget.uasset -------------------------------------------------------------------------------- /Content/Widgets/BP_VideoViewWidget.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/Widgets/BP_VideoViewWidget.uasset -------------------------------------------------------------------------------- /GuideImages/BP_VideoCallWidget_Asset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/BP_VideoCallWidget_Asset.png -------------------------------------------------------------------------------- /GuideImages/BP_VideoViewWidget_Asset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/BP_VideoViewWidget_Asset.png -------------------------------------------------------------------------------- /GuideImages/Name Inctroduction Widget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/Name Inctroduction Widget.png -------------------------------------------------------------------------------- /Source/AgoraVideoCall/AgoraVideoCall.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | -------------------------------------------------------------------------------- /Content/ButtonTextures/device_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/device_selection.png -------------------------------------------------------------------------------- /Content/ButtonTextures/screen_sharing.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/screen_sharing.uasset -------------------------------------------------------------------------------- /Content/ButtonTextures/unscreen_share.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/unscreen_share.uasset -------------------------------------------------------------------------------- /Content/Textures/cameraoff_mainVideo.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/Textures/cameraoff_mainVideo.uasset -------------------------------------------------------------------------------- /Content/Widgets/BP_EnterChannelWidget.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/Widgets/BP_EnterChannelWidget.uasset -------------------------------------------------------------------------------- /GuideImages/BP_VideoCallViewWidget_Asset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/BP_VideoCallViewWidget_Asset.png -------------------------------------------------------------------------------- /Content/ButtonTextures/device_selection.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/ButtonTextures/device_selection.uasset -------------------------------------------------------------------------------- /Content/Widgets/BP_VideoCallViewWidget.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/Widgets/BP_VideoCallViewWidget.uasset -------------------------------------------------------------------------------- /Content/Widgets/BP_VideoSettingsWidget.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/Widgets/BP_VideoSettingsWidget.uasset -------------------------------------------------------------------------------- /GuideImages/BP_EnterChannelWidgetBlueprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/BP_EnterChannelWidgetBlueprint.png -------------------------------------------------------------------------------- /GuideImages/UE4Editor_2020-03-11_11-35-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/UE4Editor_2020-03-11_11-35-02.png -------------------------------------------------------------------------------- /GuideImages/UE4Editor_2020-03-11_12-21-56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/UE4Editor_2020-03-11_12-21-56.png -------------------------------------------------------------------------------- /Source/AgoraVideoCall/AgoraVideoCallGameModeBase.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | 4 | #include "AgoraVideoCallGameModeBase.h" 5 | 6 | -------------------------------------------------------------------------------- /Content/Widgets/BP_AgoraVideoCallGameModeBase.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/Widgets/BP_AgoraVideoCallGameModeBase.uasset -------------------------------------------------------------------------------- /Content/Widgets/BP_VideoCallPlayerController.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/Content/Widgets/BP_VideoCallPlayerController.uasset -------------------------------------------------------------------------------- /GuideImages/BP_VideoCallPlayerController_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/Agora-Unreal-SDK-CPP/HEAD/GuideImages/BP_VideoCallPlayerController_settings.png -------------------------------------------------------------------------------- /Source/AgoraVideoCall/AgoraVideoCall.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #include "AgoraVideoCall.h" 4 | #include "Modules/ModuleManager.h" 5 | 6 | IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, AgoraVideoCall, "AgoraVideoCall" ); 7 | -------------------------------------------------------------------------------- /Config/DefaultGameUserSettings.ini: -------------------------------------------------------------------------------- 1 | [/Script/Engine.GameUserSettings] 2 | bUseVSync=False 3 | ResolutionSizeX=1280 4 | ResolutionSizeY=720 5 | LastUserConfirmedResolutionSizeX=1280 6 | LastUserConfirmedResolutionSizeY=720 7 | WindowPosX=-1 8 | WindowPosY=-1 9 | bUseDesktopResolutionForFullscreen=False 10 | FullscreenMode=2 11 | LastConfirmedFullscreenMode=2 12 | Version=5 -------------------------------------------------------------------------------- /Source/AgoraVideoCall.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class AgoraVideoCallTarget : TargetRules 7 | { 8 | public AgoraVideoCallTarget(TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Game; 11 | 12 | ExtraModuleNames.AddRange( new string[] { "AgoraVideoCall" } ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/AgoraVideoCallGameModeBase.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/GameModeBase.h" 7 | #include "AgoraVideoCallGameModeBase.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS() 13 | class AGORAVIDEOCALL_API AAgoraVideoCallGameModeBase : public AGameModeBase 14 | { 15 | GENERATED_BODY() 16 | 17 | }; 18 | -------------------------------------------------------------------------------- /Source/AgoraVideoCallEditor.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class AgoraVideoCallEditorTarget : TargetRules 7 | { 8 | public AgoraVideoCallEditorTarget(TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Editor; 11 | 12 | ExtraModuleNames.AddRange( new string[] { "AgoraVideoCall" } ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /AgoraVideoCall.uproject: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "EngineAssociation": "4.25", 4 | "Category": "", 5 | "Description": "", 6 | "Modules": [ 7 | { 8 | "Name": "AgoraVideoCall", 9 | "Type": "Runtime", 10 | "LoadingPhase": "Default", 11 | "AdditionalDependencies": [ 12 | "UMG", 13 | "Engine", 14 | "CoreUObject" 15 | ] 16 | } 17 | ], 18 | "TargetPlatforms": [ 19 | "MacNoEditor", 20 | "WindowsNoEditor", 21 | "IOS" 22 | ] 23 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/AgoraVideoCall.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class AgoraVideoCall : ModuleRules 6 | { 7 | public AgoraVideoCall(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | bEnableExceptions = true; 10 | 11 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 12 | 13 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); 14 | 15 | PrivateDependencyModuleNames.AddRange(new string[] { "AgoraPlugin" }); 16 | 17 | // Uncomment if you are using Slate UI 18 | PrivateDependencyModuleNames.AddRange(new string[] { "UMG", "Slate", "SlateCore" }); 19 | 20 | // Uncomment if you are using online features 21 | // PrivateDependencyModuleNames.Add("OnlineSubsystem"); 22 | 23 | // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/VideoFrameObserver.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | #include 8 | 9 | #include "AgoraMediaEngine.h" 10 | 11 | /** 12 | * 13 | */ 14 | class AGORAVIDEOCALL_API VideoFrameObserver : public agora::media::IVideoFrameObserver 15 | { 16 | public: 17 | virtual ~VideoFrameObserver() = default; 18 | public: 19 | bool onCaptureVideoFrame(VideoFrame& videoFrame) override; 20 | 21 | bool onRenderVideoFrame(unsigned int uid, VideoFrame& videoFrame) override; 22 | 23 | void setOnCaptureVideoFrameCallback( 24 | std::function callback); 25 | 26 | void setOnRenderVideoFrameCallback( 27 | std::function callback); 28 | 29 | virtual VIDEO_FRAME_TYPE getVideoFormatPreference() override { return FRAME_TYPE_RGBA; } 30 | 31 | private: 32 | 33 | std::function OnCaptureVideoFrame; 34 | std::function OnRenderVideoFrame; 35 | }; 36 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/VideoFrameObserver.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #include "VideoFrameObserver.h" 4 | 5 | 6 | bool VideoFrameObserver::onCaptureVideoFrame(VideoFrame& Frame) 7 | { 8 | const auto BufferSize = Frame.yStride*Frame.height; 9 | 10 | if (OnCaptureVideoFrame) 11 | { 12 | OnCaptureVideoFrame( static_cast< uint8_t* >( Frame.yBuffer ), Frame.width, Frame.height, BufferSize ); 13 | } 14 | 15 | return true; 16 | } 17 | 18 | bool VideoFrameObserver::onRenderVideoFrame(unsigned int uid, VideoFrame& Frame) 19 | { 20 | const auto BufferSize = Frame.yStride*Frame.height; 21 | 22 | if (OnRenderVideoFrame) 23 | { 24 | OnRenderVideoFrame( static_cast(Frame.yBuffer), Frame.width, Frame.height, BufferSize ); 25 | } 26 | 27 | return true; 28 | } 29 | 30 | void VideoFrameObserver::setOnCaptureVideoFrameCallback( 31 | std::function Callback) 32 | { 33 | OnCaptureVideoFrame = Callback; 34 | } 35 | 36 | void VideoFrameObserver::setOnRenderVideoFrameCallback( 37 | std::function Callback) 38 | { 39 | OnRenderVideoFrame = Callback; 40 | } 41 | 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.xcworkspace 37 | *.sln 38 | *.suo 39 | *.opensdf 40 | *.sdf 41 | *.VC.db 42 | *.VC.opendb 43 | 44 | # Precompiled Assets 45 | SourceArt/**/*.png 46 | SourceArt/**/*.tga 47 | 48 | # Binary Files 49 | Binaries/* 50 | Plugins/*/Binaries/* 51 | 52 | # Builds 53 | Build/* 54 | 55 | # Whitelist PakBlacklist-.txt files 56 | !Build/*/ 57 | Build/*/** 58 | !Build/*/PakBlacklist*.txt 59 | 60 | # Don't ignore icon files in Build 61 | !Build/**/*.ico 62 | 63 | # Built data for maps 64 | *_BuiltData.uasset 65 | 66 | # Configuration files generated by the Editor 67 | Saved/* 68 | 69 | # Compiled source files for the engine to use 70 | Intermediate/* 71 | Plugins/*/Intermediate/* 72 | 73 | # Cache files for the editor to use 74 | DerivedDataCache/* 75 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/VideoViewWidget.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Blueprint/UserWidget.h" 7 | 8 | #include "Components/Image.h" 9 | 10 | #include "VideoViewWidget.generated.h" 11 | 12 | /** 13 | * 14 | */ 15 | UCLASS() 16 | class AGORAVIDEOCALL_API UVideoViewWidget : public UUserWidget 17 | { 18 | GENERATED_BODY() 19 | 20 | public: 21 | UVideoViewWidget(const FObjectInitializer& ObjectInitializer); 22 | 23 | void NativeConstruct() override; 24 | 25 | void NativeDestruct() override; 26 | 27 | public: 28 | void NativeTick(const FGeometry& MyGeometry, float DeltaTime) override; 29 | 30 | void UpdateBuffer( 31 | uint8* RGBBuffer, 32 | uint32_t Width, 33 | uint32_t Height, 34 | uint32_t Size); 35 | 36 | void ResetBuffer(); 37 | 38 | public: 39 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 40 | UImage* RenderTargetImage = nullptr; 41 | 42 | UPROPERTY(EditDefaultsOnly) 43 | UTexture2D* RenderTargetTexture = nullptr; 44 | 45 | UTexture2D* CameraoffTexture = nullptr; 46 | 47 | uint8* Buffer = nullptr; 48 | uint32_t Width = 0; 49 | uint32_t Height = 0; 50 | uint32 BufferSize = 0; 51 | FUpdateTextureRegion2D* UpdateTextureRegion = nullptr; 52 | 53 | FSlateBrush Brush; 54 | 55 | FCriticalSection Mutex; 56 | }; 57 | 58 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/VideoCallViewWidget.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Blueprint/UserWidget.h" 7 | #include "Components/SizeBox.h" 8 | 9 | #include "VideoViewWidget.h" 10 | 11 | #include "VideoCallViewWidget.generated.h" 12 | 13 | /** 14 | * 15 | */ 16 | UCLASS() 17 | class AGORAVIDEOCALL_API UVideoCallViewWidget : public UUserWidget 18 | { 19 | GENERATED_BODY() 20 | 21 | public: 22 | UVideoCallViewWidget(const FObjectInitializer& ObjectInitializer); 23 | 24 | void NativeConstruct() override; 25 | 26 | void NativeDestruct() override; 27 | 28 | public: 29 | void NativeTick(const FGeometry& MyGeometry, float DeltaTime) override; 30 | 31 | void UpdateMainVideoBuffer( 32 | uint8* RGBBuffer, 33 | uint32_t Width, 34 | uint32_t Height, 35 | uint32_t Size); 36 | 37 | void UpdateAdditionalVideoBuffer( 38 | uint8* RGBBuffer, 39 | uint32_t Width, 40 | uint32_t Height, 41 | uint32_t Size); 42 | 43 | void ResetBuffers(); 44 | public: 45 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 46 | UVideoViewWidget* MainVideoViewWidget = nullptr; 47 | 48 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 49 | USizeBox* MainVideoSizeBox = nullptr; 50 | 51 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 52 | UVideoViewWidget* AdditionalVideoViewWidget = nullptr; 53 | 54 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 55 | USizeBox* AdditionalVideoSizeBox = nullptr; 56 | 57 | public: 58 | int32 MainVideoWidth = 0; 59 | int32 MainVideoHeight = 0; 60 | 61 | 62 | }; 63 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/CameraManager.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | #include 8 | #include 9 | 10 | class VideoFrameObserver; 11 | namespace agora 12 | { 13 | namespace media 14 | { 15 | namespace ue4 16 | { 17 | class AgoraMediaEngine; 18 | } 19 | } 20 | namespace rtc 21 | { 22 | namespace ue4 23 | { 24 | class AgoraVideoDeviceManager; 25 | class VideoDeviceCollection; 26 | class AgoraRtcEngine; 27 | } 28 | } 29 | } 30 | 31 | /** 32 | * 33 | */ 34 | class AGORAVIDEOCALL_API CameraManager 35 | { 36 | public: 37 | CameraManager() = delete; 38 | CameraManager( 39 | TSharedPtr RtcEngine, 40 | TSharedPtr MediaEngine); 41 | 42 | ~CameraManager(); 43 | 44 | static TUniquePtr Create( 45 | TSharedPtr RtcEngine, 46 | TSharedPtr MediaEngine); 47 | 48 | public: 49 | uint32 GetDeviceCount(); 50 | 51 | FString GetCurrDeviceID(); 52 | 53 | bool SetCurrDevice(const FString& DeviceID); 54 | 55 | FString GetDeviceID(const FString& DeviceName); 56 | 57 | FString GetCurrDeviceName(); 58 | 59 | std::vector GetVideoDevices(); 60 | 61 | bool GetDevice(uint32 Index, FString& DeviceName, FString& DeviceID); 62 | 63 | void TestCameraDevice( 64 | std::function Callback); 65 | 66 | void StopCameraDeviceTest(); 67 | 68 | bool IsTesting() const { return VideoFrameObserverPtr ? true : false; }; 69 | 70 | private: 71 | TSharedPtr RtcEnginePtr; 72 | TSharedPtr MediaEnginePtr; 73 | 74 | TUniquePtr VideoFrameObserverPtr; 75 | 76 | TUniquePtr DeviceManagerPtr; 77 | TUniquePtr CollectionPtr; 78 | }; 79 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/VideoCall.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | #include 8 | #include 9 | 10 | #include "AgoraRtcEngine.h" 11 | #include "AgoraMediaEngine.h" 12 | 13 | #include "CameraManager.h" 14 | #include "AudioInOutDeviceManager.h" 15 | 16 | class VideoFrameObserver; 17 | 18 | /** 19 | * 20 | */ 21 | class AGORAVIDEOCALL_API VideoCall 22 | { 23 | public: 24 | VideoCall(); 25 | ~VideoCall(); 26 | 27 | FString GetVersion() const; 28 | 29 | void SetResolution(int Width, int Height, agora::rtc::FRAME_RATE FrameRate); 30 | 31 | void RegisterOnLocalFrameCallback( 32 | std::function OnLocalFrameCallback); 33 | void RegisterOnRemoteFrameCallback( 34 | std::function OnRemoteFrameCallback); 35 | 36 | void StartCall( 37 | const FString& ChannelName, 38 | const FString& EncryptionKey, 39 | const FString& EncryptionType); 40 | 41 | void StopCall(); 42 | 43 | bool MuteLocalAudio(bool bMuted = true); 44 | bool IsLocalAudioMuted(); 45 | 46 | bool MuteLocalVideo(bool bMuted = true); 47 | bool IsLocalVideoMuted(); 48 | 49 | bool EnableVideo(bool bEnable = true); 50 | 51 | TUniquePtr GetCameraManager(); 52 | 53 | TUniquePtr GetAudioDeviceManager(); 54 | 55 | bool EnableEchoTest(bool bEnable); 56 | private: 57 | void InitAgora(); 58 | 59 | private: 60 | TSharedPtr RtcEnginePtr; 61 | TSharedPtr MediaEnginePtr; 62 | 63 | TUniquePtr VideoFrameObserverPtr; 64 | 65 | //callback 66 | //data, w, h, size 67 | std::function OnLocalFrameCallback; 68 | std::function OnRemoteFrameCallback; 69 | 70 | bool bLocalAudioMuted = false; 71 | bool bLocalVideoMuted = false; 72 | }; 73 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/AudioInOutDeviceManager.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | #include 8 | #include 9 | 10 | 11 | namespace agora 12 | { 13 | namespace rtc 14 | { 15 | namespace ue4 16 | { 17 | class AgoraAudioDeviceManager; 18 | class AudioDeviceCollection; 19 | class AgoraRtcEngine; 20 | } 21 | } 22 | } 23 | 24 | /** 25 | * 26 | */ 27 | class AGORAVIDEOCALL_API AudioInOutDeviceManager 28 | { 29 | public: 30 | AudioInOutDeviceManager() = delete; 31 | AudioInOutDeviceManager( 32 | TSharedPtr RtcEngine); 33 | 34 | ~AudioInOutDeviceManager(); 35 | 36 | static TUniquePtr Create( 37 | TSharedPtr RtcEngine); 38 | 39 | public: 40 | uint32 GetRecordingDeviceCount(); 41 | 42 | uint32 GetPlaybackDeviceCount(); 43 | 44 | std::vector GetRecordingDevices(); 45 | 46 | FString GetCurrRecordingDeviceName(); 47 | 48 | std::vector GetPlaybackDevices(); 49 | 50 | FString GetCurrPlaybackDeviceName(); 51 | 52 | bool SetCurrRecordingDevice(const FString& DeviceID); 53 | 54 | FString GetRecordingDeviceID(const FString& DeviceName); 55 | 56 | bool SetCurrPlaybackDevice(const FString& DeviceID); 57 | 58 | FString GetPlaybackDeviceID(const FString& DeviceName); 59 | 60 | bool GetRecordingDevice(uint32 Index, FString& DeviceName, FString& DeviceID); 61 | 62 | bool GetPlaybackDevice(uint32 Index, FString& DeviceName, FString& DeviceID); 63 | 64 | bool SetPlaybackDeviceVolume(int Volume); 65 | 66 | int GetPlaybackDeviceVolume() const; 67 | 68 | bool SetRecordingDeviceVolume(int Volume); 69 | 70 | int GetRecordingDeviceVolume() const; 71 | 72 | bool TestRecordingDevice(); 73 | 74 | bool StopRecordingDeviceTest(); 75 | 76 | bool TestPlaybackDevice(const FString& AudioFile); 77 | 78 | bool StopPlaybackDeviceTest(); 79 | 80 | private: 81 | TSharedPtr RtcEnginePtr; 82 | 83 | TUniquePtr DeviceManagerPtr; 84 | TUniquePtr RecordingDevicesPtr; 85 | TUniquePtr PlaybackDevicesPtr; 86 | }; 87 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/VideoCallPlayerController.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/PlayerController.h" 7 | #include "Templates/UniquePtr.h" 8 | 9 | #include "VideoCall.h" 10 | 11 | #include "VideoCallPlayerController.generated.h" 12 | 13 | class UEnterChannelWidget; 14 | class UDeviceTestWidget; 15 | class UVideoCallWidget; 16 | class UVideoSettingsWidget; 17 | 18 | /** 19 | * 20 | */ 21 | UCLASS() 22 | class AGORAVIDEOCALL_API AVideoCallPlayerController : public APlayerController 23 | { 24 | GENERATED_BODY() 25 | 26 | public: 27 | 28 | // Reference UMG Asset in the Editor 29 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Widgets") 30 | TSubclassOf wEnterChannelWidget; 31 | 32 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Widgets") 33 | TSubclassOf wDeviceTestWidget; 34 | 35 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Widgets") 36 | TSubclassOf wVideoCallWidget; 37 | 38 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Widgets") 39 | TSubclassOf wVideoSettingsWidget; 40 | 41 | // Variable to hold the widget After Creating it. 42 | UEnterChannelWidget* EnterChannelWidget = nullptr; 43 | 44 | UDeviceTestWidget* DeviceTestWidget = nullptr; 45 | 46 | UVideoCallWidget* VideoCallWidget = nullptr; 47 | 48 | UVideoSettingsWidget* VideoSettingsWidget = nullptr; 49 | 50 | TUniquePtr VideoCallPtr; 51 | 52 | // Override BeginPlay() 53 | void BeginPlay() override; 54 | 55 | void EndPlay(const EEndPlayReason::Type EndPlayReason) override; 56 | 57 | void StartCall( 58 | TUniquePtr PassedVideoCallPtr, 59 | const FString& ChannelName, 60 | const FString& EncryptionKey, 61 | const FString& EncryptionType 62 | ); 63 | 64 | void EndCall(TUniquePtr PassedVideoCallPtr); 65 | 66 | void SwitchOnEnterChannelWidget(TUniquePtr PassedVideoCallPtr); 67 | 68 | void SwitchOnTestWidget(TUniquePtr PassedVideoCallPtr); 69 | 70 | void SwitchOnVideoCallWidget(TUniquePtr PassedVideoCallPtr); 71 | 72 | void SwitchOnVideoSettingsWidget(TUniquePtr PassedVideoCallPtr); 73 | }; 74 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/VideoSettingsWidget.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Blueprint/UserWidget.h" 7 | #include "Components/ComboBoxString.h" 8 | #include "Components/Button.h" 9 | #include "Components/TextBlock.h" 10 | 11 | 12 | #include 13 | 14 | #include "VideoCall.h" 15 | 16 | #include "VideoSettingsWidget.generated.h" 17 | 18 | class AVideoCallPlayerController; 19 | /** 20 | * 21 | */ 22 | UCLASS() 23 | class AGORAVIDEOCALL_API UVideoSettingsWidget : public UUserWidget 24 | { 25 | GENERATED_BODY() 26 | 27 | public: 28 | UVideoSettingsWidget(const FObjectInitializer& ObjectInitializer); 29 | 30 | void NativeConstruct() override; 31 | 32 | void NativeDestruct() override; 33 | 34 | public: 35 | 36 | UFUNCTION(BlueprintCallable) 37 | void OnCancel(); 38 | 39 | UFUNCTION(BlueprintCallable) 40 | void OnConfirm(); 41 | 42 | void SetVideoCallPlayerController(AVideoCallPlayerController* VideoCallPlayerController); 43 | 44 | void SetVideoCall(TUniquePtr PassedVideoCallPtr); 45 | 46 | void GetCurrentResolution( 47 | int& Width, 48 | int& Height, 49 | agora::rtc::FRAME_RATE& FrameRate) const; 50 | 51 | FString GetCurrentResolution() const; 52 | public: 53 | AVideoCallPlayerController* PlayerController = nullptr; 54 | 55 | TUniquePtr VideoCallPtr; 56 | 57 | struct Entry 58 | { 59 | Entry( 60 | int PassedWidth, 61 | int PassedHeight, 62 | agora::rtc::FRAME_RATE PassedFrameRate) 63 | : Width(PassedWidth) 64 | , Height(PassedHeight) 65 | , FrameRate(PassedFrameRate) 66 | { 67 | TextOption = FString::FromInt(Width) + "x" 68 | + FString::FromInt(Height) + " " + FString::FromInt(FrameRate) + "fps"; 69 | } 70 | int Width; 71 | int Height; 72 | agora::rtc::FRAME_RATE FrameRate; 73 | FString TextOption; 74 | }; 75 | std::map Options; 76 | 77 | int32 SelectedIndex = 0; 78 | public: 79 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 80 | UButton* CancelButton = nullptr; 81 | 82 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 83 | UButton* ConfirmButton = nullptr; 84 | 85 | UPROPERTY(BlueprintReadOnly, meta = ( BindWidget )) 86 | UTextBlock* ResolutionTextBlock = nullptr; 87 | 88 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 89 | UComboBoxString* ResolutionComboBox = nullptr; 90 | }; 91 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/VideoCallViewWidget.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #include "VideoCallViewWidget.h" 4 | 5 | #include "Components/CanvasPanelSlot.h" 6 | 7 | UVideoCallViewWidget::UVideoCallViewWidget(const FObjectInitializer& ObjectInitializer) 8 | : Super(ObjectInitializer) 9 | { 10 | } 11 | 12 | void UVideoCallViewWidget::NativeConstruct() 13 | { 14 | Super::NativeConstruct(); 15 | 16 | } 17 | 18 | void UVideoCallViewWidget::NativeDestruct() 19 | { 20 | Super::NativeDestruct(); 21 | 22 | } 23 | 24 | void UVideoCallViewWidget::NativeTick(const FGeometry& MyGeometry, float DeltaTime) 25 | { 26 | Super::NativeTick(MyGeometry, DeltaTime); 27 | 28 | auto ScreenSize = MyGeometry.GetLocalSize(); 29 | 30 | if (MainVideoHeight != 0) 31 | { 32 | float AspectRatio = 0; 33 | AspectRatio = MainVideoWidth / (float)MainVideoHeight; 34 | 35 | auto MainVideoGeometry = MainVideoViewWidget->GetCachedGeometry(); 36 | auto MainVideoScreenSize = MainVideoGeometry.GetLocalSize(); 37 | if (MainVideoScreenSize.X == 0) 38 | { 39 | return; 40 | } 41 | 42 | auto NewMainVideoHeight = MainVideoScreenSize.Y; 43 | auto NewMainVideoWidth = AspectRatio * NewMainVideoHeight; 44 | 45 | MainVideoSizeBox->SetMinDesiredWidth(NewMainVideoWidth); 46 | MainVideoSizeBox->SetMinDesiredHeight(NewMainVideoHeight); 47 | 48 | UCanvasPanelSlot* CanvasSlot = Cast(MainVideoSizeBox->Slot); 49 | CanvasSlot->SetAutoSize(true); 50 | 51 | FVector2D NewPosition; 52 | NewPosition.X = -NewMainVideoWidth / 2; 53 | NewPosition.Y = -NewMainVideoHeight / 2; 54 | CanvasSlot->SetPosition(NewPosition); 55 | } 56 | } 57 | 58 | void UVideoCallViewWidget::UpdateMainVideoBuffer( 59 | uint8* RGBBuffer, 60 | uint32_t Width, 61 | uint32_t Height, 62 | uint32_t Size) 63 | { 64 | if (!MainVideoViewWidget) 65 | { 66 | return; 67 | } 68 | MainVideoWidth = Width; 69 | MainVideoHeight = Height; 70 | MainVideoViewWidget->UpdateBuffer(RGBBuffer, Width, Height, Size); 71 | } 72 | 73 | void UVideoCallViewWidget::UpdateAdditionalVideoBuffer( 74 | uint8* RGBBuffer, 75 | uint32_t Width, 76 | uint32_t Height, 77 | uint32_t Size) 78 | { 79 | if (!AdditionalVideoViewWidget) 80 | { 81 | return; 82 | } 83 | AdditionalVideoViewWidget->UpdateBuffer(RGBBuffer, Width, Height, Size); 84 | } 85 | 86 | void UVideoCallViewWidget::ResetBuffers() 87 | { 88 | if (!MainVideoViewWidget || !AdditionalVideoViewWidget) 89 | { 90 | return; 91 | } 92 | MainVideoViewWidget->ResetBuffer(); 93 | AdditionalVideoViewWidget->ResetBuffer(); 94 | } -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/VideoCallWidget.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Blueprint/UserWidget.h" 7 | 8 | #include "Templates/UniquePtr.h" 9 | #include "Components/Image.h" 10 | #include "Components/Button.h" 11 | #include "Engine/Texture2D.h" 12 | 13 | #include "VideoCall.h" 14 | 15 | #include "VideoCallViewWidget.h" 16 | 17 | #include "VideoCallWidget.generated.h" 18 | 19 | class AVideoCallPlayerController; 20 | class UVideoViewWidget; 21 | /** 22 | * 23 | */ 24 | UCLASS() 25 | class AGORAVIDEOCALL_API UVideoCallWidget : public UUserWidget 26 | { 27 | GENERATED_BODY() 28 | 29 | public: 30 | UVideoCallWidget(const FObjectInitializer& ObjectInitializer); 31 | 32 | void NativeConstruct() override; 33 | 34 | void NativeDestruct() override; 35 | 36 | public: 37 | void NativeTick(const FGeometry& MyGeometry, float DeltaTime) override; 38 | 39 | void OnStartCall( 40 | const FString& ChannelName, 41 | const FString& EncryptionKey, 42 | const FString& EncryptionType); 43 | 44 | UFUNCTION(BlueprintCallable) 45 | void OnEndCall(); 46 | 47 | UFUNCTION(BlueprintCallable) 48 | void OnMuteLocalAudio(); 49 | 50 | UFUNCTION(BlueprintCallable) 51 | void OnChangeVideoMode(); 52 | 53 | void SetVideoCallPlayerController(AVideoCallPlayerController* VideoCallPlayerController); 54 | 55 | void SetVideoCall(TUniquePtr PassedVideoCallPtr); 56 | public: 57 | AVideoCallPlayerController* PlayerController = nullptr; 58 | 59 | public: 60 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 61 | UVideoCallViewWidget* VideoCallViewWidget = nullptr; 62 | 63 | //Buttons 64 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 65 | UButton* EndCallButton = nullptr; 66 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 67 | UButton* MuteLocalAudioButton = nullptr; 68 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 69 | UButton* VideoModeButton = nullptr; 70 | 71 | //Button textures 72 | int32 ButtonSizeX = 96; 73 | int32 ButtonSizeY = 96; 74 | UTexture2D* EndCallButtonTexture = nullptr; 75 | UTexture2D* AudioButtonMuteTexture = nullptr; 76 | UTexture2D* AudioButtonUnmuteTexture = nullptr; 77 | UTexture2D* VideomodeButtonCameraoffTexture = nullptr; 78 | UTexture2D* VideomodeButtonCameraonTexture = nullptr; 79 | 80 | TUniquePtr VideoCallPtr; 81 | private: 82 | void InitButtons(); 83 | 84 | void SetVideoModeButtonToCameraOff(); 85 | void SetVideoModeButtonToCameraOn(); 86 | 87 | void SetAudioButtonToMute(); 88 | void SetAudioButtonToUnMute(); 89 | }; 90 | -------------------------------------------------------------------------------- /Config/DefaultGame.ini: -------------------------------------------------------------------------------- 1 | [/Script/EngineSettings.GeneralProjectSettings] 2 | ProjectID=B5CB165B4CE80A3073F94EB2DED30153 3 | bAllowWindowResize=False 4 | CopyrightNotice=Copyright (c) 2019 Agora.io. All rights reserved. 5 | CompanyName= 6 | CompanyDistinguishedName= 7 | 8 | [/Script/UnrealEd.ProjectPackagingSettings] 9 | Build=IfProjectHasCode 10 | BuildConfiguration=PPBC_Development 11 | StagingDirectory=(Path="D:/devel/Unreal_Projects/AgoraVideoCall/Packaged") 12 | FullRebuild=False 13 | ForDistribution=False 14 | IncludeDebugFiles=False 15 | BlueprintNativizationMethod=Disabled 16 | bIncludeNativizedAssetsInProjectGeneration=False 17 | bExcludeMonolithicEngineHeadersInNativizedCode=False 18 | UsePakFile=False 19 | bGenerateChunks=False 20 | bGenerateNoChunks=False 21 | bChunkHardReferencesOnly=False 22 | bForceOneChunkPerFile=False 23 | MaxChunkSize=0 24 | bBuildHttpChunkInstallData=False 25 | HttpChunkInstallDataDirectory=(Path="") 26 | PakFileCompressionFormats= 27 | PakFileAdditionalCompressionOptions= 28 | HttpChunkInstallDataVersion= 29 | IncludePrerequisites=True 30 | IncludeAppLocalPrerequisites=False 31 | bShareMaterialShaderCode=False 32 | bSharedMaterialNativeLibraries=False 33 | ApplocalPrerequisitesDirectory=(Path="") 34 | IncludeCrashReporter=True 35 | InternationalizationPreset=English 36 | +CulturesToStage=en 37 | bCookAll=False 38 | bCookMapsOnly=False 39 | bCompressed=False 40 | bEncryptIniFiles=False 41 | bEncryptPakIndex=False 42 | GenerateEarlyDownloaderPakFile=False 43 | bSkipEditorContent=False 44 | bSkipMovies=False 45 | +EarlyDownloaderPakFileFiles=...\Content\Internationalization\...\*.icu 46 | +EarlyDownloaderPakFileFiles=...\Content\Internationalization\...\*.brk 47 | +EarlyDownloaderPakFileFiles=...\Content\Internationalization\...\*.res 48 | +EarlyDownloaderPakFileFiles=...\Content\Internationalization\...\*.nrm 49 | +EarlyDownloaderPakFileFiles=...\Content\Internationalization\...\*.cfu 50 | +EarlyDownloaderPakFileFiles=...\Content\Localization\...\*.* 51 | +EarlyDownloaderPakFileFiles=...\Content\Localization\*.* 52 | +EarlyDownloaderPakFileFiles=...\Content\Certificates\...\*.* 53 | +EarlyDownloaderPakFileFiles=...\Content\Certificates\*.* 54 | +EarlyDownloaderPakFileFiles=-...\Content\Localization\Game\...\*.* 55 | +EarlyDownloaderPakFileFiles=-...\Content\Localization\Game\*.* 56 | +EarlyDownloaderPakFileFiles=...\Config\...\*.ini 57 | +EarlyDownloaderPakFileFiles=...\Config\*.ini 58 | +EarlyDownloaderPakFileFiles=...\Engine\GlobalShaderCache*.bin 59 | +EarlyDownloaderPakFileFiles=...\Content\ShaderArchive-Global*.ushaderbytecode 60 | +EarlyDownloaderPakFileFiles=...\Content\Slate\*.* 61 | +EarlyDownloaderPakFileFiles=...\Content\Slate\...\*.* 62 | +EarlyDownloaderPakFileFiles=...\*.upluginmanifest 63 | +EarlyDownloaderPakFileFiles=...\*.uproject 64 | +EarlyDownloaderPakFileFiles=...\global_sf*.metalmap 65 | bNativizeBlueprintAssets=False 66 | bNativizeOnlySelectedBlueprints=False 67 | 68 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/EnterChannelWidget.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Blueprint/UserWidget.h" 7 | #include "Components/TextBlock.h" 8 | #include "Components/RichTextBlock.h" 9 | #include "Components/EditableTextBox.h" 10 | #include "Components/ComboBoxString.h" 11 | #include "Components/Button.h" 12 | #include "Components/Image.h" 13 | 14 | #include "VideoCall.h" 15 | 16 | #include "EnterChannelWidget.generated.h" 17 | 18 | class AVideoCallPlayerController; 19 | /** 20 | * 21 | */ 22 | UCLASS() 23 | class AGORAVIDEOCALL_API UEnterChannelWidget : public UUserWidget 24 | { 25 | GENERATED_BODY() 26 | 27 | public: 28 | UEnterChannelWidget(const FObjectInitializer& objectInitializer); 29 | 30 | void NativeConstruct() override; 31 | 32 | void NativeDestruct() override; 33 | 34 | public: 35 | UFUNCTION(BlueprintCallable) 36 | void OnJoin(); 37 | 38 | UFUNCTION(BlueprintCallable) 39 | void OnTest(); 40 | 41 | UFUNCTION(BlueprintCallable) 42 | void OnVideoSettings(); 43 | 44 | //TODO: move to new base class? 45 | void SetVideoCallPlayerController(AVideoCallPlayerController* VideoCallPlayerController); 46 | 47 | void SetVideoCall(TUniquePtr PassedVideoCallPtr); 48 | 49 | void UpdateVideoSettingsButtonText(FString newValue); 50 | 51 | void UpdateVersionText(FString newValue); 52 | public: 53 | AVideoCallPlayerController* PlayerController = nullptr; 54 | 55 | TUniquePtr VideoCallPtr; 56 | public: 57 | 58 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 59 | UTextBlock* HeaderTextBlock = nullptr; 60 | 61 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 62 | UTextBlock* DescriptionTextBlock = nullptr; 63 | 64 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 65 | UEditableTextBox* ChannelNameTextBox = nullptr; 66 | 67 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 68 | UEditableTextBox* EncriptionKeyTextBox = nullptr; 69 | 70 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 71 | UTextBlock* EncriptionTypeTextBlock = nullptr; 72 | 73 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 74 | UComboBoxString* EncriptionTypeComboBox = nullptr; 75 | 76 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 77 | UButton* JoinButton = nullptr; 78 | 79 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 80 | UButton* TestButton = nullptr; 81 | 82 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 83 | UButton* VideoSettingsButton = nullptr; 84 | 85 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 86 | UTextBlock* ContactsTextBlock = nullptr; 87 | 88 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 89 | UTextBlock* BuildInfoTextBlock = nullptr; 90 | 91 | }; 92 | -------------------------------------------------------------------------------- /Config/DefaultEngine.ini: -------------------------------------------------------------------------------- 1 | [URL] 2 | [/Script/Engine.RendererSettings] 3 | r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True 4 | 5 | [/Script/HardwareTargeting.HardwareTargetingSettings] 6 | TargetedHardwareClass=Desktop 7 | AppliedTargetedHardwareClass=Desktop 8 | DefaultGraphicsPerformance=Maximum 9 | AppliedDefaultGraphicsPerformance=Maximum 10 | 11 | [/Script/EngineSettings.GameMapsSettings] 12 | GameDefaultMap=/Game/Levels/VideoCallLevel.VideoCallLevel 13 | EditorStartupMap=/Game/Levels/VideoCallLevel.VideoCallLevel 14 | GlobalDefaultGameMode=/Game/Widgets/BP_AgoraVideoCallGameModeBase.BP_AgoraVideoCallGameModeBase_C 15 | GameInstanceClass=/Script/Engine.GameInstance 16 | 17 | [/Script/UnrealEd.CookerSettings] 18 | bCookOnTheFlyForLaunchOn=False 19 | 20 | [/Script/Engine.PhysicsSettings] 21 | DefaultGravityZ=-980.000000 22 | DefaultTerminalVelocity=4000.000000 23 | DefaultFluidFriction=0.300000 24 | SimulateScratchMemorySize=262144 25 | RagdollAggregateThreshold=4 26 | TriangleMeshTriangleMinAreaThreshold=5.000000 27 | bEnableShapeSharing=False 28 | bEnablePCM=True 29 | bEnableStabilization=False 30 | bWarnMissingLocks=True 31 | bEnable2DPhysics=False 32 | PhysicErrorCorrection=(PingExtrapolation=0.100000,PingLimit=100.000000,ErrorPerLinearDifference=1.000000,ErrorPerAngularDifference=1.000000,MaxRestoredStateError=1.000000,MaxLinearHardSnapDistance=400.000000,PositionLerp=0.000000,AngleLerp=0.400000,LinearVelocityCoefficient=100.000000,AngularVelocityCoefficient=10.000000,ErrorAccumulationSeconds=0.500000,ErrorAccumulationDistanceSq=15.000000,ErrorAccumulationSimilarity=100.000000) 33 | LockedAxis=Invalid 34 | DefaultDegreesOfFreedom=Full3D 35 | BounceThresholdVelocity=200.000000 36 | FrictionCombineMode=Average 37 | RestitutionCombineMode=Average 38 | MaxAngularVelocity=3600.000000 39 | MaxDepenetrationVelocity=0.000000 40 | ContactOffsetMultiplier=0.020000 41 | MinContactOffset=2.000000 42 | MaxContactOffset=8.000000 43 | bSimulateSkeletalMeshOnDedicatedServer=True 44 | DefaultShapeComplexity=CTF_UseSimpleAndComplex 45 | bDefaultHasComplexCollision=True 46 | bSuppressFaceRemapTable=False 47 | bSupportUVFromHitResults=False 48 | bDisableActiveActors=False 49 | bDisableKinematicStaticPairs=False 50 | bDisableKinematicKinematicPairs=False 51 | bDisableCCD=False 52 | bEnableEnhancedDeterminism=False 53 | AnimPhysicsMinDeltaTime=0.000000 54 | bSimulateAnimPhysicsAfterReset=False 55 | MaxPhysicsDeltaTime=0.033333 56 | bSubstepping=False 57 | bSubsteppingAsync=False 58 | MaxSubstepDeltaTime=0.016667 59 | MaxSubsteps=6 60 | SyncSceneSmoothingFactor=0.000000 61 | InitialAverageFrameRate=0.016667 62 | PhysXTreeRebuildRate=10 63 | DefaultBroadphaseSettings=(bUseMBPOnClient=False,bUseMBPOnServer=False,bUseMBPOuterBounds=False,MBPBounds=(Min=(X=0.000000,Y=0.000000,Z=0.000000),Max=(X=0.000000,Y=0.000000,Z=0.000000),IsValid=0),MBPOuterBounds=(Min=(X=0.000000,Y=0.000000,Z=0.000000),Max=(X=0.000000,Y=0.000000,Z=0.000000),IsValid=0),MBPNumSubdivs=2) 64 | ChaosSettings=(DefaultThreadingModel=DedicatedThread,DedicatedThreadTickMode=VariableCappedWithTarget,DedicatedThreadBufferMode=Double) 65 | 66 | [/Script/IOSRuntimeSettings.IOSRuntimeSettings] 67 | BundleIdentifier=com.nixsolutions.Testtest 68 | AdditionalPlistData=NSCameraUsageDescriptionAgoraVideoCall NSMicrophoneUsageDescriptionAgoraVideoCall 69 | 70 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/VideoViewWidget.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #include "VideoViewWidget.h" 4 | 5 | #include "EngineUtils.h" 6 | #include "Engine/Texture2D.h" 7 | 8 | #include 9 | 10 | UVideoViewWidget::UVideoViewWidget(const FObjectInitializer& ObjectInitializer) 11 | : Super(ObjectInitializer) 12 | { 13 | static ConstructorHelpers::FObjectFinder DefaultTexture(TEXT("Texture2D'/Game/Textures/cameraoff_mainVideo.cameraoff_mainVideo'")); 14 | if (DefaultTexture.Succeeded()) 15 | { 16 | CameraoffTexture = DefaultTexture.Object; 17 | } 18 | } 19 | 20 | void UVideoViewWidget::NativeConstruct() 21 | { 22 | Super::NativeConstruct(); 23 | 24 | //TODO: 25 | Width = 640; 26 | Height = 360; 27 | 28 | RenderTargetTexture = UTexture2D::CreateTransient(Width, Height, PF_R8G8B8A8 ); 29 | RenderTargetTexture->UpdateResource(); 30 | 31 | BufferSize = Width * Height * 4; 32 | Buffer = new uint8[BufferSize]; 33 | for (uint32 i = 0; i < Width * Height; ++i) 34 | { 35 | Buffer[i * 4 + 0] = 0x32; 36 | Buffer[i * 4 + 1] = 0x32; 37 | Buffer[i * 4 + 2] = 0x32; 38 | Buffer[i * 4 + 3] = 0xFF; 39 | } 40 | UpdateTextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, Width, Height); 41 | RenderTargetTexture->UpdateTextureRegions(0, 1, UpdateTextureRegion, Width * 4, (uint32)4, Buffer); 42 | 43 | Brush.SetResourceObject(RenderTargetTexture); 44 | RenderTargetImage->SetBrush(Brush); 45 | } 46 | 47 | void UVideoViewWidget::NativeDestruct() 48 | { 49 | Super::NativeDestruct(); 50 | 51 | delete[] Buffer; 52 | delete UpdateTextureRegion; 53 | } 54 | 55 | void UVideoViewWidget::NativeTick(const FGeometry& MyGeometry, float DeltaTime) 56 | { 57 | Super::NativeTick(MyGeometry, DeltaTime); 58 | 59 | FScopeLock lock(&Mutex); 60 | 61 | if (UpdateTextureRegion->Width != Width || 62 | UpdateTextureRegion->Height != Height) 63 | { 64 | auto NewUpdateTextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, Width, Height); 65 | 66 | auto NewRenderTargetTexture = UTexture2D::CreateTransient(Width, Height, PF_R8G8B8A8 ); 67 | NewRenderTargetTexture->UpdateResource(); 68 | NewRenderTargetTexture->UpdateTextureRegions(0, 1, NewUpdateTextureRegion, Width * 4, (uint32)4, Buffer); 69 | 70 | Brush.SetResourceObject(NewRenderTargetTexture); 71 | RenderTargetImage->SetBrush(Brush); 72 | 73 | //UClass's such as UTexture2D are automatically garbage collected when there is no hard pointer references made to that object. 74 | //So if you just leave it and don't reference it elsewhere then it will be destroyed automatically. 75 | 76 | FUpdateTextureRegion2D* TmpUpdateTextureRegion = UpdateTextureRegion; 77 | 78 | RenderTargetTexture = NewRenderTargetTexture; 79 | UpdateTextureRegion = NewUpdateTextureRegion; 80 | 81 | delete TmpUpdateTextureRegion; 82 | return; 83 | } 84 | 85 | RenderTargetTexture->UpdateTextureRegions(0, 1, UpdateTextureRegion, Width * 4, (uint32)4, Buffer); 86 | } 87 | 88 | void UVideoViewWidget::UpdateBuffer( 89 | uint8* RGBBuffer, 90 | uint32_t NewWidth, 91 | uint32_t NewHeight, 92 | uint32_t NewSize) 93 | { 94 | FScopeLock lock(&Mutex); 95 | 96 | if (!RGBBuffer) 97 | { 98 | return; 99 | } 100 | 101 | if (BufferSize == NewSize) 102 | { 103 | std::copy(RGBBuffer, RGBBuffer + NewSize, Buffer); 104 | } 105 | else 106 | { 107 | delete[] Buffer; 108 | BufferSize = NewSize; 109 | Width = NewWidth; 110 | Height = NewHeight; 111 | Buffer = new uint8[BufferSize]; 112 | std::copy(RGBBuffer, RGBBuffer + NewSize, Buffer); 113 | } 114 | } 115 | 116 | void UVideoViewWidget::ResetBuffer() 117 | { 118 | for (uint32 i = 0; i < Width * Height; ++i) 119 | { 120 | Buffer[i * 4 + 0] = 0x32; 121 | Buffer[i * 4 + 1] = 0x32; 122 | Buffer[i * 4 + 2] = 0x32; 123 | Buffer[i * 4 + 3] = 0xFF; 124 | } 125 | } -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/VideoSettingsWidget.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #include "VideoSettingsWidget.h" 4 | 5 | #include "Blueprint/WidgetTree.h" 6 | #include "Components/TextBlock.h" 7 | 8 | #include "VideoCallPlayerController.h" 9 | 10 | UVideoSettingsWidget::UVideoSettingsWidget(const FObjectInitializer& objectInitializer) 11 | : Super(objectInitializer) 12 | { 13 | } 14 | 15 | void UVideoSettingsWidget::NativeConstruct() 16 | { 17 | Super::NativeConstruct(); 18 | 19 | // buttons 20 | if (CancelButton) 21 | { 22 | UTextBlock* CancelButtonTextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); 23 | CancelButtonTextBlock->SetText(FText::FromString("Cancel")); 24 | CancelButton->AddChild(CancelButtonTextBlock); 25 | CancelButton->OnClicked.AddDynamic(this, &UVideoSettingsWidget::OnCancel); 26 | } 27 | 28 | if (ConfirmButton) 29 | { 30 | UTextBlock* ConfirmButtonTextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); 31 | ConfirmButtonTextBlock->SetText(FText::FromString("Confirm")); 32 | ConfirmButton->AddChild(ConfirmButtonTextBlock); 33 | ConfirmButton->OnClicked.AddDynamic(this, &UVideoSettingsWidget::OnConfirm); 34 | } 35 | 36 | if (ResolutionComboBox) 37 | { 38 | FScriptDelegate Delegate; 39 | Delegate.BindUFunction(this, FName("OnResolutionComboBoxSelectionChanged")); 40 | ResolutionComboBox->OnSelectionChanged.Add(Delegate); 41 | } 42 | 43 | if (ResolutionTextBlock) 44 | { 45 | ResolutionTextBlock->SetText(FText::FromString("Resolution")); 46 | } 47 | 48 | //fill ResolutionComboBox 49 | Options.emplace(0, Entry(640, 360, agora::rtc::FRAME_RATE_FPS_15)); 50 | ResolutionComboBox->AddOption(Options.at(0).TextOption); 51 | 52 | SelectedIndex = 0; 53 | ResolutionComboBox->SetSelectedIndex(SelectedIndex); 54 | 55 | Options.emplace(1, Entry(640, 480, agora::rtc::FRAME_RATE_FPS_15)); 56 | ResolutionComboBox->AddOption(Options.at(1).TextOption); 57 | 58 | Options.emplace(2, Entry(1280, 720, agora::rtc::FRAME_RATE_FPS_30)); 59 | ResolutionComboBox->AddOption(Options.at(2).TextOption); 60 | } 61 | 62 | void UVideoSettingsWidget::NativeDestruct() 63 | { 64 | Super::NativeDestruct(); 65 | } 66 | 67 | void UVideoSettingsWidget::OnCancel() 68 | { 69 | ResolutionComboBox->SetSelectedIndex(SelectedIndex); 70 | if (PlayerController) 71 | { 72 | SetVisibility(ESlateVisibility::Collapsed); 73 | PlayerController->SwitchOnEnterChannelWidget(std::move(VideoCallPtr)); 74 | } 75 | } 76 | 77 | void UVideoSettingsWidget::OnConfirm() 78 | { 79 | auto Index = ResolutionComboBox->GetSelectedIndex(); 80 | auto Found = Options.find(Index); 81 | if (Found == Options.end()) 82 | { 83 | UE_LOG(LogTemp, Warning, TEXT("Couldn't find selected resolution option.")); 84 | return; 85 | } 86 | if (!VideoCallPtr) 87 | { 88 | return; 89 | } 90 | VideoCallPtr->SetResolution(Found->second.Width, Found->second.Height, Found->second.FrameRate); 91 | SelectedIndex = Index; 92 | 93 | if (PlayerController) 94 | { 95 | SetVisibility(ESlateVisibility::Collapsed); 96 | PlayerController->SwitchOnEnterChannelWidget(std::move(VideoCallPtr)); 97 | } 98 | } 99 | 100 | void UVideoSettingsWidget::SetVideoCallPlayerController(AVideoCallPlayerController* VideoCallPlayerController) 101 | { 102 | PlayerController = VideoCallPlayerController; 103 | } 104 | 105 | void UVideoSettingsWidget::SetVideoCall(TUniquePtr PassedVideoCallPtr) 106 | { 107 | VideoCallPtr = std::move(PassedVideoCallPtr); 108 | } 109 | 110 | void UVideoSettingsWidget::GetCurrentResolution( 111 | int& Width, 112 | int& Height, 113 | agora::rtc::FRAME_RATE& FrameRate) const 114 | { 115 | auto Index = ResolutionComboBox->GetSelectedIndex(); 116 | auto Found = Options.find(Index); 117 | if (Found == Options.end()) 118 | { 119 | UE_LOG(LogTemp, Warning, TEXT("Couldn't find selected resolution option.")); 120 | return; 121 | } 122 | Width = Found->second.Width; 123 | Height = Found->second.Height; 124 | FrameRate = Found->second.FrameRate; 125 | } 126 | 127 | FString UVideoSettingsWidget::GetCurrentResolution() const 128 | { 129 | auto Index = ResolutionComboBox->GetSelectedIndex(); 130 | auto Found = Options.find(Index); 131 | if (Found == Options.end()) 132 | { 133 | UE_LOG(LogTemp, Warning, TEXT("Couldn't find selected resolution option.")); 134 | return ""; 135 | } 136 | return Found->second.TextOption; 137 | } -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/EnterChannelWidget.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #include "EnterChannelWidget.h" 4 | 5 | #include "Blueprint/WidgetTree.h" 6 | #include "Kismet/GameplayStatics.h" 7 | 8 | #include "VideoCallPlayerController.h" 9 | #include "DeviceTestWidget.h" 10 | 11 | UEnterChannelWidget::UEnterChannelWidget(const FObjectInitializer& objectInitializer) 12 | : Super(objectInitializer) 13 | { 14 | } 15 | 16 | void UEnterChannelWidget::NativeConstruct() 17 | { 18 | Super::NativeConstruct(); 19 | 20 | if (HeaderTextBlock) 21 | HeaderTextBlock->SetText(FText::FromString("Enter a conference room name")); 22 | 23 | if (DescriptionTextBlock) 24 | DescriptionTextBlock->SetText(FText::FromString("If you are the first person to specify this name, \ 25 | the room will be created and you will\nbe placed in it. \ 26 | If it has already been created you will join the conference in progress")); 27 | 28 | if (ChannelNameTextBox) 29 | ChannelNameTextBox->SetHintText(FText::FromString("Channel Name")); 30 | 31 | if (EncriptionKeyTextBox) 32 | EncriptionKeyTextBox->SetHintText(FText::FromString("Encription Key")); 33 | 34 | if (EncriptionTypeTextBlock) 35 | EncriptionTypeTextBlock->SetText(FText::FromString("Enc Type:")); 36 | 37 | if (EncriptionTypeComboBox) 38 | { 39 | EncriptionTypeComboBox->AddOption("aes-128"); 40 | EncriptionTypeComboBox->AddOption("aes-256"); 41 | EncriptionTypeComboBox->SetSelectedIndex(0); 42 | } 43 | 44 | if (JoinButton) 45 | { 46 | UTextBlock* JoinTextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); 47 | JoinTextBlock->SetText(FText::FromString("Join")); 48 | JoinButton->AddChild(JoinTextBlock); 49 | JoinButton->OnClicked.AddDynamic(this, &UEnterChannelWidget::OnJoin); 50 | } 51 | 52 | if (TestButton) 53 | { 54 | UTextBlock* TestTextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); 55 | TestTextBlock->SetText(FText::FromString("Test")); 56 | TestButton->AddChild(TestTextBlock); 57 | TestButton->OnClicked.AddDynamic(this, &UEnterChannelWidget::OnTest); 58 | 59 | if( UGameplayStatics::GetPlatformName() == TEXT( "IOS" ) ) 60 | { 61 | TestButton->SetVisibility( ESlateVisibility::Hidden ); 62 | } 63 | } 64 | 65 | if (VideoSettingsButton) 66 | { 67 | UTextBlock* TextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); 68 | TextBlock->SetText(FText::FromString("Resolution")); 69 | VideoSettingsButton->AddChild(TextBlock); 70 | VideoSettingsButton->OnClicked.AddDynamic(this, &UEnterChannelWidget::OnVideoSettings); 71 | } 72 | 73 | if (ContactsTextBlock) 74 | ContactsTextBlock->SetText(FText::FromString("agora.io Contact support: 400 632 6626")); 75 | 76 | if (BuildInfoTextBlock) 77 | BuildInfoTextBlock->SetText(FText::FromString(" ")); 78 | } 79 | 80 | void UEnterChannelWidget::NativeDestruct() 81 | { 82 | Super::NativeDestruct(); 83 | 84 | 85 | } 86 | 87 | void UEnterChannelWidget::OnJoin() 88 | { 89 | if (!PlayerController || !VideoCallPtr) 90 | { 91 | return; 92 | } 93 | 94 | FString ChannelName = ChannelNameTextBox->GetText().ToString(); 95 | 96 | FString EncryptionKey = EncriptionKeyTextBox->GetText().ToString(); 97 | FString EncryptionType = EncriptionTypeComboBox->GetSelectedOption(); 98 | 99 | SetVisibility(ESlateVisibility::Collapsed); 100 | 101 | PlayerController->StartCall( 102 | std::move(VideoCallPtr), 103 | ChannelName, 104 | EncryptionKey, 105 | EncryptionType); 106 | } 107 | 108 | void UEnterChannelWidget::OnTest() 109 | { 110 | if (PlayerController) 111 | { 112 | SetVisibility(ESlateVisibility::Collapsed); 113 | PlayerController->SwitchOnTestWidget(std::move(VideoCallPtr)); 114 | } 115 | } 116 | 117 | void UEnterChannelWidget::OnVideoSettings() 118 | { 119 | if (PlayerController) 120 | { 121 | SetVisibility(ESlateVisibility::Collapsed); 122 | PlayerController->SwitchOnVideoSettingsWidget(std::move(VideoCallPtr)); 123 | } 124 | } 125 | 126 | void UEnterChannelWidget::SetVideoCallPlayerController(AVideoCallPlayerController* VideoCallPlayerController) 127 | { 128 | PlayerController = VideoCallPlayerController; 129 | } 130 | 131 | void UEnterChannelWidget::SetVideoCall(TUniquePtr PassedVideoCallPtr) 132 | { 133 | VideoCallPtr = std::move(PassedVideoCallPtr); 134 | } 135 | 136 | void UEnterChannelWidget::UpdateVideoSettingsButtonText(FString newValue) 137 | { 138 | if (VideoSettingsButton) 139 | { 140 | auto Child = VideoSettingsButton->GetChildAt(0); 141 | UTextBlock*TextBlock = Cast(Child); 142 | if (TextBlock) 143 | { 144 | TextBlock->SetText(FText::FromString(newValue)); 145 | } 146 | } 147 | } 148 | 149 | void UEnterChannelWidget::UpdateVersionText(FString newValue) 150 | { 151 | BuildInfoTextBlock->SetText(FText::FromString(newValue)); 152 | } 153 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/VideoCallPlayerController.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #include "VideoCallPlayerController.h" 4 | 5 | #include "Blueprint/UserWidget.h" 6 | 7 | #include "EnterChannelWidget.h" 8 | #include "DeviceTestWidget.h" 9 | #include "VideoCallWidget.h" 10 | #include "VideoSettingsWidget.h" 11 | 12 | void AVideoCallPlayerController::BeginPlay() 13 | { 14 | Super::BeginPlay(); 15 | 16 | //initialize wigets 17 | if (wEnterChannelWidget) // Check if the Asset is assigned in the blueprint. 18 | { 19 | // Create the widget and store it. 20 | if (!EnterChannelWidget) 21 | { 22 | EnterChannelWidget = CreateWidget(this, wEnterChannelWidget); 23 | EnterChannelWidget->SetVideoCallPlayerController(this); 24 | } 25 | // now you can use the widget directly since you have a referance for it. 26 | // Extra check to make sure the pointer holds the widget. 27 | if (EnterChannelWidget) 28 | { 29 | //let add it to the view port 30 | EnterChannelWidget->AddToViewport(); 31 | } 32 | //Show the Cursor. 33 | bShowMouseCursor = true; 34 | } 35 | if (wDeviceTestWidget) 36 | { 37 | if (!DeviceTestWidget) 38 | { 39 | DeviceTestWidget = CreateWidget(this, wDeviceTestWidget); 40 | DeviceTestWidget->SetVideoCallPlayerController(this); 41 | } 42 | if (DeviceTestWidget) 43 | { 44 | DeviceTestWidget->AddToViewport(); 45 | } 46 | DeviceTestWidget->SetVisibility(ESlateVisibility::Collapsed); 47 | } 48 | if (wVideoCallWidget) 49 | { 50 | if (!VideoCallWidget) 51 | { 52 | VideoCallWidget = CreateWidget(this, wVideoCallWidget); 53 | VideoCallWidget->SetVideoCallPlayerController(this); 54 | } 55 | if (VideoCallWidget) 56 | { 57 | VideoCallWidget->AddToViewport(); 58 | } 59 | VideoCallWidget->SetVisibility(ESlateVisibility::Collapsed); 60 | } 61 | if (wVideoSettingsWidget) 62 | { 63 | if (!VideoSettingsWidget) 64 | { 65 | VideoSettingsWidget = CreateWidget(this, wVideoSettingsWidget); 66 | VideoSettingsWidget->SetVideoCallPlayerController(this); 67 | } 68 | if (VideoSettingsWidget) 69 | { 70 | VideoSettingsWidget->AddToViewport(); 71 | } 72 | VideoSettingsWidget->SetVisibility(ESlateVisibility::Collapsed); 73 | } 74 | 75 | //create video call and switch on the EnterChannelWidget 76 | VideoCallPtr = MakeUnique(); 77 | 78 | if (VideoSettingsWidget) 79 | { 80 | int Width = 0; 81 | int Height = 0; 82 | agora::rtc::FRAME_RATE FrameRate = agora::rtc::FRAME_RATE::FRAME_RATE_FPS_1; 83 | VideoSettingsWidget->GetCurrentResolution(Width, Height, FrameRate); 84 | 85 | VideoCallPtr->SetResolution( Width, Height, FrameRate); 86 | } 87 | 88 | FString Version = VideoCallPtr->GetVersion(); 89 | Version = "Agora version: " + Version; 90 | EnterChannelWidget->UpdateVersionText(Version); 91 | 92 | SwitchOnEnterChannelWidget(std::move(VideoCallPtr)); 93 | } 94 | 95 | void AVideoCallPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason) 96 | { 97 | 98 | } 99 | 100 | void AVideoCallPlayerController::StartCall( 101 | TUniquePtr PassedVideoCallPtr, 102 | const FString& ChannelName, 103 | const FString& EncryptionKey, 104 | const FString& EncryptionType) 105 | { 106 | SwitchOnVideoCallWidget(std::move(PassedVideoCallPtr)); 107 | 108 | VideoCallWidget->OnStartCall( 109 | ChannelName, 110 | EncryptionKey, 111 | EncryptionType); 112 | } 113 | 114 | void AVideoCallPlayerController::EndCall(TUniquePtr PassedVideoCallPtr) 115 | { 116 | SwitchOnEnterChannelWidget(std::move(PassedVideoCallPtr)); 117 | } 118 | 119 | void AVideoCallPlayerController::SwitchOnEnterChannelWidget(TUniquePtr PassedVideoCallPtr) 120 | { 121 | if (!EnterChannelWidget) 122 | { 123 | return; 124 | } 125 | 126 | if (VideoSettingsWidget) 127 | { 128 | auto CurrentResolution = VideoSettingsWidget->GetCurrentResolution(); 129 | EnterChannelWidget->UpdateVideoSettingsButtonText(std::move(CurrentResolution)); 130 | } 131 | 132 | EnterChannelWidget->SetVideoCall(std::move(PassedVideoCallPtr)); 133 | EnterChannelWidget->SetVisibility(ESlateVisibility::Visible); 134 | } 135 | 136 | void AVideoCallPlayerController::SwitchOnTestWidget(TUniquePtr PassedVideoCallPtr) 137 | { 138 | if (!DeviceTestWidget) 139 | { 140 | return; 141 | } 142 | DeviceTestWidget->SetVideoCall(std::move(PassedVideoCallPtr)); 143 | DeviceTestWidget->SetVisibility(ESlateVisibility::Visible); 144 | } 145 | 146 | void AVideoCallPlayerController::SwitchOnVideoCallWidget(TUniquePtr PassedVideoCallPtr) 147 | { 148 | if (!VideoCallWidget) 149 | { 150 | return; 151 | } 152 | VideoCallWidget->SetVideoCall(std::move(PassedVideoCallPtr)); 153 | VideoCallWidget->SetVisibility(ESlateVisibility::Visible); 154 | } 155 | 156 | void AVideoCallPlayerController::SwitchOnVideoSettingsWidget(TUniquePtr PassedVideoCallPtr) 157 | { 158 | if (!VideoSettingsWidget) 159 | { 160 | return; 161 | } 162 | VideoSettingsWidget->SetVideoCall(std::move(PassedVideoCallPtr)); 163 | VideoSettingsWidget->SetVisibility(ESlateVisibility::Visible); 164 | } -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/DeviceTestWidget.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Blueprint/UserWidget.h" 7 | #include "Components/TextBlock.h" 8 | #include "Components/ComboBoxString.h" 9 | #include "Components/Button.h" 10 | #include "Components/Slider.h" 11 | #include "Components/Image.h" 12 | #include "Components/SizeBox.h" 13 | 14 | #include "VideoCall.h" 15 | 16 | #include "DeviceTestWidget.generated.h" 17 | 18 | class AVideoCallPlayerController; 19 | class UVideoViewWidget; 20 | /** 21 | * 22 | */ 23 | UCLASS() 24 | class AGORAVIDEOCALL_API UDeviceTestWidget : public UUserWidget 25 | { 26 | GENERATED_BODY() 27 | 28 | public: 29 | UDeviceTestWidget(const FObjectInitializer& objectInitializer); 30 | 31 | void NativeConstruct() override; 32 | 33 | void NativeDestruct() override; 34 | 35 | public: 36 | void NativeTick(const FGeometry& MyGeometry, float DeltaTime) override; 37 | 38 | UFUNCTION(BlueprintCallable) 39 | void OnMicrophoneVolumeChanged(); 40 | 41 | UFUNCTION(BlueprintCallable) 42 | void OnSpeakersVolumeChanged(); 43 | 44 | UFUNCTION(BlueprintCallable) 45 | void OnMicrophoneComboBoxSelectionChanged(); 46 | 47 | UFUNCTION(BlueprintCallable) 48 | void OnSpeakersComboBoxSelectionChanged(); 49 | 50 | UFUNCTION(BlueprintCallable) 51 | void OnCameraComboBoxSelectionChanged(); 52 | 53 | UFUNCTION(BlueprintCallable) 54 | void OnMicrophoneTest(); 55 | 56 | UFUNCTION(BlueprintCallable) 57 | void OnSpeakersTest(); 58 | 59 | UFUNCTION(BlueprintCallable) 60 | void OnCameraTest(); 61 | 62 | UFUNCTION(BlueprintCallable) 63 | void OnEchoTest(); 64 | 65 | UFUNCTION(BlueprintCallable) 66 | void OnCancel(); 67 | 68 | UFUNCTION(BlueprintCallable) 69 | void OnConfirm(); 70 | 71 | void OnExit(); 72 | 73 | void SetVideoCallPlayerController(AVideoCallPlayerController* VideoCallPlayerController); 74 | 75 | void SetVideoCall(TUniquePtr PassedVideoCallPtr); 76 | 77 | void StartCameraTest(); 78 | void StopCameraTest(); 79 | 80 | void StartMicrophoneTest(); 81 | void StopMicrophoneTest(); 82 | 83 | void StartSpeakersTest(); 84 | void StopSpeakersTest(); 85 | 86 | void StartEchoTest(); 87 | void StopEchoTest(); 88 | 89 | FString CreateAudioFileForSpeakersTest(); 90 | public: 91 | AVideoCallPlayerController* PlayerController = nullptr; 92 | 93 | TUniquePtr VideoCallPtr; 94 | 95 | TUniquePtr CameraManagerPtr; 96 | TUniquePtr AudioDeviceManagerPtr; 97 | 98 | float MicrophoneVolume = 0; 99 | float SpeakersVolume = 0; 100 | 101 | FString DefaultVideoDeviceName; 102 | FString DefaultRecordingDeviceName; 103 | FString DefaultPlaybackDeviceName; 104 | 105 | bool IsTestingRecording = false; 106 | bool IsTestingPlayback = false; 107 | bool IsEchoTesting = false; 108 | 109 | int32 VideoWidth = 0; 110 | int32 VideoHeight = 0; 111 | 112 | public: 113 | // buttons 114 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 115 | UButton* MicrophoneTestButton = nullptr; 116 | 117 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 118 | UButton* SpeakersTestButton = nullptr; 119 | 120 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 121 | UButton* CameraTestButton = nullptr; 122 | 123 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 124 | UButton* EchoTestButton = nullptr; 125 | 126 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 127 | UButton* CancelButton = nullptr; 128 | 129 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 130 | UButton* ConfirmButton = nullptr; 131 | 132 | // text blocks 133 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 134 | UTextBlock* MicrophoneTextBlock = nullptr; 135 | 136 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 137 | UTextBlock* SpeakersTextBlock = nullptr; 138 | 139 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 140 | UTextBlock* CameraTextBlock = nullptr; 141 | 142 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 143 | UTextBlock* MicrophoneVolumeTextBlock = nullptr; 144 | 145 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 146 | UTextBlock* SpeakersVolumeTextBlock = nullptr; 147 | 148 | // combo boxes 149 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 150 | UComboBoxString* MicrophoneComboBox = nullptr; 151 | 152 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 153 | UComboBoxString* SpeakersComboBox = nullptr; 154 | 155 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 156 | UComboBoxString* CameraComboBox = nullptr; 157 | 158 | // sliders 159 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 160 | USlider* MicrophoneVolumeSlider = nullptr; 161 | 162 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 163 | USlider* SpeakersVolumeSlider = nullptr; 164 | 165 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 166 | UVideoViewWidget* TestVideoViewWidget = nullptr; 167 | 168 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 169 | USizeBox* TestVideoViewWidgetSizeBox = nullptr; 170 | }; 171 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/CameraManager.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #include "CameraManager.h" 4 | 5 | #include "AgoraVideoDeviceManager.h" 6 | #include "AgoraRtcEngine.h" 7 | #include "AgoraMediaEngine.h" 8 | 9 | #include "VideoFrameObserver.h" 10 | 11 | CameraManager::CameraManager( 12 | TSharedPtr RtcEngine, 13 | TSharedPtr MediaEngine) 14 | { 15 | if (!RtcEngine || !MediaEngine) 16 | { 17 | return; 18 | } 19 | RtcEngine->enableVideo();//TODO:in VideoCall? 20 | RtcEnginePtr = std::move(RtcEngine); 21 | MediaEnginePtr = std::move(MediaEngine); 22 | DeviceManagerPtr = TUniquePtr( 23 | agora::rtc::ue4::AgoraVideoDeviceManager::Create(RtcEnginePtr.Get())); 24 | if (!DeviceManagerPtr) 25 | { 26 | return; 27 | } 28 | CollectionPtr = TUniquePtr( 29 | DeviceManagerPtr->enumerateVideoDevices()); 30 | } 31 | 32 | CameraManager::~CameraManager() 33 | { 34 | 35 | } 36 | 37 | TUniquePtr CameraManager::Create( 38 | TSharedPtr RtcEngine, 39 | TSharedPtr MediaEngine) 40 | { 41 | return MakeUnique(std::move(RtcEngine), std::move(MediaEngine)); 42 | } 43 | 44 | uint32 CameraManager::GetDeviceCount() 45 | { 46 | if (!CollectionPtr) 47 | return 0; 48 | 49 | return CollectionPtr->getCount(); 50 | } 51 | 52 | FString CameraManager::GetCurrDeviceID() 53 | { 54 | if (!DeviceManagerPtr) 55 | { 56 | return FString(); 57 | } 58 | 59 | char DefaultDeviceId[agora::rtc::MAX_DEVICE_ID_LENGTH]; 60 | DeviceManagerPtr->getDevice(DefaultDeviceId); 61 | return FString(ANSI_TO_TCHAR(DefaultDeviceId)); 62 | } 63 | 64 | bool CameraManager::SetCurrDevice(const FString& PassedDeviceID) 65 | { 66 | if (!DeviceManagerPtr) 67 | { 68 | return false; 69 | } 70 | 71 | char* DeviceID = TCHAR_TO_ANSI(*PassedDeviceID); 72 | int nRet = DeviceManagerPtr->setDevice(DeviceID); 73 | return nRet == 0 ? true : false; 74 | } 75 | 76 | FString CameraManager::GetDeviceID(const FString& DeviceNameToFind) 77 | { 78 | if (!CollectionPtr) 79 | return ""; 80 | 81 | auto NumDevices = CollectionPtr->getCount(); 82 | for (int Index = 0; Index < NumDevices; ++Index) 83 | { 84 | FString DeviceName; 85 | FString DeviceID; 86 | bool bRes = CameraManager::GetDevice(Index, DeviceName, DeviceID); 87 | if (bRes) 88 | { 89 | if (DeviceNameToFind == DeviceName) 90 | { 91 | return DeviceID; 92 | } 93 | } 94 | } 95 | return FString(); 96 | } 97 | 98 | FString CameraManager::GetCurrDeviceName() 99 | { 100 | if (!DeviceManagerPtr || !CollectionPtr) 101 | { 102 | return FString(); 103 | } 104 | 105 | char DefaultDeviceId[agora::rtc::MAX_DEVICE_ID_LENGTH]; 106 | DeviceManagerPtr->getDevice(DefaultDeviceId); 107 | 108 | auto NumDevices = CollectionPtr->getCount(); 109 | for (int i = 0; i < NumDevices; ++i) 110 | { 111 | char DeviceName[agora::rtc::MAX_DEVICE_ID_LENGTH]; 112 | char DeviceId[agora::rtc::MAX_DEVICE_ID_LENGTH]; 113 | CollectionPtr->getDevice(i, DeviceName, DeviceId); 114 | 115 | UE_LOG(LogTemp, Warning, TEXT("deviceName: %s, deviceId: %s"), 116 | *FString(ANSI_TO_TCHAR(DeviceName)), *FString(ANSI_TO_TCHAR(DeviceId))); 117 | 118 | if (strcmp(DeviceId, DefaultDeviceId) == 0) 119 | { 120 | return FString(ANSI_TO_TCHAR(DeviceName)); 121 | } 122 | } 123 | return FString(); 124 | } 125 | 126 | std::vector CameraManager::GetVideoDevices() 127 | { 128 | std::vector output; 129 | if (!CollectionPtr) 130 | return output; 131 | 132 | auto NumDevices = CollectionPtr->getCount(); 133 | for (int Index = 0; Index < NumDevices; ++Index) 134 | { 135 | FString DeviceName; 136 | FString DeviceID; 137 | bool bRes = GetDevice(Index, DeviceName, DeviceID); 138 | if (bRes) 139 | { 140 | output.push_back(DeviceName); 141 | } 142 | } 143 | return output; 144 | } 145 | 146 | bool CameraManager::GetDevice(uint32 Index, FString& DeviceName, FString& DeviceID) 147 | { 148 | if (!CollectionPtr) 149 | return false; 150 | 151 | if (Index >= GetDeviceCount()) 152 | return false; 153 | 154 | char Name[agora::rtc::MAX_DEVICE_ID_LENGTH]; 155 | char Id[agora::rtc::MAX_DEVICE_ID_LENGTH]; 156 | int nRet = CollectionPtr->getDevice(Index, Name, Id); 157 | if (nRet != 0) 158 | return false; 159 | 160 | DeviceName = FString(ANSI_TO_TCHAR(Name)); 161 | DeviceID = FString(ANSI_TO_TCHAR(Id)); 162 | 163 | return true; 164 | } 165 | 166 | void CameraManager::TestCameraDevice( 167 | std::function Callback) 168 | { 169 | if (!MediaEnginePtr || !RtcEnginePtr) 170 | { 171 | return; 172 | } 173 | if (!VideoFrameObserverPtr) 174 | { 175 | VideoFrameObserverPtr = TUniquePtr(new VideoFrameObserver()); 176 | VideoFrameObserverPtr->setOnCaptureVideoFrameCallback(std::move(Callback)); 177 | MediaEnginePtr->registerVideoFrameObserver(VideoFrameObserverPtr.Get()); 178 | } 179 | RtcEnginePtr->enableVideo(); 180 | 181 | RtcEnginePtr->startPreview(); 182 | } 183 | 184 | void CameraManager::StopCameraDeviceTest() 185 | { 186 | if (!MediaEnginePtr || !RtcEnginePtr) 187 | { 188 | return; 189 | } 190 | RtcEnginePtr->stopPreview(); 191 | 192 | MediaEnginePtr->registerVideoFrameObserver(nullptr); 193 | VideoFrameObserverPtr.Reset(); 194 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![deprecated](https://user-images.githubusercontent.com/1261195/180920123-dd6edb6a-1dbb-47b7-8059-f354c792b1a4.png) 2 | 3 | # The new github repository address is https://github.com/AgoraIO-Extensions/Agora-Unreal-RTC-SDK, if you want to use it, please click. 4 | 5 | # AgoraVideoCall Sample App 6 | 7 | The Agora Video Call is a demo app that will help you integrate Real Time Engagement directly into your Unreal Engine applications using the AgoraPlugin, that wraps Agora Video SDK. 8 | 9 | With this sample app, you can: 10 | 11 | - Join / leave channel 12 | - Mute / unmute audio 13 | - Enable / disable video 14 | - Switch camera 15 | - Setup resolution and frame rate 16 | 17 | Developed with Unreal Engine 4.23 18 | 19 | There are two implementations of Agora Video Call demo application: 20 | 1) C++ based. 21 | 2) Blueprints based. 22 | 23 | ## Supported Platforms 24 | 25 | Windows 64-bit 26 | 27 | Mac 28 | 29 | iOS 30 | 31 | ## Getting Started *IMPORTANT* 32 | ### If you are starting with a blank project, start with C++ template 33 | Agora plugin is implemented as separate module. 34 | [Download the plugin .zip file here](https://apprtcio-my.sharepoint.com/:f:/g/personal/joel_agora_io/ElB8ya6XEXNEt742um20rAQBrbSvIo_biLgTIKI8ddrc4w?e=42QaST) and add the AgoraPlugin folder to the "Plugins" folder inside your Unreal project. 35 | 36 | You can simply unzip and drop the entire AgoraPlugin folder inside of Plugins, and you're good to go! 37 | The folder structure should like like /Plugins/AgoraPlugin/ 38 | 39 | ## Building and Running the App 40 | Open AgoraVideoCall.uproject with Unreal Editor 4.23-4.25. 41 | To test Agora functionality, you have to package the project and run as a standalone build, or if testing on iOS use Unreal's "Launch" feature. 42 | 43 | To package the project: 44 | ### Windows 45 | File->Package Project->Windows->Windows(64-bit) then select a folder where you want to package and wait for result. 46 | ![Alt text](ReadMeImages/HowToPackageProject.png?raw=true "PackageProject") 47 | 48 | 49 | ## Mac 50 | File -> Package Project -> Mac 51 | 52 | ![Alt text](ReadMeImages/HowToPackageProjectMac.png?raw=true "PackageProject") 53 | 54 | ### Add the following permissions in the info.plist file for device access: 55 | 1. Right click .app file - select "Show Package Contents" 56 | 2. Go to "Contents->Info.plist" 57 | 3. Click the plus next to "Information Property List" and add: 58 | 59 | Privacy - Camera Usage Description 60 | 61 | Privacy - Microphone Usage Description 62 | 63 | ### Add AgoraRtcKit.framework to your newly built project 64 | 1. Package the project from Unreal 65 | 2. From inside the Plugins folder of the project, copy the file: **AgoraRtcKit.framework** from **Plugins/AgoraPlugin/Source/ThirdParty/Agora/Mac/Release** to 66 | 3. Paste file into your newly built project folder **(Packaged project dir)/MacNoEditor/[project_name]/Contents/MacOS/** 67 | 68 | ## iOS Packaging 69 | To package the project for iOS, you need to have a **Signing Certificate** and **Provisioning Profile** and add it to your project. 70 | I would *highly recommend* going to Project Settings > Platforms > iOS > Build > and check "Automatic Signing" - this saves me a lot of headache. 71 | 72 | ![iOS settings](ReadMeImages/iOSSettings.png) 73 | 74 | If you don't have one: follow the instructions from UE4 documentation: [iOS Provisioning](https://docs.unrealengine.com/en-US/Platforms/Mobile/iOS/Provisioning/index.html) 75 | Go to the **Edit->Project Settings->Platforms: iOS**, then select the certificate and provisioning profile you created. 76 | 77 | If you don't see one of them in the table, click **Import Certificate** or **Import Provision**, chose the right file in Finder and click **Open**. 78 | 79 | Then enter a Bundle Identifier: it must be the Bundle Id you used during certificate creation. 80 | 81 | ### iOS Testing 82 | To test your Agora project on your iOS device: 83 | 1. Connect your phone to your computer via USB 84 | 2. Make sure your signing certificates are all up to date and working 85 | 3. In the Unreal "Toolbar" (Save, Compile, Source Control, etc.) click the "Launch" button (to the right of Play) with your device selected in the dropdown 86 | 87 | ## iOS permissions 88 | In the iOS you also need the following permissions: 89 | 90 | Privacy - Camera Usage Description 91 | 92 | Privacy - Microphone Usage Description 93 | 94 | To add them in the info.plist go to the **Edit->Project Settings->Platforms: iOS** and enter the following line to Additional Plist Data: 95 | `NSCameraUsageDescriptionAgoraVideoCall NSMicrophoneUsageDescriptionAgoraVideoCall` 96 | 97 | Now you are ready to package your project for iOS or launch it on iOS device. 98 | 99 | ## Plugin Dependencies 100 | 101 | 1. Copy the plugin to [your_project]/Plugins 102 | 2. Add plugin dependency into [your_project]/Source/[project_name]/[project_name].Build.cs, Private Dependencies section 103 | 104 | `PrivateDependencyModuleNames.AddRange(new string[] { "AgoraPlugin", "AgoraBlueprintable" });` 105 | 106 | If the version of your Unreal Editor is 4.24 or higher add the following into [your_project]/Source/[project_name]Editor.Target.cs 107 | 108 | `DefaultBuildSettings = BuildSettingsVersion.V2;` 109 | 110 | 3. Open Unreal Project, go to **Edit->Plugins**. Find category **Project->Other** and make sure plugin is enabled. 111 | 112 | ![Enable Plugin](ReadMeImages/2020-03-12_13-26-59.png) 113 | 114 | ## Connect With Us 115 | 116 | - You can find full API document at [Document Center](https://docs.agora.io/en/) 117 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/AudioInOutDeviceManager.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #include "AudioInOutDeviceManager.h" 4 | 5 | #include "AgoraAudioDeviceManager.h" 6 | #include "AgoraRtcEngine.h" 7 | 8 | AudioInOutDeviceManager::AudioInOutDeviceManager( 9 | TSharedPtr RtcEngine) 10 | { 11 | if (!RtcEngine) 12 | { 13 | return; 14 | } 15 | RtcEnginePtr = std::move(RtcEngine); 16 | 17 | DeviceManagerPtr = TUniquePtr( 18 | agora::rtc::ue4::AgoraAudioDeviceManager::Create(RtcEnginePtr.Get())); 19 | if (!DeviceManagerPtr) 20 | { 21 | return; 22 | } 23 | RecordingDevicesPtr = TUniquePtr( 24 | DeviceManagerPtr->enumerateRecordingDevices()); 25 | PlaybackDevicesPtr = TUniquePtr( 26 | DeviceManagerPtr->enumeratePlaybackDevices()); 27 | } 28 | 29 | AudioInOutDeviceManager::~AudioInOutDeviceManager() 30 | { 31 | 32 | } 33 | 34 | TUniquePtr AudioInOutDeviceManager::Create( 35 | TSharedPtr RtcEngine) 36 | { 37 | return MakeUnique(std::move(RtcEngine)); 38 | } 39 | 40 | uint32 AudioInOutDeviceManager::GetRecordingDeviceCount() 41 | { 42 | if (!RecordingDevicesPtr) 43 | return 0; 44 | 45 | return RecordingDevicesPtr->getCount(); 46 | } 47 | 48 | uint32 AudioInOutDeviceManager::GetPlaybackDeviceCount() 49 | { 50 | if (!PlaybackDevicesPtr) 51 | return 0; 52 | 53 | return PlaybackDevicesPtr->getCount(); 54 | } 55 | 56 | std::vector AudioInOutDeviceManager::GetRecordingDevices() 57 | { 58 | std::vector output; 59 | if (!RecordingDevicesPtr) 60 | return output; 61 | auto NumDevices = RecordingDevicesPtr->getCount(); 62 | for (int Index = 0; Index < NumDevices; ++Index) 63 | { 64 | FString DeviceName; 65 | FString DeviceID; 66 | bool bRes = GetRecordingDevice(Index, DeviceName, DeviceID); 67 | if (bRes) 68 | { 69 | output.push_back(DeviceName); 70 | } 71 | } 72 | return output; 73 | } 74 | 75 | FString AudioInOutDeviceManager::GetCurrRecordingDeviceName() 76 | { 77 | if (!DeviceManagerPtr || !RecordingDevicesPtr) 78 | { 79 | return FString(); 80 | } 81 | 82 | char DefaultDeviceId[agora::rtc::MAX_DEVICE_ID_LENGTH]; 83 | DeviceManagerPtr->getRecordingDevice(DefaultDeviceId); 84 | auto NumDevices = RecordingDevicesPtr->getCount(); 85 | for (int i = 0; i < NumDevices; ++i) 86 | { 87 | char DeviceName[agora::rtc::MAX_DEVICE_ID_LENGTH]; 88 | char DeviceId[agora::rtc::MAX_DEVICE_ID_LENGTH]; 89 | RecordingDevicesPtr->getDevice(i, DeviceName, DeviceId); 90 | 91 | UE_LOG(LogTemp, Warning, TEXT("RecordingDevice deviceName: %s, deviceId: %s"), 92 | *FString(ANSI_TO_TCHAR(DeviceName)), *FString(ANSI_TO_TCHAR(DeviceId))); 93 | 94 | if (strcmp(DeviceId, DefaultDeviceId) == 0) 95 | { 96 | return FString(ANSI_TO_TCHAR(DeviceName)); 97 | } 98 | } 99 | return FString(); 100 | } 101 | 102 | std::vector AudioInOutDeviceManager::GetPlaybackDevices() 103 | { 104 | std::vector output; 105 | if (!PlaybackDevicesPtr) 106 | return output; 107 | auto NumDevices = PlaybackDevicesPtr->getCount(); 108 | for (int Index = 0; Index < NumDevices; ++Index) 109 | { 110 | FString DeviceName; 111 | FString DeviceID; 112 | bool bRes = GetPlaybackDevice(Index, DeviceName, DeviceID); 113 | if (bRes) 114 | { 115 | output.push_back(DeviceName); 116 | } 117 | } 118 | return output; 119 | } 120 | 121 | FString AudioInOutDeviceManager::GetCurrPlaybackDeviceName() 122 | { 123 | if (!DeviceManagerPtr || !PlaybackDevicesPtr) 124 | { 125 | return FString(); 126 | } 127 | 128 | char DefaultDeviceId[agora::rtc::MAX_DEVICE_ID_LENGTH]; 129 | DeviceManagerPtr->getPlaybackDevice(DefaultDeviceId); 130 | 131 | auto NumDevices = PlaybackDevicesPtr->getCount(); 132 | for (int i = 0; i < NumDevices; ++i) 133 | { 134 | char DeviceName[agora::rtc::MAX_DEVICE_ID_LENGTH]; 135 | char DeviceId[agora::rtc::MAX_DEVICE_ID_LENGTH]; 136 | PlaybackDevicesPtr->getDevice(i, DeviceName, DeviceId); 137 | 138 | UE_LOG(LogTemp, Warning, TEXT("PlaybackDevice deviceName: %s, deviceId: %s"), 139 | *FString(ANSI_TO_TCHAR(DeviceName)), *FString(ANSI_TO_TCHAR(DeviceId))); 140 | 141 | if (strcmp(DeviceId, DefaultDeviceId) == 0) 142 | { 143 | return FString(ANSI_TO_TCHAR(DeviceName)); 144 | } 145 | } 146 | return FString(); 147 | } 148 | 149 | bool AudioInOutDeviceManager::SetCurrRecordingDevice(const FString& PassedDeviceID) 150 | { 151 | if (!DeviceManagerPtr) 152 | { 153 | return false; 154 | } 155 | 156 | char* DeviceID = TCHAR_TO_ANSI(*PassedDeviceID); 157 | int nRet = DeviceManagerPtr->setPlaybackDevice(DeviceID); 158 | return nRet == 0 ? true : false; 159 | } 160 | 161 | FString AudioInOutDeviceManager::GetRecordingDeviceID(const FString& DeviceNameToFind) 162 | { 163 | if (!RecordingDevicesPtr) 164 | return ""; 165 | auto NumDevices = RecordingDevicesPtr->getCount(); 166 | for (int Index = 0; Index < NumDevices; ++Index) 167 | { 168 | FString DeviceName; 169 | FString DeviceID; 170 | bool bRes = GetRecordingDevice(Index, DeviceName, DeviceID); 171 | if (bRes) 172 | { 173 | if (DeviceNameToFind == DeviceName) 174 | { 175 | return DeviceID; 176 | } 177 | } 178 | } 179 | return FString(); 180 | } 181 | 182 | bool AudioInOutDeviceManager::SetCurrPlaybackDevice(const FString& PassedDeviceID) 183 | { 184 | if (!DeviceManagerPtr) 185 | { 186 | return false; 187 | } 188 | 189 | char* DeviceID = TCHAR_TO_ANSI(*PassedDeviceID); 190 | int nRet = DeviceManagerPtr->setRecordingDevice(DeviceID); 191 | return nRet == 0 ? true : false; 192 | } 193 | 194 | FString AudioInOutDeviceManager::GetPlaybackDeviceID(const FString& DeviceNameToFind) 195 | { 196 | if (!PlaybackDevicesPtr) 197 | return ""; 198 | auto NumDevices = PlaybackDevicesPtr->getCount(); 199 | for (int Index = 0; Index < NumDevices; ++Index) 200 | { 201 | FString DeviceName; 202 | FString DeviceID; 203 | bool bRes = GetPlaybackDevice(Index, DeviceName, DeviceID); 204 | if (bRes) 205 | { 206 | if (DeviceNameToFind == DeviceName) 207 | { 208 | return DeviceID; 209 | } 210 | } 211 | } 212 | return FString(); 213 | } 214 | 215 | bool AudioInOutDeviceManager::GetRecordingDevice(uint32 Index, FString& DeviceName, FString& DeviceID) 216 | { 217 | if (!RecordingDevicesPtr) 218 | return false; 219 | 220 | if (Index >= GetRecordingDeviceCount()) 221 | return false; 222 | 223 | char Name[agora::rtc::MAX_DEVICE_ID_LENGTH]; 224 | char Id[agora::rtc::MAX_DEVICE_ID_LENGTH]; 225 | int nRet = RecordingDevicesPtr->getDevice(Index, Name, Id); 226 | if (nRet != 0) 227 | return false; 228 | 229 | DeviceName = FString(ANSI_TO_TCHAR(Name)); 230 | DeviceID = FString(ANSI_TO_TCHAR(Id)); 231 | 232 | return true; 233 | } 234 | 235 | bool AudioInOutDeviceManager::GetPlaybackDevice(uint32 Index, FString& DeviceName, FString& DeviceID) 236 | { 237 | if (!PlaybackDevicesPtr) 238 | return false; 239 | 240 | if (Index >= GetRecordingDeviceCount()) 241 | return false; 242 | 243 | char Name[agora::rtc::MAX_DEVICE_ID_LENGTH]; 244 | char Id[agora::rtc::MAX_DEVICE_ID_LENGTH]; 245 | int nRet = PlaybackDevicesPtr->getDevice(Index, Name, Id); 246 | if (nRet != 0) 247 | return false; 248 | 249 | DeviceName = FString(ANSI_TO_TCHAR(Name)); 250 | DeviceID = FString(ANSI_TO_TCHAR(Id)); 251 | 252 | return true; 253 | } 254 | 255 | bool AudioInOutDeviceManager::SetPlaybackDeviceVolume(int Volume) 256 | { 257 | if (!DeviceManagerPtr) 258 | { 259 | return false; 260 | } 261 | int nRet = DeviceManagerPtr->setPlaybackDeviceVolume(Volume); 262 | if (nRet != 0) 263 | return false; 264 | return true; 265 | } 266 | 267 | int AudioInOutDeviceManager::GetPlaybackDeviceVolume() const 268 | { 269 | if (!DeviceManagerPtr) 270 | { 271 | return false; 272 | } 273 | int Volume = 0; 274 | int nRet = DeviceManagerPtr->getPlaybackDeviceVolume(&Volume); 275 | if (nRet != 0) 276 | return 0; 277 | return Volume; 278 | } 279 | 280 | bool AudioInOutDeviceManager::SetRecordingDeviceVolume(int Volume) 281 | { 282 | if (!DeviceManagerPtr) 283 | { 284 | return false; 285 | } 286 | int nRet = DeviceManagerPtr->setRecordingDeviceVolume(Volume); 287 | if (nRet != 0) 288 | return false; 289 | return true; 290 | } 291 | 292 | int AudioInOutDeviceManager::GetRecordingDeviceVolume() const 293 | { 294 | if (!DeviceManagerPtr) 295 | { 296 | return false; 297 | } 298 | int Volume = 0; 299 | int nRet = DeviceManagerPtr->getRecordingDeviceVolume(&Volume); 300 | if (nRet != 0) 301 | return 0; 302 | return Volume; 303 | } 304 | 305 | bool AudioInOutDeviceManager::TestRecordingDevice() 306 | { 307 | if (!DeviceManagerPtr) 308 | { 309 | return false; 310 | } 311 | int nRet = DeviceManagerPtr->startRecordingDeviceTest(1000); 312 | if (nRet != 0) 313 | return false; 314 | return true; 315 | } 316 | 317 | bool AudioInOutDeviceManager::StopRecordingDeviceTest() 318 | { 319 | if (!DeviceManagerPtr) 320 | { 321 | return false; 322 | } 323 | int nRet = DeviceManagerPtr->stopRecordingDeviceTest(); 324 | if (nRet != 0) 325 | return false; 326 | return true; 327 | } 328 | 329 | bool AudioInOutDeviceManager::TestPlaybackDevice(const FString& AudioFile) 330 | { 331 | if (!DeviceManagerPtr) 332 | { 333 | return false; 334 | } 335 | if(AudioFile.IsEmpty()) 336 | { 337 | return false; 338 | } 339 | char* TestAudioFilePath = TCHAR_TO_ANSI(*AudioFile); 340 | int nRet = DeviceManagerPtr->startPlaybackDeviceTest(TestAudioFilePath); 341 | if (nRet != 0) 342 | { 343 | return false; 344 | } 345 | 346 | return true; 347 | } 348 | 349 | bool AudioInOutDeviceManager::StopPlaybackDeviceTest() 350 | { 351 | if (!DeviceManagerPtr) 352 | { 353 | return false; 354 | } 355 | int nRet = DeviceManagerPtr->stopPlaybackDeviceTest(); 356 | if (nRet != 0) 357 | return false; 358 | return true; 359 | } 360 | -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/VideoCallWidget.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #include "VideoCallWidget.h" 4 | 5 | #include "Kismet/GameplayStatics.h" 6 | #include "UObject/ConstructorHelpers.h" 7 | #include "Components/CanvasPanelSlot.h" 8 | 9 | #include "VideoViewWidget.h" 10 | 11 | #include "VideoCallPlayerController.h" 12 | 13 | UVideoCallWidget::UVideoCallWidget(const FObjectInitializer& ObjectInitializer) 14 | : Super(ObjectInitializer) 15 | { 16 | static ConstructorHelpers::FObjectFinder 17 | EndCallButtonTextureFinder(TEXT("Texture'/Game/ButtonTextures/hangup.hangup'")); 18 | if (EndCallButtonTextureFinder.Succeeded()) 19 | { 20 | EndCallButtonTexture = EndCallButtonTextureFinder.Object; 21 | } 22 | static ConstructorHelpers::FObjectFinder 23 | AudioButtonMuteTextureFinder(TEXT("Texture'/Game/ButtonTextures/mute.mute'")); 24 | if (AudioButtonMuteTextureFinder.Succeeded()) 25 | { 26 | AudioButtonMuteTexture = AudioButtonMuteTextureFinder.Object; 27 | } 28 | static ConstructorHelpers::FObjectFinder 29 | AudioButtonUnmuteTextureFinder(TEXT("Texture'/Game/ButtonTextures/unmute.unmute'")); 30 | if (AudioButtonUnmuteTextureFinder.Succeeded()) 31 | { 32 | AudioButtonUnmuteTexture = AudioButtonUnmuteTextureFinder.Object; 33 | } 34 | static ConstructorHelpers::FObjectFinder 35 | VideomodeButtonCameraonTextureFinder(TEXT("Texture'/Game/ButtonTextures/cameraon.cameraon'")); 36 | if (VideomodeButtonCameraonTextureFinder.Succeeded()) 37 | { 38 | VideomodeButtonCameraonTexture = VideomodeButtonCameraonTextureFinder.Object; 39 | } 40 | static ConstructorHelpers::FObjectFinder 41 | VideomodeButtonCameraoffTextureFinder(TEXT("Texture'/Game/ButtonTextures/cameraoff.cameraoff'")); 42 | if (VideomodeButtonCameraoffTextureFinder.Succeeded()) 43 | { 44 | VideomodeButtonCameraoffTexture = VideomodeButtonCameraoffTextureFinder.Object; 45 | } 46 | } 47 | 48 | void UVideoCallWidget::NativeConstruct() 49 | { 50 | Super::NativeConstruct(); 51 | 52 | InitButtons(); 53 | } 54 | 55 | void UVideoCallWidget::NativeDestruct() 56 | { 57 | Super::NativeDestruct(); 58 | 59 | if (VideoCallPtr) 60 | { 61 | VideoCallPtr->StopCall(); 62 | } 63 | } 64 | 65 | void UVideoCallWidget::NativeTick(const FGeometry& MyGeometry, float DeltaTime) 66 | { 67 | Super::NativeTick(MyGeometry, DeltaTime); 68 | 69 | } 70 | 71 | void UVideoCallWidget::OnStartCall( 72 | const FString& ChannelName, 73 | const FString& EncryptionKey, 74 | const FString& EncryptionType) 75 | { 76 | if (!VideoCallPtr) 77 | { 78 | return; 79 | } 80 | 81 | auto OnLocalFrameCallback = [this]( 82 | std::uint8_t* Buffer, 83 | std::uint32_t Width, 84 | std::uint32_t Height, 85 | std::uint32_t Size) 86 | { 87 | VideoCallViewWidget->UpdateAdditionalVideoBuffer(Buffer, Width, Height, Size); 88 | }; 89 | VideoCallPtr->RegisterOnLocalFrameCallback(OnLocalFrameCallback); 90 | 91 | auto OnRemoteFrameCallback = [this]( 92 | std::uint8_t* Buffer, 93 | std::uint32_t Width, 94 | std::uint32_t Height, 95 | std::uint32_t Size) 96 | { 97 | VideoCallViewWidget->UpdateMainVideoBuffer(Buffer, Width, Height, Size); 98 | }; 99 | VideoCallPtr->RegisterOnRemoteFrameCallback(OnRemoteFrameCallback); 100 | 101 | VideoCallPtr->StartCall(ChannelName, EncryptionKey, EncryptionType); 102 | } 103 | 104 | void UVideoCallWidget::OnEndCall() 105 | { 106 | if (VideoCallPtr) 107 | { 108 | VideoCallPtr->StopCall(); 109 | } 110 | 111 | if (VideoCallViewWidget) 112 | { 113 | VideoCallViewWidget->ResetBuffers(); 114 | } 115 | 116 | if (PlayerController) 117 | { 118 | SetVisibility(ESlateVisibility::Collapsed); 119 | PlayerController->EndCall(std::move(VideoCallPtr)); 120 | } 121 | } 122 | 123 | void UVideoCallWidget::OnMuteLocalAudio() 124 | { 125 | if (!VideoCallPtr) 126 | { 127 | return; 128 | } 129 | if (VideoCallPtr->IsLocalAudioMuted()) 130 | { 131 | VideoCallPtr->MuteLocalAudio(false); 132 | SetAudioButtonToMute(); 133 | } 134 | else 135 | { 136 | VideoCallPtr->MuteLocalAudio(true); 137 | SetAudioButtonToUnMute(); 138 | } 139 | } 140 | 141 | void UVideoCallWidget::OnChangeVideoMode() 142 | { 143 | if (!VideoCallPtr) 144 | { 145 | return; 146 | } 147 | if (!VideoCallPtr->IsLocalVideoMuted()) 148 | { 149 | VideoCallPtr->MuteLocalVideo(true); 150 | 151 | SetVideoModeButtonToCameraOn(); 152 | } 153 | else 154 | { 155 | VideoCallPtr->EnableVideo(true); 156 | VideoCallPtr->MuteLocalVideo(false); 157 | 158 | SetVideoModeButtonToCameraOff(); 159 | } 160 | } 161 | 162 | void UVideoCallWidget::InitButtons() 163 | { 164 | if (EndCallButtonTexture) 165 | { 166 | EndCallButton->WidgetStyle.Normal.SetResourceObject(EndCallButtonTexture); 167 | EndCallButton->WidgetStyle.Normal.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 168 | EndCallButton->WidgetStyle.Normal.DrawAs = ESlateBrushDrawType::Type::Image; 169 | 170 | EndCallButton->WidgetStyle.Hovered.SetResourceObject(EndCallButtonTexture); 171 | EndCallButton->WidgetStyle.Hovered.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 172 | EndCallButton->WidgetStyle.Hovered.DrawAs = ESlateBrushDrawType::Type::Image; 173 | 174 | EndCallButton->WidgetStyle.Pressed.SetResourceObject(EndCallButtonTexture); 175 | EndCallButton->WidgetStyle.Pressed.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 176 | EndCallButton->WidgetStyle.Pressed.DrawAs = ESlateBrushDrawType::Type::Image; 177 | } 178 | EndCallButton->OnClicked.AddDynamic(this, &UVideoCallWidget::OnEndCall); 179 | 180 | SetAudioButtonToMute(); 181 | MuteLocalAudioButton->OnClicked.AddDynamic(this, &UVideoCallWidget::OnMuteLocalAudio); 182 | 183 | SetVideoModeButtonToCameraOff(); 184 | VideoModeButton->OnClicked.AddDynamic(this, &UVideoCallWidget::OnChangeVideoMode); 185 | 186 | } 187 | 188 | void UVideoCallWidget::SetVideoModeButtonToCameraOff() 189 | { 190 | if (VideomodeButtonCameraoffTexture) 191 | { 192 | VideoModeButton->WidgetStyle.Normal.SetResourceObject(VideomodeButtonCameraoffTexture); 193 | VideoModeButton->WidgetStyle.Normal.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 194 | VideoModeButton->WidgetStyle.Normal.DrawAs = ESlateBrushDrawType::Type::Image; 195 | 196 | VideoModeButton->WidgetStyle.Hovered.SetResourceObject(VideomodeButtonCameraoffTexture); 197 | VideoModeButton->WidgetStyle.Hovered.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 198 | VideoModeButton->WidgetStyle.Hovered.DrawAs = ESlateBrushDrawType::Type::Image; 199 | 200 | VideoModeButton->WidgetStyle.Pressed.SetResourceObject(VideomodeButtonCameraoffTexture); 201 | VideoModeButton->WidgetStyle.Pressed.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 202 | VideoModeButton->WidgetStyle.Pressed.DrawAs = ESlateBrushDrawType::Type::Image; 203 | } 204 | } 205 | 206 | void UVideoCallWidget::SetVideoModeButtonToCameraOn() 207 | { 208 | if (VideomodeButtonCameraonTexture) 209 | { 210 | VideoModeButton->WidgetStyle.Normal.SetResourceObject(VideomodeButtonCameraonTexture); 211 | VideoModeButton->WidgetStyle.Normal.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 212 | VideoModeButton->WidgetStyle.Normal.DrawAs = ESlateBrushDrawType::Type::Image; 213 | 214 | VideoModeButton->WidgetStyle.Hovered.SetResourceObject(VideomodeButtonCameraonTexture); 215 | VideoModeButton->WidgetStyle.Hovered.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 216 | VideoModeButton->WidgetStyle.Hovered.DrawAs = ESlateBrushDrawType::Type::Image; 217 | 218 | VideoModeButton->WidgetStyle.Pressed.SetResourceObject(VideomodeButtonCameraonTexture); 219 | VideoModeButton->WidgetStyle.Pressed.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 220 | VideoModeButton->WidgetStyle.Pressed.DrawAs = ESlateBrushDrawType::Type::Image; 221 | } 222 | } 223 | 224 | void UVideoCallWidget::SetAudioButtonToMute() 225 | { 226 | if (AudioButtonMuteTexture) 227 | { 228 | MuteLocalAudioButton->WidgetStyle.Normal.SetResourceObject(AudioButtonMuteTexture); 229 | MuteLocalAudioButton->WidgetStyle.Normal.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 230 | MuteLocalAudioButton->WidgetStyle.Normal.DrawAs = ESlateBrushDrawType::Type::Image; 231 | 232 | MuteLocalAudioButton->WidgetStyle.Hovered.SetResourceObject(AudioButtonMuteTexture); 233 | MuteLocalAudioButton->WidgetStyle.Hovered.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 234 | MuteLocalAudioButton->WidgetStyle.Hovered.DrawAs = ESlateBrushDrawType::Type::Image; 235 | 236 | MuteLocalAudioButton->WidgetStyle.Pressed.SetResourceObject(AudioButtonMuteTexture); 237 | MuteLocalAudioButton->WidgetStyle.Pressed.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 238 | MuteLocalAudioButton->WidgetStyle.Pressed.DrawAs = ESlateBrushDrawType::Type::Image; 239 | } 240 | } 241 | 242 | void UVideoCallWidget::SetAudioButtonToUnMute() 243 | { 244 | if (AudioButtonUnmuteTexture) 245 | { 246 | MuteLocalAudioButton->WidgetStyle.Normal.SetResourceObject(AudioButtonUnmuteTexture); 247 | MuteLocalAudioButton->WidgetStyle.Normal.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 248 | MuteLocalAudioButton->WidgetStyle.Normal.DrawAs = ESlateBrushDrawType::Type::Image; 249 | 250 | MuteLocalAudioButton->WidgetStyle.Hovered.SetResourceObject(AudioButtonUnmuteTexture); 251 | MuteLocalAudioButton->WidgetStyle.Hovered.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 252 | MuteLocalAudioButton->WidgetStyle.Hovered.DrawAs = ESlateBrushDrawType::Type::Image; 253 | 254 | MuteLocalAudioButton->WidgetStyle.Pressed.SetResourceObject(AudioButtonUnmuteTexture); 255 | MuteLocalAudioButton->WidgetStyle.Pressed.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 256 | MuteLocalAudioButton->WidgetStyle.Pressed.DrawAs = ESlateBrushDrawType::Type::Image; 257 | } 258 | } 259 | 260 | void UVideoCallWidget::SetVideoCallPlayerController(AVideoCallPlayerController* VideoCallPlayerController) 261 | { 262 | PlayerController = VideoCallPlayerController; 263 | } 264 | 265 | void UVideoCallWidget::SetVideoCall(TUniquePtr PassedVideoCallPtr) 266 | { 267 | VideoCallPtr = std::move(PassedVideoCallPtr); 268 | } -------------------------------------------------------------------------------- /Source/AgoraVideoCall/VideoCall.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #include "VideoCall.h" 4 | 5 | #include "AgoraVideoDeviceManager.h" 6 | #include "AgoraAudioDeviceManager.h" 7 | 8 | #include "MediaShaders.h" 9 | 10 | #include "VideoFrameObserver.h" 11 | 12 | #include "AudioInOutDeviceManager.h" 13 | 14 | 15 | //=============== RtcEngineEventHandler ================== 16 | 17 | namespace agora 18 | { 19 | namespace rtc 20 | { 21 | namespace ue4 22 | { 23 | 24 | class RtcEngineEventHandler : public agora::rtc::IRtcEngineEventHandler 25 | { 26 | void onJoinChannelSuccess( const char* channel, agora::rtc::uid_t uid, int elapsed ) override 27 | { 28 | UE_LOG( LogTemp, Warning, TEXT( "onJoinChannelSuccess. channel: %s, uid: %d" ), channel, uid ); 29 | } 30 | 31 | void onConnectionStateChanged( agora::rtc::CONNECTION_STATE_TYPE state, agora::rtc::CONNECTION_CHANGED_REASON_TYPE reason ) override 32 | { 33 | UE_LOG( LogTemp, Warning, TEXT( "onConnectionStateChanged. state: %d, reason: %d" ), state, reason ); 34 | } 35 | 36 | void onUserJoined( agora::rtc::uid_t uid, int elapsed ) override 37 | { 38 | UE_LOG( LogTemp, Warning, TEXT( "onUserJoined. uid: %d, elapsed: %d" ), uid, elapsed ); 39 | } 40 | 41 | void onRejoinChannelSuccess( const char* channel, agora::rtc::uid_t uid, int elapsed ) override 42 | { 43 | UE_LOG( LogTemp, Warning, TEXT( "onRejoinChannelSuccess. channel: %s, uid: %d" ), channel, uid ); 44 | } 45 | 46 | void onWarning( int warn, const char* msg ) override 47 | { 48 | //UE_LOG(LogTemp, Warning, TEXT("onWarning. warn: %d, msg: %s"), warn, msg ); 49 | UE_LOG( LogTemp, Warning, TEXT( "onWarning. warn: %d" ), warn ); 50 | } 51 | 52 | void onError( int err, const char* msg ) override 53 | { 54 | //UE_LOG(LogTemp, Warning, TEXT("onError. err: %d, msg: %s"), err, msg); 55 | UE_LOG( LogTemp, Warning, TEXT( "onError. err: %d" ), err ); 56 | } 57 | 58 | void onRtcStats( const agora::rtc::RtcStats& stats ) override 59 | { 60 | UE_LOG( LogTemp, Warning, TEXT( "onRtcStats. stats.duration: %d" ), stats.duration ); 61 | } 62 | 63 | void onAudioVolumeIndication( const agora::rtc::AudioVolumeInfo* speakers, unsigned int speakerNumber, int totalVolume ) override 64 | { 65 | UE_LOG( LogTemp, Warning, TEXT( "onAudioVolumeIndication. totalVolume: %d" ), totalVolume ); 66 | ( void ) speakers; 67 | ( void ) speakerNumber; 68 | ( void ) totalVolume; 69 | } 70 | }; 71 | 72 | //============== PacketObserver =================== 73 | 74 | class PacketObserver : public agora::rtc::IPacketObserver 75 | { 76 | public: 77 | bool onSendAudioPacket( Packet& packet ) override 78 | { 79 | UE_LOG( LogTemp, Warning, TEXT( "onSendAudioPacket. packet.size: %d" ), packet.size ); 80 | return true; 81 | } 82 | 83 | bool onSendVideoPacket( Packet& packet ) override 84 | { 85 | UE_LOG( LogTemp, Warning, TEXT( "onSendVideoPacket. packet.size: %d" ), packet.size ); 86 | return true; 87 | } 88 | 89 | bool onReceiveAudioPacket( Packet& packet ) override 90 | { 91 | UE_LOG( LogTemp, Warning, TEXT( "onReceiveAudioPacket. packet.size: %d" ), packet.size ); 92 | return true; 93 | } 94 | 95 | bool onReceiveVideoPacket( Packet& packet ) override 96 | { 97 | UE_LOG( LogTemp, Warning, TEXT( "onReceiveVideoPacket. packet.size: %d" ), packet.size ); 98 | return true; 99 | } 100 | }; 101 | 102 | } 103 | } 104 | } 105 | 106 | //================== VideoCall ===================== 107 | 108 | VideoCall::VideoCall() 109 | { 110 | InitAgora(); 111 | } 112 | 113 | VideoCall::~VideoCall() 114 | { 115 | StopCall(); 116 | } 117 | 118 | void VideoCall::InitAgora() 119 | { 120 | RtcEnginePtr = TSharedPtr( agora::rtc::ue4::AgoraRtcEngine::createAgoraRtcEngine() ); 121 | 122 | static agora::rtc::RtcEngineContext ctx; 123 | ctx.appId = "aab8b8f5a8cd4469a63042fcfafe7063"; 124 | ctx.eventHandler = new agora::rtc::ue4::RtcEngineEventHandler(); 125 | 126 | int ret = RtcEnginePtr->initialize( ctx ); 127 | if( ret < 0 ) 128 | { 129 | UE_LOG( LogTemp, Warning, TEXT( "RtcEngine initialize ret: %d" ), ret ); 130 | } 131 | MediaEnginePtr = TSharedPtr( agora::media::ue4::AgoraMediaEngine::Create( RtcEnginePtr.Get() ) ); 132 | } 133 | 134 | FString VideoCall::GetVersion() const 135 | { 136 | if( !RtcEnginePtr ) 137 | { 138 | return ""; 139 | } 140 | int build = 0; 141 | const char* version = RtcEnginePtr->getVersion( &build ); 142 | return FString( ANSI_TO_TCHAR( version ) ); 143 | } 144 | 145 | void VideoCall::SetResolution( int Width, int Height, agora::rtc::FRAME_RATE FrameRate ) 146 | { 147 | if( !RtcEnginePtr ) 148 | { 149 | return; 150 | } 151 | agora::rtc::VideoEncoderConfiguration conf; 152 | conf.dimensions = agora::rtc::VideoDimensions( Width, Height ); 153 | conf.frameRate = FrameRate; 154 | int nRet = RtcEnginePtr->setVideoEncoderConfiguration( conf ); 155 | if( nRet < 0 ) 156 | { 157 | UE_LOG( LogTemp, Warning, TEXT( "setVideoEncoderConfiguration : %d" ), nRet ) 158 | } 159 | } 160 | 161 | void VideoCall::RegisterOnLocalFrameCallback( 162 | std::function OnFrameCallback ) 163 | { 164 | OnLocalFrameCallback = std::move( OnFrameCallback ); 165 | } 166 | 167 | void VideoCall::RegisterOnRemoteFrameCallback( 168 | std::function OnFrameCallback ) 169 | { 170 | OnRemoteFrameCallback = std::move( OnFrameCallback ); 171 | } 172 | 173 | void VideoCall::StartCall( 174 | const FString& ChannelName, 175 | const FString& EncryptionKey, 176 | const FString& EncryptionType ) 177 | { 178 | if( !RtcEnginePtr ) 179 | { 180 | return; 181 | } 182 | if( MediaEnginePtr ) 183 | { 184 | if( !VideoFrameObserverPtr ) 185 | { 186 | VideoFrameObserverPtr = MakeUnique(); 187 | 188 | std::function OnCaptureVideoFrameCallback 189 | = [this]( std::uint8_t* buffer, std::uint32_t width, std::uint32_t height, std::uint32_t size ) 190 | { 191 | if( OnLocalFrameCallback ) 192 | { 193 | OnLocalFrameCallback( buffer, width, height, size ); 194 | } 195 | else 196 | { 197 | UE_LOG( LogTemp, Warning, TEXT( "VideoCall OnLocalFrameCallback isn't set" ) ); 198 | } 199 | }; 200 | VideoFrameObserverPtr->setOnCaptureVideoFrameCallback( std::move( OnCaptureVideoFrameCallback ) ); 201 | 202 | std::function OnRenderVideoFrameCallback 203 | = [this]( std::uint8_t* buffer, std::uint32_t width, std::uint32_t height, std::uint32_t size ) 204 | { 205 | if( OnRemoteFrameCallback ) 206 | { 207 | OnRemoteFrameCallback( buffer, width, height, size ); 208 | } 209 | else 210 | { 211 | UE_LOG( LogTemp, Warning, TEXT( "VideoCall OnRemoteFrameCallback isn't set" ) ); 212 | } 213 | }; 214 | VideoFrameObserverPtr->setOnRenderVideoFrameCallback( std::move( OnRenderVideoFrameCallback ) ); 215 | } 216 | 217 | MediaEnginePtr->registerVideoFrameObserver( VideoFrameObserverPtr.Get() ); 218 | } 219 | 220 | int nRet = RtcEnginePtr->enableVideo(); 221 | if( nRet < 0 ) 222 | { 223 | UE_LOG( LogTemp, Warning, TEXT( "enableVideo : %d" ), nRet ) 224 | } 225 | 226 | if( !EncryptionType.IsEmpty() && !EncryptionKey.IsEmpty() ) 227 | { 228 | if( EncryptionType == "aes-256" ) 229 | { 230 | RtcEnginePtr->setEncryptionMode( "aes-256-xts" ); 231 | } 232 | else 233 | { 234 | RtcEnginePtr->setEncryptionMode( "aes-128-xts" ); 235 | } 236 | 237 | nRet = RtcEnginePtr->setEncryptionSecret( TCHAR_TO_ANSI( *EncryptionKey ) ); 238 | if( nRet < 0 ) 239 | { 240 | UE_LOG( LogTemp, Warning, TEXT( "setEncryptionSecret : %d" ), nRet ) 241 | } 242 | } 243 | 244 | nRet = RtcEnginePtr->setChannelProfile( agora::rtc::CHANNEL_PROFILE_COMMUNICATION ); 245 | if( nRet < 0 ) 246 | { 247 | UE_LOG( LogTemp, Warning, TEXT( "setChannelProfile : %d" ), nRet ) 248 | } 249 | //"demoChannel1"; 250 | std::uint32_t nUID = 0; 251 | nRet = RtcEnginePtr->joinChannel( NULL, TCHAR_TO_ANSI( *ChannelName ), NULL, nUID ); 252 | if( nRet < 0 ) 253 | { 254 | UE_LOG( LogTemp, Warning, TEXT( "joinChannel ret: %d" ), nRet ); 255 | } 256 | } 257 | 258 | void VideoCall::StopCall() 259 | { 260 | if( MediaEnginePtr ) 261 | { 262 | MediaEnginePtr->registerVideoFrameObserver( nullptr ); 263 | } 264 | 265 | if( !RtcEnginePtr ) 266 | { 267 | return; 268 | } 269 | auto ConnectionState = RtcEnginePtr->getConnectionState(); 270 | if( agora::rtc::CONNECTION_STATE_DISCONNECTED != ConnectionState ) 271 | { 272 | int nRet = RtcEnginePtr->leaveChannel(); 273 | if( nRet < 0 ) 274 | { 275 | UE_LOG( LogTemp, Warning, TEXT( "leaveChannel ret: %d" ), nRet ); 276 | } 277 | } 278 | } 279 | 280 | bool VideoCall::MuteLocalAudio( bool bMuted ) 281 | { 282 | if( !RtcEnginePtr ) 283 | { 284 | return false; 285 | } 286 | int ret = RtcEnginePtr->muteLocalAudioStream( bMuted ); 287 | if( ret == 0 ) 288 | bLocalAudioMuted = bMuted; 289 | 290 | return ret == 0 ? true : false; 291 | } 292 | 293 | bool VideoCall::IsLocalAudioMuted() 294 | { 295 | return bLocalAudioMuted; 296 | } 297 | 298 | bool VideoCall::MuteLocalVideo( bool bMuted ) 299 | { 300 | if( !RtcEnginePtr ) 301 | { 302 | return false; 303 | } 304 | int ret = RtcEnginePtr->muteLocalVideoStream( bMuted ); 305 | if( ret == 0 ) 306 | bLocalVideoMuted = bMuted; 307 | 308 | return ret == 0 ? true : false; 309 | } 310 | 311 | bool VideoCall::IsLocalVideoMuted() 312 | { 313 | return bLocalVideoMuted; 314 | } 315 | 316 | bool VideoCall::EnableVideo( bool bEnable ) 317 | { 318 | if( !RtcEnginePtr ) 319 | { 320 | return false; 321 | } 322 | int nRet = 0; 323 | if( bEnable ) 324 | nRet = RtcEnginePtr->enableVideo(); 325 | else 326 | nRet = RtcEnginePtr->disableVideo(); 327 | return nRet == 0 ? true : false; 328 | } 329 | 330 | TUniquePtr VideoCall::GetCameraManager() 331 | { 332 | return CameraManager::Create( RtcEnginePtr, MediaEnginePtr ); 333 | } 334 | 335 | TUniquePtr VideoCall::GetAudioDeviceManager() 336 | { 337 | return AudioInOutDeviceManager::Create( RtcEnginePtr ); 338 | } 339 | 340 | bool VideoCall::EnableEchoTest( bool bEnable ) 341 | { 342 | if( !RtcEnginePtr ) 343 | { 344 | return false; 345 | } 346 | int nRet = 0; 347 | if( bEnable ) 348 | nRet = RtcEnginePtr->startEchoTest( 10 ); 349 | else 350 | nRet = RtcEnginePtr->stopEchoTest(); 351 | 352 | return nRet == 0 ? true : false; 353 | } -------------------------------------------------------------------------------- /Source/AgoraVideoCall/UI/DeviceTestWidget.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Agora.io. All rights reserved. 2 | 3 | #include "DeviceTestWidget.h" 4 | #include "Blueprint/WidgetTree.h" 5 | 6 | #include "Misc/Paths.h" 7 | #include "HAL/FileManager.h" 8 | #include "HAL/PlatformFileManager.h" 9 | 10 | #include "VideoViewWidget.h" 11 | #include "VideoCallPlayerController.h" 12 | 13 | #include "AudioInOutDeviceManager.h" 14 | 15 | #include 16 | 17 | 18 | UDeviceTestWidget::UDeviceTestWidget(const FObjectInitializer& objectInitializer) 19 | : Super(objectInitializer) 20 | { 21 | } 22 | 23 | void UDeviceTestWidget::NativeConstruct() 24 | { 25 | Super::NativeConstruct(); 26 | 27 | // buttons 28 | if (MicrophoneTestButton) 29 | { 30 | UTextBlock* TestTextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); 31 | TestTextBlock->SetText(FText::FromString("Test")); 32 | MicrophoneTestButton->AddChild(TestTextBlock); 33 | MicrophoneTestButton->OnClicked.AddDynamic(this, &UDeviceTestWidget::OnMicrophoneTest); 34 | } 35 | 36 | if (SpeakersTestButton) 37 | { 38 | UTextBlock* TestTextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); 39 | TestTextBlock->SetText(FText::FromString("Test")); 40 | SpeakersTestButton->AddChild(TestTextBlock); 41 | SpeakersTestButton->OnClicked.AddDynamic(this, &UDeviceTestWidget::OnSpeakersTest); 42 | } 43 | 44 | if (CameraTestButton) 45 | { 46 | UTextBlock* TestTextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); 47 | TestTextBlock->SetText(FText::FromString("Test")); 48 | CameraTestButton->AddChild(TestTextBlock); 49 | CameraTestButton->OnClicked.AddDynamic(this, &UDeviceTestWidget::OnCameraTest); 50 | } 51 | 52 | if (EchoTestButton) 53 | { 54 | UTextBlock* EchoTestButtonTextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); 55 | EchoTestButtonTextBlock->SetText(FText::FromString("EchoTest")); 56 | EchoTestButton->AddChild(EchoTestButtonTextBlock); 57 | EchoTestButton->OnClicked.AddDynamic(this, &UDeviceTestWidget::OnEchoTest); 58 | } 59 | 60 | if (CancelButton) 61 | { 62 | UTextBlock* CancelButtonTextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); 63 | CancelButtonTextBlock->SetText(FText::FromString("Cancel")); 64 | CancelButton->AddChild(CancelButtonTextBlock); 65 | CancelButton->OnClicked.AddDynamic(this, &UDeviceTestWidget::OnCancel); 66 | } 67 | 68 | if (ConfirmButton) 69 | { 70 | UTextBlock* ConfirmButtonTextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); 71 | ConfirmButtonTextBlock->SetText(FText::FromString("Confirm")); 72 | ConfirmButton->AddChild(ConfirmButtonTextBlock); 73 | ConfirmButton->OnClicked.AddDynamic(this, &UDeviceTestWidget::OnConfirm); 74 | } 75 | 76 | // text blocks 77 | if (MicrophoneTextBlock) 78 | MicrophoneTextBlock->SetText(FText::FromString("Input Device:")); 79 | 80 | if (SpeakersTextBlock) 81 | SpeakersTextBlock->SetText(FText::FromString("Output Device:")); 82 | 83 | if (CameraTextBlock) 84 | CameraTextBlock->SetText(FText::FromString("Camera:")); 85 | 86 | if (MicrophoneVolumeTextBlock) 87 | MicrophoneVolumeTextBlock->SetText(FText::FromString("Volume")); 88 | 89 | if (SpeakersVolumeTextBlock) 90 | SpeakersVolumeTextBlock->SetText(FText::FromString("Volume")); 91 | 92 | if (MicrophoneVolumeSlider) 93 | { 94 | FScriptDelegate Delegate; 95 | Delegate.BindUFunction(this, FName("OnMicrophoneVolumeChanged")); 96 | //MicrophoneVolumeSlider->OnValueChanged.Add(Delegate); 97 | MicrophoneVolumeSlider->OnMouseCaptureEnd.Add(Delegate); 98 | } 99 | 100 | if (SpeakersVolumeSlider) 101 | { 102 | FScriptDelegate Delegate; 103 | Delegate.BindUFunction(this, FName("OnSpeakersVolumeChanged")); 104 | SpeakersVolumeSlider->OnMouseCaptureEnd.Add(Delegate); 105 | } 106 | 107 | if (MicrophoneComboBox) 108 | { 109 | FScriptDelegate Delegate; 110 | Delegate.BindUFunction(this, FName("OnMicrophoneComboBoxSelectionChanged")); 111 | MicrophoneComboBox->OnSelectionChanged.Add(Delegate); 112 | } 113 | if (SpeakersComboBox) 114 | { 115 | FScriptDelegate Delegate; 116 | Delegate.BindUFunction(this, FName("OnSpeakersComboBoxSelectionChanged")); 117 | SpeakersComboBox->OnSelectionChanged.Add(Delegate); 118 | } 119 | if (CameraComboBox) 120 | { 121 | FScriptDelegate Delegate; 122 | Delegate.BindUFunction(this, FName("OnCameraComboBoxSelectionChanged")); 123 | CameraComboBox->OnSelectionChanged.Add(Delegate); 124 | } 125 | } 126 | 127 | void UDeviceTestWidget::NativeDestruct() 128 | { 129 | Super::NativeDestruct(); 130 | 131 | } 132 | 133 | void UDeviceTestWidget::NativeTick(const FGeometry& MyGeometry, float DeltaTime) 134 | { 135 | float AspectRatio = 0; 136 | if (VideoHeight != 0) 137 | { 138 | AspectRatio = VideoWidth / (float)VideoHeight; 139 | TestVideoViewWidgetSizeBox->SetMinAspectRatio(AspectRatio); 140 | TestVideoViewWidgetSizeBox->SetMinDesiredWidth(VideoWidth); 141 | TestVideoViewWidgetSizeBox->SetMinDesiredHeight(VideoHeight); 142 | } 143 | } 144 | 145 | void UDeviceTestWidget::OnMicrophoneVolumeChanged() 146 | { 147 | if (AudioDeviceManagerPtr) 148 | { 149 | float Value = MicrophoneVolumeSlider->GetValue(); 150 | int Volume = static_cast(Value); 151 | AudioDeviceManagerPtr->SetRecordingDeviceVolume(Volume); 152 | } 153 | } 154 | 155 | void UDeviceTestWidget::OnSpeakersVolumeChanged() 156 | { 157 | if (AudioDeviceManagerPtr) 158 | { 159 | float Value = SpeakersVolumeSlider->GetValue(); 160 | int Volume = static_cast(Value); 161 | AudioDeviceManagerPtr->SetPlaybackDeviceVolume(Volume); 162 | } 163 | } 164 | 165 | void UDeviceTestWidget::OnMicrophoneComboBoxSelectionChanged() 166 | { 167 | if (!AudioDeviceManagerPtr) 168 | { 169 | return; 170 | } 171 | FString Option = MicrophoneComboBox->GetSelectedOption(); 172 | FString DeviceID = AudioDeviceManagerPtr->GetRecordingDeviceID(Option); 173 | AudioDeviceManagerPtr->SetCurrRecordingDevice(DeviceID); 174 | } 175 | 176 | void UDeviceTestWidget::OnSpeakersComboBoxSelectionChanged() 177 | { 178 | if (!AudioDeviceManagerPtr) 179 | { 180 | return; 181 | } 182 | FString Option = SpeakersComboBox->GetSelectedOption(); 183 | FString DeviceID = AudioDeviceManagerPtr->GetPlaybackDeviceID(Option); 184 | AudioDeviceManagerPtr->SetCurrPlaybackDevice(DeviceID); 185 | } 186 | 187 | void UDeviceTestWidget::OnCameraComboBoxSelectionChanged() 188 | { 189 | if (!CameraManagerPtr) 190 | { 191 | return; 192 | } 193 | FString Option = CameraComboBox->GetSelectedOption(); 194 | FString DeviceID = CameraManagerPtr->GetDeviceID(Option); 195 | CameraManagerPtr->SetCurrDevice(DeviceID); 196 | } 197 | 198 | void UDeviceTestWidget::OnMicrophoneTest() 199 | { 200 | if (!AudioDeviceManagerPtr) 201 | { 202 | return; 203 | } 204 | if (0 == MicrophoneComboBox->GetOptionCount()) 205 | { 206 | return; 207 | } 208 | if (!IsTestingRecording) 209 | { 210 | StartMicrophoneTest(); 211 | } 212 | else 213 | { 214 | StopMicrophoneTest(); 215 | } 216 | } 217 | 218 | void UDeviceTestWidget::StartMicrophoneTest() 219 | { 220 | if (!AudioDeviceManagerPtr) 221 | { 222 | return; 223 | } 224 | if (!IsTestingRecording) 225 | { 226 | AudioDeviceManagerPtr->TestRecordingDevice(); 227 | IsTestingRecording = true; 228 | 229 | auto Child = MicrophoneTestButton->GetChildAt(0); 230 | UTextBlock* TestTextBlock = Cast(Child); 231 | if (TestTextBlock) 232 | { 233 | TestTextBlock->SetText(FText::FromString("Stop")); 234 | } 235 | } 236 | } 237 | 238 | void UDeviceTestWidget::StopMicrophoneTest() 239 | { 240 | if (!AudioDeviceManagerPtr) 241 | { 242 | return; 243 | } 244 | if (IsTestingRecording) 245 | { 246 | AudioDeviceManagerPtr->StopRecordingDeviceTest(); 247 | IsTestingRecording = false; 248 | 249 | auto Child = MicrophoneTestButton->GetChildAt(0); 250 | UTextBlock* TestTextBlock = Cast(Child); 251 | if (TestTextBlock) 252 | { 253 | TestTextBlock->SetText(FText::FromString("Test")); 254 | } 255 | } 256 | } 257 | 258 | void UDeviceTestWidget::OnSpeakersTest() 259 | { 260 | if (!AudioDeviceManagerPtr) 261 | { 262 | return; 263 | } 264 | if (0 == SpeakersComboBox->GetOptionCount()) 265 | { 266 | return; 267 | } 268 | if(!IsTestingPlayback) 269 | { 270 | StartSpeakersTest(); 271 | } 272 | else 273 | { 274 | StopSpeakersTest(); 275 | } 276 | } 277 | 278 | void UDeviceTestWidget::StartSpeakersTest() 279 | { 280 | if (!AudioDeviceManagerPtr) 281 | { 282 | return; 283 | } 284 | if (!IsTestingPlayback) 285 | { 286 | FString AudioFile = CreateAudioFileForSpeakersTest(); 287 | if(AudioFile.IsEmpty()) 288 | { 289 | return; 290 | } 291 | 292 | AudioDeviceManagerPtr->TestPlaybackDevice(AudioFile); 293 | IsTestingPlayback = true; 294 | 295 | auto Child = SpeakersTestButton->GetChildAt(0); 296 | UTextBlock* TestTextBlock = Cast(Child); 297 | if (TestTextBlock) 298 | { 299 | TestTextBlock->SetText(FText::FromString("Stop")); 300 | } 301 | } 302 | } 303 | 304 | void UDeviceTestWidget::StopSpeakersTest() 305 | { 306 | if (!AudioDeviceManagerPtr) 307 | { 308 | return; 309 | } 310 | if (IsTestingPlayback) 311 | { 312 | AudioDeviceManagerPtr->StopPlaybackDeviceTest(); 313 | IsTestingPlayback = false; 314 | 315 | auto Child = SpeakersTestButton->GetChildAt(0); 316 | UTextBlock* TestTextBlock = Cast(Child); 317 | if (TestTextBlock) 318 | { 319 | TestTextBlock->SetText(FText::FromString("Test")); 320 | } 321 | } 322 | } 323 | 324 | void UDeviceTestWidget::OnCameraTest() 325 | { 326 | if (!CameraManagerPtr) 327 | { 328 | return; 329 | } 330 | if (0 == CameraComboBox->GetOptionCount()) 331 | { 332 | return; 333 | } 334 | if (!CameraManagerPtr->IsTesting()) 335 | { 336 | StartCameraTest(); 337 | } 338 | else 339 | { 340 | StopCameraTest(); 341 | } 342 | } 343 | 344 | void UDeviceTestWidget::StartCameraTest() 345 | { 346 | if (!CameraManagerPtr) 347 | { 348 | return; 349 | } 350 | auto OnFrameCallback = [this]( 351 | std::uint8_t* Buffer, 352 | std::uint32_t Width, 353 | std::uint32_t Height, 354 | std::uint32_t Size) 355 | { 356 | if (!TestVideoViewWidget) 357 | { 358 | return; 359 | } 360 | VideoWidth = Width; 361 | VideoHeight = Height; 362 | 363 | TestVideoViewWidget->UpdateBuffer(Buffer, Width, Height, Size); 364 | }; 365 | CameraManagerPtr->TestCameraDevice(OnFrameCallback); 366 | 367 | auto Child = CameraTestButton->GetChildAt(0); 368 | UTextBlock* TestTextBlock = Cast(Child); 369 | if (TestTextBlock) 370 | { 371 | TestTextBlock->SetText(FText::FromString("Stop")); 372 | } 373 | } 374 | 375 | void UDeviceTestWidget::StopCameraTest() 376 | { 377 | if (!CameraManagerPtr) 378 | { 379 | return; 380 | } 381 | CameraManagerPtr->StopCameraDeviceTest(); 382 | 383 | if (TestVideoViewWidget) 384 | { 385 | TestVideoViewWidget->ResetBuffer(); 386 | } 387 | auto Child = CameraTestButton->GetChildAt(0); 388 | UTextBlock* TestTextBlock = Cast(Child); 389 | if (TestTextBlock) 390 | { 391 | TestTextBlock->SetText(FText::FromString("Test")); 392 | } 393 | } 394 | 395 | void UDeviceTestWidget::OnEchoTest() 396 | { 397 | if (!IsEchoTesting) 398 | { 399 | StartEchoTest(); 400 | } 401 | else 402 | { 403 | StopEchoTest(); 404 | } 405 | } 406 | 407 | void UDeviceTestWidget::StartEchoTest() 408 | { 409 | if (!VideoCallPtr) 410 | { 411 | return; 412 | } 413 | if (!IsEchoTesting) 414 | { 415 | VideoCallPtr->EnableEchoTest(true); 416 | IsEchoTesting = true; 417 | 418 | auto Child = EchoTestButton->GetChildAt(0); 419 | UTextBlock* TestTextBlock = Cast(Child); 420 | if (TestTextBlock) 421 | { 422 | TestTextBlock->SetText(FText::FromString("StopEcho")); 423 | } 424 | } 425 | } 426 | 427 | void UDeviceTestWidget::StopEchoTest() 428 | { 429 | if (!VideoCallPtr) 430 | { 431 | return; 432 | } 433 | if (IsEchoTesting) 434 | { 435 | VideoCallPtr->EnableEchoTest(false); 436 | IsEchoTesting = false; 437 | 438 | auto Child = EchoTestButton->GetChildAt(0); 439 | UTextBlock* TestTextBlock = Cast(Child); 440 | if (TestTextBlock) 441 | { 442 | TestTextBlock->SetText(FText::FromString("EchoTest")); 443 | } 444 | } 445 | } 446 | 447 | void UDeviceTestWidget::OnCancel() 448 | { 449 | if (AudioDeviceManagerPtr) 450 | { 451 | //reset to init values 452 | AudioDeviceManagerPtr->SetRecordingDeviceVolume(MicrophoneVolume); 453 | AudioDeviceManagerPtr->SetPlaybackDeviceVolume(SpeakersVolume); 454 | 455 | } 456 | 457 | FString Option = MicrophoneComboBox->GetSelectedOption(); 458 | if(DefaultRecordingDeviceName != Option) 459 | { 460 | FString DeviceID = AudioDeviceManagerPtr->GetRecordingDeviceID(DefaultRecordingDeviceName); 461 | AudioDeviceManagerPtr->SetCurrRecordingDevice(DeviceID); 462 | } 463 | Option = SpeakersComboBox->GetSelectedOption(); 464 | if (DefaultPlaybackDeviceName != Option) 465 | { 466 | FString DeviceID = AudioDeviceManagerPtr->GetPlaybackDeviceID(DefaultPlaybackDeviceName); 467 | AudioDeviceManagerPtr->SetCurrPlaybackDevice(DeviceID); 468 | } 469 | Option = CameraComboBox->GetSelectedOption(); 470 | if (DefaultVideoDeviceName != Option) 471 | { 472 | FString DeviceID = CameraManagerPtr->GetDeviceID(DefaultVideoDeviceName); 473 | CameraManagerPtr->SetCurrDevice(DeviceID); 474 | } 475 | 476 | OnExit(); 477 | } 478 | 479 | void UDeviceTestWidget::OnConfirm() 480 | { 481 | OnExit(); 482 | } 483 | 484 | void UDeviceTestWidget::OnExit() 485 | { 486 | if (CameraManagerPtr && CameraManagerPtr->IsTesting()) 487 | { 488 | StopCameraTest(); 489 | } 490 | 491 | StopMicrophoneTest(); 492 | StopSpeakersTest(); 493 | 494 | StopEchoTest(); 495 | 496 | CameraManagerPtr.Reset(); 497 | AudioDeviceManagerPtr.Reset(); 498 | 499 | CameraComboBox->ClearOptions(); 500 | MicrophoneComboBox->ClearOptions(); 501 | SpeakersComboBox->ClearOptions(); 502 | 503 | if (PlayerController) 504 | { 505 | SetVisibility(ESlateVisibility::Collapsed); 506 | PlayerController->SwitchOnEnterChannelWidget(std::move(VideoCallPtr)); 507 | } 508 | } 509 | 510 | void UDeviceTestWidget::SetVideoCallPlayerController(AVideoCallPlayerController* VideoCallPlayerController) 511 | { 512 | PlayerController = VideoCallPlayerController; 513 | } 514 | 515 | void UDeviceTestWidget::SetVideoCall(TUniquePtr PassedVideoCallPtr) 516 | { 517 | VideoCallPtr = std::move(PassedVideoCallPtr); 518 | 519 | if (!CameraManagerPtr) 520 | { 521 | CameraManagerPtr = VideoCallPtr->GetCameraManager(); 522 | } 523 | if (CameraManagerPtr) 524 | { 525 | auto ActiveVideoDeviceName = CameraManagerPtr->GetCurrDeviceName(); 526 | if (!ActiveVideoDeviceName.IsEmpty()) 527 | { 528 | CameraComboBox->AddOption(ActiveVideoDeviceName); 529 | CameraComboBox->SetSelectedIndex(0); 530 | auto VideoDevices = CameraManagerPtr->GetVideoDevices(); 531 | for (const auto& VideoDevice : VideoDevices) 532 | { 533 | if (VideoDevice != ActiveVideoDeviceName) 534 | { 535 | CameraComboBox->AddOption(VideoDevice); 536 | } 537 | } 538 | DefaultVideoDeviceName = ActiveVideoDeviceName; 539 | } 540 | } 541 | 542 | 543 | if (!AudioDeviceManagerPtr) 544 | { 545 | AudioDeviceManagerPtr = VideoCallPtr->GetAudioDeviceManager(); 546 | } 547 | if (AudioDeviceManagerPtr) 548 | { 549 | auto ActiveRecordingDeviceName = AudioDeviceManagerPtr->GetCurrRecordingDeviceName(); 550 | if (!ActiveRecordingDeviceName.IsEmpty()) 551 | { 552 | MicrophoneComboBox->AddOption(ActiveRecordingDeviceName); 553 | MicrophoneComboBox->SetSelectedIndex(0); 554 | auto RecordingDevices = AudioDeviceManagerPtr->GetRecordingDevices(); 555 | for (const auto& RecordingDevice : RecordingDevices) 556 | { 557 | if (RecordingDevice != ActiveRecordingDeviceName) 558 | { 559 | MicrophoneComboBox->AddOption(RecordingDevice); 560 | } 561 | } 562 | DefaultRecordingDeviceName = ActiveRecordingDeviceName; 563 | } 564 | 565 | auto ActivePlaybackDeviceName = AudioDeviceManagerPtr->GetCurrPlaybackDeviceName(); 566 | if (!ActivePlaybackDeviceName.IsEmpty()) 567 | { 568 | SpeakersComboBox->AddOption(ActivePlaybackDeviceName); 569 | SpeakersComboBox->SetSelectedIndex(0); 570 | auto PlaybackDevices = AudioDeviceManagerPtr->GetPlaybackDevices(); 571 | for (const auto& PlaybackDevice : PlaybackDevices) 572 | { 573 | if (PlaybackDevice != ActivePlaybackDeviceName) 574 | { 575 | SpeakersComboBox->AddOption(PlaybackDevice); 576 | } 577 | } 578 | DefaultPlaybackDeviceName = ActivePlaybackDeviceName; 579 | 580 | SpeakersVolume = AudioDeviceManagerPtr->GetPlaybackDeviceVolume(); 581 | SpeakersVolumeSlider->SetValue(SpeakersVolume); 582 | 583 | MicrophoneVolume = AudioDeviceManagerPtr->GetRecordingDeviceVolume(); 584 | MicrophoneVolumeSlider->SetValue(MicrophoneVolume); 585 | } 586 | } 587 | } 588 | 589 | //helper function 590 | namespace little_endian_io 591 | { 592 | template 593 | std::ostream& write_word(std::ostream& outs, Word value, unsigned size = sizeof(Word)) 594 | { 595 | for (; size; --size, value >>= 8) 596 | outs.put(static_cast (value & 0xFF)); 597 | return outs; 598 | } 599 | } 600 | 601 | FString UDeviceTestWidget::CreateAudioFileForSpeakersTest() 602 | { 603 | FString AudioContentDir; 604 | #if PLATFORM_WINDOWS 605 | const FString& RelPath = FPaths::ProjectDir(); 606 | const FString& FullPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelPath); 607 | 608 | IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); 609 | FString ContentDir = FPaths::Combine(*FullPath, TEXT("Content/")); 610 | if (!PlatformFile.DirectoryExists(*ContentDir)) 611 | { 612 | bool bRes = PlatformFile.CreateDirectory(*ContentDir); 613 | if(!bRes) 614 | { 615 | return ""; 616 | } 617 | } 618 | AudioContentDir = FPaths::Combine(*ContentDir, TEXT("Audio/")); 619 | if (!PlatformFile.DirectoryExists(*AudioContentDir)) 620 | { 621 | bool bRes = PlatformFile.CreateDirectory(*AudioContentDir); 622 | if(!bRes) 623 | { 624 | return ""; 625 | } 626 | } 627 | #endif 628 | 629 | #if PLATFORM_MAC 630 | AudioContentDir = "/Library/Caches/"; 631 | #endif 632 | FString AudioFile = FPaths::Combine(*AudioContentDir, TEXT("ID_TEST_AUDIO.wav")); 633 | { 634 | std::ifstream f(TCHAR_TO_ANSI(*AudioFile)); 635 | if (f.good()) 636 | { 637 | return AudioFile; 638 | } 639 | } 640 | 641 | std::ofstream f(TCHAR_TO_ANSI(*AudioFile), std::ios::binary); 642 | 643 | using namespace little_endian_io; 644 | // Write the file headers 645 | f << "RIFF----WAVEfmt "; // (chunk size to be filled in later) 646 | write_word(f, 16, 4); // no extension data 647 | write_word(f, 1, 2); // PCM - integer samples 648 | write_word(f, 2, 2); // two channels (stereo file) 649 | write_word(f, 44100, 4); // samples per second (Hz) 650 | write_word(f, 176400, 4); // (Sample Rate * BitsPerSample * Channels) / 8 651 | write_word(f, 4, 2); // data block size (size of two integer samples, one for each channel, in bytes) 652 | write_word(f, 16, 2); // number of bits per sample (use a multiple of 8) 653 | 654 | // Write the data chunk header 655 | size_t data_chunk_pos = f.tellp(); 656 | f << "data----"; // (chunk size to be filled in later) 657 | 658 | // Write the audio samples 659 | // (We'll generate a single C4 note with a sine wave, fading from left to right) 660 | constexpr double two_pi = 6.283185307179586476925286766559; 661 | constexpr double max_amplitude = 32760; // "volume" 662 | 663 | double hz = 44100; // samples per second 664 | double frequency = 261.626; // middle C 665 | double seconds = 2.5; // time 666 | 667 | int N = hz * seconds; // total number of samples 668 | for (int n = 0; n < N; n++) 669 | { 670 | double amplitude = (double)n / N * max_amplitude; 671 | double value = sin((two_pi * n * frequency) / hz); 672 | write_word(f, (int)(amplitude * value), 2); 673 | write_word(f, (int)((max_amplitude - amplitude) * value), 2); 674 | } 675 | 676 | // (We'll need the final file size to fix the chunk sizes above) 677 | size_t file_length = f.tellp(); 678 | 679 | // Fix the data chunk header to contain the data size 680 | f.seekp(data_chunk_pos + 4); 681 | write_word(f, file_length - data_chunk_pos + 8); 682 | 683 | // Fix the file header to contain the proper RIFF chunk size, which is (file size - 8) bytes 684 | f.seekp(0 + 4); 685 | write_word(f, file_length - 8, 4); 686 | 687 | f.close(); 688 | 689 | return AudioFile; 690 | } 691 | -------------------------------------------------------------------------------- /GUIDE.md: -------------------------------------------------------------------------------- 1 | # Steps to Create the Sample 2 | 3 | ## Prerequisites 4 | 5 | - Unreal Engine 4.23 or later 6 | - Microsoft Visual Studio or Xcode( version required by Unreal Engine ) 7 | - A Windows device running Windows 7 or later or Mac device 8 | - A valid Agora account. (Sign up for free) 9 | 10 | #### Open the specified ports in Firewall Requirements if your network has a firewall. 11 | 12 | Firewall Requirements 13 | 14 | 15 | ## New Project 16 | 17 | In this section, we will create an Unreal Engine project and integrate the plugin into the project. 18 | 19 | - [Create a project](#create-a-project) 20 | - [Installation](#installation) 21 | - [Create a New Level](#create-a-new-level) 22 | 23 | ### Create a project 24 | 25 | Now, let's build a project from scratch. Skip to **Installation** if a project already exists. 26 | 27 | 1. Open **Unreal Engine Editor** and click **New project**. 28 | 2. On the **New Project** panel, choose C++ as the project type, input the project name, choose the project location, and click **Create Project**. 29 | 30 | ![New project view](GuideImages/NewProject_View.png) 31 | 32 | 3. Make sure to uncomment the PrivateDependencyModuleNames line in [your_project]/Source/[project_name]/[project_name].Build.cs file. Unreal comments this out by default, and causes a compile error if left commented out. 33 | 34 | ```cs 35 | // Uncomment if you are using Slate UI 36 | PrivateDependencyModuleNames.AddRange(new string[] { "UMG", "Slate", "SlateCore" }); 37 | ``` 38 | 39 | ### Installation 40 | 41 | Follow these steps to integrate the Agora Plugin into your project. 42 | 43 | 1. Copy the plugin to [your_project]/Plugins 44 | 2. Add plugin dependency into [your_project]/Source/[project_name]/[project_name].Build.cs file, Private Dependencies section 45 | `PrivateDependencyModuleNames.AddRange(new string[] { "AgoraPlugin" });` 46 | 3. Restart **Unreal Engine** (if it is running). 47 | 4. Go to **Edit->Plugins**. Find category **Project->Other** and make sure plugin is enabled. 48 | 49 | ![Enable Plugin](GuideImages/2020-03-12_13-26-59.png) 50 | 51 | ### Create a New Level 52 | 53 | Next we will create a new Level to build our game environment there. 54 | There are several different ways in which you can create a new Level, we will use the File Menu method, which lists level selection options. 55 | 56 | Inside the Unreal Editor, click the File Menu option then select New Level... 57 | 58 | ![Create FileMenu_Windows](GuideImages/FileMenu_Windows.png) 59 | 60 | This will open the New Level dialog window: 61 | 62 | ![Create NewLevel_Windows](GuideImages/NewLevel_Windows.png) 63 | 64 | Click the Empty Level to select it. 65 | 66 | Save it to the prefered folder, for example like /Content/Levels/VideoCallLevel.umap 67 | 68 | 69 | ## Create Core Classes 70 | 71 | Create classes that will handle communication with the Agora SDK. 72 | 73 | - [Create VideoFrameObserver](#create-videoframeobserver) 74 | - [Create VideoCall C++ Class](#create-videocall-c-class) 75 | 76 | ### Create VideoFrameObserver 77 | 78 | VideoFrameObserver implements agora::media::IVideoFrameObserver. 79 | The methods in the VideoFrameObserver class manage video frames callbacks. 80 | Should be registered in agora::media::IMediaEngine using registerVideoFrameObserver function. 81 | 82 | - [Create VideoFrameObserver class interface](#create-videoframeobserver-class-interface) 83 | - [Override the onCaptureVideoFrame/onRenderVideoFrame Methods](#override-the-oncapturevideoframeonrendervideoframe-methods) 84 | - [Add the setOnCaptureVideoFrameCallback/setOnRenderVideoFrameCallback Methods](#add-the-setoncapturevideoframecallbacksetonrendervideoframecallback-methods) 85 | 86 | In Unreal Engine Editor clic **File->Add New C++ Class**. 87 | 88 | ![Add New C++ Class](GuideImages/New_C_Class.png) 89 | 90 | Select **None** as a parent class and click **Next**, 91 | 92 | ![Select Parent Class](GuideImages/UE4Editor_2020-03-11_11-35-02.png) 93 | 94 | Name class **VideoFrameObserver** and click **Create Class** 95 | 96 | ![Name Class](GuideImages/UE4Editor_2020-03-11_12-21-56.png) 97 | 98 | ##### Create VideoFrameObserver class interface 99 | 100 | Open **VideoFrameObserver.h** file and add such interface: 101 | 102 | ```cpp 103 | //VideoFrameObserver.h 104 | 105 | #include "CoreMinimal.h" 106 | 107 | #include 108 | 109 | #include "AgoraMediaEngine.h" 110 | 111 | class AGORAVIDEOCALL_API VideoFrameObserver : public agora::media::IVideoFrameObserver 112 | { 113 | public: 114 | virtual ~VideoFrameObserver() = default; 115 | public: 116 | bool onCaptureVideoFrame(VideoFrame& videoFrame) override; 117 | 118 | bool onRenderVideoFrame(unsigned int uid, VideoFrame& videoFrame) override; 119 | 120 | void setOnCaptureVideoFrameCallback( 121 | std::function callback); 122 | 123 | void setOnRenderVideoFrameCallback( 124 | std::function callback); 125 | 126 | virtual VIDEO_FRAME_TYPE getVideoFormatPreference() override { return FRAME_TYPE_RGBA; } 127 | 128 | private: 129 | 130 | std::function OnCaptureVideoFrame; 131 | std::function OnRenderVideoFrame; 132 | }; 133 | ``` 134 | 135 | Note that `AGORAVIDEOCALL_API` is a Project dependent define. Instead of this use your own define generated by Unreal. 136 | 137 | ##### Override the onCaptureVideoFrame/onRenderVideoFrame Methods 138 | 139 | Function onCaptureVideoFrame retrieves the camera captured image, converts to ARGB format and triggers OnCaptureVideoFrame callback. 140 | Function onRenderVideoFrame converts received image of the specified user to ARGB format and triggers OnRenderVideoFrame callback. 141 | 142 | ```cpp 143 | //VideoFrameObserver.cpp 144 | 145 | bool VideoFrameObserver::onCaptureVideoFrame(VideoFrame& Frame) 146 | { 147 | const auto BufferSize = Frame.yStride*Frame.height; 148 | 149 | if (OnCaptureVideoFrame) 150 | { 151 | OnCaptureVideoFrame( static_cast< uint8_t* >( Frame.yBuffer ), Frame.width, Frame.height, BufferSize ); 152 | } 153 | 154 | return true; 155 | } 156 | 157 | bool VideoFrameObserver::onRenderVideoFrame(unsigned int uid, VideoFrame& Frame) 158 | { 159 | const auto BufferSize = Frame.yStride*Frame.height; 160 | 161 | if (OnRenderVideoFrame) 162 | { 163 | OnRenderVideoFrame( static_cast(Frame.yBuffer), Frame.width, Frame.height, BufferSize ); 164 | } 165 | 166 | return true; 167 | } 168 | ``` 169 | 170 | ##### Add the setOnCaptureVideoFrameCallback/setOnRenderVideoFrameCallback Methods 171 | 172 | Set callbacks to retrieve the camera captured image/the received image of the remote user. 173 | 174 | ```cpp 175 | //VideoFrameObserver.cpp 176 | 177 | void VideoFrameObserver::setOnCaptureVideoFrameCallback( 178 | std::function Callback) 179 | { 180 | OnCaptureVideoFrame = Callback; 181 | } 182 | 183 | void VideoFrameObserver::setOnRenderVideoFrameCallback( 184 | std::function Callback) 185 | { 186 | OnRenderVideoFrame = Callback; 187 | } 188 | ``` 189 | 190 | ### Create VideoCall C++ Class 191 | 192 | The VideoCall class manages communication with the Agora SDK. 193 | 194 | - [Create Class Interface](#create-class-interface) 195 | - [Create Initializing Methods](#create-initializing-methods) 196 | - [Create Callbacks Methods](#create-backs-methods) 197 | - [Create Call Methods](#create-call-methods) 198 | - [Create Video Methods](#create-video-methods) 199 | - [Create Audio Methods](#create-audio-methods) 200 | 201 | #### Create Class Interface 202 | 203 | Return to Unreal Engine Editor and create new C++ class as you did in previous step, call it **VideoCall.h**. 204 | 205 | Go to **VideoCall.h** file and add such interface: 206 | 207 | ```cpp 208 | //VideoCall.h 209 | 210 | #pragma once 211 | 212 | #include "CoreMinimal.h" 213 | 214 | #include 215 | #include 216 | 217 | #include "AgoraRtcEngine.h" 218 | #include "AgoraMediaEngine.h" 219 | 220 | class VideoFrameObserver; 221 | 222 | class AGORAVIDEOCALL_API VideoCall 223 | { 224 | public: 225 | VideoCall(); 226 | ~VideoCall(); 227 | 228 | FString GetVersion() const; 229 | 230 | void RegisterOnLocalFrameCallback( 231 | std::function OnLocalFrameCallback); 232 | void RegisterOnRemoteFrameCallback( 233 | std::function OnRemoteFrameCallback); 234 | 235 | void StartCall( 236 | const FString& ChannelName, 237 | const FString& EncryptionKey, 238 | const FString& EncryptionType); 239 | 240 | void StopCall(); 241 | 242 | bool MuteLocalAudio(bool bMuted = true); 243 | bool IsLocalAudioMuted(); 244 | 245 | bool MuteLocalVideo(bool bMuted = true); 246 | bool IsLocalVideoMuted(); 247 | 248 | bool EnableVideo(bool bEnable = true); 249 | 250 | private: 251 | void InitAgora(); 252 | 253 | private: 254 | TSharedPtr RtcEnginePtr; 255 | TSharedPtr MediaEnginePtr; 256 | 257 | TUniquePtr VideoFrameObserverPtr; 258 | 259 | //callback 260 | //data, w, h, size 261 | std::function OnLocalFrameCallback; 262 | std::function OnRemoteFrameCallback; 263 | 264 | bool bLocalAudioMuted = false; 265 | bool bLocalVideoMuted = false; 266 | }; 267 | ``` 268 | 269 | #### Create Initializing Methods 270 | 271 | Go to **VideoCall.cpp** file and add required includes. 272 | 273 | ```cpp 274 | //VideoCall.cpp 275 | 276 | #include "AgoraVideoDeviceManager.h" 277 | #include "AgoraAudioDeviceManager.h" 278 | 279 | #include "MediaShaders.h" 280 | 281 | #include "VideoFrameObserver.h" 282 | ``` 283 | 284 | Create the engine using agora::rtc::ue4::AgoraRtcEngine::createAgoraRtcEngine() and initialize the RtcEnginePtr variable. 285 | Create a RtcEngineContext object and set the event handler and App ID properties ctx.eventHandler and ctx.appId. 286 | Initialize the engine with ctx using RtcEnginePtr->initialize(). 287 | Create a AgoraMediaEngine object and initialize the MediaEnginePtr variable. 288 | 289 | ```cpp 290 | //VideoCall.cpp 291 | 292 | VideoCall::VideoCall() 293 | { 294 | InitAgora(); 295 | } 296 | 297 | VideoCall::~VideoCall() 298 | { 299 | StopCall(); 300 | } 301 | 302 | void VideoCall::InitAgora() 303 | { 304 | RtcEnginePtr = TSharedPtr(agora::rtc::ue4::AgoraRtcEngine::createAgoraRtcEngine()); 305 | 306 | static agora::rtc::RtcEngineContext ctx; 307 | ctx.appId = "aab8b8f5a8cd4469a63042fcfafe7063"; 308 | ctx.eventHandler = new agora::rtc::IRtcEngineEventHandler(); 309 | 310 | int ret = RtcEnginePtr->initialize(ctx); 311 | if (ret < 0) 312 | { 313 | UE_LOG(LogTemp, Warning, TEXT("RtcEngine initialize ret: %d"), ret); 314 | } 315 | MediaEnginePtr = TSharedPtr(agora::media::ue4::AgoraMediaEngine::Create(RtcEnginePtr.Get())); 316 | } 317 | 318 | FString VideoCall::GetVersion() const 319 | { 320 | if (!RtcEnginePtr) 321 | { 322 | return ""; 323 | } 324 | int build = 0; 325 | const char* version = RtcEnginePtr->getVersion(&build); 326 | return FString(ANSI_TO_TCHAR(version)); 327 | } 328 | ``` 329 | 330 | #### Create Callbacks Methods 331 | 332 | Set callback function, to return local and remote frames 333 | 334 | ```cpp 335 | //VideoCall.cpp 336 | 337 | void VideoCall::RegisterOnLocalFrameCallback( 338 | std::function OnFrameCallback) 339 | { 340 | OnLocalFrameCallback = std::move(OnFrameCallback); 341 | } 342 | 343 | void VideoCall::RegisterOnRemoteFrameCallback( 344 | std::function OnFrameCallback) 345 | { 346 | OnRemoteFrameCallback = std::move(OnFrameCallback); 347 | } 348 | ``` 349 | 350 | #### Create Call Methods 351 | 352 | The methods in this section manage joining or leaving a channel 353 | 354 | ##### Add StartCall function 355 | 356 | Create VideoFrameObserver object, register the following callbacks according to your scenarios: 357 | 358 | -`OnLocalFrameCallback`: Occurs each time the SDK receives a video frame captured by the local camera. 359 | 360 | -`OnRemoteFrameCallback`: Occurs each time the SDK receives a video frame sent by the remote user. 361 | 362 | Then in function InitAgora register VideoFrameObserver object in MediaEngine object with `registerVideoFrameObserver` method. 363 | In case EncryptionType and EncryptionKey are not empty, set EncryptionMode and EncryptionSecret for RtcEngine, then set channel profile according to your needs and call joinChannel. 364 | 365 | ```cpp 366 | //VideoCall.cpp 367 | 368 | void VideoCall::StartCall( 369 | const FString& ChannelName, 370 | const FString& EncryptionKey, 371 | const FString& EncryptionType) 372 | { 373 | if (!RtcEnginePtr) 374 | { 375 | return; 376 | } 377 | if (MediaEnginePtr) 378 | { 379 | if (!VideoFrameObserverPtr) 380 | { 381 | VideoFrameObserverPtr = MakeUnique(); 382 | 383 | std::function OnCaptureVideoFrameCallback 384 | = [this](std::uint8_t* buffer, std::uint32_t width, std::uint32_t height, std::uint32_t size) 385 | { 386 | if (OnLocalFrameCallback) 387 | { 388 | OnLocalFrameCallback(buffer, width, height, size); 389 | } 390 | else { UE_LOG(LogTemp, Warning, TEXT("VideoCall OnLocalFrameCallback isn't set")); } 391 | }; 392 | VideoFrameObserverPtr->setOnCaptureVideoFrameCallback(std::move(OnCaptureVideoFrameCallback)); 393 | 394 | std::function OnRenderVideoFrameCallback 395 | = [this](std::uint8_t* buffer, std::uint32_t width, std::uint32_t height, std::uint32_t size) 396 | { 397 | if (OnRemoteFrameCallback) 398 | { 399 | OnRemoteFrameCallback(buffer, width, height, size); 400 | } 401 | else { UE_LOG(LogTemp, Warning, TEXT("VideoCall OnRemoteFrameCallback isn't set")); } 402 | }; 403 | VideoFrameObserverPtr->setOnRenderVideoFrameCallback(std::move(OnRenderVideoFrameCallback)); 404 | } 405 | 406 | MediaEnginePtr->registerVideoFrameObserver(VideoFrameObserverPtr.Get()); 407 | } 408 | 409 | int nRet = RtcEnginePtr->enableVideo(); 410 | if (nRet < 0) 411 | { 412 | UE_LOG(LogTemp, Warning, TEXT("enableVideo : %d"), nRet) 413 | } 414 | 415 | if (!EncryptionType.IsEmpty() && !EncryptionKey.IsEmpty()) 416 | { 417 | if (EncryptionType == "aes-256") 418 | { 419 | RtcEnginePtr->setEncryptionMode("aes-256-xts"); 420 | } 421 | else 422 | { 423 | RtcEnginePtr->setEncryptionMode("aes-128-xts"); 424 | } 425 | 426 | nRet = RtcEnginePtr->setEncryptionSecret(TCHAR_TO_ANSI(*EncryptionKey)); 427 | if (nRet < 0) 428 | { 429 | UE_LOG(LogTemp, Warning, TEXT("setEncryptionSecret : %d"), nRet) 430 | } 431 | } 432 | 433 | nRet = RtcEnginePtr->setChannelProfile(agora::rtc::CHANNEL_PROFILE_COMMUNICATION); 434 | if (nRet < 0) 435 | { 436 | UE_LOG(LogTemp, Warning, TEXT("setChannelProfile : %d"), nRet) 437 | } 438 | //"demoChannel1"; 439 | std::uint32_t nUID = 0; 440 | nRet = RtcEnginePtr->joinChannel(NULL, TCHAR_TO_ANSI(*ChannelName), NULL, nUID); 441 | if (nRet < 0) 442 | { 443 | UE_LOG(LogTemp, Warning, TEXT("joinChannel ret: %d"), nRet); 444 | } 445 | } 446 | ``` 447 | 448 | ##### Add StopCall function 449 | 450 | Call the `leaveChannel` method to leave the current call according to your scenario, for example, when the call ends, when you need to close the app, or when your app runs in the background. 451 | Call `registerVideoFrameObserver` with nullptr argument to cancel the registration of the VideoFrameObserver. 452 | 453 | ```cpp 454 | //VideoCall.cpp 455 | 456 | void VideoCall::StopCall() 457 | { 458 | if (!RtcEnginePtr) 459 | { 460 | return; 461 | } 462 | auto ConnectionState = RtcEnginePtr->getConnectionState(); 463 | if (agora::rtc::CONNECTION_STATE_DISCONNECTED != ConnectionState) 464 | { 465 | int nRet = RtcEnginePtr->leaveChannel(); 466 | if (nRet < 0) 467 | { 468 | UE_LOG(LogTemp, Warning, TEXT("leaveChannel ret: %d"), nRet); 469 | } 470 | if (MediaEnginePtr) 471 | { 472 | MediaEnginePtr->registerVideoFrameObserver(nullptr); 473 | } 474 | } 475 | } 476 | ``` 477 | 478 | #### Create Video Methods 479 | 480 | The methods in this section manage the video. 481 | 482 | ##### Add the EnableVideo() Method 483 | 484 | The `EnableVideo()` method enables the video for the sample application. 485 | Initialize `nRet` with a value for `0`. If `bEnable` is `true`, enable the video using `RtcEnginePtr->enableVideo()`. Otherwise, disable the video using `RtcEnginePtr->disableVideo()`. 486 | 487 | ```cpp 488 | //VideoCall.cpp 489 | 490 | bool VideoCall::EnableVideo(bool bEnable) 491 | { 492 | if (!RtcEnginePtr) 493 | { 494 | return false; 495 | } 496 | int nRet = 0; 497 | if (bEnable) 498 | nRet = RtcEnginePtr->enableVideo(); 499 | else 500 | nRet = RtcEnginePtr->disableVideo(); 501 | return nRet == 0 ? true : false; 502 | } 503 | ``` 504 | 505 | ##### Add the MuteLocalVideo() Method 506 | 507 | The `MuteLocalVideo()` method turns local video on or off. 508 | Ensure `RtcEnginePtr` is not `nullptr` before completing the remaining method actions. 509 | If the mute or unmute local video is successful, set `bLocalVideoMuted` to `bMuted`. 510 | 511 | ```cpp 512 | //VideoCall.cpp 513 | 514 | bool VideoCall::MuteLocalVideo(bool bMuted) 515 | { 516 | if (!RtcEnginePtr) 517 | { 518 | return false; 519 | } 520 | int ret = RtcEnginePtr->muteLocalVideoStream(bMuted); 521 | if (ret == 0) 522 | bLocalVideoMuted = bMuted; 523 | 524 | return ret == 0 ? true : false; 525 | } 526 | ``` 527 | 528 | ##### Add the IsLocalVideoMuted() Method 529 | 530 | The `IsLocalVideoMuted()` method indicates if the local video is on or off for the sample application, returning `bLocalVideoMuted`. 531 | 532 | ```cpp 533 | //VideoCall.cpp 534 | 535 | bool VideoCall::IsLocalVideoMuted() 536 | { 537 | return bLocalVideoMuted; 538 | } 539 | ``` 540 | 541 | 542 | #### Create Audio Methods 543 | 544 | The methods in this section manage the audio. 545 | 546 | ##### Add the MuteLocalAudio() Method 547 | 548 | The `MuteLocalAudio()` method mutes or unmutes the local audio. 549 | Ensure `RtcEnginePtr` is not `nullptr` before completing the remaining method actions. 550 | If the mute or unmute local audio is successful, set `bLocalAudioMuted` to `bMuted`. 551 | 552 | ```cpp 553 | //VideoCall.cpp 554 | 555 | bool VideoCall::MuteLocalAudio(bool bMuted) 556 | { 557 | if (!RtcEnginePtr) 558 | { 559 | return false; 560 | } 561 | int ret = RtcEnginePtr->muteLocalAudioStream(bMuted); 562 | if (ret == 0) 563 | bLocalAudioMuted = bMuted; 564 | 565 | return ret == 0 ? true : false; 566 | } 567 | 568 | ``` 569 | 570 | ##### Add the IsLocalAudioMuted() Method 571 | 572 | The `IsLocalAudioMuted()` method indicates if local audio is muted or unmuted for the sample application, returning `bLocalAudioMuted`. 573 | 574 | ```cpp 575 | //VideoCall.cpp 576 | 577 | bool VideoCall::IsLocalAudioMuted() 578 | { 579 | return bLocalAudioMuted; 580 | } 581 | ``` 582 | 583 | ## Create GUI 584 | 585 | Create the user interface (UI) for the one-to-one call in your project. 586 | 587 | - [Create VideoCallPlayerController](#create-videocallplayercontroller) 588 | - [Create EnterChannelWidget C++ Class](#create-enterchannelwidget-c-class) 589 | - [Create VideoViewWidget C++ Class](#create-videoviewwidget-c-class) 590 | - [Create VideoCallViewWidget C++ Class](#create-videocallviewwidget-c-class) 591 | - [Create VideoCallWidget C++ Class](#create-videocallwidget-c-class) 592 | - [Create BP_EnterChannelWidget blueprint asset](#create-bp_enterchannelwidget-blueprint-asset) 593 | - [Create BP_VideoViewWidget Asset](#create-bp_videoviewwidget-asset) 594 | - [Create BP_VideoCallViewWidget Asset](#create-bp_videocallviewwidget-asset) 595 | - [Create BP_VideoCallWidget Asset](#create-bp_videocallwidget-asset) 596 | - [Create BP_VideoCallPlayerController blueprint asset](#create-bp_videocallplayercontroller-blueprint-asset) 597 | - [Create BP_AgoraVideoCallGameModeBase Asset](#create-bp_agoravideocallgamemodebase-asset) 598 | - [Modify Game Mode](#modify-game-mode) 599 | 600 | ### Create VideoCallPlayerController 601 | 602 | To be able to add our Widget Blueprints to the Viewport, we create our custom player controller class. 603 | 604 | On the Content browser, press the Button **Add New**, and select **New C++ Class**. 605 | On the **Add C++ Class** window, tick the button Show All Classes, and type PlayerController. 606 | Press the **Next** Button and name the class **VideoCallPlayerController**. Press the **Create Class** Button. 607 | 608 | ![Create C++ class](GuideImages/CreateClass_Menu.png) 609 | ![Add C++ class](GuideImages/AddCppClass_Window.png) 610 | ![Name C++ class](GuideImages/NameCppClass_Window.png) 611 | 612 | ```cpp 613 | //VideoCallPlayerController.h 614 | 615 | #include "CoreMinimal.h" 616 | #include "GameFramework/PlayerController.h" 617 | #include "VideoCallPlayerController.generated.h" 618 | 619 | UCLASS() 620 | class AGORAVIDEOCALL_API AVideoCallPlayerController : public APlayerController 621 | { 622 | GENERATED_BODY() 623 | 624 | public: 625 | }; 626 | ``` 627 | 628 | This class is a base class for BP_VideoCallPlayerController blueprint asset, that we will be created in the end. 629 | 630 | - [Add Requered Includes](#add-requered-includes) 631 | - [Class Declaration](#class-declaration) 632 | - [Create Callbacks Methods](#create-backs-methods) 633 | - [Override BeginPlay/EndPlay](#override-beginplayendPlay) 634 | - [Add StartCall/EndCall Methods](#add-startcallendcall-methods) 635 | - [Add Switch On Another Widget Methods](#add-switch-on-another-widget-methods) 636 | 637 | ##### Add Requered Includes 638 | On the top of the **VideoCallPlayerController.h** file include required header files: 639 | 640 | ```cpp 641 | //VideoCallPlayerController.h 642 | 643 | #include "CoreMinimal.h" 644 | #include "GameFramework/PlayerController.h" 645 | #include "Templates/UniquePtr.h" 646 | 647 | #include "VideoCall.h" 648 | 649 | #include "VideoCallPlayerController.generated.h" 650 | ... 651 | ``` 652 | 653 | ```cpp 654 | //VideoCallPlayerController.cpp 655 | 656 | #include "Blueprint/UserWidget.h" 657 | 658 | #include "EnterChannelWidget.h" 659 | #include "VideoCallWidget.h" 660 | ``` 661 | 662 | ##### Class Declaration 663 | 664 | Add forward declaration of the next classes: 665 | 666 | ```cpp 667 | //VideoCallPlayerController.h 668 | 669 | class UEnterChannelWidget; 670 | class UVideoCallWidget; 671 | ``` 672 | 673 | Later we will follow up on the creation of two of them, UEnterChannelWidget and UVideoCallWidget 674 | 675 | ##### Add Member Variables 676 | Now add the member references to the UMG Asset in the Editor: 677 | 678 | ```cpp 679 | //VideoCallPlayerController.h 680 | 681 | ... 682 | 683 | UCLASS() 684 | class AGORAVIDEOCALL_API AVideoCallPlayerController : public APlayerController 685 | { 686 | GENERATED_BODY() 687 | 688 | public: 689 | 690 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Widgets") 691 | TSubclassOf wEnterChannelWidget; 692 | 693 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Widgets") 694 | TSubclassOf wVideoCallWidget; 695 | 696 | ... 697 | }; 698 | ``` 699 | 700 | And variables to hold the widgets after creating and a pointer to VideoCall 701 | 702 | ```cpp 703 | //VideoCallPlayerController.h 704 | 705 | ... 706 | 707 | UCLASS() 708 | class AGORAVIDEOCALL_API AVideoCallPlayerController : public APlayerController 709 | { 710 | GENERATED_BODY() 711 | 712 | public: 713 | 714 | ... 715 | 716 | UEnterChannelWidget* EnterChannelWidget = nullptr; 717 | 718 | UVideoCallWidget* VideoCallWidget = nullptr; 719 | 720 | TUniquePtr VideoCallPtr; 721 | 722 | ... 723 | }; 724 | ``` 725 | 726 | ##### Override BeginPlay/EndPlay 727 | 728 | ```cpp 729 | //VideoCallPlayerController.h 730 | 731 | ... 732 | 733 | UCLASS() 734 | class AGORAVIDEOCALL_API AVideoCallPlayerController : public APlayerController 735 | { 736 | GENERATED_BODY() 737 | 738 | public: 739 | 740 | ... 741 | 742 | void BeginPlay() override; 743 | 744 | void EndPlay(const EEndPlayReason::Type EndPlayReason) override; 745 | 746 | ... 747 | }; 748 | ``` 749 | 750 | ```cpp 751 | //VideoCallPlayerController.cpp 752 | 753 | void AVideoCallPlayerController::BeginPlay() 754 | { 755 | Super::BeginPlay(); 756 | 757 | //initialize wigets 758 | if (wEnterChannelWidget) // Check if the Asset is assigned in the blueprint. 759 | { 760 | // Create the widget and store it. 761 | if (!EnterChannelWidget) 762 | { 763 | EnterChannelWidget = CreateWidget(this, wEnterChannelWidget); 764 | EnterChannelWidget->SetVideoCallPlayerController(this); 765 | } 766 | // now you can use the widget directly since you have a referance for it. 767 | // Extra check to make sure the pointer holds the widget. 768 | if (EnterChannelWidget) 769 | { 770 | //let add it to the view port 771 | EnterChannelWidget->AddToViewport(); 772 | } 773 | //Show the Cursor. 774 | bShowMouseCursor = true; 775 | } 776 | if (wVideoCallWidget) 777 | { 778 | if (!VideoCallWidget) 779 | { 780 | VideoCallWidget = CreateWidget(this, wVideoCallWidget); 781 | VideoCallWidget->SetVideoCallPlayerController(this); 782 | } 783 | if (VideoCallWidget) 784 | { 785 | VideoCallWidget->AddToViewport(); 786 | } 787 | VideoCallWidget->SetVisibility(ESlateVisibility::Collapsed); 788 | } 789 | 790 | //create video call and switch on the EnterChannelWidget 791 | VideoCallPtr = MakeUnique(); 792 | 793 | 794 | FString Version = VideoCallPtr->GetVersion(); 795 | Version = "Agora version: " + Version; 796 | EnterChannelWidget->UpdateVersionText(Version); 797 | 798 | SwitchOnEnterChannelWidget(std::move(VideoCallPtr)); 799 | } 800 | 801 | void AVideoCallPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason) 802 | { 803 | Super::EndPlay(EndPlayReason); 804 | } 805 | ``` 806 | 807 | You may notice that **EnterChannelWidget** and **VideoCallWidget** methods are marked as errors, that is because they are not implemented yet. 808 | We will implement them in the next steps. 809 | 810 | ##### Add StartCall/EndCall Methods 811 | 812 | ```cpp 813 | //VideoCallPlayerController.h 814 | 815 | ... 816 | 817 | UCLASS() 818 | class AGORAVIDEOCALL_API AVideoCallPlayerController : public APlayerController 819 | { 820 | GENERATED_BODY() 821 | 822 | public: 823 | 824 | ... 825 | 826 | void StartCall( 827 | TUniquePtr PassedVideoCallPtr, 828 | const FString& ChannelName, 829 | const FString& EncryptionKey, 830 | const FString& EncryptionType 831 | ); 832 | 833 | void EndCall(TUniquePtr PassedVideoCallPtr); 834 | 835 | ... 836 | }; 837 | ``` 838 | 839 | ```cpp 840 | //VideoCallPlayerController.cpp 841 | 842 | void AVideoCallPlayerController::StartCall( 843 | TUniquePtr PassedVideoCallPtr, 844 | const FString& ChannelName, 845 | const FString& EncryptionKey, 846 | const FString& EncryptionType) 847 | { 848 | SwitchOnVideoCallWidget(std::move(PassedVideoCallPtr)); 849 | 850 | VideoCallWidget->OnStartCall( 851 | ChannelName, 852 | EncryptionKey, 853 | EncryptionType); 854 | } 855 | 856 | void AVideoCallPlayerController::EndCall(TUniquePtr PassedVideoCallPtr) 857 | { 858 | SwitchOnEnterChannelWidget(std::move(PassedVideoCallPtr)); 859 | } 860 | ``` 861 | 862 | ##### Add **Switch On Another Widget** Methods 863 | 864 | Managing visibility of a widget and passing VideoCall pointer we define an active widget. 865 | 866 | ```cpp 867 | //VideoCallPlayerController.h 868 | 869 | ... 870 | 871 | UCLASS() 872 | class AGORAVIDEOCALL_API AVideoCallPlayerController : public APlayerController 873 | { 874 | GENERATED_BODY() 875 | 876 | public: 877 | 878 | ... 879 | 880 | void SwitchOnEnterChannelWidget(TUniquePtr PassedVideoCallPtr); 881 | void SwitchOnVideoCallWidget(TUniquePtr PassedVideoCallPtr); 882 | 883 | ... 884 | }; 885 | ``` 886 | 887 | ```cpp 888 | //VideoCallPlayerController.cpp 889 | 890 | void AVideoCallPlayerController::SwitchOnEnterChannelWidget(TUniquePtr PassedVideoCallPtr) 891 | { 892 | if (!EnterChannelWidget) 893 | { 894 | return; 895 | } 896 | 897 | EnterChannelWidget->SetVideoCall(std::move(PassedVideoCallPtr)); 898 | EnterChannelWidget->SetVisibility(ESlateVisibility::Visible); 899 | } 900 | 901 | void AVideoCallPlayerController::SwitchOnVideoCallWidget(TUniquePtr PassedVideoCallPtr) 902 | { 903 | if (!VideoCallWidget) 904 | { 905 | return; 906 | } 907 | VideoCallWidget->SetVideoCall(std::move(PassedVideoCallPtr)); 908 | VideoCallWidget->SetVisibility(ESlateVisibility::Visible); 909 | } 910 | ``` 911 | 912 | 913 | ### Create EnterChannelWidget C++ Class 914 | 915 | The EnterChannelWidget class manages UI element interactions( from the corresponding blueprint asset ) with the application. 916 | 917 | Create a new class of UserWidget type. On the **Content browser**, press the **Add New** button, and select **New C++ Class**, then tick the button **Show All Classes**, 918 | and type UserWidget. 919 | Press the Next Button and set a name for the class, EnterChannelWidget. 920 | 921 | ![Add EnterChannelWidget](GuideImages/AddCppWidget_Window.png) 922 | ![Name EnterChannelWidget](GuideImages/NameCppWidget_Window.png) 923 | 924 | We get something like this: 925 | 926 | ```cpp 927 | //EnterChannelWidget.h 928 | 929 | #include "CoreMinimal.h" 930 | #include "Blueprint/UserWidget.h" 931 | #include "EnterChannelWidget.generated.h" 932 | 933 | UCLASS() 934 | class AGORAVIDEOCALL_API UEnterChannelWidget : public UUserWidget 935 | { 936 | GENERATED_BODY() 937 | 938 | }; 939 | ``` 940 | 941 | - [Add Requered Includes](#add-requered-includes) 942 | - [Add Member Variables](#add-member-variables) 943 | - [Add Constructor and Construct/Destruct Methods](#add-constructor-and-constructdestruct-methods) 944 | - [Add Setter Methods](#add-setter-methods) 945 | - [Add BlueprintCallable Methods](#add-blueprintcallable-methods) 946 | - [Add Update Methods](#add-update-methods) 947 | 948 | ##### Add Requered Includes 949 | On the top of the **EnterChannelWidget.h** file include required header files and forvard declarations: 950 | 951 | ```cpp 952 | //EnterCahnnelWidget.h 953 | 954 | #include "CoreMinimal.h" 955 | #include "Blueprint/UserWidget.h" 956 | #include "Components/TextBlock.h" 957 | #include "Components/RichTextBlock.h" 958 | #include "Components/EditableTextBox.h" 959 | #include "Components/ComboBoxString.h" 960 | #include "Components/Button.h" 961 | #include "Components/Image.h" 962 | 963 | #include "VideoCall.h" 964 | 965 | #include "EnterChannelWidget.generated.h" 966 | 967 | class AVideoCallPlayerController; 968 | ``` 969 | 970 | ```cpp 971 | //EnterCahnnelWidget.cpp 972 | 973 | #include "Blueprint/WidgetTree.h" 974 | 975 | #include "VideoCallPlayerController.h" 976 | ``` 977 | 978 | ##### Add Member Variables 979 | Now add the next member variables: 980 | 981 | ```cpp 982 | //EnterChannelWidget.h 983 | 984 | ... 985 | 986 | UCLASS() 987 | class AGORAVIDEOCALL_API UEnterChannelWidget : public UUserWidget 988 | { 989 | GENERATED_BODY() 990 | 991 | public: 992 | 993 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 994 | UTextBlock* HeaderTextBlock = nullptr; 995 | 996 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 997 | UTextBlock* DescriptionTextBlock = nullptr; 998 | 999 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 1000 | UEditableTextBox* ChannelNameTextBox = nullptr; 1001 | 1002 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 1003 | UEditableTextBox* EncriptionKeyTextBox = nullptr; 1004 | 1005 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 1006 | UTextBlock* EncriptionTypeTextBlock = nullptr; 1007 | 1008 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 1009 | UComboBoxString* EncriptionTypeComboBox = nullptr; 1010 | 1011 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 1012 | UButton* JoinButton = nullptr; 1013 | 1014 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 1015 | UButton* TestButton = nullptr; 1016 | 1017 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 1018 | UButton* VideoSettingsButton = nullptr; 1019 | 1020 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 1021 | UTextBlock* ContactsTextBlock = nullptr; 1022 | 1023 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget)) 1024 | UTextBlock* BuildInfoTextBlock = nullptr; 1025 | 1026 | ... 1027 | }; 1028 | ``` 1029 | 1030 | These variables needed to control the corresponding UI elements in the blueprint asset. The most important here is BindWidget meta property. 1031 | By marking a pointer to a widget as BindWidget, you can create an identically-named widget in a Blueprint subclass of your C++ class, and at run-time access it from the C++. 1032 | 1033 | Add also the next members: 1034 | 1035 | ```cpp 1036 | //EnterChannelWidget.h 1037 | 1038 | ... 1039 | 1040 | UCLASS() 1041 | class AGORAVIDEOCALL_API UEnterChannelWidget : public UUserWidget 1042 | { 1043 | GENERATED_BODY() 1044 | 1045 | ... 1046 | 1047 | public: 1048 | AVideoCallPlayerController* PlayerController = nullptr; 1049 | 1050 | TUniquePtr VideoCallPtr; 1051 | 1052 | ... 1053 | }; 1054 | ``` 1055 | 1056 | ##### Add Constructor and Construct/Destruct Methods 1057 | 1058 | ```cpp 1059 | //EnterChannelWidget.h 1060 | 1061 | ... 1062 | 1063 | UCLASS() 1064 | class AGORAVIDEOCALL_API UEnterChannelWidget : public UUserWidget 1065 | { 1066 | GENERATED_BODY() 1067 | 1068 | public: 1069 | 1070 | ... 1071 | 1072 | UEnterChannelWidget(const FObjectInitializer& objectInitializer); 1073 | 1074 | void NativeConstruct() override; 1075 | 1076 | ... 1077 | }; 1078 | ``` 1079 | 1080 | ```cpp 1081 | //EnterChannelWidget.cpp 1082 | 1083 | UEnterChannelWidget::UEnterChannelWidget(const FObjectInitializer& objectInitializer) 1084 | : Super(objectInitializer) 1085 | { 1086 | } 1087 | 1088 | void UEnterChannelWidget::NativeConstruct() 1089 | { 1090 | Super::NativeConstruct(); 1091 | 1092 | if (HeaderTextBlock) 1093 | HeaderTextBlock->SetText(FText::FromString("Enter a conference room name")); 1094 | 1095 | if (DescriptionTextBlock) 1096 | DescriptionTextBlock->SetText(FText::FromString("If you are the first person to specify this name, \ 1097 | the room will be created and you will\nbe placed in it. \ 1098 | If it has already been created you will join the conference in progress")); 1099 | 1100 | if (ChannelNameTextBox) 1101 | ChannelNameTextBox->SetHintText(FText::FromString("Channel Name")); 1102 | 1103 | if (EncriptionKeyTextBox) 1104 | EncriptionKeyTextBox->SetHintText(FText::FromString("Encription Key")); 1105 | 1106 | if (EncriptionTypeTextBlock) 1107 | EncriptionTypeTextBlock->SetText(FText::FromString("Enc Type:")); 1108 | 1109 | if (EncriptionTypeComboBox) 1110 | { 1111 | EncriptionTypeComboBox->AddOption("aes-128"); 1112 | EncriptionTypeComboBox->AddOption("aes-256"); 1113 | EncriptionTypeComboBox->SetSelectedIndex(0); 1114 | } 1115 | 1116 | if (JoinButton) 1117 | { 1118 | UTextBlock* JoinTextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); 1119 | JoinTextBlock->SetText(FText::FromString("Join")); 1120 | JoinButton->AddChild(JoinTextBlock); 1121 | JoinButton->OnClicked.AddDynamic(this, &UEnterChannelWidget::OnJoin); 1122 | } 1123 | 1124 | if (ContactsTextBlock) 1125 | ContactsTextBlock->SetText(FText::FromString("agora.io Contact support: 400 632 6626")); 1126 | 1127 | if (BuildInfoTextBlock) 1128 | BuildInfoTextBlock->SetText(FText::FromString(" ")); 1129 | } 1130 | ``` 1131 | 1132 | ##### Add Setter Methods 1133 | 1134 | To initialize PlayerController and VideoCallPtr variables 1135 | 1136 | ```cpp 1137 | //EnterChannelWidget.h 1138 | 1139 | ... 1140 | 1141 | UCLASS() 1142 | class AGORAVIDEOCALL_API UEnterChannelWidget : public UUserWidget 1143 | { 1144 | GENERATED_BODY() 1145 | 1146 | public: 1147 | 1148 | ... 1149 | 1150 | void SetVideoCallPlayerController(AVideoCallPlayerController* VideoCallPlayerController); 1151 | 1152 | void SetVideoCall(TUniquePtr PassedVideoCallPtr); 1153 | 1154 | ... 1155 | }; 1156 | ``` 1157 | 1158 | ```cpp 1159 | //EnterChannelWidget.cpp 1160 | 1161 | void UEnterChannelWidget::SetVideoCallPlayerController(AVideoCallPlayerController* VideoCallPlayerController) 1162 | { 1163 | PlayerController = VideoCallPlayerController; 1164 | } 1165 | 1166 | void UEnterChannelWidget::SetVideoCall(TUniquePtr PassedVideoCallPtr) 1167 | { 1168 | VideoCallPtr = std::move(PassedVideoCallPtr); 1169 | } 1170 | ``` 1171 | 1172 | ##### Add BlueprintCallable Methods 1173 | 1174 | To react on the corresponding button 'onButtonClick' event: 1175 | 1176 | ```cpp 1177 | //EnterChannelWidget.h 1178 | 1179 | ... 1180 | 1181 | UCLASS() 1182 | class AGORAVIDEOCALL_API UEnterChannelWidget : public UUserWidget 1183 | { 1184 | GENERATED_BODY() 1185 | 1186 | public: 1187 | 1188 | ... 1189 | 1190 | UFUNCTION(BlueprintCallable) 1191 | void OnJoin(); 1192 | 1193 | ... 1194 | }; 1195 | ``` 1196 | 1197 | ```cpp 1198 | //EnterChannelWidget.cpp 1199 | 1200 | void UEnterChannelWidget::OnJoin() 1201 | { 1202 | if (!PlayerController || !VideoCallPtr) 1203 | { 1204 | return; 1205 | } 1206 | 1207 | FString ChannelName = ChannelNameTextBox->GetText().ToString(); 1208 | 1209 | FString EncryptionKey = EncriptionKeyTextBox->GetText().ToString(); 1210 | FString EncryptionType = EncriptionTypeComboBox->GetSelectedOption(); 1211 | 1212 | SetVisibility(ESlateVisibility::Collapsed); 1213 | 1214 | PlayerController->StartCall( 1215 | std::move(VideoCallPtr), 1216 | ChannelName, 1217 | EncryptionKey, 1218 | EncryptionType); 1219 | } 1220 | ``` 1221 | 1222 | ##### Add Update Methods 1223 | 1224 | ```cpp 1225 | //EnterChannelWidget.h 1226 | 1227 | ... 1228 | 1229 | UCLASS() 1230 | class AGORAVIDEOCALL_API UEnterChannelWidget : public UUserWidget 1231 | { 1232 | GENERATED_BODY() 1233 | 1234 | public: 1235 | 1236 | ... 1237 | 1238 | void UpdateVersionText(FString newValue); 1239 | 1240 | ... 1241 | }; 1242 | ``` 1243 | 1244 | ```cpp 1245 | //EnterChannelWidget.cpp 1246 | 1247 | void UEnterChannelWidget::UpdateVersionText(FString newValue) 1248 | { 1249 | if (BuildInfoTextBlock) 1250 | BuildInfoTextBlock->SetText(FText::FromString(newValue)); 1251 | } 1252 | ``` 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | ### Create VideoViewWidget C++ Class. 1259 | 1260 | VideoViewWidget is a class to store the dynamic texture and update it, using the RGBA buffer, received from VideoCall `OnLocalFrameCallback`/`OnRemoteFrameCallback` functions. 1261 | 1262 | - [Create Class and Add Required Includes](#create-class-and-add-required-includes) 1263 | - [Add Member Variables](#add-member-variables) 1264 | - [Override NativeConstruct Method](#override-nativeconstruct-method) 1265 | - [Override NativeDestruct Method](#override-nativedestruct-method) 1266 | - [Override NativeTick Method](#override-nativetick-method) 1267 | - [Add UpdateBuffer Method](#add-updatebuffer-method) 1268 | 1269 | ##### Create Class and Add Required Includes 1270 | 1271 | Create Widget C++ class as you did before, add required includes: 1272 | 1273 | ```cpp 1274 | //VideoViewWidget.h 1275 | 1276 | #include "CoreMinimal.h" 1277 | #include "Blueprint/UserWidget.h" 1278 | 1279 | #include "Components/Image.h" 1280 | 1281 | #include "VideoViewWidget.generated.h" 1282 | ``` 1283 | 1284 | ```cpp 1285 | //VideoViewWidget.cpp 1286 | 1287 | #include "EngineUtils.h" 1288 | #include "Engine/Texture2D.h" 1289 | 1290 | #include 1291 | ``` 1292 | 1293 | ##### Add Member Variables 1294 | 1295 | Buffer - Variable to store the RGBA buffer, Width, Height and BufferSize - params of the video frame. 1296 | RenderTargetImage - The image widget that allows you to display a Slate Brush, or texture or material in the UI. 1297 | RenderTargetTexture - The dynamic texture, that we will update using the Buffer variable. 1298 | FUpdateTextureRegion2D - Specifies an update region for a texture 1299 | Brush - A brush which contains information about how to draw a Slate element. We will use it to draw RenderTargetTexture on RenderTargetImage. 1300 | 1301 | ```cpp 1302 | //VideoViewWidget.h 1303 | 1304 | ... 1305 | 1306 | UCLASS() 1307 | class AGORAVIDEOCALL_API UVideoViewWidget : public UUserWidget 1308 | { 1309 | GENERATED_BODY() 1310 | 1311 | public: 1312 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 1313 | UImage* RenderTargetImage = nullptr; 1314 | 1315 | UPROPERTY(EditDefaultsOnly) 1316 | UTexture2D* RenderTargetTexture = nullptr; 1317 | 1318 | UTexture2D* CameraoffTexture = nullptr; 1319 | 1320 | uint8* Buffer = nullptr; 1321 | uint32_t Width = 0; 1322 | uint32_t Height = 0; 1323 | uint32 BufferSize = 0; 1324 | FUpdateTextureRegion2D* UpdateTextureRegion = nullptr; 1325 | 1326 | FSlateBrush Brush; 1327 | 1328 | FCriticalSection Mutex; 1329 | 1330 | ... 1331 | }; 1332 | ``` 1333 | 1334 | ##### Override NativeConstruct() Method 1335 | 1336 | In the NativeConstruct we will initialize our image with default color. 1337 | To initialize our RenderTargetTexture we need to create the dynamic texture (Texture2D ) using a CreateTransient call. 1338 | Then allocate Buffer with BufferSize calculated as Width * Height * 4 ( to store the RGBA format, where each pixel can be represented using 4 bytes ). 1339 | To update our texture we can use a call to UpdateTextureRegions. One of the input parameters to this function is our pixel data buffer. 1340 | So that whenever we modified the pixel data buffer we need to call this function to make the change visible in the texture. 1341 | Now initialize the Brush variable with our RenderTargetTexture, and then set this Brush in RenderTargetImage widget. 1342 | 1343 | ```cpp 1344 | //VideoViewWidget.h 1345 | 1346 | ... 1347 | 1348 | UCLASS() 1349 | class AGORAVIDEOCALL_API UVideoViewWidget : public UUserWidget 1350 | { 1351 | GENERATED_BODY() 1352 | 1353 | public: 1354 | 1355 | ... 1356 | 1357 | void NativeConstruct() override; 1358 | 1359 | ... 1360 | }; 1361 | ``` 1362 | 1363 | ```cpp 1364 | //VideoViewWidget.cpp 1365 | 1366 | void UVideoViewWidget::NativeConstruct() 1367 | { 1368 | Super::NativeConstruct(); 1369 | 1370 | Width = 640; 1371 | Height = 360; 1372 | 1373 | RenderTargetTexture = UTexture2D::CreateTransient(Width, Height, PF_R8G8B8A8); 1374 | RenderTargetTexture->UpdateResource(); 1375 | 1376 | BufferSize = Width * Height * 4; 1377 | Buffer = new uint8[BufferSize]; 1378 | for (uint32 i = 0; i < Width * Height; ++i) 1379 | { 1380 | Buffer[i * 4 + 0] = 0x32; 1381 | Buffer[i * 4 + 1] = 0x32; 1382 | Buffer[i * 4 + 2] = 0x32; 1383 | Buffer[i * 4 + 3] = 0xFF; 1384 | } 1385 | UpdateTextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, Width, Height); 1386 | RenderTargetTexture->UpdateTextureRegions(0, 1, UpdateTextureRegion, Width * 4, (uint32)4, Buffer); 1387 | 1388 | Brush.SetResourceObject(RenderTargetTexture); 1389 | RenderTargetImage->SetBrush(Brush); 1390 | } 1391 | ``` 1392 | 1393 | ##### Override NativeDestruct() Method 1394 | 1395 | ```cpp 1396 | //VideoViewWidget.h 1397 | 1398 | ... 1399 | 1400 | UCLASS() 1401 | class AGORAVIDEOCALL_API UVideoViewWidget : public UUserWidget 1402 | { 1403 | GENERATED_BODY() 1404 | 1405 | public: 1406 | 1407 | ... 1408 | 1409 | void NativeDestruct() override; 1410 | 1411 | ... 1412 | }; 1413 | ``` 1414 | 1415 | ```cpp 1416 | //VideoViewWidget.cpp 1417 | 1418 | void UVideoViewWidget::NativeDestruct() 1419 | { 1420 | Super::NativeDestruct(); 1421 | 1422 | delete[] Buffer; 1423 | delete UpdateTextureRegion; 1424 | } 1425 | ``` 1426 | 1427 | ##### Override NativeTick() Method 1428 | 1429 | In case UpdateTextureRegion Width or Height are not equal to the memember Width Height values we need to recreate RenderTargetTexture to support the updated values, 1430 | and repeat initialization like in the Native Construct member. 1431 | Otherwise just call UpdateTextureRegions with Buffer. 1432 | 1433 | ```cpp 1434 | //VideoViewWidget.h 1435 | 1436 | ... 1437 | 1438 | UCLASS() 1439 | class AGORAVIDEOCALL_API UVideoViewWidget : public UUserWidget 1440 | { 1441 | GENERATED_BODY() 1442 | 1443 | public: 1444 | 1445 | ... 1446 | 1447 | void NativeTick(const FGeometry& MyGeometry, float DeltaTime) override; 1448 | 1449 | ... 1450 | }; 1451 | ``` 1452 | 1453 | ```cpp 1454 | //VideoViewWidget.cpp 1455 | 1456 | void UVideoViewWidget::NativeTick(const FGeometry& MyGeometry, float DeltaTime) 1457 | { 1458 | Super::NativeTick(MyGeometry, DeltaTime); 1459 | 1460 | FScopeLock lock(&Mutex); 1461 | 1462 | if (UpdateTextureRegion->Width != Width || 1463 | UpdateTextureRegion->Height != Height) 1464 | { 1465 | auto NewUpdateTextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, Width, Height); 1466 | 1467 | auto NewRenderTargetTexture = UTexture2D::CreateTransient(Width, Height, PF_R8G8B8A8); 1468 | NewRenderTargetTexture->UpdateResource(); 1469 | NewRenderTargetTexture->UpdateTextureRegions(0, 1, NewUpdateTextureRegion, Width * 4, (uint32)4, Buffer); 1470 | 1471 | Brush.SetResourceObject(NewRenderTargetTexture); 1472 | RenderTargetImage->SetBrush(Brush); 1473 | 1474 | //UClass's such as UTexture2D are automatically garbage collected when there is no hard pointer references made to that object. 1475 | //So if you just leave it and don't reference it elsewhere then it will be destroyed automatically. 1476 | 1477 | FUpdateTextureRegion2D* TmpUpdateTextureRegion = UpdateTextureRegion; 1478 | 1479 | RenderTargetTexture = NewRenderTargetTexture; 1480 | UpdateTextureRegion = NewUpdateTextureRegion; 1481 | 1482 | delete TmpUpdateTextureRegion; 1483 | return; 1484 | } 1485 | 1486 | RenderTargetTexture->UpdateTextureRegions(0, 1, UpdateTextureRegion, Width * 4, (uint32)4, Buffer); 1487 | } 1488 | ``` 1489 | 1490 | ##### Add UpdateBuffer() Method 1491 | 1492 | Called to update the Buffer value. We expect the new value to be received from Agora SDK thread, so due to UE4 limitation we save the value into the variable Buffer, 1493 | and update texture in the NativeTick method, and don't call UpdateTextureRegions here. 1494 | 1495 | ```cpp 1496 | //VideoViewWidget.h 1497 | 1498 | ... 1499 | 1500 | UCLASS() 1501 | class AGORAVIDEOCALL_API UVideoViewWidget : public UUserWidget 1502 | { 1503 | GENERATED_BODY() 1504 | 1505 | public: 1506 | 1507 | ... 1508 | 1509 | void UpdateBuffer( uint8* RGBBuffer, uint32_t Width, uint32_t Height, uint32_t Size ); 1510 | void ResetBuffer(); 1511 | ... 1512 | }; 1513 | ``` 1514 | 1515 | ```cpp 1516 | //VideoViewWidget.cpp 1517 | 1518 | void UVideoViewWidget::UpdateBuffer( 1519 | uint8* RGBBuffer, 1520 | uint32_t NewWidth, 1521 | uint32_t NewHeight, 1522 | uint32_t NewSize) 1523 | { 1524 | FScopeLock lock(&Mutex); 1525 | 1526 | if (!RGBBuffer) 1527 | { 1528 | return; 1529 | } 1530 | 1531 | if (BufferSize == NewSize) 1532 | { 1533 | std::copy(RGBBuffer, RGBBuffer + NewSize, Buffer); 1534 | } 1535 | else 1536 | { 1537 | delete[] Buffer; 1538 | BufferSize = NewSize; 1539 | Width = NewWidth; 1540 | Height = NewHeight; 1541 | Buffer = new uint8[BufferSize]; 1542 | std::copy(RGBBuffer, RGBBuffer + NewSize, Buffer); 1543 | } 1544 | } 1545 | 1546 | void UVideoViewWidget::ResetBuffer() 1547 | { 1548 | for (uint32 i = 0; i < Width * Height; ++i) 1549 | { 1550 | Buffer[i * 4 + 0] = 0x32; 1551 | Buffer[i * 4 + 1] = 0x32; 1552 | Buffer[i * 4 + 2] = 0x32; 1553 | Buffer[i * 4 + 3] = 0xFF; 1554 | } 1555 | } 1556 | ``` 1557 | 1558 | 1559 | ### Create VideoCallViewWidget C++ Class. 1560 | 1561 | The VideoCallViewWidget class serves to display the local and remote user video. 1562 | We need two VideoViewWidget widgets, one to display video from the local camera, another to display video received from the remote user (assume we support only one remote user). 1563 | 1564 | - [Create Class and Add Required Includes](#create-class-and-add-required-includes) 1565 | - [Add Member Variables](#add-member-variables) 1566 | - [Override NativeTick Method](#override-nativetick-method) 1567 | - [Add Update UpdateMainVideoBuffer/UpdateAdditionalVideoBuffer Methods](#add-update-updatemainvideobufferupdateaadditionalvideobuffer-methods) 1568 | 1569 | ##### Create Class and Add Required Includes 1570 | 1571 | Create Widget C++ class as you did before, add required includes: 1572 | 1573 | ```cpp 1574 | //VideoCallViewWidget.h 1575 | 1576 | #include "CoreMinimal.h" 1577 | #include "Blueprint/UserWidget.h" 1578 | #include "Components/SizeBox.h" 1579 | 1580 | #include "VideoViewWidget.h" 1581 | 1582 | #include "VideoCallViewWidget.generated.h" 1583 | ``` 1584 | 1585 | ```cpp 1586 | //VideoCallViewWidget.cpp 1587 | 1588 | #include "Components/CanvasPanelSlot.h" 1589 | ``` 1590 | 1591 | ##### Add Member Variables 1592 | 1593 | ```cpp 1594 | //VideoCallViewWidget.h 1595 | 1596 | ... 1597 | 1598 | UCLASS() 1599 | class AGORAVIDEOCALL_API UVideoCallViewWidget : public UUserWidget 1600 | { 1601 | GENERATED_BODY() 1602 | 1603 | public: 1604 | 1605 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 1606 | UVideoViewWidget* MainVideoViewWidget = nullptr; 1607 | 1608 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 1609 | USizeBox* MainVideoSizeBox = nullptr; 1610 | 1611 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 1612 | UVideoViewWidget* AdditionalVideoViewWidget = nullptr; 1613 | 1614 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 1615 | USizeBox* AdditionalVideoSizeBox = nullptr; 1616 | 1617 | public: 1618 | int32 MainVideoWidth = 0; 1619 | int32 MainVideoHeight = 0; 1620 | 1621 | ... 1622 | }; 1623 | ``` 1624 | 1625 | ##### Override NativeTick() Method 1626 | 1627 | In NativeTick we update the widgets geometry 1628 | ```cpp 1629 | //VideoCallViewWidget.h 1630 | 1631 | ... 1632 | 1633 | UCLASS() 1634 | class AGORAVIDEOCALL_API UVideoCallViewWidget : public UUserWidget 1635 | { 1636 | GENERATED_BODY() 1637 | 1638 | public: 1639 | 1640 | ... 1641 | 1642 | void NativeTick(const FGeometry& MyGeometry, float DeltaTime) override; 1643 | 1644 | ... 1645 | }; 1646 | ``` 1647 | 1648 | ```cpp 1649 | //VideoCallViewWidget.cpp 1650 | 1651 | void UVideoCallViewWidget::NativeTick(const FGeometry& MyGeometry, float DeltaTime) 1652 | { 1653 | Super::NativeTick(MyGeometry, DeltaTime); 1654 | 1655 | auto ScreenSize = MyGeometry.GetLocalSize(); 1656 | 1657 | if (MainVideoHeight != 0) 1658 | { 1659 | float AspectRatio = 0; 1660 | AspectRatio = MainVideoWidth / (float)MainVideoHeight; 1661 | 1662 | auto MainVideoGeometry = MainVideoViewWidget->GetCachedGeometry(); 1663 | auto MainVideoScreenSize = MainVideoGeometry.GetLocalSize(); 1664 | if (MainVideoScreenSize.X == 0) 1665 | { 1666 | return; 1667 | } 1668 | 1669 | auto NewMainVideoHeight = MainVideoScreenSize.Y; 1670 | auto NewMainVideoWidth = AspectRatio * NewMainVideoHeight; 1671 | 1672 | MainVideoSizeBox->SetMinDesiredWidth(NewMainVideoWidth); 1673 | MainVideoSizeBox->SetMinDesiredHeight(NewMainVideoHeight); 1674 | 1675 | UCanvasPanelSlot* CanvasSlot = Cast(MainVideoSizeBox->Slot); 1676 | CanvasSlot->SetAutoSize(true); 1677 | 1678 | FVector2D NewPosition; 1679 | NewPosition.X = -NewMainVideoWidth / 2; 1680 | NewPosition.Y = -NewMainVideoHeight / 2; 1681 | CanvasSlot->SetPosition(NewPosition); 1682 | } 1683 | } 1684 | ``` 1685 | 1686 | ##### Add Update UpdateMainVideoBuffer/UpdateAdditionalVideoBuffer Methods 1687 | 1688 | ```cpp 1689 | //VideoCallViewWidget.h 1690 | 1691 | ... 1692 | 1693 | UCLASS() 1694 | class AGORAVIDEOCALL_API UVideoCallViewWidget : public UUserWidget 1695 | { 1696 | GENERATED_BODY() 1697 | 1698 | public: 1699 | 1700 | ... 1701 | 1702 | void UpdateMainVideoBuffer( uint8* RGBBuffer, uint32_t Width, uint32_t Height, uint32_t Size); 1703 | void UpdateAdditionalVideoBuffer( uint8* RGBBuffer, uint32_t Width, uint32_t Height, uint32_t Size); 1704 | 1705 | void ResetBuffers(); 1706 | ... 1707 | }; 1708 | ``` 1709 | 1710 | ```cpp 1711 | //VideoCallViewWidget.cpp 1712 | 1713 | void UVideoCallViewWidget::UpdateMainVideoBuffer( 1714 | uint8* RGBBuffer, 1715 | uint32_t Width, 1716 | uint32_t Height, 1717 | uint32_t Size) 1718 | { 1719 | if (!MainVideoViewWidget) 1720 | { 1721 | return; 1722 | } 1723 | MainVideoWidth = Width; 1724 | MainVideoHeight = Height; 1725 | MainVideoViewWidget->UpdateBuffer(RGBBuffer, Width, Height, Size); 1726 | } 1727 | 1728 | void UVideoCallViewWidget::UpdateAdditionalVideoBuffer( 1729 | uint8* RGBBuffer, 1730 | uint32_t Width, 1731 | uint32_t Height, 1732 | uint32_t Size) 1733 | { 1734 | if (!AdditionalVideoViewWidget) 1735 | { 1736 | return; 1737 | } 1738 | AdditionalVideoViewWidget->UpdateBuffer(RGBBuffer, Width, Height, Size); 1739 | } 1740 | 1741 | void UVideoCallViewWidget::ResetBuffers() 1742 | { 1743 | if (!MainVideoViewWidget || !AdditionalVideoViewWidget) 1744 | { 1745 | return; 1746 | } 1747 | MainVideoViewWidget->ResetBuffer(); 1748 | AdditionalVideoViewWidget->ResetBuffer(); 1749 | } 1750 | ``` 1751 | 1752 | 1753 | ### Create VideoCallWidget C++ Class. 1754 | 1755 | The VideoCallWidget class serves as the audio/video call widget for the sample application. 1756 | It contains the following controls, binded with UI elements in blueprint asset: 1757 | 1758 | - The local and remote video view( represented by VideoCallViewWidget ) 1759 | - The end-call button( EndCallButton variable ) 1760 | - The mute-local-audio button( MuteLocalAudioButton variable ) 1761 | - The video-mode button( VideoModeButton variable ) 1762 | 1763 | 1764 | - [Create Class and Add Required Includes](#create-class-and-add-required-includes) 1765 | - [Add Member Variables](#add-member-variables) 1766 | - [Initialize VideoCallWidget](#initialize-videocallwidget) 1767 | - [Add Button Textures](#add-button-textures) 1768 | - [Add Setters](#add-setters) 1769 | - [Add Methods to Update Buttons View ](#add-methods-to-update-buttons-view) 1770 | - [Add OnStartCall Method](#add-onstartcall-method) 1771 | - [Add OnEndCall Method](#add-onendcall-method) 1772 | - [Add OnMuteLocalAudio Method](#add-onmutelocalaudio-method) 1773 | - [Add OnChangeVideoMode Method](#add-onchangevideomode-method) 1774 | 1775 | ##### Create Class and Add Required Includes 1776 | 1777 | Create Widget C++ class as you did before, add required includes and forward declarations: 1778 | 1779 | ```cpp 1780 | //VideoCallWidget.h 1781 | #include "CoreMinimal.h" 1782 | #include "Blueprint/UserWidget.h" 1783 | 1784 | #include "Templates/UniquePtr.h" 1785 | #include "Components/Image.h" 1786 | #include "Components/Button.h" 1787 | #include "Engine/Texture2D.h" 1788 | 1789 | #include "VideoCall.h" 1790 | 1791 | #include "VideoCallViewWidget.h" 1792 | 1793 | #include "VideoCallWidget.generated.h" 1794 | 1795 | class AVideoCallPlayerController; 1796 | class UVideoViewWidget; 1797 | ``` 1798 | 1799 | ```cpp 1800 | //VideoCallWidget.cpp 1801 | 1802 | #include "Kismet/GameplayStatics.h" 1803 | #include "UObject/ConstructorHelpers.h" 1804 | #include "Components/CanvasPanelSlot.h" 1805 | 1806 | #include "VideoViewWidget.h" 1807 | 1808 | #include "VideoCallPlayerController.h" 1809 | ``` 1810 | 1811 | ##### Add Member Variables 1812 | 1813 | ```cpp 1814 | //VideoCallWidget.h 1815 | 1816 | ... 1817 | 1818 | UCLASS() 1819 | class AGORAVIDEOCALL_API UVideoCallWidget : public UUserWidget 1820 | { 1821 | GENERATED_BODY() 1822 | 1823 | public: 1824 | AVideoCallPlayerController* PlayerController = nullptr; 1825 | 1826 | public: 1827 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 1828 | UVideoCallViewWidget* VideoCallViewWidget = nullptr; 1829 | 1830 | //Buttons 1831 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 1832 | UButton* EndCallButton = nullptr; 1833 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 1834 | UButton* MuteLocalAudioButton = nullptr; 1835 | UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) 1836 | UButton* VideoModeButton = nullptr; 1837 | 1838 | //Button textures 1839 | int32 ButtonSizeX = 96; 1840 | int32 ButtonSizeY = 96; 1841 | UTexture2D* EndCallButtonTexture = nullptr; 1842 | UTexture2D* AudioButtonMuteTexture = nullptr; 1843 | UTexture2D* AudioButtonUnmuteTexture = nullptr; 1844 | UTexture2D* VideomodeButtonCameraoffTexture = nullptr; 1845 | UTexture2D* VideomodeButtonCameraonTexture = nullptr; 1846 | 1847 | TUniquePtr VideoCallPtr; 1848 | 1849 | ... 1850 | }; 1851 | ``` 1852 | 1853 | ##### Initialize VideoCallWidget 1854 | 1855 | Find asset image for each button and assigne it to corresponding texture. Then initialize each button with textures. 1856 | 1857 | ```cpp 1858 | //VideoCallWidget.h 1859 | 1860 | ... 1861 | 1862 | UCLASS() 1863 | class AGORAVIDEOCALL_API UVideoCallWidget : public UUserWidget 1864 | { 1865 | GENERATED_BODY() 1866 | 1867 | public: 1868 | 1869 | ... 1870 | 1871 | UVideoCallWidget(const FObjectInitializer& ObjectInitializer); 1872 | 1873 | void NativeConstruct() override; 1874 | void NativeDestruct() override; 1875 | 1876 | private: 1877 | void InitButtons(); 1878 | 1879 | ... 1880 | }; 1881 | ``` 1882 | 1883 | ```cpp 1884 | //VideoCallWidget.cpp 1885 | 1886 | void UVideoCallWidget::NativeConstruct() 1887 | { 1888 | Super::NativeConstruct(); 1889 | 1890 | InitButtons(); 1891 | } 1892 | 1893 | void UVideoCallWidget::NativeDestruct() 1894 | { 1895 | Super::NativeDestruct(); 1896 | 1897 | if (VideoCallPtr) 1898 | { 1899 | VideoCallPtr->StopCall(); 1900 | } 1901 | } 1902 | 1903 | UVideoCallWidget::UVideoCallWidget(const FObjectInitializer& ObjectInitializer) 1904 | : Super(ObjectInitializer) 1905 | { 1906 | static ConstructorHelpers::FObjectFinder 1907 | EndCallButtonTextureFinder(TEXT("Texture'/Game/ButtonTextures/hangup.hangup'")); 1908 | if (EndCallButtonTextureFinder.Succeeded()) 1909 | { 1910 | EndCallButtonTexture = EndCallButtonTextureFinder.Object; 1911 | } 1912 | static ConstructorHelpers::FObjectFinder 1913 | AudioButtonMuteTextureFinder(TEXT("Texture'/Game/ButtonTextures/mute.mute'")); 1914 | if (AudioButtonMuteTextureFinder.Succeeded()) 1915 | { 1916 | AudioButtonMuteTexture = AudioButtonMuteTextureFinder.Object; 1917 | } 1918 | static ConstructorHelpers::FObjectFinder 1919 | AudioButtonUnmuteTextureFinder(TEXT("Texture'/Game/ButtonTextures/unmute.unmute'")); 1920 | if (AudioButtonUnmuteTextureFinder.Succeeded()) 1921 | { 1922 | AudioButtonUnmuteTexture = AudioButtonUnmuteTextureFinder.Object; 1923 | } 1924 | static ConstructorHelpers::FObjectFinder 1925 | VideomodeButtonCameraonTextureFinder(TEXT("Texture'/Game/ButtonTextures/cameraon.cameraon'")); 1926 | if (VideomodeButtonCameraonTextureFinder.Succeeded()) 1927 | { 1928 | VideomodeButtonCameraonTexture = VideomodeButtonCameraonTextureFinder.Object; 1929 | } 1930 | static ConstructorHelpers::FObjectFinder 1931 | VideomodeButtonCameraoffTextureFinder(TEXT("Texture'/Game/ButtonTextures/cameraoff.cameraoff'")); 1932 | if (VideomodeButtonCameraoffTextureFinder.Succeeded()) 1933 | { 1934 | VideomodeButtonCameraoffTexture = VideomodeButtonCameraoffTextureFinder.Object; 1935 | } 1936 | } 1937 | 1938 | void UVideoCallWidget::InitButtons() 1939 | { 1940 | if (EndCallButtonTexture) 1941 | { 1942 | EndCallButton->WidgetStyle.Normal.SetResourceObject(EndCallButtonTexture); 1943 | EndCallButton->WidgetStyle.Normal.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 1944 | EndCallButton->WidgetStyle.Normal.DrawAs = ESlateBrushDrawType::Type::Image; 1945 | 1946 | EndCallButton->WidgetStyle.Hovered.SetResourceObject(EndCallButtonTexture); 1947 | EndCallButton->WidgetStyle.Hovered.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 1948 | EndCallButton->WidgetStyle.Hovered.DrawAs = ESlateBrushDrawType::Type::Image; 1949 | 1950 | EndCallButton->WidgetStyle.Pressed.SetResourceObject(EndCallButtonTexture); 1951 | EndCallButton->WidgetStyle.Pressed.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 1952 | EndCallButton->WidgetStyle.Pressed.DrawAs = ESlateBrushDrawType::Type::Image; 1953 | } 1954 | EndCallButton->OnClicked.AddDynamic(this, &UVideoCallWidget::OnEndCall); 1955 | 1956 | SetAudioButtonToMute(); 1957 | MuteLocalAudioButton->OnClicked.AddDynamic(this, &UVideoCallWidget::OnMuteLocalAudio); 1958 | 1959 | SetVideoModeButtonToCameraOff(); 1960 | VideoModeButton->OnClicked.AddDynamic(this, &UVideoCallWidget::OnChangeVideoMode); 1961 | 1962 | } 1963 | ``` 1964 | 1965 | ##### Add Button Textures 1966 | 1967 | Find directory **Content/ButtonTextures** in demo application (you dont have to open the project, simply find this folder in filesystem). 1968 | All button textures are stored there. 1969 | In your project content create new directory called **ButtonTextures**, drag and drop all button images there to make them available in your project. 1970 | 1971 | ##### Add Setters 1972 | 1973 | ```cpp 1974 | //VideoCallWidget.h 1975 | 1976 | ... 1977 | 1978 | UCLASS() 1979 | class AGORAVIDEOCALL_API UVideoCallWidget : public UUserWidget 1980 | { 1981 | GENERATED_BODY() 1982 | 1983 | ... 1984 | 1985 | public: 1986 | void SetVideoCallPlayerController(AVideoCallPlayerController* VideoCallPlayerController); 1987 | void SetVideoCall(TUniquePtr PassedVideoCallPtr); 1988 | 1989 | ... 1990 | }; 1991 | ``` 1992 | 1993 | ```cpp 1994 | //VideoCallWidget.cpp 1995 | 1996 | void UVideoCallWidget::SetVideoCallPlayerController(AVideoCallPlayerController* VideoCallPlayerController) 1997 | { 1998 | PlayerController = VideoCallPlayerController; 1999 | } 2000 | 2001 | void UVideoCallWidget::SetVideoCall(TUniquePtr PassedVideoCallPtr) 2002 | { 2003 | VideoCallPtr = std::move(PassedVideoCallPtr); 2004 | } 2005 | ``` 2006 | 2007 | ##### Add Methods to Update Buttons View 2008 | 2009 | ```cpp 2010 | //VideoCallWidget.h 2011 | 2012 | ... 2013 | 2014 | UCLASS() 2015 | class AGORAVIDEOCALL_API UVideoCallWidget : public UUserWidget 2016 | { 2017 | GENERATED_BODY() 2018 | 2019 | ... 2020 | 2021 | private: 2022 | 2023 | void SetVideoModeButtonToCameraOff(); 2024 | void SetVideoModeButtonToCameraOn(); 2025 | 2026 | void SetAudioButtonToMute(); 2027 | void SetAudioButtonToUnMute(); 2028 | 2029 | ... 2030 | }; 2031 | ``` 2032 | 2033 | ```cpp 2034 | //VideoCallWidget.cpp 2035 | 2036 | void UVideoCallWidget::SetVideoModeButtonToCameraOff() 2037 | { 2038 | if (VideomodeButtonCameraoffTexture) 2039 | { 2040 | VideoModeButton->WidgetStyle.Normal.SetResourceObject(VideomodeButtonCameraoffTexture); 2041 | VideoModeButton->WidgetStyle.Normal.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 2042 | VideoModeButton->WidgetStyle.Normal.DrawAs = ESlateBrushDrawType::Type::Image; 2043 | 2044 | VideoModeButton->WidgetStyle.Hovered.SetResourceObject(VideomodeButtonCameraoffTexture); 2045 | VideoModeButton->WidgetStyle.Hovered.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 2046 | VideoModeButton->WidgetStyle.Hovered.DrawAs = ESlateBrushDrawType::Type::Image; 2047 | 2048 | VideoModeButton->WidgetStyle.Pressed.SetResourceObject(VideomodeButtonCameraoffTexture); 2049 | VideoModeButton->WidgetStyle.Pressed.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 2050 | VideoModeButton->WidgetStyle.Pressed.DrawAs = ESlateBrushDrawType::Type::Image; 2051 | } 2052 | } 2053 | 2054 | void UVideoCallWidget::SetVideoModeButtonToCameraOn() 2055 | { 2056 | if (VideomodeButtonCameraonTexture) 2057 | { 2058 | VideoModeButton->WidgetStyle.Normal.SetResourceObject(VideomodeButtonCameraonTexture); 2059 | VideoModeButton->WidgetStyle.Normal.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 2060 | VideoModeButton->WidgetStyle.Normal.DrawAs = ESlateBrushDrawType::Type::Image; 2061 | 2062 | VideoModeButton->WidgetStyle.Hovered.SetResourceObject(VideomodeButtonCameraonTexture); 2063 | VideoModeButton->WidgetStyle.Hovered.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 2064 | VideoModeButton->WidgetStyle.Hovered.DrawAs = ESlateBrushDrawType::Type::Image; 2065 | 2066 | VideoModeButton->WidgetStyle.Pressed.SetResourceObject(VideomodeButtonCameraonTexture); 2067 | VideoModeButton->WidgetStyle.Pressed.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 2068 | VideoModeButton->WidgetStyle.Pressed.DrawAs = ESlateBrushDrawType::Type::Image; 2069 | } 2070 | } 2071 | 2072 | void UVideoCallWidget::SetAudioButtonToMute() 2073 | { 2074 | if (AudioButtonMuteTexture) 2075 | { 2076 | MuteLocalAudioButton->WidgetStyle.Normal.SetResourceObject(AudioButtonMuteTexture); 2077 | MuteLocalAudioButton->WidgetStyle.Normal.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 2078 | MuteLocalAudioButton->WidgetStyle.Normal.DrawAs = ESlateBrushDrawType::Type::Image; 2079 | 2080 | MuteLocalAudioButton->WidgetStyle.Hovered.SetResourceObject(AudioButtonMuteTexture); 2081 | MuteLocalAudioButton->WidgetStyle.Hovered.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 2082 | MuteLocalAudioButton->WidgetStyle.Hovered.DrawAs = ESlateBrushDrawType::Type::Image; 2083 | 2084 | MuteLocalAudioButton->WidgetStyle.Pressed.SetResourceObject(AudioButtonMuteTexture); 2085 | MuteLocalAudioButton->WidgetStyle.Pressed.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 2086 | MuteLocalAudioButton->WidgetStyle.Pressed.DrawAs = ESlateBrushDrawType::Type::Image; 2087 | } 2088 | } 2089 | 2090 | void UVideoCallWidget::SetAudioButtonToUnMute() 2091 | { 2092 | if (AudioButtonUnmuteTexture) 2093 | { 2094 | MuteLocalAudioButton->WidgetStyle.Normal.SetResourceObject(AudioButtonUnmuteTexture); 2095 | MuteLocalAudioButton->WidgetStyle.Normal.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 2096 | MuteLocalAudioButton->WidgetStyle.Normal.DrawAs = ESlateBrushDrawType::Type::Image; 2097 | 2098 | MuteLocalAudioButton->WidgetStyle.Hovered.SetResourceObject(AudioButtonUnmuteTexture); 2099 | MuteLocalAudioButton->WidgetStyle.Hovered.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 2100 | MuteLocalAudioButton->WidgetStyle.Hovered.DrawAs = ESlateBrushDrawType::Type::Image; 2101 | 2102 | MuteLocalAudioButton->WidgetStyle.Pressed.SetResourceObject(AudioButtonUnmuteTexture); 2103 | MuteLocalAudioButton->WidgetStyle.Pressed.ImageSize = FVector2D(ButtonSizeX, ButtonSizeY); 2104 | MuteLocalAudioButton->WidgetStyle.Pressed.DrawAs = ESlateBrushDrawType::Type::Image; 2105 | } 2106 | } 2107 | ``` 2108 | 2109 | ##### Add OnStartCall Method 2110 | 2111 | ```cpp 2112 | //VideoCallWidget.h 2113 | 2114 | ... 2115 | 2116 | UCLASS() 2117 | class AGORAVIDEOCALL_API UVideoCallWidget : public UUserWidget 2118 | { 2119 | GENERATED_BODY() 2120 | 2121 | public: 2122 | 2123 | ... 2124 | 2125 | void OnStartCall( const FString& ChannelName, const FString& EncryptionKey, const FString& EncryptionType ); 2126 | 2127 | ... 2128 | }; 2129 | ``` 2130 | 2131 | ```cpp 2132 | //VideoCallWidget.cpp 2133 | 2134 | void UVideoCallWidget::OnStartCall( 2135 | const FString& ChannelName, 2136 | const FString& EncryptionKey, 2137 | const FString& EncryptionType) 2138 | { 2139 | if (!VideoCallPtr) 2140 | { 2141 | return; 2142 | } 2143 | 2144 | auto OnLocalFrameCallback = [this]( 2145 | std::uint8_t* Buffer, 2146 | std::uint32_t Width, 2147 | std::uint32_t Height, 2148 | std::uint32_t Size) 2149 | { 2150 | VideoCallViewWidget->UpdateAdditionalVideoBuffer(Buffer, Width, Height, Size); 2151 | }; 2152 | VideoCallPtr->RegisterOnLocalFrameCallback(OnLocalFrameCallback); 2153 | 2154 | auto OnRemoteFrameCallback = [this]( 2155 | std::uint8_t* Buffer, 2156 | std::uint32_t Width, 2157 | std::uint32_t Height, 2158 | std::uint32_t Size) 2159 | { 2160 | VideoCallViewWidget->UpdateMainVideoBuffer(Buffer, Width, Height, Size); 2161 | }; 2162 | VideoCallPtr->RegisterOnRemoteFrameCallback(OnRemoteFrameCallback); 2163 | 2164 | VideoCallPtr->StartCall(ChannelName, EncryptionKey, EncryptionType); 2165 | } 2166 | ``` 2167 | 2168 | ##### Add OnEndCall Method 2169 | 2170 | ```cpp 2171 | //VideoCallWidget.h 2172 | 2173 | ... 2174 | 2175 | UCLASS() 2176 | class AGORAVIDEOCALL_API UVideoCallWidget : public UUserWidget 2177 | { 2178 | GENERATED_BODY() 2179 | 2180 | public: 2181 | 2182 | ... 2183 | 2184 | UFUNCTION(BlueprintCallable) 2185 | void OnEndCall(); 2186 | 2187 | ... 2188 | }; 2189 | ``` 2190 | 2191 | ```cpp 2192 | //VideoCallWidget.cpp 2193 | 2194 | void UVideoCallWidget::OnEndCall() 2195 | { 2196 | if (VideoCallPtr) 2197 | { 2198 | VideoCallPtr->StopCall(); 2199 | } 2200 | 2201 | if (VideoCallViewWidget) 2202 | { 2203 | VideoCallViewWidget->ResetBuffers(); 2204 | } 2205 | 2206 | if (PlayerController) 2207 | { 2208 | SetVisibility(ESlateVisibility::Collapsed); 2209 | PlayerController->EndCall(std::move(VideoCallPtr)); 2210 | } 2211 | } 2212 | ``` 2213 | 2214 | ##### Add OnMuteLocalAudio Method 2215 | 2216 | ```cpp 2217 | //VideoCallWidget.h 2218 | 2219 | ... 2220 | 2221 | UCLASS() 2222 | class AGORAVIDEOCALL_API UVideoCallWidget : public UUserWidget 2223 | { 2224 | GENERATED_BODY() 2225 | 2226 | public: 2227 | 2228 | ... 2229 | 2230 | UFUNCTION(BlueprintCallable) 2231 | void OnMuteLocalAudio(); 2232 | 2233 | ... 2234 | }; 2235 | ``` 2236 | 2237 | ```cpp 2238 | //VideoCallWidget.cpp 2239 | 2240 | void UVideoCallWidget::OnMuteLocalAudio() 2241 | { 2242 | if (!VideoCallPtr) 2243 | { 2244 | return; 2245 | } 2246 | if (VideoCallPtr->IsLocalAudioMuted()) 2247 | { 2248 | VideoCallPtr->MuteLocalAudio(false); 2249 | SetAudioButtonToMute(); 2250 | } 2251 | else 2252 | { 2253 | VideoCallPtr->MuteLocalAudio(true); 2254 | SetAudioButtonToUnMute(); 2255 | } 2256 | } 2257 | ``` 2258 | 2259 | ##### Add OnChangeVideoMode Method 2260 | 2261 | ```cpp 2262 | //VideoCallWidget.h 2263 | 2264 | ... 2265 | 2266 | UCLASS() 2267 | class AGORAVIDEOCALL_API UVideoCallWidget : public UUserWidget 2268 | { 2269 | GENERATED_BODY() 2270 | 2271 | public: 2272 | 2273 | ... 2274 | 2275 | UFUNCTION(BlueprintCallable) 2276 | void OnChangeVideoMode(); 2277 | 2278 | ... 2279 | }; 2280 | ``` 2281 | 2282 | ```cpp 2283 | //VideoCallWidget.cpp 2284 | 2285 | void UVideoCallWidget::OnChangeVideoMode() 2286 | { 2287 | if (!VideoCallPtr) 2288 | { 2289 | return; 2290 | } 2291 | if (!VideoCallPtr->IsLocalVideoMuted()) 2292 | { 2293 | VideoCallPtr->MuteLocalVideo(true); 2294 | 2295 | SetVideoModeButtonToCameraOn(); 2296 | } 2297 | else 2298 | { 2299 | VideoCallPtr->EnableVideo(true); 2300 | VideoCallPtr->MuteLocalVideo(false); 2301 | 2302 | SetVideoModeButtonToCameraOff(); 2303 | } 2304 | } 2305 | ``` 2306 | 2307 | ## Create Blueprint Classes 2308 | 2309 | Make sure C++ code compiles properly. Without successfuly compiled project you will not be able to do the next steps. 2310 | You you have compiled c++ code successfuly and still do not see required classes in Unreal Editor try to reopen the project. 2311 | 2312 | ### Create BP_EnterChannelWidget blueprint asset 2313 | 2314 | Create a Blueprint of UEnterChannelWidget 2315 | Right-click on the **Content**, Select the **User Interface** menu and select **Widget Blueprint**. 2316 | 2317 | ![Create Widget Blueprint](GuideImages/CreateWidgetBlueprint.png) 2318 | 2319 | Change the parent of the class of this new User Widget. 2320 | Open the Blueprint, the UMG Editor Interface appears, and by default it opens to the **Designer** tab. Click on the **Graph** button (right top corner button) and select **Class Settings**. 2321 | On the panel **Details**, click on the drop-down list **Parent Class** and select the C++ class previously created, UEnterChannelWidget. 2322 | Now return to the **Designer** tab. The **Palette** window contains several different types of widgets that you can use to construct your UI elements. 2323 | Find **Text**, **Editable Text**, **Button** and **ComboBox (String)** elements, then drag them to the workspace as you see on the picture. 2324 | Then go to the definition of UEnterChannelWidget in "EnterChannelWidget.h" file to see the names of the member variables with the corresponding types(UTextBlock, UEditableTextBox, UButton and UComboBoxString). 2325 | Return to the BP_VideoCallWiewVidget editor and set same names to UI elements that you have dragged. 2326 | You can do this by clicking on the element and changing the name in the Details panel. Try to compile the blueprint. 2327 | You will be shown an error if you forgot to add something, or if there is widget name/type mismatch inside your UserWidget class. 2328 | 2329 | ![BP_EnterChannelWidget blueprint](GuideImages/BP_EnterChannelWidgetBlueprint.png) 2330 | 2331 | Save it to the prefered folder, for example like /Content/Widgets/BP_EnterChannelWidget.uasset 2332 | 2333 | ### Create BP_VideoViewWidget Asset. 2334 | 2335 | Create BP_VideoViewWidget Asset, set parent class to UVideoViewWidget, and Image element, call it RenderTargetImage. 2336 | 2337 | ![BP_VideoViewWidget Asset](GuideImages/BP_VideoViewWidget_Asset.png) 2338 | 2339 | Its important to set image anchor here: 2340 | 2341 | ![Inage anchor](GuideImages/Anchor.png) 2342 | 2343 | ### Create BP_VideoCallViewWidget Asset. 2344 | 2345 | Create BP_VideoCallViewWidget Asset, set parent class to UVideoCallViewWidget, and add UI elements MainVideoViewWidget and AdditionalVideoViewWidget of BP_VideoViewWidget type. 2346 | Also add MainVideoSizeBox and AdditionalVideoSizeBox UI element of SizeBox type. 2347 | 2348 | ![BP_VideoCallViewWidget Asset](GuideImages/BP_VideoCallViewWidget_Asset.png) 2349 | 2350 | ### Create BP_VideoCallWidget Asset. 2351 | 2352 | Create BP_VideoCallWidget Asset, set parent class to UVideoCallWidget, find in Palette UI element BP_VideoCallViewWidget and add with name VideoCallViewWidget, 2353 | add EndCallButton, MuteLocalAudioButton and VideoModeButton buttons. 2354 | 2355 | ![Now to add custom Widget](GuideImages/AddWidget_View.png) 2356 | 2357 | ![BP_VideoCallWidget Asset](GuideImages/BP_VideoCallWidget_Asset.png) 2358 | 2359 | 2360 | ### Create BP_VideoCallPlayerController blueprint asset 2361 | 2362 | Now it's time to create BP_VideoCallPlayerController blueprint asset, based on the AVideoCallPlayerController class that we described earlier 2363 | 2364 | ![Create CreateBP_mainFlow](https://docs.unrealengine.com/Images/Engine/Blueprints/UserGuide/Types/ClassBlueprint/Creation/CreateBP_mainFlow.jpg) 2365 | 2366 | Create a Blueprint of AVideoCallPlayerController: 2367 | Right-click on the **Content**, press the **Add New** button, select the **Blueprint Class**, in the window **Pick parent class** go to the **All classes** section, and find the VideoCallPlayerController class. 2368 | 2369 | Now assign our previously created widgets to the PlayerController as shown in the following image: 2370 | 2371 | ![BP_VideoCallPlayerController settings](GuideImages/BP_VideoCallPlayerController_settings.png) 2372 | 2373 | Save it to the prefered folder, for example like /Content/Widgets/BP_VideoCallPlayerController.uasset 2374 | 2375 | 2376 | ### Create BP_AgoraVideoCallGameModeBase Asset. 2377 | 2378 | Create a Blueprint of AVideoCallPlayerController 2379 | Right-click on the **Content**, press the **Add New** button, select the **Blueprint Class**, in the window **Pick parent class** 2380 | select the **Game Mode Base** Class. This is the parent class for all Game Modes. 2381 | 2382 | #### Modify GameMode 2383 | 2384 | Now you need to set your custom GameMode class and Player Controller. 2385 | Go to the world settings and set the specified variables: 2386 | ![World Settings](GuideImages/WorldSettings_View.png) 2387 | 2388 | ### Specify project settings 2389 | 2390 | Go to **Edit->Project settings**, open **Maps & Modes** tab. Specify Default parameters: 2391 | 2392 | ![Default Project Settings](GuideImages/Default_Settings.png) --------------------------------------------------------------------------------