diff --git a/android/build.gradle b/android/build.gradle index 0c3bf100a..0d478b418 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -52,5 +52,5 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${safeExtGet('kotlin_version', '1.3.72')}" implementation "org.jetbrains.kotlin:kotlin-reflect:${safeExtGet('kotlin_version', '1.3.72')}" - implementation "io.agora.rtc:full-sdk:3.1.2" + implementation "io.agora.rtc:full-sdk:3.1.3" } diff --git a/android/src/main/kotlin/io/agora/agora_rtc_engine/AgoraRtcEnginePlugin.kt b/android/src/main/kotlin/io/agora/agora_rtc_engine/AgoraRtcEnginePlugin.kt index 47ba629dc..a71d4cae9 100644 --- a/android/src/main/kotlin/io/agora/agora_rtc_engine/AgoraRtcEnginePlugin.kt +++ b/android/src/main/kotlin/io/agora/agora_rtc_engine/AgoraRtcEnginePlugin.kt @@ -7,26 +7,28 @@ import androidx.annotation.NonNull import io.agora.rtc.RtcEngine import io.agora.rtc.base.RtcEngineManager import io.flutter.embedding.engine.plugins.FlutterPlugin -import io.flutter.plugin.common.BinaryMessenger -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.* import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.common.PluginRegistry.Registrar import io.flutter.plugin.platform.PlatformViewRegistry +import java.io.FileNotFoundException import kotlin.reflect.full.declaredMemberFunctions import kotlin.reflect.jvm.javaMethod /** AgoraRtcEnginePlugin */ class AgoraRtcEnginePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler { + private var registrar: Registrar? = null + private var binding: FlutterPlugin.FlutterPluginBinding? = null + private lateinit var applicationContext: Context + /// The MethodChannel that will the communication between Flutter and native Android /// /// This local reference serves to register the plugin with the Flutter Engine and unregister it /// when the Flutter Engine is detached from the Activity private lateinit var methodChannel: MethodChannel private lateinit var eventChannel: EventChannel - private lateinit var applicationContext: Context + private var eventSink: EventChannel.EventSink? = null private val manager = RtcEngineManager { methodName, data -> emit(methodName, data) } private val handler = Handler(Looper.getMainLooper()) @@ -45,8 +47,9 @@ class AgoraRtcEnginePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stre @JvmStatic fun registerWith(registrar: Registrar) { AgoraRtcEnginePlugin().apply { - initPlugin(registrar.context(), registrar.messenger(), registrar.platformViewRegistry()) + this.registrar = registrar rtcChannelPlugin.initPlugin(registrar.messenger()) + initPlugin(registrar.context(), registrar.messenger(), registrar.platformViewRegistry()) } } } @@ -63,6 +66,7 @@ class AgoraRtcEnginePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stre } override fun onAttachedToEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + this.binding = binding rtcChannelPlugin.onAttachedToEngine(binding) initPlugin(binding.applicationContext, binding.binaryMessenger, binding.platformViewRegistry) } @@ -95,6 +99,10 @@ class AgoraRtcEnginePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stre } override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + if (call.method == "getAssetAbsolutePath") { + getAssetAbsolutePath(call, result) + return + } manager::class.declaredMemberFunctions.find { it.name == call.method }?.let { function -> function.javaMethod?.let { method -> try { @@ -114,4 +122,19 @@ class AgoraRtcEnginePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stre } result.notImplemented() } + + private fun getAssetAbsolutePath(call: MethodCall, result: Result) { + call.arguments()?.let { + val assetKey = registrar?.lookupKeyForAsset(it) + ?: binding?.flutterAssets?.getAssetFilePathByName(it) + try { + applicationContext.assets.openFd(assetKey!!).close() + result.success("/assets/$assetKey") + } catch (e: Exception) { + result.error(e.javaClass.simpleName, e.message, e.cause) + } + return@getAssetAbsolutePath + } + result.error(IllegalArgumentException::class.simpleName, null, null) + } } diff --git a/example/lib/config/agora.config.dart b/example/lib/config/agora.config.dart index 12a4ebe61..945487a81 100644 --- a/example/lib/config/agora.config.dart +++ b/example/lib/config/agora.config.dart @@ -1,5 +1,9 @@ +/// Get your own App ID at https://dashboard.agora.io/ const appId = YOUR_APP_ID; + +/// Please refer to https://docs.agora.io/en/Agora%20Platform/token const token = YOUR_TOEKN; + const channelId = YOUR_CHANNEL_ID; const uid = YOUR_UID; const stringUid = YOUR_STRING_UID; diff --git a/example/lib/examples/basic/join_channel_audio.dart b/example/lib/examples/basic/join_channel_audio.dart index 392f967a8..3b18200fd 100644 --- a/example/lib/examples/basic/join_channel_audio.dart +++ b/example/lib/examples/basic/join_channel_audio.dart @@ -17,7 +17,10 @@ class JoinChannelAudio extends StatefulWidget { class _State extends State { String channelId = config.channelId; - bool isJoined = false, openMicrophone = true, enableSpeakerphone = true; + bool isJoined = false, + openMicrophone = true, + enableSpeakerphone = true, + playEffect = false; TextEditingController _controller; @override @@ -91,6 +94,36 @@ class _State extends State { }); } + _switchEffect() async { + if (playEffect) { + widget._engine?.stopEffect(1)?.then((value) { + setState(() { + playEffect = false; + }); + })?.catchError((err) { + log('stopEffect $err'); + }); + } else { + widget._engine + ?.playEffect( + 1, + await RtcEngineExtension.getAssetAbsolutePath( + "assets/Sound_Horizon.mp3"), + -1, + 1, + 1, + 100, + true) + ?.then((value) { + setState(() { + playEffect = true; + }); + })?.catchError((err) { + log('playEffect $err'); + }); + } + } + @override Widget build(BuildContext context) { return Stack( @@ -133,6 +166,10 @@ class _State extends State { onPressed: this._switchSpeakerphone, child: Text(enableSpeakerphone ? 'Speakerphone' : 'Earpiece'), ), + RaisedButton( + onPressed: this._switchEffect, + child: Text('${playEffect ? 'Stop' : 'Play'} effect'), + ), ], ), ) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9da0b40b4..3b69f97a8 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -48,6 +48,8 @@ flutter: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg + assets: + - assets/Sound_Horizon.mp3 # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. diff --git a/ios/Classes/SwiftAgoraRtcEnginePlugin.swift b/ios/Classes/SwiftAgoraRtcEnginePlugin.swift index f8ada518a..34df87aff 100644 --- a/ios/Classes/SwiftAgoraRtcEnginePlugin.swift +++ b/ios/Classes/SwiftAgoraRtcEnginePlugin.swift @@ -2,6 +2,7 @@ import Flutter import UIKit public class SwiftAgoraRtcEnginePlugin: NSObject, FlutterPlugin, FlutterStreamHandler { + private var registrar: FlutterPluginRegistrar? private var methodChannel: FlutterMethodChannel? private var eventChannel: FlutterEventChannel? private var eventSink: FlutterEventSink? = nil @@ -16,6 +17,7 @@ public class SwiftAgoraRtcEnginePlugin: NSObject, FlutterPlugin, FlutterStreamHa public static func register(with registrar: FlutterPluginRegistrar) { let rtcEnginePlugin = SwiftAgoraRtcEnginePlugin() + rtcEnginePlugin.registrar = registrar rtcEnginePlugin.rtcChannelPlugin.initPlugin(registrar) rtcEnginePlugin.initPlugin(registrar) } @@ -61,6 +63,10 @@ public class SwiftAgoraRtcEnginePlugin: NSObject, FlutterPlugin, FlutterStreamHa } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + if call.method == "getAssetAbsolutePath" { + getAssetAbsolutePath(call, result: result) + return + } if let params = call.arguments as? NSDictionary { let selector = NSSelectorFromString(call.method + "::") if manager.responds(to: selector) { @@ -76,4 +82,18 @@ public class SwiftAgoraRtcEnginePlugin: NSObject, FlutterPlugin, FlutterStreamHa } result(FlutterMethodNotImplemented) } + + private func getAssetAbsolutePath(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + if let assetPath = call.arguments as? String { + if let assetKey = registrar?.lookupKey(forAsset: assetPath) { + if let realPath = Bundle.main.path(forResource: assetKey, ofType: nil) { + result(realPath) + return + } + } + result(FlutterError.init(code: "FileNotFoundException", message: nil, details: nil)) + return + } + result(FlutterError.init(code: "IllegalArgumentException", message: nil, details: nil)) + } } diff --git a/lib/rtc_engine.dart b/lib/rtc_engine.dart index a1c503b7c..8919aefe4 100644 --- a/lib/rtc_engine.dart +++ b/lib/rtc_engine.dart @@ -2,3 +2,4 @@ export 'src/classes.dart'; export 'src/enums.dart'; export 'src/events.dart' show RtcEngineEventHandler; export 'src/rtc_engine.dart' show RtcEngine; +export 'src/rtc_engine_extension.dart'; diff --git a/lib/src/rtc_channel.dart b/lib/src/rtc_channel.dart index ae678abbb..862acae55 100644 --- a/lib/src/rtc_channel.dart +++ b/lib/src/rtc_channel.dart @@ -310,6 +310,7 @@ class RtcChannel with RtcChannelInterface { } } +/// @nodoc mixin RtcChannelInterface implements RtcAudioInterface, @@ -421,6 +422,7 @@ mixin RtcChannelInterface Future getCallId(); } +/// @nodoc mixin RtcAudioInterface { /// Adjusts the playback volume of a specified remote user. /// @@ -463,6 +465,7 @@ mixin RtcAudioInterface { Future setDefaultMuteAllRemoteAudioStreams(bool muted); } +/// @nodoc mixin RtcVideoInterface { /// Stops/Resumes receiving the video stream of the specified user. /// @@ -488,6 +491,7 @@ mixin RtcVideoInterface { Future setDefaultMuteAllRemoteVideoStreams(bool muted); } +/// @nodoc mixin RtcVoicePositionInterface { /// Sets the sound position of a remote user. /// @@ -508,6 +512,7 @@ mixin RtcVoicePositionInterface { Future setRemoteVoicePosition(int uid, double pan, double gain); } +/// @nodoc mixin RtcPublishStreamInterface { /// Sets the video layout and audio settings for CDN live. /// @@ -552,6 +557,7 @@ mixin RtcPublishStreamInterface { Future removePublishStreamUrl(String url); } +/// @nodoc mixin RtcMediaRelayInterface { /// Starts to relay media streams across channels. /// @@ -596,6 +602,7 @@ mixin RtcMediaRelayInterface { Future stopChannelMediaRelay(); } +/// @nodoc mixin RtcDualStreamInterface { /// Sets the video stream type of the remote video stream when the remote user sends dual streams. /// @@ -614,6 +621,7 @@ mixin RtcDualStreamInterface { Future setRemoteDefaultVideoStreamType(VideoStreamType streamType); } +/// @nodoc mixin RtcFallbackInterface { /// Sets the priority of a remote user's media stream. /// @@ -628,6 +636,7 @@ mixin RtcFallbackInterface { Future setRemoteUserPriority(int uid, UserPriority userPriority); } +/// @nodoc mixin RtcMediaMetadataInterface { /// Registers the metadata observer. /// @@ -654,6 +663,7 @@ mixin RtcMediaMetadataInterface { Future sendMetadata(String metadata); } +/// @nodoc mixin RtcEncryptionInterface { /// Enables built-in encryption with an encryption password before joining a channel. /// @@ -708,6 +718,7 @@ mixin RtcEncryptionInterface { Future enableEncryption(bool enabled, EncryptionConfig config); } +/// @nodoc mixin RtcInjectStreamInterface { /// Injects an online media stream to a [ChannelProfile.LiveBroadcasting] channel. /// @@ -739,6 +750,7 @@ mixin RtcInjectStreamInterface { Future removeInjectStreamUrl(String url); } +/// @nodoc mixin RtcStreamMessageInterface { /// Creates a data stream. /// diff --git a/lib/src/rtc_engine.dart b/lib/src/rtc_engine.dart index 7faa3d355..1e212ad73 100644 --- a/lib/src/rtc_engine.dart +++ b/lib/src/rtc_engine.dart @@ -15,6 +15,9 @@ class RtcEngine with RtcEngineInterface { static const EventChannel _eventChannel = EventChannel('agora_rtc_engine/events'); + /// Exposing methodChannel to other files + static MethodChannel get methodChannel => _methodChannel; + static RtcEngine _engine; RtcEngineEventHandler _handler; @@ -867,6 +870,7 @@ class RtcEngine with RtcEngineInterface { } } +/// @nodoc mixin RtcEngineInterface implements RtcUserInfoInterface, @@ -1076,6 +1080,7 @@ mixin RtcEngineInterface Future getNativeHandle(); } +/// @nodoc mixin RtcUserInfoInterface { /// Registers a user account. /// @@ -1163,6 +1168,7 @@ mixin RtcUserInfoInterface { Future getUserInfoByUid(int uid); } +/// @nodoc mixin RtcAudioInterface { /// Enables the audio module. /// @@ -1320,6 +1326,7 @@ mixin RtcAudioInterface { int interval, int smooth, bool report_vad); } +/// @nodoc mixin RtcVideoInterface { /// Enables the video module. /// @@ -1451,6 +1458,7 @@ mixin RtcVideoInterface { Future setBeautyEffectOptions(bool enabled, BeautyOptions options); } +/// @nodoc mixin RtcAudioMixingInterface { /// Starts playing and mixing the music file. /// @@ -1578,6 +1586,7 @@ mixin RtcAudioMixingInterface { Future setAudioMixingPitch(int pitch); } +/// @nodoc mixin RtcAudioEffectInterface { /// Gets the volume of the audio effects. /// @@ -1687,6 +1696,7 @@ mixin RtcAudioEffectInterface { AudioSessionOperationRestriction restriction); } +/// @nodoc mixin RtcVoiceChangerInterface { /// Sets the local voice changer option. /// @@ -1730,6 +1740,7 @@ mixin RtcVoiceChangerInterface { Future setLocalVoiceReverb(AudioReverbType reverbKey, int value); } +/// @nodoc mixin RtcVoicePositionInterface { /// Enables/Disables stereo panning for remote users. /// @@ -1758,6 +1769,7 @@ mixin RtcVoicePositionInterface { Future setRemoteVoicePosition(int uid, double pan, double gain); } +/// @nodoc mixin RtcPublishStreamInterface { /// Sets the video layout and audio settings for CDN live. /// @@ -1805,6 +1817,7 @@ mixin RtcPublishStreamInterface { Future removePublishStreamUrl(String url); } +/// @nodoc mixin RtcMediaRelayInterface { /// Starts to relay media streams across channels. /// @@ -1848,6 +1861,7 @@ mixin RtcMediaRelayInterface { Future stopChannelMediaRelay(); } +/// @nodoc mixin RtcAudioRouteInterface { /// Sets the default audio playback route. /// @@ -1885,6 +1899,7 @@ mixin RtcAudioRouteInterface { Future isSpeakerphoneEnabled(); } +/// @nodoc mixin RtcEarMonitoringInterface { /// Enables in-ear monitoring. /// @@ -1899,6 +1914,7 @@ mixin RtcEarMonitoringInterface { Future setInEarMonitoringVolume(int volume); } +/// @nodoc mixin RtcDualStreamInterface { /// Enables/Disables the dual video stream mode. /// @@ -1930,6 +1946,7 @@ mixin RtcDualStreamInterface { Future setRemoteDefaultVideoStreamType(VideoStreamType streamType); } +/// @nodoc mixin RtcFallbackInterface { /// Sets the fallback option for the locally published video stream based on the network conditions. /// @@ -1966,6 +1983,7 @@ mixin RtcFallbackInterface { Future setRemoteUserPriority(int uid, UserPriority userPriority); } +/// @nodoc mixin RtcTestInterface { /// Starts an audio call test. /// @@ -2020,6 +2038,7 @@ mixin RtcTestInterface { Future stopLastmileProbeTest(); } +/// @nodoc mixin RtcMediaMetadataInterface { /// Registers the metadata observer. /// @@ -2047,6 +2066,7 @@ mixin RtcMediaMetadataInterface { Future sendMetadata(String metadata); } +/// @nodoc mixin RtcWatermarkInterface { /// Adds a watermark image to the local video. /// @@ -2076,6 +2096,7 @@ mixin RtcWatermarkInterface { Future clearVideoWatermarks(); } +/// @nodoc mixin RtcEncryptionInterface { /// Enables built-in encryption with an encryption password before joining a channel. /// @@ -2130,6 +2151,7 @@ mixin RtcEncryptionInterface { Future enableEncryption(bool enabled, EncryptionConfig config); } +/// @nodoc mixin RtcAudioRecorderInterface { /// Starts an audio recording on the client. /// @@ -2159,6 +2181,7 @@ mixin RtcAudioRecorderInterface { Future stopAudioRecording(); } +/// @nodoc mixin RtcInjectStreamInterface { /// Injects an online media stream to a live broadcast. /// @@ -2193,6 +2216,7 @@ mixin RtcInjectStreamInterface { Future removeInjectStreamUrl(String url); } +/// @nodoc mixin RtcCameraInterface { /// Switches between front and rear cameras. Future switchCamera(); @@ -2281,6 +2305,7 @@ mixin RtcCameraInterface { CameraCapturerConfiguration config); } +/// @nodoc mixin RtcStreamMessageInterface { /// Creates a data stream. ///