diff --git a/CHANGELOG.md b/CHANGELOG.md index f6da706da..006492cb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.7.5 - 2022-08-18 + +- Added support on iOS for sharing audio from local files on your device & from other audio playing apps +- Added ability to apply local peer track settings while initializing HMSSDK +- Added APIs to fetch local peer track settings +- Fixed an issue where exiting from Preview without joining room was not releasing camera access +- Added `destroy` API to cleanup Native HMSSDK instance correctly +- Disabled Hardware Scaler on Android to correct intermittent Video tile flickering +- Updated to Native Android SDK 2.4.8 & Native iOS SDK 0.3.3 + ## 0.7.4 - 2022-07-29 ### Added diff --git a/README.md b/README.md index 5971058f9..cb70a63b2 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,14 @@ Here you will find everything you need to build experiences with video using 100 📲 Download the Sample iOS app here: 🤖 Download the Sample Android app here: - + + +100ms Flutter apps are also released on the App Stores, you can download them here: + +📲 iOS app on Apple App Store: + +🤖 Android app on Google Play Store: + ## 🚂 Setup Guide 1. Sign up on & visit the Developer tab to access your credentials. @@ -30,8 +37,12 @@ Here you will find everything you need to build experiences with video using 100 4. Get the HMSSDK via [pub.dev](https://pub.dev/packages/hmssdk_flutter). Add the `hmssdk_flutter` to your pubspec.yaml. - If you are running Flutter 3.0 or higher, use this branch to add the package: `flutter3.0` - +### Note for Flutter 3.0+ + +At this moment, Flutter 3.0 or higher is not supported due to ongoing issues in Flutter 3.x release for Platform Views. Refer issues: [107313](https://github.com/flutter/flutter/issues/107313) & [103630](https://github.com/flutter/flutter/issues/103630). We are actively monitoring these issues & once [Flutter](https://github.com/flutter/flutter) team releases a fix for these issues on [stable](https://github.com/flutter/flutter/tree/stable) branch, we'll release a compatible version of 100ms SDK. + + +Until then, please use Flutter version `2.10.5` or any other 2.x series. ## 🏃‍♀️ How to run the Sample App diff --git a/android/build.gradle b/android/build.gradle index b4ece072a..4f36dc8df 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -37,7 +37,7 @@ android { } dependencies { - implementation 'com.github.100mslive.android-sdk:lib:2.4.7' + implementation 'com.github.100mslive.android-sdk:lib:2.4.8' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0' implementation "org.jetbrains.kotlin:kotlin-script-runtime:1.5.0" diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackSettingsExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackSettingsExtension.kt new file mode 100644 index 000000000..806deb97b --- /dev/null +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackSettingsExtension.kt @@ -0,0 +1,85 @@ +package live.hms.hmssdk_flutter + +import live.hms.hmssdk_flutter.hms_role_components.AudioParamsExtension +import live.hms.hmssdk_flutter.hms_role_components.VideoParamsExtension +import live.hms.video.media.codec.HMSAudioCodec +import live.hms.video.media.codec.HMSVideoCodec +import live.hms.video.media.settings.HMSAudioTrackSettings +import live.hms.video.media.settings.HMSTrackSettings +import live.hms.video.media.settings.HMSVideoTrackSettings +import live.hms.video.sdk.HMSSDK + +class HMSTrackSettingsExtension { + + companion object{ + fun toDictionary(hmssdk: HMSSDK):HashMap?{ + + val map = HashMap(); + val hmsTrackSettings:HMSTrackSettings = hmssdk.hmsSettings; + + if(hmsTrackSettings.videoSettings != null){ + map["video_track_setting"] = HMSVideoTrackSettingsExtension.toDictionary(hmsTrackSettings.videoSettings)!! + } + + if(hmsTrackSettings.audioSettings != null){ + map["audio_track_setting"] = HMSAudioTrackSettingsExtension.toDictionary(hmsTrackSettings.audioSettings)!! + } + + return map + } + + fun setTrackSettings(hmsAudioTrackHashMap:HashMap?,hmsVideoTrackHashMap: HashMap?):HMSTrackSettings{ + + var hmsAudioTrackSettings = HMSAudioTrackSettings.Builder() + if (hmsAudioTrackHashMap != null) { + val maxBitRate = hmsAudioTrackHashMap["bit_rate"] as Int? + val volume = hmsAudioTrackHashMap["volume"] as Double? + val useHardwareAcousticEchoCanceler = + hmsAudioTrackHashMap["user_hardware_acoustic_echo_canceler"] as Boolean? + val audioCodec = + AudioParamsExtension.getValueOfHMSAudioCodecFromString(hmsAudioTrackHashMap["audio_codec"] as String?) as HMSAudioCodec? + + if (maxBitRate != null) { + hmsAudioTrackSettings = hmsAudioTrackSettings.maxBitrate(maxBitRate) + } + + if (volume != null) { + hmsAudioTrackSettings = hmsAudioTrackSettings.volume(volume) + } + + if (useHardwareAcousticEchoCanceler != null) { + hmsAudioTrackSettings = hmsAudioTrackSettings.setUseHardwareAcousticEchoCanceler( + useHardwareAcousticEchoCanceler + ) + } + + if (audioCodec != null) { + hmsAudioTrackSettings = hmsAudioTrackSettings.codec(audioCodec) + } + } + + + var hmsVideoTrackSettings = HMSVideoTrackSettings.Builder() + if (hmsVideoTrackHashMap != null) { + val maxBitRate = hmsVideoTrackHashMap["max_bit_rate"] as Int? + val maxFrameRate = hmsVideoTrackHashMap["max_frame_rate"] as Int? + val videoCodec = + VideoParamsExtension.getValueOfHMSAudioCodecFromString(hmsVideoTrackHashMap["video_codec"] as String?) as HMSVideoCodec? + + + if (maxBitRate != null) { + hmsVideoTrackSettings = hmsVideoTrackSettings.maxBitrate(maxBitRate) + } + + if (maxFrameRate != null) { + hmsVideoTrackSettings = hmsVideoTrackSettings.maxFrameRate(maxFrameRate) + } + if (videoCodec != null) { + hmsVideoTrackSettings = hmsVideoTrackSettings.codec(videoCodec) + } + } + + return HMSTrackSettings.Builder().audio(hmsAudioTrackSettings.build()).video(hmsVideoTrackSettings.build()).build() + } + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSVideoTrackSettingsExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSVideoTrackSettingsExtension.kt index 37b668519..77193204f 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSVideoTrackSettingsExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSVideoTrackSettingsExtension.kt @@ -19,7 +19,7 @@ class HMSVideoTrackSettingsExtension { } - fun getValueOfHMSCameraFacing(cameraFacing: HMSVideoTrackSettings.CameraFacing?):String?{ + private fun getValueOfHMSCameraFacing(cameraFacing: HMSVideoTrackSettings.CameraFacing?):String?{ if(cameraFacing==null)return null return when(cameraFacing){ @@ -29,7 +29,7 @@ class HMSVideoTrackSettingsExtension { } } - fun getValueOfHMSVideoCodec(codec: HMSVideoCodec?):String?{ + private fun getValueOfHMSVideoCodec(codec: HMSVideoCodec?):String?{ if(codec==null)return null return when(codec){ diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt index 1f6607109..f770b50ba 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt @@ -6,6 +6,7 @@ import android.content.Context import android.content.Intent import android.media.projection.MediaProjectionManager import android.os.Build +import android.util.Log import androidx.annotation.NonNull import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware @@ -57,7 +58,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, private var logsSink: EventChannel.EventSink? = null private var rtcSink: EventChannel.EventSink? = null private lateinit var activity: Activity - lateinit var hmssdk: HMSSDK + var hmssdk: HMSSDK? = null private lateinit var hmsVideoFactory: HMSVideoViewFactory private var requestChange: HMSRoleChangeRequest? = null @@ -101,28 +102,28 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, } // MARK: Build Actions - "build", "preview", "join", "leave" -> { + "build", "preview", "join", "leave","destroy" -> { buildActions(call, result) } // MARK: Room Actions "get_room", "get_local_peer", "get_remote_peers", "get_peers" -> { - HMSRoomAction.roomActions(call, result, hmssdk) + HMSRoomAction.roomActions(call, result, hmssdk!!) } // MARK: Audio Helpers "switch_audio", "is_audio_mute", "mute_all", "un_mute_all", "set_volume" -> { - HMSAudioAction.audioActions(call, result, hmssdk) + HMSAudioAction.audioActions(call, result, hmssdk!!) } // MARK: Video Helpers "switch_video", "switch_camera", "start_capturing", "stop_capturing", "is_video_mute", "set_playback_allowed" -> { - HMSVideoAction.videoActions(call, result, hmssdk) + HMSVideoAction.videoActions(call, result, hmssdk!!) } // MARK: Messaging "send_broadcast_message", "send_direct_message", "send_group_message" -> { - HMSMessageAction.messageActions(call, result, hmssdk) + HMSMessageAction.messageActions(call, result, hmssdk!!) } // MARK: Role based Actions @@ -137,12 +138,12 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, // MARK: Recording "start_rtmp_or_recording", "stop_rtmp_and_recording" -> { - HMSRecordingAction.recordingActions(call, result, hmssdk) + HMSRecordingAction.recordingActions(call, result, hmssdk!!) } // MARK: HLS "hls_start_streaming", "hls_stop_streaming" -> { - HMSHLSAction.hlsActions(call, result, hmssdk) + HMSHLSAction.hlsActions(call, result, hmssdk!!) } // MARK: Logger @@ -168,11 +169,15 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, statsListenerAction(call, result) } "get_audio_devices_list","get_current_audio_device","switch_audio_output" -> { - HMSAudioDeviceAction.audioDeviceActions(call,result,hmssdk) + HMSAudioDeviceAction.audioDeviceActions(call,result,hmssdk!!) } "start_audio_share","stop_audio_share","set_audio_mixing_mode"->{ audioShare(call,result) } + + "get_track_settings"->{ + trackSettings(call, result) + } else -> { result.notImplemented() } @@ -194,6 +199,9 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, "leave" -> { leave(result) } + "destroy" -> { + destroy(result) + } else -> { result.notImplemented() } @@ -274,7 +282,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, } "is_screen_share_active" -> { - result.success(hmssdk.isScreenShared()) + result.success(hmssdk!!.isScreenShared()) } else -> { result.notImplemented() @@ -285,11 +293,11 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, private fun statsListenerAction(call: MethodCall, result: Result){ when (call.method) { "start_stats_listener" -> { - hmssdk.addRtcStatsObserver(this.hmsStatsListener) + hmssdk!!.addRtcStatsObserver(this.hmsStatsListener) } "remove_stats_listener" -> { - hmssdk.removeRtcStatsObserver() + hmssdk!!.removeRtcStatsObserver() } else -> { @@ -317,6 +325,25 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, } } + private fun trackSettings(call: MethodCall, result: Result){ + when (call.method) { + "get_track_settings"->{ + result.success(HMSTrackSettingsExtension.toDictionary(hmssdk!!)) + } +// "set_track_settings"->{ +// val hmsTrackSettingMap = +// call.argument?>?>("hms_track_setting") +// if(hmsTrackSettingMap!=null){ +// val hmsAudioTrackHashMap: HashMap? = hmsTrackSettingMap["audio_track_setting"] +// val hmsVideoTrackHashMap: HashMap? = hmsTrackSettingMap["video_track_setting"] +// +// val hmsTrackSettings = HMSTrackSettingsExtension.setTrackSettings(hmsAudioTrackHashMap,hmsVideoTrackHashMap) +// hmssdk.setTrackSettings() +// } +// } + } + } + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) meetingEventChannel.setStreamHandler(null) @@ -344,8 +371,8 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, private fun join(call: MethodCall, result: Result) { val config = getConfig(call) - hmssdk.join(config, this.hmsUpdateListener) - hmssdk.setAudioDeviceChangeListener(audioDeviceChangeListener) + hmssdk!!.join(config, this.hmsUpdateListener) + hmssdk!!.setAudioDeviceChangeListener(audioDeviceChangeListener) result.success(null) } @@ -404,7 +431,11 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, private fun leave(result: Result) { - hmssdk.leave(hmsActionResultListener = HMSCommonAction.getActionListener(result)) + hmssdk!!.leave(hmsActionResultListener = HMSCommonAction.getActionListener(result)) + } + + private fun destroy(result:Result){ + hmssdk = null } override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { @@ -428,7 +459,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, fun getPeerById(id: String): HMSPeer? { if (id == "") return getLocalPeer() - val peers = hmssdk.getPeers() + val peers = hmssdk!!.getPeers() peers.forEach { if (it.peerID == id) return it } @@ -438,7 +469,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, private fun getAudioTracks(): ArrayList { val audioTracks = ArrayList(); - val peers = hmssdk.getPeers() + val peers = hmssdk!!.getPeers() peers.forEach { if (it.audioTrack != null) audioTracks.add(it.audioTrack!!) @@ -448,7 +479,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, private fun getVideoTracks(): ArrayList { val videoTracks = ArrayList(); - val peers = hmssdk.getPeers() + val peers = hmssdk!!.getPeers() peers.forEach { if (it.videoTrack != null) videoTracks.add(it.videoTrack!!) @@ -458,7 +489,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, private fun getAuxiliaryTracks(): ArrayList { val auxiliaryTracks = ArrayList(); - val peers = hmssdk.getPeers() + val peers = hmssdk!!.getPeers() peers.forEach { if (it.auxiliaryTracks != null) auxiliaryTracks.addAll(it.auxiliaryTracks!!) @@ -481,25 +512,25 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, val config = getConfig(call) - hmssdk.preview(config, this.hmsPreviewListener) + hmssdk!!.preview(config, this.hmsPreviewListener) result.success(null) } fun getLocalPeer(): HMSLocalPeer? { - return hmssdk.getLocalPeer() + return hmssdk!!.getLocalPeer() } private fun changeRole(call: MethodCall, result: Result) { val roleUWant = call.argument("role_name") val peerId = call.argument("peer_id") val forceChange = call.argument("force_change") - val roles = hmssdk.getRoles() + val roles = hmssdk!!.getRoles() val roleToChangeTo: HMSRole = roles.first { it.name == roleUWant } val peer = getPeerById(peerId!!) as HMSPeer - hmssdk.changeRole( + hmssdk!!.changeRole( peer, roleToChangeTo, forceChange ?: false, @@ -511,7 +542,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, val args = HashMap() val roles = ArrayList() - hmssdk.getRoles().forEach { + hmssdk!!.getRoles().forEach { roles.add(HMSRoleExtension.toDictionary(it)!!) } args["roles"] = roles @@ -519,7 +550,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, } private fun acceptChangeRole(result: Result) { - hmssdk.acceptChangeRole( + hmssdk!!.acceptChangeRole( this.requestChange!!, hmsActionResultListener = HMSCommonAction.getActionListener(result) ) @@ -564,7 +595,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, it.trackId == trackId } - hmssdk.changeTrackState( + hmssdk!!.changeTrackState( track, mute!!, hmsActionResultListener = HMSCommonAction.getActionListener(result) @@ -579,7 +610,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, val reason = call.argument("reason") ?: "Removed from room" - hmssdk.removePeerRequest( + hmssdk!!.removePeerRequest( peer = peer, hmsActionResultListener = HMSCommonAction.getActionListener(result), reason = reason @@ -593,7 +624,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, private fun endRoom(call: MethodCall, result: Result) { val lock = call.argument("lock") ?: false val reason = call.argument("reason") ?: "End room invoked" - hmssdk.endRoom( + hmssdk!!.endRoom( lock = lock!!, reason = reason, hmsActionResultListener = HMSCommonAction.getActionListener(result) @@ -601,7 +632,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, } private fun isAllowedToEndMeeting(): Boolean? { - return hmssdk.getLocalPeer()!!.hmsRole.permission?.endRoom + return hmssdk!!.getLocalPeer()!!.hmsRole.permission?.endRoom } private fun changeTrackStateForRole(call: MethodCall, result: Result) { @@ -611,11 +642,11 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, val roles: List? = call.argument>("roles") val hmsRoles: List?; if (roles != null) { - hmsRoles = hmssdk.getRoles().filter { roles?.contains(it.name)!! } + hmsRoles = hmssdk!!.getRoles().filter { roles?.contains(it.name)!! } } else { hmsRoles = null; } - hmssdk.changeTrackState( + hmssdk!!.changeTrackState( mute = mute!!, type = HMSTrackExtension.getStringFromKind(type), source = source, @@ -635,58 +666,9 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, } val hmsAudioTrackHashMap: HashMap? = hmsTrackSettingMap["audio_track_setting"] - var hmsAudioTrackSettings = HMSAudioTrackSettings.Builder() - if (hmsAudioTrackHashMap != null) { - val maxBitRate = hmsAudioTrackHashMap["bit_rate"] as Int? - val volume = hmsAudioTrackHashMap["volume"] as Double? - val useHardwareAcousticEchoCanceler = - hmsAudioTrackHashMap["user_hardware_acoustic_echo_canceler"] as Boolean? - val audioCodec = - AudioParamsExtension.getValueOfHMSAudioCodecFromString(hmsAudioTrackHashMap["audio_codec"] as String?) as HMSAudioCodec? - - if (maxBitRate != null) { - hmsAudioTrackSettings = hmsAudioTrackSettings.maxBitrate(maxBitRate) - } - - if (volume != null) { - hmsAudioTrackSettings = hmsAudioTrackSettings.volume(volume) - } - - if (useHardwareAcousticEchoCanceler != null) { - hmsAudioTrackSettings = hmsAudioTrackSettings.setUseHardwareAcousticEchoCanceler( - useHardwareAcousticEchoCanceler - ) - } - - if (audioCodec != null) { - hmsAudioTrackSettings = hmsAudioTrackSettings.codec(audioCodec) - } - } - - - var hmsVideoTrackSettings = HMSVideoTrackSettings.Builder() val hmsVideoTrackHashMap: HashMap? = hmsTrackSettingMap["video_track_setting"] - if (hmsVideoTrackHashMap != null) { - val maxBitRate = hmsVideoTrackHashMap["max_bit_rate"] as Int? - val maxFrameRate = hmsVideoTrackHashMap["max_frame_rate"] as Int? - val videoCodec = - VideoParamsExtension.getValueOfHMSAudioCodecFromString(hmsVideoTrackHashMap["video_codec"] as String?) as HMSVideoCodec? - - - if (maxBitRate != null) { - hmsVideoTrackSettings = hmsVideoTrackSettings.maxBitrate(maxBitRate) - } - - if (maxFrameRate != null) { - hmsVideoTrackSettings = hmsVideoTrackSettings.maxFrameRate(maxFrameRate) - } - if (videoCodec != null) { - hmsVideoTrackSettings = hmsVideoTrackSettings.codec(videoCodec) - } - } - val hmsTrackSettings = HMSTrackSettings.Builder().audio(hmsAudioTrackSettings.build()) - .video(hmsVideoTrackSettings.build()).build() + val hmsTrackSettings = HMSTrackSettingsExtension.setTrackSettings(hmsAudioTrackHashMap,hmsVideoTrackHashMap) hmssdk = HMSSDK .Builder(activity) .setTrackSettings(hmsTrackSettings) @@ -727,7 +709,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, hasChangedMetadata = !hasChangedMetadata val metadata = call.argument("metadata") - hmssdk.changeMetadata( + hmssdk!!.changeMetadata( metadata!!, hmsActionResultListener = HMSCommonAction.getActionListener(result) ) @@ -759,7 +741,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, override fun onJoin(room: HMSRoom) { // hasJoined = true - hmssdk.addAudioObserver(hmsAudioListener) + hmssdk!!.addAudioObserver(hmsAudioListener) previewChannel.setStreamHandler(null) val args = HashMap() args.put("event_name", "on_join_room") @@ -963,7 +945,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, private fun changeName(call: MethodCall, result: Result) { val name = call.argument("name") - hmssdk.changeName( + hmssdk!!.changeName( name = name!!, hmsActionResultListener = HMSCommonAction.getActionListener(result) ); @@ -984,7 +966,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, fun requestScreenShare(data: Intent?) { - hmssdk.startScreenshare(object : HMSActionResultListener { + hmssdk!!.startScreenshare(object : HMSActionResultListener { override fun onError(error: HMSException) { CoroutineScope(Dispatchers.Main).launch { @@ -1005,7 +987,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, private fun stopScreenShare(result: Result) { - hmssdk.stopScreenshare(HMSCommonAction.getActionListener(result)) + hmssdk!!.stopScreenshare(HMSCommonAction.getActionListener(result)) } private var androidAudioShareResult: Result? = null @@ -1024,7 +1006,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, fun requestAudioShare(data: Intent?) { - hmssdk.startAudioshare(object : HMSActionResultListener { + hmssdk!!.startAudioshare(object : HMSActionResultListener { override fun onError(error: HMSException) { CoroutineScope(Dispatchers.Main).launch { androidAudioShareResult?.success(HMSExceptionExtension.toDictionary(error)) @@ -1042,14 +1024,14 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, } private fun stopAudioShare(result: Result){ - hmssdk.stopAudioshare(HMSCommonAction.getActionListener(result)) + hmssdk!!.stopAudioshare(HMSCommonAction.getActionListener(result)) } private fun setAudioMixingMode(call: MethodCall,result: Result){ val mode = call.argument("audio_mixing_mode") if(mode!=null) { val audioMixingMode: AudioMixingMode = AudioMixingMode.valueOf(mode) - hmssdk.setAudioMixingMode(audioMixingMode) + hmssdk!!.setAudioMixingMode(audioMixingMode) } } @@ -1186,7 +1168,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, } if(p1!=null){ val audioDevicesList = ArrayList(); - for (device in hmssdk.getAudioDevicesList()){ + for (device in hmssdk!!.getAudioDevicesList()){ audioDevicesList.add(device.name); } dict["available_audio_device"] = audioDevicesList diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt index a355865e0..5d5cec173 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt @@ -27,7 +27,7 @@ class HMSVideoView( val view = inflater.inflate(R.layout.hms_video_view, this) surfaceViewRenderer = view.findViewById(R.id.surfaceViewRenderer) - surfaceViewRenderer.setEnableHardwareScaler(true) + surfaceViewRenderer.setEnableHardwareScaler(false) surfaceViewRenderer.setMirror(setMirror) if ((scaleType ?: 0) <= RendererCommon.ScalingType.values().size) { diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt index 68eeb34db..6d0faa13e 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt @@ -65,7 +65,7 @@ class HMSVideoViewFactory(private val plugin: HmssdkFlutterPlugin) : val matchParent = args!!["match_parent"] as? Boolean - val room = plugin.hmssdk.getRoom() + val room = plugin.hmssdk!!.getRoom() val track = HmsUtilities.getVideoTrack(trackId!!, room!!) diff --git a/example/android/Gemfile.lock b/example/android/Gemfile.lock index 4ee9abea7..35d25abb1 100644 --- a/example/android/Gemfile.lock +++ b/example/android/Gemfile.lock @@ -8,8 +8,8 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.611.0) - aws-sdk-core (3.131.4) + aws-partitions (1.619.0) + aws-sdk-core (3.132.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) @@ -37,7 +37,7 @@ GEM dotenv (2.8.1) emoji_regex (3.2.3) excon (0.92.4) - faraday (1.10.0) + faraday (1.10.1) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -66,7 +66,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.208.0) + fastlane (2.209.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -105,7 +105,7 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.3.4) + fastlane-plugin-firebase_app_distribution (0.3.5) gh_inspector (1.1.3) google-apis-androidpublisher_v3 (0.25.0) google-apis-core (>= 0.7, < 2.a) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index fccedb097..89367ef41 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -32,8 +32,8 @@ android { applicationId "live.hms.flutter" minSdkVersion 21 targetSdkVersion 33 - versionCode 123 - versionName "1.3.3" + versionCode 132 + versionName "1.3.12" } signingConfigs { diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 889b415ad..a022cc64b 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -50,7 +50,7 @@ diff --git a/example/ios/FlutterBroadcastUploadExtension/SampleHandler.swift b/example/ios/FlutterBroadcastUploadExtension/SampleHandler.swift index 79269e9dc..bfe695ab7 100644 --- a/example/ios/FlutterBroadcastUploadExtension/SampleHandler.swift +++ b/example/ios/FlutterBroadcastUploadExtension/SampleHandler.swift @@ -43,7 +43,7 @@ class SampleHandler: RPBroadcastSampleHandler { } break case RPSampleBufferType.audioApp: - // Handle audio sample buffer for app audio + _ = self.screenRenderer.process(audioSampleBuffer: sampleBuffer) break case RPSampleBufferType.audioMic: // Handle audio sample buffer for mic audio diff --git a/example/ios/Gemfile.lock b/example/ios/Gemfile.lock index 43fcc0c7a..ad4293675 100644 --- a/example/ios/Gemfile.lock +++ b/example/ios/Gemfile.lock @@ -8,8 +8,8 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.611.0) - aws-sdk-core (3.131.4) + aws-partitions (1.619.0) + aws-sdk-core (3.132.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) @@ -37,7 +37,7 @@ GEM dotenv (2.8.1) emoji_regex (3.2.3) excon (0.92.4) - faraday (1.10.0) + faraday (1.10.1) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -66,7 +66,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.208.0) + fastlane (2.209.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -105,7 +105,7 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.3.4) + fastlane-plugin-firebase_app_distribution (0.3.5) fastlane-plugin-versioning (0.5.1) gh_inspector (1.1.3) google-apis-androidpublisher_v3 (0.25.0) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 09686dd63..892a7b3d2 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,4 +1,38 @@ PODS: + - DKImagePickerController/Core (4.3.4): + - DKImagePickerController/ImageDataManager + - DKImagePickerController/Resource + - DKImagePickerController/ImageDataManager (4.3.4) + - DKImagePickerController/PhotoGallery (4.3.4): + - DKImagePickerController/Core + - DKPhotoGallery + - DKImagePickerController/Resource (4.3.4) + - DKPhotoGallery (0.0.17): + - DKPhotoGallery/Core (= 0.0.17) + - DKPhotoGallery/Model (= 0.0.17) + - DKPhotoGallery/Preview (= 0.0.17) + - DKPhotoGallery/Resource (= 0.0.17) + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Core (0.0.17): + - DKPhotoGallery/Model + - DKPhotoGallery/Preview + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Model (0.0.17): + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Preview (0.0.17): + - DKPhotoGallery/Model + - DKPhotoGallery/Resource + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Resource (0.0.17): + - SDWebImage + - SwiftyGif + - file_picker (0.0.1): + - DKImagePickerController/PhotoGallery + - Flutter - Firebase/Analytics (9.3.0): - Firebase/Core - Firebase/Core (9.3.0): @@ -15,26 +49,26 @@ PODS: - Firebase/Performance (9.3.0): - Firebase/CoreOnly - FirebasePerformance (~> 9.3.0) - - firebase_analytics (9.3.0): + - firebase_analytics (9.3.1): - Firebase/Analytics (= 9.3.0) - firebase_core - Flutter - - firebase_core (1.20.0): + - firebase_core (1.20.1): - Firebase/CoreOnly (= 9.3.0) - Flutter - - firebase_crashlytics (2.8.6): + - firebase_crashlytics (2.8.7): - Firebase/Crashlytics (= 9.3.0) - firebase_core - Flutter - - firebase_dynamic_links (4.3.3): + - firebase_dynamic_links (4.3.4): - Firebase/DynamicLinks (= 9.3.0) - firebase_core - Flutter - - firebase_performance (0.8.2-1): + - firebase_performance (0.8.2-2): - Firebase/Performance (= 9.3.0) - firebase_core - Flutter - - FirebaseABTesting (9.3.0): + - FirebaseABTesting (9.4.0): - FirebaseCore (~> 9.0) - FirebaseAnalytics (9.3.0): - FirebaseAnalytics/AdIdSupport (= 9.3.0) @@ -59,12 +93,12 @@ PODS: - FirebaseCoreInternal (~> 9.0) - GoogleUtilities/Environment (~> 7.7) - GoogleUtilities/Logger (~> 7.7) - - FirebaseCoreDiagnostics (9.3.0): + - FirebaseCoreDiagnostics (9.4.0): - GoogleDataTransport (< 10.0.0, >= 9.1.4) - GoogleUtilities/Environment (~> 7.7) - GoogleUtilities/Logger (~> 7.7) - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseCoreInternal (9.3.0): + - FirebaseCoreInternal (9.4.0): - "GoogleUtilities/NSData+zlib (~> 7.7)" - FirebaseCrashlytics (9.3.0): - FirebaseCore (~> 9.0) @@ -75,7 +109,7 @@ PODS: - PromisesObjC (~> 2.1) - FirebaseDynamicLinks (9.3.0): - FirebaseCore (~> 9.0) - - FirebaseInstallations (9.3.0): + - FirebaseInstallations (9.4.0): - FirebaseCore (~> 9.0) - GoogleUtilities/Environment (~> 7.7) - GoogleUtilities/UserDefaults (~> 7.7) @@ -89,7 +123,7 @@ PODS: - GoogleUtilities/ISASwizzler (~> 7.7) - GoogleUtilities/MethodSwizzler (~> 7.7) - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseRemoteConfig (9.3.0): + - FirebaseRemoteConfig (9.4.0): - FirebaseABTesting (~> 9.0) - FirebaseCore (~> 9.0) - FirebaseInstallations (~> 9.0) @@ -143,12 +177,12 @@ PODS: - GoogleUtilities/Logger - GoogleUtilities/UserDefaults (7.7.0): - GoogleUtilities/Logger - - HMSBroadcastExtensionSDK (0.0.1) + - HMSBroadcastExtensionSDK (0.0.2) - HMSSDK (0.3.3): - HMSWebRTC (= 1.0.4898) - - hmssdk_flutter (0.7.4): + - hmssdk_flutter (0.7.5): - Flutter - - HMSBroadcastExtensionSDK (= 0.0.1) + - HMSBroadcastExtensionSDK (= 0.0.2) - HMSSDK (= 0.3.3) - HMSWebRTC (1.0.4898) - MTBBarcodeScanner (5.0.11) @@ -167,8 +201,12 @@ PODS: - qr_code_scanner (0.2.0): - Flutter - MTBBarcodeScanner + - SDWebImage (5.13.2): + - SDWebImage/Core (= 5.13.2) + - SDWebImage/Core (5.13.2) - shared_preferences_ios (0.0.1): - Flutter + - SwiftyGif (5.4.3) - Toast (4.0.0) - uni_links (0.0.1): - Flutter @@ -178,6 +216,7 @@ PODS: - Flutter DEPENDENCIES: + - file_picker (from `.symlinks/plugins/file_picker/ios`) - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) @@ -198,6 +237,8 @@ DEPENDENCIES: SPEC REPOS: trunk: + - DKImagePickerController + - DKPhotoGallery - Firebase - FirebaseABTesting - FirebaseAnalytics @@ -218,9 +259,13 @@ SPEC REPOS: - MTBBarcodeScanner - nanopb - PromisesObjC + - SDWebImage + - SwiftyGif - Toast EXTERNAL SOURCES: + file_picker: + :path: ".symlinks/plugins/file_picker/ios" firebase_analytics: :path: ".symlinks/plugins/firebase_analytics/ios" firebase_core: @@ -255,30 +300,33 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wakelock/ios" SPEC CHECKSUMS: + DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac + DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 + file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95 Firebase: ef75abb1cdbc746d5a38f4e26c422c807b189b8c - firebase_analytics: 8f772ccb3dad1e8968019a158823f99e63069d2b - firebase_core: 96214f90497b808a2cf2a24517084c5f6de37b53 - firebase_crashlytics: cdba3f556462dd5ee25cc7d28790ff2de2c0edcf - firebase_dynamic_links: 0cc25cd58aa0b24acfcf10e1aaf6f32323f1c361 - firebase_performance: 82d5f536250a88ad3351583df5b1878f82cfd293 - FirebaseABTesting: d7383da017eff5dc4fccb40987fa76271fd1cdbf + firebase_analytics: 76a3a81b95ea32c1f8dbd900ec751b342d26d543 + firebase_core: e66a443ec996cb5e364dc70b4cfc1809c32cbb2e + firebase_crashlytics: 95cb124fe8ba64e9cedad8816b8aee6204f8dbeb + firebase_dynamic_links: 6b03202f0a9aca5f748d453028c54063a1ba60cd + firebase_performance: 39e081d1a507aeac5a84dff1027a01ede988a5bd + FirebaseABTesting: e59eec91fafce74a0f5261809ed0025b7e450db1 FirebaseAnalytics: bf46f5163f44097ce2c789de0b3e6f87f1da834a FirebaseCore: c088995ece701a021a48a1348ea0174877de2a6a - FirebaseCoreDiagnostics: 060eb57cc56dfaf40b1b1b5874a5c17c41ce79f8 - FirebaseCoreInternal: 635d1c9a612a6502b6377a0c92af83758076ffff + FirebaseCoreDiagnostics: aaa87098082c4d4bdd1a9557b1186d18ca85ce8c + FirebaseCoreInternal: a13302b0088fbf5f38b79b6ece49c2af7d3e05d6 FirebaseCrashlytics: 65a5b349e664e986e6c7486b0a9b5ed8c11d0491 FirebaseDynamicLinks: af0acc2d1fa117a074e84a3c06a1d9f817443490 - FirebaseInstallations: 54b40022cb06e462740c9f2b9fbe38b5e78a825a + FirebaseInstallations: 61db1054e688d2bdc4e2b3f744c1b086e913b742 FirebasePerformance: 18bb0984808ec429fae69fb5f1da4a68f57605b1 - FirebaseRemoteConfig: 0a644c924b3339bcf3bc3ea253206f171672308e + FirebaseRemoteConfig: 6d9982bc64548a6e3c1b497b9fa53938ad135f2d Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037 GoogleAppMeasurement: b907bdad775b6975a8108762345b2cfbf1a93c37 GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1 - HMSBroadcastExtensionSDK: a19186ebbf8e381f447f382448038b38a9985be0 + HMSBroadcastExtensionSDK: 2c3bf5b25453459d60799697bab1e30576690ab5 HMSSDK: 7c9d01559f1ac06cea982097138c1d3c836df0ae - hmssdk_flutter: d0dd70ae6165b2195e57ecb1447e631e437118d4 + hmssdk_flutter: 81d3fee132241d40e290c71cb238b2de11c82cf7 HMSWebRTC: d3a9b2866e4a36a1d3834728a548a4a46309bb86 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 @@ -287,7 +335,9 @@ SPEC CHECKSUMS: permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e + SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866 shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad + SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 uni_links: d97da20c7701486ba192624d99bffaaffcfc298a video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index b69c06e8c..6971d19f8 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -206,6 +206,7 @@ 7B56423DDF1948A38AE2EA10 /* [CP] Embed Pods Frameworks */, CC19BBB77EA3E7F699588CF7 /* [firebase_crashlytics] Crashlytics Upload Symbols */, EC1052BA2858A77D005EAB9E /* Embed App Extensions */, + E4ABAC2BDF36E37231B50E15 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -405,6 +406,23 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + E4ABAC2BDF36E37231B50E15 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -726,7 +744,7 @@ CODE_SIGN_ENTITLEMENTS = FlutterBroadcastUploadExtension/FlutterBroadcastUploadExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 84; + CURRENT_PROJECT_VERSION = 130; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 5N85PP82A9; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -740,7 +758,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.3.10; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = live.100ms.flutter.FlutterBroadcastUploadExtension; @@ -769,7 +787,7 @@ CODE_SIGN_ENTITLEMENTS = FlutterBroadcastUploadExtension/FlutterBroadcastUploadExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 84; + CURRENT_PROJECT_VERSION = 130; DEVELOPMENT_TEAM = 5N85PP82A9; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -782,7 +800,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.3.10; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = live.100ms.flutter.FlutterBroadcastUploadExtension; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -808,7 +826,7 @@ CODE_SIGN_ENTITLEMENTS = FlutterBroadcastUploadExtension/FlutterBroadcastUploadExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 84; + CURRENT_PROJECT_VERSION = 130; DEVELOPMENT_TEAM = 5N85PP82A9; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -821,7 +839,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.3.10; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = live.100ms.flutter.FlutterBroadcastUploadExtension; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index d9c87b52a..6bc4f9602 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.3 + 1.3.13 CFBundleSignature ???? CFBundleURLTypes @@ -44,7 +44,7 @@ CFBundleVersion - 123 + 133 ITSAppUsesNonExemptEncryption LSApplicationCategoryType @@ -63,6 +63,8 @@ 100ms needs access to Microphone to enable high quality video conferencing. NSPhotoLibraryAddUsageDescription 100ms needs access to storage for sharing file. + NSPhotoLibraryUsageDescription + 100ms needs access to storage for sharing file. UIBackgroundModes audio diff --git a/example/ios/Runner/Runner.entitlements b/example/ios/Runner/Runner.entitlements index b80ac6508..25bd6b378 100644 --- a/example/ios/Runner/Runner.entitlements +++ b/example/ios/Runner/Runner.entitlements @@ -19,6 +19,8 @@ com.apple.security.device.camera + com.apple.security.files.user-selected.read-only + com.apple.security.network.client diff --git a/example/lib/common/ui/organisms/video_tile.dart b/example/lib/common/ui/organisms/video_tile.dart index b2117a4b8..d173d1701 100644 --- a/example/lib/common/ui/organisms/video_tile.dart +++ b/example/lib/common/ui/organisms/video_tile.dart @@ -118,7 +118,6 @@ class _VideoTileState extends State { roles: _meetingStore.roles, peer: peerNode, changeRole: (role, forceChange) { - Navigator.pop(context); _meetingStore.changeRole( peer: peerNode, roleName: role, @@ -150,7 +149,6 @@ class _VideoTileState extends State { roles: _meetingStore.roles, peer: peerNode, changeRole: (role, forceChange) { - Navigator.pop(context); _meetingStore.changeRole( peer: peerNode, roleName: role, diff --git a/example/lib/common/util/utility_components.dart b/example/lib/common/util/utility_components.dart index 976afa3ec..969d0c7ad 100644 --- a/example/lib/common/util/utility_components.dart +++ b/example/lib/common/util/utility_components.dart @@ -1,5 +1,7 @@ //Package imports + import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -1301,4 +1303,120 @@ class UtilityComponents { ); })); } + + static Future showAudioShareDialog( + {required BuildContext context, + required MeetingStore meetingStore, + required bool isPlaying}) async { + double volume = meetingStore.audioPlayerVolume; + String answer = await showDialog( + context: context, + builder: (context) => StatefulBuilder(builder: (context, setState) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + backgroundColor: bottomSheetColor, + contentPadding: + EdgeInsets.only(left: 14, right: 10, top: 15, bottom: 15), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(isPlaying ? "Stop playing" : "Pick song from files"), + SizedBox(height: 10), + if (isPlaying) + Column( + children: [ + Text("Volume: ${(volume * 100).truncate()}"), + Slider( + min: 0.0, + max: 1.0, + value: volume, + onChanged: (value) { + setState(() { + volume = value; + meetingStore.setAudioPlayerVolume(volume); + }); + }, + ), + ], + ) + ], + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + style: ButtonStyle( + shadowColor: + MaterialStateProperty.all(surfaceColor), + backgroundColor: + MaterialStateProperty.all(bottomSheetColor), + shape: MaterialStateProperty.all< + RoundedRectangleBorder>( + RoundedRectangleBorder( + side: BorderSide( + width: 1, + color: Color.fromRGBO(107, 125, 153, 1)), + borderRadius: BorderRadius.circular(8.0), + ))), + onPressed: () => Navigator.pop(context, ""), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 12), + child: Text('Cancel', + style: GoogleFonts.inter( + color: defaultColor, + fontSize: 16, + fontWeight: FontWeight.w600, + letterSpacing: 0.50)), + )), + ElevatedButton( + style: ButtonStyle( + shadowColor: + MaterialStateProperty.all(surfaceColor), + backgroundColor: + MaterialStateProperty.all(hmsdefaultColor), + shape: MaterialStateProperty.all< + RoundedRectangleBorder>(RoundedRectangleBorder( + side: + BorderSide(width: 1, color: hmsdefaultColor), + borderRadius: BorderRadius.circular(8.0), + ))), + onPressed: () async { + if (isPlaying) { + meetingStore.stopAudioIos(); + Navigator.pop(context, ""); + } else { + FilePickerResult? result = + await FilePicker.platform.pickFiles(); + if (result != null) { + String? path = + "file://" + result.files.single.path!; + + Navigator.pop(context, path); + } + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 12), + child: Text( + isPlaying ? 'Stop' : 'Select', + style: GoogleFonts.inter( + color: defaultColor, + fontSize: 16, + fontWeight: FontWeight.w600, + letterSpacing: 0.50), + ), + ), + ), + ], + ) + ], + ); + })); + + return answer; + } } diff --git a/example/lib/common/util/utility_function.dart b/example/lib/common/util/utility_function.dart index db3391873..1d6960fa5 100644 --- a/example/lib/common/util/utility_function.dart +++ b/example/lib/common/util/utility_function.dart @@ -68,6 +68,10 @@ class Utilities { } static void setRTMPUrl(String roomUrl) { + if (roomUrl.contains("flutterhms.page.link") && + roomUrl.contains("meetingUrl")) { + roomUrl = roomUrl.split("meetingUrl=")[1]; + } List urlSplit = roomUrl.split('/'); int index = urlSplit.lastIndexOf("meeting"); if (index != -1) { diff --git a/example/lib/hls-streaming/hls_broadcaster_page.dart b/example/lib/hls-streaming/hls_broadcaster_page.dart index 4b51a3621..cc5abad58 100644 --- a/example/lib/hls-streaming/hls_broadcaster_page.dart +++ b/example/lib/hls-streaming/hls_broadcaster_page.dart @@ -575,92 +575,148 @@ class _HLSBroadcasterPageState extends State { .localPeer! .role .name - .contains("hls")) + .contains("hls-")) Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ if (Provider.of(context) - .localPeer != - null && - (Provider.of(context) - .localPeer - ?.role - .publishSettings - ?.allowed - .contains("audio") ?? - false)) - Selector( - selector: (_, meetingStore) => - meetingStore.isMicOn, - builder: (_, isMicOn, __) { - return EmbeddedButton( - onTap: () => { - context - .read() - .switchAudio() - }, - width: 40, - height: 40, - disabledBorderColor: - borderColor, - offColor: borderColor, - onColor: screenBackgroundColor, - isActive: isMicOn, - child: SvgPicture.asset( - isMicOn - ? "assets/icons/mic_state_on.svg" - : "assets/icons/mic_state_off.svg", - color: defaultColor, - fit: BoxFit.scaleDown, - semanticsLabel: - "audio_mute_button", - ), - ); - }), - if (Provider.of(context) - .localPeer != - null && - (Provider.of(context) - .localPeer - ?.role - .publishSettings - ?.allowed - .contains("video") ?? - false)) - Selector>( - selector: (_, meetingStore) => - Tuple2( - meetingStore.isVideoOn, - meetingStore.meetingMode == - MeetingMode.Audio), - builder: (_, data, __) { - return EmbeddedButton( - onTap: () => { - (data.item2) - ? null - : context + .localPeer != + null) + (Provider.of(context) + .localPeer + ?.role + .publishSettings + ?.allowed + .contains("audio") ?? + false) + ? Selector( + selector: (_, meetingStore) => + meetingStore.isMicOn, + builder: (_, isMicOn, __) { + return EmbeddedButton( + onTap: () => { + context .read() - .switchVideo(), - }, - width: 40, - height: 40, - disabledBorderColor: - borderColor, - offColor: borderColor, - onColor: screenBackgroundColor, - isActive: data.item1, - child: SvgPicture.asset( - data.item1 - ? "assets/icons/cam_state_on.svg" - : "assets/icons/cam_state_off.svg", - color: defaultColor, - fit: BoxFit.scaleDown, - semanticsLabel: - "video_mute_button"), - ); - }), + .switchAudio() + }, + width: 40, + height: 40, + disabledBorderColor: + borderColor, + offColor: borderColor, + onColor: + screenBackgroundColor, + isActive: isMicOn, + child: SvgPicture.asset( + isMicOn + ? "assets/icons/mic_state_on.svg" + : "assets/icons/mic_state_off.svg", + color: defaultColor, + fit: BoxFit.scaleDown, + semanticsLabel: + "audio_mute_button", + ), + ); + }) + : Selector( + selector: (_, meetingStore) => + meetingStore.isSpeakerOn, + builder: (_, isSpeakerOn, __) { + return EmbeddedButton( + onTap: () => { + context + .read() + .toggleSpeaker(), + }, + width: 40, + height: 40, + disabledBorderColor: + borderColor, + offColor: borderColor, + onColor: + screenBackgroundColor, + isActive: isSpeakerOn, + child: SvgPicture.asset( + isSpeakerOn + ? "assets/icons/speaker_state_on.svg" + : "assets/icons/speaker_state_off.svg", + color: defaultColor, + fit: BoxFit.scaleDown, + semanticsLabel: + "speaker_mute_button"), + ); + }), + if (Provider.of(context) + .localPeer != + null) + (Provider.of(context) + .localPeer + ?.role + .publishSettings + ?.allowed + .contains("video") ?? + false) + ? Selector>( + selector: (_, meetingStore) => + Tuple2( + meetingStore.isVideoOn, + meetingStore + .meetingMode == + MeetingMode.Audio), + builder: (_, data, __) { + return EmbeddedButton( + onTap: () => { + (data.item2) + ? null + : context + .read< + MeetingStore>() + .switchVideo(), + }, + width: 40, + height: 40, + disabledBorderColor: + borderColor, + offColor: borderColor, + onColor: + screenBackgroundColor, + isActive: data.item1, + child: SvgPicture.asset( + data.item1 + ? "assets/icons/cam_state_on.svg" + : "assets/icons/cam_state_off.svg", + color: defaultColor, + fit: BoxFit.scaleDown, + semanticsLabel: + "video_mute_button"), + ); + }) + : Selector( + selector: (_, meetingStore) => + meetingStore.isStatsVisible, + builder: + (_, isStatsVisible, __) { + return EmbeddedButton( + width: 40, + height: 40, + onTap: () => context + .read() + .changeStatsVisible(), + disabledBorderColor: + borderColor, + offColor: + screenBackgroundColor, + onColor: borderColor, + isActive: isStatsVisible, + child: SvgPicture.asset( + "assets/icons/stats.svg", + fit: BoxFit.scaleDown, + semanticsLabel: + "stats_button"), + ); + }), if (Provider.of(context) .localPeer != null) @@ -807,49 +863,76 @@ class _HLSBroadcasterPageState extends State { ); }), if (Provider.of(context) - .localPeer != - null && - (Provider.of(context) - .localPeer - ?.role - .publishSettings - ?.allowed - .contains("screen") ?? - false)) - Selector( - selector: (_, meetingStore) => - meetingStore.isScreenShareOn, - builder: (_, data, __) { - return EmbeddedButton( - onTap: () { - MeetingStore meetingStore = - Provider.of( - context, - listen: false); - if (meetingStore - .isScreenShareOn) { - meetingStore - .stopScreenShare(); - } else { + .localPeer != + null) + (Provider.of(context) + .localPeer + ?.role + .publishSettings + ?.allowed + .contains("screen") ?? + false) + ? Selector( + selector: (_, meetingStore) => meetingStore - .startScreenShare(); - } - }, - width: 40, - height: 40, - disabledBorderColor: - borderColor, - offColor: screenBackgroundColor, - onColor: borderColor, - isActive: data, - child: SvgPicture.asset( - "assets/icons/screen_share.svg", - color: defaultColor, - fit: BoxFit.scaleDown, - semanticsLabel: - "screen_share_button"), - ); - }), + .isScreenShareOn, + builder: (_, data, __) { + return EmbeddedButton( + onTap: () { + MeetingStore + meetingStore = + Provider.of< + MeetingStore>( + context, + listen: false); + if (meetingStore + .isScreenShareOn) { + meetingStore + .stopScreenShare(); + } else { + meetingStore + .startScreenShare(); + } + }, + width: 40, + height: 40, + disabledBorderColor: + borderColor, + offColor: + screenBackgroundColor, + onColor: borderColor, + isActive: data, + child: SvgPicture.asset( + "assets/icons/screen_share.svg", + color: defaultColor, + fit: BoxFit.scaleDown, + semanticsLabel: + "screen_share_button"), + ); + }) + : Selector( + selector: (_, meetingStore) => + (meetingStore.isBRB), + builder: (_, isBRB, __) { + return EmbeddedButton( + width: 40, + height: 40, + onTap: () => context + .read() + .changeMetadataBRB(), + disabledBorderColor: + borderColor, + offColor: + screenBackgroundColor, + onColor: borderColor, + isActive: isBRB, + child: SvgPicture.asset( + "assets/icons/brb.svg", + fit: BoxFit.scaleDown, + semanticsLabel: + "brb_button"), + ); + }), if (Provider.of(context) .localPeer != null) diff --git a/example/lib/hls-streaming/hls_message.dart b/example/lib/hls-streaming/hls_message.dart index afdc3da8b..ece8b7664 100644 --- a/example/lib/hls-streaming/hls_message.dart +++ b/example/lib/hls-streaming/hls_message.dart @@ -86,7 +86,7 @@ class _HLSMessageState extends State { child: Padding( padding: MediaQuery.of(context).viewInsets, child: FractionallySizedBox( - heightFactor: 0.8, + heightFactor: 0.81, child: Padding( padding: const EdgeInsets.only( top: 20.0, left: 10, right: 10, bottom: 10), diff --git a/example/lib/hls-streaming/hls_screen_controller.dart b/example/lib/hls-streaming/hls_screen_controller.dart index 7f6bca983..ad306b189 100644 --- a/example/lib/hls-streaming/hls_screen_controller.dart +++ b/example/lib/hls-streaming/hls_screen_controller.dart @@ -53,7 +53,7 @@ class _HLSScreenControllerState extends State { .localPeer! .role .name - .contains("hls")) { + .contains("hls-")) { return HLSViewerPage(); } else { return HLSBroadcasterPage( diff --git a/example/lib/hls-streaming/hls_settings.dart b/example/lib/hls-streaming/hls_settings.dart index 5b9d39d46..ceeee9ebe 100644 --- a/example/lib/hls-streaming/hls_settings.dart +++ b/example/lib/hls-streaming/hls_settings.dart @@ -18,152 +18,199 @@ class _HLSSettingsState extends State { @override Widget build(BuildContext context) { return FractionallySizedBox( - heightFactor: 0.5, + heightFactor: 0.6, child: Padding( padding: const EdgeInsets.only(top: 20.0, left: 15, right: 15), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + "More Options", + style: GoogleFonts.inter( + fontSize: 16, + color: defaultColor, + letterSpacing: 0.15, + fontWeight: FontWeight.w600), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + icon: SvgPicture.asset( + "assets/icons/close_button.svg", + width: 40, + ), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ) + ], + ), + Padding( + padding: EdgeInsets.only(top: 15, bottom: 10), + child: Divider( + color: dividerColor, + height: 5, + ), + ), + Expanded( + child: ListView( children: [ - Row( - children: [ - Text( - "More Options", + if (Platform.isAndroid) + ListTile( + horizontalTitleGap: 2, + onTap: () async { + Navigator.pop(context); + showModalBottomSheet( + isScrollControlled: true, + backgroundColor: bottomSheetColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + context: context, + builder: (ctx) => ChangeNotifierProvider.value( + value: context.read(), + child: HLSDeviceSettings()), + ); + }, + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + "assets/icons/settings.svg", + fit: BoxFit.scaleDown, + ), + title: Text( + "Device Settings", style: GoogleFonts.inter( - fontSize: 16, + fontSize: 14, color: defaultColor, - letterSpacing: 0.15, + letterSpacing: 0.25, fontWeight: FontWeight.w600), ), - ], + ), + ListTile( + horizontalTitleGap: 2, + onTap: () async { + FocusManager.instance.primaryFocus?.unfocus(); + String name = + await UtilityComponents.showNameChangeDialog( + context: context, + placeholder: "Enter Name", + prefilledValue: context + .read() + .localPeer + ?.name ?? + ""); + if (name.isNotEmpty) { + context.read().changeName(name: name); + } + Navigator.pop(context); + }, + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + "assets/icons/pencil.svg", + fit: BoxFit.scaleDown, + ), + title: Text( + "Change Name", + style: GoogleFonts.inter( + fontSize: 14, + color: defaultColor, + letterSpacing: 0.25, + fontWeight: FontWeight.w600), + ), ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - IconButton( - icon: SvgPicture.asset( - "assets/icons/close_button.svg", - width: 40, - ), - onPressed: () { - Navigator.pop(context); - }, - ), - ], - ) - ], - ), - Padding( - padding: EdgeInsets.only(top: 15, bottom: 10), - child: Divider( - color: dividerColor, - height: 5, - ), - ), - if (Platform.isAndroid) - ListTile( - horizontalTitleGap: 2, - onTap: () async { - Navigator.pop(context); - showModalBottomSheet( - isScrollControlled: true, - backgroundColor: bottomSheetColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - context: context, - builder: (ctx) => ChangeNotifierProvider.value( - value: context.read(), - child: HLSDeviceSettings()), - ); - }, - contentPadding: EdgeInsets.zero, - leading: SvgPicture.asset( - "assets/icons/settings.svg", - fit: BoxFit.scaleDown, + ListTile( + horizontalTitleGap: 2, + onTap: () { + Navigator.pop(context); + context.read().toggleSpeaker(); + }, + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + "assets/icons/speaker_state_on.svg", + fit: BoxFit.scaleDown, + ), + title: Text( + "Change Speaker State", + style: GoogleFonts.inter( + fontSize: 14, + color: defaultColor, + letterSpacing: 0.25, + fontWeight: FontWeight.w600), + ), ), - title: Text( - "Device Settings", - style: GoogleFonts.inter( - fontSize: 14, - color: defaultColor, - letterSpacing: 0.25, - fontWeight: FontWeight.w600), + ListTile( + horizontalTitleGap: 2, + onTap: () { + context.read().switchCamera(); + Navigator.pop(context); + }, + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + "assets/icons/camera.svg", + fit: BoxFit.scaleDown, + ), + title: Text( + "Switch Camera", + style: GoogleFonts.inter( + fontSize: 14, + color: defaultColor, + letterSpacing: 0.25, + fontWeight: FontWeight.w600), + ), ), - ), - ListTile( - horizontalTitleGap: 2, - onTap: () async { - FocusManager.instance.primaryFocus?.unfocus(); - String name = await UtilityComponents.showNameChangeDialog( - context: context, - placeholder: "Enter Name", - prefilledValue: - context.read().localPeer?.name ?? ""); - if (name.isNotEmpty) { - context.read().changeName(name: name); - } - Navigator.pop(context); - }, - contentPadding: EdgeInsets.zero, - leading: SvgPicture.asset( - "assets/icons/pencil.svg", - fit: BoxFit.scaleDown, - ), - title: Text( - "Change Name", - style: GoogleFonts.inter( - fontSize: 14, - color: defaultColor, - letterSpacing: 0.25, - fontWeight: FontWeight.w600), - ), - ), - ListTile( - horizontalTitleGap: 2, - onTap: () { - Navigator.pop(context); - context.read().toggleSpeaker(); - }, - contentPadding: EdgeInsets.zero, - leading: SvgPicture.asset( - "assets/icons/speaker_state_on.svg", - fit: BoxFit.scaleDown, - ), - title: Text( - "Change Speaker State", - style: GoogleFonts.inter( - fontSize: 14, - color: defaultColor, - letterSpacing: 0.25, - fontWeight: FontWeight.w600), - ), - ), - ListTile( - horizontalTitleGap: 2, - onTap: () { - context.read().switchCamera(); - Navigator.pop(context); - }, - contentPadding: EdgeInsets.zero, - leading: SvgPicture.asset( - "assets/icons/camera.svg", - fit: BoxFit.scaleDown, - ), - title: Text( - "Switch Camera", - style: GoogleFonts.inter( - fontSize: 14, - color: defaultColor, - letterSpacing: 0.25, - fontWeight: FontWeight.w600), - ), + ListTile( + horizontalTitleGap: 2, + onTap: () async { + context.read().changeMetadataBRB(); + Navigator.pop(context); + }, + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + "assets/icons/brb.svg", + fit: BoxFit.scaleDown, + ), + title: Text( + "BRB", + style: GoogleFonts.inter( + fontSize: 14, + color: defaultColor, + letterSpacing: 0.25, + fontWeight: FontWeight.w600), + ), + ), + ListTile( + horizontalTitleGap: 2, + onTap: () async { + context.read().changeStatsVisible(); + Navigator.pop(context); + }, + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + "assets/icons/stats.svg", + fit: BoxFit.scaleDown, + ), + title: Text( + "${context.read().isStatsVisible ? "Hide" : "Show"} Stats", + style: GoogleFonts.inter( + fontSize: 14, + color: defaultColor, + letterSpacing: 0.25, + fontWeight: FontWeight.w600), + )), + ], ), - ], - ), + ) + ], ), ), ); diff --git a/example/lib/hls-streaming/util/hls_participant_sheet.dart b/example/lib/hls-streaming/util/hls_participant_sheet.dart index 55a8893c0..b5a13b17f 100644 --- a/example/lib/hls-streaming/util/hls_participant_sheet.dart +++ b/example/lib/hls-streaming/util/hls_participant_sheet.dart @@ -241,7 +241,7 @@ class _HLSParticipantSheetState extends State { @override Widget build(BuildContext context) { return FractionallySizedBox( - heightFactor: 0.8, + heightFactor: 0.81, child: Material( color: bottomSheetColor, child: Padding( diff --git a/example/lib/main.dart b/example/lib/main.dart index 6f32a2420..61c1bdec7 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -497,7 +497,6 @@ class _HomePageState extends State { }, style: GoogleFonts.inter(), controller: meetingLinkController, - keyboardType: TextInputType.url, onChanged: (value) { setState(() {}); }, diff --git a/example/lib/meeting/hms_sdk_interactor.dart b/example/lib/meeting/hms_sdk_interactor.dart index 55489f469..588cf50ec 100644 --- a/example/lib/meeting/hms_sdk_interactor.dart +++ b/example/lib/meeting/hms_sdk_interactor.dart @@ -16,16 +16,27 @@ class HMSSDKInteractor { bool skipPreview = false; HMSSDKInteractor({String? appGroup, String? preferredExtension}) { - hmsSDK = HMSSDK(appGroup: appGroup, preferredExtension: preferredExtension); + HMSTrackSetting trackSetting = HMSTrackSetting( + audioTrackSetting: HMSAudioTrackSetting( + audioSource: HMSAudioMixerSource(node: [ + HMSAudioFilePlayerNode("audioFilePlayerNode"), + HMSMicNode(), + HMSScreenBroadcastAudioReceiverNode() + ])), + videoTrackSetting: HMSVideoTrackSetting()); + hmsSDK = HMSSDK( + appGroup: appGroup, + preferredExtension: preferredExtension, + hmsTrackSetting: trackSetting); hmsSDK.build(); } - void join({required HMSConfig config}) async { + void join({required HMSConfig config}) { this.config = config; hmsSDK.join(config: this.config); } - void leave({required HMSActionResultListener hmsActionResultListener}) async { + void leave({HMSActionResultListener? hmsActionResultListener}) { hmsSDK.leave(hmsActionResultListener: hmsActionResultListener); } @@ -54,7 +65,7 @@ class HMSSDKInteractor { } void sendDirectMessage(String message, HMSPeer peerTo, - HMSActionResultListener hmsActionResultListener) async { + HMSActionResultListener hmsActionResultListener) { hmsSDK.sendDirectMessage( message: message, peerTo: peerTo, @@ -63,7 +74,7 @@ class HMSSDKInteractor { } void sendGroupMessage(String message, List hmsRolesTo, - HMSActionResultListener hmsActionResultListener) async { + HMSActionResultListener hmsActionResultListener) { hmsSDK.sendGroupMessage( message: message, hmsRolesTo: hmsRolesTo, @@ -71,7 +82,7 @@ class HMSSDKInteractor { hmsActionResultListener: hmsActionResultListener); } - Future preview({required HMSConfig config}) async { + Future preview({required HMSConfig config}) { this.config = config; return hmsSDK.preview(config: config); } @@ -300,4 +311,18 @@ class HMSSDKInteractor { void setAudioMixingMode(HMSAudioMixingMode audioMixingMode) { hmsSDK.setAudioMixingMode(audioMixingMode: audioMixingMode); } + + Future getTrackSettings() async { + return await hmsSDK.getTrackSettings(); + } + + void setTrackSettings( + {HMSActionResultListener? hmsActionResultListener, + required HMSTrackSetting hmsTrackSetting}) { + hmsSDK.setTrackSettings(hmsTrackSetting: hmsTrackSetting); + } + + void destroy() { + hmsSDK.destroy(); + } } diff --git a/example/lib/meeting/meeting_page.dart b/example/lib/meeting/meeting_page.dart index 5bc6b1cd2..8ba64e845 100644 --- a/example/lib/meeting/meeting_page.dart +++ b/example/lib/meeting/meeting_page.dart @@ -198,10 +198,20 @@ class _MeetingPageState extends State } break; case 12: - if (_meetingStore.isAudioShareStarted) + if (Platform.isAndroid) if (_meetingStore.isAudioShareStarted) _meetingStore.stopAudioShare(); else _meetingStore.startAudioShare(); + if (Platform.isIOS) { + bool isPlaying = await _meetingStore.isPlayerRunningIos(); + String url = await UtilityComponents.showAudioShareDialog( + context: context, + meetingStore: _meetingStore, + isPlaying: isPlaying); + if (url != "") { + _meetingStore.playAudioIos(url); + } + } break; case 13: if (_meetingStore.isAudioShareStarted) @@ -992,6 +1002,26 @@ class _MeetingPageState extends State ]), value: 12, ), + if (Platform.isIOS) + PopupMenuItem( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Audio Share", + style: GoogleFonts.inter( + color: meetingStore.isAudioShareStarted + ? Colors.blue + : iconColor, + )), + Icon( + Icons.music_note, + color: meetingStore.isAudioShareStarted + ? Colors.blue + : iconColor, + ) + ]), + value: 12, + ), if (Platform.isAndroid && meetingStore.isAudioShareStarted) PopupMenuItem( child: Row( @@ -1008,6 +1038,7 @@ class _MeetingPageState extends State ]), value: 13, ), + PopupMenuItem( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/example/lib/meeting/meeting_store.dart b/example/lib/meeting/meeting_store.dart index 8a48b79d7..1cb7ab9b4 100644 --- a/example/lib/meeting/meeting_store.dart +++ b/example/lib/meeting/meeting_store.dart @@ -141,6 +141,10 @@ class MeetingStore extends ChangeNotifier bool hlsStreamingRetry = false; + bool isTrackSettingApplied = false; + + double audioPlayerVolume = 1.0; + Future join(String user, String roomUrl) async { List? token = await RoomService().getToken(user: user, room: roomUrl); @@ -164,6 +168,7 @@ class MeetingStore extends ChangeNotifier _hmsSDKInteractor.removeStatsListener(this); WidgetsBinding.instance!.removeObserver(this); _hmsSDKInteractor.leave(hmsActionResultListener: this); + _hmsSDKInteractor.destroy(); } Future switchAudio() async { @@ -198,6 +203,7 @@ class MeetingStore extends ChangeNotifier void endRoom(bool lock, String? reason) { _hmsSDKInteractor.endRoom(lock, reason == null ? "" : reason, this); + _hmsSDKInteractor.destroy(); } void removePeerFromRoom(HMSPeer peer) { @@ -247,6 +253,8 @@ class MeetingStore extends ChangeNotifier } void stopCapturing() { + isVideoOn = false; + notifyListeners(); _hmsSDKInteractor.stopCapturing(); } @@ -796,7 +804,7 @@ class MeetingStore extends ChangeNotifier int index = this.peers.indexOf(peer); this.peers.removeAt(index); this.peers.insert(index, peer); - notifyListeners(); + // notifyListeners(); } void updateFilteredList(HMSPeerUpdate peerUpdate, HMSPeer peer) { @@ -1142,6 +1150,45 @@ class MeetingStore extends ChangeNotifier return false; } + HMSAudioFilePlayerNode audioFilePlayerNode = + HMSAudioFilePlayerNode("audioFilePlayerNode"); + HMSMicNode micNode = HMSMicNode(); + // void setTrackSettings() async { + // HMSTrackSetting trackSetting = HMSTrackSetting( + // audioTrackSetting: HMSAudioTrackSetting( + // maxBitrate: 32, + // audioSource: HMSAudioMixerSource(node: [ + // HMSAudioFilePlayerNode("audioFilePlayerNode"), + // HMSMicNode() + // ])), + // videoTrackSetting: HMSVideoTrackSetting( + // cameraFacing: HMSCameraFacing.FRONT, + // maxBitrate: 512, + // maxFrameRate: 25, + // resolution: HMSResolution(height: 180, width: 320))); + // _hmsSDKInteractor.setTrackSettings(hmsTrackSetting: trackSetting); + // isTrackSettingApplied = true; + // notifyListeners(); + // } + + void playAudioIos(String url) { + audioFilePlayerNode.play(fileUrl: url); + } + + Future isPlayerRunningIos() async { + bool isPlaying = await audioFilePlayerNode.isPlaying(); + return isPlaying; + } + + void stopAudioIos() { + audioFilePlayerNode.stop(); + } + + void setAudioPlayerVolume(double volume) { + audioFilePlayerNode.setVolume(volume); + audioPlayerVolume = volume; + } + //Get onSuccess or onException callbacks for HMSActionResultListenerMethod @override @@ -1275,6 +1322,9 @@ class MeetingStore extends ChangeNotifier isAudioShareStarted = false; notifyListeners(); break; + case HMSActionResultListenerMethod.setTrackSettings: + // TODO: Handle this case. + break; } } @@ -1344,6 +1394,7 @@ class MeetingStore extends ChangeNotifier hlsStreamingRetry = true; } else { Utilities.showToast("Start HLS failed"); + hlsStreamingRetry = false; } break; @@ -1368,6 +1419,9 @@ class MeetingStore extends ChangeNotifier case HMSActionResultListenerMethod.stopAudioShare: Utilities.showToast("Stop audio share failed"); break; + case HMSActionResultListenerMethod.setTrackSettings: + // TODO: Handle this case. + break; } } @@ -1379,7 +1433,7 @@ class MeetingStore extends ChangeNotifier } if (state == AppLifecycleState.resumed) { WidgetsBinding.instance!.addPostFrameCallback((_) { - if (localPeer?.role.name.contains("hls") ?? false) + if (localPeer?.role.name.contains("hls-") ?? false) hlsVideoController = new VideoPlayerController.network( streamUrl, )..initialize().then((_) { @@ -1401,13 +1455,13 @@ class MeetingStore extends ChangeNotifier }); } else if (state == AppLifecycleState.paused) { HMSLocalPeer? localPeer = await getLocalPeer(); - if (localPeer?.role.name.contains("hls") ?? false) { + if (localPeer?.role.name.contains("hls-") ?? false) { hlsVideoController?.dispose(); hlsVideoController = null; notifyListeners(); } if (localPeer != null && !(localPeer.videoTrack?.isMute ?? true)) { - stopCapturing(); + switchVideo(); } for (PeerTrackNode peerTrackNode in peerTracks) { peerTrackNode.setOffScreenStatus(true); @@ -1415,7 +1469,7 @@ class MeetingStore extends ChangeNotifier } else if (state == AppLifecycleState.inactive) { HMSLocalPeer? localPeer = await getLocalPeer(); if (localPeer != null && !(localPeer.videoTrack?.isMute ?? true)) { - stopCapturing(); + switchVideo(); } for (PeerTrackNode peerTrackNode in peerTracks) { peerTrackNode.setOffScreenStatus(true); diff --git a/example/lib/preview/preview_page.dart b/example/lib/preview/preview_page.dart index f4aef5266..7bec6f973 100644 --- a/example/lib/preview/preview_page.dart +++ b/example/lib/preview/preview_page.dart @@ -78,319 +78,331 @@ class _PreviewPageState extends State { disableInteraction: true, offlineWidget: UtilityComponents.showReconnectingDialog(context, alertMessage: "Leave Preview"), - child: Scaffold( - body: Stack( - children: [ - (_previewStore.peer == null) - ? Center( - child: CircularProgressIndicator( - strokeWidth: 2, - ), - ) - : (_previewStore.peer!.role.name.contains("hls")) - ? Container( - child: Center( - child: CircleAvatar( - backgroundColor: defaultAvatarColor, - radius: 40, - child: Text( - Utilities.getAvatarTitle( - _previewStore.peer!.name), - style: GoogleFonts.inter( - fontSize: 40, - color: Colors.white, - ), - )), - ), - ) - : (_previewStore.localTracks.isEmpty) - ? Center( - child: CircularProgressIndicator( - strokeWidth: 2, - ), - ) - : Container( - height: height, - width: width, - child: (_previewStore.isVideoOn) - ? HMSVideoView( - scaleType: ScaleType.SCALE_ASPECT_FILL, - track: _previewStore.localTracks[0], - setMirror: true, - matchParent: false, - ) - : Container( - child: Center( - child: CircleAvatar( - backgroundColor: defaultAvatarColor, - radius: 40, - child: Text( - Utilities.getAvatarTitle( - _previewStore.peer!.name), - style: GoogleFonts.inter( - fontSize: 40, - color: Colors.white, - ), - )), - ), + child: WillPopScope( + onWillPop: () async { + context.read().leave(); + return true; + }, + child: Scaffold( + body: Stack( + children: [ + (_previewStore.peer == null) + ? Center( + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ) + : (_previewStore.peer!.role.name.contains("hls-")) + ? Container( + child: Center( + child: CircleAvatar( + backgroundColor: defaultAvatarColor, + radius: 40, + child: Text( + Utilities.getAvatarTitle( + _previewStore.peer!.name), + style: GoogleFonts.inter( + fontSize: 40, + color: Colors.white, ), + )), ), - Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Center( - child: Column( - children: [ - SizedBox( - height: orientation == Orientation.portrait - ? width * 0.2 - : width * 0.05, - ), - Text( - "Let's get you started, ${widget.name.split(' ')[0].substring(0, min(widget.name.split(' ')[0].length, 10))}!", - textAlign: TextAlign.center, - style: GoogleFonts.inter( - color: defaultColor, - fontSize: 28, - fontWeight: FontWeight.w600)), - Text("Set your studio setup in less than 5 minutes", - textAlign: TextAlign.center, - style: GoogleFonts.inter( - color: disabledTextColor, - fontSize: 14, - fontWeight: FontWeight.w400)), - ], + ) + : (_previewStore.localTracks.isEmpty && + _previewStore.isVideoOn) + ? Center( + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ) + : Container( + height: height, + width: width, + child: (_previewStore.isVideoOn) + ? HMSVideoView( + scaleType: ScaleType.SCALE_ASPECT_FILL, + track: _previewStore.localTracks[0], + setMirror: true, + matchParent: false, + ) + : Container( + child: Center( + child: CircleAvatar( + backgroundColor: + defaultAvatarColor, + radius: 40, + child: Text( + Utilities.getAvatarTitle( + _previewStore.peer!.name), + style: GoogleFonts.inter( + fontSize: 40, + color: Colors.white, + ), + )), + ), + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Center( + child: Column( + children: [ + SizedBox( + height: orientation == Orientation.portrait + ? width * 0.2 + : width * 0.05, + ), + Text( + "Let's get you started, ${widget.name.split(' ')[0].substring(0, min(widget.name.split(' ')[0].length, 10))}!", + textAlign: TextAlign.center, + style: GoogleFonts.inter( + color: defaultColor, + fontSize: 28, + fontWeight: FontWeight.w600)), + Text("Set your studio setup in less than 5 minutes", + textAlign: TextAlign.center, + style: GoogleFonts.inter( + color: disabledTextColor, + fontSize: 14, + fontWeight: FontWeight.w400)), + ], + ), ), ), - ), - Padding( - padding: const EdgeInsets.only( - bottom: 15.0, left: 8, right: 8), - child: (_previewStore.peer != null) - ? Column( - children: [ - if (_previewStore.peer != null && - !_previewStore.peer!.role.name - .contains("hls")) - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - if (_previewStore.peer != null && - context - .read() - .peer! - .role - .publishSettings! - .allowed - .contains("audio")) - EmbeddedButton( - onTap: () async => - _previewStore.switchAudio( - isOn: _previewStore - .isAudioOn), - offColor: defaultColor, - onColor: borderColor, - isActive: _previewStore.isAudioOn, - child: SvgPicture.asset( - _previewStore.isAudioOn - ? "assets/icons/mic_state_on.svg" - : "assets/icons/mic_state_off.svg", - color: _previewStore.isAudioOn - ? defaultColor - : Colors.black, - fit: BoxFit.scaleDown, - semanticsLabel: - "audio_mute_button", - ), - ), - SizedBox( - width: 10, - ), - if (_previewStore.peer != null && - _previewStore.peer!.role - .publishSettings!.allowed - .contains("video")) - EmbeddedButton( - onTap: () async => (_previewStore - .localTracks.isEmpty) - ? null - : _previewStore.switchVideo( - isOn: _previewStore - .isVideoOn), - offColor: defaultColor, - onColor: borderColor, - isActive: _previewStore.isVideoOn, - child: SvgPicture.asset( - _previewStore.isVideoOn - ? "assets/icons/cam_state_on.svg" - : "assets/icons/cam_state_off.svg", - color: _previewStore.isVideoOn - ? defaultColor - : Colors.black, - fit: BoxFit.scaleDown, - semanticsLabel: - "video_mute_button", + Padding( + padding: const EdgeInsets.only( + bottom: 15.0, left: 8, right: 8), + child: (_previewStore.peer != null) + ? Column( + children: [ + if (_previewStore.peer != null && + !_previewStore.peer!.role.name + .contains("hls-")) + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + if (_previewStore.peer != null && + context + .read() + .peer! + .role + .publishSettings! + .allowed + .contains("audio")) + EmbeddedButton( + onTap: () async => + _previewStore.switchAudio( + isOn: _previewStore + .isAudioOn), + offColor: defaultColor, + onColor: borderColor, + isActive: + _previewStore.isAudioOn, + child: SvgPicture.asset( + _previewStore.isAudioOn + ? "assets/icons/mic_state_on.svg" + : "assets/icons/mic_state_off.svg", + color: _previewStore.isAudioOn + ? defaultColor + : Colors.black, + fit: BoxFit.scaleDown, + semanticsLabel: + "audio_mute_button", + ), ), + SizedBox( + width: 10, ), - ], - ), - Row( - children: [ - if (_previewStore.networkQuality != - null && - _previewStore.networkQuality != - -1) - EmbeddedButton( - onTap: () { - switch (_previewStore - .networkQuality) { - case 0: - Utilities.showToast( - "Very Bad network"); - break; - case 1: - Utilities.showToast( - "Poor network"); - break; - case 2: - Utilities.showToast( - "Bad network"); - break; - case 3: - Utilities.showToast( - "Average network"); - break; - case 4: - Utilities.showToast( - "Good network"); - break; - case 5: - Utilities.showToast( - "Best network"); - break; - default: - break; - } - }, - offColor: dividerColor, - onColor: dividerColor, - isActive: true, + if (_previewStore.peer != null && + _previewStore.peer!.role + .publishSettings!.allowed + .contains("video")) + EmbeddedButton( + onTap: () async => (_previewStore + .localTracks.isEmpty) + ? null + : _previewStore.switchVideo( + isOn: _previewStore + .isVideoOn), + offColor: defaultColor, + onColor: borderColor, + isActive: + _previewStore.isVideoOn, child: SvgPicture.asset( - 'assets/icons/network_${_previewStore.networkQuality}.svg', + _previewStore.isVideoOn + ? "assets/icons/cam_state_on.svg" + : "assets/icons/cam_state_off.svg", + color: _previewStore.isVideoOn + ? defaultColor + : Colors.black, fit: BoxFit.scaleDown, semanticsLabel: - "network_button", - )), - ], - ) - ], - ), - SizedBox( - height: 30, - ), - HMSButton( - width: width * 0.5, - onPressed: () async => { - context - .read() - .removePreviewListener(), - _previewStore - .hmsSDKInteractor.mirrorCamera = true, - _previewStore.hmsSDKInteractor.showStats = - false, - _previewStore.hmsSDKInteractor.skipPreview = - false, - if (widget.meetingFlow == - MeetingFlow.meeting) - { - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (_) => - ListenableProvider.value( - value: MeetingStore( - hmsSDKInteractor: - _previewStore - .hmsSDKInteractor, - ), - child: MeetingPage( - meetingLink: - widget.meetingLink, - flow: - MeetingFlow.meeting, - user: widget.name, - isAudioOn: _previewStore - .isAudioOn, - localPeerNetworkQuality: - _previewStore - .networkQuality, - ), - ))) - } - else - { - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (_) => - ListenableProvider.value( - value: MeetingStore( - hmsSDKInteractor: - _previewStore - .hmsSDKInteractor, - ), - child: - HLSScreenController( - isAudioOn: _previewStore - .isAudioOn, - meetingLink: - widget.meetingLink, - localPeerNetworkQuality: - _previewStore - .networkQuality, - user: widget.name, - ), - ))) - } - }, - childWidget: Container( - padding: - const EdgeInsets.fromLTRB(8, 16, 8, 16), - decoration: BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(8))), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Text('Enter Studio', - style: GoogleFonts.inter( - color: enabledTextColor, - height: 1, - fontSize: 16, - fontWeight: FontWeight.w600)), - SizedBox( - width: 4, + "video_mute_button", + ), + ), + ], ), - Icon( - Icons.arrow_forward, - color: enabledTextColor, - size: 16, + Row( + children: [ + if (_previewStore.networkQuality != + null && + _previewStore.networkQuality != + -1) + EmbeddedButton( + onTap: () { + switch (_previewStore + .networkQuality) { + case 0: + Utilities.showToast( + "Very Bad network"); + break; + case 1: + Utilities.showToast( + "Poor network"); + break; + case 2: + Utilities.showToast( + "Bad network"); + break; + case 3: + Utilities.showToast( + "Average network"); + break; + case 4: + Utilities.showToast( + "Good network"); + break; + case 5: + Utilities.showToast( + "Best network"); + break; + default: + break; + } + }, + offColor: dividerColor, + onColor: dividerColor, + isActive: true, + child: SvgPicture.asset( + 'assets/icons/network_${_previewStore.networkQuality}.svg', + fit: BoxFit.scaleDown, + semanticsLabel: + "network_button", + )), + ], ) ], ), + SizedBox( + height: 30, ), - ), - ], - ) - : SizedBox()) - ], - ), - ], + HMSButton( + width: width * 0.5, + onPressed: () async => { + context + .read() + .removePreviewListener(), + _previewStore.hmsSDKInteractor! + .mirrorCamera = true, + _previewStore + .hmsSDKInteractor!.showStats = false, + _previewStore.hmsSDKInteractor + ?.skipPreview = false, + if (widget.meetingFlow == + MeetingFlow.meeting) + { + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (_) => + ListenableProvider.value( + value: MeetingStore( + hmsSDKInteractor: + _previewStore + .hmsSDKInteractor!, + ), + child: MeetingPage( + meetingLink: widget + .meetingLink, + flow: MeetingFlow + .meeting, + user: widget.name, + isAudioOn: + _previewStore + .isAudioOn, + localPeerNetworkQuality: + _previewStore + .networkQuality, + ), + ))) + } + else + { + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (_) => + ListenableProvider.value( + value: MeetingStore( + hmsSDKInteractor: + _previewStore + .hmsSDKInteractor!, + ), + child: + HLSScreenController( + isAudioOn: + _previewStore + .isAudioOn, + meetingLink: widget + .meetingLink, + localPeerNetworkQuality: + _previewStore + .networkQuality, + user: widget.name, + ), + ))) + } + }, + childWidget: Container( + padding: const EdgeInsets.fromLTRB( + 8, 16, 8, 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(8))), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Text('Enter Studio', + style: GoogleFonts.inter( + color: enabledTextColor, + height: 1, + fontSize: 16, + fontWeight: FontWeight.w600)), + SizedBox( + width: 4, + ), + Icon( + Icons.arrow_forward, + color: enabledTextColor, + size: 16, + ) + ], + ), + ), + ), + ], + ) + : SizedBox()) + ], + ), + ], + ), ), ), ), diff --git a/example/lib/preview/preview_store.dart b/example/lib/preview/preview_store.dart index d5904dde1..1983841f1 100644 --- a/example/lib/preview/preview_store.dart +++ b/example/lib/preview/preview_store.dart @@ -8,7 +8,7 @@ import 'package:hmssdk_flutter_example/service/room_service.dart'; class PreviewStore extends ChangeNotifier implements HMSPreviewListener, HMSLogListener { - late HMSSDKInteractor hmsSDKInteractor; + HMSSDKInteractor? hmsSDKInteractor; PreviewStore() { hmsSDKInteractor = HMSSDKInteractor( @@ -55,6 +55,11 @@ class PreviewStore extends ChangeNotifier isHLSLink = true; notifyListeners(); } + if (!each.role.publishSettings!.allowed.contains("video")) { + isVideoOn = false; + notifyListeners(); + } + break; } } @@ -82,8 +87,8 @@ class PreviewStore extends ChangeNotifier userName: user, endPoint: token[1] == "true" ? "" : "https://qa-init.100ms.live/init", captureNetworkQualityInPreview: true); - hmsSDKInteractor.addPreviewListener(this); - hmsSDKInteractor.preview(config: config); + hmsSDKInteractor!.addPreviewListener(this); + hmsSDKInteractor!.preview(config: config); meetingUrl = meetingLink; return ""; } @@ -141,35 +146,35 @@ class PreviewStore extends ChangeNotifier } void removePreviewListener() { - hmsSDKInteractor.removePreviewListener(this); + hmsSDKInteractor!.removePreviewListener(this); } void stopCapturing() { - hmsSDKInteractor.stopCapturing(); + hmsSDKInteractor!.stopCapturing(); } void startCapturing() { - hmsSDKInteractor.startCapturing(); + hmsSDKInteractor!.startCapturing(); } void switchVideo({bool isOn = false}) { - hmsSDKInteractor.switchVideo(isOn: isOn); + hmsSDKInteractor!.switchVideo(isOn: isOn); isVideoOn = !isVideoOn; notifyListeners(); } void switchAudio({bool isOn = false}) { - hmsSDKInteractor.switchAudio(isOn: isOn); + hmsSDKInteractor!.switchAudio(isOn: isOn); isAudioOn = !isAudioOn; notifyListeners(); } void addLogsListener(HMSLogListener hmsLogListener) { - hmsSDKInteractor.addLogsListener(hmsLogListener); + hmsSDKInteractor!.addLogsListener(hmsLogListener); } void removeLogsListener(HMSLogListener hmsLogListener) { - hmsSDKInteractor.removeLogsListener(hmsLogListener); + hmsSDKInteractor!.removeLogsListener(hmsLogListener); } @override @@ -180,4 +185,15 @@ class PreviewStore extends ChangeNotifier void updateError(HMSException error) { this.error = error; } + + void destroy() { + hmsSDKInteractor!.removePreviewListener(this); + hmsSDKInteractor!.destroy(); + hmsSDKInteractor = null; + } + + void leave() { + hmsSDKInteractor!.leave(); + destroy(); + } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 29cebd2bc..8f35328b1 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -50,6 +50,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5" clock: dependency: transitive description: @@ -120,6 +134,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.2" + file_picker: + dependency: "direct main" + description: + name: file_picker + url: "https://pub.dartlang.org" + source: hosted + version: "4.6.1" firebase: dependency: transitive description: @@ -133,28 +154,28 @@ packages: name: firebase_analytics url: "https://pub.dartlang.org" source: hosted - version: "9.3.0" + version: "9.3.1" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.3.0" + version: "3.3.1" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.4.2+1" firebase_core: dependency: "direct main" description: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "1.20.0" + version: "1.20.1" firebase_core_platform_interface: dependency: transitive description: @@ -175,49 +196,49 @@ packages: name: firebase_crashlytics url: "https://pub.dartlang.org" source: hosted - version: "2.8.6" + version: "2.8.7" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.2.12" + version: "3.2.13" firebase_dynamic_links: dependency: "direct main" description: name: firebase_dynamic_links url: "https://pub.dartlang.org" source: hosted - version: "4.3.3" + version: "4.3.4" firebase_dynamic_links_platform_interface: dependency: transitive description: name: firebase_dynamic_links_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "0.2.3+8" + version: "0.2.3+9" firebase_performance: dependency: "direct main" description: name: firebase_performance url: "https://pub.dartlang.org" source: hosted - version: "0.8.2+1" + version: "0.8.2+2" firebase_performance_platform_interface: dependency: transitive description: name: firebase_performance_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+12" + version: "0.1.1+13" firebase_performance_web: dependency: transitive description: name: firebase_performance_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+1" + version: "0.1.1+2" flutter: dependency: "direct main" description: flutter @@ -229,7 +250,14 @@ packages: name: flutter_launcher_icons url: "https://pub.dartlang.org" source: hosted - version: "0.9.3" + version: "0.10.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" flutter_staggered_grid_view: dependency: "direct main" description: @@ -281,7 +309,7 @@ packages: path: ".." relative: true source: path - version: "0.7.4" + version: "0.7.5" html: dependency: transitive description: @@ -295,7 +323,7 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.13.4" + version: "0.13.5" http_parser: dependency: transitive description: @@ -324,6 +352,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.3" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.6.0" logger: dependency: "direct main" description: @@ -414,14 +449,14 @@ packages: name: path_drawing url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" path_parsing: dependency: transitive description: name: path_parsing url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" path_provider: dependency: "direct main" description: @@ -435,7 +470,7 @@ packages: name: path_provider_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.17" + version: "2.0.19" path_provider_ios: dependency: transitive description: @@ -706,14 +741,14 @@ packages: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "2.4.5" + version: "2.4.6" video_player_android: dependency: transitive description: name: video_player_android url: "https://pub.dartlang.org" source: hosted - version: "2.3.8" + version: "2.3.9" video_player_avfoundation: dependency: transitive description: @@ -727,7 +762,7 @@ packages: name: video_player_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "5.1.3" + version: "5.1.4" video_player_web: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index fa8d29659..afb9afe86 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -43,6 +43,7 @@ dependencies: qr_code_scanner: shared_preferences: uni_links: + file_picker: dev_dependencies: flutter_test: diff --git a/ios/Classes/Models/HMSAudioFilePlayerNodeExtension.swift b/ios/Classes/Models/HMSAudioFilePlayerNodeExtension.swift new file mode 100644 index 000000000..b02531525 --- /dev/null +++ b/ios/Classes/Models/HMSAudioFilePlayerNodeExtension.swift @@ -0,0 +1,61 @@ +// +// HMSAudioFilePlayerNode.swift +// hmssdk_flutter +// +// Created by Govind on 09/08/22. +// + +import Foundation +import HMSSDK + +class HMSAudioFilePlayerNodeExtension { + + static func play(_ call: [AnyHashable: Any], _ playerNode:HMSAudioFilePlayerNode, _ result: @escaping FlutterResult){ + do{ + try playerNode.play(fileUrl: URL(string:call["file_url"] as! String)!, loops: call["loops"] as? Bool ?? false, interrupts: call["interrupts"] as? Bool ?? false) + }catch{ + let error = HMSCommonAction.getError(message: error.localizedDescription, params: ["function": #function]) + result(HMSErrorExtension.toDictionary(error)) + } + } + + static func pause( _ playerNode:HMSAudioFilePlayerNode){ + playerNode.pause() + } + + static func resume( _ playerNode:HMSAudioFilePlayerNode, _ result: @escaping FlutterResult){ + do{ + try playerNode.resume() + }catch{ + let error = HMSCommonAction.getError(message: error.localizedDescription, params: ["function": #function]) + result(HMSErrorExtension.toDictionary(error)) + } + } + + static func stop( _ playerNode:HMSAudioFilePlayerNode){ + playerNode.stop() + } + + static func setVolume(_ call: [AnyHashable: Any],_ playerNode:HMSAudioFilePlayerNode){ + playerNode.volume = Float(call["volume"] as! Double) + } + + static func isPlaying(_ playerNode:HMSAudioFilePlayerNode,_ result: FlutterResult) { + var dict = [String: Any]() + dict["is_playing"] = playerNode.isPlaying + result(dict) + } + + static func currentDuration(_ playerNode:HMSAudioFilePlayerNode,_ result: FlutterResult) { + var dict = [String: Any]() + dict["current_duration"] = playerNode.currentTime + result(dict) + } + + static func duration(_ playerNode:HMSAudioFilePlayerNode,_ result: FlutterResult) { + var dict = [String: Any]() + dict["duration"] = playerNode.duration + result(dict) + } + +} diff --git a/ios/Classes/Models/HMSMicNodeExtension.swift b/ios/Classes/Models/HMSMicNodeExtension.swift new file mode 100644 index 000000000..ccd12b139 --- /dev/null +++ b/ios/Classes/Models/HMSMicNodeExtension.swift @@ -0,0 +1,14 @@ +// +// HMSMicNodeExtension.swift +// hmssdk_flutter +// +// Created by Govind on 10/08/22. +// + +import Foundation +import HMSSDK +class HMSMicNodeExtension { + static func setVolume(_ call: [AnyHashable: Any],_ playerNode:HMSMicNode){ + playerNode.volume = call["volume"] as! Float + } +} diff --git a/ios/Classes/Models/HMSTrackSettingsExtension.swift b/ios/Classes/Models/HMSTrackSettingsExtension.swift new file mode 100644 index 000000000..bac6823ff --- /dev/null +++ b/ios/Classes/Models/HMSTrackSettingsExtension.swift @@ -0,0 +1,106 @@ +// +// HMSTrackSettingsExtension.swift +// hmssdk_flutter +// +// Created by Govind on 04/08/22. +// + +import Foundation +import HMSSDK + +class HMSTrackSettingsExtension { + static func toDictionary(_ hmssdk: HMSSDK,_ audioMixerSourceMap: [String:HMSAudioNode]?) -> [String: Any] { + + let hmsTrackSettings = hmssdk.trackSettings + var dict = [String: Any]() + + if let hmsVideoSettings = hmsTrackSettings.video { + dict["video_track_setting"] = HMSTrackSettingsExtension.toDictionary(hmsVideoSettings) + } + if let hmsAudioSettings = hmsTrackSettings.audio { + dict["audio_track_setting"] = HMSTrackSettingsExtension.toDictionary(hmsAudioSettings,audioMixerSourceMap!) + } + + return dict + } + + static func toDictionary(_ hmsVideoTrackSettings: HMSVideoTrackSettings) -> [String: Any] { + + var dict = [String: Any]() + if(hmsVideoTrackSettings.codec == HMSCodec.H264){ + dict["video_codec"] = "h264" + }else{ + dict["video_codec"] = "vp8" + } + if(hmsVideoTrackSettings.cameraFacing == .front){ + dict["camera_facing"] = "front" + }else{ + dict["camera_facing"] = "back" + } + dict["max_bitrate"] = hmsVideoTrackSettings.maxBitrate + dict["max_frame_rate"] = hmsVideoTrackSettings.maxFrameRate + dict["resolution"] = HMSVideoResolutionExtension.toDictionary(hmsVideoTrackSettings.resolution) + dict["simulcast_settings"] = hmsVideoTrackSettings.simulcastSettings + dict["track_description"] = hmsVideoTrackSettings.trackDescription + dict["video_plugins"] = hmsVideoTrackSettings.videoPlugins + return dict + } + + static func toDictionary(_ hmsAudioTrackSettings: HMSAudioTrackSettings,_ audioMixerSourceMap: [String:HMSAudioNode]) -> [String: Any] { + + var dict = [String: Any]() + + dict["track_description"] = hmsAudioTrackSettings.trackDescription + dict["max_bitrate"] = hmsAudioTrackSettings.maxBitrate + dict["audio_source"] = audioMixerSourceMap.keys.map{$0} + return dict + } + + static func setTrackSetting(_ settingsDict: [AnyHashable: Any],_ audioMixerSourceMap: [String:HMSAudioNode], _ result: @escaping FlutterResult) -> HMSTrackSettings? { + + var audioSettings: HMSAudioTrackSettings? + if let audioSettingsDict = settingsDict["audio_track_setting"] as? [AnyHashable: Any] { + if #available(iOS 13.0, *) { + do { + let audioMixerSource = try HMSAudioMixerSource(nodes: audioMixerSourceMap.values.map{$0}) + if let bitrate = audioSettingsDict["bit_rate"] as? Int, let desc = audioSettingsDict["track_description"] as? String { + audioSettings = HMSAudioTrackSettings(maxBitrate: bitrate, trackDescription: desc,audioSource: audioMixerSource) + } + } catch { + let error = HMSCommonAction.getError(message: error.localizedDescription, params: ["function": #function]) + result(HMSErrorExtension.toDictionary(error)) + } + } else { + if let bitrate = audioSettingsDict["bit_rate"] as? Int, let desc = audioSettingsDict["track_description"] as? String { + audioSettings = HMSAudioTrackSettings(maxBitrate: bitrate, trackDescription: desc) + } + } + } + + var videoSettings: HMSVideoTrackSettings? + if let videoSettingsDict = settingsDict["video_track_setting"] as? [AnyHashable: Any] { + if let codec = videoSettingsDict["video_codec"] as? String, + let bitrate = videoSettingsDict["max_bit_rate"] as? Int, + let framerate = videoSettingsDict["max_frame_rate"] as? Int, + let desc = videoSettingsDict["track_description"] as? String { + + videoSettings = HMSVideoTrackSettings(codec: getCodec(from: codec), + resolution: .init(width: 320, height: 180), + maxBitrate: bitrate, + maxFrameRate: framerate, + cameraFacing: .front, + trackDescription: desc, + videoPlugins: nil) + } + } + + return HMSTrackSettings(videoSettings: videoSettings, audioSettings: audioSettings) + } + + static private func getCodec(from string: String) -> HMSCodec { + if string.lowercased().contains("h264") { + return HMSCodec.H264 + } + return HMSCodec.VP8 + } +} diff --git a/ios/Classes/SwiftHmssdkFlutterPlugin.swift b/ios/Classes/SwiftHmssdkFlutterPlugin.swift index 2898ce623..17105fb9a 100644 --- a/ios/Classes/SwiftHmssdkFlutterPlugin.swift +++ b/ios/Classes/SwiftHmssdkFlutterPlugin.swift @@ -109,7 +109,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene // MARK: Room Actions - case "build", "preview", "join", "leave": + case "build", "preview", "join", "leave","destroy": buildActions(call, result) // MARK: Room Actions @@ -166,6 +166,11 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene case "start_screen_share", "stop_screen_share", "is_screen_share_active": screenShareActions(call, result) + case "get_track_settings","set_track_settings": + trackSettingsAction(call,result) + break + case "play_audio_share","stop_audio_share","pause_audio_share","resume_audio_share","set_audio_share_volume","audio_share_playing","audio_share_current_time","audio_share_duration": + audioShareAction(call, result) default: result(FlutterMethodNotImplemented) @@ -187,6 +192,9 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene case "leave": leave(result) + case "destroy": + destroy(result) + default: result(FlutterMethodNotImplemented) } @@ -266,6 +274,85 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene } } + var audioMixerSourceMap = [String: HMSAudioNode]() + private func trackSettingsAction(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + switch call.method { + case "get_track_settings": + result(HMSTrackSettingsExtension.toDictionary(hmsSDK!,audioMixerSourceMap)) + break + case "set_track_settings": + let arguments = call.arguments as! [AnyHashable: Any] + let settingsDict = arguments["hms_track_setting"] as! [AnyHashable: Any] + let audioTrackSetting = settingsDict["audio_track_setting"] as! [AnyHashable: Any] + + if let playerNode = audioTrackSetting["audio_source"] as? [String] { + for node in playerNode { + if (audioMixerSourceMap[node] == nil) { + if(node=="mic_node"){ + audioMixerSourceMap["mic_node"] = HMSMicNode() + }else if(node == "screen_broadcast_audio_receiver_node"){ + do { + audioMixerSourceMap["screen_broadcast_audio_receiver_node"] = try hmsSDK!.screenBroadcastAudioReceiverNode() + }catch { + let error = HMSCommonAction.getError(message: error.localizedDescription, params: ["function": #function]) + result(HMSErrorExtension.toDictionary(error)) + } + } + else{ + audioMixerSourceMap[node] = HMSAudioFilePlayerNode() + } + } + } + } + let trackSetting = HMSTrackSettingsExtension.setTrackSetting(settingsDict,audioMixerSourceMap,result) + if let settings = trackSetting { + hmsSDK?.trackSettings = settings + } + result(nil) + break + default: + result(FlutterMethodNotImplemented) + } + } + + private func audioShareAction(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + let arguments = call.arguments as! [AnyHashable: Any] + if let audioNode = audioMixerSourceMap[arguments["name"] as! String] { + switch call.method { + case "play_audio_share": + HMSAudioFilePlayerNodeExtension.play(arguments,audioNode as! HMSAudioFilePlayerNode,result) + break + case "stop_audio_share": + HMSAudioFilePlayerNodeExtension.stop(audioNode as! HMSAudioFilePlayerNode) + break + case "pause_audio_share": + HMSAudioFilePlayerNodeExtension.pause(audioNode as! HMSAudioFilePlayerNode) + break + case "resume_audio_share": + HMSAudioFilePlayerNodeExtension.resume(audioNode as! HMSAudioFilePlayerNode,result) + break + case "set_audio_share_volume": + if(arguments["name"] as! String != "mic_node"){ + HMSAudioFilePlayerNodeExtension.setVolume(arguments,audioNode as! HMSAudioFilePlayerNode) + }else { + HMSMicNodeExtension.setVolume(arguments,audioNode as! HMSMicNode) + } + break + case "audio_share_playing": + HMSAudioFilePlayerNodeExtension.isPlaying(audioNode as! HMSAudioFilePlayerNode,result) + break + case "audio_share_current_time": + HMSAudioFilePlayerNodeExtension.currentDuration(audioNode as! HMSAudioFilePlayerNode,result) + break + case "audio_share_duration": + HMSAudioFilePlayerNodeExtension.duration(audioNode as! HMSAudioFilePlayerNode,result) + break + default: + result(FlutterMethodNotImplemented) + } + } + } + // MARK: - Screen Share var isScreenShareOn = false { didSet { @@ -282,7 +369,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene switch call.method { case "start_screen_share", "stop_screen_share": guard let preferredExtension = preferredExtension else { - let error = getError(message: "Could not start Screen share, preferredExtension not passed in Build Method", params: ["function": #function]) + let error = HMSCommonAction.getError(message: "Could not start Screen share, preferredExtension not passed in Build Method", params: ["function": #function]) result(HMSErrorExtension.toDictionary(error)) screenShareActionResult = nil return @@ -305,69 +392,65 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene case "is_screen_share_active": result(isScreenShareOn) default: - print("Not Valid") + result(FlutterMethodNotImplemented) } } // MARK: - Room Actions - private func build(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let arguments = call.arguments as! [AnyHashable: Any] - var trackSettings: HMSTrackSettings? - if let settingsDict = arguments["hms_track_setting"] as? [AnyHashable: Any] { - - var audioSettings: HMSAudioTrackSettings? - if let audioSettingsDict = settingsDict["audio_track_setting"] as? [AnyHashable: Any] { - if let bitrate = audioSettingsDict["bit_rate"] as? Int, let desc = audioSettingsDict["track_description"] as? String { - audioSettings = HMSAudioTrackSettings(maxBitrate: bitrate, trackDescription: desc) - } - } - - var videoSettings: HMSVideoTrackSettings? - if let videoSettingsDict = settingsDict["video_track_setting"] as? [AnyHashable: Any] { - if let codec = videoSettingsDict["video_codec"] as? String, - let bitrate = videoSettingsDict["max_bit_rate"] as? Int, - let framerate = videoSettingsDict["max_frame_rate"] as? Int, - let desc = videoSettingsDict["track_description"] as? String { - - videoSettings = HMSVideoTrackSettings(codec: getCodec(from: codec), - resolution: .init(width: 320, height: 180), - maxBitrate: bitrate, - maxFrameRate: framerate, - cameraFacing: .front, - trackDescription: desc, - videoPlugins: nil) - } - } - - trackSettings = HMSTrackSettings(videoSettings: videoSettings, audioSettings: audioSettings) - } - if let prefExtension = arguments["preferred_extension"] as? String { preferredExtension = prefExtension } - var setLogger = false if let level = arguments["log_level"] as? String { logLevel = getLogLevel(from: level) setLogger = true } - - hmsSDK = HMSSDK.build { sdk in + audioMixerSourceMap = [:] + hmsSDK = HMSSDK.build { [self] sdk in if let appGroup = arguments["app_group"] as? String { sdk.appGroup = appGroup } - if let settings = trackSettings { - sdk.trackSettings = settings - } if setLogger { sdk.logger = self } + var trackSettings: HMSTrackSettings? + if let settingsDict = arguments["hms_track_setting"] as? [AnyHashable: Any] { + if let audioTrackSetting = settingsDict["audio_track_setting"] as? [AnyHashable: Any]{ + if let playerNode = audioTrackSetting["audio_source"] as? [String] { + for node in playerNode { + if (self.audioMixerSourceMap[node] == nil) { + if(node=="mic_node"){ + self.audioMixerSourceMap["mic_node"] = HMSMicNode() + }else if(node == "screen_broadcast_audio_receiver_node"){ + do { + self.audioMixerSourceMap["screen_broadcast_audio_receiver_node"] = try sdk.screenBroadcastAudioReceiverNode() + }catch { + let error = HMSCommonAction.getError(message: error.localizedDescription, params: ["function": #function]) + result(HMSErrorExtension.toDictionary(error)) + } + } + else{ + self.audioMixerSourceMap[node] = HMSAudioFilePlayerNode() + } + } + } + } + + trackSettings = HMSTrackSettingsExtension.setTrackSetting(settingsDict,audioMixerSourceMap,result) + } + } + + if let settings = trackSettings { + sdk.trackSettings = settings + } + result(true) } } @@ -377,7 +460,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene let arguments = call.arguments as! [AnyHashable: Any] guard let config = getConfig(from: arguments) else { - let error = getError(message: "Could not join room, invalid parameters passed", params: ["function": #function, "arguments": arguments]) + let error = HMSCommonAction.getError(message: "Could not join room, invalid parameters passed", params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) return } @@ -407,7 +490,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene let arguments = call.arguments as! [AnyHashable: Any] guard let config = getConfig(from: arguments) else { - let error = getError(message: "Could not join room, invalid parameters passed", params: ["function": #function, "arguments": arguments]) + let error = HMSCommonAction.getError(message: "Could not join room, invalid parameters passed", params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) return } @@ -427,6 +510,10 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene } } + private func destroy(_ result: @escaping FlutterResult){ + hmsSDK = nil + } + // MARK: - Role based Actions private func getRoles(_ call: FlutterMethodCall, _ result: FlutterResult) { @@ -447,7 +534,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene let roleString = arguments["role_name"] as? String, let role = HMSCommonAction.getRole(by: roleString,hmsSDK: hmsSDK) else { - let error = getError(message: "Invalid parameters for change role", params: ["function": #function, "arguments": arguments]) + let error = HMSCommonAction.getError(message: "Invalid parameters for change role", params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) return } @@ -498,7 +585,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene guard let peerID = arguments["peer_id"] as? String, let peer = HMSCommonAction.getPeer(by: peerID,hmsSDK: hmsSDK) else { - let error = getError(message: "Peer not found", params: ["function": #function, "arguments": arguments]) + let error = HMSCommonAction.getError(message: "Peer not found", params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) return } @@ -520,7 +607,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene guard let trackID = arguments["track_id"] as? String, let track = HMSUtilities.getTrack(for: trackID, in: hmsSDK!.room!) else { - let error = getError(message: "Could not find track to change track", + let error = HMSCommonAction.getError(message: "Could not find track to change track", description: "Could not find track from trackID", params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) @@ -543,7 +630,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene let arguments = call.arguments as! [AnyHashable: Any] guard let mute = arguments["mute"] as? Bool else { - let error = getError(message: "Mute status to be set not found", + let error = HMSCommonAction.getError(message: "Mute status to be set not found", params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) return @@ -577,7 +664,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene let arguments = call.arguments as! [AnyHashable: Any] guard let metadata = arguments["metadata"] as? String else { - let error = getError(message: "No metadata found in \(#function)", + let error = HMSCommonAction.getError(message: "No metadata found in \(#function)", description: "Metadata is nil", params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) @@ -602,7 +689,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene let arguments = call.arguments as![AnyHashable: Any] guard let name = arguments["name"] as? String else { - let error = getError(message: "No name found in \(#function)", + let error = HMSCommonAction.getError(message: "No name found in \(#function)", description: "Name is nil", params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) @@ -936,15 +1023,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene captureNetworkQualityInPreview: captureNetworkQualityInPreview) } - private func getError(message: String, description: String? = nil, params: [String: Any]) -> HMSError { - HMSError(id: "NONE", - code: .genericErrorJsonParsingFailed, - message: message, - info: description, - params: params) - } - - + private func kind(from string: String) -> HMSTrackKind { switch string { case "KHmsTrackAudio": diff --git a/ios/hmssdk_flutter.podspec b/ios/hmssdk_flutter.podspec index 345655d00..bd5b95b8a 100644 --- a/ios/hmssdk_flutter.podspec +++ b/ios/hmssdk_flutter.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'hmssdk_flutter' - s.version = '0.7.4' + s.version = '0.7.5' s.summary = 'The Flutter plugin for 100ms SDK' s.description = <<-DESC A Flutter Project for 100ms SDK. @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source_files = 'Classes/**/*' s.dependency 'Flutter' s.dependency 'HMSSDK', '0.3.3' - s.dependency 'HMSBroadcastExtensionSDK', '0.0.1' + s.dependency 'HMSBroadcastExtensionSDK', '0.0.2' s.platform = :ios, '12.0' s.ios.deployment_target = '12.0' # Flutter.framework does not contain a i386 slice. diff --git a/lib/hmssdk_flutter.dart b/lib/hmssdk_flutter.dart index abc094d52..36b45171a 100644 --- a/lib/hmssdk_flutter.dart +++ b/lib/hmssdk_flutter.dart @@ -75,3 +75,7 @@ export 'src/model/hms_stats_listener.dart'; export 'src/model/hms_hls_recording_config.dart'; export 'src/enum/hms_audio_device.dart'; export 'src/enum/hms_audio_mixing_mode.dart'; +export 'src/model/hms_audio_file_player_node.dart'; +export 'src/model/hms_mic_node.dart'; +export 'src/model/hms_audio_mixer_source.dart'; +export 'src/model/hms_screen_broadcast_audio_receiver_node.dart'; diff --git a/lib/src/common/platform_methods.dart b/lib/src/common/platform_methods.dart index e91427ab0..cdda2cdbf 100644 --- a/lib/src/common/platform_methods.dart +++ b/lib/src/common/platform_methods.dart @@ -138,7 +138,17 @@ enum PlatformMethod { switchAudioOutput, startAudioShare, stopAudioShare, - setAudioMixingMode + setAudioMixingMode, + pauseAudioShare, + playAudioShare, + resumeAudioShare, + setAudioShareVolume, + audioSharePlaying, + audioShareCurrentTime, + audioShareDuration, + getTrackSettings, + setTrackSettings, + destroy } extension PlatformMethodValues on PlatformMethod { @@ -315,6 +325,26 @@ extension PlatformMethodValues on PlatformMethod { return "stop_audio_share"; case PlatformMethod.setAudioMixingMode: return "set_audio_mixing_mode"; + case PlatformMethod.pauseAudioShare: + return "pause_audio_share"; + case PlatformMethod.playAudioShare: + return "play_audio_share"; + case PlatformMethod.resumeAudioShare: + return "resume_audio_share"; + case PlatformMethod.setAudioShareVolume: + return "set_audio_share_volume"; + case PlatformMethod.audioSharePlaying: + return "audio_share_playing"; + case PlatformMethod.audioShareCurrentTime: + return "audio_share_current_time"; + case PlatformMethod.audioShareDuration: + return "audio_share_duration"; + case PlatformMethod.getTrackSettings: + return "get_track_settings"; + case PlatformMethod.setTrackSettings: + return "set_track_settings"; + case PlatformMethod.destroy: + return "destroy"; default: return 'unknown'; } @@ -497,6 +527,26 @@ extension PlatformMethodValues on PlatformMethod { return PlatformMethod.stopAudioShare; case "set_audio_mixing_mode": return PlatformMethod.setAudioMixingMode; + case "pause_audio_share": + return PlatformMethod.pauseAudioShare; + case "play_audio_share": + return PlatformMethod.playAudioShare; + case "resume_audio_share": + return PlatformMethod.resumeAudioShare; + case "set_audio_share_volume": + return PlatformMethod.setAudioShareVolume; + case "audio_share_playing": + return PlatformMethod.audioSharePlaying; + case "audio_share_current_time": + return PlatformMethod.audioShareCurrentTime; + case "audio_share_duration": + return PlatformMethod.audioShareDuration; + case "get_track_settings": + return PlatformMethod.getTrackSettings; + case "set_track_settings": + return PlatformMethod.setTrackSettings; + case "destroy": + return PlatformMethod.destroy; default: return PlatformMethod.unknown; } diff --git a/lib/src/enum/hms_action_result_listener_method.dart b/lib/src/enum/hms_action_result_listener_method.dart index a17242c85..3b2013d9c 100644 --- a/lib/src/enum/hms_action_result_listener_method.dart +++ b/lib/src/enum/hms_action_result_listener_method.dart @@ -19,5 +19,6 @@ enum HMSActionResultListenerMethod { stopScreenShare, startAudioShare, stopAudioShare, + setTrackSettings, unknown } diff --git a/lib/src/hmssdk.dart b/lib/src/hmssdk.dart index e12cbae85..c5a16f8a0 100644 --- a/lib/src/hmssdk.dart +++ b/lib/src/hmssdk.dart @@ -44,7 +44,7 @@ class HMSSDK { } /// Join the room with configuration options passed as a [HMSConfig] object - dynamic join({ + Future join({ required HMSConfig config, }) async { if (previewState) { @@ -79,7 +79,7 @@ class HMSSDK { /// Call this method to leave the room /// [HMSActionResultListener] callback will be used by SDK to inform application if there was a success or failure when the leave was executed - void leave({HMSActionResultListener? hmsActionResultListener}) async { + Future leave({HMSActionResultListener? hmsActionResultListener}) async { var result = await PlatformService.invokeMethod(PlatformMethod.leave); if (hmsActionResultListener != null) { if (result == null) { @@ -220,7 +220,7 @@ class HMSSDK { /// Sends a message to everyone in the call. [message] contains the content of the message. /// The [hmsActionResultListener] informs about whether the message was successfully sent, or the kind of error if not. - void sendBroadcastMessage( + Future sendBroadcastMessage( {required String message, String? type, HMSActionResultListener? hmsActionResultListener}) async { @@ -246,7 +246,7 @@ class HMSSDK { /// Sends a message to all the peers of a role defined in [roleName]. All peers currently with that role will receive the message. /// [message] contains the content of the message. /// The [hmsActionResultListener] informs about whether the message was successfully sent, or the kind of error if not. - void sendGroupMessage( + Future sendGroupMessage( {required String message, required List hmsRolesTo, String? type, @@ -276,7 +276,7 @@ class HMSSDK { /// Sends a message a particular peer only. The one specified in [peer]. /// [message] contains the content of the message. /// The [hmsActionResultListener] informs about whether the message was successfully sent, or the kind of error if not. - void sendDirectMessage( + Future sendDirectMessage( {required String message, required HMSPeer peerTo, String? type, @@ -324,7 +324,7 @@ class HMSSDK { /// The [roleName] new role the HMSPeer would have if they accept or are forced to change to. /// Set [forceChange] to false if the peer should be requested to accept the new role (they can choose to deny). Set [forceChange] to true if their role should be changed without asking them. /// [hmsActionResultListener] - Listener that will return HMSActionResultListener.onSuccess if the role change request is successful else will call [HMSActionResultListener.onException] with the error received from server - void changeRole( + Future changeRole( {required HMSPeer forPeer, required HMSRole toRole, bool force = false, @@ -362,7 +362,7 @@ class HMSSDK { /// When a peer is requested to change their role (see [changeRole]) to accept the new role this has to be called. Once this method is called, the peer's role will be changed to the requested one. The HMSRoleChangeRequest that the SDK had sent to this peer (in HMSUpdateListener.onRoleChangeRequest) to inform them that a role change was requested. /// [hmsActionResultListener] - Listener that will return HMSActionResultListener.onSuccess if the role change request is successful else will call HMSActionResultListener.onException with the error received from server - void acceptChangeRole( + Future acceptChangeRole( {required HMSRoleChangeRequest hmsRoleChangeRequest, HMSActionResultListener? hmsActionResultListener}) async { var result = @@ -381,7 +381,7 @@ class HMSSDK { /// To change the mute status of a single remote peer's track /// Set [mute] to true if the track needs to be muted, false otherwise. /// [hmsActionResultListener] - the callback that would be called by SDK in case of a success or failure. - void changeTrackState( + Future changeTrackState( {required HMSTrack forRemoteTrack, required bool mute, HMSActionResultListener? hmsActionResultListener}) async { @@ -416,7 +416,7 @@ class HMSSDK { /// [source] is the HMSTrackSource which should be affected. If this and type are specified, it is considered an AND operation. If not specified, all track types are affected. /// [roles] is a list of roles, may have a single item in a list, whose tracks should be affected. If not specified, all roles are affected. /// [hmsActionResultListener] - the callback that would be called by SDK in case of a success or failure. - void changeTrackStateForRole( + Future changeTrackStateForRole( {required bool mute, required HMSTrackKind? kind, required String? source, @@ -463,7 +463,7 @@ class HMSSDK { /// Removes the given peer from the room /// A [reason] string will be shown to them. /// [hmsActionResultListener] is the callback that would be called by SDK in case of a success or failure - void removePeer( + Future removePeer( {required HMSPeer peer, required String reason, HMSActionResultListener? hmsActionResultListener}) async { @@ -490,7 +490,7 @@ class HMSSDK { /// [lock] bool is whether rejoining the room should be disabled for the foreseeable future. /// [hmsActionResultListener] is the callback that would be called by SDK in case of a success or failure - void endRoom( + Future endRoom( {required bool lock, required String reason, HMSActionResultListener? hmsActionResultListener}) async { @@ -515,7 +515,7 @@ class HMSSDK { /// Starts rtmp streaming or recording on the parameters described in config. /// [config] is the HMSRecordingConfig which defines streaming/recording parameters for this start request. /// [hmsActionResultListener] is the callback that would be called by SDK in case of a success or failure. - void startRtmpOrRecording( + Future startRtmpOrRecording( {required HMSRecordingConfig hmsRecordingConfig, HMSActionResultListener? hmsActionResultListener}) async { var arguments = hmsRecordingConfig.getJson(); @@ -539,7 +539,7 @@ class HMSSDK { /// Stops a previously started rtmp recording or stream. See startRtmpOrRecording for starting. /// [hmsActionResultListener] is the callback that would be called by SDK in case of a success or failure. - void stopRtmpAndRecording( + Future stopRtmpAndRecording( {HMSActionResultListener? hmsActionResultListener}) async { var result = await PlatformService.invokeMethod(PlatformMethod.stopRtmpAndRecording); @@ -558,7 +558,7 @@ class HMSSDK { /// Starts HLS streaming for the [meetingUrl] room. /// You can set a custom [metadata] for the HLS Stream /// [hmsActionResultListener] is callback whose [HMSActionResultListener.onSuccess] will be called when the the action completes successfully. - void startHlsStreaming( + Future startHlsStreaming( {HMSHLSConfig? hmshlsConfig, HMSActionResultListener? hmsActionResultListener}) async { var result = await PlatformService.invokeMethod( @@ -577,7 +577,7 @@ class HMSSDK { /// Stops ongoing HLS streaming in the room /// [hmsActionResultListener] is callback whose [HMSActionResultListener.onSuccess] will be called when the the action completes successfully. - void stopHlsStreaming( + Future stopHlsStreaming( {HMSHLSConfig? hmshlsConfig, HMSActionResultListener? hmsActionResultListener}) async { var result = await PlatformService.invokeMethod( @@ -597,7 +597,7 @@ class HMSSDK { /// Change the metadata that appears inside [HMSPeer.metadata]. This change is persistent and all peers joining after the change will still see these values. /// [metadata] is the string data to be set now /// [hmsActionResultListener] is callback whose [HMSActionResultListener.onSuccess] will be called when the the action completes successfully. - void changeMetadata( + Future changeMetadata( {required String metadata, HMSActionResultListener? hmsActionResultListener}) async { var arguments = {"metadata": metadata}; @@ -622,7 +622,7 @@ class HMSSDK { /// Change the name that appears inside [HMSPeer.name] This change is persistent and all peers joining after the change will still see these values. /// [name] is the string which is to be set as the [HMSPeer.name] /// [hmsActionResultListener] is the callback whose [HMSActionResultListener.onSuccess] will be called when the the action completes successfully. - void changeName( + Future changeName( {required String name, HMSActionResultListener? hmsActionResultListener}) async { var arguments = {"name": name}; @@ -646,7 +646,7 @@ class HMSSDK { /// [hmsActionResultListener] is a callback instance on which [HMSActionResultListener.onSuccess] /// and [HMSActionResultListener.onException] will be called /// [preferredExtension] is only used for screen share (broadcast screen) in iOS. - void startScreenShare( + Future startScreenShare( {HMSActionResultListener? hmsActionResultListener}) async { HMSLocalPeer? localPeer = await getLocalPeer(); if (localPeer?.role.publishSettings?.allowed.contains("screen") ?? false) { @@ -766,7 +766,7 @@ class HMSSDK { arguments: {"audio_device_name": audioDevice.name}); } - void startAudioShare( + Future startAudioShare( {HMSActionResultListener? hmsActionResultListener, HMSAudioMixingMode audioMixingMode = HMSAudioMixingMode.TALK_AND_MUSIC}) async { @@ -786,7 +786,7 @@ class HMSSDK { } } - void stopAudioShare( + Future stopAudioShare( {HMSActionResultListener? hmsActionResultListener}) async { if (!Platform.isAndroid) return; var result = @@ -809,6 +809,39 @@ class HMSSDK { arguments: {"audio_mixing_mode": audioMixingMode.name}); } + Future getTrackSettings() async { + var result = + await PlatformService.invokeMethod(PlatformMethod.getTrackSettings); + HMSTrackSetting trackSetting = HMSTrackSetting.fromMap(result); + return trackSetting; + } + + void setTrackSettings( + {HMSActionResultListener? hmsActionResultListener, + required HMSTrackSetting hmsTrackSetting}) async { + var result = await PlatformService.invokeMethod( + PlatformMethod.setTrackSettings, + arguments: {"hms_track_setting": hmsTrackSetting.toMap()}); + if (hmsActionResultListener != null) { + if (result == null) { + hmsActionResultListener.onSuccess( + methodType: HMSActionResultListenerMethod.setTrackSettings); + } else { + hmsActionResultListener.onException( + methodType: HMSActionResultListenerMethod.setTrackSettings, + hmsException: HMSException( + message: "Unable to Set track Settings", + action: '', + description: 'Unable to Set track Settings', + isTerminal: false)); + } + } + } + + void destroy() { + PlatformService.invokeMethod(PlatformMethod.destroy); + } + /// To modify local peer's audio & video track settings use the [hmsTrackSetting]. Only required for advanced use-cases. HMSTrackSetting? hmsTrackSetting; diff --git a/lib/src/model/hms_audio_file_player_node.dart b/lib/src/model/hms_audio_file_player_node.dart new file mode 100644 index 000000000..68cc18e09 --- /dev/null +++ b/lib/src/model/hms_audio_file_player_node.dart @@ -0,0 +1,84 @@ +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:hmssdk_flutter/src/model/hms_audio_node.dart'; +import 'package:hmssdk_flutter/src/service/platform_service.dart'; + +class HMSAudioFilePlayerNode extends HMSAudioNode { + final String methodName; + + HMSAudioFilePlayerNode(this.methodName) : super(methodName); + + void setVolume(double volume) async { + await PlatformService.invokeMethod(PlatformMethod.setAudioShareVolume, + arguments: {"name": methodName, "volume": volume}); + } + + Future play( + {required String fileUrl, + bool loop = false, + bool interrupts = true}) async { + var result = await PlatformService.invokeMethod( + PlatformMethod.playAudioShare, + arguments: { + "name": methodName, + "file_url": fileUrl, + "loops": loop, + "interrupts": interrupts + }); + if (result == null) { + return null; + } else { + return HMSException.fromMap(result["error"]); + } + } + + void pause() { + PlatformService.invokeMethod(PlatformMethod.pauseAudioShare, + arguments: {"name": methodName}); + } + + Future resume() async { + var result = await PlatformService.invokeMethod( + PlatformMethod.resumeAudioShare, + arguments: {"name": methodName}); + if (result == null) { + return null; + } else { + return HMSException.fromMap(result["error"]); + } + } + + void stop() { + PlatformService.invokeMethod(PlatformMethod.stopAudioShare, + arguments: {"name": methodName}); + } + + Future isPlaying() async { + var result = await PlatformService.invokeMethod( + PlatformMethod.audioSharePlaying, + arguments: {"name": methodName}); + if (result != null) { + return result["is_playing"]; + } + return false; + } + + Future currentDuration() async { + var result = await PlatformService.invokeMethod( + PlatformMethod.audioShareCurrentTime, + arguments: {"name": methodName}); + if (result != null) { + return result["current_duration"]; + } + return null; + } + + Future duration() async { + var result = await PlatformService.invokeMethod( + PlatformMethod.audioShareDuration, + arguments: {"name": methodName}); + if (result != null) { + return result["duration"]; + } + return null; + } +} diff --git a/lib/src/model/hms_audio_mixer_source.dart b/lib/src/model/hms_audio_mixer_source.dart new file mode 100644 index 000000000..4c4431912 --- /dev/null +++ b/lib/src/model/hms_audio_mixer_source.dart @@ -0,0 +1,14 @@ +import 'package:hmssdk_flutter/src/model/hms_audio_node.dart'; + +class HMSAudioMixerSource { + final List node; + HMSAudioMixerSource({required this.node}); + + List toList() { + List nodeName = []; + for (HMSAudioNode i in node) { + nodeName.add(i.toString()); + } + return nodeName; + } +} diff --git a/lib/src/model/hms_audio_node.dart b/lib/src/model/hms_audio_node.dart new file mode 100644 index 000000000..bba17a96f --- /dev/null +++ b/lib/src/model/hms_audio_node.dart @@ -0,0 +1,8 @@ +class HMSAudioNode { + final String name; + HMSAudioNode(this.name); + @override + String toString() { + return name; + } +} diff --git a/lib/src/model/hms_audio_share.dart b/lib/src/model/hms_audio_share.dart new file mode 100644 index 000000000..81ae2c363 --- /dev/null +++ b/lib/src/model/hms_audio_share.dart @@ -0,0 +1,12 @@ +class HMSAudioShare { + final String url; + final bool loop; + final bool interrupts; + + HMSAudioShare( + {required this.url, this.loop = false, this.interrupts = false}); + + Map toMap() { + return {"url": url, "loop": loop, "interrupts": interrupts}; + } +} diff --git a/lib/src/model/hms_audio_track_setting.dart b/lib/src/model/hms_audio_track_setting.dart index 5171057e9..544262aa3 100644 --- a/lib/src/model/hms_audio_track_setting.dart +++ b/lib/src/model/hms_audio_track_setting.dart @@ -1,27 +1,48 @@ // Project imports: import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:hmssdk_flutter/src/model/hms_audio_node.dart'; class HMSAudioTrackSetting { final int? maxBitrate; final HMSAudioCodec? hmsAudioCodec; final bool? useHardwareAcousticEchoCanceler; final double? volume; + final String? trackDescription; + HMSAudioMixerSource? audioSource; HMSAudioTrackSetting( - {this.maxBitrate, + {this.maxBitrate = 32, this.hmsAudioCodec, this.useHardwareAcousticEchoCanceler, - this.volume}); + this.volume, + this.trackDescription = "This is an audio Track", + this.audioSource}); factory HMSAudioTrackSetting.fromMap(Map map) { + List nodeList = []; + List? node = map["audio_source"] ?? null; + HMSAudioMixerSource? audioMixerSource; + if (node != null) { + for (String i in node) { + if (i == "mic_node") { + nodeList.add(HMSMicNode()); + } else { + nodeList.add(HMSAudioFilePlayerNode(i)); + } + } + audioMixerSource = HMSAudioMixerSource(node: nodeList); + } + HMSAudioMixerSource(node: nodeList); return HMSAudioTrackSetting( volume: map['volume'] ?? null, - maxBitrate: map['bit_rate'] ?? null, + maxBitrate: map['bit_rate'] ?? 32, hmsAudioCodec: map["audio_codec"] == null ? null : HMSAudioCodecValues.getHMSCodecFromName(map['audio_codec']), useHardwareAcousticEchoCanceler: - map['user_hardware_acoustic_echo_canceler'] ?? null); + map['user_hardware_acoustic_echo_canceler'] ?? null, + trackDescription: map['track_description'] ?? "This is an audio Track", + audioSource: audioMixerSource); } Map toMap() { @@ -31,7 +52,9 @@ class HMSAudioTrackSetting { 'audio_codec': hmsAudioCodec != null ? HMSAudioCodecValues.getValueFromHMSAudioCodec(hmsAudioCodec!) : null, - 'user_hardware_acoustic_echo_canceler': useHardwareAcousticEchoCanceler + 'user_hardware_acoustic_echo_canceler': useHardwareAcousticEchoCanceler, + 'track_description': trackDescription, + 'audio_source': audioSource?.toList() }; } } diff --git a/lib/src/model/hms_mic_node.dart b/lib/src/model/hms_mic_node.dart new file mode 100644 index 000000000..8f79280b9 --- /dev/null +++ b/lib/src/model/hms_mic_node.dart @@ -0,0 +1,12 @@ +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:hmssdk_flutter/src/model/hms_audio_node.dart'; +import 'package:hmssdk_flutter/src/service/platform_service.dart'; + +class HMSMicNode extends HMSAudioNode { + HMSMicNode() : super("mic_node"); + + void setVolume(double volume) async { + await PlatformService.invokeMethod(PlatformMethod.pauseAudioShare, + arguments: {"name": "mic_node", "volume": volume}); + } +} diff --git a/lib/src/model/hms_screen_broadcast_audio_receiver_node.dart b/lib/src/model/hms_screen_broadcast_audio_receiver_node.dart new file mode 100644 index 000000000..9768069d9 --- /dev/null +++ b/lib/src/model/hms_screen_broadcast_audio_receiver_node.dart @@ -0,0 +1,6 @@ +import 'package:hmssdk_flutter/src/model/hms_audio_node.dart'; + +class HMSScreenBroadcastAudioReceiverNode extends HMSAudioNode { + HMSScreenBroadcastAudioReceiverNode() + : super("screen_broadcast_audio_receiver_node"); +} diff --git a/lib/src/model/hms_track_setting.dart b/lib/src/model/hms_track_setting.dart index d59f8ef3c..c110eb1be 100644 --- a/lib/src/model/hms_track_setting.dart +++ b/lib/src/model/hms_track_setting.dart @@ -7,7 +7,7 @@ class HMSTrackSetting { HMSTrackSetting({this.audioTrackSetting, this.videoTrackSetting}); - factory HMSTrackSetting.fromMap(Map map) { + factory HMSTrackSetting.fromMap(Map map) { HMSAudioTrackSetting? audioTrackSetting; HMSVideoTrackSetting? videoTrackSetting; if (map.containsKey('audio_track_setting')) { diff --git a/lib/src/model/hms_video_track_setting.dart b/lib/src/model/hms_video_track_setting.dart index 1e7c19618..4496d2ea4 100644 --- a/lib/src/model/hms_video_track_setting.dart +++ b/lib/src/model/hms_video_track_setting.dart @@ -7,14 +7,16 @@ class HMSVideoTrackSetting { final int? maxBitrate; final int? maxFrameRate; final HMSCameraFacing? cameraFacing; + final String? trackDescription; final bool? disableAutoResize; HMSVideoTrackSetting( - {this.codec, + {this.codec = HMSCodec.VP8, this.resolution, - this.maxBitrate, - this.maxFrameRate, - this.cameraFacing, + this.maxBitrate = 512, + this.maxFrameRate = 25, + this.cameraFacing = HMSCameraFacing.FRONT, + this.trackDescription = "This a video track", this.disableAutoResize = false}); factory HMSVideoTrackSetting.fromMap(Map map) { @@ -42,6 +44,7 @@ class HMSVideoTrackSetting { 'camera_facing': cameraFacing != null ? HMSCameraFacingValues.getValueFromHMSCameraFacing(cameraFacing!) : null, + 'track_description': trackDescription, 'disable_auto_resize': disableAutoResize ?? false }; } diff --git a/pubspec.yaml b/pubspec.yaml index fd65a70f0..2edcf7378 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: hmssdk_flutter description: The Flutter package for 100ms SDK. Video Conferencing infrastructure for a Video first world. -version: 0.7.4 +version: 0.7.5 homepage: https://www.100ms.live/ repository: https://github.com/100mslive/100ms-flutter issue_tracker: https://github.com/100mslive/100ms-flutter/issues diff --git a/sample apps/bloc/android/app/src/main/AndroidManifest.xml b/sample apps/bloc/android/app/src/main/AndroidManifest.xml index 4e9bb6fab..32849fb09 100644 --- a/sample apps/bloc/android/app/src/main/AndroidManifest.xml +++ b/sample apps/bloc/android/app/src/main/AndroidManifest.xml @@ -19,6 +19,9 @@ + + + + + diff --git a/sample apps/bloc/ios/flutterBroadcast/Info.plist b/sample apps/bloc/ios/flutterBroadcast/Info.plist new file mode 100644 index 000000000..e9367904d --- /dev/null +++ b/sample apps/bloc/ios/flutterBroadcast/Info.plist @@ -0,0 +1,15 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.broadcast-services-upload + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).SampleHandler + RPBroadcastProcessMode + RPBroadcastProcessModeSampleBuffer + + + diff --git a/sample apps/bloc/ios/flutterBroadcast/SampleHandler.swift b/sample apps/bloc/ios/flutterBroadcast/SampleHandler.swift new file mode 100644 index 000000000..037030f86 --- /dev/null +++ b/sample apps/bloc/ios/flutterBroadcast/SampleHandler.swift @@ -0,0 +1,48 @@ +import ReplayKit +import HMSBroadcastExtensionSDK + +class SampleHandler: RPBroadcastSampleHandler { + + let screenRenderer = HMSScreenRenderer(appGroup: "group.bloc.100ms.live") + + override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) { + // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional. + } + + override func broadcastPaused() { + // User has requested to pause the broadcast. Samples will stop being delivered. + } + + override func broadcastResumed() { + // User has requested to resume the broadcast. Samples delivery will resume. + } + + override func broadcastFinished() { + // User has requested to finish the broadcast. + screenRenderer.invalidate() + } + + override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) { + switch sampleBufferType { + case RPSampleBufferType.video: + // Handle video sample buffer + if let error = screenRenderer.process(sampleBuffer) { + if error.code == .noActiveMeeting { + finishBroadcastWithError(NSError(domain: "ScreenShare", + code: error.code.rawValue, + userInfo: [NSLocalizedFailureReasonErrorKey : "You are not in a meeting."])) + } + } + break + case RPSampleBufferType.audioApp: + // Handle audio sample buffer for app audio + break + case RPSampleBufferType.audioMic: + // Handle audio sample buffer for mic audio + break + @unknown default: + // Handle other sample buffer types + fatalError("Unknown type of sample buffer") + } + } +} diff --git a/sample apps/bloc/ios/flutterBroadcast/flutterBroadcast.entitlements b/sample apps/bloc/ios/flutterBroadcast/flutterBroadcast.entitlements new file mode 100644 index 000000000..2889f9c89 --- /dev/null +++ b/sample apps/bloc/ios/flutterBroadcast/flutterBroadcast.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.bloc.100ms.live + + + diff --git a/sample apps/bloc/lib/bloc/room/room_overview_bloc.dart b/sample apps/bloc/lib/bloc/room/room_overview_bloc.dart index 174e0079c..88cc05534 100644 --- a/sample apps/bloc/lib/bloc/room/room_overview_bloc.dart +++ b/sample apps/bloc/lib/bloc/room/room_overview_bloc.dart @@ -8,18 +8,23 @@ import 'package:hmssdk_flutter/hmssdk_flutter.dart'; class RoomOverviewBloc extends Bloc { final bool isVideoMute; final bool isAudioMute; + final bool isScreenShareActive; HMSSDK hmsSdk = HMSSDK(); String name; String url; late RoomObserver roomObserver; - RoomOverviewBloc(this.isVideoMute, this.isAudioMute, this.name, this.url) + RoomOverviewBloc(this.isVideoMute, this.isAudioMute, this.name, this.url, + this.isScreenShareActive) : super(RoomOverviewState( - isAudioMute: isAudioMute, isVideoMute: isVideoMute)) { + isAudioMute: isAudioMute, + isVideoMute: isVideoMute, + isScreenShareActive: isScreenShareActive)) { roomObserver = RoomObserver(this); on(_onSubscription); on(_onLocalAudioToggled); on(_onLocalVideoToggled); + on(_onScreenShareToggled); on(_onJoinSuccess); on(_onPeerLeave); on(_onPeerJoin); @@ -47,6 +52,16 @@ class RoomOverviewBloc extends Bloc { emit(state.copyWith(isVideoMute: !state.isVideoMute)); } + void _onScreenShareToggled(RoomOverviewLocalPeerScreenshareToggled event, + Emitter emit) async { + if (!state.isScreenShareActive) { + hmsSdk.startScreenShare(); + } else { + hmsSdk.stopScreenShare(); + } + emit(state.copyWith(isScreenShareActive: !state.isScreenShareActive)); + } + Future _onLocalAudioToggled(RoomOverviewLocalPeerAudioToggled event, Emitter emit) async { hmsSdk.switchAudio(isOn: !state.isAudioMute); diff --git a/sample apps/bloc/lib/bloc/room/room_overview_event.dart b/sample apps/bloc/lib/bloc/room/room_overview_event.dart index 7d80119c0..9438f3d53 100644 --- a/sample apps/bloc/lib/bloc/room/room_overview_event.dart +++ b/sample apps/bloc/lib/bloc/room/room_overview_event.dart @@ -16,6 +16,10 @@ class RoomOverviewLocalPeerVideoToggled extends RoomOverviewEvent { const RoomOverviewLocalPeerVideoToggled(); } +class RoomOverviewLocalPeerScreenshareToggled extends RoomOverviewEvent { + const RoomOverviewLocalPeerScreenshareToggled(); +} + class RoomOverviewLocalPeerAudioToggled extends RoomOverviewEvent { const RoomOverviewLocalPeerAudioToggled(); } diff --git a/sample apps/bloc/lib/bloc/room/room_overview_state.dart b/sample apps/bloc/lib/bloc/room/room_overview_state.dart index aeb7d0130..99bd31c9d 100644 --- a/sample apps/bloc/lib/bloc/room/room_overview_state.dart +++ b/sample apps/bloc/lib/bloc/room/room_overview_state.dart @@ -9,29 +9,38 @@ class RoomOverviewState extends Equatable { final bool isVideoMute; final bool isAudioMute; final bool leaveMeeting; - + final bool isScreenShareActive; const RoomOverviewState( {this.status = RoomOverviewStatus.initial, this.peerTrackNodes = const [], this.isVideoMute = false, this.isAudioMute = false, - this.leaveMeeting = false}); + this.leaveMeeting = false, + this.isScreenShareActive = false}); @override - List get props => - [status, peerTrackNodes, isAudioMute, isVideoMute, leaveMeeting]; + List get props => [ + status, + peerTrackNodes, + isAudioMute, + isVideoMute, + leaveMeeting, + isScreenShareActive + ]; RoomOverviewState copyWith( {RoomOverviewStatus? status, List? peerTrackNodes, bool? isVideoMute, bool? isAudioMute, - bool? leaveMeeting}) { + bool? leaveMeeting, + bool? isScreenShareActive}) { return RoomOverviewState( status: status ?? this.status, peerTrackNodes: peerTrackNodes ?? this.peerTrackNodes, isVideoMute: isVideoMute ?? this.isVideoMute, isAudioMute: isAudioMute ?? this.isAudioMute, - leaveMeeting: leaveMeeting ?? this.leaveMeeting); + leaveMeeting: leaveMeeting ?? this.leaveMeeting, + isScreenShareActive: isScreenShareActive ?? this.isScreenShareActive); } } diff --git a/sample apps/bloc/lib/views/preview_widget.dart b/sample apps/bloc/lib/views/preview_widget.dart index 00f3bc8d5..0e293f70c 100644 --- a/sample apps/bloc/lib/views/preview_widget.dart +++ b/sample apps/bloc/lib/views/preview_widget.dart @@ -64,8 +64,12 @@ class PreviewWidget extends StatelessWidget { padding: const EdgeInsets.all(14)), onPressed: () { Navigator.of(context).pushReplacement( - Room.route(meetingUrl, userName, - state.isVideoOff, state.isMicOff)); + Room.route( + meetingUrl, + userName, + state.isVideoOff, + state.isMicOff, + false)); }, child: const Text( "Join Now", diff --git a/sample apps/bloc/lib/views/room_widget.dart b/sample apps/bloc/lib/views/room_widget.dart index 888a3e8b3..175890468 100644 --- a/sample apps/bloc/lib/views/room_widget.dart +++ b/sample apps/bloc/lib/views/room_widget.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:demo_app_with_100ms_and_bloc/bloc/preview/preview_cubit.dart'; import 'package:demo_app_with_100ms_and_bloc/bloc/room/room_overview_bloc.dart'; import 'package:demo_app_with_100ms_and_bloc/bloc/room/room_overview_event.dart'; @@ -12,21 +14,22 @@ class Room extends StatelessWidget { final String userName; final bool isVideoOff; final bool isAudioOff; - - static Route route(String url, String name, bool v, bool a) { - return MaterialPageRoute(builder: (_) => Room(url, name, v, a)); + final bool isScreenshareActive; + static Route route(String url, String name, bool v, bool a, bool ss) { + return MaterialPageRoute(builder: (_) => Room(url, name, v, a, ss)); } const Room(this.meetingUrl, this.userName, this.isVideoOff, this.isAudioOff, + this.isScreenshareActive, {Key? key}) : super(key: key); @override Widget build(BuildContext context) { return BlocProvider( - create: (_) => - RoomOverviewBloc(isVideoOff, isAudioOff, userName, meetingUrl) - ..add(const RoomOverviewSubscriptionRequested()), + create: (_) => RoomOverviewBloc( + isVideoOff, isAudioOff, userName, meetingUrl, isScreenshareActive) + ..add(const RoomOverviewSubscriptionRequested()), child: RoomWidget(meetingUrl, userName), ); } @@ -67,13 +70,9 @@ class RoomWidget extends StatelessWidget { return BottomNavigationBar( type: BottomNavigationBarType.fixed, backgroundColor: Colors.black, - selectedItemColor: Colors.greenAccent, + selectedItemColor: Colors.grey, unselectedItemColor: Colors.grey, items: [ - const BottomNavigationBarItem( - icon: Icon(Icons.cancel), - label: 'Leave Meeting', - ), BottomNavigationBarItem( icon: Icon(state.isAudioMute ? Icons.mic_off : Icons.mic), label: 'Mic', @@ -83,6 +82,20 @@ class RoomWidget extends StatelessWidget { state.isVideoMute ? Icons.videocam_off : Icons.videocam), label: 'Camera', ), + //For screenshare in iOS follow the steps here : https://www.100ms.live/docs/flutter/v2/features/Screen-Share + if (Platform.isAndroid) + BottomNavigationBarItem( + icon: Icon( + Icons.screen_share, + color: (state.isScreenShareActive) + ? Colors.green + : Colors.grey, + ), + label: "ScreenShare"), + const BottomNavigationBarItem( + icon: Icon(Icons.cancel), + label: 'Leave Meeting', + ), ], //New @@ -93,16 +106,26 @@ class RoomWidget extends StatelessWidget { } void _onItemTapped(int index, BuildContext context) { - if (index == 0) { - context.read().add(const RoomOverviewLeaveRequested()); - } else if (index == 1) { - context - .read() - .add(const RoomOverviewLocalPeerAudioToggled()); - } else { - context - .read() - .add(const RoomOverviewLocalPeerVideoToggled()); + switch (index) { + case 0: + context + .read() + .add(const RoomOverviewLocalPeerAudioToggled()); + break; + case 1: + context + .read() + .add(const RoomOverviewLocalPeerVideoToggled()); + break; + case 2: + context + .read() + .add(const RoomOverviewLocalPeerScreenshareToggled()); + break; + case 3: + context + .read() + .add(const RoomOverviewLeaveRequested()); } } } diff --git a/sample apps/getx/lib/controllers/PreviewController.dart b/sample apps/getx/lib/controllers/PreviewController.dart index 99b21cc70..0e81ef0f6 100644 --- a/sample apps/getx/lib/controllers/PreviewController.dart +++ b/sample apps/getx/lib/controllers/PreviewController.dart @@ -70,7 +70,7 @@ class PreviewController extends GetxController {HMSActionResultListenerMethod? methodType, Map? arguments, required HMSException hmsException}) { - Get.snackbar("Error", hmsException.message); + Get.snackbar("Error", hmsException.message ?? ""); } @override @@ -99,7 +99,7 @@ class PreviewController extends GetxController @override void onHMSError({required HMSException error}) { - Get.snackbar("Error", error.message); + Get.snackbar("Error", error.message ?? ""); } @override diff --git a/sample apps/getx/lib/controllers/RoomController.dart b/sample apps/getx/lib/controllers/RoomController.dart index e118e3173..ff65783cb 100644 --- a/sample apps/getx/lib/controllers/RoomController.dart +++ b/sample apps/getx/lib/controllers/RoomController.dart @@ -52,7 +52,7 @@ class RoomController extends GetxController @override void onHMSError({required HMSException error}) { - Get.snackbar("Error", error.message); + Get.snackbar("Error", error.message ?? ""); } @override @@ -153,7 +153,7 @@ class RoomController extends GetxController {HMSActionResultListenerMethod? methodType, Map? arguments, required HMSException hmsException}) { - Get.snackbar("Error", hmsException.message); + Get.snackbar("Error", hmsException.message ?? ""); } @override