diff --git a/android/build.gradle b/android/build.gradle index e35637e06..273554e74 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -50,5 +50,5 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${safeExtGet('kotlin_version', '1.3.72')}" - implementation "io.agora.rtc:full-sdk:3.3.2" + implementation "io.agora.rtc:full-sdk:3.4.1" } diff --git a/android/src/main/java/io/agora/rtc/base/Annotations.java b/android/src/main/java/io/agora/rtc/base/Annotations.java index 784a30968..ddcbb1e24 100644 --- a/android/src/main/java/io/agora/rtc/base/Annotations.java +++ b/android/src/main/java/io/agora/rtc/base/Annotations.java @@ -98,13 +98,20 @@ public class Annotations { } @IntDef({ - Constants.MEDIA_ENGINE_AUDIO_ERROR_MIXING_OPEN, - Constants.MEDIA_ENGINE_AUDIO_ERROR_MIXING_TOO_FREQUENT, - Constants.MEDIA_ENGINE_AUDIO_EVENT_MIXING_INTERRUPTED_EOF, - AgoraAudioMixingErrorCode.MEDIA_ENGINE_AUDIO_ERROR_OK, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface AgoraAudioMixingErrorCode { + Constants.AUDIO_MIXING_REASON_CAN_NOT_OPEN, + Constants.AUDIO_MIXING_REASON_TOO_FREQUENT_CALL, + Constants.AUDIO_MIXING_REASON_INTERRUPTED_EOF, + Constants.AUDIO_MIXING_REASON_STARTED_BY_USER, + Constants.AUDIO_MIXING_REASON_ONE_LOOP_COMPLETED, + Constants.AUDIO_MIXING_REASON_START_NEW_LOOP, + Constants.AUDIO_MIXING_REASON_ALL_LOOPS_COMPLETED, + Constants.AUDIO_MIXING_REASON_STOPPED_BY_USER, + Constants.AUDIO_MIXING_REASON_PAUSED_BY_USER, + Constants.AUDIO_MIXING_REASON_RESUMED_BY_USER, + AgoraAudioMixingReason.MEDIA_ENGINE_AUDIO_ERROR_OK, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AgoraAudioMixingReason { int MEDIA_ENGINE_AUDIO_ERROR_OK = 0; } @@ -534,6 +541,7 @@ public class Annotations { Constants.LOCAL_VIDEO_STREAM_ERROR_DEVICE_BUSY, Constants.LOCAL_VIDEO_STREAM_ERROR_CAPTURE_FAILURE, Constants.LOCAL_VIDEO_STREAM_ERROR_ENCODE_FAILURE, + Constants.LOCAL_VIDEO_STREAM_ERROR_DEVICE_NOT_FOUND }) @Retention(RetentionPolicy.SOURCE) public @interface AgoraLocalVideoStreamError { diff --git a/android/src/main/java/io/agora/rtc/base/BeanCovertor.kt b/android/src/main/java/io/agora/rtc/base/BeanCovertor.kt index 3ae90efde..6b3150b4d 100644 --- a/android/src/main/java/io/agora/rtc/base/BeanCovertor.kt +++ b/android/src/main/java/io/agora/rtc/base/BeanCovertor.kt @@ -2,6 +2,8 @@ package io.agora.rtc.base import android.graphics.Color import io.agora.rtc.RtcEngineConfig +import io.agora.rtc.audio.AgoraRhythmPlayerConfig +import io.agora.rtc.audio.AudioRecordingConfiguration import io.agora.rtc.internal.EncryptionConfig import io.agora.rtc.internal.LastmileProbeConfig import io.agora.rtc.live.LiveInjectStreamConfig @@ -160,6 +162,14 @@ fun mapToLiveInjectStreamConfig(map: Map<*, *>): LiveInjectStreamConfig { } } +fun mapToRhythmPlayerConfig(map: Map<*, *>): AgoraRhythmPlayerConfig { + return AgoraRhythmPlayerConfig().apply { + (map["beatsPerMeasure"] as? Number)?.let { beatsPerMeasure = it.toInt() } + (map["beatsPerMinute"] as? Number)?.let { beatsPerMinute = it.toInt() } + (map["publish"] as? Boolean)?.let { publish = it } + } +} + fun mapToCameraCapturerConfiguration(map: Map<*, *>): CameraCapturerConfiguration { return CameraCapturerConfiguration( intToCapturerOutputPreference((map["preference"] as Number).toInt()), @@ -186,6 +196,15 @@ fun mapToRtcEngineConfig(map: Map<*, *>): RtcEngineConfig { } } +fun mapToAudioRecordingConfiguration(map: Map<*, *>): AudioRecordingConfiguration { + return AudioRecordingConfiguration().apply { + (map["filePath"] as? String)?.let { filePath = it } + (map["quality"] as? Number)?.let { recordingQuality = it.toInt() } + (map["recordingPosition"] as? Number)?.let { recordingPosition = it.toInt() } + (map["sampleRate"] as? Number)?.let { recordingSampleRate = it.toInt() } + } +} + fun mapToEncryptionConfig(map: Map<*, *>): EncryptionConfig { return EncryptionConfig().apply { (map["encryptionMode"] as? Number)?.let { encryptionMode = intToEncryptionMode(it.toInt()) } diff --git a/android/src/main/java/io/agora/rtc/base/RtcEngine.kt b/android/src/main/java/io/agora/rtc/base/RtcEngine.kt index c245fd2bb..62c195295 100644 --- a/android/src/main/java/io/agora/rtc/base/RtcEngine.kt +++ b/android/src/main/java/io/agora/rtc/base/RtcEngine.kt @@ -101,6 +101,12 @@ class IRtcEngine { fun setDefaultMuteAllRemoteAudioStreams(params: Map, callback: Callback) fun enableAudioVolumeIndication(params: Map, callback: Callback) + + fun startRhythmPlayer(params: Map,callback: Callback) + + fun stopRhythmPlayer(callback: Callback) + + fun configRhythmPlayer(params: Map,callback: Callback) } interface RtcVideoInterface { @@ -149,7 +155,7 @@ class IRtcEngine { fun getAudioMixingPublishVolume(callback: Callback) - fun getAudioMixingDuration(callback: Callback) + fun getAudioMixingDuration(params: Map, callback: Callback) fun getAudioMixingCurrentPosition(callback: Callback) @@ -167,6 +173,12 @@ class IRtcEngine { fun playEffect(params: Map, callback: Callback) + fun setEffectPosition(params: Map, callback: Callback) + + fun getEffectDuration(params: Map, callback: Callback) + + fun getEffectCurrentPosition(params: Map, callback: Callback) + fun stopEffect(params: Map, callback: Callback) fun stopAllEffects(callback: Callback) @@ -563,6 +575,17 @@ class RtcEngineManager( override fun enableAudioVolumeIndication(params: Map, callback: Callback) { callback.code(engine?.enableAudioVolumeIndication((params["interval"] as Number).toInt(), (params["smooth"] as Number).toInt(), params["report_vad"] as Boolean)) } + override fun startRhythmPlayer(params: Map,callback: Callback){ + callback.code(engine?.audioEffectManager?.startRhythmPlayer(params["sound1"] as String, params["sound2"] as String,mapToRhythmPlayerConfig(params["config"] as Map<*, *>))) + } + + override fun stopRhythmPlayer(callback: Callback){ + callback.code(engine?.audioEffectManager?.stopRhythmPlayer()) + } + + override fun configRhythmPlayer(params: Map,callback: Callback){ + callback.code(engine?.audioEffectManager?.configRhythmPlayer(mapToRhythmPlayerConfig(params as Map<*, *>))) + } override fun enableVideo(callback: Callback) { callback.code(engine?.enableVideo()) @@ -613,6 +636,10 @@ class RtcEngineManager( } override fun startAudioMixing(params: Map, callback: Callback) { + (params["startPos"] as? Number)?.let { startPos -> + callback.code(engine?.startAudioMixing(params["filePath"] as String, params["loopback"] as Boolean, params["replace"] as Boolean, (params["cycle"] as Number).toInt(), startPos.toInt())) + return@startAudioMixing + } callback.code(engine?.startAudioMixing(params["filePath"] as String, params["loopback"] as Boolean, params["replace"] as Boolean, (params["cycle"] as Number).toInt())) } @@ -648,7 +675,11 @@ class RtcEngineManager( callback.code(engine?.audioMixingPublishVolume) { it } } - override fun getAudioMixingDuration(callback: Callback) { + override fun getAudioMixingDuration(params: Map, callback: Callback) { + (params["filePath"] as? String)?.let { file -> + callback.code(engine?.getAudioMixingDuration(file)){ it } + return@getAudioMixingDuration + } callback.code(engine?.audioMixingDuration) { it } } @@ -677,9 +708,27 @@ class RtcEngineManager( } override fun playEffect(params: Map, callback: Callback) { + (params["startPos"] as? Number)?.let { startPos -> + callback.code(engine?.audioEffectManager?.playEffect((params["soundId"] as Number).toInt(), params["filePath"] as String, (params["loopCount"] as Number).toInt(), (params["pitch"] as Number).toDouble(), (params["pan"] as Number).toDouble(), (params["gain"] as Number).toDouble(), params["publish"] as Boolean, startPos.toInt())) + return@playEffect + } callback.code(engine?.audioEffectManager?.playEffect((params["soundId"] as Number).toInt(), params["filePath"] as String, (params["loopCount"] as Number).toInt(), (params["pitch"] as Number).toDouble(), (params["pan"] as Number).toDouble(), (params["gain"] as Number).toDouble(), params["publish"] as Boolean)) } + override fun setEffectPosition(params: Map, callback: Callback){ + callback.code(engine?.audioEffectManager?.setEffectPosition((params["soundId"] as Number).toInt(), (params["pos"] as Number).toInt())) + } + + override fun getEffectDuration(params: Map, callback: Callback){ + callback.code(engine?.audioEffectManager?.getEffectDuration(params["filePath"] as String)){ + it + } + } + + override fun getEffectCurrentPosition(params: Map, callback: Callback){ + callback.code(engine?.audioEffectManager?.getEffectCurrentPosition((params["soundId"] as Number).toInt())){ it } + } + override fun stopEffect(params: Map, callback: Callback) { callback.code(engine?.audioEffectManager?.stopEffect((params["soundId"] as Number).toInt())) } @@ -912,6 +961,10 @@ class RtcEngineManager( } override fun startAudioRecording(params: Map, callback: Callback) { + (params["recordingPosition"] as? Number)?.let { + callback.code(engine?.startAudioRecording(mapToAudioRecordingConfiguration(params))) + return@startAudioRecording + } callback.code(engine?.startAudioRecording(params["filePath"] as String, (params["sampleRate"] as Number).toInt(), (params["quality"] as Number).toInt())) } diff --git a/android/src/main/java/io/agora/rtc/base/RtcEngineEvent.kt b/android/src/main/java/io/agora/rtc/base/RtcEngineEvent.kt index 8a003e383..d7cdffb2a 100644 --- a/android/src/main/java/io/agora/rtc/base/RtcEngineEvent.kt +++ b/android/src/main/java/io/agora/rtc/base/RtcEngineEvent.kt @@ -360,8 +360,8 @@ class RtcEngineEventHandler( callback(RtcEngineEvents.AudioMixingFinished) } - override fun onAudioMixingStateChanged(@Annotations.AgoraAudioMixingStateCode state: Int, @Annotations.AgoraAudioMixingErrorCode errorCode: Int) { - callback(RtcEngineEvents.AudioMixingStateChanged, state, errorCode) + override fun onAudioMixingStateChanged(@Annotations.AgoraAudioMixingStateCode state: Int, @Annotations.AgoraAudioMixingReason reasonCode: Int) { + callback(RtcEngineEvents.AudioMixingStateChanged, state, reasonCode) } override fun onAudioEffectFinished(soundId: Int) { diff --git a/example/ios/Podfile b/example/ios/Podfile index 6697f0a53..1e8c3c90a 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -10,78 +10,32 @@ project 'Runner', { 'Release' => :release, } -def parse_KV_file(file, separator='=') - file_abs_path = File.expand_path(file) - if !File.exists? file_abs_path - return []; +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end - generated_key_values = {} - skip_line_start_symbols = ["#", "/"] - File.foreach(file_abs_path) do |line| - next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } - plugin = line.split(pattern=separator) - if plugin.length == 2 - podname = plugin[0].strip() - path = plugin[1].strip() - podpath = File.expand_path("#{path}", file_abs_path) - generated_key_values[podname] = podpath - else - puts "Invalid plugin specification: #{line}" - end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches end - generated_key_values + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + target 'Runner' do use_frameworks! use_modular_headers! - # Flutter Pod - - copied_flutter_dir = File.join(__dir__, 'Flutter') - copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') - copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') - unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) - # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. - # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. - # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. - - generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') - unless File.exist?(generated_xcode_build_settings_path) - raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) - cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; - - unless File.exist?(copied_framework_path) - FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) - end - unless File.exist?(copied_podspec_path) - FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) - end - end - - # Keep pod path relative so it can be checked into Podfile.lock. - pod 'Flutter', :path => 'Flutter' - - # Plugin Pods - - # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock - # referring to absolute paths on developers' machines. - system('rm -rf .symlinks') - system('mkdir -p .symlinks/plugins') - plugin_pods = parse_KV_file('../.flutter-plugins') - plugin_pods.each do |name, path| - symlink = File.join('.symlinks', 'plugins', name) - File.symlink(path, symlink) - pod name, :path => File.join(symlink, 'ios') - end + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['ENABLE_BITCODE'] = 'NO' - end + flutter_additional_ios_build_settings(target) end end diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/ios/Classes/Base/BeanCovertor.swift b/ios/Classes/Base/BeanCovertor.swift index 3b79590fb..860ffe8a8 100644 --- a/ios/Classes/Base/BeanCovertor.swift +++ b/ios/Classes/Base/BeanCovertor.swift @@ -319,6 +319,27 @@ func mapToRtcEngineConfig(_ map: Dictionary) -> AgoraRtcEngineConfi return config } +func mapToAudioRecordingConfiguration(_ map: Dictionary) -> AgoraAudioRecordingConfiguration { + let config = AgoraAudioRecordingConfiguration() + if let filePath = map["filePath"] as? String { + config.filePath = filePath + } + if let recordingQuality = map["quality"] as? NSNumber { + if let recordingQuality = AgoraAudioRecordingQuality.init(rawValue: recordingQuality.intValue) { + config.recordingQuality = recordingQuality + } + } + if let recordingPosition = map["recordingPosition"] as? NSNumber { + if let recordingPosition = AgoraAudioRecordingPosition.init(rawValue: recordingPosition.intValue) { + config.recordingPosition = recordingPosition + } + } + if let recordingSampleRate = map["sampleRate"] as? NSNumber { + config.recordingSampleRate = recordingSampleRate.intValue + } + return config +} + func mapToEncryptionConfig(_ map: Dictionary) -> AgoraEncryptionConfig { let config = AgoraEncryptionConfig() if let encryptionMode = map["encryptionMode"] as? NSNumber { @@ -332,6 +353,20 @@ func mapToEncryptionConfig(_ map: Dictionary) -> AgoraEncryptionCon return config } +func mapToRhythmPlayerConfig(_ map: Dictionary) -> AgoraRtcRhythmPlayerConfig { + let config = AgoraRtcRhythmPlayerConfig() + if let beatsPerMeasure = map["beatsPerMeasure"] as? NSNumber { + config.beatsPerMeasure = beatsPerMeasure.uintValue; + } + if let beatsPerMinute = map["beatsPerMinute"] as? NSNumber { + config.beatsPerMinute = beatsPerMinute.uintValue; + } + if let publish = map["publish"] as? NSNumber { + config.publish = publish.boolValue; + } + return config +} + func mapToClientRoleOptions(_ map: Dictionary) -> AgoraClientRoleOptions { let options = AgoraClientRoleOptions() if let audienceLatencyLevel = map["audienceLatencyLevel"] as? NSNumber { diff --git a/ios/Classes/Base/RtcEngine.swift b/ios/Classes/Base/RtcEngine.swift index 08a6e1d40..b6fe8dd20 100644 --- a/ios/Classes/Base/RtcEngine.swift +++ b/ios/Classes/Base/RtcEngine.swift @@ -119,6 +119,12 @@ protocol RtcEngineAudioInterface { func setDefaultMuteAllRemoteAudioStreams(_ params: NSDictionary, _ callback: Callback) func enableAudioVolumeIndication(_ params: NSDictionary, _ callback: Callback) + + func startRhythmPlayer(_ params: NSDictionary, _ callback: Callback) + + func stopRhythmPlayer(_ callback: Callback) + + func configRhythmPlayer(_ params: NSDictionary, _ callback: Callback) } protocol RtcEngineVideoInterface { @@ -167,7 +173,7 @@ protocol RtcEngineAudioMixingInterface { func getAudioMixingPublishVolume(_ callback: Callback) - func getAudioMixingDuration(_ callback: Callback) + func getAudioMixingDuration(_ params: NSDictionary, _ callback: Callback) func getAudioMixingCurrentPosition(_ callback: Callback) @@ -184,6 +190,12 @@ protocol RtcEngineAudioEffectInterface { func setVolumeOfEffect(_ params: NSDictionary, _ callback: Callback) func playEffect(_ params: NSDictionary, _ callback: Callback) + + func setEffectPosition(_ params: NSDictionary, _ callback: Callback) + + func getEffectDuration(_ params: NSDictionary, _ callback: Callback) + + func getEffectCurrentPosition(_ params: NSDictionary, _ callback: Callback) func stopEffect(_ params: NSDictionary, _ callback: Callback) @@ -567,6 +579,17 @@ class RtcEngineManager: NSObject, RtcEngineInterface { @objc func enableAudioVolumeIndication(_ params: NSDictionary, _ callback: Callback) { callback.code(engine?.enableAudioVolumeIndication((params["interval"] as! NSNumber).intValue, smooth: (params["smooth"] as! NSNumber).intValue, report_vad: params["report_vad"] as! Bool)) } + @objc func startRhythmPlayer(_ params: NSDictionary,_ callback: Callback){ + callback.code(engine?.startRhythmPlayer(params["sound1"] as! String, sound2: params["sound2"] as! String, config: mapToRhythmPlayerConfig(params["config"] as! Dictionary))) + } + + @objc func stopRhythmPlayer(_ callback: Callback){ + callback.code(engine?.stopRhythmPlayer()) + } + + @objc func configRhythmPlayer(_ params: NSDictionary,_ callback: Callback){ + callback.code(engine?.configRhythmPlayer(mapToRhythmPlayerConfig(params as! Dictionary))) + } @objc func enableVideo(_ callback: Callback) { callback.code(engine?.enableVideo()) @@ -613,6 +636,10 @@ class RtcEngineManager: NSObject, RtcEngineInterface { } @objc func startAudioMixing(_ params: NSDictionary, _ callback: Callback) { + if let startPos = params["startPos"] as? NSNumber { + callback.code(engine?.startAudioMixing(params["filePath"] as! String, loopback: params["loopback"] as! Bool, replace: params["replace"] as! Bool, cycle: (params["cycle"] as! NSNumber).intValue,startPos: startPos.intValue)) + return + } callback.code(engine?.startAudioMixing(params["filePath"] as! String, loopback: params["loopback"] as! Bool, replace: params["replace"] as! Bool, cycle: (params["cycle"] as! NSNumber).intValue)) } @@ -652,7 +679,13 @@ class RtcEngineManager: NSObject, RtcEngineInterface { } } - @objc func getAudioMixingDuration(_ callback: Callback) { + @objc func getAudioMixingDuration(_ params: NSDictionary,_ callback: Callback) { + if let filePath = (params["filePath"] as? String) { + callback.code(engine?.getAudioMixingDuration(filePath)) { + $0 + } + return + } callback.code(engine?.getAudioMixingDuration()) { $0 } @@ -687,9 +720,26 @@ class RtcEngineManager: NSObject, RtcEngineInterface { } @objc func playEffect(_ params: NSDictionary, _ callback: Callback) { + if let startPos = (params["startPos"] as? NSNumber) { + callback.code(engine?.playEffect((params["soundId"] as! NSNumber).int32Value, filePath: params["filePath"] as? String, loopCount: (params["loopCount"] as! NSNumber).int32Value, pitch: (params["pitch"] as! NSNumber).doubleValue, pan: (params["pan"] as! NSNumber).doubleValue, gain: (params["gain"] as! NSNumber).doubleValue, publish: params["publish"] as! Bool, startPos: startPos.int32Value)) + return + } callback.code(engine?.playEffect((params["soundId"] as! NSNumber).int32Value, filePath: params["filePath"] as? String, loopCount: (params["loopCount"] as! NSNumber).int32Value, pitch: (params["pitch"] as! NSNumber).doubleValue, pan: (params["pan"] as! NSNumber).doubleValue, gain: (params["gain"] as! NSNumber).doubleValue, publish: params["publish"] as! Bool)) } + @objc func setEffectPosition(_ params: NSDictionary, _ callback: Callback) { + callback.code(engine?.setEffectPosition((params["soundId"] as! NSNumber).int32Value, pos: (params["pos"] as! NSNumber).intValue)) + } + @objc func getEffectDuration(_ params: NSDictionary, _ callback: Callback) { + callback.code(engine?.getEffectDuration(params["filePath"] as? String)){ + $0 + } + } + @objc func getEffectCurrentPosition(_ params: NSDictionary, _ callback: Callback) { + callback.code(engine?.getEffectCurrentPosition((params["soundId"] as! NSNumber).int32Value)){ + $0 + } + } @objc func stopEffect(_ params: NSDictionary, _ callback: Callback) { callback.code(engine?.stopEffect((params["soundId"] as! NSNumber).int32Value)) } @@ -930,7 +980,11 @@ class RtcEngineManager: NSObject, RtcEngineInterface { } @objc func startAudioRecording(_ params: NSDictionary, _ callback: Callback) { - callback.code(engine?.startAudioRecording(params["filePath"] as! String, sampleRate: (params["sampleRate"] as! NSNumber).intValue, quality: AgoraAudioRecordingQuality(rawValue: (params["quality"] as! NSNumber).intValue)!)) + guard params["recordingPosition"] != nil else { + callback.code(engine?.startAudioRecording(params["filePath"] as! String, sampleRate: (params["sampleRate"] as! NSNumber).intValue, quality: AgoraAudioRecordingQuality(rawValue: (params["quality"] as! NSNumber).intValue)!)) + return + } + callback.code(engine?.startAudioRecording(withConfig: mapToAudioRecordingConfiguration(params as! Dictionary))) } @objc func stopAudioRecording(_ callback: Callback) { diff --git a/ios/Classes/Base/RtcEngineEvent.swift b/ios/Classes/Base/RtcEngineEvent.swift index 241c1685d..ee6eba56e 100644 --- a/ios/Classes/Base/RtcEngineEvent.swift +++ b/ios/Classes/Base/RtcEngineEvent.swift @@ -354,8 +354,8 @@ extension RtcEngineEventHandler: AgoraRtcEngineDelegate { callback(RtcEngineEvents.AudioMixingFinished) } - public func rtcEngine(_ engine: AgoraRtcEngineKit, localAudioMixingStateDidChanged state: AgoraAudioMixingStateCode, errorCode: AgoraAudioMixingErrorCode) { - callback(RtcEngineEvents.AudioMixingStateChanged, state.rawValue, errorCode.rawValue) + func rtcEngine(_ engine: AgoraRtcEngineKit, localAudioMixingStateDidChanged state: AgoraAudioMixingStateCode, reason: AgoraAudioMixingReasonCode) { + callback(RtcEngineEvents.AudioMixingStateChanged, state.rawValue, reason.rawValue) } public func rtcEngineRemoteAudioMixingDidStart(_ engine: AgoraRtcEngineKit) { diff --git a/ios/agora_rtc_engine.podspec b/ios/agora_rtc_engine.podspec index adcdfe784..d39d18b6c 100644 --- a/ios/agora_rtc_engine.podspec +++ b/ios/agora_rtc_engine.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'AgoraRtcEngine_iOS', '3.3.2' + s.dependency 'AgoraRtcEngine_iOS', '3.4.1' s.platform = :ios, '8.0' # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. diff --git a/lib/src/classes.dart b/lib/src/classes.dart index 75388f64e..f879ac18d 100644 --- a/lib/src/classes.dart +++ b/lib/src/classes.dart @@ -462,6 +462,25 @@ class ChannelMediaRelayConfiguration { Map toJson() => _$ChannelMediaRelayConfigurationToJson(this); } + +@JsonSerializable(explicitToJson: true) +class RhythmPlayerConfig { + + int beatsPerMeasure; + int beatsPerMinute; + bool publish; + + /// Constructs a [ChannelMediaRelayConfiguration] + RhythmPlayerConfig(this.beatsPerMeasure, this.beatsPerMinute, this.publish); + + /// @nodoc + factory RhythmPlayerConfig.fromJson(Map json) => + _$RhythmPlayerConfigFromJson(json); + + /// @nodoc + Map toJson() => _$RhythmPlayerConfigToJson(this); +} + /// Lastmile probe configuration. @JsonSerializable(explicitToJson: true) class LastmileProbeConfig { diff --git a/lib/src/classes.g.dart b/lib/src/classes.g.dart index 36b125194..883c6055a 100644 --- a/lib/src/classes.g.dart +++ b/lib/src/classes.g.dart @@ -360,6 +360,20 @@ Map _$ChannelMediaRelayConfigurationToJson( 'destInfos': instance.destInfos?.map((e) => e?.toJson())?.toList(), }; +RhythmPlayerConfig _$RhythmPlayerConfigFromJson(Map json) { + return RhythmPlayerConfig( + json['beatsPerMeasure'] as int, + json['beatsPerMinute'] as int, + json['publish'] as bool, + ); +} + +Map _$RhythmPlayerConfigToJson(RhythmPlayerConfig instance) => { + 'beatsPerMeasure': instance.beatsPerMeasure, + 'beatsPerMinute': instance.beatsPerMinute, + 'publish': instance.publish, + }; + LastmileProbeConfig _$LastmileProbeConfigFromJson(Map json) { return LastmileProbeConfig( json['probeUplink'] as bool, diff --git a/lib/src/enum_converter.dart b/lib/src/enum_converter.dart index 22d4aefba..f11458e12 100644 --- a/lib/src/enum_converter.dart +++ b/lib/src/enum_converter.dart @@ -147,6 +147,19 @@ class AudioRecordingQualityConverter } } +@JsonSerializable() +class AudioRecordingPositionConverter + extends EnumConverter { + AudioRecordingPositionConverter(AudioRecordingPosition e) : super(e); + + AudioRecordingPositionConverter.fromValue(int value) + : super.fromValue(_$AudioRecordingPositionEnumMap, value); + + int value() { + return super.toValue(_$AudioRecordingPositionEnumMap); + } +} + @JsonSerializable() class AudioRemoteStateConverter extends EnumConverter { AudioRemoteStateConverter(AudioRemoteState e) : super(e); diff --git a/lib/src/enum_converter.g.dart b/lib/src/enum_converter.g.dart index 598bd663a..179132f20 100644 --- a/lib/src/enum_converter.g.dart +++ b/lib/src/enum_converter.g.dart @@ -250,6 +250,12 @@ const _$AudioRecordingQualityEnumMap = { AudioRecordingQuality.High: 2, }; +const _$AudioRecordingPositionEnumMap = { + AudioRecordingPosition.PositionMixedRecordingAndPlayback: 0, + AudioRecordingPosition.PositionRecording: 1, + AudioRecordingPosition.PositionMixedPlayback: 2, +}; + AudioRemoteStateConverter _$AudioRemoteStateConverterFromJson( Map json) { return AudioRemoteStateConverter( diff --git a/lib/src/enums.dart b/lib/src/enums.dart index 3f810d690..89432770a 100644 --- a/lib/src/enums.dart +++ b/lib/src/enums.dart @@ -247,6 +247,20 @@ enum AudioRecordingQuality { High, } +enum AudioRecordingPosition { + /// Low quality. The sample rate is 32 KHz, and the file size is around 1.2 MB after 10 minutes of recording. + @JsonValue(0) + PositionMixedRecordingAndPlayback, + + /// Medium quality. The sample rate is 32 KHz, and the file size is around 2 MB after 10 minutes of recording. + @JsonValue(1) + PositionRecording, + + /// High quality. The sample rate is 32 KHz, and the file size is around 3.75 MB after 10 minutes of recording. + @JsonValue(2) + PositionMixedPlayback, +} + /// The state of the remote audio. enum AudioRemoteState { /// The remote audio is in the default state, probably due to: diff --git a/lib/src/rtc_engine.dart b/lib/src/rtc_engine.dart index 8bdbe77f2..e38e92159 100644 --- a/lib/src/rtc_engine.dart +++ b/lib/src/rtc_engine.dart @@ -441,8 +441,10 @@ class RtcEngine with RtcEngineInterface { } @override - Future getAudioMixingDuration() { - return _invokeMethod('getAudioMixingDuration'); + Future getAudioMixingDuration([String filePath]) { + return _invokeMethod('getAudioMixingDuration', { + 'filePath': filePath, + }); } @override @@ -472,12 +474,14 @@ class RtcEngine with RtcEngineInterface { @override Future startAudioMixing( - String filePath, bool loopback, bool replace, int cycle) { + String filePath, bool loopback, bool replace, int cycle, + [int startPos]) { return _invokeMethod('startAudioMixing', { 'filePath': filePath, 'loopback': loopback, 'replace': replace, - 'cycle': cycle + 'cycle': cycle, + 'startPos': startPos }); } @@ -593,7 +597,8 @@ class RtcEngine with RtcEngineInterface { @override Future playEffect(int soundId, String filePath, int loopCount, - double pitch, double pan, double gain, bool publish) { + double pitch, double pan, double gain, bool publish, + [int startPos]) { return _invokeMethod('playEffect', { 'soundId': soundId, 'filePath': filePath, @@ -601,10 +606,26 @@ class RtcEngine with RtcEngineInterface { 'pitch': pitch, 'pan': pan, 'gain': gain, - 'publish': publish + 'publish': publish, + 'startPos': startPos }); } + @override + Future setEffectPosition(int soundId, int pos) { + return _invokeMethod('setEffectPosition', {'soundId': soundId, 'pos': pos}); + } + + @override + Future getEffectDuration(String filePath) { + return _invokeMethod('getEffectDuration', {'filePath': filePath}); + } + + @override + Future getEffectCurrentPosition(int soundId) { + return _invokeMethod('getEffectCurrentPosition', {'soundId': soundId}); + } + @override Future preloadEffect(int soundId, String filePath) { return _invokeMethod( @@ -816,14 +837,17 @@ class RtcEngine with RtcEngineInterface { @override Future startAudioRecording(String filePath, - AudioSampleRateType sampleRate, AudioRecordingQuality quality) { + AudioSampleRateType sampleRate, + AudioRecordingQuality quality, + [AudioRecordingPosition recordingPosition] + ) { return _invokeMethod('startAudioRecording', { 'filePath': filePath, 'sampleRate': AudioSampleRateTypeConverter(sampleRate).value(), - 'quality': AudioRecordingQualityConverter(quality).value() + 'quality': AudioRecordingQualityConverter(quality).value(), + 'recordingPosition': AudioRecordingPositionConverter(recordingPosition).value(), }); } - @override Future startChannelMediaRelay( ChannelMediaRelayConfiguration channelMediaRelayConfiguration) { @@ -832,6 +856,27 @@ class RtcEngine with RtcEngineInterface { }); } + @override + Future startRhythmPlayer(String sound1, String sound2, RhythmPlayerConfig config) { + return _invokeMethod('startRhythmPlayer', { + 'sound1': sound1, + 'sound2': sound2, + 'config': config.toJson(), + }); + } + + @override + Future stopRhythmPlayer() { + return _invokeMethod('stopRhythmPlayer'); + } + + @override + Future configRhythmPlayer(RhythmPlayerConfig config) { + return _invokeMethod('configRhythmPlayer', { + 'config': config.toJson() + }); + } + @override Future startEchoTest(int intervalInSeconds) { return _invokeMethod( @@ -962,8 +1007,7 @@ class RtcEngine with RtcEngineInterface { @override Future createDataStreamWithConfig(DataStreamConfig config) { - return _invokeMethod( - 'createDataStream', {'config': config.toJson()}); + return _invokeMethod('createDataStream', {'config': config.toJson()}); } @override @@ -1720,7 +1764,8 @@ mixin RtcAudioMixingInterface { /// - Positive integer: Number of playback loops /// - -1: Infinite playback loops Future startAudioMixing( - String filePath, bool loopback, bool replace, int cycle); + String filePath, bool loopback, bool replace, int cycle, + [int startPos]); /// Stops playing or mixing the music file. /// @@ -1801,7 +1846,7 @@ mixin RtcAudioMixingInterface { /// **Returns** /// - The audio mixing duration, if this method call is successful. /// - < 0: Failure. - Future getAudioMixingDuration(); + Future getAudioMixingDuration([String filePath]); /// Gets the playback position (ms) of the music file. /// @@ -1886,7 +1931,14 @@ mixin RtcAudioEffectInterface { /// - `true`: The locally played audio effect is published to the Agora Cloud and the remote users can hear it. /// - `false`: The locally played audio effect is not published to the Agora Cloud and the remote users cannot hear it. Future playEffect(int soundId, String filePath, int loopCount, - double pitch, double pan, double gain, bool publish); + double pitch, double pan, double gain, bool publish, + [int startPos]); + + Future setEffectPosition(int soundId, int pos); + + Future getEffectDuration(String filePath); + + Future getEffectCurrentPosition(int soundId); /// Stops playing a specified audio effect. /// @@ -2617,7 +2669,7 @@ mixin RtcAudioRecorderInterface { /// /// **Parameter** [quality] The audio recording quality. See [AudioRecordingQuality]. Future startAudioRecording(String filePath, - AudioSampleRateType sampleRate, AudioRecordingQuality quality); + AudioSampleRateType sampleRate, AudioRecordingQuality quality, [AudioRecordingPosition recordingPosition]); /// Stops the audio recording on the client. /// diff --git a/pubspec.yaml b/pubspec.yaml index 78e884836..e77fc641b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: agora_rtc_engine description: Flutter plugin for Agora Video SDK. -version: 3.3.3 +version: 3.4.1 homepage: 'https://www.agora.io' repository: 'https://github.com/AgoraIO/Flutter-SDK' environment: