diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb8839c3..0e50a7c0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## 0.7.7 - 2022-10-20 + +- Added ability to set & get session metadata +- Added ability to join with muted audio & video using Initial states (Muted / Unmuted) `HMSVideoTrackSettings` & `HMSAudioTrackSettings` in the builder of HMSSDK +- Added better Telemetrics for analytics +- Added option to use Software Decoder for Video rendering on Android devices +- Added action result listener to `switchCamera` function on local video track +- Fixed LetterBoxing (Black borders on top and bottom) observed when sharing the screen in landscape mode on Android +- Fixed incorrect sending of Speaker Updates when peer has left the room +- Removed unused setters for Local Audio & Video Track Settings +- Updated to Native Android SDK 2.5.0 & Native iOS SDK 0.4.5 + +Full Changelog: [0.7.6...0.7.7](https://github.com/100mslive/100ms-flutter/compare/0.7.6...0.7.7) + ## 0.7.6 - 2022-09-23 - Added audio output change listener callback while in Preview on Android diff --git a/android/build.gradle b/android/build.gradle index 4f36dc8df..b977c7fcd 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -37,7 +37,7 @@ android { } dependencies { - implementation 'com.github.100mslive.android-sdk:lib:2.4.8' + implementation 'com.github.100mslive.android-sdk:lib:2.5.0' 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/HMSAudioTrackSettingsExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSAudioTrackSettingsExtension.kt index eb991a685..5a089be4e 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSAudioTrackSettingsExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSAudioTrackSettingsExtension.kt @@ -10,12 +10,9 @@ class HMSAudioTrackSettingsExtension { companion object{ fun toDictionary(hmsAudioTrackSettings: HMSAudioTrackSettings?):HashMap? { val map = HashMap() - map["max_bit_rate"] = hmsAudioTrackSettings?.maxBitrate!! - // map["track_description"] = hmsAudioTrackSettings?.track_description ?: "" - map["volume"] = hmsAudioTrackSettings.volume - map["codec"]=AudioParamsExtension.getValueOfHMSAudioCodec(hmsAudioTrackSettings.codec) - map["user_hardware_acoustic_echo_canceler"] = hmsAudioTrackSettings.useHardwareAcousticEchoCanceler - return map + map["user_hardware_acoustic_echo_canceler"] = hmsAudioTrackSettings?.useHardwareAcousticEchoCanceler!! + map["track_initial_state"] = HMSTrackInitStateExtension.getValueFromHMSTrackInitState(hmsAudioTrackSettings.initialState) + return map } } } \ No newline at end of file diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoomExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoomExtension.kt index 327c6396c..32ef6fbfe 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoomExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoomExtension.kt @@ -42,7 +42,6 @@ class HMSRoomExtension { HMSRoomUpdate.HLS_STREAMING_STATE_UPDATED-> "hls_streaming_state_updated" HMSRoomUpdate.BROWSER_RECORDING_STATE_UPDATED-> "browser_recording_state_updated" HMSRoomUpdate.HLS_RECORDING_STATE_UPDATED-> "hls_recording_state_updated" - HMSRoomUpdate.ROOM_NAME_UPDATED->"room_name_updated" HMSRoomUpdate.ROOM_PEER_COUNT_UPDATED->"room_peer_count_updated" else-> "defaultUpdate" } diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSSessionMetadataExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSSessionMetadataExtension.kt new file mode 100644 index 000000000..25c7bfccb --- /dev/null +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSSessionMetadataExtension.kt @@ -0,0 +1,14 @@ +package live.hms.hmssdk_flutter + +class HMSSessionMetadataExtension { + companion object{ + fun toDictionary(metadata: String?):HashMap?{ + + val hashMap=HashMap() + + hashMap["metadata"] = metadata + + return hashMap + } + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSSpeakerExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSSpeakerExtension.kt index 2808281d7..f8f5c4242 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSSpeakerExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSSpeakerExtension.kt @@ -8,8 +8,13 @@ class HMSSpeakerExtension { val hashMap = HashMap() if(speaker==null)return null hashMap.put("audioLevel",speaker.level) - hashMap.put("track",HMSTrackExtension.toDictionary(speaker.hmsTrack)) - hashMap.put("peer",HMSPeerExtension.toDictionary(speaker.peer)) + val hmsTrackMap = HMSTrackExtension.toDictionary(speaker.hmsTrack) + val hmsPeerMap = HMSPeerExtension.toDictionary(speaker.peer) + if((hmsTrackMap == null) || (hmsPeerMap == null)){ + return null + } + hashMap.put("track",hmsTrackMap) + hashMap.put("peer",hmsPeerMap) return hashMap } } diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackInitStateExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackInitStateExtension.kt new file mode 100644 index 000000000..a2a28a3c3 --- /dev/null +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackInitStateExtension.kt @@ -0,0 +1,23 @@ +package live.hms.hmssdk_flutter + +import live.hms.video.media.settings.HMSTrackSettings + +class HMSTrackInitStateExtension { + companion object{ + fun getHMSTrackInitStatefromValue(value:String):HMSTrackSettings.InitState{ + return when(value){ + "MUTED" -> HMSTrackSettings.InitState.MUTED + "UNMUTED" -> HMSTrackSettings.InitState.UNMUTED + else->HMSTrackSettings.InitState.MUTED + } + } + + fun getValueFromHMSTrackInitState(hmsTrackInitState:HMSTrackSettings.InitState):String{ + return when(hmsTrackInitState){ + HMSTrackSettings.InitState.UNMUTED-> "UNMUTED" + HMSTrackSettings.InitState.MUTED-> "MUTED" + else->"MUTED" + } + } + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackSettingsExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackSettingsExtension.kt index 806deb97b..76887b108 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackSettingsExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackSettingsExtension.kt @@ -32,20 +32,10 @@ class HMSTrackSettingsExtension { 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) - } + val initialState = HMSTrackInitStateExtension.getHMSTrackInitStatefromValue(hmsAudioTrackHashMap["track_initial_state"] as String) if (useHardwareAcousticEchoCanceler != null) { hmsAudioTrackSettings = hmsAudioTrackSettings.setUseHardwareAcousticEchoCanceler( @@ -53,33 +43,42 @@ class HMSTrackSettingsExtension { ) } - if (audioCodec != null) { - hmsAudioTrackSettings = hmsAudioTrackSettings.codec(audioCodec) + if(initialState != null){ + hmsAudioTrackSettings = hmsAudioTrackSettings.initialState(initialState) } } - 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? + val cameraFacing = getHMSCameraFacingFromValue(hmsVideoTrackHashMap["camera_facing"] as String?) + val disableAutoResize = hmsVideoTrackHashMap["disable_auto_resize"] as Boolean + val initialState = HMSTrackInitStateExtension.getHMSTrackInitStatefromValue(hmsVideoTrackHashMap["track_initial_state"] as String) + val forceSoftwareDecoder = hmsVideoTrackHashMap["force_software_decoder"] as Boolean - if (maxBitRate != null) { - hmsVideoTrackSettings = hmsVideoTrackSettings.maxBitrate(maxBitRate) + if(cameraFacing != null){ + hmsVideoTrackSettings = hmsVideoTrackSettings.cameraFacing(cameraFacing) } - - if (maxFrameRate != null) { - hmsVideoTrackSettings = hmsVideoTrackSettings.maxFrameRate(maxFrameRate) + if(disableAutoResize != null){ + hmsVideoTrackSettings = hmsVideoTrackSettings.disableAutoResize(disableAutoResize); } - if (videoCodec != null) { - hmsVideoTrackSettings = hmsVideoTrackSettings.codec(videoCodec) + if(initialState != null){ + hmsVideoTrackSettings = hmsVideoTrackSettings.initialState(initialState) + } + if(forceSoftwareDecoder != null){ + hmsVideoTrackSettings = hmsVideoTrackSettings.forceSoftwareDecoder(forceSoftwareDecoder) } } return HMSTrackSettings.Builder().audio(hmsAudioTrackSettings.build()).video(hmsVideoTrackSettings.build()).build() } + + private fun getHMSCameraFacingFromValue(cameraFacing:String?):HMSVideoTrackSettings.CameraFacing{ + return when(cameraFacing){ + "back" -> HMSVideoTrackSettings.CameraFacing.BACK + "front" -> HMSVideoTrackSettings.CameraFacing.FRONT + else -> HMSVideoTrackSettings.CameraFacing.FRONT + } + } } } \ 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 77193204f..1826aa7ed 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSVideoTrackSettingsExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSVideoTrackSettingsExtension.kt @@ -11,10 +11,9 @@ class HMSVideoTrackSettingsExtension { fun toDictionary(hmsVideoTrackSettings: HMSVideoTrackSettings?):HashMap?{ val map = HashMap() map["camera_facing"] = getValueOfHMSCameraFacing(hmsVideoTrackSettings?.cameraFacing)!! - map["video_codec"] = getValueOfHMSVideoCodec(hmsVideoTrackSettings?.codec)!! - map["max_bit_rate"] = hmsVideoTrackSettings?.maxBitRate!! - map["max_frame_rate"] = hmsVideoTrackSettings.maxFrameRate - map["disable_auto_resize"] = hmsVideoTrackSettings.disableAutoResize + map["disable_auto_resize"] = hmsVideoTrackSettings?.disableAutoResize!! + map["track_initial_state"] = HMSTrackInitStateExtension.getValueFromHMSTrackInitState(hmsVideoTrackSettings.initialState) + map["force_software_decoder"] = hmsVideoTrackSettings.forceSoftwareDecoder return map } @@ -29,15 +28,5 @@ class HMSVideoTrackSettingsExtension { } } - private fun getValueOfHMSVideoCodec(codec: HMSVideoCodec?):String?{ - if(codec==null)return null - - return when(codec){ - HMSVideoCodec.H264-> "h264" - HMSVideoCodec.VP8->"vp8" - HMSVideoCodec.VP9->"vp9" - else-> "defaultCodec" - } - } } } \ No newline at end of file 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 4afd835c2..68bbc418d 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt @@ -6,7 +6,6 @@ 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 @@ -19,17 +18,13 @@ import io.flutter.plugin.common.MethodChannel.Result import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import live.hms.hmssdk_flutter.hms_role_components.AudioParamsExtension import live.hms.hmssdk_flutter.hms_role_components.VideoParamsExtension +import live.hms.hmssdk_flutter.methods.HMSSessionMetadataAction import live.hms.hmssdk_flutter.views.HMSVideoViewFactory import live.hms.video.audio.HMSAudioManager.* import live.hms.video.connection.stats.* import live.hms.video.error.HMSException -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.media.tracks.* import live.hms.video.sdk.* import live.hms.video.sdk.models.* @@ -40,7 +35,7 @@ import live.hms.video.sdk.models.enums.HMSTrackUpdate import live.hms.video.sdk.models.role.HMSRole import live.hms.video.sdk.models.trackchangerequest.HMSChangeTrackStateRequest import live.hms.video.utils.HMSLogger -import live.hms.video.audio.HMSAudioManager.* +import live.hms.video.events.AgentType /** HmssdkFlutterPlugin */ @@ -67,31 +62,33 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, } override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - this.channel = MethodChannel(flutterPluginBinding.binaryMessenger, "hmssdk_flutter") - this.meetingEventChannel = - EventChannel(flutterPluginBinding.binaryMessenger, "meeting_event_channel") - this.previewChannel = - EventChannel(flutterPluginBinding.binaryMessenger, "preview_event_channel") - - this.logsEventChannel = - EventChannel(flutterPluginBinding.binaryMessenger, "logs_event_channel") - - this.rtcStatsChannel = EventChannel(flutterPluginBinding.binaryMessenger, "rtc_event_channel") - - - this.meetingEventChannel.setStreamHandler(this) - this.channel.setMethodCallHandler(this) - this.previewChannel.setStreamHandler(this) - this.logsEventChannel.setStreamHandler(this) - this.rtcStatsChannel.setStreamHandler(this) - this.hmsVideoFactory = HMSVideoViewFactory(this) - - flutterPluginBinding.platformViewRegistry.registerViewFactory( - "HMSVideoView", - hmsVideoFactory - ) - hmssdkFlutterPlugin = this - + if(hmssdkFlutterPlugin == null) { + this.channel = MethodChannel(flutterPluginBinding.binaryMessenger, "hmssdk_flutter") + this.meetingEventChannel = + EventChannel(flutterPluginBinding.binaryMessenger, "meeting_event_channel") + this.previewChannel = + EventChannel(flutterPluginBinding.binaryMessenger, "preview_event_channel") + + this.logsEventChannel = + EventChannel(flutterPluginBinding.binaryMessenger, "logs_event_channel") + + this.rtcStatsChannel = + EventChannel(flutterPluginBinding.binaryMessenger, "rtc_event_channel") + + + this.meetingEventChannel.setStreamHandler(this) + this.channel.setMethodCallHandler(this) + this.previewChannel.setStreamHandler(this) + this.logsEventChannel.setStreamHandler(this) + this.rtcStatsChannel.setStreamHandler(this) + this.hmsVideoFactory = HMSVideoViewFactory(this) + + flutterPluginBinding.platformViewRegistry.registerViewFactory( + "HMSVideoView", + hmsVideoFactory + ) + hmssdkFlutterPlugin = this + } } override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { @@ -155,10 +152,7 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, "start_screen_share", "stop_screen_share", "is_screen_share_active" -> { screenshareActions(call, result) } - - "update_hms_video_track_settings" -> { - updateHMSLocalTrackSetting(call) - } + "get_track_by_id" -> { getTrackById(call, result) } @@ -178,6 +172,9 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, "get_track_settings"->{ trackSettings(call, result) } + "get_session_metadata","set_session_metadata" -> { + HMSSessionMetadataAction.sessionMetadataActions(call, result,hmssdk!!) + } else -> { result.notImplemented() } @@ -345,12 +342,14 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, } override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - meetingEventChannel.setStreamHandler(null) - previewChannel.setStreamHandler(null) - logsEventChannel.setStreamHandler(null) - rtcStatsChannel.setStreamHandler(null) - hmssdkFlutterPlugin = null + if(hmssdkFlutterPlugin != null){ + channel.setMethodCallHandler(null) + meetingEventChannel.setStreamHandler(null) + previewChannel.setStreamHandler(null) + logsEventChannel.setStreamHandler(null) + rtcStatsChannel.setStreamHandler(null) + hmssdkFlutterPlugin = null + } } override fun onAttachedToActivity(binding: ActivityPluginBinding) { @@ -568,7 +567,10 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, if (speakers.isNotEmpty()) { speakers.forEach { - speakersList.add(HMSSpeakerExtension.toDictionary(it)!!) + val hmsSpeakerMap = HMSSpeakerExtension.toDictionary(it) + if(hmsSpeakerMap != null){ + speakersList.add(hmsSpeakerMap!!) + } } } val speakersMap = HashMap() @@ -667,42 +669,18 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, val hmsAudioTrackHashMap: HashMap? = hmsTrackSettingMap["audio_track_setting"] val hmsVideoTrackHashMap: HashMap? = hmsTrackSettingMap["video_track_setting"] - val hmsTrackSettings = HMSTrackSettingsExtension.setTrackSettings(hmsAudioTrackHashMap,hmsVideoTrackHashMap) + val dartSDKVersion = call.argument("dart_sdk_version") + val hmsSDKVersion = call.argument("hmssdk_version") + val framework = FrameworkInfo(framework = AgentType.FLUTTER, frameworkVersion = dartSDKVersion, frameworkSdkVersion = hmsSDKVersion) hmssdk = HMSSDK .Builder(activity) .setTrackSettings(hmsTrackSettings) + .setFrameworkInfo(framework) .build() result.success(true) } - private fun updateHMSLocalTrackSetting(call: MethodCall) { - val localPeerVideoTrack = getLocalPeer()!!.videoTrack - var hmsVideoTrackSettings = localPeerVideoTrack!!.settings.builder() - val hmsVideoTrackHashMap: HashMap? = call.argument("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) - } - } - - CoroutineScope(Dispatchers.Default).launch { - localPeerVideoTrack.setSettings(hmsVideoTrackSettings.build()) - } - } - private var hasChangedMetadata: Boolean = false private fun changeMetadata(call: MethodCall, result: Result) { diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSSessionMetadataAction.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSSessionMetadataAction.kt new file mode 100644 index 000000000..4c06868d0 --- /dev/null +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSSessionMetadataAction.kt @@ -0,0 +1,58 @@ +package live.hms.hmssdk_flutter.methods + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import live.hms.hmssdk_flutter.HMSCommonAction +import live.hms.hmssdk_flutter.HMSExceptionExtension +import live.hms.hmssdk_flutter.HMSSessionMetadataExtension +import live.hms.video.error.HMSException +import live.hms.video.sdk.HMSSDK +import live.hms.video.sdk.HMSSessionMetadataListener + +class HMSSessionMetadataAction { + companion object { + fun sessionMetadataActions(call: MethodCall, result: MethodChannel.Result, hmssdk: HMSSDK) { + when (call.method) { + "get_session_metadata" -> { + getSessionMetadata(result, hmssdk) + } + "set_session_metadata" -> { + setSessionMetadata(call, result, hmssdk) + } + else -> { + result.notImplemented() + } + } + } + + private fun getSessionMetadata(result: MethodChannel.Result, hmssdk: HMSSDK) { + hmssdk.getSessionMetaData(getSessionMetadataResultListener(result)) + } + + private fun setSessionMetadata(call: MethodCall, result: MethodChannel.Result, hmssdk: HMSSDK) { + val metadata = call.argument("session_metadata") + hmssdk.setSessionMetaData(metadata, HMSCommonAction.getActionListener(result)) + } + + private fun getSessionMetadataResultListener(result: MethodChannel.Result) = object: + HMSSessionMetadataListener { + override fun onError(error: HMSException) { + result.success(null) + } + + override fun onSuccess(sessionMetadata: String?) { + val args = HashMap() + args["event_name"] = "session_metadata" + args["data"] = HMSSessionMetadataExtension.toDictionary(sessionMetadata) + if (args["data"] != null) + CoroutineScope(Dispatchers.Main).launch { + result.success(args) + } + } + } + + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSVideoAction.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSVideoAction.kt index d364ce0ac..179f7f062 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSVideoAction.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSVideoAction.kt @@ -17,8 +17,7 @@ class HMSVideoAction { } "switch_camera" -> { - switchCamera(hmssdk) - result.success("switch_camera") + switchCamera(result,hmssdk) } "start_capturing" -> { @@ -77,11 +76,11 @@ class HMSVideoAction { } } - private fun switchCamera(hmssdk:HMSSDK) { + private fun switchCamera(result : Result,hmssdk:HMSSDK) { val peer = hmssdk.getLocalPeer() val videoTrack = peer?.videoTrack CoroutineScope(Dispatchers.Default).launch { - videoTrack?.switchCamera() + videoTrack?.switchCamera(onAction = HMSCommonAction.getActionListener(result)) } } diff --git a/example/android/Gemfile.lock b/example/android/Gemfile.lock index af14a1f2c..be7cbba2b 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.635.0) - aws-sdk-core (3.153.0) + aws-partitions (1.648.0) + aws-sdk-core (3.162.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) @@ -17,11 +17,11 @@ GEM aws-sdk-kms (1.58.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.114.0) + aws-sdk-s3 (1.115.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.1) + aws-sigv4 (1.5.2) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) @@ -36,7 +36,7 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.92.5) + excon (0.93.1) faraday (1.10.2) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -105,11 +105,11 @@ 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.6) + fastlane-plugin-firebase_app_distribution (0.3.7) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.27.0) - google-apis-core (>= 0.7.2, < 2.a) - google-apis-core (0.9.0) + google-apis-androidpublisher_v3 (0.29.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-core (0.9.1) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -118,27 +118,27 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-iamcredentials_v1 (0.14.0) - google-apis-core (>= 0.7.2, < 2.a) - google-apis-playcustomapp_v1 (0.10.0) - google-apis-core (>= 0.7, < 2.a) - google-apis-storage_v1 (0.18.0) - google-apis-core (>= 0.7, < 2.a) + google-apis-iamcredentials_v1 (0.15.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-playcustomapp_v1 (0.11.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-storage_v1 (0.19.0) + google-apis-core (>= 0.9.0, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.3.0) - google-cloud-storage (1.37.0) + google-cloud-storage (1.43.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.19.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.2.0) + googleauth (1.3.0) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 46a1f9e1e..5ea6e2d9e 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 140 - versionName "1.4.5" + versionCode 153 + versionName "1.4.18" } signingConfigs { @@ -46,9 +46,11 @@ android { } buildTypes { release { - shrinkResources true + shrinkResources false - minifyEnabled true + minifyEnabled false + + debuggable true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' diff --git a/example/assets/icons/decoder.svg b/example/assets/icons/decoder.svg new file mode 100644 index 000000000..6cf2ebc41 --- /dev/null +++ b/example/assets/icons/decoder.svg @@ -0,0 +1,3 @@ + + + diff --git a/example/assets/icons/pin.svg b/example/assets/icons/pin.svg new file mode 100644 index 000000000..13025ca56 --- /dev/null +++ b/example/assets/icons/pin.svg @@ -0,0 +1,3 @@ + + + diff --git a/example/ios/Gemfile.lock b/example/ios/Gemfile.lock index 3fcee7e80..cb87d6958 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.635.0) - aws-sdk-core (3.153.0) + aws-partitions (1.648.0) + aws-sdk-core (3.162.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) @@ -17,11 +17,11 @@ GEM aws-sdk-kms (1.58.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.114.0) + aws-sdk-s3 (1.115.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.1) + aws-sigv4 (1.5.2) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) @@ -36,7 +36,7 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.92.5) + excon (0.93.1) faraday (1.10.2) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -105,12 +105,12 @@ 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.6) + fastlane-plugin-firebase_app_distribution (0.3.7) fastlane-plugin-versioning (0.5.1) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.27.0) - google-apis-core (>= 0.7.2, < 2.a) - google-apis-core (0.9.0) + google-apis-androidpublisher_v3 (0.29.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-core (0.9.1) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -119,27 +119,27 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-iamcredentials_v1 (0.14.0) - google-apis-core (>= 0.7.2, < 2.a) - google-apis-playcustomapp_v1 (0.10.0) - google-apis-core (>= 0.7, < 2.a) - google-apis-storage_v1 (0.18.0) - google-apis-core (>= 0.7, < 2.a) + google-apis-iamcredentials_v1 (0.15.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-playcustomapp_v1 (0.11.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-storage_v1 (0.19.0) + google-apis-core (>= 0.9.0, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.3.0) - google-cloud-storage (1.37.0) + google-cloud-storage (1.43.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.19.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.2.0) + googleauth (1.3.0) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 3c90c3466..aeb4a0edc 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -33,45 +33,45 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter - - Firebase/Analytics (9.5.0): + - Firebase/Analytics (9.6.0): - Firebase/Core - - Firebase/Core (9.5.0): + - Firebase/Core (9.6.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 9.5.0) - - Firebase/CoreOnly (9.5.0): - - FirebaseCore (= 9.5.0) - - Firebase/Crashlytics (9.5.0): + - FirebaseAnalytics (~> 9.6.0) + - Firebase/CoreOnly (9.6.0): + - FirebaseCore (= 9.6.0) + - Firebase/Crashlytics (9.6.0): - Firebase/CoreOnly - - FirebaseCrashlytics (~> 9.5.0) - - Firebase/DynamicLinks (9.5.0): + - FirebaseCrashlytics (~> 9.6.0) + - Firebase/DynamicLinks (9.6.0): - Firebase/CoreOnly - - FirebaseDynamicLinks (~> 9.5.0) - - Firebase/Performance (9.5.0): + - FirebaseDynamicLinks (~> 9.6.0) + - Firebase/Performance (9.6.0): - Firebase/CoreOnly - - FirebasePerformance (~> 9.5.0) - - firebase_analytics (9.3.4): - - Firebase/Analytics (= 9.5.0) + - FirebasePerformance (~> 9.6.0) + - firebase_analytics (9.3.8): + - Firebase/Analytics (= 9.6.0) - firebase_core - Flutter - - firebase_core (1.22.0): - - Firebase/CoreOnly (= 9.5.0) + - firebase_core (1.24.0): + - Firebase/CoreOnly (= 9.6.0) - Flutter - - firebase_crashlytics (2.8.10): - - Firebase/Crashlytics (= 9.5.0) + - firebase_crashlytics (2.9.0): + - Firebase/Crashlytics (= 9.6.0) - firebase_core - Flutter - - firebase_dynamic_links (4.3.7): - - Firebase/DynamicLinks (= 9.5.0) + - firebase_dynamic_links (4.3.11): + - Firebase/DynamicLinks (= 9.6.0) - firebase_core - Flutter - - firebase_performance (0.8.3): - - Firebase/Performance (= 9.5.0) + - firebase_performance (0.8.3-3): + - Firebase/Performance (= 9.6.0) - firebase_core - Flutter - FirebaseABTesting (9.6.0): - FirebaseCore (~> 9.0) - - FirebaseAnalytics (9.5.0): - - FirebaseAnalytics/AdIdSupport (= 9.5.0) + - FirebaseAnalytics (9.6.0): + - FirebaseAnalytics/AdIdSupport (= 9.6.0) - FirebaseCore (~> 9.0) - FirebaseInstallations (~> 9.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.7) @@ -79,16 +79,16 @@ PODS: - GoogleUtilities/Network (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseAnalytics/AdIdSupport (9.5.0): + - FirebaseAnalytics/AdIdSupport (9.6.0): - FirebaseCore (~> 9.0) - FirebaseInstallations (~> 9.0) - - GoogleAppMeasurement (= 9.5.0) + - GoogleAppMeasurement (= 9.6.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - GoogleUtilities/MethodSwizzler (~> 7.7) - GoogleUtilities/Network (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseCore (9.5.0): + - FirebaseCore (9.6.0): - FirebaseCoreDiagnostics (~> 9.0) - FirebaseCoreInternal (~> 9.0) - GoogleUtilities/Environment (~> 7.7) @@ -100,21 +100,21 @@ PODS: - nanopb (< 2.30910.0, >= 2.30908.0) - FirebaseCoreInternal (9.6.0): - "GoogleUtilities/NSData+zlib (~> 7.7)" - - FirebaseCrashlytics (9.5.0): + - FirebaseCrashlytics (9.6.0): - FirebaseCore (~> 9.0) - FirebaseInstallations (~> 9.0) - GoogleDataTransport (< 10.0.0, >= 9.1.4) - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesObjC (~> 2.1) - - FirebaseDynamicLinks (9.5.0): + - FirebaseDynamicLinks (9.6.0): - FirebaseCore (~> 9.0) - FirebaseInstallations (9.6.0): - FirebaseCore (~> 9.0) - GoogleUtilities/Environment (~> 7.7) - GoogleUtilities/UserDefaults (~> 7.7) - PromisesObjC (~> 2.1) - - FirebasePerformance (9.5.0): + - FirebasePerformance (9.6.0): - FirebaseCore (~> 9.0) - FirebaseInstallations (~> 9.0) - FirebaseRemoteConfig (~> 9.0) @@ -133,21 +133,21 @@ PODS: - fluttertoast (0.0.2): - Flutter - Toast - - GoogleAppMeasurement (9.5.0): - - GoogleAppMeasurement/AdIdSupport (= 9.5.0) + - GoogleAppMeasurement (9.6.0): + - GoogleAppMeasurement/AdIdSupport (= 9.6.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - GoogleUtilities/MethodSwizzler (~> 7.7) - GoogleUtilities/Network (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (9.5.0): - - GoogleAppMeasurement/WithoutAdIdSupport (= 9.5.0) + - GoogleAppMeasurement/AdIdSupport (9.6.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 9.6.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - GoogleUtilities/MethodSwizzler (~> 7.7) - GoogleUtilities/Network (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/WithoutAdIdSupport (9.5.0): + - GoogleAppMeasurement/WithoutAdIdSupport (9.6.0): - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - GoogleUtilities/MethodSwizzler (~> 7.7) - GoogleUtilities/Network (~> 7.7) @@ -177,13 +177,13 @@ PODS: - GoogleUtilities/Logger - GoogleUtilities/UserDefaults (7.8.0): - GoogleUtilities/Logger - - HMSBroadcastExtensionSDK (0.0.3) - - HMSSDK (0.4.2): + - HMSBroadcastExtensionSDK (0.0.4) + - HMSSDK (0.4.5): - HMSWebRTC (= 1.0.4898) - - hmssdk_flutter (0.7.6): + - hmssdk_flutter (0.7.7): - Flutter - - HMSBroadcastExtensionSDK (= 0.0.3) - - HMSSDK (= 0.4.2) + - HMSBroadcastExtensionSDK (= 0.0.4) + - HMSSDK (= 0.4.5) - HMSWebRTC (1.0.4898) - MTBBarcodeScanner (5.0.11) - nanopb (2.30909.0): @@ -201,9 +201,9 @@ PODS: - qr_code_scanner (0.2.0): - Flutter - MTBBarcodeScanner - - SDWebImage (5.13.3): - - SDWebImage/Core (= 5.13.3) - - SDWebImage/Core (5.13.3) + - SDWebImage (5.13.4): + - SDWebImage/Core (= 5.13.4) + - SDWebImage/Core (5.13.4) - shared_preferences_ios (0.0.1): - Flutter - SwiftyGif (5.4.3) @@ -307,31 +307,31 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 - Firebase: 800f16f07af493d98d017446a315c27af0552f41 - firebase_analytics: 350f8b25c613a64e64b90d1207f41cb7c529a62c - firebase_core: 31872a49ffe0bf6f834e7044c7334e433536735a - firebase_crashlytics: 1d9af2a9b4c0a3d4ed3ee61c7b4d053484d1a476 - firebase_dynamic_links: 0004551f927f87dc7dadbdc2b843cfab0f3067c7 - firebase_performance: f1f6b5daf5848f01636d6ec6a496beaa2ccbe16a + file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95 + Firebase: 5ae8b7cf8efce559a653aef0ad95bab3f427c351 + firebase_analytics: 9937a91df25cce5b0ee19eaf65277ff8bf0f88e5 + firebase_core: 7c28ecc1e5dd74e03829ac3e9ff5ba3314e737a9 + firebase_crashlytics: 520a59314eaaadb34f9be4c2a285d99cfa88ebdb + firebase_dynamic_links: 8fc870871acfbbd39172cb430e4a04c0fe2be4c7 + firebase_performance: 3ae9ae3142d5d0bf8782e1e7a73dde2267bc4415 FirebaseABTesting: 61826730ce9eee8781ba99a2b3420e9bce148dc9 - FirebaseAnalytics: 1b60984a408320dda637306f3f733699ef8473d7 - FirebaseCore: 25c0400b670fd1e2f2104349cd3b5dcce8d9418f + FirebaseAnalytics: 89ad762c6c3852a685794174757e2c60a36b6a82 + FirebaseCore: 2082fffcd855f95f883c0a1641133eb9bbe76d40 FirebaseCoreDiagnostics: 99a495094b10a57eeb3ae8efa1665700ad0bdaa6 FirebaseCoreInternal: bca76517fe1ed381e989f5e7d8abb0da8d85bed3 - FirebaseCrashlytics: d20fa38bb8c88cb0f1c9211286bc23ab58c1b464 - FirebaseDynamicLinks: e2805928aefebd742288161f99469ab84d4aa8c8 + FirebaseCrashlytics: 3210572ddb77801e5a0bd9d7bc890769f2066a0c + FirebaseDynamicLinks: 894ee3b4e56a77abee067d371c9a23e7b5a3c686 FirebaseInstallations: 0a115432c4e223c5ab20b0dbbe4cbefa793a0e8e - FirebasePerformance: 5ec395fd74f35bfa68e969703922a46a8f143053 + FirebasePerformance: f1e62598cd98c88ce6c10e0d26223d18b85c26d6 FirebaseRemoteConfig: ee09d77a7d7c7e31da6a0d1cf956cd611c85609c Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037 - GoogleAppMeasurement: 6ee231473fbd75c11221dfce489894334024eead + fluttertoast: 74526702fea2c060ea55dde75895b7e1bde1c86b + GoogleAppMeasurement: 6de2b1a69e4326eb82ee05d138f6a5cb7311bcb1 GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7 - HMSBroadcastExtensionSDK: a69a4503a1708b5d51e2890d70f574b9cdfea760 - HMSSDK: 50c5ea8edf7093472d4254c6f69ccce11323be10 - hmssdk_flutter: d01a09c586d25afda61f5535963f06aa1d992af5 + HMSBroadcastExtensionSDK: 9a8888c524db5582437d06e6087bc4be6d323412 + HMSSDK: 7a8f5503eedffc024b959988a5bd97427c5a4ede + hmssdk_flutter: 37fc22fa8f420fe4d3eeca4a14170d93985c1ef0 HMSWebRTC: d3a9b2866e4a36a1d3834728a548a4a46309bb86 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 @@ -340,7 +340,7 @@ SPEC CHECKSUMS: permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e - SDWebImage: af5bbffef2cde09f148d826f9733dcde1a9414cd + SDWebImage: e5cc87bf736e60f49592f307bdf9e157189298a3 shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index f26d7131f..fb780911d 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.7 + 1.4.18 CFBundleSignature ???? CFBundleURLTypes @@ -46,7 +46,7 @@ CFBundleVersion - 142 + 153 ITSAppUsesNonExemptEncryption LSApplicationCategoryType diff --git a/example/lib/common/util/utility_components.dart b/example/lib/common/util/utility_components.dart index 1fd967ac3..c7edbd04c 100644 --- a/example/lib/common/util/utility_components.dart +++ b/example/lib/common/util/utility_components.dart @@ -855,60 +855,68 @@ class UtilityComponents { static Widget showReconnectingDialog(BuildContext context, {String alertMessage = "Leave Room"}) { - return WillPopScope( - onWillPop: () async => false, - child: AlertDialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - insetPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 10), - backgroundColor: themeBottomSheetColor, - title: Text( - "Reconnecting...", - style: GoogleFonts.inter( - color: Colors.red.shade300, - fontSize: 16, - fontWeight: FontWeight.w600), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ + return Container( + height: MediaQuery.of(context).size.height, + color: Colors.black.withOpacity(0.5), + child: Center( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 10), + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(12)), + color: themeBottomSheetColor, + boxShadow: [ + BoxShadow( + color: Colors.black, + offset: Offset(0.0, 1.0), + blurRadius: 6.0, + ), + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Text( + "Reconnecting...", + style: GoogleFonts.inter( + color: Colors.red.shade300, + fontSize: 16, + fontWeight: FontWeight.w600), + ), + SizedBox( + height: 15, + ), LinearProgressIndicator( color: hmsdefaultColor, ), SizedBox( - height: 10, + height: 15, ), Text('Oops, No internet Connection.\nReconnecting...', style: GoogleFonts.inter( color: themeDefaultColor, fontSize: 14, fontWeight: FontWeight.w400)), - ], + SizedBox( + height: 15, + ), + ElevatedButton( + style: ButtonStyle( + shadowColor: MaterialStateProperty.all(themeSurfaceColor), + backgroundColor: MaterialStateProperty.all(hmsdefaultColor), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + side: BorderSide(width: 1, color: hmsdefaultColor), + borderRadius: BorderRadius.circular(8.0), + ))), + child: Text( + alertMessage, + style: GoogleFonts.inter(), + ), + onPressed: () { + context.read().leave(); + Navigator.of(context).popUntil((route) => route.isFirst); + }) + ]), ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - style: ButtonStyle( - shadowColor: MaterialStateProperty.all(themeSurfaceColor), - backgroundColor: - MaterialStateProperty.all(hmsdefaultColor), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - side: BorderSide(width: 1, color: hmsdefaultColor), - borderRadius: BorderRadius.circular(8.0), - ))), - child: Text( - alertMessage, - style: GoogleFonts.inter(), - ), - onPressed: () { - context.read().leave(); - Navigator.of(context).popUntil((route) => route.isFirst); - }), - ], - ) - ], ), ); } diff --git a/example/lib/data_store/meeting_store.dart b/example/lib/data_store/meeting_store.dart index bca6c903c..20392774b 100644 --- a/example/lib/data_store/meeting_store.dart +++ b/example/lib/data_store/meeting_store.dart @@ -150,6 +150,8 @@ class MeetingStore extends ChangeNotifier bool retryHLS = true; + String? sessionMetadata; + Future join(String user, String roomUrl) async { List? token = await RoomService().getToken(user: user, room: roomUrl); @@ -197,7 +199,7 @@ class MeetingStore extends ChangeNotifier Future switchCamera() async { if (isVideoOn) { - await _hmsSDKInteractor.switchCamera(); + await _hmsSDKInteractor.switchCamera(hmsActionResultListener: this); } } @@ -590,10 +592,19 @@ class MeetingStore extends ChangeNotifier @override void onMessage({required HMSMessage message}) { - log("onMessage-> sender: ${message.sender} message: ${message.message} time: ${message.time}"); - addMessage(message); - isNewMessageReceived = true; - notifyListeners(); + log("onMessage-> sender: ${message.sender} message: ${message.message} time: ${message.time}, type: ${message.type}"); + switch (message.type) { + case "chat": + addMessage(message); + isNewMessageReceived = true; + notifyListeners(); + break; + case "metadata": + getSessionMetadata(); + break; + default: + break; + } } @override @@ -648,12 +659,14 @@ class MeetingStore extends ChangeNotifier void onReconnecting() { reconnected = false; reconnecting = true; + notifyListeners(); } @override void onReconnected() { reconnecting = false; reconnected = true; + notifyListeners(); } @override @@ -1179,35 +1192,21 @@ class MeetingStore extends ChangeNotifier 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); + isPlayerRunningIos(); } Future isPlayerRunningIos() async { bool isPlaying = await audioFilePlayerNode.isPlaying(); + isAudioShareStarted = isPlaying; return isPlaying; } void stopAudioIos() { audioFilePlayerNode.stop(); + isPlayerRunningIos(); } void setAudioPlayerVolume(double volume) { @@ -1215,6 +1214,16 @@ class MeetingStore extends ChangeNotifier audioPlayerVolume = volume; } + void setSessionMetadata(String metadata) { + _hmsSDKInteractor.setSessionMetadata( + metadata: metadata, hmsActionResultListener: this); + } + + void getSessionMetadata() async { + sessionMetadata = await _hmsSDKInteractor.getSessionMetadata(); + notifyListeners(); + } + //Get onSuccess or onException callbacks for HMSActionResultListenerMethod @override @@ -1289,8 +1298,10 @@ class MeetingStore extends ChangeNotifier recipientPeer: null, recipientRoles: null, hmsMessageRecipientType: HMSMessageRecipientType.BROADCAST)); - addMessage(message); - notifyListeners(); + if (arguments['type'] != "metadata") { + addMessage(message); + notifyListeners(); + } break; case HMSActionResultListenerMethod.sendGroupMessage: var message = HMSMessage( @@ -1348,8 +1359,15 @@ class MeetingStore extends ChangeNotifier isAudioShareStarted = false; notifyListeners(); break; - case HMSActionResultListenerMethod.setTrackSettings: - // TODO: Handle this case. + case HMSActionResultListenerMethod.setSessionMetadata: + _hmsSDKInteractor.sendBroadcastMessage("refresh", this, + type: "metadata"); + Utilities.showToast("Session Metadata changed"); + sessionMetadata = arguments!["session_metadata"]; + notifyListeners(); + break; + case HMSActionResultListenerMethod.switchCamera: + Utilities.showToast("Camera switched successfully"); break; } } @@ -1369,7 +1387,6 @@ class MeetingStore extends ChangeNotifier case HMSActionResultListenerMethod.changeTrackState: break; case HMSActionResultListenerMethod.changeMetadata: - // TODO: Handle this case. break; case HMSActionResultListenerMethod.endRoom: break; @@ -1418,8 +1435,10 @@ class MeetingStore extends ChangeNotifier break; case HMSActionResultListenerMethod.stopAudioShare: break; - case HMSActionResultListenerMethod.setTrackSettings: - // TODO: Handle this case. + case HMSActionResultListenerMethod.setSessionMetadata: + break; + case HMSActionResultListenerMethod.switchCamera: + Utilities.showToast("Camera switching failed"); break; } notifyListeners(); diff --git a/example/lib/hls-streaming/bottom_sheets/hls_message.dart b/example/lib/hls-streaming/bottom_sheets/hls_message.dart index b17d92874..563294a2f 100644 --- a/example/lib/hls-streaming/bottom_sheets/hls_message.dart +++ b/example/lib/hls-streaming/bottom_sheets/hls_message.dart @@ -257,6 +257,63 @@ class _HLSMessageState extends State { meetingStore.isMessageInfoShown, builder: (context, infoDialog, _) { if (infoDialog) + return Column( + children: [ + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: themeSurfaceColor), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + SvgPicture.asset( + "assets/icons/info.svg"), + SizedBox(width: 18.5), + SizedBox( + width: MediaQuery.of(context) + .size + .width * + 0.66, + child: Text( + "Messages can only be seen by people in the call and are deleted when the call ends.", + style: GoogleFonts.inter( + fontWeight: FontWeight.w400, + color: themeSubHeadingColor, + letterSpacing: 0.4, + height: 16 / 12, + fontSize: 12), + ), + ), + ], + ), + GestureDetector( + onTap: () { + context + .read() + .setMessageInfoFalse(); + }, + child: SvgPicture.asset( + "assets/icons/close.svg")) + ], + ), + ), + SizedBox( + height: 15, + ), + ], + ); + else + return SizedBox(); + }), + Selector( + selector: (_, meetingStore) => + meetingStore.sessionMetadata, + builder: (context, sessionMetadata, _) { + if (sessionMetadata != null && sessionMetadata != "") return Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( @@ -267,13 +324,13 @@ class _HLSMessageState extends State { children: [ Row( children: [ - SvgPicture.asset("assets/icons/info.svg"), + SvgPicture.asset("assets/icons/pin.svg"), SizedBox(width: 18.5), SizedBox( width: MediaQuery.of(context).size.width * 0.66, child: Text( - "Messages can only be seen by people in the call and are deleted when the call ends.", + sessionMetadata, style: GoogleFonts.inter( fontWeight: FontWeight.w400, color: themeSubHeadingColor, @@ -288,7 +345,7 @@ class _HLSMessageState extends State { onTap: () { context .read() - .setMessageInfoFalse(); + .setSessionMetadata(""); }, child: SvgPicture.asset( "assets/icons/close.svg")) @@ -321,7 +378,7 @@ class _HLSMessageState extends State { (index) => Container( padding: EdgeInsets.fromLTRB( (data.item1[index].sender?.isLocal ?? false) - ? 24.0 + ? 50.0 : 8.0, 10, (data.item1[index].sender?.isLocal ?? false) diff --git a/example/lib/hls-streaming/bottom_sheets/hls_more_settings.dart b/example/lib/hls-streaming/bottom_sheets/hls_more_settings.dart index 2d409129f..69abb44da 100644 --- a/example/lib/hls-streaming/bottom_sheets/hls_more_settings.dart +++ b/example/lib/hls-streaming/bottom_sheets/hls_more_settings.dart @@ -233,7 +233,9 @@ class _HLSMoreSettingsState extends State { semanticsLabel: "fl_brb_list_tile", style: GoogleFonts.inter( fontSize: 14, - color: themeDefaultColor, + color: _meetingStore.isBRB + ? errorColor + : themeDefaultColor, letterSpacing: 0.25, fontWeight: FontWeight.w600), ), diff --git a/example/lib/hls-streaming/hls_broadcaster_page.dart b/example/lib/hls-streaming/hls_broadcaster_page.dart index b7512c94b..e65547d8f 100644 --- a/example/lib/hls-streaming/hls_broadcaster_page.dart +++ b/example/lib/hls-streaming/hls_broadcaster_page.dart @@ -1,4 +1,3 @@ -import 'package:connectivity_checker/connectivity_checker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -141,424 +140,862 @@ class _HLSBroadcasterPageState extends State { Widget build(BuildContext context) { bool isPortraitMode = MediaQuery.of(context).orientation == Orientation.portrait; - return ConnectivityAppWrapper( - app: WillPopScope( - onWillPop: () async { - bool ans = await UtilityComponents.onBackPressed(context) ?? false; - return ans; - }, - child: ConnectivityWidgetWrapper( - disableInteraction: true, - offlineWidget: UtilityComponents.showReconnectingDialog(context), - child: Selector>( - selector: (_, meetingStore) => - Tuple2(meetingStore.isRoomEnded, meetingStore.hmsException), - builder: (_, data, __) { - if (data.item2 != null) { - if (data.item2?.code?.errorCode == 1003 || - data.item2?.code?.errorCode == 2000 || - data.item2?.code?.errorCode == 4005) { - WidgetsBinding.instance.addPostFrameCallback((_) { - UtilityComponents.showErrorDialog( - context: context, - errorMessage: - "Error Code: ${data.item2!.code?.errorCode ?? ""} ${data.item2!.description}", - errorTitle: data.item2!.message ?? "", - actionMessage: "Leave Room", - action: () { - Navigator.of(context) - .popUntil((route) => route.isFirst); - }); - }); - } else { - Utilities.showToast( - "Error : ${data.item2!.code?.errorCode ?? ""} ${data.item2!.description} ${data.item2!.message}", - time: 5); - } - context.read().hmsException = null; - } - if (data.item1) { - WidgetsBinding.instance.addPostFrameCallback((_) { - Utilities.showToast( - context.read().description); - Navigator.of(context).popUntil((route) => route.isFirst); - }); - } - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Stack( - children: [ - Selector< - MeetingStore, - Tuple6, bool, int, int, - MeetingMode, PeerTrackNode?>>( - selector: (_, meetingStore) => Tuple6( - meetingStore.peerTracks, - meetingStore.isHLSLink, - meetingStore.peerTracks.length, - meetingStore.screenShareCount, - meetingStore.meetingMode, - meetingStore.peerTracks.length > 0 - ? meetingStore.peerTracks[ - meetingStore.screenShareCount] - : null), - builder: (_, data, __) { - if (data.item2) { - return Selector( - selector: (_, meetingStore) => - meetingStore.hasHlsStarted, - builder: (_, hasHlsStarted, __) { - return hasHlsStarted - ? Container( - height: MediaQuery.of(context) - .size - .height * - 0.735, - child: Center( - child: HLSPlayer( - streamUrl: context - .read() - .streamUrl), - ), - ) - : Container( - height: MediaQuery.of(context) - .size - .height * - 0.735, - child: Center( - child: Column( - mainAxisAlignment: - MainAxisAlignment.center, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Padding( - padding: - const EdgeInsets.only( - bottom: 8.0), - child: Text( - "Waiting for HLS to start...", - style: - GoogleFonts.inter( - color: - iconColor, - fontSize: 20), - ), - ), - ], + return WillPopScope( + onWillPop: () async { + bool ans = await UtilityComponents.onBackPressed(context) ?? false; + return ans; + }, + child: Selector>( + selector: (_, meetingStore) => + Tuple2(meetingStore.isRoomEnded, meetingStore.hmsException), + builder: (_, data, __) { + if (data.item2 != null) { + if (data.item2?.code?.errorCode == 1003 || + data.item2?.code?.errorCode == 2000 || + data.item2?.code?.errorCode == 4005) { + WidgetsBinding.instance.addPostFrameCallback((_) { + UtilityComponents.showErrorDialog( + context: context, + errorMessage: + "Error Code: ${data.item2!.code?.errorCode ?? ""} ${data.item2!.description}", + errorTitle: data.item2!.message ?? "", + actionMessage: "Leave Room", + action: () { + Navigator.of(context) + .popUntil((route) => route.isFirst); + }); + }); + } else { + Utilities.showToast( + "Error : ${data.item2!.code?.errorCode ?? ""} ${data.item2!.description} ${data.item2!.message}", + time: 5); + } + context.read().hmsException = null; + } + if (data.item1) { + WidgetsBinding.instance.addPostFrameCallback((_) { + Utilities.showToast(context.read().description); + Navigator.of(context).popUntil((route) => route.isFirst); + }); + } + return Scaffold( + resizeToAvoidBottomInset: false, + body: SafeArea( + child: Stack( + children: [ + Selector< + MeetingStore, + Tuple6, bool, int, int, + MeetingMode, PeerTrackNode?>>( + selector: (_, meetingStore) => Tuple6( + meetingStore.peerTracks, + meetingStore.isHLSLink, + meetingStore.peerTracks.length, + meetingStore.screenShareCount, + meetingStore.meetingMode, + meetingStore.peerTracks.length > 0 + ? meetingStore + .peerTracks[meetingStore.screenShareCount] + : null), + builder: (_, data, __) { + if (data.item2) { + return Selector( + selector: (_, meetingStore) => + meetingStore.hasHlsStarted, + builder: (_, hasHlsStarted, __) { + return hasHlsStarted + ? Container( + height: MediaQuery.of(context) + .size + .height * + 0.735, + child: Center( + child: HLSPlayer( + streamUrl: context + .read() + .streamUrl), + ), + ) + : Container( + height: MediaQuery.of(context) + .size + .height * + 0.735, + child: Center( + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Padding( + padding: + const EdgeInsets.only( + bottom: 8.0), + child: Text( + "Waiting for HLS to start...", + style: GoogleFonts.inter( + color: iconColor, + fontSize: 20), + ), ), - ), - ); - }); - } - if (data.item3 == 0) { - return Container( - height: MediaQuery.of(context).size.height * - 0.735, - child: Center( - child: CircularProgressIndicator( - strokeWidth: 2, - )), - ); - } - return Selector>( - selector: (_, meetingStore) => Tuple2( - meetingStore.meetingMode, - meetingStore.localPeer), - builder: (_, modeData, __) { - Size size = Size( - MediaQuery.of(context).size.width, - MediaQuery.of(context).size.height - - (widget.isStreamingLink - ? 163 - : 122) - - MediaQuery.of(context) - .padding - .bottom - - MediaQuery.of(context).padding.top); - return Positioned( - top: 55, - left: 0, - right: 0, - bottom: - widget.isStreamingLink ? 108 : 68, - child: Container( - child: ((modeData.item1 == - MeetingMode.Video) && - (data.item3 == 2) && - modeData.item2 != null) - ? OneToOneMode( - bottomMargin: - widget.isStreamingLink - ? 272 - : 235, + ], + ), + ), + ); + }); + } + if (data.item3 == 0) { + return Container( + height: + MediaQuery.of(context).size.height * 0.735, + child: Center( + child: CircularProgressIndicator( + strokeWidth: 2, + )), + ); + } + return Selector>( + selector: (_, meetingStore) => Tuple3( + meetingStore.meetingMode, + meetingStore.localPeer, + meetingStore.peers.length), + builder: (_, modeData, __) { + Size size = Size( + MediaQuery.of(context).size.width, + MediaQuery.of(context).size.height - + (widget.isStreamingLink ? 163 : 122) - + MediaQuery.of(context).padding.bottom - + MediaQuery.of(context).padding.top); + return Positioned( + top: 55, + left: 0, + right: 0, + bottom: widget.isStreamingLink ? 108 : 68, + child: Container( + child: ((modeData.item1 == + MeetingMode.Video) && + (data.item3 == 2) && + (modeData.item2 != null) && + (modeData.item3 == 2)) + ? OneToOneMode( + bottomMargin: widget.isStreamingLink + ? 272 + : 235, + peerTracks: data.item1, + screenShareCount: data.item4, + context: context, + size: size) + : (modeData.item1 == + MeetingMode.Hero) + ? gridHeroView( peerTracks: data.item1, + itemCount: data.item3, screenShareCount: data.item4, context: context, + isPortrait: isPortraitMode, size: size) : (modeData.item1 == - MeetingMode.Hero) - ? gridHeroView( - peerTracks: data.item1, - itemCount: data.item3, - screenShareCount: - data.item4, + MeetingMode.Audio) + ? gridAudioView( + peerTracks: data.item1 + .sublist( + data.item4), + itemCount: data.item1 + .sublist(data.item4) + .length, context: context, isPortrait: isPortraitMode, size: size) - : (modeData.item1 == - MeetingMode.Audio) - ? gridAudioView( - peerTracks: data - .item1 - .sublist( - data.item4), - itemCount: data.item1 - .sublist( - data.item4) - .length, + : (data.item5 == + MeetingMode.Single) + ? fullScreenView( + peerTracks: + data.item1, + itemCount: + data.item3, + screenShareCount: + data.item4, context: context, isPortrait: isPortraitMode, size: size) - : (data.item5 == - MeetingMode - .Single) - ? fullScreenView( - peerTracks: data.item1, - itemCount: data.item3, - screenShareCount: data.item4, - context: context, - isPortrait: isPortraitMode, - size: size) - : hlsGridView(peerTracks: data.item1, itemCount: data.item3, screenShareCount: data.item4, context: context, isPortrait: true, size: size))); - }); - }), - Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 15, right: 15, top: 5, bottom: 2), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, + : hlsGridView( + peerTracks: + data.item1, + itemCount: + data.item3, + screenShareCount: + data.item4, + context: context, + isPortrait: true, + size: size))); + }); + }), + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 15, right: 15, top: 5, bottom: 2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( children: [ - Row( - children: [ - Selector( - selector: (_, meetingStore) => - meetingStore.hasHlsStarted, - builder: (_, hasHlsStarted, __) { - return hasHlsStarted - ? EmbeddedButton( - onTap: () async => {}, - width: 40, - height: 40, - offColor: Color(0xffCC525F), - onColor: Color(0xffCC525F), - disabledBorderColor: - Color(0xffCC525F), - isActive: false, - child: _showPopupMenuButton( - isHLSRunning: - hasHlsStarted)) - : EmbeddedButton( - onTap: () async => { - await UtilityComponents - .onBackPressed( - context) - }, - width: 40, - height: 40, - offColor: Color(0xffCC525F), - onColor: Color(0xffCC525F), - disabledBorderColor: - Color(0xffCC525F), - isActive: false, + Selector( + selector: (_, meetingStore) => + meetingStore.hasHlsStarted, + builder: (_, hasHlsStarted, __) { + return hasHlsStarted + ? EmbeddedButton( + onTap: () async => {}, + width: 40, + height: 40, + offColor: Color(0xffCC525F), + onColor: Color(0xffCC525F), + disabledBorderColor: + Color(0xffCC525F), + isActive: false, + child: _showPopupMenuButton( + isHLSRunning: + hasHlsStarted)) + : EmbeddedButton( + onTap: () async => { + await UtilityComponents + .onBackPressed(context) + }, + width: 40, + height: 40, + offColor: Color(0xffCC525F), + onColor: Color(0xffCC525F), + disabledBorderColor: + Color(0xffCC525F), + isActive: false, + child: SvgPicture.asset( + "assets/icons/leave_hls.svg", + color: Colors.white, + fit: BoxFit.scaleDown, + semanticsLabel: + "leave_button", + ), + ); + }), + SizedBox( + width: 10, + ), + Selector( + selector: (_, meetingStore) => + meetingStore.hasHlsStarted, + builder: (_, hasHlsStarted, __) { + if (hasHlsStarted) + return Column( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + children: [ + Padding( + padding: + const EdgeInsets.only( + right: 5.0), child: SvgPicture.asset( - "assets/icons/leave_hls.svg", - color: Colors.white, + "assets/icons/live_stream.svg", + color: errorColor, fit: BoxFit.scaleDown, - semanticsLabel: - "leave_button", ), - ); - }), - SizedBox( - width: 10, - ), - Selector( - selector: (_, meetingStore) => - meetingStore.hasHlsStarted, - builder: (_, hasHlsStarted, __) { - if (hasHlsStarted) - return Column( + ), + Text( + "Live", + semanticsLabel: + "fl_live_stream_running", + style: GoogleFonts.inter( + fontSize: 16, + color: + themeDefaultColor, + letterSpacing: 0.5, + fontWeight: + FontWeight.w600), + ), + ], + ), + Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, - crossAxisAlignment: - CrossAxisAlignment.start, children: [ Row( children: [ - Padding( - padding: - const EdgeInsets - .only( - right: 5.0), - child: SvgPicture.asset( - "assets/icons/live_stream.svg", - color: errorColor, - fit: BoxFit.scaleDown, - ), + SvgPicture.asset( + "assets/icons/clock.svg", + color: + themeSubHeadingColor, + fit: BoxFit.scaleDown, ), - Text( - "Live", - semanticsLabel: - "fl_live_stream_running", - style: GoogleFonts.inter( - fontSize: 16, - color: - themeDefaultColor, - letterSpacing: 0.5, - fontWeight: - FontWeight - .w600), + SizedBox( + width: 6, ), + Selector( + selector: + (_, meetingStore) => + meetingStore + .hmsRoom, + builder: + (_, hmsRoom, __) { + if (hmsRoom != + null && + hmsRoom.hmshlsStreamingState != + null && + hmsRoom + .hmshlsStreamingState! + .variants + .length != + 0 && + hmsRoom + .hmshlsStreamingState! + .variants[ + 0]! + .startedAt != + null) { + return StreamTimer( + startedAt: hmsRoom + .hmshlsStreamingState! + .variants[ + 0]! + .startedAt!); + } + return HLSSubtitleText( + text: "00:00", + textColor: + themeSubHeadingColor, + ); + }), ], ), + HLSSubtitleText( + text: " | ", + textColor: dividerColor, + ), Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, children: [ - Row( - children: [ - SvgPicture.asset( - "assets/icons/clock.svg", - color: - themeSubHeadingColor, - fit: BoxFit - .scaleDown, - ), - SizedBox( - width: 6, - ), - Selector( - selector: (_, - meetingStore) => - meetingStore - .hmsRoom, - builder: (_, - hmsRoom, __) { - if (hmsRoom != - null && - hmsRoom.hmshlsStreamingState != - null && - hmsRoom - .hmshlsStreamingState! - .variants - .length != - 0 && - hmsRoom - .hmshlsStreamingState! - .variants[0]! - .startedAt != - null) { - return StreamTimer( - startedAt: hmsRoom - .hmshlsStreamingState! - .variants[ - 0]! - .startedAt!); - } - return HLSSubtitleText( - text: "00:00", - textColor: - themeSubHeadingColor, - ); - }), - ], + SvgPicture.asset( + "assets/icons/watching.svg", + color: + themeSubHeadingColor, + fit: BoxFit.scaleDown, ), - HLSSubtitleText( - text: " | ", - textColor: dividerColor, + SizedBox( + width: 6, ), - Row( - children: [ - SvgPicture.asset( - "assets/icons/watching.svg", - color: - themeSubHeadingColor, - fit: BoxFit - .scaleDown, - ), - SizedBox( - width: 6, - ), - Selector( - selector: (_, - meetingStore) => + Selector( + selector: + (_, meetingStore) => meetingStore .peers .length, - builder: (_, - length, __) { - return HLSSubtitleText( - text: length - .toString(), - textColor: - themeSubHeadingColor); - }) - ], - ) + builder: + (_, length, __) { + return HLSSubtitleText( + text: length + .toString(), + textColor: + themeSubHeadingColor); + }) ], ) ], - ); - return SizedBox(); - }) - ], + ) + ], + ); + return SizedBox(); + }) + ], + ), + Row( + children: [ + Selector( + selector: (_, meetingStore) => + meetingStore.isRaisedHand, + builder: (_, handRaised, __) { + return EmbeddedButton( + onTap: () => { + context + .read() + .changeMetadata() + }, + width: 40, + height: 40, + disabledBorderColor: borderColor, + offColor: themeScreenBackgroundColor, + onColor: themeHintColor, + isActive: handRaised, + child: SvgPicture.asset( + "assets/icons/hand_outline.svg", + color: themeDefaultColor, + fit: BoxFit.scaleDown, + semanticsLabel: "hand_raise_button", + ), + ); + }), + SizedBox( + width: 10, ), - Row( - children: [ - Selector( - selector: (_, meetingStore) => - meetingStore.isRaisedHand, - builder: (_, handRaised, __) { - return EmbeddedButton( - onTap: () => { - context - .read() - .changeMetadata() - }, - width: 40, - height: 40, - disabledBorderColor: borderColor, - offColor: - themeScreenBackgroundColor, - onColor: themeHintColor, - isActive: handRaised, - child: SvgPicture.asset( - "assets/icons/hand_outline.svg", - color: themeDefaultColor, - fit: BoxFit.scaleDown, - semanticsLabel: - "hand_raise_button", + EmbeddedButton( + onTap: () => { + showModalBottomSheet( + isScrollControlled: true, + backgroundColor: themeBottomSheetColor, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(20), + ), + context: context, + builder: (ctx) => + ChangeNotifierProvider.value( + value: context + .read(), + child: HLSParticipantSheet()), + ) + }, + width: 40, + height: 40, + offColor: themeScreenBackgroundColor, + onColor: themeScreenBackgroundColor, + isActive: true, + child: SvgPicture.asset( + "assets/icons/participants.svg", + color: themeDefaultColor, + fit: BoxFit.scaleDown, + semanticsLabel: "participants_button", + ), + ), + SizedBox( + width: 10, + ), + Selector( + selector: (_, meetingStore) => + meetingStore.isNewMessageReceived, + builder: (_, isNewMessageReceived, __) { + return EmbeddedButton( + onTap: () => { + context + .read() + .getSessionMetadata(), + context + .read() + .setNewMessageFalse(), + showModalBottomSheet( + isScrollControlled: true, + backgroundColor: + themeBottomSheetColor, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(20), ), + context: context, + builder: (ctx) => + ChangeNotifierProvider.value( + value: context + .read(), + child: HLSMessage()), + ) + }, + width: 40, + height: 40, + offColor: themeHintColor, + onColor: themeScreenBackgroundColor, + isActive: true, + child: SvgPicture.asset( + isNewMessageReceived + ? "assets/icons/message_badge_on.svg" + : "assets/icons/message_badge_off.svg", + fit: BoxFit.scaleDown, + semanticsLabel: "chat_button", + ), + ); + }) + ], + ) + ], + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Column( + children: [ + if (Provider.of(context) + .localPeer != + null && + !Provider.of(context) + .localPeer! + .role + .name + .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: themeHMSBorderColor, + onColor: + themeScreenBackgroundColor, + isActive: isMicOn, + child: SvgPicture.asset( + isMicOn + ? "assets/icons/mic_state_on.svg" + : "assets/icons/mic_state_off.svg", + color: themeDefaultColor, + 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: themeHMSBorderColor, + onColor: + themeScreenBackgroundColor, + isActive: isSpeakerOn, + child: SvgPicture.asset( + isSpeakerOn + ? "assets/icons/speaker_state_on.svg" + : "assets/icons/speaker_state_off.svg", + color: themeDefaultColor, + 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: themeHMSBorderColor, + onColor: + themeScreenBackgroundColor, + isActive: data.item1, + child: SvgPicture.asset( + data.item1 + ? "assets/icons/cam_state_on.svg" + : "assets/icons/cam_state_off.svg", + color: themeDefaultColor, + 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: + themeScreenBackgroundColor, + onColor: themeHMSBorderColor, + isActive: isStatsVisible, + child: SvgPicture.asset( + "assets/icons/stats.svg", + fit: BoxFit.scaleDown, + semanticsLabel: + "stats_button"), + ); + }), + if (Provider.of(context) + .localPeer != + null && + widget.isStreamingLink) + Selector>( + selector: (_, meetingStore) => Tuple2( + meetingStore.hasHlsStarted, + meetingStore.isHLSLoading), + builder: (_, data, __) { + if (data.item1) { + return Column( + children: [ + SizedBox( + height: 10, + ), + InkWell( + onTap: () { + UtilityComponents.onEndStream( + context: context, + title: + 'End live stream for all?', + content: + "Your live stream will end and stream viewers will go offline immediately in this room. You can’t undo this action.", + ignoreText: + "Don't End ", + actionText: + 'End Stream'); + }, + child: CircleAvatar( + radius: 40, + backgroundColor: + errorColor, + child: SvgPicture.asset( + "assets/icons/end.svg", + color: + themeDefaultColor, + height: 36, + semanticsLabel: + "hls_end_button"), + ), + ), + SizedBox( + height: 5, + ), + Text( + "END STREAM", + style: GoogleFonts.inter( + letterSpacing: 1.5, + fontSize: 10, + height: 1.6, + fontWeight: + FontWeight.w600), + ) + ], + ); + } else if (data.item2) { + return Column( + children: [ + SizedBox( + height: 10, + ), + InkWell( + onTap: () {}, + child: CircleAvatar( + radius: 40, + backgroundColor: + themeScreenBackgroundColor, + child: + CircularProgressIndicator( + semanticsLabel: + "hls_loader", + strokeWidth: 2, + color: + hmsdefaultColor, + )), + ), + SizedBox( + height: 5, + ), + Text( + "STARTING HLS", + style: GoogleFonts.inter( + letterSpacing: 1.5, + fontSize: 10, + height: 1.6, + fontWeight: + FontWeight.w600), + ) + ], + ); + } + return Column( + children: [ + SizedBox( + height: 10, + ), + InkWell( + onTap: () { + showModalBottomSheet( + isScrollControlled: true, + backgroundColor: + themeBottomSheetColor, + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius + .circular(20), + ), + context: context, + builder: (ctx) => + ChangeNotifierProvider.value( + value: context.read< + MeetingStore>(), + child: + HLSStartBottomSheet()), + ); + }, + child: CircleAvatar( + radius: 40, + backgroundColor: + hmsdefaultColor, + child: SvgPicture.asset( + "assets/icons/live.svg", + color: + themeDefaultColor, + fit: BoxFit.scaleDown, + semanticsLabel: + "start_hls_button"), + ), + ), + SizedBox( + height: 5, + ), + Text( + "GO LIVE", + style: GoogleFonts.inter( + letterSpacing: 1.5, + fontSize: 10, + height: 1.6, + fontWeight: + FontWeight.w600), + ) + ], ); }), - SizedBox( - width: 10, - ), + 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< + MeetingStore>( + context, + listen: false); + if (meetingStore + .isScreenShareOn) { + meetingStore + .stopScreenShare(); + } else { + meetingStore + .startScreenShare(); + } + }, + width: 40, + height: 40, + disabledBorderColor: + borderColor, + offColor: + themeScreenBackgroundColor, + onColor: borderColor, + isActive: data, + child: SvgPicture.asset( + "assets/icons/screen_share.svg", + color: themeDefaultColor, + 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: + themeScreenBackgroundColor, + onColor: borderColor, + isActive: isBRB, + child: SvgPicture.asset( + "assets/icons/brb.svg", + fit: BoxFit.scaleDown, + semanticsLabel: + "brb_button"), + ); + }), + if (Provider.of(context) + .localPeer != + null) EmbeddedButton( onTap: () => { showModalBottomSheet( @@ -574,583 +1011,104 @@ class _HLSBroadcasterPageState extends State { ChangeNotifierProvider.value( value: context .read(), - child: - HLSParticipantSheet()), + child: HLSMoreSettings()), ) }, width: 40, height: 40, - offColor: themeScreenBackgroundColor, + offColor: themeHintColor, onColor: themeScreenBackgroundColor, isActive: true, child: SvgPicture.asset( - "assets/icons/participants.svg", - color: themeDefaultColor, - fit: BoxFit.scaleDown, - semanticsLabel: "participants_button", - ), - ), - SizedBox( - width: 10, + "assets/icons/more.svg", + color: themeDefaultColor, + fit: BoxFit.scaleDown, + semanticsLabel: "more_button"), ), - Selector( - selector: (_, meetingStore) => - meetingStore.isNewMessageReceived, - builder: - (_, isNewMessageReceived, __) { - return EmbeddedButton( - onTap: () => { - context - .read() - .setNewMessageFalse(), - showModalBottomSheet( - isScrollControlled: true, - backgroundColor: - themeBottomSheetColor, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular( - 20), - ), - context: context, - builder: (ctx) => - ChangeNotifierProvider.value( - value: context.read< - MeetingStore>(), - child: HLSMessage()), - ) - }, - width: 40, - height: 40, - offColor: themeHintColor, - onColor: - themeScreenBackgroundColor, - isActive: true, - child: SvgPicture.asset( - isNewMessageReceived - ? "assets/icons/message_badge_on.svg" - : "assets/icons/message_badge_off.svg", - fit: BoxFit.scaleDown, - color: themeDefaultColor, - semanticsLabel: "chat_button", - ), - ); - }) - ], - ) - ], - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: Column( - children: [ - if (Provider.of(context) - .localPeer != - null && - !Provider.of(context) - .localPeer! - .role - .name - .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< - MeetingStore>() - .switchAudio() - }, - width: 40, - height: 40, - disabledBorderColor: - borderColor, - offColor: - themeHMSBorderColor, - onColor: - themeScreenBackgroundColor, - isActive: isMicOn, - child: SvgPicture.asset( - isMicOn - ? "assets/icons/mic_state_on.svg" - : "assets/icons/mic_state_off.svg", - color: - themeDefaultColor, - fit: BoxFit.scaleDown, - semanticsLabel: - "audio_mute_button", - ), - ); - }) - : Selector( - selector: (_, meetingStore) => - meetingStore.isSpeakerOn, - builder: - (_, isSpeakerOn, __) { - return EmbeddedButton( - onTap: () => { - context - .read< - MeetingStore>() - .toggleSpeaker(), - }, - width: 40, - height: 40, - disabledBorderColor: - borderColor, - offColor: - themeHMSBorderColor, - onColor: - themeScreenBackgroundColor, - isActive: isSpeakerOn, - child: SvgPicture.asset( - isSpeakerOn - ? "assets/icons/speaker_state_on.svg" - : "assets/icons/speaker_state_off.svg", - color: - themeDefaultColor, - 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: - themeHMSBorderColor, - onColor: - themeScreenBackgroundColor, - isActive: data.item1, - child: SvgPicture.asset( - data.item1 - ? "assets/icons/cam_state_on.svg" - : "assets/icons/cam_state_off.svg", - color: - themeDefaultColor, - 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: - themeScreenBackgroundColor, - onColor: - themeHMSBorderColor, - isActive: isStatsVisible, - child: SvgPicture.asset( - "assets/icons/stats.svg", - fit: BoxFit.scaleDown, - semanticsLabel: - "stats_button"), - ); - }), - if (Provider.of(context) - .localPeer != - null && - widget.isStreamingLink) - Selector>( - selector: (_, meetingStore) => - Tuple2( - meetingStore - .hasHlsStarted, - meetingStore - .isHLSLoading), - builder: (_, data, __) { - if (data.item1) { - return Column( - children: [ - SizedBox( - height: 10, - ), - InkWell( - onTap: () { - UtilityComponents.onEndStream( - context: context, - title: - 'End live stream for all?', - content: - "Your live stream will end and stream viewers will go offline immediately in this room. You can’t undo this action.", - ignoreText: - "Don't End ", - actionText: - 'End Stream'); - }, - child: CircleAvatar( - radius: 40, - backgroundColor: - errorColor, - child: SvgPicture.asset( - "assets/icons/end.svg", - color: - themeDefaultColor, - height: 36, - semanticsLabel: - "hls_end_button"), - ), - ), - SizedBox( - height: 5, - ), - Text( - "END STREAM", - style: - GoogleFonts.inter( - letterSpacing: - 1.5, - fontSize: 10, - height: 1.6, - fontWeight: - FontWeight - .w600), - ) - ], - ); - } else if (data.item2) { - return Column( - children: [ - SizedBox( - height: 10, - ), - InkWell( - onTap: () {}, - child: CircleAvatar( - radius: 40, - backgroundColor: - themeScreenBackgroundColor, - child: - CircularProgressIndicator( - semanticsLabel: - "hls_loader", - strokeWidth: 2, - color: - hmsdefaultColor, - )), - ), - SizedBox( - height: 5, - ), - Text( - "STARTING HLS", - style: - GoogleFonts.inter( - letterSpacing: - 1.5, - fontSize: 10, - height: 1.6, - fontWeight: - FontWeight - .w600), - ) - ], - ); - } - return Column( - children: [ - SizedBox( - height: 10, - ), - InkWell( - onTap: () { - showModalBottomSheet( - isScrollControlled: - true, - backgroundColor: - themeBottomSheetColor, - shape: - RoundedRectangleBorder( - borderRadius: - BorderRadius - .circular( - 20), - ), - context: context, - builder: (ctx) => - ChangeNotifierProvider.value( - value: context - .read< - MeetingStore>(), - child: - HLSStartBottomSheet()), - ); - }, - child: CircleAvatar( - radius: 40, - backgroundColor: - hmsdefaultColor, - child: SvgPicture.asset( - "assets/icons/live.svg", - color: - themeDefaultColor, - fit: BoxFit - .scaleDown, - semanticsLabel: - "start_hls_button"), - ), - ), - SizedBox( - height: 5, - ), - Text( - "GO LIVE", - style: GoogleFonts.inter( - letterSpacing: 1.5, - fontSize: 10, - height: 1.6, - fontWeight: - FontWeight.w600), - ) - ], - ); - }), - 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< - MeetingStore>( - context, - listen: false); - if (meetingStore - .isScreenShareOn) { - meetingStore - .stopScreenShare(); - } else { - meetingStore - .startScreenShare(); - } - }, - width: 40, - height: 40, - disabledBorderColor: - borderColor, - offColor: - themeScreenBackgroundColor, - onColor: borderColor, - isActive: data, - child: SvgPicture.asset( - "assets/icons/screen_share.svg", - color: - themeDefaultColor, - 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: - themeScreenBackgroundColor, - onColor: borderColor, - isActive: isBRB, - child: SvgPicture.asset( - "assets/icons/brb.svg", - fit: BoxFit.scaleDown, - semanticsLabel: - "brb_button"), - ); - }), - if (Provider.of(context) - .localPeer != - null) - EmbeddedButton( - onTap: () => { - showModalBottomSheet( - isScrollControlled: true, - backgroundColor: - themeBottomSheetColor, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(20), - ), - context: context, - builder: (ctx) => - ChangeNotifierProvider.value( - value: context.read< - MeetingStore>(), - child: - HLSMoreSettings()), - ) - }, - width: 40, - height: 40, - offColor: themeHintColor, - onColor: themeScreenBackgroundColor, - isActive: true, - child: SvgPicture.asset( - "assets/icons/more.svg", - color: themeDefaultColor, - fit: BoxFit.scaleDown, - semanticsLabel: "more_button"), - ), - ], - ), - ], - ), - ) - ], - ), - Selector( - selector: (_, meetingStore) => - meetingStore.currentRoleChangeRequest, - builder: (_, roleChangeRequest, __) { - if (roleChangeRequest != null) { - HMSRoleChangeRequest currentRequest = - roleChangeRequest; - context - .read() - .currentRoleChangeRequest = null; - WidgetsBinding.instance - .addPostFrameCallback((_) { - UtilityComponents.showRoleChangeDialog( - currentRequest, context); - }); - } - return SizedBox(); - }), - Selector( - selector: (_, meetingStore) => - meetingStore.hmsTrackChangeRequest, - builder: (_, hmsTrackChangeRequest, __) { - if (hmsTrackChangeRequest != null) { - HMSTrackChangeRequest currentRequest = - hmsTrackChangeRequest; - context - .read() - .hmsTrackChangeRequest = null; - WidgetsBinding.instance - .addPostFrameCallback((_) { - UtilityComponents.showTrackChangeDialog( - context, currentRequest); - }); - } - return SizedBox(); - }), - Selector( - selector: (_, meetingStore) => - meetingStore.showAudioDeviceChangePopup, - builder: (_, showAudioDeviceChangePopup, __) { - if (showAudioDeviceChangePopup) { - context - .read() - .showAudioDeviceChangePopup = false; - WidgetsBinding.instance - .addPostFrameCallback((_) { - showDialog( - context: context, - builder: (_) => AudioDeviceChangeDialog( - currentAudioDevice: context - .read() - .currentAudioOutputDevice!, - audioDevicesList: context - .read() - .availableAudioOutputDevices, - changeAudioDevice: (audioDevice) { - context - .read() - .switchAudioOutput( - audioDevice: audioDevice); - }, - )); - }); - } - return SizedBox(); - }), + ], + ), + ], + ), + ) ], ), - ), - ); - }), - ), - ), + Selector( + selector: (_, meetingStore) => + meetingStore.currentRoleChangeRequest, + builder: (_, roleChangeRequest, __) { + if (roleChangeRequest != null) { + HMSRoleChangeRequest currentRequest = + roleChangeRequest; + context + .read() + .currentRoleChangeRequest = null; + WidgetsBinding.instance.addPostFrameCallback((_) { + UtilityComponents.showRoleChangeDialog( + currentRequest, context); + }); + } + return SizedBox(); + }), + Selector( + selector: (_, meetingStore) => + meetingStore.hmsTrackChangeRequest, + builder: (_, hmsTrackChangeRequest, __) { + if (hmsTrackChangeRequest != null) { + HMSTrackChangeRequest currentRequest = + hmsTrackChangeRequest; + context.read().hmsTrackChangeRequest = + null; + WidgetsBinding.instance.addPostFrameCallback((_) { + UtilityComponents.showTrackChangeDialog( + context, currentRequest); + }); + } + return SizedBox(); + }), + Selector( + selector: (_, meetingStore) => + meetingStore.showAudioDeviceChangePopup, + builder: (_, showAudioDeviceChangePopup, __) { + if (showAudioDeviceChangePopup) { + context + .read() + .showAudioDeviceChangePopup = false; + WidgetsBinding.instance.addPostFrameCallback((_) { + showDialog( + context: context, + builder: (_) => AudioDeviceChangeDialog( + currentAudioDevice: context + .read() + .currentAudioOutputDevice!, + audioDevicesList: context + .read() + .availableAudioOutputDevices, + changeAudioDevice: (audioDevice) { + context + .read() + .switchAudioOutput( + audioDevice: audioDevice); + }, + )); + }); + } + return SizedBox(); + }), + Selector( + selector: (_, meetingStore) => + meetingStore.reconnecting, + builder: (_, reconnecting, __) { + if (reconnecting) { + return UtilityComponents.showReconnectingDialog( + context); + } + return SizedBox(); + }), + ], + ), + ), + ); + }), ); } } diff --git a/example/lib/hls-streaming/hls_viewer_page.dart b/example/lib/hls-streaming/hls_viewer_page.dart index d966b1c84..cd14d275e 100644 --- a/example/lib/hls-streaming/hls_viewer_page.dart +++ b/example/lib/hls-streaming/hls_viewer_page.dart @@ -1,4 +1,3 @@ -import 'package:connectivity_checker/connectivity_checker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -29,332 +28,278 @@ class HLSViewerPage extends StatefulWidget { class _HLSViewerPageState extends State { @override Widget build(BuildContext context) { - return ConnectivityAppWrapper( - app: WillPopScope( - onWillPop: () async { - bool ans = await UtilityComponents.onBackPressed(context) ?? false; - return ans; - }, - child: ConnectivityWidgetWrapper( - disableInteraction: true, - offlineWidget: UtilityComponents.showReconnectingDialog(context), - child: Selector>( - selector: (_, meetingStore) => - Tuple2(meetingStore.isRoomEnded, meetingStore.hmsException), - builder: (_, data, __) { - if (data.item2 != null && - (data.item2?.code?.errorCode == 1003 || - data.item2?.code?.errorCode == 2000 || - data.item2?.code?.errorCode == 4005)) { - WidgetsBinding.instance.addPostFrameCallback((_) { - UtilityComponents.showErrorDialog( - context: context, - errorMessage: - "Error Code: ${data.item2!.code?.errorCode ?? ""} ${data.item2!.description}", - errorTitle: data.item2!.message ?? "", - actionMessage: "Leave Room", - action: () { - Navigator.of(context) - .popUntil((route) => route.isFirst); - }); - }); - } - if (data.item1) { - WidgetsBinding.instance.addPostFrameCallback((_) { - Utilities.showToast( - context.read().description); - Navigator.of(context).popUntil((route) => route.isFirst); - }); - } - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Stack( + return WillPopScope( + onWillPop: () async { + bool ans = await UtilityComponents.onBackPressed(context) ?? false; + return ans; + }, + child: Selector>( + selector: (_, meetingStore) => + Tuple2(meetingStore.isRoomEnded, meetingStore.hmsException), + builder: (_, data, __) { + if (data.item2 != null && + (data.item2?.code?.errorCode == 1003 || + data.item2?.code?.errorCode == 2000 || + data.item2?.code?.errorCode == 4005)) { + WidgetsBinding.instance.addPostFrameCallback((_) { + UtilityComponents.showErrorDialog( + context: context, + errorMessage: + "Error Code: ${data.item2!.code?.errorCode ?? ""} ${data.item2!.description}", + errorTitle: data.item2!.message ?? "", + actionMessage: "Leave Room", + action: () { + Navigator.of(context).popUntil((route) => route.isFirst); + }); + }); + } + if (data.item1) { + WidgetsBinding.instance.addPostFrameCallback((_) { + Utilities.showToast(context.read().description); + Navigator.of(context).popUntil((route) => route.isFirst); + }); + } + return Scaffold( + resizeToAvoidBottomInset: false, + body: SafeArea( + child: Stack( + children: [ + Selector( + selector: (_, meetingStore) => + meetingStore.hasHlsStarted, + builder: (_, hasHlsStarted, __) { + return hasHlsStarted + ? Container( + height: MediaQuery.of(context).size.height, + child: Center( + child: HLSPlayer( + streamUrl: context + .read() + .streamUrl), + ), + ) + : Container( + height: MediaQuery.of(context).size.height * + 0.735, + child: Center( + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only( + bottom: 8.0), + child: HLSTitleText( + text: + "Waiting for HLS to start...", + textColor: themeDefaultColor)), + ], + ), + ), + ); + }), + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Selector( - selector: (_, meetingStore) => - meetingStore.hasHlsStarted, - builder: (_, hasHlsStarted, __) { - return hasHlsStarted - ? Container( - height: - MediaQuery.of(context).size.height, - child: Center( - child: HLSPlayer( - streamUrl: context - .read() - .streamUrl), - ), - ) - : Container( - height: - MediaQuery.of(context).size.height * - 0.735, - child: Center( - child: Column( - mainAxisAlignment: - MainAxisAlignment.center, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.only( - bottom: 8.0), - child: HLSTitleText( - text: - "Waiting for HLS to start...", - textColor: - themeDefaultColor)), - ], - ), - ), - ); - }), - Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 15, right: 15, top: 5, bottom: 2), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, + Padding( + padding: const EdgeInsets.only( + left: 15, right: 15, top: 5, bottom: 2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( children: [ - Row( - children: [ - EmbeddedButton( - onTap: () async => { - await UtilityComponents.onBackPressed( - context) - }, - disabledBorderColor: Color(0xffCC525F), - width: 40, - height: 40, - offColor: Color(0xffCC525F), - onColor: Color(0xffCC525F), - isActive: false, - child: SvgPicture.asset( - "assets/icons/leave_hls.svg", - color: Colors.white, - fit: BoxFit.scaleDown, - semanticsLabel: "leave_button", - ), - ), - SizedBox( - width: 10, - ), - Selector( - selector: (_, meetingStore) => - meetingStore.hasHlsStarted, - builder: (_, hasHlsStarted, __) { - if (hasHlsStarted) - return Column( + EmbeddedButton( + onTap: () async => { + await UtilityComponents.onBackPressed( + context) + }, + disabledBorderColor: Color(0xffCC525F), + width: 40, + height: 40, + offColor: Color(0xffCC525F), + onColor: Color(0xffCC525F), + isActive: false, + child: SvgPicture.asset( + "assets/icons/leave_hls.svg", + color: Colors.white, + fit: BoxFit.scaleDown, + semanticsLabel: "leave_button", + ), + ), + SizedBox( + width: 10, + ), + Selector( + selector: (_, meetingStore) => + meetingStore.hasHlsStarted, + builder: (_, hasHlsStarted, __) { + if (hasHlsStarted) + return Column( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + children: [ + Padding( + padding: + const EdgeInsets.only( + right: 5.0), + child: SvgPicture.asset( + "assets/icons/live_stream.svg", + color: errorColor, + fit: BoxFit.scaleDown, + ), + ), + Text( + "Live", + style: GoogleFonts.inter( + fontSize: 16, + color: + themeDefaultColor, + letterSpacing: 0.5, + fontWeight: + FontWeight.w600), + ), + ], + ), + Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, - crossAxisAlignment: - CrossAxisAlignment.start, children: [ Row( children: [ - Padding( - padding: - const EdgeInsets - .only( - right: 5.0), - child: SvgPicture.asset( - "assets/icons/live_stream.svg", - color: errorColor, - fit: BoxFit.scaleDown, - ), + SvgPicture.asset( + "assets/icons/clock.svg", + color: + themeSubHeadingColor, + fit: BoxFit.scaleDown, ), - Text( - "Live", - style: GoogleFonts.inter( - fontSize: 16, - color: - themeDefaultColor, - letterSpacing: 0.5, - fontWeight: - FontWeight - .w600), + SizedBox( + width: 6, ), + Selector( + selector: + (_, meetingStore) => + meetingStore + .hmsRoom, + builder: + (_, hmsRoom, __) { + if (hmsRoom != + null && + hmsRoom.hmshlsStreamingState != + null && + hmsRoom + .hmshlsStreamingState! + .variants + .length != + 0 && + hmsRoom + .hmshlsStreamingState! + .variants[ + 0]! + .startedAt != + null) { + return StreamTimer( + startedAt: hmsRoom + .hmshlsStreamingState! + .variants[ + 0]! + .startedAt!); + } + return HLSSubtitleText( + text: "00:00", + textColor: + themeSubHeadingColor, + ); + }), ], ), + HLSSubtitleText( + text: " | ", + textColor: dividerColor, + ), Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, children: [ - Row( - children: [ - SvgPicture.asset( - "assets/icons/clock.svg", - color: - themeSubHeadingColor, - fit: BoxFit - .scaleDown, - ), - SizedBox( - width: 6, - ), - Selector( - selector: (_, - meetingStore) => - meetingStore - .hmsRoom, - builder: (_, - hmsRoom, __) { - if (hmsRoom != - null && - hmsRoom.hmshlsStreamingState != - null && - hmsRoom - .hmshlsStreamingState! - .variants - .length != - 0 && - hmsRoom - .hmshlsStreamingState! - .variants[0]! - .startedAt != - null) { - return StreamTimer( - startedAt: hmsRoom - .hmshlsStreamingState! - .variants[ - 0]! - .startedAt!); - } - return HLSSubtitleText( - text: "00:00", - textColor: - themeSubHeadingColor, - ); - }), - ], + SvgPicture.asset( + "assets/icons/watching.svg", + color: + themeSubHeadingColor, + fit: BoxFit.scaleDown, ), - HLSSubtitleText( - text: " | ", - textColor: dividerColor, + SizedBox( + width: 6, ), - Row( - children: [ - SvgPicture.asset( - "assets/icons/watching.svg", - color: - themeSubHeadingColor, - fit: BoxFit - .scaleDown, - ), - SizedBox( - width: 6, - ), - Selector( - selector: (_, - meetingStore) => + Selector( + selector: + (_, meetingStore) => meetingStore .peers .length, - builder: (_, - length, __) { - return HLSSubtitleText( - text: length - .toString(), - textColor: - themeSubHeadingColor); - }) - ], - ) + builder: + (_, length, __) { + return HLSSubtitleText( + text: length + .toString(), + textColor: + themeSubHeadingColor); + }) ], ) ], - ); - return SizedBox(); - }) - ], + ) + ], + ); + return SizedBox(); + }) + ], + ), + Row( + children: [ + Selector( + selector: (_, meetingStore) => + meetingStore.isRaisedHand, + builder: (_, handRaised, __) { + return EmbeddedButton( + onTap: () => { + context + .read() + .changeMetadata() + }, + width: 40, + height: 40, + disabledBorderColor: borderColor, + offColor: themeScreenBackgroundColor, + onColor: themeHintColor, + isActive: handRaised, + child: SvgPicture.asset( + "assets/icons/hand_outline.svg", + color: themeDefaultColor, + fit: BoxFit.scaleDown, + semanticsLabel: "hand_raise_button", + ), + ); + }), + SizedBox( + width: 10, ), - Row( - children: [ - Selector( - selector: (_, meetingStore) => - meetingStore.isRaisedHand, - builder: (_, handRaised, __) { - return EmbeddedButton( - onTap: () => { - context - .read() - .changeMetadata() - }, - width: 40, - height: 40, - disabledBorderColor: borderColor, - offColor: - themeScreenBackgroundColor, - onColor: themeHintColor, - isActive: handRaised, - child: SvgPicture.asset( - "assets/icons/hand_outline.svg", - color: themeDefaultColor, - fit: BoxFit.scaleDown, - semanticsLabel: - "hand_raise_button", - ), - ); - }), - SizedBox( - width: 10, - ), - Selector( - selector: (_, meetingStore) => - meetingStore.isNewMessageReceived, - builder: - (_, isNewMessageReceived, __) { - return EmbeddedButton( - onTap: () => { - context - .read() - .setNewMessageFalse(), - showModalBottomSheet( - isScrollControlled: true, - backgroundColor: - themeBottomSheetColor, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular( - 20), - ), - context: context, - builder: (ctx) => - ChangeNotifierProvider.value( - value: context.read< - MeetingStore>(), - child: HLSMessage()), - ) - }, - width: 40, - height: 40, - offColor: themeHintColor, - onColor: - themeScreenBackgroundColor, - isActive: true, - child: SvgPicture.asset( - isNewMessageReceived - ? "assets/icons/message_badge_on.svg" - : "assets/icons/message_badge_off.svg", - fit: BoxFit.scaleDown, - semanticsLabel: "chat_button", - ), - ); - }), - SizedBox( - width: 10, - ), - EmbeddedButton( - onTap: () => { - showModalBottomSheet( + Selector( + selector: (_, meetingStore) => + meetingStore.isNewMessageReceived, + builder: (_, isNewMessageReceived, __) { + return EmbeddedButton( + onTap: () => { + context + .read() + .setNewMessageFalse(), + showModalBottomSheet( isScrollControlled: true, backgroundColor: themeBottomSheetColor, @@ -367,101 +312,139 @@ class _HLSViewerPageState extends State { ChangeNotifierProvider.value( value: context .read(), - child: - HLSViewerSettings())) - }, - width: 40, - height: 40, - offColor: themeHintColor, - onColor: themeScreenBackgroundColor, - isActive: true, - child: SvgPicture.asset( - "assets/icons/more.svg", - color: themeDefaultColor, - fit: BoxFit.scaleDown, - semanticsLabel: "more_button", - ), - ), - ], - ) + child: HLSMessage()), + ) + }, + width: 40, + height: 40, + offColor: themeHintColor, + onColor: themeScreenBackgroundColor, + isActive: true, + child: SvgPicture.asset( + isNewMessageReceived + ? "assets/icons/message_badge_on.svg" + : "assets/icons/message_badge_off.svg", + fit: BoxFit.scaleDown, + semanticsLabel: "chat_button", + ), + ); + }), + SizedBox( + width: 10, + ), + EmbeddedButton( + onTap: () => { + showModalBottomSheet( + isScrollControlled: true, + backgroundColor: + themeBottomSheetColor, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(20), + ), + context: context, + builder: (ctx) => + ChangeNotifierProvider.value( + value: context + .read(), + child: HLSViewerSettings())) + }, + width: 40, + height: 40, + offColor: themeHintColor, + onColor: themeScreenBackgroundColor, + isActive: true, + child: SvgPicture.asset( + "assets/icons/more.svg", + color: themeDefaultColor, + fit: BoxFit.scaleDown, + semanticsLabel: "more_button", + ), + ), ], - ), - ), - ], + ) + ], + ), ), - Selector( - selector: (_, meetingStore) => - meetingStore.currentRoleChangeRequest, - builder: (_, roleChangeRequest, __) { - if (roleChangeRequest != null) { - HMSRoleChangeRequest currentRequest = - roleChangeRequest; - context - .read() - .currentRoleChangeRequest = null; - WidgetsBinding.instance - .addPostFrameCallback((_) { - UtilityComponents.showRoleChangeDialog( - currentRequest, context); - }); - } - return SizedBox(); - }), - Selector( - selector: (_, meetingStore) => - meetingStore.hmsTrackChangeRequest, - builder: (_, hmsTrackChangeRequest, __) { - if (hmsTrackChangeRequest != null) { - HMSTrackChangeRequest currentRequest = - hmsTrackChangeRequest; - context - .read() - .hmsTrackChangeRequest = null; - WidgetsBinding.instance - .addPostFrameCallback((_) { - UtilityComponents.showTrackChangeDialog( - context, currentRequest); - }); - } - return SizedBox(); - }), - Selector( - selector: (_, meetingStore) => - meetingStore.showAudioDeviceChangePopup, - builder: (_, showAudioDeviceChangePopup, __) { - if (showAudioDeviceChangePopup) { - context - .read() - .showAudioDeviceChangePopup = false; - WidgetsBinding.instance - .addPostFrameCallback((_) { - showDialog( - context: context, - builder: (_) => AudioDeviceChangeDialog( - currentAudioDevice: context - .read() - .currentAudioOutputDevice!, - audioDevicesList: context - .read() - .availableAudioOutputDevices, - changeAudioDevice: (audioDevice) { - context - .read() - .switchAudioOutput( - audioDevice: audioDevice); - }, - )); - }); - } - return SizedBox(); - }), ], ), - ), - ); - }), - ), - ), + Selector( + selector: (_, meetingStore) => + meetingStore.currentRoleChangeRequest, + builder: (_, roleChangeRequest, __) { + if (roleChangeRequest != null) { + HMSRoleChangeRequest currentRequest = + roleChangeRequest; + context + .read() + .currentRoleChangeRequest = null; + WidgetsBinding.instance.addPostFrameCallback((_) { + UtilityComponents.showRoleChangeDialog( + currentRequest, context); + }); + } + return SizedBox(); + }), + Selector( + selector: (_, meetingStore) => + meetingStore.hmsTrackChangeRequest, + builder: (_, hmsTrackChangeRequest, __) { + if (hmsTrackChangeRequest != null) { + HMSTrackChangeRequest currentRequest = + hmsTrackChangeRequest; + context.read().hmsTrackChangeRequest = + null; + WidgetsBinding.instance.addPostFrameCallback((_) { + UtilityComponents.showTrackChangeDialog( + context, currentRequest); + }); + } + return SizedBox(); + }), + Selector( + selector: (_, meetingStore) => + meetingStore.showAudioDeviceChangePopup, + builder: (_, showAudioDeviceChangePopup, __) { + if (showAudioDeviceChangePopup) { + context + .read() + .showAudioDeviceChangePopup = false; + WidgetsBinding.instance.addPostFrameCallback((_) { + showDialog( + context: context, + builder: (_) => AudioDeviceChangeDialog( + currentAudioDevice: context + .read() + .currentAudioOutputDevice!, + audioDevicesList: context + .read() + .availableAudioOutputDevices, + changeAudioDevice: (audioDevice) { + context + .read() + .switchAudioOutput( + audioDevice: audioDevice); + }, + )); + }); + } + return SizedBox(); + }), + Selector( + selector: (_, meetingStore) => + meetingStore.reconnecting, + builder: (_, reconnecting, __) { + if (reconnecting) { + return UtilityComponents.showReconnectingDialog( + context); + } + return SizedBox(); + }), + ], + ), + ), + ); + }), ); } } diff --git a/example/lib/hls-streaming/util/hls_message_organism.dart b/example/lib/hls-streaming/util/hls_message_organism.dart index eaee45133..90d813a85 100644 --- a/example/lib/hls-streaming/util/hls_message_organism.dart +++ b/example/lib/hls-streaming/util/hls_message_organism.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hmssdk_flutter_example/common/util/app_color.dart'; import 'package:hmssdk_flutter_example/hls-streaming/util/hls_subtitle_text.dart'; import 'package:hmssdk_flutter_example/hls-streaming/util/hls_title_text.dart'; +import 'package:provider/provider.dart'; +import '../../data_store/meeting_store.dart'; class HLSMessageOrganism extends StatelessWidget { final String message; @@ -25,131 +28,160 @@ class HLSMessageOrganism extends StatelessWidget { return Align( alignment: isLocalMessage ? Alignment.centerRight : Alignment.centerLeft, child: Container( - width: width - 50, - padding: (role != "" || isLocalMessage) - ? EdgeInsets.symmetric(vertical: 8, horizontal: 8) - : EdgeInsets.symmetric(horizontal: 8), - decoration: (role != "" || isLocalMessage) - ? BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: themeSurfaceColor) - : BoxDecoration(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, + width: width - (role == "" ? 80 : 60), + padding: EdgeInsets.symmetric(vertical: 8, horizontal: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), color: themeSurfaceColor), + child: Stack( children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Container( - constraints: BoxConstraints( - maxWidth: role != "" ? width * 0.25 : width * 0.5), - child: HLSTitleText( - text: senderName ?? "", - fontSize: 14, - letterSpacing: 0.1, - lineHeight: 20, - textColor: themeDefaultColor, - ), - ), - SizedBox( - width: 5, - ), - HLSSubtitleText( - text: date, textColor: themeSubHeadingColor), - ], - ), - (role != "" || isLocalMessage) - ? Row( - mainAxisAlignment: MainAxisAlignment.end, + Padding( + padding: EdgeInsets.only(right: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( children: [ Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - border: role != "" - ? Border.all(color: borderColor, width: 1) - : Border.symmetric()), - child: Padding( - padding: const EdgeInsets.all(4.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - (isLocalMessage ? "" : "TO YOU"), - style: GoogleFonts.inter( - fontSize: 10.0, - color: themeSubHeadingColor, - letterSpacing: 1.5, - fontWeight: FontWeight.w600), - ), - isLocalMessage - ? SizedBox() - : Padding( - padding: const EdgeInsets.symmetric( - horizontal: 2), - child: Text( - "|", + constraints: BoxConstraints( + maxWidth: + role != "" ? width * 0.25 : width * 0.5), + child: HLSTitleText( + text: senderName ?? "", + fontSize: 14, + letterSpacing: 0.1, + lineHeight: 20, + textColor: themeDefaultColor, + ), + ), + SizedBox( + width: 5, + ), + HLSSubtitleText( + text: date, textColor: themeSubHeadingColor), + ], + ), + (role != "" || isLocalMessage) + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + width: 5, + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: role != "" + ? Border.all( + color: borderColor, width: 1) + : Border.symmetric()), + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + if (role != "PRIVATE") + Text( + (isLocalMessage ? "" : "TO"), style: GoogleFonts.inter( fontSize: 10.0, - color: borderColor, + color: themeSubHeadingColor, letterSpacing: 1.5, fontWeight: FontWeight.w600), - )), - role != "" - ? Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Container( - constraints: BoxConstraints( - maxWidth: isLocalMessage - ? width * 0.25 - : width * 0.15), - child: Text( - "${role.toUpperCase()} ", - overflow: TextOverflow.ellipsis, - style: GoogleFonts.inter( - fontSize: 10.0, - color: themeDefaultColor, - letterSpacing: 1.5, - fontWeight: - FontWeight.w400), - ), - ), - Text( - "›", - overflow: TextOverflow.ellipsis, - style: GoogleFonts.inter( - fontSize: 10.0, - color: themeDefaultColor, - letterSpacing: 1.5, - fontWeight: FontWeight.w400), - ), - ], - ) - : SizedBox(), - ], - ), - ), - ), - ], - ) - : SizedBox() - ], - ), - SizedBox( - height: 8, - ), - Text( - message, - style: GoogleFonts.inter( - fontSize: 14.0, - color: themeDefaultColor, - letterSpacing: 0.25, - fontWeight: FontWeight.w400), + ), + (isLocalMessage || (role == "PRIVATE")) + ? SizedBox() + : Padding( + padding: + const EdgeInsets.symmetric( + horizontal: 2), + child: Text( + "|", + style: GoogleFonts.inter( + fontSize: 10.0, + color: borderColor, + letterSpacing: 1.5, + fontWeight: + FontWeight.w600), + )), + role != "" + ? Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Container( + constraints: BoxConstraints( + maxWidth: isLocalMessage + ? width * 0.25 + : width * 0.15), + child: Text( + "${role.toUpperCase()} ", + overflow: + TextOverflow.ellipsis, + style: GoogleFonts.inter( + fontSize: 10.0, + color: + themeDefaultColor, + letterSpacing: 1.5, + fontWeight: + FontWeight.w400), + ), + ), + ], + ) + : SizedBox(), + ], + ), + ), + ), + ], + ) + : SizedBox() + ], + ), + SizedBox( + height: 8, + ), + Text( + message, + style: GoogleFonts.inter( + fontSize: 14.0, + color: themeDefaultColor, + letterSpacing: 0.25, + fontWeight: FontWeight.w400), + ), + ], + ), ), + if (role == "") + Positioned( + top: 0, + right: 0, + bottom: 0, + child: PopupMenuButton( + color: themeSurfaceColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10)), + itemBuilder: (context) { + return List.generate(1, (index) { + return PopupMenuItem( + child: Text('Pin Message'), + onTap: () => context + .read() + .setSessionMetadata(senderName! + ": " + message), + ); + }); + }, + child: SvgPicture.asset( + "assets/icons/more.svg", + fit: BoxFit.scaleDown, + ), + ), + ) ], ), ), diff --git a/example/lib/hms_app_settings.dart b/example/lib/hms_app_settings.dart index 4aaf26c1c..3909ea0d5 100644 --- a/example/lib/hms_app_settings.dart +++ b/example/lib/hms_app_settings.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -13,23 +15,30 @@ class HMSAppSettings extends StatefulWidget { } class _HMSAppSettingsState extends State { + bool joinWithMutedAudio = true; + bool joinWithMutedVideo = true; bool isDarkMode = true; bool skipPreview = false; - bool mirrorCamera = false; + bool mirrorCamera = true; bool showStats = false; - + bool isSoftwareDecoder = false; @override void initState() { - // TODO: implement initState super.initState(); getAppSettings(); } Future getAppSettings() async { + joinWithMutedAudio = + await Utilities.getBoolData(key: 'join-with-muted-audio') ?? true; + joinWithMutedVideo = + await Utilities.getBoolData(key: 'join-with-muted-video') ?? true; skipPreview = await Utilities.getBoolData(key: 'skip-preview') ?? false; - mirrorCamera = await Utilities.getBoolData(key: 'mirror-camera') ?? false; + mirrorCamera = await Utilities.getBoolData(key: 'mirror-camera') ?? true; showStats = await Utilities.getBoolData(key: 'show-stats') ?? false; isDarkMode = await Utilities.getBoolData(key: 'dark-mode') ?? true; + isSoftwareDecoder = + await Utilities.getBoolData(key: 'software-decoder') ?? false; WidgetsBinding.instance.addPostFrameCallback((_) { setState(() {}); @@ -130,6 +139,64 @@ class _HMSAppSettingsState extends State { // setState(() {}) // })), // ), + if (Platform.isAndroid) + ListTile( + horizontalTitleGap: 2, + enabled: false, + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + "assets/icons/mic_state_off.svg", + fit: BoxFit.scaleDown, + color: themeDefaultColor, + ), + title: Text( + "Join with muted audio", + semanticsLabel: "fl_join_with_muted_audio", + style: GoogleFonts.inter( + fontSize: 14, + color: themeDefaultColor, + letterSpacing: 0.25, + fontWeight: FontWeight.w600), + ), + trailing: CupertinoSwitch( + activeColor: hmsdefaultColor, + value: joinWithMutedAudio, + onChanged: (value) => { + joinWithMutedAudio = value, + Utilities.saveBoolData( + key: 'join-with-muted-audio', value: value), + setState(() {}) + }), + ), + if (Platform.isAndroid) + ListTile( + horizontalTitleGap: 2, + enabled: false, + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + "assets/icons/cam_state_off.svg", + fit: BoxFit.scaleDown, + color: themeDefaultColor, + ), + title: Text( + "Join with muted video", + semanticsLabel: "fl_join_with_muted_video", + style: GoogleFonts.inter( + fontSize: 14, + color: themeDefaultColor, + letterSpacing: 0.25, + fontWeight: FontWeight.w600), + ), + trailing: CupertinoSwitch( + activeColor: hmsdefaultColor, + value: joinWithMutedVideo, + onChanged: (value) => { + joinWithMutedVideo = value, + Utilities.saveBoolData( + key: 'join-with-muted-video', value: value), + setState(() {}) + }), + ), ListTile( horizontalTitleGap: 2, enabled: false, @@ -212,6 +279,34 @@ class _HMSAppSettingsState extends State { setState(() {}) }), ), + if (Platform.isAndroid) + ListTile( + horizontalTitleGap: 2, + enabled: false, + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + 'assets/icons/decoder.svg', + color: themeDefaultColor, + ), + title: Text( + "Software Decoder", + semanticsLabel: "fl_software_decoder_enable", + style: GoogleFonts.inter( + fontSize: 14, + color: themeDefaultColor, + letterSpacing: 0.25, + fontWeight: FontWeight.w600), + ), + trailing: CupertinoSwitch( + activeColor: hmsdefaultColor, + value: isSoftwareDecoder, + onChanged: (value) => { + isSoftwareDecoder = value, + Utilities.saveBoolData( + key: 'software-decoder', value: value), + setState(() {}) + }), + ), ListTile( horizontalTitleGap: 2, enabled: true, diff --git a/example/lib/hms_sdk_interactor.dart b/example/lib/hms_sdk_interactor.dart index ffa47f4b6..b61571fa9 100644 --- a/example/lib/hms_sdk_interactor.dart +++ b/example/lib/hms_sdk_interactor.dart @@ -10,15 +10,29 @@ class HMSSDKInteractor { /// [appGroup] & [preferredExtension] are optional values only required for implementing Screen & Audio Share on iOS. They are not required for Android. /// Remove [appGroup] & [preferredExtension] if your app does not implements Screen or Audio Share on iOS. - HMSSDKInteractor({String? appGroup, String? preferredExtension}) { + /// [joinWithMutedAudio] & [joinWithMutedVideo] are required to set the initial audio/video state i.e what should be camera and mic + /// state while room is joined.By default both audio and video are kept as mute. + HMSSDKInteractor( + {String? appGroup, + String? preferredExtension, + bool joinWithMutedAudio = true, + bool joinWithMutedVideo = true, + bool softwareDecoder = false}) { HMSTrackSetting trackSetting = HMSTrackSetting( audioTrackSetting: HMSAudioTrackSetting( audioSource: HMSAudioMixerSource(node: [ - HMSAudioFilePlayerNode("audioFilePlayerNode"), - HMSMicNode(), - HMSScreenBroadcastAudioReceiverNode() - ])), - videoTrackSetting: HMSVideoTrackSetting()); + HMSAudioFilePlayerNode("audioFilePlayerNode"), + HMSMicNode(), + HMSScreenBroadcastAudioReceiverNode() + ]), + trackInitialState: joinWithMutedAudio + ? HMSTrackInitState.MUTED + : HMSTrackInitState.UNMUTED), + videoTrackSetting: HMSVideoTrackSetting( + trackInitialState: joinWithMutedVideo + ? HMSTrackInitState.MUTED + : HMSTrackInitState.UNMUTED, + forceSoftwareDecoder: softwareDecoder)); hmsSDK = HMSSDK( appGroup: appGroup, preferredExtension: preferredExtension, @@ -43,8 +57,10 @@ class HMSSDKInteractor { return await hmsSDK.switchVideo(isOn: isOn); } - Future switchCamera() async { - return await hmsSDK.switchCamera(); + Future switchCamera( + {HMSActionResultListener? hmsActionResultListener}) async { + return await hmsSDK.switchCamera( + hmsActionResultListener: hmsActionResultListener); } Future isScreenShareActive() async { @@ -52,28 +68,31 @@ class HMSSDKInteractor { } void sendBroadcastMessage( - String message, HMSActionResultListener hmsActionResultListener) { + String message, HMSActionResultListener hmsActionResultListener, + {String type = "chat"}) { hmsSDK.sendBroadcastMessage( message: message, - type: "chat", + type: type, hmsActionResultListener: hmsActionResultListener); } void sendDirectMessage(String message, HMSPeer peerTo, - HMSActionResultListener hmsActionResultListener) { + HMSActionResultListener hmsActionResultListener, + {String type = "chat"}) { hmsSDK.sendDirectMessage( message: message, peerTo: peerTo, - type: "chat", + type: type, hmsActionResultListener: hmsActionResultListener); } void sendGroupMessage(String message, List hmsRolesTo, - HMSActionResultListener hmsActionResultListener) { + HMSActionResultListener hmsActionResultListener, + {String type = "chat"}) { hmsSDK.sendGroupMessage( message: message, hmsRolesTo: hmsRolesTo, - type: "chat", + type: type, hmsActionResultListener: hmsActionResultListener); } @@ -311,13 +330,18 @@ class HMSSDKInteractor { return await hmsSDK.getTrackSettings(); } - void setTrackSettings( - {HMSActionResultListener? hmsActionResultListener, - required HMSTrackSetting hmsTrackSetting}) { - hmsSDK.setTrackSettings(hmsTrackSetting: hmsTrackSetting); - } - void destroy() { hmsSDK.destroy(); } + + void setSessionMetadata( + {required String metadata, + HMSActionResultListener? hmsActionResultListener}) { + hmsSDK.setSessionMetadata( + metadata: metadata, hmsActionResultListener: hmsActionResultListener); + } + + Future getSessionMetadata() { + return hmsSDK.getSessionMetadata(); + } } diff --git a/example/lib/preview/preview_details.dart b/example/lib/preview/preview_details.dart index 7da934435..0f1d385e5 100644 --- a/example/lib/preview/preview_details.dart +++ b/example/lib/preview/preview_details.dart @@ -26,7 +26,6 @@ class PreviewDetails extends StatefulWidget { class _PreviewDetailsState extends State { TextEditingController nameController = TextEditingController(); - bool toShowPreview = true; @override void initState() { super.initState(); @@ -37,7 +36,6 @@ class _PreviewDetailsState extends State { nameController.text = await Utilities.getStringData(key: "name"); nameController.selection = TextSelection.fromPosition( TextPosition(offset: nameController.text.length)); - toShowPreview = await Utilities.getBoolData(key: 'show-preview') ?? false; setState(() {}); } @@ -49,11 +47,20 @@ class _PreviewDetailsState extends State { res = await Utilities.getPermissions(); bool skipPreview = await Utilities.getBoolData(key: 'skip-preview') ?? false; + bool joinWithMutedAudio = + await Utilities.getBoolData(key: 'join-with-muted-audio') ?? true; + bool joinWithMutedVideo = + await Utilities.getBoolData(key: 'join-with-muted-video') ?? true; + bool sotfwareDecoder = + await Utilities.getBoolData(key: 'software-decoder') ?? false; if (res) { if (!skipPreview) { Navigator.of(context).pushReplacement(MaterialPageRoute( builder: (_) => ListenableProvider.value( - value: PreviewStore(), + value: PreviewStore( + joinWithMutedAudio: joinWithMutedAudio, + joinWithMutedVideo: joinWithMutedVideo, + softwareDecoder: sotfwareDecoder), child: PreviewPage( meetingFlow: widget.meetingFlow, name: nameController.text, @@ -64,7 +71,15 @@ class _PreviewDetailsState extends State { await Utilities.getBoolData(key: 'show-stats') ?? false; bool mirrorCamera = await Utilities.getBoolData(key: 'mirror-camera') ?? false; - HMSSDKInteractor _hmsSDKInteractor = HMSSDKInteractor(); + bool softwareDecoder = + await Utilities.getBoolData(key: 'software-decoder') ?? false; + HMSSDKInteractor _hmsSDKInteractor = HMSSDKInteractor( + appGroup: "group.flutterhms", + preferredExtension: + "live.100ms.flutter.FlutterBroadcastUploadExtension", + joinWithMutedAudio: joinWithMutedAudio, + joinWithMutedVideo: joinWithMutedVideo, + softwareDecoder: softwareDecoder); Navigator.of(context).pushReplacement(MaterialPageRoute( builder: (_) => ListenableProvider.value( value: MeetingStore(hmsSDKInteractor: _hmsSDKInteractor), diff --git a/example/lib/preview/preview_page.dart b/example/lib/preview/preview_page.dart index c6068618d..8ff349c92 100644 --- a/example/lib/preview/preview_page.dart +++ b/example/lib/preview/preview_page.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'package:badges/badges.dart'; -import 'package:connectivity_checker/connectivity_checker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -77,192 +76,138 @@ class _PreviewPageState extends State { final double width = size.width; final Orientation orientation = MediaQuery.of(context).orientation; final _previewStore = context.watch(); - return ConnectivityAppWrapper( - app: ConnectivityWidgetWrapper( - disableInteraction: true, - offlineWidget: UtilityComponents.showReconnectingDialog(context, - alertMessage: "Leave Preview"), - child: WillPopScope( - onWillPop: () async { - context.read().leave(); - return true; - }, - child: Selector( - selector: (_, previewStore) => previewStore.error, - builder: (_, error, __) { - if (error != null) { - if ((error.code?.errorCode == 1003) || - (error.code?.errorCode == 2000) || - (error.code?.errorCode == 4005)) { - WidgetsBinding.instance.addPostFrameCallback((_) { - UtilityComponents.showErrorDialog( - context: context, - errorMessage: - "Error Code: ${error.code?.errorCode ?? ""} ${error.description}", - errorTitle: error.message ?? "", - actionMessage: "Leave Room", - action: () { - Navigator.of(context) - .popUntil((route) => route.isFirst); - }); - }); - } else { - Utilities.showToast( - "Error : ${error.code?.errorCode ?? ""} ${error.description} ${error.message}", - time: 5); - } - } - return Scaffold( - body: Stack( - children: [ - (_previewStore.peer == null) - ? Center( - child: CircularProgressIndicator( - strokeWidth: 2, + return WillPopScope( + onWillPop: () async { + context.read().leave(); + return true; + }, + child: Selector( + selector: (_, previewStore) => previewStore.error, + builder: (_, error, __) { + if (error != null) { + if ((error.code?.errorCode == 1003) || + (error.code?.errorCode == 2000) || + (error.code?.errorCode == 4005)) { + WidgetsBinding.instance.addPostFrameCallback((_) { + UtilityComponents.showErrorDialog( + context: context, + errorMessage: + "Error Code: ${error.code?.errorCode ?? ""} ${error.description}", + errorTitle: error.message ?? "", + actionMessage: "Leave Room", + action: () { + Navigator.of(context) + .popUntil((route) => route.isFirst); + }); + }); + } else { + Utilities.showToast( + "Error : ${error.code?.errorCode ?? ""} ${error.description} ${error.message}", + time: 5); + } + } + return 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.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 && + _previewStore.isVideoOn) + ? Center( + child: CircularProgressIndicator( + strokeWidth: 2, ), ) - : (_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( + : 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.1 + : width * 0.05, + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ - SizedBox( - height: orientation == Orientation.portrait - ? width * 0.1 - : width * 0.05, + Row( + children: [ + IconButton( + onPressed: () { + context + .read() + .leave(); + Navigator.of(context).popUntil( + (route) => route.isFirst); + }, + icon: Icon(Icons.arrow_back_ios)), + HLSTitleText( + text: "Configure", + textColor: themeDefaultColor), + ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - IconButton( - onPressed: () { - Navigator.of(context).popUntil( - (route) => route.isFirst); - }, - icon: Icon(Icons.arrow_back_ios)), - HLSTitleText( - text: "Configure", - textColor: themeDefaultColor), - ], - ), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - EmbeddedButton( - height: 40, - width: 40, - onTap: () async => Platform - .isAndroid - ? showModalBottomSheet( - isScrollControlled: true, - backgroundColor: - themeBottomSheetColor, - shape: - RoundedRectangleBorder( - borderRadius: - BorderRadius.circular( - 20), - ), - context: context, - builder: (ctx) => - ChangeNotifierProvider.value( - value: context.read< - PreviewStore>(), - child: - PreviewDeviceSettings()), - ) - : _previewStore.toggleSpeaker(), - // { - - // _previewStore.toggleSpeaker()}, - offColor: themeHintColor, - onColor: themeScreenBackgroundColor, - isActive: true, - child: SvgPicture.asset( - !_previewStore.isRoomMute - ? "assets/icons/speaker_state_on.svg" - : "assets/icons/speaker_state_off.svg", - color: themeDefaultColor, - fit: BoxFit.scaleDown, - semanticsLabel: - "fl_mute_room_btn", - ), - ), - SizedBox( - width: 10, - ), - Badge( - animationType: - BadgeAnimationType.fade, - badgeColor: hmsdefaultColor, - badgeContent: Text( - "${_previewStore.peers.length.toString()}"), - child: EmbeddedButton( - height: 40, - width: 40, - onTap: () async => - showModalBottomSheet( + EmbeddedButton( + height: 40, + width: 40, + onTap: () async => Platform.isAndroid + ? showModalBottomSheet( isScrollControlled: true, backgroundColor: themeBottomSheetColor, @@ -276,263 +221,293 @@ class _PreviewPageState extends State { value: context.read< PreviewStore>(), child: - PreviewParticipantSheet()), - ), - offColor: themeHintColor, - onColor: - themeScreenBackgroundColor, - isActive: true, - child: SvgPicture.asset( - "assets/icons/participants.svg", - color: themeDefaultColor, - fit: BoxFit.scaleDown, - semanticsLabel: - "fl_participants_btn", - ), + PreviewDeviceSettings()), + ) + : _previewStore.toggleSpeaker(), + // { + + // _previewStore.toggleSpeaker()}, + offColor: themeHintColor, + onColor: themeScreenBackgroundColor, + isActive: true, + child: SvgPicture.asset( + !_previewStore.isRoomMute + ? "assets/icons/speaker_state_on.svg" + : "assets/icons/speaker_state_off.svg", + color: themeDefaultColor, + fit: BoxFit.scaleDown, + semanticsLabel: "fl_mute_room_btn", + ), + ), + SizedBox( + width: 10, + ), + Badge( + animationType: BadgeAnimationType.fade, + badgeColor: hmsdefaultColor, + badgeContent: Text( + "${_previewStore.peers.length.toString()}"), + child: EmbeddedButton( + height: 40, + width: 40, + onTap: () async => + showModalBottomSheet( + isScrollControlled: true, + backgroundColor: + themeBottomSheetColor, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(20), ), + context: context, + builder: (ctx) => + ChangeNotifierProvider.value( + value: context + .read(), + child: + PreviewParticipantSheet()), ), - SizedBox( - width: 5, + offColor: themeHintColor, + onColor: themeScreenBackgroundColor, + isActive: true, + child: SvgPicture.asset( + "assets/icons/participants.svg", + color: themeDefaultColor, + fit: BoxFit.scaleDown, + semanticsLabel: + "fl_participants_btn", ), - ], - ) + ), + ), + SizedBox( + width: 5, + ), ], - ), + ) ], ), - ), + ], ), - 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-")) + ), + ), + 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( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - if (_previewStore.peer != - null && - context - .read() - .peer! - .role - .publishSettings! - .allowed - .contains("audio")) - EmbeddedButton( - height: 40, - width: 40, - onTap: () async => - _previewStore.switchAudio( - isOn: _previewStore - .isAudioOn), - offColor: hmsWhiteColor, - onColor: - themeHMSBorderColor, - isActive: _previewStore - .isAudioOn, - child: SvgPicture.asset( + if (_previewStore.peer != null && + context + .read() + .peer! + .role + .publishSettings! + .allowed + .contains("audio")) + EmbeddedButton( + height: 40, + width: 40, + onTap: () async => + _previewStore.switchAudio( + isOn: _previewStore + .isAudioOn), + offColor: hmsWhiteColor, + onColor: themeHMSBorderColor, + isActive: + _previewStore.isAudioOn, + child: SvgPicture.asset( + _previewStore.isAudioOn + ? "assets/icons/mic_state_on.svg" + : "assets/icons/mic_state_off.svg", + color: _previewStore.isAudioOn - ? "assets/icons/mic_state_on.svg" - : "assets/icons/mic_state_off.svg", - color: _previewStore - .isAudioOn ? themeDefaultColor : Colors.black, - fit: BoxFit.scaleDown, - semanticsLabel: - "audio_mute_button", - ), - ), - SizedBox( - width: 10, + fit: BoxFit.scaleDown, + semanticsLabel: + "audio_mute_button", ), - if (_previewStore.peer != - null && - _previewStore - .peer! - .role - .publishSettings! - .allowed - .contains("video")) - EmbeddedButton( - height: 40, - width: 40, - onTap: () async => (_previewStore - .localTracks - .isEmpty) - ? null - : _previewStore.switchVideo( + ), + SizedBox( + width: 10, + ), + if (_previewStore.peer != null && + _previewStore.peer!.role + .publishSettings!.allowed + .contains("video")) + EmbeddedButton( + height: 40, + width: 40, + onTap: () async => (_previewStore + .localTracks.isEmpty) + ? null + : _previewStore + .switchVideo( isOn: _previewStore .isVideoOn), - offColor: hmsWhiteColor, - onColor: - themeHMSBorderColor, - isActive: _previewStore - .isVideoOn, - child: SvgPicture.asset( + offColor: hmsWhiteColor, + onColor: themeHMSBorderColor, + isActive: + _previewStore.isVideoOn, + child: SvgPicture.asset( + _previewStore.isVideoOn + ? "assets/icons/cam_state_on.svg" + : "assets/icons/cam_state_off.svg", + color: _previewStore.isVideoOn - ? "assets/icons/cam_state_on.svg" - : "assets/icons/cam_state_off.svg", - color: _previewStore - .isVideoOn ? themeDefaultColor : Colors.black, - fit: BoxFit.scaleDown, - semanticsLabel: - "video_mute_button", - ), - ), - ], - ), - Row( - children: [ - if (_previewStore - .networkQuality != - null && - _previewStore - .networkQuality != - -1) - EmbeddedButton( - height: 40, - width: 40, - 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", - )), - ], - ) + fit: BoxFit.scaleDown, + semanticsLabel: + "video_mute_button", + ), + ), ], ), - SizedBox( - height: 30, - ), - HMSButton( - width: width * 0.5, - onPressed: () async => { - context - .read() - .removePreviewListener(), - Navigator.of(context) - .pushReplacement( - MaterialPageRoute( - builder: (_) => - ListenableProvider - .value( - value: - MeetingStore( - hmsSDKInteractor: - _previewStore - .hmsSDKInteractor!, - ), - child: - HLSScreenController( - isRoomMute: - _previewStore - .isRoomMute, - isStreamingLink: - widget.meetingFlow == - MeetingFlow.meeting - ? false - : true, - 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, - ) - ], + Row( + children: [ + if (_previewStore + .networkQuality != + null && + _previewStore + .networkQuality != + -1) + EmbeddedButton( + height: 40, + width: 40, + 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, + ), + HMSButton( + width: width * 0.5, + onPressed: () async => { + context + .read() + .removePreviewListener(), + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (_) => + ListenableProvider.value( + value: MeetingStore( + hmsSDKInteractor: + _previewStore + .hmsSDKInteractor!, + ), + child: + HLSScreenController( + isRoomMute: + _previewStore + .isRoomMute, + isStreamingLink: + widget.meetingFlow == + MeetingFlow + .meeting + ? false + : true, + 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()) - ], - ), + ), + ), + ], + ) + : SizedBox()) ], ), - ); - }), - ), - ), + ], + ), + ); + }), ); } } diff --git a/example/lib/preview/preview_store.dart b/example/lib/preview/preview_store.dart index 5d65bc878..bab98aece 100644 --- a/example/lib/preview/preview_store.dart +++ b/example/lib/preview/preview_store.dart @@ -12,15 +12,21 @@ class PreviewStore extends ChangeNotifier implements HMSPreviewListener, HMSLogListener { HMSSDKInteractor? hmsSDKInteractor; - - PreviewStore() { - + PreviewStore( + {bool joinWithMutedAudio = true, + bool joinWithMutedVideo = true, + bool softwareDecoder = false}) { /// [appGroup] & [preferredExtension] of [HMSSDKInteractor] are optional values only required for implementing Screen & Audio Share on iOS. They are not required for Android. /// Remove [appGroup] & [preferredExtension] if your app does not implements Screen or Audio Share on iOS. + /// [joinWithMutedAudio] & [joinWithMutedVideo] are required to set the initial audio/video state i.e what should be camera and mic + /// state while room is joined.By default both audio and video are kept as mute. hmsSDKInteractor = HMSSDKInteractor( appGroup: "group.flutterhms", preferredExtension: - "live.100ms.flutter.FlutterBroadcastUploadExtension"); + "live.100ms.flutter.FlutterBroadcastUploadExtension", + joinWithMutedAudio: joinWithMutedAudio, + joinWithMutedVideo: joinWithMutedVideo, + softwareDecoder: softwareDecoder); } List localTracks = []; @@ -84,8 +90,12 @@ class PreviewStore extends ChangeNotifier List videoTracks = []; for (var track in localTracks) { if (track.kind == HMSTrackKind.kHMSTrackKindVideo) { + isVideoOn = !(track.isMute); videoTracks.add(track as HMSVideoTrack); } + if (track.kind == HMSTrackKind.kHMSTrackKindAudio) { + isAudioOn = !(track.isMute); + } } this.localTracks = videoTracks; Utilities.saveStringData(key: "meetingLink", value: this.meetingUrl); diff --git a/example/lib/service/room_service.dart b/example/lib/service/room_service.dart index b8e9e5dff..05c25c0e3 100644 --- a/example/lib/service/room_service.dart +++ b/example/lib/service/room_service.dart @@ -47,7 +47,8 @@ class RoomService { codeAndDomain = isProd ? url.split(".app.100ms.live") : url.split(".qa-app.100ms.live"); code = codeAndDomain[1]; - subDomain = codeAndDomain[0].split("https://")[1] + + subDomain = codeAndDomain[0] + .split(roomUrl.contains("https") ? "https://" : "http://")[1] + (isProd ? ".app.100ms.live" : ".qa-app.100ms.live"); if (code.contains("meeting")) code = code.split("/meeting/")[1]; diff --git a/example/pubspec.lock b/example/pubspec.lock index b7fd87368..5df8fb66d 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,6 +1,13 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" archive: dependency: transitive description: @@ -71,20 +78,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" - collection: + cloud_firestore_platform_interface: dependency: transitive description: - name: collection + name: cloud_firestore_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.16.0" - connectivity_checker: - dependency: "direct main" + version: "5.7.7" + cloud_firestore_web: + dependency: transitive description: - name: connectivity_checker + name: cloud_firestore_web url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "2.8.10" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.0" crypto: dependency: transitive description: @@ -119,7 +133,7 @@ packages: name: dropdown_button2 url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.5" fake_async: dependency: transitive description: @@ -147,7 +161,7 @@ packages: name: file_picker url: "https://pub.dartlang.org" source: hosted - version: "3.0.4" + version: "5.2.1" firebase: dependency: transitive description: @@ -161,28 +175,28 @@ packages: name: firebase_analytics url: "https://pub.dartlang.org" source: hosted - version: "9.3.4" + version: "9.3.8" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.3.4" + version: "3.3.7" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web url: "https://pub.dartlang.org" source: hosted - version: "0.4.2+4" + version: "0.4.2+7" firebase_core: dependency: "direct main" description: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "1.22.0" + version: "1.24.0" firebase_core_platform_interface: dependency: transitive description: @@ -196,56 +210,56 @@ packages: name: firebase_core_web url: "https://pub.dartlang.org" source: hosted - version: "1.7.2" + version: "1.7.3" firebase_crashlytics: dependency: "direct main" description: name: firebase_crashlytics url: "https://pub.dartlang.org" source: hosted - version: "2.8.10" + version: "2.9.0" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.2.16" + version: "3.3.0" firebase_dynamic_links: dependency: "direct main" description: name: firebase_dynamic_links url: "https://pub.dartlang.org" source: hosted - version: "4.3.7" + version: "4.3.11" 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+12" + version: "0.2.3+15" firebase_performance: dependency: "direct main" description: name: firebase_performance url: "https://pub.dartlang.org" source: hosted - version: "0.8.3" + version: "0.8.3+3" firebase_performance_platform_interface: dependency: transitive description: name: firebase_performance_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+16" + version: "0.1.1+19" firebase_performance_web: dependency: transitive description: name: firebase_performance_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+5" + version: "0.1.1+8" flutter: dependency: "direct main" description: flutter @@ -295,7 +309,7 @@ packages: name: fluttertoast url: "https://pub.dartlang.org" source: hosted - version: "8.0.9" + version: "8.1.1" focus_detector: dependency: "direct main" description: @@ -316,7 +330,7 @@ packages: path: ".." relative: true source: path - version: "0.7.6" + version: "0.7.7" html: dependency: transitive description: @@ -337,7 +351,7 @@ packages: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "4.0.1" + version: "4.0.2" image: dependency: transitive description: @@ -365,7 +379,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.6.0" + version: "4.7.0" logger: dependency: "direct main" description: @@ -407,42 +421,42 @@ packages: name: package_info_plus url: "https://pub.dartlang.org" source: hosted - version: "1.4.3+1" + version: "2.0.0" package_info_plus_linux: dependency: transitive description: name: package_info_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "2.0.0" package_info_plus_macos: dependency: transitive description: name: package_info_plus_macos url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "2.0.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "2.0.0" package_info_plus_web: dependency: transitive description: name: package_info_plus_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "2.0.0" package_info_plus_windows: dependency: transitive description: name: package_info_plus_windows url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "3.0.0" path: dependency: transitive description: @@ -505,7 +519,7 @@ packages: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.0.5" path_provider_windows: dependency: transitive description: @@ -519,35 +533,35 @@ packages: name: permission_handler url: "https://pub.dartlang.org" source: hosted - version: "10.0.0" + version: "10.1.0" permission_handler_android: dependency: transitive description: name: permission_handler_android url: "https://pub.dartlang.org" source: hosted - version: "10.0.0" + version: "10.1.0" permission_handler_apple: dependency: transitive description: name: permission_handler_apple url: "https://pub.dartlang.org" source: hosted - version: "9.0.4" + version: "9.0.6" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.7.0" + version: "3.9.0" permission_handler_windows: dependency: transitive description: name: permission_handler_windows url: "https://pub.dartlang.org" source: hosted - version: "0.1.0" + version: "0.1.1" petitparser: dependency: transitive description: @@ -568,7 +582,7 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.1.3" process: dependency: transitive description: @@ -582,7 +596,7 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "6.0.3" + version: "6.0.4" qr_code_scanner: dependency: "direct main" description: @@ -590,13 +604,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" shared_preferences: dependency: "direct main" description: @@ -610,7 +617,7 @@ packages: name: shared_preferences_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.13" + version: "2.0.14" shared_preferences_ios: dependency: transitive description: @@ -706,7 +713,7 @@ packages: name: tuple url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" typed_data: dependency: transitive description: @@ -741,7 +748,7 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.1.5" + version: "6.1.6" url_launcher_android: dependency: transitive description: @@ -776,7 +783,7 @@ packages: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" url_launcher_web: dependency: transitive description: @@ -818,7 +825,7 @@ packages: name: video_player_avfoundation url: "https://pub.dartlang.org" source: hosted - version: "2.3.5" + version: "2.3.7" video_player_platform_interface: dependency: transitive description: @@ -881,7 +888,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.1" xdg_directories: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 8328c8b68..334f4fec8 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -30,7 +30,6 @@ dependencies: package_info_plus: logger: path_provider: - connectivity_checker: dropdown_button2: video_player: focus_detector: diff --git a/ios/Classes/Models/HMSTrackSettingsExtension.swift b/ios/Classes/Models/HMSTrackSettingsExtension.swift index 8773f18a0..3942f406b 100644 --- a/ios/Classes/Models/HMSTrackSettingsExtension.swift +++ b/ios/Classes/Models/HMSTrackSettingsExtension.swift @@ -59,47 +59,37 @@ class HMSTrackSettingsExtension { 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) - } + audioSettings = HMSAudioTrackSettings(maxBitrate: 32, trackDescription: "track_description", audioSource: audioMixerSource) } catch { 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), + if let cameraFacing = videoSettingsDict["camera_facing"] as? String + { + videoSettings = HMSVideoTrackSettings(codec: HMSCodec.VP8, resolution: .init(width: 320, height: 180), - maxBitrate: bitrate, - maxFrameRate: framerate, - cameraFacing: .front, - trackDescription: desc, + maxBitrate: 32, + maxFrameRate: 30, + cameraFacing: getCameraFacing(from: cameraFacing), + trackDescription: "track_description", videoPlugins: nil) } } return HMSTrackSettings(videoSettings: videoSettings, audioSettings: audioSettings) } - - static private func getCodec(from string: String) -> HMSCodec { - if string.lowercased().contains("h264") { - return HMSCodec.H264 + + static private func getCameraFacing(from string: String) -> HMSCameraFacing { + if string.lowercased().contains("back") { + return HMSCameraFacing.back } - return HMSCodec.VP8 + return HMSCameraFacing.front } + } diff --git a/ios/Classes/SwiftHmssdkFlutterPlugin.swift b/ios/Classes/SwiftHmssdkFlutterPlugin.swift index 0c682b2fc..aa3ebc63f 100644 --- a/ios/Classes/SwiftHmssdkFlutterPlugin.swift +++ b/ios/Classes/SwiftHmssdkFlutterPlugin.swift @@ -166,7 +166,7 @@ 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": + case "get_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": @@ -174,6 +174,9 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene case "switch_audio_output": switchAudioOutput(call, result) + case "get_session_metadata", "set_session_metadata": + sessionMetadataAction(call, result) + default: result(FlutterMethodNotImplemented) } @@ -275,45 +278,20 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene } } + // MARK: - Track Setting 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 { - 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) } } + // MARK: - Audio Share + private func audioShareAction(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let arguments = call.arguments as! [AnyHashable: Any] if let audioNode = audioMixerSourceMap[arguments["name"] as! String] { @@ -352,6 +330,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene } } + // MARK: - Audio Output private func switchAudioOutput(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let routerPicker = AVRoutePickerView() for view in routerPicker.subviews { @@ -403,6 +382,21 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene } } + // MARK: - Session Metadata + private func sessionMetadataAction(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + switch call.method { + + case "get_session_metadata": + getSessionMetadata(result) + + case "set_session_metadata": + setSessionMetadata(call, result) + + default: + result(FlutterMethodNotImplemented) + } + } + // MARK: - Room Actions private func build(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let arguments = call.arguments as! [AnyHashable: Any] @@ -416,6 +410,9 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene logLevel = getLogLevel(from: level) setLogger = true } + let dartSDKVersion = arguments["dart_sdk_version"] as! String + let hmsSDKVersion = arguments["hmssdk_version"] as! String + let framework = HMSFrameworkInfo(type: .flutter, version: dartSDKVersion, sdkVersion: hmsSDKVersion) audioMixerSourceMap = [:] hmsSDK = HMSSDK.build { [self] sdk in @@ -423,6 +420,8 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene sdk.appGroup = appGroup } + sdk.frameworkInfo = framework + if setLogger { sdk.logger = self } @@ -745,6 +744,42 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene hmsSDK?.logger = nil } + private func getSessionMetadata(_ result: @escaping FlutterResult) { + hmsSDK?.getSessionMetadata(completion: { metadata, _ in + if let metadata = metadata { + let data = [ + "event_name": "session_metadata", + "data": [ + "metadata": metadata + ] + ] as [String: Any] + result(data) + } else { + result(nil) + } + }) + } + + private func setSessionMetadata(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + + let arguments = call.arguments as! [AnyHashable: Any] + + guard let metadata = arguments["session_metadata"] as? String else { + result(HMSErrorExtension.getError("No session metadata found in \(#function)")) + return + } + + hmsSDK?.setSessionMetadata(metadata, completion: { _, error in + if let error = error { + result(HMSErrorExtension.toDictionary(error)) + return + } else { + result(nil) + } + } + ) + } + // MARK: - 100ms SDK Delegate Callbacks public func onPreview(room: HMSRoom, localTracks: [HMSTrack]) { diff --git a/ios/hmssdk_flutter.podspec b/ios/hmssdk_flutter.podspec index a32b0fe42..797592f45 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.6' + s.version = '0.7.7' s.summary = 'The Flutter plugin for 100ms SDK' s.description = <<-DESC A Flutter Project for 100ms SDK. @@ -12,8 +12,8 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/100mslive/100ms-flutter.git' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'HMSSDK', '0.4.2' - s.dependency 'HMSBroadcastExtensionSDK', '0.0.3' + s.dependency 'HMSSDK', '0.4.5' + s.dependency 'HMSBroadcastExtensionSDK', '0.0.4' 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 36b45171a..f6e1e26c5 100644 --- a/lib/hmssdk_flutter.dart +++ b/lib/hmssdk_flutter.dart @@ -6,7 +6,6 @@ export 'src/common/platform_methods.dart'; // ENUMS export 'src/enum/hms_audio_codec.dart'; export 'src/enum/hms_camera_facing.dart'; -export 'src/enum/hms_codec.dart'; export 'src/enum/hms_peer_update.dart'; export 'src/enum/hms_preview_update_listener_method.dart'; export 'src/enum/hms_room_update.dart'; @@ -17,6 +16,7 @@ export 'src/enum/hms_video_codec.dart'; export 'src/enum/hms_message_recipient_type.dart'; export 'src/enum/hms_log_level.dart'; export 'src/enum/hms_stats_listener_method.dart'; +export 'src/enum/hms_track_init_state.dart'; //EXCEPTIONS export 'src/exceptions/hms_exception.dart'; export 'src/exceptions/hms_in_sufficient_data.dart'; diff --git a/lib/src/common/platform_methods.dart b/lib/src/common/platform_methods.dart index cdda2cdbf..8d593a386 100644 --- a/lib/src/common/platform_methods.dart +++ b/lib/src/common/platform_methods.dart @@ -110,7 +110,6 @@ enum PlatformMethod { stopRtmpAndRecording, build, getRoom, - updateHMSLocalVideoTrackSettings, ///change metadata for local peer changeMetadata, @@ -147,8 +146,9 @@ enum PlatformMethod { audioShareCurrentTime, audioShareDuration, getTrackSettings, - setTrackSettings, - destroy + destroy, + setSessionMetadata, + getSessionMetadata } extension PlatformMethodValues on PlatformMethod { @@ -277,9 +277,6 @@ extension PlatformMethodValues on PlatformMethod { case PlatformMethod.build: return 'build'; - case PlatformMethod.updateHMSLocalVideoTrackSettings: - return "update_hms_video_track_settings"; - case PlatformMethod.changeMetadata: return "change_metadata"; @@ -341,10 +338,12 @@ extension PlatformMethodValues on PlatformMethod { return "audio_share_duration"; case PlatformMethod.getTrackSettings: return "get_track_settings"; - case PlatformMethod.setTrackSettings: - return "set_track_settings"; case PlatformMethod.destroy: return "destroy"; + case PlatformMethod.setSessionMetadata: + return "set_session_metadata"; + case PlatformMethod.getSessionMetadata: + return "get_session_metadata"; default: return 'unknown'; } @@ -478,9 +477,6 @@ extension PlatformMethodValues on PlatformMethod { case "get_room": return PlatformMethod.getRoom; - case "update_hms_video_track_settings": - return PlatformMethod.updateHMSLocalVideoTrackSettings; - case "change_metadata": return PlatformMethod.changeMetadata; @@ -543,10 +539,12 @@ extension PlatformMethodValues on PlatformMethod { return PlatformMethod.audioShareDuration; case "get_track_settings": return PlatformMethod.getTrackSettings; - case "set_track_settings": - return PlatformMethod.setTrackSettings; case "destroy": return PlatformMethod.destroy; + case "set_session_metadata": + return PlatformMethod.setSessionMetadata; + case "get_session_metadata": + return PlatformMethod.getSessionMetadata; 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 3b2013d9c..7a831d235 100644 --- a/lib/src/enum/hms_action_result_listener_method.dart +++ b/lib/src/enum/hms_action_result_listener_method.dart @@ -19,6 +19,7 @@ enum HMSActionResultListenerMethod { stopScreenShare, startAudioShare, stopAudioShare, - setTrackSettings, + setSessionMetadata, + switchCamera, unknown } diff --git a/lib/src/enum/hms_codec.dart b/lib/src/enum/hms_codec.dart deleted file mode 100644 index 17fd4cfc4..000000000 --- a/lib/src/enum/hms_codec.dart +++ /dev/null @@ -1,30 +0,0 @@ -enum HMSCodec { H264, VP8, VP9, unknown } - -///HMSCodec -extension HMSCodecValues on HMSCodec { - static HMSCodec getHMSCodecFromName(String name) { - switch (name) { - case 'h264': - return HMSCodec.H264; - case 'vp8': - return HMSCodec.VP8; - case 'vp9': - return HMSCodec.VP9; - default: - return HMSCodec.unknown; - } - } - - static String getValueFromHMSCodec(HMSCodec hmsCodec) { - switch (hmsCodec) { - case HMSCodec.H264: - return 'h264'; - case HMSCodec.VP8: - return 'vp8'; - case HMSCodec.VP9: - return 'vp9'; - case HMSCodec.unknown: - return 'defaultCodec'; - } - } -} diff --git a/lib/src/enum/hms_room_update.dart b/lib/src/enum/hms_room_update.dart index 218d48924..985fd440e 100644 --- a/lib/src/enum/hms_room_update.dart +++ b/lib/src/enum/hms_room_update.dart @@ -20,9 +20,6 @@ enum HMSRoomUpdate { ///When browser recording state is changed browserRecordingStateUpdated, - ///When room name changed - RoomNameUpdated, - ///Default Update defaultUpdate } @@ -51,9 +48,6 @@ extension HMSRoomUpdateValues on HMSRoomUpdate { case 'hls_recording_state_updated': return HMSRoomUpdate.hlsRecordingStateUpdated; - case "room_name_updated": - return HMSRoomUpdate.RoomNameUpdated; - default: return HMSRoomUpdate.defaultUpdate; } @@ -82,9 +76,6 @@ extension HMSRoomUpdateValues on HMSRoomUpdate { case HMSRoomUpdate.hlsRecordingStateUpdated: return 'hls_recording_state_updated'; - case HMSRoomUpdate.RoomNameUpdated: - return "room_name_updated"; - default: return 'defaultUpdate'; } diff --git a/lib/src/enum/hms_track_init_state.dart b/lib/src/enum/hms_track_init_state.dart new file mode 100644 index 000000000..bc93c7c50 --- /dev/null +++ b/lib/src/enum/hms_track_init_state.dart @@ -0,0 +1,30 @@ +///Enum to set the audio/video initial track state to be passed with HMSTrackSetting +enum HMSTrackInitState { + MUTED, + UNMUTED, +} + +extension HMSTrackInitStateValue on HMSTrackInitState { + static HMSTrackInitState getHMSTrackInitStateFromName(String name) { + switch (name) { + case 'MUTED': + return HMSTrackInitState.MUTED; + case 'UNMUTED': + return HMSTrackInitState.UNMUTED; + default: + return HMSTrackInitState.UNMUTED; + } + } + + static String getValuefromHMSTrackInitState( + HMSTrackInitState? hmsTrackInitState) { + switch (hmsTrackInitState) { + case HMSTrackInitState.MUTED: + return 'MUTED'; + case HMSTrackInitState.UNMUTED: + return 'UNMUTED'; + default: + return "UNMUTED"; + } + } +} diff --git a/lib/src/hmssdk.dart b/lib/src/hmssdk.dart index fad2e7f21..3a36949ea 100644 --- a/lib/src/hmssdk.dart +++ b/lib/src/hmssdk.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:hmssdk_flutter/src/manager/hms_sdk_manager.dart'; +import 'package:hmssdk_flutter/src/model/hms_session_metadata.dart'; import 'package:hmssdk_flutter/src/service/platform_service.dart'; import '../hmssdk_flutter.dart'; @@ -129,10 +130,22 @@ class HMSSDK { } /// Switch camera to front or rear mode - Future switchCamera() async { - return await PlatformService.invokeMethod( + Future switchCamera( + {HMSActionResultListener? hmsActionResultListener}) async { + var result = await PlatformService.invokeMethod( PlatformMethod.switchCamera, ); + if (hmsActionResultListener != null) { + if (result != null && result["error"] != null) { + hmsActionResultListener.onException( + methodType: HMSActionResultListenerMethod.switchCamera, + hmsException: HMSException.fromMap(result["error"])); + } else { + hmsActionResultListener.onSuccess( + methodType: HMSActionResultListenerMethod.switchCamera, + ); + } + } } /// To start capturing the local peer's video & send it to other peer's in the room @@ -645,6 +658,10 @@ 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. + /// + /// ❗️ NOTE on iOS 16: If you start Screenshare from an iPhone/iPad running iOS 16 version, then if the app is in foreground then Screenshare will work fine. But if you start Screenshare & background the app, then Screenshare pauses as the SDK is unable to send video frames using IPC. This results in other peers in room seeing stuck frame. We are actively working to resolve this issue. On iOS 15 or below, this issue does not exists. + /// + /// Note viewing Screenshare on iOS 16 devices is unaffected by this & works fine. Future startScreenShare( {HMSActionResultListener? hmsActionResultListener}) async { HMSLocalPeer? localPeer = await getLocalPeer(); @@ -771,6 +788,7 @@ class HMSSDK { PlatformService.invokeMethod(PlatformMethod.switchAudioOutput); } + ///Method to start audio share of other apps.(Android Only) Future startAudioShare( {HMSActionResultListener? hmsActionResultListener, HMSAudioMixingMode audioMixingMode = @@ -791,6 +809,7 @@ class HMSSDK { } } + ///Method to stop audio share of other apps.(Android Only) Future stopAudioShare( {HMSActionResultListener? hmsActionResultListener}) async { if (!Platform.isAndroid) return; @@ -808,12 +827,14 @@ class HMSSDK { } } + ///Method to change audio mixing mode of shared audio from other apps.(Android only) void setAudioMixingMode({required HMSAudioMixingMode audioMixingMode}) { if (Platform.isAndroid) PlatformService.invokeMethod(PlatformMethod.setAudioMixingMode, arguments: {"audio_mixing_mode": audioMixingMode.name}); } + ///Method to get Track Settings. Future getTrackSettings() async { var result = await PlatformService.invokeMethod(PlatformMethod.getTrackSettings); @@ -821,30 +842,42 @@ class HMSSDK { return trackSetting; } - void setTrackSettings( - {HMSActionResultListener? hmsActionResultListener, - required HMSTrackSetting hmsTrackSetting}) async { + ///Method to destroy HMSSDK instance. + void destroy() { + PlatformService.invokeMethod(PlatformMethod.destroy); + } + + /// Method to update the value of the session metadata. + Future setSessionMetadata( + {required String metadata, + HMSActionResultListener? hmsActionResultListener}) async { + var arguments = {"session_metadata": metadata}; var result = await PlatformService.invokeMethod( - PlatformMethod.setTrackSettings, - arguments: {"hms_track_setting": hmsTrackSetting.toMap()}); + PlatformMethod.setSessionMetadata, + arguments: arguments); + if (hmsActionResultListener != null) { - if (result == null) { - hmsActionResultListener.onSuccess( - methodType: HMSActionResultListenerMethod.setTrackSettings); - } else { + if (result != null && result["error"] != null) { hmsActionResultListener.onException( - methodType: HMSActionResultListenerMethod.setTrackSettings, - hmsException: HMSException( - message: "Unable to Set track Settings", - action: '', - description: 'Unable to Set track Settings', - isTerminal: false)); + methodType: HMSActionResultListenerMethod.setSessionMetadata, + arguments: arguments, + hmsException: HMSException.fromMap(result["error"])); + } else { + hmsActionResultListener.onSuccess( + methodType: HMSActionResultListenerMethod.setSessionMetadata, + arguments: arguments); } } } - void destroy() { - PlatformService.invokeMethod(PlatformMethod.destroy); + ///Method to fetches the latest metadata from the server and returns it + Future getSessionMetadata() async { + var result = + await PlatformService.invokeMethod(PlatformMethod.getSessionMetadata); + if (result != null) { + return HMSSessionMetadata.fromMap(result).metadata; + } + return null; } /// To modify local peer's audio & video track settings use the [hmsTrackSetting]. Only required for advanced use-cases. diff --git a/lib/src/manager/hms_sdk_manager.dart b/lib/src/manager/hms_sdk_manager.dart index 1e289982c..c15b82f1d 100644 --- a/lib/src/manager/hms_sdk_manager.dart +++ b/lib/src/manager/hms_sdk_manager.dart @@ -1,3 +1,6 @@ +//Dart imports: +import 'dart:io'; + // Project imports: import 'package:hmssdk_flutter/src/service/platform_service.dart'; import '../../hmssdk_flutter.dart'; @@ -11,12 +14,19 @@ class HmsSdkManager { return isCreated; } - Future createHMSSdk(HMSTrackSetting? hmsTrackSetting, String? appGroup, - String? preferredExtension) async { + Future createHMSSdk( + HMSTrackSetting? hmsTrackSetting, + String? appGroup, + String? preferredExtension, + ) async { + List dartSDKVersion = Platform.version.split(" "); return await PlatformService.invokeMethod(PlatformMethod.build, arguments: { "hms_track_setting": hmsTrackSetting?.toMap(), "app_group": appGroup, - "preferred_extension": preferredExtension + "preferred_extension": preferredExtension, + "dart_sdk_version": + dartSDKVersion.length > 0 ? dartSDKVersion[0] : "null", + "hmssdk_version": "0.7.7" }); } } diff --git a/lib/src/model/hms_audio_track_setting.dart b/lib/src/model/hms_audio_track_setting.dart index 544262aa3..40b1dbbdd 100644 --- a/lib/src/model/hms_audio_track_setting.dart +++ b/lib/src/model/hms_audio_track_setting.dart @@ -3,20 +3,22 @@ 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; + /// [useHardwareAcousticEchoCanceler] controls if the built-in HW acoustic echo canceler should be used or not. + /// The default is on if it is supported. + /// Please note that on some devices the hardware wrongly reports the HW echo canceler to be present whereas it does not work + /// In such as application need to set this to false, so that SW echo canceler is picked up. final bool? useHardwareAcousticEchoCanceler; - final double? volume; - final String? trackDescription; + + ///[audioSource] only for iOS.Used for audioSharing use cases. HMSAudioMixerSource? audioSource; + ///[trackInitialState] property to set the initial state of the audio track i.e Mute/Unmute.By default it's unmuted. + final HMSTrackInitState? trackInitialState; + HMSAudioTrackSetting( - {this.maxBitrate = 32, - this.hmsAudioCodec, - this.useHardwareAcousticEchoCanceler, - this.volume, - this.trackDescription = "This is an audio Track", - this.audioSource}); + {this.useHardwareAcousticEchoCanceler, + this.audioSource, + this.trackInitialState = HMSTrackInitState.UNMUTED}); factory HMSAudioTrackSetting.fromMap(Map map) { List nodeList = []; @@ -34,27 +36,22 @@ class HMSAudioTrackSetting { } HMSAudioMixerSource(node: nodeList); return HMSAudioTrackSetting( - volume: map['volume'] ?? 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, - trackDescription: map['track_description'] ?? "This is an audio Track", - audioSource: audioMixerSource); + audioSource: audioMixerSource, + trackInitialState: map.containsKey("track_initial_state") + ? HMSTrackInitStateValue.getHMSTrackInitStateFromName( + map['track_initial_state']) + : HMSTrackInitState.UNMUTED); } Map toMap() { return { - 'bit_rate': maxBitrate, - 'volume': volume, - 'audio_codec': hmsAudioCodec != null - ? HMSAudioCodecValues.getValueFromHMSAudioCodec(hmsAudioCodec!) - : null, 'user_hardware_acoustic_echo_canceler': useHardwareAcousticEchoCanceler, - 'track_description': trackDescription, - 'audio_source': audioSource?.toList() + 'audio_source': audioSource?.toList(), + 'track_initial_state': + HMSTrackInitStateValue.getValuefromHMSTrackInitState( + trackInitialState) }; } } diff --git a/lib/src/model/hms_local_video_track.dart b/lib/src/model/hms_local_video_track.dart index a453f4f56..6bdf6066f 100644 --- a/lib/src/model/hms_local_video_track.dart +++ b/lib/src/model/hms_local_video_track.dart @@ -47,11 +47,4 @@ class HMSLocalVideoTrack extends HMSVideoTrack { isDegraded: map['is_degraded'], setting: HMSVideoTrackSetting.fromMap(map["hms_video_track_settings"])); } - - set setHMSTrackSetting(HMSVideoTrackSetting hmsVideoTrackSetting) { - this.setting = hmsVideoTrackSetting; - PlatformService.invokeMethod( - PlatformMethod.updateHMSLocalVideoTrackSettings, - arguments: {"video_track_setting": this.setting.toMap()}); - } } diff --git a/lib/src/model/hms_preview_listener.dart b/lib/src/model/hms_preview_listener.dart index 9758c9b81..ab859acb2 100644 --- a/lib/src/model/hms_preview_listener.dart +++ b/lib/src/model/hms_preview_listener.dart @@ -23,6 +23,8 @@ abstract class HMSPreviewListener { /// This is called when there is a change in any property of the Room /// + /// To Enable [onRoomUpdate] activate Enable Room-State from dashboard under templates->Advance Settings. + /// /// - Parameters: /// - room: the room which was joined /// - update: the triggered update type. Should be used to perform different UI Actions @@ -31,6 +33,8 @@ abstract class HMSPreviewListener { /// This will be called whenever there is an update on an existing peer /// or a new peer got added/existing peer is removed. /// + /// To Enable [onPeerUpdate] activate Send Peer List in Room-state from dashboard under templates->Advance Settings. + /// /// This callback can be used to keep a track of all the peers in the room /// - Parameters: /// - peer: the peer who joined/left or was updated diff --git a/lib/src/model/hms_session_metadata.dart b/lib/src/model/hms_session_metadata.dart new file mode 100644 index 000000000..595ff24ad --- /dev/null +++ b/lib/src/model/hms_session_metadata.dart @@ -0,0 +1,12 @@ +///[HMSSessionMetadata] contain metadata for the current session room. +class HMSSessionMetadata { + final String? metadata; + + HMSSessionMetadata({this.metadata}); + + factory HMSSessionMetadata.fromMap(Map map) { + return HMSSessionMetadata( + metadata: + map["data"]["metadata"] != null ? map["data"]["metadata"] : null); + } +} diff --git a/lib/src/model/hms_speaker.dart b/lib/src/model/hms_speaker.dart index ba17c149f..3cd0627cb 100644 --- a/lib/src/model/hms_speaker.dart +++ b/lib/src/model/hms_speaker.dart @@ -3,17 +3,15 @@ import 'package:hmssdk_flutter/hmssdk_flutter.dart'; class HMSSpeaker { final HMSPeer peer; - final HMSTrack? track; + final HMSTrack track; final int audioLevel; factory HMSSpeaker.fromMap(Map data) { return new HMSSpeaker( peer: HMSPeer.fromMap(data['peer']), - track: data["track"] == null - ? null - : data['track']['instance_of'] - ? HMSVideoTrack.fromMap(map: data['track']) - : HMSAudioTrack.fromMap(map: data['track']), + track: data['track']['instance_of'] + ? HMSVideoTrack.fromMap(map: data['track']) + : HMSAudioTrack.fromMap(map: data['track']), audioLevel: data['audioLevel'] as int, ); } diff --git a/lib/src/model/hms_video_track_setting.dart b/lib/src/model/hms_video_track_setting.dart index 4496d2ea4..cc82398f4 100644 --- a/lib/src/model/hms_video_track_setting.dart +++ b/lib/src/model/hms_video_track_setting.dart @@ -2,50 +2,48 @@ import 'package:hmssdk_flutter/hmssdk_flutter.dart'; class HMSVideoTrackSetting { - final HMSCodec? codec; - final HMSResolution? resolution; - final int? maxBitrate; - final int? maxFrameRate; + /// [cameraFacing] property specifies which camera to use while joining. It can be toggled later on. The default value is `HMSCameraFacing.FRONT`. final HMSCameraFacing? cameraFacing; - final String? trackDescription; + + /// [disableAutoResize] property to disable auto-resizing. By default it's set to false. final bool? disableAutoResize; + /// [trackInitialState] property to set the initial state of the video track i.e Mute/Unmute. + final HMSTrackInitState? trackInitialState; + + /// [forceSoftwareDecoder] property to use software decoder. By default it's set to false.(Android Only) + final bool? forceSoftwareDecoder; + HMSVideoTrackSetting( - {this.codec = HMSCodec.VP8, - this.resolution, - this.maxBitrate = 512, - this.maxFrameRate = 25, - this.cameraFacing = HMSCameraFacing.FRONT, - this.trackDescription = "This a video track", - this.disableAutoResize = false}); + {this.cameraFacing = HMSCameraFacing.FRONT, + this.disableAutoResize = false, + this.trackInitialState = HMSTrackInitState.UNMUTED, + this.forceSoftwareDecoder = false}); factory HMSVideoTrackSetting.fromMap(Map map) { - HMSResolution? resolution; - if (map.containsKey('resolution')) { - resolution = HMSResolution.fromMap(map['resolution']); - } return HMSVideoTrackSetting( - codec: HMSCodecValues.getHMSCodecFromName(map['video_codec']), - resolution: resolution, - maxBitrate: map['bit_rate'] ?? 0, - maxFrameRate: map['max_frame_rate'] ?? 0, cameraFacing: HMSCameraFacingValues.getHMSCameraFacingFromName( map['camera_facing']), - disableAutoResize: map['disable_auto_resize'] ?? false); + disableAutoResize: map['disable_auto_resize'] ?? false, + trackInitialState: map.containsKey('track_initial_state') + ? HMSTrackInitStateValue.getHMSTrackInitStateFromName( + map['track_initial_state']) + : HMSTrackInitState.UNMUTED, + forceSoftwareDecoder: map.containsKey('force_software_decoder') + ? map['force_software_decoder'] + : false); } Map toMap() { return { - 'video_codec': - codec != null ? HMSCodecValues.getValueFromHMSCodec(codec!) : null, - 'max_bit_rate': maxBitrate, - 'max_frame_rate': maxFrameRate, - 'resolution': resolution?.toMap(), 'camera_facing': cameraFacing != null ? HMSCameraFacingValues.getValueFromHMSCameraFacing(cameraFacing!) : null, - 'track_description': trackDescription, - 'disable_auto_resize': disableAutoResize ?? false + 'disable_auto_resize': disableAutoResize ?? false, + 'track_initial_state': + HMSTrackInitStateValue.getValuefromHMSTrackInitState( + trackInitialState), + 'force_software_decoder': forceSoftwareDecoder ?? false }; } } diff --git a/pubspec.yaml b/pubspec.yaml index 269219af6..a50907658 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.6 +version: 0.7.7 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/lib/home_page.dart b/sample apps/bloc/lib/home_page.dart index bb36e6854..0fcaef22c 100644 --- a/sample apps/bloc/lib/home_page.dart +++ b/sample apps/bloc/lib/home_page.dart @@ -14,7 +14,8 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { final meetingTextController = TextEditingController( - text: "https://yogi-livestreamingkit.app.100ms.live/meeting/xuq-zjx-ovh"); + text: + "https://yogi-livestreamingkit.app.100ms.live/meeting/xuq-zjx-ovh"); final nameTextController = TextEditingController(); return SafeArea( diff --git a/sample apps/getx/lib/views/HomePage.dart b/sample apps/getx/lib/views/HomePage.dart index 53a413276..09d38b37f 100644 --- a/sample apps/getx/lib/views/HomePage.dart +++ b/sample apps/getx/lib/views/HomePage.dart @@ -13,7 +13,8 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { final meetingTextController = TextEditingController( - text: "https://yogi-livestreamingkit.app.100ms.live/meeting/xuq-zjx-ovh"); + text: + "https://yogi-livestreamingkit.app.100ms.live/meeting/xuq-zjx-ovh"); final nameTextController = TextEditingController(); return SafeArea( diff --git a/sample apps/mobx/lib/setup/meeting_store.dart b/sample apps/mobx/lib/setup/meeting_store.dart index ced2b8557..401b8681a 100644 --- a/sample apps/mobx/lib/setup/meeting_store.dart +++ b/sample apps/mobx/lib/setup/meeting_store.dart @@ -774,9 +774,6 @@ abstract class MeetingStoreBase extends ChangeNotifier case HMSActionResultListenerMethod.stopAudioShare: // TODO: Handle this case. break; - case HMSActionResultListenerMethod.setTrackSettings: - // TODO: Handle this case. - break; } } @@ -845,9 +842,6 @@ abstract class MeetingStoreBase extends ChangeNotifier case HMSActionResultListenerMethod.stopAudioShare: // TODO: Handle this case. break; - case HMSActionResultListenerMethod.setTrackSettings: - // TODO: Handle this case. - break; } } diff --git a/sample apps/riverpod/lib/meeting/meeting_store.dart b/sample apps/riverpod/lib/meeting/meeting_store.dart index 4605c69ab..9ffe7fa55 100644 --- a/sample apps/riverpod/lib/meeting/meeting_store.dart +++ b/sample apps/riverpod/lib/meeting/meeting_store.dart @@ -500,9 +500,6 @@ class MeetingStore extends ChangeNotifier case HMSActionResultListenerMethod.stopAudioShare: // TODO: Handle this case. break; - case HMSActionResultListenerMethod.setTrackSettings: - // TODO: Handle this case. - break; } } @@ -562,9 +559,6 @@ class MeetingStore extends ChangeNotifier case HMSActionResultListenerMethod.stopAudioShare: // TODO: Handle this case. break; - case HMSActionResultListenerMethod.setTrackSettings: - // TODO: Handle this case. - break; } }