From 34a1e9734e269e2180c00c49cd41eb3d9482003e Mon Sep 17 00:00:00 2001 From: Arlin Date: Fri, 21 Jan 2022 18:00:52 +0800 Subject: [PATCH] add feature video enhancement and virtual background base on v3.6.2 macos --- macOS/APIExample.xcodeproj/project.pbxproj | 16 + .../VideoProcess/VideoProcess.storyboard | 229 +++++++++ .../Advanced/VideoProcess/VideoProcess.swift | 444 ++++++++++++++++++ .../Base.lproj/JoinChannelVideo.storyboard | 17 +- .../JoinChannelVideo/JoinChannelVideo.swift | 56 +-- macOS/APIExample/ViewController.swift | 3 +- .../zh-Hans.lproj/Localizable.strings | 11 +- 7 files changed, 705 insertions(+), 71 deletions(-) create mode 100644 macOS/APIExample/Examples/Advanced/VideoProcess/VideoProcess.storyboard create mode 100644 macOS/APIExample/Examples/Advanced/VideoProcess/VideoProcess.swift diff --git a/macOS/APIExample.xcodeproj/project.pbxproj b/macOS/APIExample.xcodeproj/project.pbxproj index 691d22a03..04fc8faf0 100644 --- a/macOS/APIExample.xcodeproj/project.pbxproj +++ b/macOS/APIExample.xcodeproj/project.pbxproj @@ -87,6 +87,8 @@ 57AF397B259B31AA00601E02 /* RawAudioData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57AF397A259B31AA00601E02 /* RawAudioData.swift */; }; 57AF3981259B329B00601E02 /* RawAudioData.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 57AF3980259B329B00601E02 /* RawAudioData.storyboard */; }; 596A9F79AF0CD8DC1CA93253 /* Pods_APIExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F65EF2B97B89DE4581B426B /* Pods_APIExample.framework */; }; + 67C3646427980E600080DB3A /* VideoProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67C3646327980E600080DB3A /* VideoProcess.swift */; }; + 67C3646A27993A580080DB3A /* VideoProcess.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 67C3646927993A580080DB3A /* VideoProcess.storyboard */; }; 8B6F4EAD276ECC370097E67E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8B6F4EAF276ECC370097E67E /* Localizable.strings */; }; 8B733B8C267B1C0B00CC3DE3 /* bg.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 8B733B8B267B1C0B00CC3DE3 /* bg.jpg */; }; EBDD0209B272C276B21B6270 /* Pods_APIExample_APIExampleUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC2BAB0AC82140B7CEEA31DA /* Pods_APIExample_APIExampleUITests.framework */; }; @@ -227,6 +229,8 @@ 57A635F32593544600EDC2F7 /* effectA.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = effectA.wav; sourceTree = ""; }; 57AF397A259B31AA00601E02 /* RawAudioData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawAudioData.swift; sourceTree = ""; }; 57AF3980259B329B00601E02 /* RawAudioData.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = RawAudioData.storyboard; sourceTree = ""; }; + 67C3646327980E600080DB3A /* VideoProcess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoProcess.swift; sourceTree = ""; }; + 67C3646927993A580080DB3A /* VideoProcess.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = VideoProcess.storyboard; sourceTree = ""; }; 6F65EF2B97B89DE4581B426B /* Pods_APIExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_APIExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 84C863718A380DFD36ABF19F /* Pods-APIExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-APIExample.debug.xcconfig"; path = "Target Support Files/Pods-APIExample/Pods-APIExample.debug.xcconfig"; sourceTree = ""; }; 8B6F4EAE276ECC370097E67E /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -475,6 +479,7 @@ 036D3AA524FB797700B1D8DC /* Advanced */ = { isa = PBXGroup; children = ( + 67C3646227980E1F0080DB3A /* VideoProcess */, 57AF3979259B30BB00601E02 /* RawAudioData */, 576459FD259B1C22007B1E30 /* CreateDataStream */, 033A9F95252EA86A00BC26E1 /* CustomAudioRender */, @@ -639,6 +644,15 @@ path = RawAudioData; sourceTree = ""; }; + 67C3646227980E1F0080DB3A /* VideoProcess */ = { + isa = PBXGroup; + children = ( + 67C3646327980E600080DB3A /* VideoProcess.swift */, + 67C3646927993A580080DB3A /* VideoProcess.storyboard */, + ); + path = VideoProcess; + sourceTree = ""; + }; 72510F6AF209B24C1F66A819 /* Pods */ = { isa = PBXGroup; children = ( @@ -790,6 +804,7 @@ 033A9FBD252EB02600BC26E1 /* CustomAudioRender.storyboard in Resources */, 034C62A025297ABB00296ECF /* audioeffect.mp3 in Resources */, 03896D3424F8A011008593CD /* Assets.xcassets in Resources */, + 67C3646A27993A580080DB3A /* VideoProcess.storyboard in Resources */, 03896D3724F8A011008593CD /* Main.storyboard in Resources */, 033A9FE0252EB58600BC26E1 /* CustomVideoSourceMediaIO.storyboard in Resources */, 8B733B8C267B1C0B00CC3DE3 /* bg.jpg in Resources */, @@ -944,6 +959,7 @@ 034C629C25295F2800296ECF /* AudioMixing.swift in Sources */, 03267E222500C265004A91A6 /* AgoraMediaDataPlugin.mm in Sources */, 036D3AA224FAA00A00B1D8DC /* Configs.swift in Sources */, + 67C3646427980E600080DB3A /* VideoProcess.swift in Sources */, 03267E1C24FF3AF4004A91A6 /* AgoraCameraSourcePush.swift in Sources */, 034C626C25259FC200296ECF /* JoinChannelVideo.swift in Sources */, 033A9FA5252EA86A00BC26E1 /* RawMediaData.swift in Sources */, diff --git a/macOS/APIExample/Examples/Advanced/VideoProcess/VideoProcess.storyboard b/macOS/APIExample/Examples/Advanced/VideoProcess/VideoProcess.storyboard new file mode 100644 index 000000000..0bb772625 --- /dev/null +++ b/macOS/APIExample/Examples/Advanced/VideoProcess/VideoProcess.storyboard @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macOS/APIExample/Examples/Advanced/VideoProcess/VideoProcess.swift b/macOS/APIExample/Examples/Advanced/VideoProcess/VideoProcess.swift new file mode 100644 index 000000000..48b894707 --- /dev/null +++ b/macOS/APIExample/Examples/Advanced/VideoProcess/VideoProcess.swift @@ -0,0 +1,444 @@ +// +// VideoProcess.swift +// APIExample +// +// Created by Arlin on 2022/1/19. +// Copyright © 2022 Agora Corp. All rights reserved. +// + +import Cocoa +import AgoraRtcKit +import AGEVideoLayout + +class VideoProcess: BaseViewController { + + @IBOutlet weak var Container: AGEVideoContainer! + @IBOutlet weak var selectResolutionPicker: Picker! + @IBOutlet weak var selectFpsPicker: Picker! + @IBOutlet weak var selectLayoutPicker: Picker! + @IBOutlet weak var virtualBackgroundSwitch: NSSwitch! + @IBOutlet weak var selectVirtualBackgroundPicker: Picker! + @IBOutlet weak var channelField: Input! + @IBOutlet weak var joinChannelButton: NSButton! + @IBOutlet weak var colorEnhanceSwitch: NSSwitch! + + @IBOutlet weak var lowlightEnhanceLabel: NSTextField! + @IBOutlet weak var videoDenoiseLabel: NSTextField! + @IBOutlet weak var colorEnhanceLabel: NSTextField! + @IBOutlet weak var strenghtLabel: NSTextField! + @IBOutlet weak var skinProtectLabel: NSTextField! + + var videos: [VideoView] = [] + let layouts = [Layout("1v1", 2), Layout("1v3", 4), Layout("1v8", 9), Layout("1v15", 16)] + let backgroundTypes = AgoraVirtualBackgroundSourceType.allValues() + var agoraKit: AgoraRtcEngineKit! + var colorEnhanceOptions = AgoraColorEnhanceOptions() + + // indicate if current instance has joined channel + var isJoined: Bool = false { + didSet { + channelField.isEnabled = !isJoined + selectLayoutPicker.isEnabled = !isJoined + joinChannelButton.title = isJoined ? "Leave Channel".localized : "Join Channel".localized + } + } + + // MARK: - LifeCycle + override func viewDidLoad() { + super.viewDidLoad() + self.setupUI() + self.setupAgoraKit() + } + + func setupAgoraKit() { + let config = AgoraRtcEngineConfig() + config.appId = KeyCenter.AppId + config.areaCode = GlobalSettings.shared.area.rawValue + agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self) + + agoraKit.setChannelProfile(.liveBroadcasting) + agoraKit.setClientRole(.broadcaster) + + agoraKit.enableVideo() + } + + override func viewWillBeRemovedFromSplitView() { + if isJoined { + agoraKit.disableVideo() + agoraKit.leaveChannel { (stats:AgoraChannelStats) in + LogUtils.log(message: "Left channel", level: .info) + } + } + AgoraRtcEngineKit.destroy() + } + + // MARK: - UI + func setupUI() { + channelField.label.stringValue = "Channel".localized + channelField.field.placeholderString = "Channel Name".localized + joinChannelButton.title = isJoined ? "Leave Channel".localized : "Join Channel".localized + lowlightEnhanceLabel.stringValue = "Low light Enhancement".localized + videoDenoiseLabel.stringValue = "Video Denoise".localized + colorEnhanceLabel.stringValue = "Color Enhancement".localized + strenghtLabel.stringValue = "Strength".localized + skinProtectLabel.stringValue = "Skin Protect".localized + + initSelectResolutionPicker() + initSelectFpsPicker() + initSelectLayoutPicker() + initSelectBackgroundPicker() + } + + @IBAction func onJoinButtonPressed(_ sender: NSButton) { + if !isJoined { + let channel = channelField.stringValue + guard !channel.isEmpty, + let resolution = selectedResolution(), + let fps = selectedFps() else { + return + } + + agoraKit.setVideoEncoderConfiguration( + AgoraVideoEncoderConfiguration( + size: resolution.size(), + frameRate: AgoraVideoFrameRate(rawValue: fps) ?? .fps15, + bitrate: AgoraVideoBitrateStandard, + orientationMode: .adaptative + ) + ) + + // set up local video to render your local camera preview + let localVideo = videos[0] + let videoCanvas = AgoraRtcVideoCanvas() + videoCanvas.uid = 0 + // the view to be binded + videoCanvas.view = localVideo.videocanvas + videoCanvas.renderMode = .hidden + agoraKit.setupLocalVideo(videoCanvas) + + setVirtualBackground() + + // start joining channel + // 1. Users can only see each other after they join the + // same channel successfully using the same app id. + // 2. If app certificate is turned on at dashboard, token is needed + // when joining channel. The channel name and uid used to calculate + // the token has to match the ones used for channel join + let option = AgoraRtcChannelMediaOptions() + let result = agoraKit.joinChannel(byToken: KeyCenter.Token, channelId: channel, info: nil, uid: 0, options: option) + if result != 0 { + // Usually happens with invalid parameters + // Error code description can be found at: + // en: https://docs.agora.io/en/Voice/API%20Reference/oc/Constants/AgoraErrorCode.html + // cn: https://docs.agora.io/cn/Voice/API%20Reference/oc/Constants/AgoraErrorCode.html + self.showAlert(title: "Error", message: "joinChannel call failed: \(result), please check your params") + } + } else { + agoraKit.leaveChannel { (stats:AgoraChannelStats) in + LogUtils.log(message: "Left channel", level: .info) + self.videos[0].uid = nil + self.isJoined = false + self.videos.forEach { + $0.uid = nil + $0.statsLabel.stringValue = "" + } + } + } + } + + @IBAction func onLowLightEnhanceSwitchChange(_ sender: NSSwitch) { + agoraKit.setLowlightEnhanceOptions(sender.state.rawValue != 0, options: nil) + } + + @IBAction func onVideoDenoiseSwitchChange(_ sender: NSSwitch) { + agoraKit.setVideoDenoiserOptions(sender.state.rawValue != 0, options: nil) + } + + @IBAction func onColorEnhanceSwitchChange(_ sender: NSSwitch) { + agoraKit.setColorEnhanceOptions(colorEnhanceSwitch.state.rawValue != 0, options: colorEnhanceOptions) + } + + @IBAction func onStrengthSliderChange(_ sender: NSSlider) { + colorEnhanceOptions.strengthLevel = sender.floatValue + agoraKit.setColorEnhanceOptions(colorEnhanceSwitch.state.rawValue != 0, options: colorEnhanceOptions) + } + + @IBAction func onSkinProtectSliderChange(_ sender: NSSlider) { + colorEnhanceOptions.skinProtectLevel = sender.floatValue + agoraKit.setColorEnhanceOptions(colorEnhanceSwitch.state.rawValue != 0, options: colorEnhanceOptions) + } + + @IBAction func onVirtualBackgroundSwitchChange(_ sender: NSSwitch) { + setVirtualBackground() + } + + func setVirtualBackground(){ + let backgroundSource = AgoraVirtualBackgroundSource() + backgroundSource.backgroundSourceType = selectedBackgroundType() ?? .img + switch backgroundSource.backgroundSourceType { + case .color: + backgroundSource.color = 0xFFFFFF + break + case .img: + if let resourcePath = Bundle.main.resourcePath { + let imgPath = resourcePath + "/" + "bg.jpg" + backgroundSource.source = imgPath + } + break + case .blur: + backgroundSource.blur_degree = .high + break + default: + break + } + agoraKit.enableVirtualBackground(virtualBackgroundSwitch.state.rawValue != 0, backData: backgroundSource) + } + + func initSelectBackgroundPicker() { + selectVirtualBackgroundPicker.label.stringValue = "Virtual Background".localized + selectVirtualBackgroundPicker.picker.addItems(withTitles: backgroundTypes.map { $0.description() }) + + selectVirtualBackgroundPicker.onSelectChanged { + guard self.selectedBackgroundType() != nil else { return } + self.setVirtualBackground() + } + } + + func selectedBackgroundType() ->AgoraVirtualBackgroundSourceType? { + let index = selectVirtualBackgroundPicker.indexOfSelectedItem + if index >= 0 && index < backgroundTypes.count { + return backgroundTypes[index] + } else { + return nil + } + } + + // MARK: Vedio Setting + func initSelectResolutionPicker() { + selectResolutionPicker.label.stringValue = "Resolution".localized + selectResolutionPicker.picker.addItems(withTitles: Configs.Resolutions.map { $0.name() }) + selectResolutionPicker.picker.selectItem(at: GlobalSettings.shared.resolutionSetting.selectedOption().value) + + selectResolutionPicker.onSelectChanged { + if !self.isJoined { + return + } + + guard let resolution = self.selectedResolution(), + let fps = self.selectedFps() else { + return + } + self.agoraKit.setVideoEncoderConfiguration( + AgoraVideoEncoderConfiguration( + size: resolution.size(), + frameRate: AgoraVideoFrameRate(rawValue: fps) ?? .fps15, + bitrate: AgoraVideoBitrateStandard, + orientationMode: .adaptative + ) + ) + } + } + + func selectedResolution() -> Resolution? { + let index = self.selectResolutionPicker.indexOfSelectedItem + if index >= 0 && index < Configs.Resolutions.count { + return Configs.Resolutions[index] + } else { + return nil + } + } + + func initSelectFpsPicker() { + selectFpsPicker.label.stringValue = "Frame Rate".localized + selectFpsPicker.picker.addItems(withTitles: Configs.Fps.map { "\($0)fps" }) + selectFpsPicker.picker.selectItem(at: GlobalSettings.shared.fpsSetting.selectedOption().value) + + selectFpsPicker.onSelectChanged { + if !self.isJoined { + return + } + + guard let resolution = self.selectedResolution(), + let fps = self.selectedFps() else { + return + } + self.agoraKit.setVideoEncoderConfiguration( + AgoraVideoEncoderConfiguration( + size: resolution.size(), + frameRate: AgoraVideoFrameRate(rawValue: fps) ?? .fps15, + bitrate: AgoraVideoBitrateStandard, + orientationMode: .adaptative + ) + ) + } + } + + func selectedFps() -> Int? { + let index = self.selectFpsPicker.indexOfSelectedItem + if index >= 0 && index < Configs.Fps.count { + return Configs.Fps[index] + } else { + return nil + } + } + + func initSelectLayoutPicker() { + layoutVideos(2) + selectLayoutPicker.label.stringValue = "Layout".localized + selectLayoutPicker.picker.addItems(withTitles: layouts.map { $0.label }) + selectLayoutPicker.onSelectChanged { + if self.isJoined { + return + } + guard let layout = self.selectedLayout() else { return } + self.layoutVideos(layout.value) + } + } + + func selectedLayout() ->Layout? { + let index = self.selectLayoutPicker.indexOfSelectedItem + if index >= 0 && index < layouts.count { + return layouts[index] + } else { + return nil + } + } + + func layoutVideos(_ count: Int) { + videos = [] + for i in 0...count - 1 { + let view = VideoView.createFromNib()! + if(i == 0) { + view.placeholder.stringValue = "Local" + view.type = .local + view.statsInfo = StatisticsInfo(type: .local(StatisticsInfo.LocalInfo())) + } else { + view.placeholder.stringValue = "Remote \(i)" + view.type = .remote + view.statsInfo = StatisticsInfo(type: .remote(StatisticsInfo.RemoteInfo())) + } + videos.append(view) + } + // layout render view + Container.layoutStream(views: videos) + } +} + +/// agora rtc engine delegate events +extension VideoProcess: AgoraRtcEngineDelegate { + /// callback when warning occured for agora sdk, warning can usually be ignored, still it's nice to check out + /// what is happening + /// Warning code description can be found at: + /// en: https://docs.agora.io/en/Voice/API%20Reference/oc/Constants/AgoraWarningCode.html + /// cn: https://docs.agora.io/cn/Voice/API%20Reference/oc/Constants/AgoraWarningCode.html + /// @param warningCode warning code of the problem + func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurWarning warningCode: AgoraWarningCode) { + LogUtils.log(message: "warning: \(warningCode.rawValue)", level: .warning) + } + + /// callback when error occured for agora sdk, you are recommended to display the error descriptions on demand + /// to let user know something wrong is happening + /// Error code description can be found at: + /// en: https://docs.agora.io/en/Voice/API%20Reference/oc/Constants/AgoraErrorCode.html + /// cn: https://docs.agora.io/cn/Voice/API%20Reference/oc/Constants/AgoraErrorCode.html + /// @param errorCode error code of the problem + func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurError errorCode: AgoraErrorCode) { + LogUtils.log(message: "error: \(errorCode)", level: .error) + self.showAlert(title: "Error", message: "Error \(errorCode.rawValue) occur") + } + + /// callback when the local user joins a specified channel. + /// @param channel + /// @param uid uid of local user + /// @param elapsed time elapse since current sdk instance join the channel in ms + func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) { + isJoined = true + let localVideo = videos[0] + localVideo.uid = uid + LogUtils.log(message: "Join \(channel) with uid \(uid) elapsed \(elapsed)ms", level: .info) + } + + /// callback when a remote user is joinning the channel, note audience in live broadcast mode will NOT trigger this event + /// @param uid uid of remote joined user + /// @param elapsed time elapse since current sdk instance join the channel in ms + func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) { + LogUtils.log(message: "remote user join: \(uid) \(elapsed)ms", level: .info) + + // find a VideoView w/o uid assigned + if let remoteVideo = videos.first(where: { $0.uid == nil }) { + let videoCanvas = AgoraRtcVideoCanvas() + videoCanvas.uid = uid + // the view to be binded + videoCanvas.view = remoteVideo.videocanvas + videoCanvas.renderMode = .hidden + agoraKit.setupRemoteVideo(videoCanvas) + remoteVideo.uid = uid + } else { + LogUtils.log(message: "no video canvas available for \(uid), cancel bind", level: .warning) + } + } + + /// callback when a remote user is leaving the channel, note audience in live broadcast mode will NOT trigger this event + /// @param uid uid of remote joined user + /// @param reason reason why this user left, note this event may be triggered when the remote user + /// become an audience in live broadcasting profile + func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) { + LogUtils.log(message: "remote user left: \(uid) reason \(reason)", level: .info) + + // to unlink your view from sdk, so that your view reference will be released + // note the video will stay at its last frame, to completely remove it + // you will need to remove the EAGL sublayer from your binded view + if let remoteVideo = videos.first(where: { $0.uid == uid }) { + let videoCanvas = AgoraRtcVideoCanvas() + videoCanvas.uid = uid + // the view to be binded + videoCanvas.view = nil + videoCanvas.renderMode = .hidden + agoraKit.setupRemoteVideo(videoCanvas) + remoteVideo.uid = nil + } else { + LogUtils.log(message: "no matching video canvas for \(uid), cancel unbind", level: .warning) + } + } + + /// Reports the statistics of the current call. The SDK triggers this callback once every two seconds after the user joins the channel. + /// @param stats stats struct + func rtcEngine(_ engine: AgoraRtcEngineKit, reportRtcStats stats: AgoraChannelStats) { + videos[0].statsInfo?.updateChannelStats(stats) + } + + /// Reports the statistics of the uploading local video streams once every two seconds. + /// @param stats stats struct + func rtcEngine(_ engine: AgoraRtcEngineKit, localVideoStats stats: AgoraRtcLocalVideoStats) { + videos[0].statsInfo?.updateLocalVideoStats(stats) + } + + /// Reports the statistics of the uploading local audio streams once every two seconds. + /// @param stats stats struct + func rtcEngine(_ engine: AgoraRtcEngineKit, localAudioStats stats: AgoraRtcLocalAudioStats) { + videos[0].statsInfo?.updateLocalAudioStats(stats) + } + + /// Reports the statistics of the video stream from each remote user/host. + /// @param stats stats struct + func rtcEngine(_ engine: AgoraRtcEngineKit, remoteVideoStats stats: AgoraRtcRemoteVideoStats) { + videos.first(where: { $0.uid == stats.uid })?.statsInfo?.updateVideoStats(stats) + } + + /// Reports the statistics of the audio stream from each remote user/host. + /// @param stats stats struct for current call statistics + func rtcEngine(_ engine: AgoraRtcEngineKit, remoteAudioStats stats: AgoraRtcRemoteAudioStats) { + videos.first(where: { $0.uid == stats.uid })?.statsInfo?.updateAudioStats(stats) + } + + /// Reports the video background substitution success or failed. + /// @param enabled whether background substitution is enabled. + /// @param reason The reason of the background substitution callback. See [AgoraVideoBackgroundSourceStateReason](AgoraVideoBackgroundSourceStateReason). + + func rtcEngine(_ engine: AgoraRtcEngineKit, virtualBackgroundSourceEnabled enabled: Bool, reason: AgoraVirtualBackgroundSourceStateReason) { + if reason != .vbsStateReasonSuccess { + LogUtils.log(message: "background substitution failed to enabled for \(reason.rawValue)", level: .warning) + } + } +} + diff --git a/macOS/APIExample/Examples/Basic/JoinChannelVideo/Base.lproj/JoinChannelVideo.storyboard b/macOS/APIExample/Examples/Basic/JoinChannelVideo/Base.lproj/JoinChannelVideo.storyboard index 9ff1bbc31..383683219 100644 --- a/macOS/APIExample/Examples/Basic/JoinChannelVideo/Base.lproj/JoinChannelVideo.storyboard +++ b/macOS/APIExample/Examples/Basic/JoinChannelVideo/Base.lproj/JoinChannelVideo.storyboard @@ -1,8 +1,8 @@ - + - + @@ -52,21 +52,10 @@ - - - - - - - - - - - @@ -137,14 +126,12 @@ - - diff --git a/macOS/APIExample/Examples/Basic/JoinChannelVideo/JoinChannelVideo.swift b/macOS/APIExample/Examples/Basic/JoinChannelVideo/JoinChannelVideo.swift index 3ed6c67be..6a5a5c1eb 100644 --- a/macOS/APIExample/Examples/Basic/JoinChannelVideo/JoinChannelVideo.swift +++ b/macOS/APIExample/Examples/Basic/JoinChannelVideo/JoinChannelVideo.swift @@ -15,10 +15,7 @@ class JoinChannelVideoMain: BaseViewController { var videos: [VideoView] = [] @IBOutlet weak var Container: AGEVideoContainer! - - var isVirtualBackgroundEnabled: Bool = false - @IBOutlet weak var virtualBackgroundSwitch: NSSwitch! - + /** --- Cameras Picker --- */ @@ -220,29 +217,7 @@ class JoinChannelVideoMain: BaseViewController { } } } - - /** - --- Background Picker --- - */ - @IBOutlet weak var selectBackgroundPicker: Picker! - private let backgroundTypes = AgoraVirtualBackgroundSourceType.allValues() - var selectedBackgroundType: AgoraVirtualBackgroundSourceType? { - let index = self.selectBackgroundPicker.indexOfSelectedItem - if index >= 0 && index < backgroundTypes.count { - return backgroundTypes[index] - } else { - return nil - } - } - func initSelectBackgroundPicker() { - selectBackgroundPicker.label.stringValue = "Virtual Background".localized - selectBackgroundPicker.picker.addItems(withTitles: backgroundTypes.map { $0.description() }) - selectBackgroundPicker.onSelectChanged { - guard self.selectedBackgroundType != nil else { return } - self.setBackground() - } - } - + /** --- Channel TextField --- */ @@ -284,36 +259,16 @@ class JoinChannelVideoMain: BaseViewController { config.areaCode = GlobalSettings.shared.area.rawValue agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self) agoraKit.enableVideo() - setBackground() initSelectCameraPicker() initSelectResolutionPicker() initSelectFpsPicker() initSelectMicsPicker() initSelectLayoutPicker() initSelectRolePicker() - initSelectBackgroundPicker() initChannelField() initJoinChannelButton() } - - private func setBackground(){ - let backgroundSource = AgoraVirtualBackgroundSource() - backgroundSource.backgroundSourceType = selectedBackgroundType ?? .img - switch self.selectedBackgroundType { - case .color: - backgroundSource.color = 0x000000 - case .img: - if let resourcePath = Bundle.main.resourcePath { - let imgName = "bg.jpg" - let path = resourcePath + "/" + imgName - backgroundSource.source = path - } - default: - break - } - agoraKit.enableVirtualBackground(isVirtualBackgroundEnabled, backData: backgroundSource) - } - + func layoutVideos(_ count: Int) { videos = [] for i in 0...count - 1 { @@ -333,11 +288,6 @@ class JoinChannelVideoMain: BaseViewController { Container.layoutStream(views: videos) } - @IBAction func onSwitchVirtualBackground(_ sender: NSSwitch) { - isVirtualBackgroundEnabled = (sender.state.rawValue != 0) - setBackground() - } - @IBAction func onVideoCallButtonPressed(_ sender: NSButton) { if !isJoined { // check configuration diff --git a/macOS/APIExample/ViewController.swift b/macOS/APIExample/ViewController.swift index d57fbdea3..2290ab6b5 100644 --- a/macOS/APIExample/ViewController.swift +++ b/macOS/APIExample/ViewController.swift @@ -39,7 +39,8 @@ class MenuController: NSViewController { MenuItem(name: "Voice Changer".localized, identifier: "menuCell", controller: "VoiceChanger", storyboard: "VoiceChanger"), MenuItem(name: "Create Data Stream".localized, identifier: "menuCell", controller: "CreateDataStream", storyboard: "CreateDataStream"), MenuItem(name: "Raw Audio Data".localized, identifier: "menuCell", controller: "RawAudioData", storyboard: "RawAudioData"), - MenuItem(name: "Precall Test".localized, identifier: "menuCell", controller: "PrecallTest", storyboard: "PrecallTest") + MenuItem(name: "Precall Test".localized, identifier: "menuCell", controller: "PrecallTest", storyboard: "PrecallTest"), + MenuItem(name: "Video Process".localized, identifier: "menuCell", controller: "Video Process", storyboard: "VideoProcess") ] @IBOutlet weak var tableView:NSTableView! diff --git a/macOS/APIExample/zh-Hans.lproj/Localizable.strings b/macOS/APIExample/zh-Hans.lproj/Localizable.strings index b52135e82..2b1774163 100644 --- a/macOS/APIExample/zh-Hans.lproj/Localizable.strings +++ b/macOS/APIExample/zh-Hans.lproj/Localizable.strings @@ -158,9 +158,16 @@ "Video Source" = "视频源选择"; "Sending" = "发送中"; "None" = "无背景"; +"Start Video/Audio Echo Test" = "开始音视频回路测试"; +"Stop Video/Audio Echo Test" = "停止音视频回路测试"; +"Video Process" = "视频增强"; +"Low light Enhancement" = "暗光增强"; +"Video Denoise" = "视频降噪"; +"Color Enhancement" = "色彩增强"; +"Strength" = "强度"; +"Skin Protect" = "肤色保护"; "Colored Background" = "纯色背景"; "Image Background" = "图片背景"; "Virtual Background" = "虚拟背景"; "Blur Background" = "背景虚化"; -"Start Video/Audio Echo Test" = "开始音视频回路测试"; -"Stop Video/Audio Echo Test" = "停止音视频回路测试"; +