diff --git a/.gitignore b/.gitignore index ad01b6290..5a14401db 100644 --- a/.gitignore +++ b/.gitignore @@ -21,13 +21,17 @@ example/android/fastlane/Appfile example/android/fastlane/Fastfile example/android/fastlane/Pluginfile example/android/fastlane/README.md +example/android/flutter-hms-4aea6d38fd2a.json /example/android/fastlane/report.xml example/ios/fastlane/Appfile example/ios/fastlane/Fastfile example/ios/fastlane/Pluginfile +example/ios/flutter-hms-4aea6d38fd2a.json example/ios/fastlane/README.md /example/ios/fastlane/report.xml example/ios/Runner/GoogleService-Info.plist example/ios/config/GoogleService-Info.plist *.p8 +sample apps/hms-callkit-app/.flutter-plugins +sample apps/hms-callkit-app/.flutter-plugins-dependencies \ No newline at end of file diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index b3af55343..6cdd1752b 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,23 +1,23 @@ version: 0.1 cli: - version: 1.9.1 + version: 1.11.0 plugins: sources: - id: trunk - ref: v0.0.16 + ref: v0.0.17 uri: https://github.com/trunk-io/plugins lint: enabled: - oxipng@8.0.0 - - yamllint@1.31.0 - - markdownlint@0.34.0 + - yamllint@1.32.0 + - markdownlint@0.35.0 - prettier@2.8.8 - git-diff-check - shfmt@3.5.0 - shellcheck@0.9.0 - - gitleaks@8.16.3 + - gitleaks@8.17.0 - svgo@3.0.2 - - ktlint@0.49.0 + - ktlint@0.49.1 runtimes: enabled: - python@3.10.8 diff --git a/CHANGELOG.md b/CHANGELOG.md index add8233ca..ee1cca2fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,45 @@ +## 1.7.0 - 2023-06-20 + +### Breaking + +- Removed Session Metadata methods + + The `setSessionMetadata` and `getSessionMetadata` methods which were deprecated in previous versions have been removed now. + + Utilize the Session Store functionality which is more convenient to implement features like Spotlight a Peer in Room, Keep a Message Pinned, etc. Read more about Session Store [here](https://www.100ms.live/docs/flutter/v2/how-to-guides/interact-with-room/room/session-store). + +### Added + +- Added HLS Player + + Introducing the 100ms HLS Player named `HMSHLSPlayer` a comprehensive end-to-end solution for playing Live Streaming content with inbuilt support for [Timed Metadata](https://www.100ms.live/docs/flutter/v2/how-to-guides/record-and-live-stream/hls-player#how-to-get-hls-callbacks), [HLS Diagnostic Stats](https://www.100ms.live/docs/flutter/v2/how-to-guides/record-and-live-stream/hls-player#how-to-know-the-stats-related-to-hls-playback) & [Custom Player Controls](https://www.100ms.live/docs/flutter/v2/how-to-guides/record-and-live-stream/hls-player#hls-player-controls). + + Learn more about `HMSHLSPlayer` [here](https://www.100ms.live/docs/flutter/v2/how-to-guides/record-and-live-stream/hls-player). + +- Switch Audio Output using Native iOS UI + + On iOS devices, you can now show the Native Airplay UI provided by iOS. Users can control the connected device which can be Airpods, any Bluetooth earphones, Wired Headsets, etc through which the Room's audio should be routed. + + Learn more about it [here](https://www.100ms.live/docs/flutter/v2/how-to-guides/configure-your-device/speaker/audio-output-routing#switch-audio-output-device-ui-ios-only). + + - Added `messageId` to `HMSMessage` + + You can now uniquely identify a message using the `messageId` property of `HMSMessage` class. + + Checkout more about Messaging [here](https://www.100ms.live/docs/flutter/v2/how-to-guides/set-up-video-conferencing/chat#receiving-chat-messages). + +### Changed + +- RTMP Streaming can now be started without the `meetingUrl`. It is now an optional parameter. Learn more about RTMP Streaming [here](https://www.100ms.live/docs/flutter/v2/how-to-guides/record-and-live-stream/recording#startstop-streaming-recording). + +- [Software Audio Echo Cancellation](https://www.100ms.live/docs/flutter/v2/how-to-guides/configure-your-device/microphone/echo-cancellation) is now enabled by default. To further customize Audio & Video Track Settings, refer the docs [here](https://www.100ms.live/docs/flutter/v2/how-to-guides/interact-with-room/track/set-track-settings). + +- On Android, Screen share from a remote peer will now appear correctly occupy the space provided by the enclosing widget. Read more about Screen Share [here](https://www.100ms.live/docs/flutter/v2/how-to-guides/set-up-video-conferencing/screen-share). + +Updated to Android SDK 2.6.7 & iOS SDK 0.9.5 + +**Full Changelog**: [1.6.0...1.7.0](https://github.com/100mslive/100ms-flutter/compare/1.6.0...1.7.0) + ## 1.6.0 - 2023-05-04 ### New Features diff --git a/android/build.gradle b/android/build.gradle index 68a1da0c5..2ac0b37ec 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -43,6 +43,8 @@ android { dependencies { implementation "live.100ms:android-sdk:${sdkVersions['android']}" implementation "live.100ms:video-view:${sdkVersions['android']}" + implementation "live.100ms:hls-player:${sdkVersions['android']}" + implementation 'com.google.code.gson:gson:2.9.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/Constants.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/Constants.kt index 6589ea932..3e64cf1f6 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/Constants.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/Constants.kt @@ -13,5 +13,7 @@ class Constants { const val SCREEN_SHARE_REQUEST = "REQUEST_SCREEN_SHARE" const val AUDIO_SHARE_REQUEST = "REQUEST_AUDIO_SHARE" + + const val HLS_PLAYER_INTENT = "HLS_PLAYER" } } 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 5a089be4e..7ee2a0654 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSAudioTrackSettingsExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSAudioTrackSettingsExtension.kt @@ -1,18 +1,14 @@ package live.hms.hmssdk_flutter -import live.hms.hmssdk_flutter.hms_role_components.AudioParamsExtension -import live.hms.video.media.tracks.HMSTrack -import live.hms.video.media.tracks.HMSAudioTrack -import live.hms.video.utils.HMSLogger import live.hms.video.media.settings.HMSAudioTrackSettings class HMSAudioTrackSettingsExtension { - companion object{ - fun toDictionary(hmsAudioTrackSettings: HMSAudioTrackSettings?):HashMap? { - val map = HashMap() + companion object { + fun toDictionary(hmsAudioTrackSettings: HMSAudioTrackSettings?): HashMap? { + val map = HashMap() 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/HMSChangeTrackStateRequestExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSChangeTrackStateRequestExtension.kt index 63e6c793d..96a157f8e 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSChangeTrackStateRequestExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSChangeTrackStateRequestExtension.kt @@ -4,18 +4,18 @@ import live.hms.video.sdk.models.trackchangerequest.HMSChangeTrackStateRequest class HMSChangeTrackStateRequestExtension { - companion object{ - fun toDictionary(hmsChangeTrackStateRequest: HMSChangeTrackStateRequest?):HashMap?{ - val hashMap = HashMap() - if(hmsChangeTrackStateRequest==null)return null - hashMap.put("mute",hmsChangeTrackStateRequest.mute) - hashMap.put("requested_by",HMSPeerExtension.toDictionary(hmsChangeTrackStateRequest.requestedBy)) - hashMap.put("track",HMSTrackExtension.toDictionary(hmsChangeTrackStateRequest.track)) + companion object { + fun toDictionary(hmsChangeTrackStateRequest: HMSChangeTrackStateRequest?): HashMap? { + val hashMap = HashMap() + if (hmsChangeTrackStateRequest == null)return null + hashMap.put("mute", hmsChangeTrackStateRequest.mute) + hashMap.put("requested_by", HMSPeerExtension.toDictionary(hmsChangeTrackStateRequest.requestedBy)) + hashMap.put("track", HMSTrackExtension.toDictionary(hmsChangeTrackStateRequest.track)) - val args=HashMap() - args.put("track_change_request",hashMap) + val args = HashMap() + args.put("track_change_request", hashMap) - return args + return args } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSHLSVariantExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSHLSVariantExtension.kt index 3b0cbfd77..ecd84e17c 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSHLSVariantExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSHLSVariantExtension.kt @@ -1,21 +1,19 @@ package live.hms.hmssdk_flutter -import live.hms.video.error.HMSException import live.hms.video.sdk.models.HMSHLSVariant import java.text.SimpleDateFormat class HMSHLSVariantExtension { - companion object{ - fun toDictionary(hmshlsVariant: HMSHLSVariant?):HashMap?{ - val args=HashMap() + companion object { + fun toDictionary(hmshlsVariant: HMSHLSVariant?): HashMap? { + val args = HashMap() if (hmshlsVariant == null)return null - args["hls_stream_url"] = hmshlsVariant.hlsStreamUrl?:"" - args["meeting_url"] = hmshlsVariant.meetingUrl?:"" - args["metadata"] = hmshlsVariant.metadata?:"" + args["hls_stream_url"] = hmshlsVariant.hlsStreamUrl ?: "" + args["meeting_url"] = hmshlsVariant.meetingUrl ?: "" + args["metadata"] = hmshlsVariant.metadata ?: "" args["started_at"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmshlsVariant.startedAt).toString() return args } } - -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSLogSettings.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSLogSettings.kt index 851317c60..24564a13a 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSLogSettings.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSLogSettings.kt @@ -5,14 +5,14 @@ import live.hms.video.utils.HMSLogger class HMSLogSettings { - companion object{ + companion object { - fun setLogSettings(maxDirSizeInBytes: Double,logStorageEnabled: Boolean,logLevel: String): HMSLogSettings { - return HMSLogSettings(maxDirSizeInBytes = maxDirSizeInBytes.toLong(), isLogStorageEnabled = logStorageEnabled, level = getLogLevel(logLevel)) + fun setLogSettings(maxDirSizeInBytes: Double, logStorageEnabled: Boolean, logLevel: String): HMSLogSettings { + return HMSLogSettings(maxDirSizeInBytes = maxDirSizeInBytes.toLong(), isLogStorageEnabled = logStorageEnabled, level = getLogLevel(logLevel)) } - private fun getLogLevel(logLevel:String?): HMSLogger.LogLevel { - return when(logLevel){ + private fun getLogLevel(logLevel: String?): HMSLogger.LogLevel { + return when (logLevel) { "error" -> HMSLogger.LogLevel.ERROR "off" -> HMSLogger.LogLevel.OFF "verbose" -> HMSLogger.LogLevel.VERBOSE @@ -21,4 +21,4 @@ class HMSLogSettings { } } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSLogsExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSLogsExtension.kt index 3620e6e0a..32e53508a 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSLogsExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSLogsExtension.kt @@ -1,18 +1,17 @@ package live.hms.hmssdk_flutter -import live.hms.video.sdk.models.enums.HMSPeerUpdate import live.hms.video.utils.HMSLogger class HMSLogsExtension { - companion object{ + companion object { fun toDictionary( level: HMSLogger.LogLevel, tag: String, message: String, - isWebRtCLog: Boolean - ) : HashMap{ - val map = HashMap() - map["level"]= getValueOfHMSLog(level)!! + isWebRtCLog: Boolean, + ): HashMap { + val map = HashMap() + map["level"] = getValueOfHMSLog(level)!! map["tag"] = tag map["message"] = message map["is_web_rtc_log"] = isWebRtCLog @@ -20,18 +19,18 @@ class HMSLogsExtension { return map } - fun getValueOfHMSLog(level: HMSLogger.LogLevel?):String?{ - if(level==null)return null + fun getValueOfHMSLog(level: HMSLogger.LogLevel?): String? { + if (level == null)return null - return when(level){ - HMSLogger.LogLevel.DEBUG-> "debug" - HMSLogger.LogLevel.ERROR-> "error" - HMSLogger.LogLevel.INFO-> "info" - HMSLogger.LogLevel.OFF-> "off" - HMSLogger.LogLevel.VERBOSE->"verbose" - HMSLogger.LogLevel.WARN->"warn" - else-> "defaultUpdate" + return when (level) { + HMSLogger.LogLevel.DEBUG -> "debug" + HMSLogger.LogLevel.ERROR -> "error" + HMSLogger.LogLevel.INFO -> "info" + HMSLogger.LogLevel.OFF -> "off" + HMSLogger.LogLevel.VERBOSE -> "verbose" + HMSLogger.LogLevel.WARN -> "warn" + else -> "defaultUpdate" } } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSMessageExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSMessageExtension.kt index c1af27ff8..0436770e2 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSMessageExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSMessageExtension.kt @@ -4,20 +4,22 @@ import live.hms.video.sdk.models.HMSMessage import java.text.SimpleDateFormat class HMSMessageExtension { - companion object{ - fun toDictionary(message:HMSMessage?):HashMap?{ - val args=HashMap() - if(message==null)return null + companion object { + fun toDictionary(message: HMSMessage?): HashMap? { + val args = HashMap() + if (message == null)return null + args["message_id"] = message.messageId args["message"] = message.message args["time"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(message.serverReceiveTime).toString() args["type"] = message.type - if(message.sender != null) + if (message.sender != null) { args["sender"] = HMSPeerExtension.toDictionary(message.sender)!! + } args["hms_message_recipient"] = HMSMessageRecipientExtension.toDictionary(message.recipient)!! - val messageArgs=HashMap() + val messageArgs = HashMap() messageArgs["message"] = args return messageArgs } - } -} \ No newline at end of file + } +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSMessageRecipientExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSMessageRecipientExtension.kt index 3406c1a66..47f58af95 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSMessageRecipientExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSMessageRecipientExtension.kt @@ -1,36 +1,35 @@ package live.hms.hmssdk_flutter -import io.flutter.Log import live.hms.video.sdk.models.HMSMessageRecipient import live.hms.video.sdk.models.enums.HMSMessageRecipientType class HMSMessageRecipientExtension { - companion object{ - fun toDictionary(hmsMessageRecipient: HMSMessageRecipient?):HashMap?{ - val hashMap = HashMap() - if(hmsMessageRecipient==null)return null + companion object { + fun toDictionary(hmsMessageRecipient: HMSMessageRecipient?): HashMap? { + val hashMap = HashMap() + if (hmsMessageRecipient == null)return null hashMap["recipient_peer"] = HMSPeerExtension.toDictionary(hmsMessageRecipient.recipientPeer) - val recipientRoles = ArrayList?>() + val recipientRoles = ArrayList?>() hmsMessageRecipient.recipientRoles.forEach { recipientRoles.add(HMSRoleExtension.toDictionary(it)) } - hashMap["recipient_roles"] = if(recipientRoles.size!=0)recipientRoles else null + hashMap["recipient_roles"] = if (recipientRoles.size != 0)recipientRoles else null - hashMap["recipient_type"] = getValueOfHMSMessageRecipient(hmsMessageRecipient.recipientType) + hashMap["recipient_type"] = getValueOfHMSMessageRecipient(hmsMessageRecipient.recipientType) return hashMap } - private fun getValueOfHMSMessageRecipient(hmsMessageRecipientType: HMSMessageRecipientType?):String?{ - if(hmsMessageRecipientType==null)return null - return when(hmsMessageRecipientType){ - HMSMessageRecipientType.BROADCAST-> "broadCast" - HMSMessageRecipientType.PEER-> "peer" - HMSMessageRecipientType.ROLES-> "roles" - else-> "defaultRecipient" + private fun getValueOfHMSMessageRecipient(hmsMessageRecipientType: HMSMessageRecipientType?): String? { + if (hmsMessageRecipientType == null)return null + return when (hmsMessageRecipientType) { + HMSMessageRecipientType.BROADCAST -> "broadCast" + HMSMessageRecipientType.PEER -> "peer" + HMSMessageRecipientType.ROLES -> "roles" + else -> "defaultRecipient" } } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSNetworkQualityExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSNetworkQualityExtension.kt index 3bb247bf0..a814152d9 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSNetworkQualityExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSNetworkQualityExtension.kt @@ -1,8 +1,6 @@ package live.hms.hmssdk_flutter import live.hms.video.connection.stats.quality.HMSNetworkQuality -import live.hms.video.sdk.models.HMSPeer -import live.hms.video.sdk.models.enums.HMSPeerUpdate class HMSNetworkQualityExtension { @@ -14,4 +12,4 @@ class HMSNetworkQualityExtension { return args } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPeerExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPeerExtension.kt index 7adf49fc6..94b15db98 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPeerExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPeerExtension.kt @@ -39,12 +39,9 @@ class HMSPeerExtension { HMSPeerUpdate.ROLE_CHANGED -> "roleUpdated" HMSPeerUpdate.METADATA_CHANGED -> "metadataChanged" HMSPeerUpdate.NAME_CHANGED -> "nameChanged" + HMSPeerUpdate.NETWORK_QUALITY_UPDATED -> "networkQualityUpdated" HMSPeerUpdate.BECAME_DOMINANT_SPEAKER -> "becameDominantSpeaker" HMSPeerUpdate.NO_DOMINANT_SPEAKER -> "noDominantSpeaker" - HMSPeerUpdate.RESIGNED_DOMINANT_SPEAKER -> "resignedDominantSpeaker" - HMSPeerUpdate.STARTED_SPEAKING -> "startedSpeaking" - HMSPeerUpdate.STOPPED_SPEAKING -> "stoppedSpeaking" - HMSPeerUpdate.NETWORK_QUALITY_UPDATED -> "networkQualityUpdated" else -> "defaultUpdate" } } diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPeerUpdateExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPeerUpdateExtension.kt index 800620e73..fce6649d4 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPeerUpdateExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPeerUpdateExtension.kt @@ -1,18 +1,17 @@ package live.hms.hmssdk_flutter -import io.flutter.Log import live.hms.video.sdk.models.HMSPeer import live.hms.video.sdk.models.enums.HMSPeerUpdate class HMSPeerUpdateExtension { - companion object{ - fun toDictionary(peer:HMSPeer?,update: HMSPeerUpdate?):HashMap?{ - val args=HashMap() - if(peer==null || update==null)return null + companion object { + fun toDictionary(peer: HMSPeer?, update: HMSPeerUpdate?): HashMap? { + val args = HashMap() + if (peer == null || update == null)return null args.put("peer", HMSPeerExtension.toDictionary(peer)!!) args.put("update", HMSPeerExtension.getValueofHMSPeerUpdate(update)!!) return args } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPreviewExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPreviewExtension.kt index 987ecfe86..d6b7989c3 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPreviewExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPreviewExtension.kt @@ -1,16 +1,15 @@ package live.hms.hmssdk_flutter import live.hms.video.media.tracks.HMSTrack -import live.hms.video.sdk.models.HMSPeer import live.hms.video.sdk.models.HMSRoom class HMSPreviewExtension { - companion object{ - fun toDictionary(room: HMSRoom?,allTracks:Array?):HashMap?{ - val args=HashMap() - if(room==null || allTracks==null)return null + companion object { + fun toDictionary(room: HMSRoom?, allTracks: Array?): HashMap? { + val args = HashMap() + if (room == null || allTracks == null)return null args["room"] = HMSRoomExtension.toDictionary(room)!! - val tracks=ArrayList() + val tracks = ArrayList() for (eachTrack in allTracks) { tracks.add(HMSTrackExtension.toDictionary(eachTrack)!!) @@ -19,4 +18,4 @@ class HMSPreviewExtension { return args } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRemovedFromRoomExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRemovedFromRoomExtension.kt index 01dd696c2..0187c4f6e 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRemovedFromRoomExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRemovedFromRoomExtension.kt @@ -3,18 +3,18 @@ package live.hms.hmssdk_flutter import live.hms.video.sdk.models.HMSRemovedFromRoom class HMSRemovedFromRoomExtension { - companion object{ - fun toDictionary(notification:HMSRemovedFromRoom?): HashMap? { - val hashMap = HashMap() + companion object { + fun toDictionary(notification: HMSRemovedFromRoom?): HashMap? { + val hashMap = HashMap() - if(notification==null)return null - hashMap.put("peer_who_removed",HMSPeerExtension.toDictionary(notification.peerWhoRemoved)) - hashMap.put("reason",notification.reason) - hashMap.put("room_was_ended",notification.roomWasEnded) + if (notification == null)return null + hashMap.put("peer_who_removed", HMSPeerExtension.toDictionary(notification.peerWhoRemoved)) + hashMap.put("reason", notification.reason) + hashMap.put("room_was_ended", notification.roomWasEnded) - val roomMap = HashMap() - roomMap.put("removed_from_room",hashMap) + val roomMap = HashMap() + roomMap.put("removed_from_room", hashMap) return roomMap } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoleChangedExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoleChangedExtension.kt index b46105bae..3c3d1b17d 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoleChangedExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoleChangedExtension.kt @@ -3,15 +3,15 @@ package live.hms.hmssdk_flutter import live.hms.video.sdk.models.HMSRoleChangeRequest class HMSRoleChangedExtension { - companion object{ - fun toDictionary(role:HMSRoleChangeRequest?):HashMap?{ - val args=HashMap() - if(role==null)return null - args.put("requested_by",HMSPeerExtension.toDictionary(role.requestedBy)) - args.put("suggested_role",HMSRoleExtension.toDictionary(role.suggestedRole)) - val roleChanged=HashMap() - roleChanged.put("role_change_request",args) + companion object { + fun toDictionary(role: HMSRoleChangeRequest?): HashMap? { + val args = HashMap() + if (role == null)return null + args.put("requested_by", HMSPeerExtension.toDictionary(role.requestedBy)) + args.put("suggested_role", HMSRoleExtension.toDictionary(role.suggestedRole)) + val roleChanged = HashMap() + roleChanged.put("role_change_request", args) return roleChanged } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoleExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoleExtension.kt index c89aad09f..dffe96e1e 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoleExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoleExtension.kt @@ -1,29 +1,25 @@ package live.hms.hmssdk_flutter import android.annotation.SuppressLint -import android.util.Log import live.hms.hmssdk_flutter.hms_role_components.PermissionParamsExtension import live.hms.hmssdk_flutter.hms_role_components.PublishParamsExtension import live.hms.hmssdk_flutter.hms_role_components.SubscribeSettings import live.hms.video.sdk.models.role.HMSRole -import live.hms.video.sdk.models.role.PermissionsParams -import kotlin.jvm.internal.Intrinsics class HMSRoleExtension { - companion object{ + companion object { @SuppressLint("LongLogTag") - fun toDictionary(role:HMSRole?):HashMap?{ + fun toDictionary(role: HMSRole?): HashMap? { + val hashMap = HashMap() + if (role == null)return null - val hashMap=HashMap() - if(role==null)return null - - hashMap["name"] = role?.name?:"unknown" - hashMap["publish_settings"] = PublishParamsExtension.toDictionary(role?.publishParams?:null) - hashMap["subscribe_settings"] = SubscribeSettings.toDictionary(role?.subscribeParams?:null) + hashMap["name"] = role?.name ?: "unknown" + hashMap["publish_settings"] = PublishParamsExtension.toDictionary(role?.publishParams ?: null) + hashMap["subscribe_settings"] = SubscribeSettings.toDictionary(role?.subscribeParams ?: null) hashMap["priority"] = role?.priority!! - hashMap["permissions"] = PermissionParamsExtension.toDictionary(role.permission?:null) + hashMap["permissions"] = PermissionParamsExtension.toDictionary(role.permission ?: null) return hashMap } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoomUpdateExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoomUpdateExtension.kt index 16a9a0578..27d06984f 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoomUpdateExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoomUpdateExtension.kt @@ -1,15 +1,14 @@ package live.hms.hmssdk_flutter -import android.util.Log import live.hms.video.sdk.models.HMSRoom import live.hms.video.sdk.models.enums.HMSRoomUpdate class HMSRoomUpdateExtension { - companion object{ - fun toDictionary(room:HMSRoom?,update: HMSRoomUpdate?):HashMap?{ - val args=HashMap() + companion object { + fun toDictionary(room: HMSRoom?, update: HMSRoomUpdate?): HashMap? { + val args = HashMap() - if (room==null)return null + if (room == null)return null args.put("room", HMSRoomExtension.toDictionary(room)!!) args.put("update", HMSRoomExtension.getValueofHMSRoomUpdate(update)!!) diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRtmpStreamingState.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRtmpStreamingState.kt index aa4a0d9f9..6dae4fa4d 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRtmpStreamingState.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRtmpStreamingState.kt @@ -5,58 +5,60 @@ import live.hms.video.sdk.models.* import java.text.SimpleDateFormat class HMSStreamingState { - companion object{ - fun toDictionary(hmsRtmpStreamingState: HMSRtmpStreamingState?):HashMap?{ - val map = HashMap() - if(hmsRtmpStreamingState == null)return null + companion object { + fun toDictionary(hmsRtmpStreamingState: HMSRtmpStreamingState?): HashMap? { + val map = HashMap() + if (hmsRtmpStreamingState == null)return null map["running"] = hmsRtmpStreamingState.running map["error"] = HMSExceptionExtension.toDictionary(hmsRtmpStreamingState.error) - if(hmsRtmpStreamingState.running) - map["started_at"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsRtmpStreamingState.startedAt).toString() + if (hmsRtmpStreamingState.running) { + map["started_at"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsRtmpStreamingState.startedAt).toString() + } return map } - fun toDictionary(hmsServerRecordingState: HMSServerRecordingState?):HashMap?{ - val map = HashMap() - if(hmsServerRecordingState == null)return null + fun toDictionary(hmsServerRecordingState: HMSServerRecordingState?): HashMap? { + val map = HashMap() + if (hmsServerRecordingState == null)return null map["running"] = hmsServerRecordingState.running map["error"] = HMSExceptionExtension.toDictionary(hmsServerRecordingState.error) - if(hmsServerRecordingState.running) - map["started_at"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsServerRecordingState.startedAt).toString() + if (hmsServerRecordingState.running) { + map["started_at"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsServerRecordingState.startedAt).toString() + } return map } - fun toDictionary(hmsBrowserRecordingState: HMSBrowserRecordingState?):HashMap?{ - val map = HashMap() - if(hmsBrowserRecordingState == null)return null + fun toDictionary(hmsBrowserRecordingState: HMSBrowserRecordingState?): HashMap? { + val map = HashMap() + if (hmsBrowserRecordingState == null)return null map["running"] = hmsBrowserRecordingState.running map["error"] = HMSExceptionExtension.toDictionary(hmsBrowserRecordingState.error) - if(hmsBrowserRecordingState.running) - map["started_at"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsBrowserRecordingState.startedAt).toString() + if (hmsBrowserRecordingState.running) { + map["started_at"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsBrowserRecordingState.startedAt).toString() + } return map } - fun toDictionary(hmsHlsStreamingState: HMSHLSStreamingState?):HashMap?{ - val map = HashMap() - if(hmsHlsStreamingState == null)return null + fun toDictionary(hmsHlsStreamingState: HMSHLSStreamingState?): HashMap? { + val map = HashMap() + if (hmsHlsStreamingState == null)return null map["running"] = hmsHlsStreamingState.running - val args=ArrayList() + val args = ArrayList() hmsHlsStreamingState.variants?.forEach { args.add(HMSHLSVariantExtension.toDictionary(it)!!) } - map["variants"]=args + map["variants"] = args return map } - fun toDictionary(hmsHlsRecordingState: HmsHlsRecordingState?):HashMap?{ - val map = HashMap() - if(hmsHlsRecordingState == null)return null + fun toDictionary(hmsHlsRecordingState: HmsHlsRecordingState?): HashMap? { + val map = HashMap() + if (hmsHlsRecordingState == null)return null map["running"] = hmsHlsRecordingState.running - if(hmsHlsRecordingState.running == true) - map["started_at"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsHlsRecordingState.startedAt).toString() + if (hmsHlsRecordingState.running == true) { + map["started_at"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsHlsRecordingState.startedAt).toString() + } return map } - } - } diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSSessionMetadataExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSSessionMetadataExtension.kt index 25c7bfccb..4a8c8faf6 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSSessionMetadataExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSSessionMetadataExtension.kt @@ -1,14 +1,13 @@ package live.hms.hmssdk_flutter class HMSSessionMetadataExtension { - companion object{ - fun toDictionary(metadata: String?):HashMap?{ - - val hashMap=HashMap() + 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 f8f5c4242..eb48daa83 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSSpeakerExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSSpeakerExtension.kt @@ -3,19 +3,19 @@ package live.hms.hmssdk_flutter import live.hms.video.sdk.models.HMSSpeaker class HMSSpeakerExtension { - companion object{ - fun toDictionary(speaker:HMSSpeaker?):HashMap?{ - val hashMap = HashMap() - if(speaker==null)return null - hashMap.put("audioLevel",speaker.level) + companion object { + fun toDictionary(speaker: HMSSpeaker?): HashMap? { + val hashMap = HashMap() + if (speaker == null)return null + hashMap.put("audioLevel", speaker.level) val hmsTrackMap = HMSTrackExtension.toDictionary(speaker.hmsTrack) val hmsPeerMap = HMSPeerExtension.toDictionary(speaker.peer) - if((hmsTrackMap == null) || (hmsPeerMap == null)){ + if ((hmsTrackMap == null) || (hmsPeerMap == null)) { return null } - hashMap.put("track",hmsTrackMap) - hashMap.put("peer",hmsPeerMap) + hashMap.put("track", hmsTrackMap) + hashMap.put("peer", hmsPeerMap) return hashMap } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackInitStateExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackInitStateExtension.kt index a2a28a3c3..360c689bc 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackInitStateExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackInitStateExtension.kt @@ -3,21 +3,21 @@ 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){ + companion object { + fun getHMSTrackInitStatefromValue(value: String): HMSTrackSettings.InitState { + return when (value) { "MUTED" -> HMSTrackSettings.InitState.MUTED "UNMUTED" -> HMSTrackSettings.InitState.UNMUTED - else->HMSTrackSettings.InitState.MUTED + else -> HMSTrackSettings.InitState.MUTED } } - fun getValueFromHMSTrackInitState(hmsTrackInitState:HMSTrackSettings.InitState):String{ - return when(hmsTrackInitState){ - HMSTrackSettings.InitState.UNMUTED-> "UNMUTED" - HMSTrackSettings.InitState.MUTED-> "MUTED" - else->"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 76887b108..19870df19 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackSettingsExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackSettingsExtension.kt @@ -1,9 +1,5 @@ package live.hms.hmssdk_flutter -import live.hms.hmssdk_flutter.hms_role_components.AudioParamsExtension -import live.hms.hmssdk_flutter.hms_role_components.VideoParamsExtension -import live.hms.video.media.codec.HMSAudioCodec -import live.hms.video.media.codec.HMSVideoCodec import live.hms.video.media.settings.HMSAudioTrackSettings import live.hms.video.media.settings.HMSTrackSettings import live.hms.video.media.settings.HMSVideoTrackSettings @@ -11,25 +7,23 @@ import live.hms.video.sdk.HMSSDK class HMSTrackSettingsExtension { - companion object{ - fun toDictionary(hmssdk: HMSSDK):HashMap?{ + companion object { + fun toDictionary(hmssdk: HMSSDK): HashMap? { + val map = HashMap() + val hmsTrackSettings: HMSTrackSettings = hmssdk.hmsSettings - val map = HashMap(); - val hmsTrackSettings:HMSTrackSettings = hmssdk.hmsSettings; - - if(hmsTrackSettings.videoSettings != null){ + if (hmsTrackSettings.videoSettings != null) { map["video_track_setting"] = HMSVideoTrackSettingsExtension.toDictionary(hmsTrackSettings.videoSettings)!! } - if(hmsTrackSettings.audioSettings != null){ + if (hmsTrackSettings.audioSettings != null) { map["audio_track_setting"] = HMSAudioTrackSettingsExtension.toDictionary(hmsTrackSettings.audioSettings)!! } return map } - fun setTrackSettings(hmsAudioTrackHashMap:HashMap?,hmsVideoTrackHashMap: HashMap?):HMSTrackSettings{ - + fun setTrackSettings(hmsAudioTrackHashMap: HashMap?, hmsVideoTrackHashMap: HashMap?): HMSTrackSettings { var hmsAudioTrackSettings = HMSAudioTrackSettings.Builder() if (hmsAudioTrackHashMap != null) { val useHardwareAcousticEchoCanceler = @@ -37,35 +31,42 @@ class HMSTrackSettingsExtension { val initialState = HMSTrackInitStateExtension.getHMSTrackInitStatefromValue(hmsAudioTrackHashMap["track_initial_state"] as String) - if (useHardwareAcousticEchoCanceler != null) { - hmsAudioTrackSettings = hmsAudioTrackSettings.setUseHardwareAcousticEchoCanceler( - useHardwareAcousticEchoCanceler + /** + * Setting hardware acoustic echo canceler to false by default + * If no value is passed from flutter + */ + hmsAudioTrackSettings = if (useHardwareAcousticEchoCanceler != null) { + hmsAudioTrackSettings.setUseHardwareAcousticEchoCanceler( + useHardwareAcousticEchoCanceler, + ) + } else { + hmsAudioTrackSettings.setUseHardwareAcousticEchoCanceler( + false, ) } - if(initialState != null){ + if (initialState != null) { hmsAudioTrackSettings = hmsAudioTrackSettings.initialState(initialState) } } var hmsVideoTrackSettings = HMSVideoTrackSettings.Builder() if (hmsVideoTrackHashMap != null) { - - val cameraFacing = getHMSCameraFacingFromValue(hmsVideoTrackHashMap["camera_facing"] as String?) + 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(cameraFacing != null){ + if (cameraFacing != null) { hmsVideoTrackSettings = hmsVideoTrackSettings.cameraFacing(cameraFacing) } - if(disableAutoResize != null){ - hmsVideoTrackSettings = hmsVideoTrackSettings.disableAutoResize(disableAutoResize); + if (disableAutoResize != null) { + hmsVideoTrackSettings = hmsVideoTrackSettings.disableAutoResize(disableAutoResize) } - if(initialState != null){ + if (initialState != null) { hmsVideoTrackSettings = hmsVideoTrackSettings.initialState(initialState) } - if(forceSoftwareDecoder != null){ + if (forceSoftwareDecoder != null) { hmsVideoTrackSettings = hmsVideoTrackSettings.forceSoftwareDecoder(forceSoftwareDecoder) } } @@ -73,12 +74,12 @@ class HMSTrackSettingsExtension { return HMSTrackSettings.Builder().audio(hmsAudioTrackSettings.build()).video(hmsVideoTrackSettings.build()).build() } - private fun getHMSCameraFacingFromValue(cameraFacing:String?):HMSVideoTrackSettings.CameraFacing{ - return when(cameraFacing){ + 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/HMSTrackUpdateExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackUpdateExtension.kt index 707c3f11a..eb86f2321 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackUpdateExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackUpdateExtension.kt @@ -1,21 +1,20 @@ package live.hms.hmssdk_flutter -import android.util.Log import live.hms.video.media.tracks.HMSTrack import live.hms.video.sdk.models.HMSPeer import live.hms.video.sdk.models.enums.HMSTrackUpdate class HMSTrackUpdateExtension { - companion object{ - fun toDictionary(peer:HMSPeer?,track:HMSTrack?,update: HMSTrackUpdate?):HashMap?{ - val hashMap=HashMap() + companion object { + fun toDictionary(peer: HMSPeer?, track: HMSTrack?, update: HMSTrackUpdate?): HashMap? { + val hashMap = HashMap() - if(peer==null || track==null || update==null)return null + if (peer == null || track == null || update == null)return null - hashMap["peer"] = HMSPeerExtension.toDictionary(peer)!! - hashMap["track"] = HMSTrackExtension.toDictionary(track)!! + hashMap["peer"] = HMSPeerExtension.toDictionary(peer)!! + hashMap["track"] = HMSTrackExtension.toDictionary(track)!! hashMap["update"] = HMSTrackExtension.getTrackUpdateInString(update)!! return hashMap } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSVideoResolutionExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSVideoResolutionExtension.kt index 69e5720a8..0a1de7e9b 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSVideoResolutionExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSVideoResolutionExtension.kt @@ -4,16 +4,15 @@ import live.hms.video.media.settings.HMSVideoResolution class HMSVideoResolutionExtension { - companion object{ + companion object { - fun toDictionary(hmsVideoResolution: HMSVideoResolution?):HashMap?{ + fun toDictionary(hmsVideoResolution: HMSVideoResolution?): HashMap? { + val args = HashMap() + if (hmsVideoResolution == null) return null + args["height"] = hmsVideoResolution.height / 1.0 + args["width"] = hmsVideoResolution.width / 1.0 - val args=HashMap() - if(hmsVideoResolution == null) return null - args["height"] = hmsVideoResolution.height/1.0 - args["width"] = hmsVideoResolution.width/1.0 - - return args; + return args } } -} \ 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 1826aa7ed..a9369731c 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSVideoTrackSettingsExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSVideoTrackSettingsExtension.kt @@ -1,32 +1,26 @@ package live.hms.hmssdk_flutter -import live.hms.video.media.codec.HMSVideoCodec import live.hms.video.media.settings.HMSVideoTrackSettings -import live.hms.video.media.tracks.HMSTrack -import live.hms.video.media.tracks.HMSVideoTrack -import live.hms.video.utils.HMSLogger class HMSVideoTrackSettingsExtension { - companion object{ - fun toDictionary(hmsVideoTrackSettings: HMSVideoTrackSettings?):HashMap?{ - val map = HashMap() + companion object { + fun toDictionary(hmsVideoTrackSettings: HMSVideoTrackSettings?): HashMap? { + val map = HashMap() map["camera_facing"] = getValueOfHMSCameraFacing(hmsVideoTrackSettings?.cameraFacing)!! map["disable_auto_resize"] = hmsVideoTrackSettings?.disableAutoResize!! map["track_initial_state"] = HMSTrackInitStateExtension.getValueFromHMSTrackInitState(hmsVideoTrackSettings.initialState) map["force_software_decoder"] = hmsVideoTrackSettings.forceSoftwareDecoder - return map + return map } + private fun getValueOfHMSCameraFacing(cameraFacing: HMSVideoTrackSettings.CameraFacing?): String? { + if (cameraFacing == null)return null - private fun getValueOfHMSCameraFacing(cameraFacing: HMSVideoTrackSettings.CameraFacing?):String?{ - if(cameraFacing==null)return null - - return when(cameraFacing){ - HMSVideoTrackSettings.CameraFacing.BACK-> "back" - HMSVideoTrackSettings.CameraFacing.FRONT->"front" - else-> "default" + return when (cameraFacing) { + HMSVideoTrackSettings.CameraFacing.BACK -> "back" + HMSVideoTrackSettings.CameraFacing.FRONT -> "front" + else -> "default" } } - } -} \ 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 599f42ed5..92f244765 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt @@ -9,6 +9,7 @@ import android.media.projection.MediaProjectionManager import android.os.Build import android.util.Log import androidx.annotation.NonNull +import com.google.gson.JsonElement import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding @@ -20,7 +21,10 @@ import io.flutter.plugin.common.MethodChannel.Result import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import live.hms.hmssdk_flutter.Constants.Companion.METHOD_CALL +import live.hms.hmssdk_flutter.hls_player.HMSHLSPlayerAction import live.hms.hmssdk_flutter.methods.* +import live.hms.hmssdk_flutter.views.HMSHLSPlayerFactory import live.hms.hmssdk_flutter.views.HMSVideoViewFactory import live.hms.video.audio.HMSAudioManager.* import live.hms.video.connection.stats.* @@ -55,18 +59,22 @@ class HmssdkFlutterPlugin : private var logsEventChannel: EventChannel? = null private var rtcStatsChannel: EventChannel? = null private var sessionStoreChannel: EventChannel? = null + var hlsPlayerChannel: EventChannel? = null private var eventSink: EventChannel.EventSink? = null private var previewSink: EventChannel.EventSink? = null private var logsSink: EventChannel.EventSink? = null private var rtcSink: EventChannel.EventSink? = null private var sessionStoreSink: EventChannel.EventSink? = null + var hlsPlayerSink: EventChannel.EventSink? = null private lateinit var activity: Activity var hmssdk: HMSSDK? = null private lateinit var hmsVideoFactory: HMSVideoViewFactory + private lateinit var hmsHLSPlayerFactory: HMSHLSPlayerFactory private var requestChange: HMSRoleChangeRequest? = null var hmssdkFlutterPlugin: HmssdkFlutterPlugin? = null private var hmsSessionStore: HmsSessionStore? = null private var hmsKeyChangeObserverList = ArrayList() + var hlsStreamUrl: String? = null override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { if (hmssdkFlutterPlugin == null) { @@ -85,18 +93,28 @@ class HmssdkFlutterPlugin : this.sessionStoreChannel = EventChannel(flutterPluginBinding.binaryMessenger, "session_event_channel") + this.hlsPlayerChannel = + EventChannel(flutterPluginBinding.binaryMessenger, "hls_player_channel") + this.meetingEventChannel?.setStreamHandler(this) ?: Log.e("Channel Error", "Meeting event channel not found") this.channel?.setMethodCallHandler(this) ?: Log.e("Channel Error", "Event channel not found") this.previewChannel?.setStreamHandler(this) ?: Log.e("Channel Error", "Preview channel not found") this.logsEventChannel?.setStreamHandler(this) ?: Log.e("Channel Error", "Logs event channel not found") this.rtcStatsChannel?.setStreamHandler(this) ?: Log.e("Channel Error", "RTC Stats channel not found") this.sessionStoreChannel?.setStreamHandler(this) ?: Log.e("Channel Error", "Session Store channel not found") + this.hlsPlayerChannel?.setStreamHandler(this) ?: Log.e("Channel Error", "HLS Player channel not found") this.hmsVideoFactory = HMSVideoViewFactory(this) + this.hmsHLSPlayerFactory = HMSHLSPlayerFactory(this) flutterPluginBinding.platformViewRegistry.registerViewFactory( "HMSVideoView", hmsVideoFactory, ) + + flutterPluginBinding.platformViewRegistry.registerViewFactory( + "HMSHLSPlayer", + hmsHLSPlayerFactory, + ) hmssdkFlutterPlugin = this } else { Log.e("Plugin Warning", "hmssdkFlutterPlugin already exists in onAttachedToEngine") @@ -150,7 +168,7 @@ class HmssdkFlutterPlugin : } // MARK: HLS - "hls_start_streaming", "hls_stop_streaming" -> { + "hls_start_streaming", "hls_stop_streaming", "send_hls_timed_metadata" -> { HMSHLSAction.hlsActions(call, result, hmssdk!!) } @@ -183,9 +201,7 @@ class HmssdkFlutterPlugin : "get_track_settings" -> { trackSettings(call, result) } - "get_session_metadata", "set_session_metadata" -> { - HMSSessionMetadataAction.sessionMetadataActions(call, result, hmssdk!!) - } + "set_playback_allowed_for_track" -> { setPlaybackAllowedForTrack(call, result) } @@ -208,7 +224,10 @@ class HmssdkFlutterPlugin : addKeyChangeListener(call, result) } "remove_key_change_listener" -> { - removeKeyChangeListener(call,result) + removeKeyChangeListener(call, result) + } + "start_hls_player", "stop_hls_player", "pause_hls_player", "resume_hls_player", "seek_to_live_position", "seek_forward", "seek_backward", "set_hls_player_volume", "add_hls_stats_listener", "remove_hls_stats_listener" -> { + HMSHLSPlayerAction.hlsPlayerAction(call, result, activity) } else -> { result.notImplemented() @@ -384,11 +403,13 @@ class HmssdkFlutterPlugin : logsEventChannel?.setStreamHandler(null) ?: Log.e("Channel Error", "Logs event channel not found") rtcStatsChannel?.setStreamHandler(null) ?: Log.e("Channel Error", "RTC Stats channel not found") sessionStoreChannel?.setStreamHandler(null) ?: Log.e("Channel Error", "Session Store channel not found") + hlsPlayerChannel?.setStreamHandler(null) ?: Log.e("Channel Error", "HLS Player channel not found") eventSink = null previewSink = null rtcSink = null logsSink = null sessionStoreSink = null + hlsPlayerSink = null hmssdkFlutterPlugin = null } else { Log.e("Plugin Error", "hmssdkFlutterPlugin is null in onDetachedFromEngine") @@ -491,6 +512,8 @@ class HmssdkFlutterPlugin : this.rtcSink = events } else if (nameOfEventSink == "session_store") { this.sessionStoreSink = events + } else if (nameOfEventSink == "hls_player") { + this.hlsPlayerSink = events } } @@ -796,6 +819,23 @@ class HmssdkFlutterPlugin : override fun onJoin(room: HMSRoom) { hmssdk!!.addAudioObserver(hmsAudioListener) + + /** + * This sets the [hlsStreamUrl] variable to + * fetch the stream URL directly from onRoomUpdate + * This helps to play the HLS Stream even if user doesn't send + * the stream URL. + */ + room.hlsStreamingState?.let { streamingState -> + if (streamingState.running) { + streamingState.variants?.let { variants -> + if (variants.isNotEmpty()) { + hlsStreamUrl = variants[0].hlsStreamUrl + } + } + } + } + val args = HashMap() args.put("event_name", "on_join_room") @@ -822,14 +862,13 @@ class HmssdkFlutterPlugin : } override fun onPeerUpdate(type: HMSPeerUpdate, peer: HMSPeer) { - if (type == HMSPeerUpdate.AUDIO_TOGGLED || type == HMSPeerUpdate.VIDEO_TOGGLED || - type == HMSPeerUpdate.BECAME_DOMINANT_SPEAKER || type == HMSPeerUpdate.NO_DOMINANT_SPEAKER || - type == HMSPeerUpdate.RESIGNED_DOMINANT_SPEAKER || type == HMSPeerUpdate.STARTED_SPEAKING || - type == HMSPeerUpdate.STOPPED_SPEAKING - ) { + /** + * Since these methods are not part of HMSPeerUpdate enum in flutter + * We return the method call and don't send update to flutter layer + */ + if (type == HMSPeerUpdate.BECAME_DOMINANT_SPEAKER || type == HMSPeerUpdate.NO_DOMINANT_SPEAKER) { return } - val args = HashMap() args["event_name"] = "on_peer_update" @@ -843,6 +882,23 @@ class HmssdkFlutterPlugin : } override fun onRoomUpdate(type: HMSRoomUpdate, hmsRoom: HMSRoom) { + /** + * This sets the [hlsStreamUrl] variable to + * fetch the stream URL directly from onRoomUpdate + * This helps to play the HLS Stream even if user doesn't send + * the stream URL. + */ + if (type == HMSRoomUpdate.HLS_STREAMING_STATE_UPDATED) { + hmsRoom.hlsStreamingState?.let { streamingState -> + if (streamingState.running) { + streamingState.variants?.let { variants -> + if (variants.isNotEmpty()) { + hlsStreamUrl = variants[0].hlsStreamUrl + } + } + } + } + } val args = HashMap() args.put("event_name", "on_room_update") args.put("data", HMSRoomUpdateExtension.toDictionary(hmsRoom, type)) @@ -934,11 +990,11 @@ class HmssdkFlutterPlugin : } override fun onPeerUpdate(type: HMSPeerUpdate, peer: HMSPeer) { - if (type == HMSPeerUpdate.AUDIO_TOGGLED || type == HMSPeerUpdate.VIDEO_TOGGLED || - type == HMSPeerUpdate.BECAME_DOMINANT_SPEAKER || type == HMSPeerUpdate.NO_DOMINANT_SPEAKER || - type == HMSPeerUpdate.RESIGNED_DOMINANT_SPEAKER || type == HMSPeerUpdate.STARTED_SPEAKING || - type == HMSPeerUpdate.STOPPED_SPEAKING - ) { + /** + * Since these methods are not part of HMSPeerUpdate enum in flutter + * We return the method call and don't send update to flutter layer + */ + if (type == HMSPeerUpdate.BECAME_DOMINANT_SPEAKER || type == HMSPeerUpdate.NO_DOMINANT_SPEAKER) { return } @@ -1034,11 +1090,19 @@ class HmssdkFlutterPlugin : ) } - public fun onVideoViewError(args: HashMap) { - if (args["data"] != null) { - CoroutineScope(Dispatchers.Main).launch { - eventSink?.success(args) - } + fun onVideoViewError(methodName: String, error: String, errorMessage: String) { + val args = HashMap() + args["event_name"] = "on_error" + val hmsException = HMSException( + action = "Check the logs for more info", + code = 6004, + description = error, + message = errorMessage, + name = "$methodName Error", + ) + args["data"] = HMSExceptionExtension.toDictionary(hmsException) + CoroutineScope(Dispatchers.Main).launch { + eventSink?.success(args) } } @@ -1170,7 +1234,7 @@ class HmssdkFlutterPlugin : val trackId: String? = call.argument("track_id") if (trackId != null) { hmsVideoViewResult = result - activity.sendBroadcast(Intent(trackId).putExtra("method_name", "CAPTURE_SNAPSHOT")) + activity.sendBroadcast(Intent(trackId).putExtra(METHOD_CALL, "CAPTURE_SNAPSHOT")) } } @@ -1228,17 +1292,31 @@ class HmssdkFlutterPlugin : uid?.let { val keyChangeListener = object : HMSKeyChangeListener { - override fun onKeyChanged(key: String, value: Any?) { + override fun onKeyChanged(key: String, value: JsonElement?) { val args = HashMap() args["event_name"] = "on_key_changed" val newData = HashMap() newData["key"] = key - if (value is String?) { - newData["value"] = value - } else { - HMSErrorLogger.logError("onKeyChanged", "Session metadata type is not compatible, Please use String? type while setting metadata", "Type Incompatibility Error") + + /** + * Here depending on the value we parse the JsonElement + * if it's a JsonPrimitive we parse it as String and then send to flutter + * if it's a JsonObject,JsonArray we convert it to String and then send to flutter + * if it's a JsonNull we send it as null + */ + + value?.let { + if (it.isJsonPrimitive) { + newData["value"] = value.asString + } else if (it.isJsonNull) { + newData["value"] = null + } else { + newData["value"] = value.toString() + } + } ?: run { newData["value"] = null } + newData["uid"] = uid as String args["data"] = newData CoroutineScope(Dispatchers.Main).launch { @@ -1258,14 +1336,14 @@ class HmssdkFlutterPlugin : * This method is used to remove the attached key change listeners * attached using [addKeyChangeListener] method */ - private fun removeKeyChangeListener(call: MethodCall,result: Result) { + private fun removeKeyChangeListener(call: MethodCall, result: Result) { val uid = call.argument("uid") ?: run { HMSErrorLogger.returnArgumentsError("uid is null") } - //There is no need to call removeKeyChangeListener since - //there is no keyChangeListener attached - if(hmsKeyChangeObserverList.isEmpty()){ - result.success(HMSResultExtension.toDictionary(true,null)) + // There is no need to call removeKeyChangeListener since + // there is no keyChangeListener attached + if (hmsKeyChangeObserverList.isEmpty()) { + result.success(HMSResultExtension.toDictionary(true, null)) return } @@ -1275,12 +1353,12 @@ class HmssdkFlutterPlugin : if (hmsKeyChangeObserver.uid == uid) { hmsSessionStore?.removeKeyChangeListener(hmsKeyChangeObserver.keyChangeListener) hmsKeyChangeObserverList.remove(hmsKeyChangeObserver) - result.success(HMSResultExtension.toDictionary(true,null)) + result.success(HMSResultExtension.toDictionary(true, null)) return } } - }?: run { - result.success(HMSResultExtension.toDictionary(false,"keyChangeListener uid is null")) + } ?: run { + result.success(HMSResultExtension.toDictionary(false, "keyChangeListener uid is null")) } } diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HLSStatsHandler.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HLSStatsHandler.kt new file mode 100644 index 000000000..ef8e447e6 --- /dev/null +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HLSStatsHandler.kt @@ -0,0 +1,82 @@ +package live.hms.hmssdk_flutter.hls_player + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import live.hms.hls_player.HmsHlsPlayer +import live.hms.hmssdk_flutter.HMSErrorLogger +import live.hms.hmssdk_flutter.HMSExceptionExtension +import live.hms.hmssdk_flutter.HmssdkFlutterPlugin +import live.hms.stats.PlayerStatsListener +import live.hms.stats.model.PlayerStatsModel +import live.hms.video.error.HMSException + +/** + * This class handles the HLS Player stats + */ +class HLSStatsHandler { + + companion object { + + /** + * Adds an HLS stats listener to the HLS player. + * This listener captures HLS player errors and updates in player stats. + * + * @param hmssdkFlutterPlugin The HMSSDK Flutter plugin instance. + * @param hlsPlayer The HLS player instance. + */ + fun addHLSStatsListener(hmssdkFlutterPlugin: HmssdkFlutterPlugin?, hlsPlayer: HmsHlsPlayer?) { + hlsPlayer?.setStatsMonitor(object : PlayerStatsListener { + + /** + * Callback method triggered when an HLS error occurs during playback. + * + * @param error The HMSException representing the error. + */ + override fun onError(error: HMSException) { + val hashMap: HashMap = HashMap() + hmssdkFlutterPlugin?.let { plugin -> + hashMap["event_name"] = "on_hls_error" + hashMap["data"] = HMSExceptionExtension.toDictionary(error) + if (hashMap["data"] != null) { + CoroutineScope(Dispatchers.Main).launch { + plugin.hlsPlayerSink?.success(hashMap) + } + } + } + } + + /** + * Callback method triggered when an HLS player event update occurs. + * + * @param playerStatsModel The PlayerStatsModel containing the updated player statistics. + */ + override fun onEventUpdate(playerStatsModel: PlayerStatsModel) { + val hashMap: HashMap = HashMap() + hmssdkFlutterPlugin?.let { plugin -> + hashMap["event_name"] = "on_hls_event_update" + hashMap["data"] = HMSPlayerStatsExtension.toDictionary(playerStatsModel) + if (hashMap["data"] != null) { + CoroutineScope(Dispatchers.Main).launch { + plugin.hlsPlayerSink?.success(hashMap) + } + } + } + } + }) ?: run { + HMSErrorLogger.logError("addHLSStatsListener", "hlsPlayer is null, Consider calling this method after attaching the HMSHLSPlayer or sending isHLSStatsRequired as true to get the stats", "NULL_ERROR") + } + } + + /** + * Removes the HLS stats listener from the HLS player. + * + * @param hlsPlayer The HLS player instance. + */ + fun removeStatsListener(hlsPlayer: HmsHlsPlayer?) { + hlsPlayer?.setStatsMonitor(null) ?: run { + HMSErrorLogger.logError("removeStatsListener", "hlsPlayer is null", "NULL_ERROR") + } + } + } +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSCueExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSCueExtension.kt new file mode 100644 index 000000000..93f4af18a --- /dev/null +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSCueExtension.kt @@ -0,0 +1,20 @@ +package live.hms.hmssdk_flutter.hls_player + +import live.hms.hls_player.HmsHlsCue +import java.text.SimpleDateFormat + +class HMSHLSCueExtension { + + companion object { + fun toDictionary(hmsHlsCue: HmsHlsCue): HashMap { + val args = HashMap() + + args["id"] = hmsHlsCue.id + args["start_date"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsHlsCue.startDate).toString() + args["end_date"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsHlsCue.endDate).toString() + args["payload"] = hmsHlsCue.payloadval + + return args + } + } +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlaybackStateExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlaybackStateExtension.kt new file mode 100644 index 000000000..2def96d3f --- /dev/null +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlaybackStateExtension.kt @@ -0,0 +1,20 @@ +package live.hms.hmssdk_flutter.hls_player + +import live.hms.hls_player.HmsHlsPlaybackState + +class HMSHLSPlaybackStateExtension { + companion object { + fun toDictionary(hmsHlsPlaybackState: HmsHlsPlaybackState): HashMap { + val args = HashMap() + args["playback_state"] = when (hmsHlsPlaybackState) { + HmsHlsPlaybackState.playing -> "playing" + HmsHlsPlaybackState.stopped -> "stopped" + HmsHlsPlaybackState.paused -> "paused" + HmsHlsPlaybackState.buffering -> "buffering" + HmsHlsPlaybackState.failed -> "failed" + else -> "unknown" + } + return args + } + } +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlayerAction.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlayerAction.kt new file mode 100644 index 000000000..4d3fbe821 --- /dev/null +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlayerAction.kt @@ -0,0 +1,171 @@ +package live.hms.hmssdk_flutter.hls_player + +import android.app.Activity +import android.content.Intent +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel.Result +import live.hms.hmssdk_flutter.Constants.Companion.HLS_PLAYER_INTENT +import live.hms.hmssdk_flutter.Constants.Companion.METHOD_CALL +import live.hms.hmssdk_flutter.HMSErrorLogger + +/** + * This class is used to send actions from flutter plugin to HLS Player + * We use broadcast receiver to forward the request to HMSHLSPlayer + */ +class HMSHLSPlayerAction { + companion object { + fun hlsPlayerAction(call: MethodCall, result: Result, activity: Activity) { + when (call.method) { + "start_hls_player" -> start(call, result, activity) + "stop_hls_player" -> stop(result, activity) + "pause_hls_player" -> pause(result, activity) + "resume_hls_player" -> resume(result, activity) + "seek_to_live_position" -> seekToLivePosition(result, activity) + "seek_forward" -> seekForward(call, result, activity) + "seek_backward" -> seekBackward(call, result, activity) + "set_hls_player_volume" -> setVolume(call, result, activity) + "add_hls_stats_listener" -> addHLSStatsListener(result, activity) + "remove_hls_stats_listener" -> removeHLSStatsListener(result, activity) + else -> { + result.notImplemented() + } + } + } + + /** + * Starts the HLS player by sending a broadcast intent with the specified method call and HLS URL. + * + * @param call The method call object containing the HLS URL as an argument. + * @param result The result object to be returned after starting the player. + * @param activity The current activity from which the method is called. + */ + private fun start(call: MethodCall, result: Result, activity: Activity) { + val hlsUrl = call.argument("hls_url") + activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "start_hls_player").putExtra("hls_url", hlsUrl)) + result.success(null) + } + + /** + * Stops the HLS player by sending a broadcast intent with the specified method call. + * + * @param result The result object to be returned after stopping the player. + * @param activity The current activity from which the method is called. + */ + private fun stop(result: Result, activity: Activity) { + activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "stop_hls_player")) + result.success(null) + } + + /** + * Pauses the HLS player by sending a broadcast intent with the specified method call. + * + * @param result The result object to be returned after pausing the player. + * @param activity The current activity from which the method is called. + */ + private fun pause(result: Result, activity: Activity) { + activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "pause_hls_player")) + result.success(null) + } + + /** + * Resumes the HLS player by sending a broadcast intent with the specified method call. + * + * @param result The result object to be returned after resuming the player. + * @param activity The current activity from which the method is called. + */ + private fun resume(result: Result, activity: Activity) { + activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "resume_hls_player")) + result.success(null) + } + + /** + * Seeks to the live position in the HLS player by sending a broadcast intent with the specified method call. + * + * @param result The result object to be returned after seeking to the live position. + * @param activity The current activity from which the method is called. + */ + private fun seekToLivePosition(result: Result, activity: Activity) { + activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "seek_to_live_position")) + result.success(null) + } + + /** + * Seeks forward in the HLS player by the specified number of seconds, sending a broadcast intent with the seek duration. + * + * @param call The method call object containing the number of seconds to seek forward as an argument. + * @param result The result object to be returned after seeking forward. + * @param activity The current activity from which the method is called. + */ + private fun seekForward(call: MethodCall, result: Result, activity: Activity) { + val seconds: Int? = call.argument("seconds") ?: run { + HMSErrorLogger.returnArgumentsError("seconds parameter is null in seekForward method") + null + } + + seconds?.let { + activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "seek_forward").putExtra("seconds", seconds)) + } + result.success(null) + } + + /** + * Seeks backward in the HLS player by the specified number of seconds, sending a broadcast intent with the seek duration. + * + * @param call The method call object containing the number of seconds to seek backward as an argument. + * @param result The result object to be returned after seeking backward. + * @param activity The current activity from which the method is called. + */ + private fun seekBackward(call: MethodCall, result: Result, activity: Activity) { + val seconds: Int? = call.argument("seconds") ?: run { + HMSErrorLogger.returnArgumentsError("seconds parameter is null in seekBackward method") + null + } + + seconds?.let { + activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "seek_backward").putExtra("seconds", seconds)) + } + result.success(null) + } + + /** + * Sets the volume level of the HLS player by sending a broadcast intent with the specified volume value. + * + * @param call The method call object containing the volume level as an argument. + * @param result The result object to be returned after setting the volume. + * @param activity The current activity from which the method is called. + */ + private fun setVolume(call: MethodCall, result: Result, activity: Activity) { + val volume: Int? = call.argument("volume") ?: run { + HMSErrorLogger.returnArgumentsError("Volume parameter is null in setVolume method") + null + } + + volume?.let { + activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "set_volume").putExtra("volume", volume)) + } + result.success(null) + } + + /** + * Adds a listener to receive HLS player statistics by sending a broadcast intent with the corresponding method call. + * + * @param result The result object to be returned after adding the HLS stats listener. + * @param activity The current activity from which the method is called. + */ + private fun addHLSStatsListener(result: Result, activity: Activity) { + activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "add_hls_stats_listener")) + result.success(null) + } + + /** + * Removes the listener for HLS player statistics by sending a broadcast intent with the corresponding method call. + * + * @param result The result object to be returned after removing the HLS stats listener. + * @param activity The current activity from which the method is called. + */ + private fun removeHLSStatsListener(result: Result, activity: Activity) { + activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "remove_hls_stats_listener")) + result.success(null) + } + } +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSPlayerStatsExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSPlayerStatsExtension.kt new file mode 100644 index 000000000..eff349c56 --- /dev/null +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSPlayerStatsExtension.kt @@ -0,0 +1,22 @@ +package live.hms.hmssdk_flutter.hls_player + +import live.hms.stats.model.PlayerStatsModel + +class HMSPlayerStatsExtension { + + companion object { + fun toDictionary(hmsPlayerStatsModel: PlayerStatsModel): Map { + val args = HashMap() + + args["bandwidth_estimate"] = hmsPlayerStatsModel.bandwidth.bandWidthEstimate + args["total_bytes_loaded"] = hmsPlayerStatsModel.bandwidth.totalBytesLoaded + args["buffered_duration"] = hmsPlayerStatsModel.bufferedDuration + args["distance_from_live"] = hmsPlayerStatsModel.distanceFromLive + args["dropped_frame_count"] = hmsPlayerStatsModel.frameInfo.droppedFrameCount + args["video_height"] = hmsPlayerStatsModel.videoInfo.videoHeight + args["video_width"] = hmsPlayerStatsModel.videoInfo.videoWidth + args["average_bitrate"] = hmsPlayerStatsModel.videoInfo.averageBitrate + return args + } + } +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/AudioParamsExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/AudioParamsExtension.kt index 9ad7a10cf..96ddf7e81 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/AudioParamsExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/AudioParamsExtension.kt @@ -3,32 +3,32 @@ package live.hms.hmssdk_flutter.hms_role_components import live.hms.video.media.codec.HMSAudioCodec import live.hms.video.sdk.models.role.AudioParams -class AudioParamsExtension{ - companion object{ - fun toDictionary(audioParams: AudioParams):HashMap? { - val args=HashMap() - if(audioParams==null || audioParams.codec==null)return null - args.put("bit_rate",audioParams.bitRate) +class AudioParamsExtension { + companion object { + fun toDictionary(audioParams: AudioParams): HashMap? { + val args = HashMap() + if (audioParams == null || audioParams.codec == null)return null + args.put("bit_rate", audioParams.bitRate) args.put("codec", getValueOfHMSAudioCodec(audioParams.codec)) return args } - fun getValueOfHMSAudioCodec (codec: HMSAudioCodec):String{ + fun getValueOfHMSAudioCodec(codec: HMSAudioCodec): String { return when (codec) { - HMSAudioCodec.OPUS-> + HMSAudioCodec.OPUS -> "opus" - else-> + else -> "defaultCodec" } } - fun getValueOfHMSAudioCodecFromString (codec:String?):HMSAudioCodec?{ + fun getValueOfHMSAudioCodecFromString(codec: String?): HMSAudioCodec? { return when (codec) { - "opus"-> + "opus" -> HMSAudioCodec.OPUS - else-> + else -> null } } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/PermissionParamsExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/PermissionParamsExtension.kt index 46fc659f7..a7123ba96 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/PermissionParamsExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/PermissionParamsExtension.kt @@ -3,10 +3,10 @@ package live.hms.hmssdk_flutter.hms_role_components import live.hms.video.sdk.models.role.PermissionsParams class PermissionParamsExtension { - companion object{ - fun toDictionary(permissionsParams: PermissionsParams?):HashMap?{ - val args=HashMap() - if(permissionsParams==null)return null + companion object { + fun toDictionary(permissionsParams: PermissionsParams?): HashMap? { + val args = HashMap() + if (permissionsParams == null)return null args["browser_recording"] = permissionsParams.browserRecording args["change_role"] = permissionsParams.changeRole args["end_room"] = permissionsParams.endRoom @@ -18,4 +18,4 @@ class PermissionParamsExtension { return args } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/VideoParamsExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/VideoParamsExtension.kt index 4dbb3a725..fd9c7a7f7 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/VideoParamsExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/VideoParamsExtension.kt @@ -1,47 +1,46 @@ package live.hms.hmssdk_flutter.hms_role_components -import live.hms.video.media.codec.HMSAudioCodec import live.hms.video.media.codec.HMSVideoCodec import live.hms.video.sdk.models.role.VideoParams -class VideoParamsExtension{ +class VideoParamsExtension { - companion object{ - fun toDictionary(videoParams: VideoParams):HashMap?{ - val args=HashMap() - if (videoParams==null || videoParams.codec==null)return null - args.put("bit_rate",videoParams.bitRate) - args.put("frame_rate",videoParams.frameRate) - args.put("width",videoParams.width) - args.put("height",videoParams.height) + companion object { + fun toDictionary(videoParams: VideoParams): HashMap? { + val args = HashMap() + if (videoParams == null || videoParams.codec == null)return null + args.put("bit_rate", videoParams.bitRate) + args.put("frame_rate", videoParams.frameRate) + args.put("width", videoParams.width) + args.put("height", videoParams.height) args.put("codec", getValueOfHMSAudioCodec(videoParams.codec)) return args } - fun getValueOfHMSAudioCodec (codec: HMSVideoCodec):String{ + fun getValueOfHMSAudioCodec(codec: HMSVideoCodec): String { return when (codec) { - HMSVideoCodec.H264-> + HMSVideoCodec.H264 -> "h264" - HMSVideoCodec.VP8-> + HMSVideoCodec.VP8 -> "vp8" - HMSVideoCodec.VP9-> + HMSVideoCodec.VP9 -> "vp9" - else-> + else -> "defaultCodec" } } - fun getValueOfHMSAudioCodecFromString(codec: String?):HMSVideoCodec?{ + fun getValueOfHMSAudioCodecFromString(codec: String?): HMSVideoCodec? { return when (codec) { - "h264"->HMSVideoCodec.H264 + "h264" -> HMSVideoCodec.H264 - "vp8"->HMSVideoCodec.VP8 + "vp8" -> HMSVideoCodec.VP8 - "vp9"->HMSVideoCodec.VP9 + "vp9" -> HMSVideoCodec.VP9 - else-> + else -> null } } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSAudioDeviceAction.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSAudioDeviceAction.kt index 559035eb7..59ff6c3f4 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSAudioDeviceAction.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSAudioDeviceAction.kt @@ -1,5 +1,4 @@ package live.hms.hmssdk_flutter -import android.util.Log import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel.Result import kotlinx.coroutines.CoroutineScope @@ -10,20 +9,18 @@ import live.hms.video.media.tracks.* import live.hms.video.sdk.* import live.hms.video.sdk.models.* - - class HMSAudioDeviceAction { companion object { - fun audioDeviceActions(call: MethodCall, result: Result,hmssdk:HMSSDK) { + fun audioDeviceActions(call: MethodCall, result: Result, hmssdk: HMSSDK) { when (call.method) { "get_audio_devices_list" -> { - getAudioDevicesList(call, result,hmssdk) + getAudioDevicesList(call, result, hmssdk) } - "get_current_audio_device"-> { - getCurrentDevice(call,result,hmssdk) + "get_current_audio_device" -> { + getCurrentDevice(call, result, hmssdk) } "switch_audio_output" -> { - switchAudioOutput(call,result,hmssdk) + switchAudioOutput(call, result, hmssdk) } else -> { result.notImplemented() @@ -31,29 +28,28 @@ class HMSAudioDeviceAction { } } - private fun getAudioDevicesList(call: MethodCall, result: Result,hmssdk:HMSSDK){ - val audioDevicesList = ArrayList(); - for (device in hmssdk.getAudioDevicesList()){ - audioDevicesList.add(device.name); + private fun getAudioDevicesList(call: MethodCall, result: Result, hmssdk: HMSSDK) { + val audioDevicesList = ArrayList() + for (device in hmssdk.getAudioDevicesList()) { + audioDevicesList.add(device.name) } CoroutineScope(Dispatchers.Main).launch { result.success(audioDevicesList) } } - private fun getCurrentDevice(call: MethodCall, result: Result,hmssdk:HMSSDK){ + private fun getCurrentDevice(call: MethodCall, result: Result, hmssdk: HMSSDK) { CoroutineScope(Dispatchers.Main).launch { result.success(hmssdk.getAudioOutputRouteType().name) } } - private fun switchAudioOutput(call: MethodCall, result: Result,hmssdk:HMSSDK){ - var argument:String? = call.argument("audio_device_name") - if(argument!=null){ - var audioDevice:HMSAudioManager.AudioDevice = HMSAudioManager.AudioDevice.valueOf(argument) + private fun switchAudioOutput(call: MethodCall, result: Result, hmssdk: HMSSDK) { + var argument: String? = call.argument("audio_device_name") + if (argument != null) { + var audioDevice: HMSAudioManager.AudioDevice = HMSAudioManager.AudioDevice.valueOf(argument) hmssdk.switchAudioOutput(audioDevice) } - } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSHLSAction.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSHLSAction.kt index 8bbfef6f0..2ae66ab21 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSHLSAction.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSHLSAction.kt @@ -1,24 +1,27 @@ package live.hms.hmssdk_flutter -import android.util.Log +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel.Result import live.hms.video.sdk.HMSSDK import live.hms.video.sdk.models.HMSHLSConfig import live.hms.video.sdk.models.HMSHLSMeetingURLVariant -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel.Result +import live.hms.video.sdk.models.HMSHLSTimedMetadata import live.hms.video.sdk.models.HMSHlsRecordingConfig class HMSHLSAction { - companion object{ + companion object { - fun hlsActions(call: MethodCall, result: Result,hmssdk:HMSSDK){ + fun hlsActions(call: MethodCall, result: Result, hmssdk: HMSSDK) { when (call.method) { - "hls_start_streaming"->{ - hlsStreaming(call, result,hmssdk) + "hls_start_streaming" -> { + hlsStreaming(call, result, hmssdk) + } + "hls_stop_streaming" -> { + stopHLSStreaming(call, result, hmssdk) } - "hls_stop_streaming"->{ - stopHLSStreaming(call,result,hmssdk) + "send_hls_timed_metadata" -> { + sendHLSTimedMetadata(call, result, hmssdk) } else -> { result.notImplemented() @@ -26,57 +29,89 @@ class HMSHLSAction { } } - - private fun hlsStreaming(call: MethodCall, result: Result,hmssdk:HMSSDK) { - val meetingUrlVariantsList = call.argument>?>("meeting_url_variants") - val recordingConfig = call.argument?>("recording_config") - var meetingUrlVariant : ArrayList? = null + private fun hlsStreaming(call: MethodCall, result: Result, hmssdk: HMSSDK) { + val meetingUrlVariantsList = call.argument>?>("meeting_url_variants") + val recordingConfig = call.argument?>("recording_config") + var meetingUrlVariant: ArrayList? = null var hmsHLSRecordingConfig: HMSHlsRecordingConfig? = null var hlsConfig: HMSHLSConfig? = null - if(meetingUrlVariantsList!=null) { + if (meetingUrlVariantsList != null) { meetingUrlVariant = ArrayList() meetingUrlVariantsList?.forEach { meetingUrlVariant.add( HMSHLSMeetingURLVariant( meetingUrl = it["meeting_url"]!!, - metadata = it["meta_data"]!! - ) + metadata = it["meta_data"]!!, + ), ) } } - if(recordingConfig!=null){ + if (recordingConfig != null) { hmsHLSRecordingConfig = HMSHlsRecordingConfig( singleFilePerLayer = recordingConfig?.get("single_file_per_layer")!!, - videoOnDemand = recordingConfig?.get("video_on_demand")!! + videoOnDemand = recordingConfig?.get("video_on_demand")!!, ) } - if(meetingUrlVariant!=null || hmsHLSRecordingConfig!=null) { + if (meetingUrlVariant != null || hmsHLSRecordingConfig != null) { hlsConfig = HMSHLSConfig(meetingUrlVariant, hmsHLSRecordingConfig) } hmssdk.startHLSStreaming(config = hlsConfig, hmsActionResultListener = HMSCommonAction.getActionListener(result)) } + private fun stopHLSStreaming(call: MethodCall, result: Result, hmssdk: HMSSDK) { + val meetingUrlVariantsList = call.argument>>("meeting_url_variants") - private fun stopHLSStreaming(call: MethodCall,result: Result,hmssdk:HMSSDK) { - val meetingUrlVariantsList = call.argument>>("meeting_url_variants") - - val meetingUrlVariant1 : ArrayList = ArrayList() + val meetingUrlVariant1: ArrayList = ArrayList() meetingUrlVariantsList?.forEach { meetingUrlVariant1.add( - HMSHLSMeetingURLVariant( - meetingUrl = it["meeting_url"]!!, - metadata = it["meta_data"]!! - ) + HMSHLSMeetingURLVariant( + meetingUrl = it["meeting_url"]!!, + metadata = it["meta_data"]!!, + ), ) } - var hlsConfig : HMSHLSConfig? = null - if(meetingUrlVariant1.isNotEmpty()) + var hlsConfig: HMSHLSConfig? = null + if (meetingUrlVariant1.isNotEmpty()) { hlsConfig = HMSHLSConfig(meetingUrlVariant1) + } hmssdk.stopHLSStreaming(config = hlsConfig, hmsActionResultListener = HMSCommonAction.getActionListener(result)) } + /** + * This method is used to send timed metadata to the HLS Player + * + * This takes a list of [HMSHLSTimedMetadata] objects + * From flutter we receive a list of Map and then we + * convert it to list of HMSHLSTimedMetadata objects + */ + private fun sendHLSTimedMetadata(call: MethodCall, result: Result, hmssdk: HMSSDK) { + val metadata = call.argument>>("metadata") ?: kotlin.run { + HMSErrorLogger.returnArgumentsError("metadata Parameter is null") + } + + metadata?.let { + hlsMetadataList -> + hlsMetadataList as List> + + val hlsMetadata = ArrayList() + + /*** + * Here we parse the Map from flutter to + * [HMSHLSTimedMetadata] objects + */ + hlsMetadataList.forEach { + hlsMetadata.add( + HMSHLSTimedMetadata( + it["metadata"] as String, + (it["duration"] as Int).toLong(), + ), + ) + } + hmssdk.setHlsSessionMetadata(hlsMetadata, HMSCommonAction.getActionListener(result)) + } + } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSMessageAction.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSMessageAction.kt index 0f69ba2a3..4db401f38 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSMessageAction.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSMessageAction.kt @@ -1,25 +1,25 @@ package live.hms.hmssdk_flutter import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel.Result -import live.hms.video.sdk.* -import live.hms.video.error.HMSException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import live.hms.video.error.HMSException +import live.hms.video.sdk.* import live.hms.video.sdk.models.HMSMessage class HMSMessageAction { companion object { - fun messageActions(call: MethodCall, result: Result,hmssdk:HMSSDK) { + fun messageActions(call: MethodCall, result: Result, hmssdk: HMSSDK) { when (call.method) { "send_broadcast_message" -> { - sendBroadCastMessage(call, result,hmssdk) + sendBroadCastMessage(call, result, hmssdk) } "send_direct_message" -> { - sendDirectMessage(call, result,hmssdk) + sendDirectMessage(call, result, hmssdk) } "send_group_message" -> { - sendGroupMessage(call, result,hmssdk) + sendGroupMessage(call, result, hmssdk) } else -> { result.notImplemented() @@ -27,14 +27,13 @@ class HMSMessageAction { } } - - private fun sendBroadCastMessage(call: MethodCall, result: Result,hmssdk:HMSSDK) { + private fun sendBroadCastMessage(call: MethodCall, result: Result, hmssdk: HMSSDK) { val message = call.argument("message") val type = call.argument("type") ?: "chat" hmssdk?.sendBroadcastMessage(message!!, type, getMessageResultListener(result)) } - private fun sendGroupMessage(call: MethodCall, result: Result,hmssdk:HMSSDK) { + private fun sendGroupMessage(call: MethodCall, result: Result, hmssdk: HMSSDK) { val message = call.argument("message") val roles: List? = call.argument>("roles") val type = call.argument("type") ?: "chat" @@ -44,35 +43,32 @@ class HMSMessageAction { hmssdk?.sendGroupMessage(message!!, type, hmsRoles, getMessageResultListener(result)) } - private fun sendDirectMessage(call: MethodCall, result: Result,hmssdk:HMSSDK) { + private fun sendDirectMessage(call: MethodCall, result: Result, hmssdk: HMSSDK) { val message = call.argument("message") val peerId = call.argument("peer_id") val type = call.argument("type") ?: "chat" - val peer = HMSCommonAction.getPeerById(peerId!!,hmssdk) + val peer = HMSCommonAction.getPeerById(peerId!!, hmssdk) hmssdk?.sendDirectMessage(message!!, type, peer!!, getMessageResultListener(result)) } - private fun getMessageResultListener(result: Result) = object: HMSMessageResultListener { + private fun getMessageResultListener(result: Result) = object : HMSMessageResultListener { override fun onError(error: HMSException) { val args = HashMap() args["event_name"] = "on_error" args["data"] = HMSExceptionExtension.toDictionary(error) - if (args["data"] != null) + if (args["data"] != null) { CoroutineScope(Dispatchers.Main).launch { result.success(args) } + } } override fun onSuccess(hmsMessage: HMSMessage) { - val args = HashMap() - args["event_name"] = "on_success" - args["message"] = HMSMessageExtension.toDictionary(hmsMessage) - if (args["message"] != null) - CoroutineScope(Dispatchers.Main).launch { - result.success(args) - } + val args = HMSMessageExtension.toDictionary(hmsMessage) + 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/HMSRecordingAction.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSRecordingAction.kt index 2ba564493..f42628ee4 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSRecordingAction.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSRecordingAction.kt @@ -1,24 +1,19 @@ package live.hms.hmssdk_flutter -import live.hms.video.sdk.HMSSDK import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel.Result -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import live.hms.video.error.HMSException import live.hms.video.media.settings.HMSRtmpVideoResolution -import live.hms.video.sdk.HMSActionResultListener +import live.hms.video.sdk.HMSSDK import live.hms.video.sdk.models.HMSRecordingConfig class HMSRecordingAction { - companion object{ - fun recordingActions(call: MethodCall, result: Result,hmssdk:HMSSDK){ + companion object { + fun recordingActions(call: MethodCall, result: Result, hmssdk: HMSSDK) { when (call.method) { "start_rtmp_or_recording" -> { - startRtmpOrRecording(call, result,hmssdk) + startRtmpOrRecording(call, result, hmssdk) } "stop_rtmp_and_recording" -> { - stopRtmpAndRecording(result,hmssdk) + stopRtmpAndRecording(result, hmssdk) } else -> { result.notImplemented() @@ -26,29 +21,36 @@ class HMSRecordingAction { } } - private fun startRtmpOrRecording(call: MethodCall, result: Result,hmssdk:HMSSDK) { - val meetingUrl = call.argument("meeting_url") - val toRecord = call.argument("to_record") - val listOfRtmpUrls: List = call.argument>("rtmp_urls") ?: listOf() - val resolutionMap = call.argument>("resolution") - val hmsRecordingConfig = if(resolutionMap!=null) { - val resolution = HMSRtmpVideoResolution( - width = resolutionMap["width"]!!, - height = resolutionMap["height"]!! + private fun startRtmpOrRecording(call: MethodCall, result: Result, hmssdk: HMSSDK) { + val meetingUrl = call.argument("meeting_url") + val toRecord = call.argument("to_record") ?: run { + HMSErrorLogger.returnArgumentsError("toRecord parameter is null") + } + toRecord?.let { enableRecording -> + + val listOfRtmpUrls: List = call.argument>("rtmp_urls") ?: listOf() + val resolutionMap = call.argument>("resolution") + val hmsRecordingConfig = if (resolutionMap != null) { + val resolution = HMSRtmpVideoResolution( + width = resolutionMap["width"]!!, + height = resolutionMap["height"]!!, + ) + HMSRecordingConfig(meetingUrl, listOfRtmpUrls, enableRecording as Boolean, resolution) + } else { + HMSRecordingConfig(meetingUrl, listOfRtmpUrls, enableRecording as Boolean) + } + hmssdk.startRtmpOrRecording( + hmsRecordingConfig, + hmsActionResultListener = HMSCommonAction.getActionListener(result), ) - HMSRecordingConfig(meetingUrl!!, listOfRtmpUrls, toRecord!!,resolution) - }else{ - HMSRecordingConfig(meetingUrl!!, listOfRtmpUrls, toRecord!!) } - hmssdk.startRtmpOrRecording( - hmsRecordingConfig, - hmsActionResultListener = HMSCommonAction.getActionListener(result) - ) + ?: run { + HMSErrorLogger.returnArgumentsError("toRecord parameter is null") + } } - private fun stopRtmpAndRecording(result: Result,hmssdk:HMSSDK) { + private fun stopRtmpAndRecording(result: Result, hmssdk: HMSSDK) { hmssdk.stopRtmpAndRecording(hmsActionResultListener = HMSCommonAction.getActionListener(result)) } - } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSRoomAction.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSRoomAction.kt index e6e107e03..b06b664e8 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSRoomAction.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSRoomAction.kt @@ -1,39 +1,39 @@ package live.hms.hmssdk_flutter import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel.Result -import live.hms.video.sdk.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import live.hms.video.sdk.* class HMSRoomAction { companion object { fun roomActions(call: MethodCall, result: Result, hmssdk: HMSSDK) { when (call.method) { "get_room" -> { - getRoom(result,hmssdk) + getRoom(result, hmssdk) } "get_local_peer" -> { - localPeer(result,hmssdk) + localPeer(result, hmssdk) } "get_remote_peers" -> { - getRemotePeers(result,hmssdk) + getRemotePeers(result, hmssdk) } "get_peers" -> { - getPeers(result,hmssdk) + getPeers(result, hmssdk) } else -> { result.notImplemented() } } } - private fun getRoom(result: Result,hmssdk: HMSSDK) { + private fun getRoom(result: Result, hmssdk: HMSSDK) { result.success(HMSRoomExtension.toDictionary(hmssdk?.getRoom())) } - private fun localPeer(result: Result,hmssdk: HMSSDK) { + private fun localPeer(result: Result, hmssdk: HMSSDK) { result.success(HMSPeerExtension.toDictionary(HMSCommonAction.getLocalPeer(hmssdk))) } - private fun getRemotePeers(result: Result,hmssdk: HMSSDK) { + private fun getRemotePeers(result: Result, hmssdk: HMSSDK) { val peersList = hmssdk.getRemotePeers() val peersMapList = ArrayList?>() peersList.forEach { @@ -45,7 +45,7 @@ class HMSRoomAction { } // TODO: check behaviour when room is not joined - private fun getPeers(result: Result,hmssdk: HMSSDK) { + private fun getPeers(result: Result, hmssdk: HMSSDK) { val peersList = hmssdk.getPeers() val peersMapList = ArrayList?>() peersList.forEach { @@ -56,4 +56,4 @@ class HMSRoomAction { } } } -} \ No newline at end of file +} 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 deleted file mode 100644 index 6b9cc1f5e..000000000 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSSessionMetadataAction.kt +++ /dev/null @@ -1,63 +0,0 @@ -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.HMSErrorLogger -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: Any?) { - val args = HashMap() - args["event_name"] = "session_metadata" - if (sessionMetadata is String?) { - args["data"] = HMSSessionMetadataExtension.toDictionary(sessionMetadata) - } else { - HMSErrorLogger.logError("getSessionMetadata", "Session metadata type is not compatible, Please use String? type while setting metadata", "Type Incompatibility Error") - args["data"] = null - } - if (args["data"] != null) { - CoroutineScope(Dispatchers.Main).launch { - result.success(args) - } - } - } - } - } -} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSSessionStoreAction.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSSessionStoreAction.kt index 496366bae..7453830be 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSSessionStoreAction.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSSessionStoreAction.kt @@ -1,5 +1,6 @@ package live.hms.hmssdk_flutter.methods +import com.google.gson.JsonElement import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel.Result import live.hms.hmssdk_flutter.HMSCommonAction @@ -47,12 +48,26 @@ class HMSSessionStoreAction { result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.toDictionary(error))) } - override fun onSuccess(sessionMetadata: Any?) { - if (sessionMetadata is String?) { - result.success(HMSResultExtension.toDictionary(true, sessionMetadata)) - } else { - HMSErrorLogger.returnHMSException("getSessionMetadataForKey", "Session metadata type is not compatible, Please use String? type while setting metadata", "Type Incompatibility Error", result) + override fun onSuccess(sessionMetadata: JsonElement?) { + var value: Any? = null + + /** + * Here depending on the value we parse the JsonElement + * if it's a JsonPrimitive we parse it as String and then send to flutter + * if it's a JsonObject,JsonArray we convert it to String and then send to flutter + * if it's a JsonNull we send it as null + */ + sessionMetadata?.let { + value = if (it.isJsonPrimitive) { + it.asString + } else if (it.isJsonNull) { + null + } else { + it.toString() + } } + + result.success(HMSResultExtension.toDictionary(true, value)) } }, ) diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayer.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayer.kt new file mode 100644 index 000000000..94ef9df2a --- /dev/null +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayer.kt @@ -0,0 +1,287 @@ +package live.hms.hmssdk_flutter.views + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.util.Log +import android.view.LayoutInflater +import android.widget.FrameLayout +import androidx.media3.ui.PlayerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import live.hms.hls_player.* +import live.hms.hmssdk_flutter.Constants +import live.hms.hmssdk_flutter.Constants.Companion.HLS_PLAYER_INTENT +import live.hms.hmssdk_flutter.HMSErrorLogger +import live.hms.hmssdk_flutter.HmssdkFlutterPlugin +import live.hms.hmssdk_flutter.R +import live.hms.hmssdk_flutter.hls_player.HLSStatsHandler +import live.hms.hmssdk_flutter.hls_player.HMSHLSCueExtension +import live.hms.hmssdk_flutter.hls_player.HMSHLSPlaybackStateExtension +import java.util.concurrent.TimeUnit + +/** + * This class renders the HMSHLSPlayer over Android View + * + * We create the object of this class when the init of HMSHLSPlayerWidget is called from create method. + */ +class HMSHLSPlayer( + context: Context, + private val hlsUrl: String?, + private val hmssdkFlutterPlugin: HmssdkFlutterPlugin?, + private val isHLSStatsRequired: Boolean, + showHLSControls: Boolean, +) : FrameLayout(context, null) { + + var hlsPlayer: HmsHlsPlayer? = null + private var hlsPlayerView: PlayerView? = null + + /** + * Inflate the HLS player view and initialize the HLS player. + * Set the HLS player view and player if the inflation is successful. + * Otherwise, log an error indicating that the HLS player view is null. + * If inflation is successful, then we add the HLS Controls based on the parameter + * passed from flutter i.e. whether to show controls or not. + */ + init { + // Inflate the HLS player view using the layout inflater service. + hlsPlayerView = (context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater).inflate(R.layout.hms_hls_player, this)?.findViewById(R.id.hlsView) + hlsPlayerView?.let { + + // Set the HLS player controller visibility based on the showHLSControls flag. + it.useController = showHLSControls + + // Initialize the HmsHlsPlayer with the provided context and hmssdk instance. + hlsPlayer = HmsHlsPlayer(context, hmssdkFlutterPlugin?.hmssdk) + + // Set the native player of the HLS player view. + it.player = hlsPlayer?.getNativePlayer() + } ?: run { + HMSErrorLogger.logError("init HMSHLSPlayer", "hlsPlayerView is null", "NULL_ERROR") + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + /** + * Here we start the player + * We first check whether the user has passed the streamUrl or not + * If not, we fetch the URL from SDK + * else we use the URL sent by the user + */ + hlsPlayer?.let { player -> + hlsUrl?.let { + /** + * Here we play the stream using streamUrl using the stream from + * url passed by the user + */ + player.play(hlsUrl) + } ?: run { + hmssdkFlutterPlugin?.let { plugin -> + plugin.hlsStreamUrl?.let { streamUrl -> + /** + * Here we play the stream using streamUrl using the stream from + * onRoomUpdate or onJoin + */ + player.play(streamUrl) + context.registerReceiver(broadcastReceiver, IntentFilter(HLS_PLAYER_INTENT)) + + /** + * Here we add the event listener to listen to the events + * of HLS Player + */ + player.addPlayerEventListener( + hmsHLSPlaybackEvents, + ) + + /** + * Here we add the stats listener to the + * HLS Player + */ + if (isHLSStatsRequired) { + HLSStatsHandler.addHLSStatsListener(hmssdkFlutterPlugin, hlsPlayer) + } + } + } + } + } + } + + /** + * This method is used to dispose of the HLS player and clean up resources. + * Stop the HLS player. + * Set the HLS player and player view references to null. + * Unregister the broadcast receiver. + * Remove the HLS stats listener. + */ + fun dispose() { + hlsPlayer?.stop() + hlsPlayer = null + hlsPlayerView = null + context.unregisterReceiver(broadcastReceiver) + HLSStatsHandler.removeStatsListener(hlsPlayer) + } + + /** + * An object implementing the HmsHlsPlaybackEvents interface. + * It handles the events related to HLS playback. + */ + private val hmsHLSPlaybackEvents = object : HmsHlsPlaybackEvents { + + /** + * Callback function triggered when there is a playback failure in the HLS player. + * It constructs a HashMap with the event details and error information, and sends it to the Flutter side through the plugin's sink. + * - Parameters: + * - error: The HmsHlsException object representing the playback failure. + */ + override fun onPlaybackFailure(error: HmsHlsException) { + val hashMap: HashMap = HashMap() + val args: HashMap = HashMap() + hashMap["event_name"] = "on_playback_failure" + args["error"] = error.error.localizedMessage + hashMap["data"] = args + CoroutineScope(Dispatchers.Main).launch { + hmssdkFlutterPlugin?.hlsPlayerSink?.success(hashMap) + } + } + + /** + * Callback function triggered when the playback state of the HLS player changes. + * It constructs a HashMap with the event details and the playback state information, + * and sends it to the Flutter side through the plugin's sink. + * - Parameters: + * - p1: The HmsHlsPlaybackState representing the updated playback state. + */ + override fun onPlaybackStateChanged(p1: HmsHlsPlaybackState) { + val hashMap: HashMap = HashMap() + hashMap["event_name"] = "on_playback_state_changed" + hashMap["data"] = HMSHLSPlaybackStateExtension.toDictionary(p1) + if (hashMap["data"] != null) { + CoroutineScope(Dispatchers.Main).launch { + hmssdkFlutterPlugin?.hlsPlayerSink?.success(hashMap) + } + } + } + + /** + * Callback function triggered when a cue event occurs in the HLS player. + * It constructs a HashMap with the event details and the cue information, + * and sends it to the Flutter side through the plugin's sink. + * - Parameters: + * - hlsCue: The HmsHlsCue representing the cue event. + */ + override fun onCue(hlsCue: HmsHlsCue) { + val hashMap: HashMap = HashMap() + hashMap["event_name"] = "on_cue" + hashMap["data"] = HMSHLSCueExtension.toDictionary(hlsCue) + if (hashMap["data"] != null) { + CoroutineScope(Dispatchers.Main).launch { + hmssdkFlutterPlugin?.hlsPlayerSink?.success(hashMap) + } + } + } + } + + /** + * An object implementing the BroadcastReceiver interface. + * It handles the events related to HLS Controller. + */ + private val broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(contxt: Context?, intent: Intent?) { + if (intent?.action == HLS_PLAYER_INTENT) { + when (intent.extras?.getString(Constants.METHOD_CALL)) { + "start_hls_player" -> { + return start(intent.extras?.getString("hls_url")) + } + "stop_hls_player" -> { + return stop() + } + "pause_hls_player" -> { + return pause() + } + "resume_hls_player" -> { + return resume() + } + "seek_to_live_position" -> { + return seekToLivePosition() + } + "seek_forward" -> { + return seekForward(intent.extras?.getInt("seconds")) + } + "seek_backward" -> { + return seekBackward(intent.extras?.getInt("seconds")) + } + "set_volume" -> { + return setVolume(intent.extras?.getInt("volume")) + } + "add_hls_stats_listener" -> { + return HLSStatsHandler.addHLSStatsListener(hmssdkFlutterPlugin, hlsPlayer) + } + "remove_hls_stats_listener" -> { + return HLSStatsHandler.removeStatsListener(hlsPlayer) + } + } + } else { + Log.e("Receiver error", "No receiver found for given action") + } + } + } + + /** + * Below methods handles the HLS Player controller calls + */ + private fun seekBackward(seconds: Int?) { + seconds?.let { time -> + hlsPlayer?.seekBackward(time.toLong(), TimeUnit.SECONDS) + } + } + + private fun seekForward(seconds: Int?) { + seconds?.let { time -> + hlsPlayer?.seekForward(time.toLong(), TimeUnit.SECONDS) + } + } + + private fun seekToLivePosition() { + hlsPlayer?.seekToLivePosition() + } + + private fun resume() { + hlsPlayer?.resume() + } + + private fun pause() { + hlsPlayer?.pause() + } + + private fun stop() { + hlsPlayer?.stop() + } + + /** + * Starts the HLS player with the specified HLS URL. + * - Parameters: + * - hlsUrl: The HLS URL to play. If nil, the HLS URL from hmssdkFlutterPlugin will be used. + */ + private fun start(hlsUrl: String?) { + hlsUrl?.let { + hlsPlayer?.play(hlsUrl) + } ?: run { + hmssdkFlutterPlugin?.hlsStreamUrl?.let { + hlsPlayer?.play(it) + } ?: run { + HMSErrorLogger.logError("start", "HLS Stream URL is null", "NULL Error") + } + } + } + + private fun setVolume(volume: Int?) { + volume?.let { + hlsPlayer?.volume = it + } + } + + /********************************************/ +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayerFactory.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayerFactory.kt new file mode 100644 index 000000000..096dddb37 --- /dev/null +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayerFactory.kt @@ -0,0 +1,57 @@ +package live.hms.hmssdk_flutter.views + +import android.content.Context +import android.view.View +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.platform.PlatformView +import io.flutter.plugin.platform.PlatformViewFactory +import live.hms.hmssdk_flutter.HmssdkFlutterPlugin + +class HMSHLSPlayerWidget( + context: Context, + hlsUrl: String?, + hmssdkFlutterPlugin: HmssdkFlutterPlugin?, + isHLSStatsRequired: Boolean, + showPlayerControls: Boolean, +) : PlatformView { + + private var hmsHLSPlayer: HMSHLSPlayer? = null + + /** + * Initializes the HLS player if it is null. + * - Parameters: + * - context: The Android context. + * - hlsUrl: The HLS URL. + * - hmssdkFlutterPlugin: The Flutter plugin instance. + * - isHLSStatsRequired: Indicates whether HLS stats are required. + * - showPlayerControls: Indicates whether to show player controls. + */ + init { + if (hmsHLSPlayer == null) { + hmsHLSPlayer = HMSHLSPlayer(context, hlsUrl, hmssdkFlutterPlugin, isHLSStatsRequired, showPlayerControls) + } + } + + override fun getView(): View? { + return hmsHLSPlayer + } + + override fun dispose() { + hmsHLSPlayer?.dispose() + hmsHLSPlayer = null + } +} +class HMSHLSPlayerFactory(private val plugin: HmssdkFlutterPlugin) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { + override fun create(context: Context?, viewId: Int, args: Any?): PlatformView { + var hlsUrl: String? = null + var isHLSStatsRequired: Boolean? = false + var showPlayerControls: Boolean? = false + val params: Map? = args as Map? + if (params != null) { + hlsUrl = params["hls_url"] as? String? + isHLSStatsRequired = params["is_hls_stats_required"] as? Boolean? + showPlayerControls = params["show_player_controls"] as? Boolean? + } + return HMSHLSPlayerWidget(requireNotNull(context), hlsUrl, plugin, isHLSStatsRequired ?: false, showPlayerControls ?: false) + } +} diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt index 4f027aef5..03e655d00 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt @@ -10,6 +10,7 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout +import live.hms.hmssdk_flutter.Constants.Companion.METHOD_CALL import live.hms.hmssdk_flutter.HmssdkFlutterPlugin import live.hms.hmssdk_flutter.R import live.hms.video.media.tracks.HMSVideoTrack @@ -23,7 +24,7 @@ class HMSVideoView( private val scaleType: Int? = RendererCommon.ScalingType.SCALE_ASPECT_FIT.ordinal, private val track: HMSVideoTrack?, private val disableAutoSimulcastLayerSelect: Boolean, - private val hmssdkFlutterPlugin: HmssdkFlutterPlugin? + private val hmssdkFlutterPlugin: HmssdkFlutterPlugin?, ) : FrameLayout(context, null) { private var hmsVideoView: HMSVideoView? = null @@ -31,7 +32,7 @@ class HMSVideoView( private val broadcastReceiver = object : BroadcastReceiver() { override fun onReceive(contxt: Context?, intent: Intent?) { if (intent?.action == track?.trackId) { - when (intent?.extras?.getString("method_name")) { + when (intent?.extras?.getString(METHOD_CALL)) { "CAPTURE_SNAPSHOT" -> { return captureSnapshot() } @@ -99,6 +100,7 @@ class HMSVideoView( super.onAttachedToWindow() if (track != null) { if (hmsVideoView != null) { + // delay addTrack hmsVideoView?.addTrack(track) context.registerReceiver(broadcastReceiver, IntentFilter(track.trackId)) } else { diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt index e9c402ed1..e19f8cd4d 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt @@ -3,13 +3,10 @@ package live.hms.hmssdk_flutter.views import android.content.Context import android.util.Log import android.view.View -import android.widget.FrameLayout import io.flutter.plugin.common.StandardMessageCodec import io.flutter.plugin.platform.PlatformView import io.flutter.plugin.platform.PlatformViewFactory -import live.hms.hmssdk_flutter.HMSExceptionExtension import live.hms.hmssdk_flutter.HmssdkFlutterPlugin -import live.hms.video.error.HMSException import live.hms.video.media.tracks.HMSVideoTrack import live.hms.video.utils.HmsUtilities @@ -22,7 +19,7 @@ class HMSVideoViewWidget( scaleType: Int?, private val matchParent: Boolean? = true, disableAutoSimulcastLayerSelect: Boolean, - hmssdkFlutterPlugin: HmssdkFlutterPlugin? + hmssdkFlutterPlugin: HmssdkFlutterPlugin?, ) : PlatformView { private var hmsVideoView: HMSVideoView? = null @@ -37,25 +34,6 @@ class HMSVideoViewWidget( return hmsVideoView } - override fun onFlutterViewAttached(flutterView: View) { - super.onFlutterViewAttached(flutterView) - var frameLayoutParams = FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT - ) - if (matchParent == false) { - frameLayoutParams = FrameLayout.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - FrameLayout.LayoutParams.WRAP_CONTENT - ) - } - if (view != null) { - view?.layoutParams = frameLayoutParams - } else { - Log.e("HMSVideoView error", "onFlutterViewAttached error view is null") - } - } - override fun dispose() { if (hmsVideoView != null) { hmsVideoView?.onDisposeCalled() @@ -83,17 +61,11 @@ class HMSVideoViewFactory(private val plugin: HmssdkFlutterPlugin) : val track = HmsUtilities.getVideoTrack(trackId!!, room!!) if (track == null) { - val args = HashMap() - args["event_name"] = "on_error" - val hmsException = HMSException( - action = "Check the trackId for the track", - code = 6004, - description = "There is no track corresponding to the given trackId", - message = "Video track is null for corresponding trackId", - name = "HMSVideoView Error" + plugin.onVideoViewError( + methodName = "HMSVideoView", + error = "There is no track corresponding to the given trackId", + errorMessage = "Video track is null for corresponding trackId", ) - args["data"] = HMSExceptionExtension.toDictionary(hmsException) - plugin.onVideoViewError(args) } val disableAutoSimulcastLayerSelect = args!!["disable_auto_simulcast_layer_select"] as? Boolean ?: false diff --git a/android/src/main/res/layout/hms_hls_player.xml b/android/src/main/res/layout/hms_hls_player.xml new file mode 100644 index 000000000..3e4db7483 --- /dev/null +++ b/android/src/main/res/layout/hms_hls_player.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/android/src/main/res/layout/hms_video_view.xml b/android/src/main/res/layout/hms_video_view.xml index e7a5566f2..4b2d59dab 100644 --- a/android/src/main/res/layout/hms_video_view.xml +++ b/android/src/main/res/layout/hms_video_view.xml @@ -3,13 +3,12 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="#F44336"> + android:layout_height="match_parent"> Token from 100ms dashboard is for testing purposes only, For production applications you must generate tokens on your own server. > Refer to the [Management Token section](/flutter/v2/foundation/security-and-tokens#management-token) in Authentication and Tokens guide for more information. @@ -768,12 +770,76 @@ After calling `startHLSStreaming` we will get an `onSuccess` callback if the met 100ms Flutter SDK provides support for creating a Picture in Picture mode experience for video calls. PIP Mode lets the user watch the room video in a small window pinned to a corner of the screen while navigating between apps or browsing content on the main screen. -Currently, this functionality is only available on Android. To know more about how to implement PIP mode. Please check: [PIP Mode](https://www.100ms.live/docs/flutter/v2/advanced-features/pip-mode) +- PIP in Android + https://user-images.githubusercontent.com/93931528/205587304-772a5dd6-ed64-4d9e-8bb5-4fc3eed83bea.mp4 +- PIP in iOS + +https://github.com/100mslive/100ms-flutter/assets/93931528/af331f42-fe33-4345-bf6b-e623cc2d29e8 + +### 17. Spotlight + +Sometimes in large video conferences or webinars where numerous participants are present, spotlighting helps draw attention to a speaker, presenter, or panelist. + +HMSSDK provides a way to achieve the same, here comes `HMSSessionStore`. To read more about Session store checkout our docs [here](https://www.100ms.live/docs/flutter/v2/how-to-guides/interact-with-room/room/session-store) + +Let's see how we can spotlight a peer using session store: + +- Setup the Session store using the steps mentioned in the doc above. + +- Set the session metadata using `setSessionMetadataForKey` method with key as `spotlight` and value as anything unique for a particular peer so here we are using audio trackId as the unique value. + +```dart +/// Here [_hmsSessionStore] is an instance of HMSSessionStore +/// Details about how to get this are mentioned in above docs: https://www.100ms.live/docs/flutter/v2/how-to-guides/interact-with-room/room/session-store + +///Setting key as spotlight and data as peer's audio track Id +///[hmsActionResultListener] is an instance of class implementing HMSActionResultListener +_hmsSessionStore?.setSessionMetadataForKey( + key: 'spotlight', data: audioTrackId, hmsActionResultListener: this); +``` + +- Listen to `onKeyChanged` method for updates regarding the session store values. + +The `onKeyChanged` method is a listener that allows us to receive updates about the values stored in the session. In this case, we are interested in updates related to the `spotlight` key. When we receive an update with the key as `spotlight`, it means that there is new data available to indicate which participant's video feed should be highlighted. + +```dart + @override + void onKeyChanged({required String key, required String? value}) { + switch (key) { + case 'spotlight': + //Set the peer to spotlight here + break; + default: + break; + } + } +``` + +- Remove a peer from spotlight + +To remove a peer from spotlight just set the `spotlight` key as null. + +```dart +/// Here [_hmsSessionStore] is an instance of HMSSessionStore +/// Details about how to get this are mentioned in above docs: https://www.100ms.live/docs/flutter/v2/how-to-guides/interact-with-room/room/session-store + +///Setting key as spotlight and data as null +///[hmsActionResultListener] is an instance of class implementing HMSActionResultListener +_hmsSessionStore?.setSessionMetadataForKey( + key: 'spotlight', data: null, hmsActionResultListener: this); +``` + +Checkout spotlight in action: + +https://github.com/100mslive/100ms-flutter/assets/93931528/0a9df0b9-8518-4ca4-8cd3-7447a450acb8 + +You can check the spotlight implementation code in the example folder above. + ## Handling Errors Error handling is an important part of the application.`HMSSDK` has `HMSException` class for errors and exceptions. diff --git a/example/android/Gemfile.lock b/example/android/Gemfile.lock index 2a7168aa7..bdc8d22d5 100644 --- a/example/android/Gemfile.lock +++ b/example/android/Gemfile.lock @@ -8,17 +8,17 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.756.0) - aws-sdk-core (3.171.0) + aws-partitions (1.780.0) + aws-sdk-core (3.175.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.63.0) - aws-sdk-core (~> 3, >= 3.165.0) + aws-sdk-kms (1.67.0) + aws-sdk-core (~> 3, >= 3.174.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.121.0) - aws-sdk-core (~> 3, >= 3.165.0) + aws-sdk-s3 (1.126.0) + aws-sdk-core (~> 3, >= 3.174.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sigv4 (1.5.2) @@ -36,7 +36,7 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.99.0) + excon (0.100.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -65,8 +65,8 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.6) - fastlane (2.212.2) + fastimage (2.2.7) + fastlane (2.213.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -90,7 +90,7 @@ GEM json (< 3.0.0) jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (~> 2.0.0) + multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) optparse (~> 0.1.1) plist (>= 3.1.0, < 4.0.0) @@ -105,9 +105,9 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.5.0) + fastlane-plugin-firebase_app_distribution (0.6.1) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.40.0) + google-apis-androidpublisher_v3 (0.44.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-core (0.11.0) addressable (~> 2.5, >= 2.5.1) @@ -151,12 +151,12 @@ GEM httpclient (2.8.3) jmespath (1.6.2) json (2.6.3) - jwt (2.7.0) + jwt (2.7.1) memoist (0.16.2) mini_magick (4.12.0) mini_mime (1.1.2) multi_json (1.15.0) - multipart-post (2.0.0) + multipart-post (2.3.0) nanaimo (0.3.0) naturally (2.2.1) optparse (0.1.1) @@ -218,4 +218,4 @@ DEPENDENCIES fastlane-plugin-firebase_app_distribution BUNDLED WITH - 2.4.12 + 2.4.14 diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 96ad1908f..b7416d8e8 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 238 - versionName "1.4.103" + versionCode 258 + versionName "1.4.123" } signingConfigs { diff --git a/example/android/app/src/main/kotlin/live/hms/flutter/MainActivity.kt b/example/android/app/src/main/kotlin/live/hms/flutter/MainActivity.kt index a4b34c4a8..83a6af844 100644 --- a/example/android/app/src/main/kotlin/live/hms/flutter/MainActivity.kt +++ b/example/android/app/src/main/kotlin/live/hms/flutter/MainActivity.kt @@ -27,7 +27,7 @@ class MainActivity : FlutterActivity() { override fun onPictureInPictureModeChanged( isInPictureInPictureMode: Boolean, - newConfig: Configuration? + newConfig: Configuration?, ) { if (isInPictureInPictureMode) { if (HMSPipAction.pipResult != null) { diff --git a/example/android/build.gradle b/example/android/build.gradle index 70b9d6bdc..9a20f1434 100755 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -9,8 +9,8 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' - classpath 'com.google.gms:google-services:4.3.14' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.5' + classpath 'com.google.gms:google-services:4.3.15' classpath 'com.google.firebase:perf-plugin:1.4.2' } } diff --git a/example/assets/icons/emoji.svg b/example/assets/icons/emoji.svg new file mode 100644 index 000000000..cf36508ed --- /dev/null +++ b/example/assets/icons/emoji.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/example/assets/icons/timed_metadata.svg b/example/assets/icons/timed_metadata.svg new file mode 100644 index 000000000..2bfcd9da7 --- /dev/null +++ b/example/assets/icons/timed_metadata.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/example/ios/Gemfile.lock b/example/ios/Gemfile.lock index 5fdb088ec..52823634c 100644 --- a/example/ios/Gemfile.lock +++ b/example/ios/Gemfile.lock @@ -8,17 +8,17 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.756.0) - aws-sdk-core (3.171.0) + aws-partitions (1.780.0) + aws-sdk-core (3.175.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.63.0) - aws-sdk-core (~> 3, >= 3.165.0) + aws-sdk-kms (1.67.0) + aws-sdk-core (~> 3, >= 3.174.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.121.0) - aws-sdk-core (~> 3, >= 3.165.0) + aws-sdk-s3 (1.126.0) + aws-sdk-core (~> 3, >= 3.174.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sigv4 (1.5.2) @@ -36,7 +36,7 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.99.0) + excon (0.100.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -65,8 +65,8 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.6) - fastlane (2.212.2) + fastimage (2.2.7) + fastlane (2.213.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -90,7 +90,7 @@ GEM json (< 3.0.0) jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (~> 2.0.0) + multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) optparse (~> 0.1.1) plist (>= 3.1.0, < 4.0.0) @@ -105,10 +105,10 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.5.0) + fastlane-plugin-firebase_app_distribution (0.6.1) fastlane-plugin-versioning (0.5.1) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.40.0) + google-apis-androidpublisher_v3 (0.44.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-core (0.11.0) addressable (~> 2.5, >= 2.5.1) @@ -152,12 +152,12 @@ GEM httpclient (2.8.3) jmespath (1.6.2) json (2.6.3) - jwt (2.7.0) + jwt (2.7.1) memoist (0.16.2) mini_magick (4.12.0) mini_mime (1.1.2) multi_json (1.15.0) - multipart-post (2.0.0) + multipart-post (2.3.0) nanaimo (0.3.0) naturally (2.2.1) optparse (0.1.1) @@ -221,4 +221,4 @@ DEPENDENCIES fastlane-plugin-versioning BUNDLED WITH - 2.4.12 + 2.4.14 diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index ad9f58740..37c830385 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,5 +1,4 @@ PODS: - - Cache (6.0.0) - DKImagePickerController/Core (4.3.4): - DKImagePickerController/ImageDataManager - DKImagePickerController/Resource @@ -34,70 +33,70 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter - - Firebase/Analytics (10.7.0): + - Firebase/Analytics (10.9.0): - Firebase/Core - - Firebase/Core (10.7.0): + - Firebase/Core (10.9.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 10.7.0) - - Firebase/CoreOnly (10.7.0): - - FirebaseCore (= 10.7.0) - - Firebase/Crashlytics (10.7.0): + - FirebaseAnalytics (~> 10.9.0) + - Firebase/CoreOnly (10.9.0): + - FirebaseCore (= 10.9.0) + - Firebase/Crashlytics (10.9.0): - Firebase/CoreOnly - - FirebaseCrashlytics (~> 10.7.0) - - Firebase/DynamicLinks (10.7.0): + - FirebaseCrashlytics (~> 10.9.0) + - Firebase/DynamicLinks (10.9.0): - Firebase/CoreOnly - - FirebaseDynamicLinks (~> 10.7.0) - - Firebase/Performance (10.7.0): + - FirebaseDynamicLinks (~> 10.9.0) + - Firebase/Performance (10.9.0): - Firebase/CoreOnly - - FirebasePerformance (~> 10.7.0) - - firebase_analytics (10.2.1): - - Firebase/Analytics (= 10.7.0) + - FirebasePerformance (~> 10.9.0) + - firebase_analytics (10.4.2): + - Firebase/Analytics (= 10.9.0) - firebase_core - Flutter - - firebase_core (2.10.0): - - Firebase/CoreOnly (= 10.7.0) + - firebase_core (2.13.1): + - Firebase/CoreOnly (= 10.9.0) - Flutter - - firebase_crashlytics (3.1.2): - - Firebase/Crashlytics (= 10.7.0) + - firebase_crashlytics (3.3.2): + - Firebase/Crashlytics (= 10.9.0) - firebase_core - Flutter - - firebase_dynamic_links (5.1.1): - - Firebase/DynamicLinks (= 10.7.0) + - firebase_dynamic_links (5.3.2): + - Firebase/DynamicLinks (= 10.9.0) - firebase_core - Flutter - - firebase_performance (0.9.1-1): - - Firebase/Performance (= 10.7.0) + - firebase_performance (0.9.2-2): + - Firebase/Performance (= 10.9.0) - firebase_core - Flutter - - FirebaseABTesting (10.9.0): + - FirebaseABTesting (10.11.0): - FirebaseCore (~> 10.0) - - FirebaseAnalytics (10.7.0): - - FirebaseAnalytics/AdIdSupport (= 10.7.0) + - FirebaseAnalytics (10.9.0): + - FirebaseAnalytics/AdIdSupport (= 10.9.0) - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.8) - - GoogleUtilities/MethodSwizzler (~> 7.8) - - GoogleUtilities/Network (~> 7.8) - - "GoogleUtilities/NSData+zlib (~> 7.8)" + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseAnalytics/AdIdSupport (10.7.0): + - FirebaseAnalytics/AdIdSupport (10.9.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - - GoogleAppMeasurement (= 10.7.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.8) - - GoogleUtilities/MethodSwizzler (~> 7.8) - - GoogleUtilities/Network (~> 7.8) - - "GoogleUtilities/NSData+zlib (~> 7.8)" + - GoogleAppMeasurement (= 10.9.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseCore (10.7.0): + - FirebaseCore (10.9.0): - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/Logger (~> 7.8) - - FirebaseCoreExtension (10.9.0): + - FirebaseCoreExtension (10.11.0): - FirebaseCore (~> 10.0) - - FirebaseCoreInternal (10.9.0): + - FirebaseCoreInternal (10.11.0): - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseCrashlytics (10.7.0): + - FirebaseCrashlytics (10.9.0): - FirebaseCore (~> 10.5) - FirebaseInstallations (~> 10.0) - FirebaseSessions (~> 10.5) @@ -105,14 +104,14 @@ PODS: - GoogleUtilities/Environment (~> 7.8) - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesObjC (~> 2.1) - - FirebaseDynamicLinks (10.7.0): + - FirebaseDynamicLinks (10.9.0): - FirebaseCore (~> 10.0) - - FirebaseInstallations (10.9.0): + - FirebaseInstallations (10.11.0): - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) - PromisesObjC (~> 2.1) - - FirebasePerformance (10.7.0): + - FirebasePerformance (10.9.0): - FirebaseCore (~> 10.5) - FirebaseInstallations (~> 10.0) - FirebaseRemoteConfig (~> 10.0) @@ -122,13 +121,13 @@ PODS: - GoogleUtilities/ISASwizzler (~> 7.8) - GoogleUtilities/MethodSwizzler (~> 7.8) - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseRemoteConfig (10.9.0): + - FirebaseRemoteConfig (10.11.0): - FirebaseABTesting (~> 10.0) - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseSessions (10.9.0): + - FirebaseSessions (10.11.0): - FirebaseCore (~> 10.5) - FirebaseCoreExtension (~> 10.0) - FirebaseInstallations (~> 10.0) @@ -139,30 +138,27 @@ PODS: - Flutter (1.0.0) - flutter_foreground_task (0.0.1): - Flutter - - GCDWebServer (3.5.4): - - GCDWebServer/Core (= 3.5.4) - - GCDWebServer/Core (3.5.4) - - GoogleAppMeasurement (10.7.0): - - GoogleAppMeasurement/AdIdSupport (= 10.7.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.8) - - GoogleUtilities/MethodSwizzler (~> 7.8) - - GoogleUtilities/Network (~> 7.8) - - "GoogleUtilities/NSData+zlib (~> 7.8)" + - GoogleAppMeasurement (10.9.0): + - GoogleAppMeasurement/AdIdSupport (= 10.9.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (10.7.0): - - GoogleAppMeasurement/WithoutAdIdSupport (= 10.7.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.8) - - GoogleUtilities/MethodSwizzler (~> 7.8) - - GoogleUtilities/Network (~> 7.8) - - "GoogleUtilities/NSData+zlib (~> 7.8)" + - GoogleAppMeasurement/AdIdSupport (10.9.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.9.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/WithoutAdIdSupport (10.7.0): - - GoogleUtilities/AppDelegateSwizzler (~> 7.8) - - GoogleUtilities/MethodSwizzler (~> 7.8) - - GoogleUtilities/Network (~> 7.8) - - "GoogleUtilities/NSData+zlib (~> 7.8)" + - GoogleAppMeasurement/WithoutAdIdSupport (10.9.0): + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleDataTransport (9.2.2): + - GoogleDataTransport (9.2.3): - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) @@ -186,20 +182,20 @@ PODS: - GoogleUtilities/Logger - GoogleUtilities/UserDefaults (7.11.1): - GoogleUtilities/Logger - - HLSCachingReverseProxyServer (0.1.0): - - GCDWebServer (~> 3.5) - - PINCache (>= 3.0.1-beta.3) - HMSAnalyticsSDK (0.0.2) - HMSBroadcastExtensionSDK (0.0.9) - - HMSSDK (0.9.3): + - HMSHLSPlayerSDK (0.0.2): + - HMSAnalyticsSDK (= 0.0.2) + - HMSSDK (0.9.5): - HMSAnalyticsSDK (= 0.0.2) - HMSWebRTC (= 1.0.5116) - - hmssdk_flutter (1.6.0): + - hmssdk_flutter (1.7.0): - Flutter - HMSBroadcastExtensionSDK (= 0.0.9) - - HMSSDK (= 0.9.3) + - HMSHLSPlayerSDK (= 0.0.2) + - HMSSDK (= 0.9.5) - HMSWebRTC (1.0.5116) - - image_gallery_saver (1.5.0): + - image_gallery_saver (2.0.2): - Flutter - MTBBarcodeScanner (5.0.11) - nanopb (2.30909.0): @@ -212,29 +208,17 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - permission_handler_apple (9.0.4): - - Flutter - - PINCache (3.0.3): - - PINCache/Arc-exception-safe (= 3.0.3) - - PINCache/Core (= 3.0.3) - - PINCache/Arc-exception-safe (3.0.3): - - PINCache/Core - - PINCache/Core (3.0.3): - - PINOperation (~> 1.2.1) - - PINOperation (1.2.2) - - pip_flutter (0.0.3): - - Cache (~> 6.0.0) + - permission_handler_apple (9.1.0): - Flutter - - HLSCachingReverseProxyServer - PromisesObjC (2.2.0) - PromisesSwift (2.2.0): - PromisesObjC (= 2.2.0) - qr_code_scanner (0.2.0): - Flutter - MTBBarcodeScanner - - SDWebImage (5.15.6): - - SDWebImage/Core (= 5.15.6) - - SDWebImage/Core (5.15.6) + - SDWebImage (5.16.0): + - SDWebImage/Core (= 5.16.0) + - SDWebImage/Core (5.16.0) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -263,7 +247,6 @@ DEPENDENCIES: - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - - pip_flutter (from `.symlinks/plugins/pip_flutter/ios`) - qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) @@ -273,7 +256,6 @@ DEPENDENCIES: SPEC REPOS: trunk: - - Cache - DKImagePickerController - DKPhotoGallery - Firebase @@ -288,19 +270,16 @@ SPEC REPOS: - FirebasePerformance - FirebaseRemoteConfig - FirebaseSessions - - GCDWebServer - GoogleAppMeasurement - GoogleDataTransport - GoogleUtilities - - HLSCachingReverseProxyServer - HMSAnalyticsSDK - HMSBroadcastExtensionSDK + - HMSHLSPlayerSDK - HMSSDK - HMSWebRTC - MTBBarcodeScanner - nanopb - - PINCache - - PINOperation - PromisesObjC - PromisesSwift - SDWebImage @@ -333,8 +312,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/ios" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" - pip_flutter: - :path: ".symlinks/plugins/pip_flutter/ios" qr_code_scanner: :path: ".symlinks/plugins/qr_code_scanner/ios" share_plus: @@ -349,54 +326,49 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wakelock/ios" SPEC CHECKSUMS: - Cache: 4ca7e00363fca5455f26534e5607634c820ffc2d DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 file_picker: ce3938a0df3cc1ef404671531facef740d03f920 - Firebase: 0219acf760880eeec8ce479895bd7767466d9f81 - firebase_analytics: e8e294333de66e5429d4aac365966281b4dbfb7d - firebase_core: 18d44f087248303a4a8f05d0099a000c46e3c77a - firebase_crashlytics: 621d91c0c5752e8219f8fe05a4d1e76394f4896a - firebase_dynamic_links: 464b974eeb7a5b4b7c970062c49a3e39ff924f47 - firebase_performance: 7dd98298ea2f19f480efd59ccbfcad3972ced984 - FirebaseABTesting: 005b70969e2817e2a1e631e8dba29134a04c0622 - FirebaseAnalytics: f8133442ee6f8512e28ff19e62ce15398bfaeace - FirebaseCore: e317665b9d744727a97e623edbbed009320afdd7 - FirebaseCoreExtension: d3e9bba2930a8033042112397cd9f006a1bb203d - FirebaseCoreInternal: d2b4acb827908e72eca47a9fd896767c3053921e - FirebaseCrashlytics: 35fdd1a433b31e28adcf5c8933f4c526691a1e0b - FirebaseDynamicLinks: 16a8fae3697fba66fed7a1d646fe59f30d42aa31 - FirebaseInstallations: c58489c9caacdbf27d1da60891a87318e20218e0 - FirebasePerformance: 8281bbaf08aad194001018b932115b7d58a6f00b - FirebaseRemoteConfig: 5ea5834e8c518f377bf1af2d97ebd611914ebf2d - FirebaseSessions: 44a6782502eb279a214d4adca20891353278760c + Firebase: bd152f0f3d278c4060c5c71359db08ebcfd5a3e2 + firebase_analytics: 48ed4a230abf9f585f7e7b477791effe96f5a791 + firebase_core: ce64b0941c6d87c6ef5022ae9116a158236c8c94 + firebase_crashlytics: 9b80d1944507cc07fa1c4455797f7d2eb7c8873f + firebase_dynamic_links: db9f2ebcc3ea646e76a1d3ee37e9e57890ff0a83 + firebase_performance: d11d1fd9591547f6b75f325aaadd6550eaf7e090 + FirebaseABTesting: 3f6b711718feb87346dc05f8c964601bc8fb2cf6 + FirebaseAnalytics: 5ea0db4893825e7b0149d575352cd838236313dc + FirebaseCore: b68d3616526ec02e4d155166bbafb8eca64af557 + FirebaseCoreExtension: cacdad57fdb60e0b86dcbcac058ec78237946759 + FirebaseCoreInternal: 9e46c82a14a3b3a25be4e1e151ce6d21536b89c0 + FirebaseCrashlytics: b60329455285aff853e54139d8ddbfe1e5f2b9f9 + FirebaseDynamicLinks: 8cb66c4f403aa6ddf86ff3bc3c383a652f344ce9 + FirebaseInstallations: 2a2c6859354cbec0a228a863d4daf6de7c74ced4 + FirebasePerformance: eee2f5da94fd7e5d15487649f8fe10a90c87c174 + FirebaseRemoteConfig: 5f907cd5cf6f64d026c8bfd196a1086cbe1e95e2 + FirebaseSessions: a62ba5c45284adb7714f4126cfbdb32b17c260bd Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_foreground_task: 21ef182ab0a29a3005cc72cd70e5f45cb7f7f817 - GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4 - GoogleAppMeasurement: fe17c92a32207dd5cdd4e8d742767f2da74857f6 - GoogleDataTransport: 8378d1fa8ac49753ea6ce70d65a7cb70ce5f66e6 + GoogleAppMeasurement: 373bcbead1bb6a85be7a64d5d8f96284b762ea9c + GoogleDataTransport: f0308f5905a745f94fb91fea9c6cbaf3831cb1bd GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749 - HLSCachingReverseProxyServer: 59935e1e0244ad7f3375d75b5ef46e8eb26ab181 HMSAnalyticsSDK: 4d2a88a729b1eb42f3d25f217c28937ec318a5b7 HMSBroadcastExtensionSDK: d80fe325f6c928bd8e5176290b5a4b7ae15d6fbb - HMSSDK: 0b25c97c4575e19e0e20acc49ca5fa8bdf1df626 - hmssdk_flutter: de578436ee99bd89458160c0ac6a4df599d3f826 + HMSHLSPlayerSDK: 6a54ad4d12f3dc2270d1ecd24019d71282a4f6a3 + HMSSDK: 81804295a3c5ec6016c0966f2053d7f5f51ccc26 + hmssdk_flutter: b57a9cc476ec04062d3189935893f5b0b22ab617 HMSWebRTC: ae54e9dd91b869051b283b43b14f57d43b7bf8e1 - image_gallery_saver: 259eab68fb271cfd57d599904f7acdc7832e7ef2 + image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 - permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce - PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086 - PINOperation: daa34d4aa1d8449089be7d405b9d974abc4724c6 - pip_flutter: ab714aa0773b07b3e8c41a5ab314c8e22fd53c25 + path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 + permission_handler_apple: 8f116445eff3c0e7c65ad60f5fef5490aa94b4e4 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef PromisesSwift: cf9eb58666a43bbe007302226e510b16c1e10959 qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e - SDWebImage: d47d81bea8a77187896b620dc79c3c528e8906b9 + SDWebImage: 2aea163b50bfcb569a2726b6a754c54a4506fcf6 share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 - shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 + shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f uni_links: d97da20c7701486ba192624d99bffaaffcfc298a url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index aa803228e..b52e636de 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -303,6 +303,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index ac3e4c2c0..a60cec9a4 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -23,7 +23,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.103 + 1.4.123 CFBundleSignature ???? CFBundleURLTypes @@ -50,7 +50,7 @@ CFBundleVersion - 238 + 258 ITSAppUsesNonExemptEncryption LSApplicationCategoryType diff --git a/example/lib/common/app_dialogs/audio_device_change_dialog.dart b/example/lib/common/app_dialogs/audio_device_change_dialog.dart index 751598cda..124af9e38 100644 --- a/example/lib/common/app_dialogs/audio_device_change_dialog.dart +++ b/example/lib/common/app_dialogs/audio_device_change_dialog.dart @@ -42,7 +42,6 @@ class _AudioDeviceChangeDialogState extends State { @override Widget build(BuildContext context) { String message = "Route the audio output to"; - double width = MediaQuery.of(context).size.width; return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), actionsPadding: EdgeInsets.only(left: 20, right: 20, bottom: 10), diff --git a/example/lib/common/app_dialogs/audio_mode_select_dialog.dart b/example/lib/common/app_dialogs/audio_mode_select_dialog.dart index 4a95f3d84..1b7c3e972 100644 --- a/example/lib/common/app_dialogs/audio_mode_select_dialog.dart +++ b/example/lib/common/app_dialogs/audio_mode_select_dialog.dart @@ -38,7 +38,6 @@ class _AudioModeSelectDialogState extends State { @override Widget build(BuildContext context) { String message = "Select the audio mode"; - double width = MediaQuery.of(context).size.width; return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), actionsPadding: EdgeInsets.only(left: 20, right: 20, bottom: 10), diff --git a/example/lib/common/app_dialogs/change_simulcast_layer_option_dialog.dart b/example/lib/common/app_dialogs/change_simulcast_layer_option_dialog.dart index 2778c9478..7cfb6db6a 100644 --- a/example/lib/common/app_dialogs/change_simulcast_layer_option_dialog.dart +++ b/example/lib/common/app_dialogs/change_simulcast_layer_option_dialog.dart @@ -13,9 +13,9 @@ import 'package:hmssdk_flutter_example/common/util/utility_function.dart'; import 'package:collection/collection.dart'; class ChangeSimulcastLayerOptionDialog extends StatefulWidget { - List layerDefinitions; - HMSSimulcastLayer selectedLayer; - HMSRemoteVideoTrack track; + final List layerDefinitions; + final HMSSimulcastLayer selectedLayer; + final HMSRemoteVideoTrack track; ChangeSimulcastLayerOptionDialog( {required this.layerDefinitions, required this.selectedLayer, @@ -43,7 +43,6 @@ class _ChangeSimulcastLayerOptionDialogState @override Widget build(BuildContext context) { String message = "Current Layer: ${widget.selectedLayer.name}"; - double width = MediaQuery.of(context).size.width; return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), actionsPadding: EdgeInsets.only(left: 20, right: 20, bottom: 10), diff --git a/example/lib/common/app_dialogs/hls_aspect_ratio_option_dialog.dart b/example/lib/common/app_dialogs/hls_aspect_ratio_option_dialog.dart index 7da277523..0ed5c4639 100644 --- a/example/lib/common/app_dialogs/hls_aspect_ratio_option_dialog.dart +++ b/example/lib/common/app_dialogs/hls_aspect_ratio_option_dialog.dart @@ -13,7 +13,7 @@ import 'package:hmssdk_flutter_example/meeting/meeting_store.dart'; class AspectRatioOptionDialog extends StatefulWidget { final List availableAspectRatios; - MeetingStore meetingStore; + final MeetingStore meetingStore; AspectRatioOptionDialog({ required this.availableAspectRatios, required this.meetingStore, @@ -140,16 +140,17 @@ class _AspectRatioOptionDialogState extends State { } else { double ratio; if (valueChoose!.contains("Default")) { + widget.meetingStore.isDefaultAspectRatioSelected = true; ratio = Utilities.getHLSPlayerDefaultRatio( MediaQuery.of(context).size); } else { + widget.meetingStore.isDefaultAspectRatioSelected = false; List number = valueChoose!.split(":"); ratio = double.parse(number[0]) / double.parse(number[1]); } Utilities.showToast( "Player aspect ratio changed to $valueChoose"); - widget.meetingStore - .setPIPVideoController(true, aspectRatio: ratio); + widget.meetingStore.setAspectRatio(ratio); Navigator.pop(context); } }, diff --git a/example/lib/common/app_dialogs/stats_for_nerds.dart b/example/lib/common/app_dialogs/stats_for_nerds.dart index 1ac526891..9f85fd7b7 100644 --- a/example/lib/common/app_dialogs/stats_for_nerds.dart +++ b/example/lib/common/app_dialogs/stats_for_nerds.dart @@ -60,7 +60,6 @@ class _StatsForNerdsState extends State { @override Widget build(BuildContext context) { - double width = MediaQuery.of(context).size.width; return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), actionsPadding: EdgeInsets.only(left: 20, right: 20, bottom: 10), diff --git a/example/lib/common/bottom_sheets/app_settings_bottom_sheet.dart b/example/lib/common/bottom_sheets/app_settings_bottom_sheet.dart index 1ca45ae11..56e57fc20 100644 --- a/example/lib/common/bottom_sheets/app_settings_bottom_sheet.dart +++ b/example/lib/common/bottom_sheets/app_settings_bottom_sheet.dart @@ -481,9 +481,13 @@ class _AppSettingsBottomSheetState extends State { ListTile( horizontalTitleGap: 2, onTap: () async { - File logFile = await getLogFile; - Share.shareXFiles([XFile(logFile.path)], - text: "HMS Log file"); + File? logFile = await getLogFile; + if (logFile != null) { + Share.shareXFiles([XFile(logFile.path)], + text: "HMS Log file"); + } else { + Utilities.showToast("No file found"); + } }, contentPadding: EdgeInsets.zero, leading: SvgPicture.asset( diff --git a/example/lib/common/bottom_sheets/chat_bottom_sheet.dart b/example/lib/common/bottom_sheets/chat_bottom_sheet.dart index e0880f64d..338e0f1f3 100644 --- a/example/lib/common/bottom_sheets/chat_bottom_sheet.dart +++ b/example/lib/common/bottom_sheets/chat_bottom_sheet.dart @@ -287,7 +287,7 @@ class _ChatBottomSheetState extends State { .width * 0.66, child: Text( - "Messages can only be seen by people in the call and are deleted when the call ends.", + "Messages can only be seen by people in the call and are deleted when the call ends.\nLong Press the send button to send timed metadata", style: GoogleFonts.inter( fontWeight: FontWeight.w400, color: themeSubHeadingColor, @@ -367,11 +367,13 @@ class _ChatBottomSheetState extends State { ), GestureDetector( onTap: () { - context.read().setSessionMetadata( - key: SessionStoreKeyValues - .getNameFromMethod(SessionStoreKey - .PINNED_MESSAGE_SESSION_KEY), - metadata: null); + context + .read() + .setSessionMetadataForKey( + key: SessionStoreKeyValues + .getNameFromMethod(SessionStoreKey + .PINNED_MESSAGE_SESSION_KEY), + metadata: null); }, child: SvgPicture.asset( "assets/icons/close.svg")) @@ -420,9 +422,8 @@ class _ChatBottomSheetState extends State { false ? true : false, - message: data.item1[index].message - .trim() - .toString(), + message: + "${data.item1[index].message.trim().toString()}", senderName: data.item1[index].sender?.name ?? "Anonymous", @@ -479,6 +480,30 @@ class _ChatBottomSheetState extends State { } sendMessage(); }, + onLongPress: () { + if (!(context + .read() + .localPeer + ?.role + .name + .contains("hls-") ?? + false)) { + if (messageTextController.text.isEmpty) { + Utilities.showToast("Message can't be empty"); + return; + } + context + .read() + .sendHLSTimedMetadata([ + HMSHLSTimedMetadata( + metadata: messageTextController.text) + ]); + messageTextController.clear(); + } else { + Utilities.showToast( + "Role doesn't have permission"); + } + }, child: Container( width: 40, height: 40, diff --git a/example/lib/common/bottom_sheets/more_settings_bottom_sheet.dart b/example/lib/common/bottom_sheets/more_settings_bottom_sheet.dart index 7bebf08f7..cab045c2e 100644 --- a/example/lib/common/bottom_sheets/more_settings_bottom_sheet.dart +++ b/example/lib/common/bottom_sheets/more_settings_bottom_sheet.dart @@ -9,7 +9,6 @@ import 'package:hmssdk_flutter_example/common/app_dialogs/stats_for_nerds.dart'; import 'package:hmssdk_flutter_example/common/bottom_sheets/logger_view_bottom_sheet.dart'; import 'package:hmssdk_flutter_example/common/bottom_sheets/notification_settings_bottom_sheet.dart'; import 'package:hmssdk_flutter_example/common/bottom_sheets/participants_bottom_sheet.dart'; -import 'package:hmssdk_flutter_example/service/constant.dart'; import 'package:hmssdk_flutter_example/common/widgets/share_link_option.dart'; import 'package:hmssdk_flutter_example/common/util/app_color.dart'; import 'package:hmssdk_flutter_example/common/util/utility_components.dart'; @@ -141,17 +140,24 @@ class _MoreSettingsBottomSheetState extends State { InkWell( onTap: () async { Navigator.pop(context); - showModalBottomSheet( - isScrollControlled: true, - backgroundColor: themeBottomSheetColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - context: context, - builder: (ctx) => ChangeNotifierProvider.value( - value: _meetingStore, - child: AudioSettingsBottomSheet()), - ); + + if (Platform.isAndroid) { + showModalBottomSheet( + isScrollControlled: true, + backgroundColor: themeBottomSheetColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + context: context, + builder: (ctx) => ChangeNotifierProvider.value( + value: _meetingStore, + child: AudioSettingsBottomSheet()), + ); + } else { + context + .read() + .switchAudioOutputUsingiOSUI(); + } }, child: Container( decoration: BoxDecoration( @@ -432,12 +438,10 @@ class _MoreSettingsBottomSheetState extends State { } if (urls != null) { _meetingStore.startRtmpOrRecording( - meetingUrl: Constant.streamingUrl, toRecord: data["toRecord"] ?? false, rtmpUrls: urls); } else if (data["toRecord"] ?? false) { _meetingStore.startRtmpOrRecording( - meetingUrl: Constant.streamingUrl, toRecord: data["toRecord"] ?? false, rtmpUrls: null); } @@ -479,9 +483,7 @@ class _MoreSettingsBottomSheetState extends State { _meetingStore.stopRtmpAndRecording(); } else { _meetingStore.startRtmpOrRecording( - meetingUrl: Constant.streamingUrl, - toRecord: true, - rtmpUrls: []); + toRecord: true, rtmpUrls: []); } Navigator.pop(context); }, diff --git a/example/lib/common/bottom_sheets/viewer_settings_bottom_sheet.dart b/example/lib/common/bottom_sheets/viewer_settings_bottom_sheet.dart index 1db8edfbb..188af5036 100644 --- a/example/lib/common/bottom_sheets/viewer_settings_bottom_sheet.dart +++ b/example/lib/common/bottom_sheets/viewer_settings_bottom_sheet.dart @@ -1,3 +1,6 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -18,6 +21,13 @@ class ViewerSettingsBottomSheet extends StatefulWidget { } class _ViewerSettingsBottomSheetState extends State { + bool isStatsEnabled = true; + @override + void initState() { + super.initState(); + isStatsEnabled = context.read().isHLSStatsEnabled; + } + @override Widget build(BuildContext context) { return FractionallySizedBox( @@ -266,6 +276,52 @@ class _ViewerSettingsBottomSheetState extends State { letterSpacing: 0.25, fontWeight: FontWeight.w600), )), + if (Platform.isAndroid) + ListTile( + horizontalTitleGap: 2, + onTap: () async { + Navigator.pop(context); + context.read().enterPipModeOnAndroid(); + }, + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + "assets/icons/screen_share.svg", + fit: BoxFit.scaleDown, + color: themeDefaultColor, + ), + title: Text( + "Enter Pip Mode", + semanticsLabel: "fl_pip_mode", + style: GoogleFonts.inter( + fontSize: 14, + color: themeDefaultColor, + letterSpacing: 0.25, + fontWeight: FontWeight.w600), + )), + ListTile( + horizontalTitleGap: 2, + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + "assets/icons/stats.svg", + fit: BoxFit.scaleDown, + ), + title: Text( + "Show Player Stats", + style: GoogleFonts.inter( + fontSize: 14, + color: themeDefaultColor, + letterSpacing: 0.25, + fontWeight: FontWeight.w600), + ), + trailing: CupertinoSwitch( + activeColor: hmsdefaultColor, + value: isStatsEnabled, + onChanged: (value) => { + isStatsEnabled = value, + context.read().setHLSPlayerStats(value), + setState(() {}), + }), + ), ], ), ), diff --git a/example/lib/common/peer_widgets/more_option.dart b/example/lib/common/peer_widgets/more_option.dart index fd753ade8..8a60f2a25 100644 --- a/example/lib/common/peer_widgets/more_option.dart +++ b/example/lib/common/peer_widgets/more_option.dart @@ -65,7 +65,7 @@ class MoreOption extends StatelessWidget { setOnSpotlight: () { if (context.read().spotLightPeer?.uid == peerTrackNode.uid) { - _meetingStore.setSessionMetadata( + _meetingStore.setSessionMetadataForKey( key: SessionStoreKeyValues.getNameFromMethod( SessionStoreKey.SPOTLIGHT), metadata: null); @@ -77,7 +77,7 @@ class MoreOption extends StatelessWidget { String? metadata = (peerTrackNode.audioTrack == null) ? peerTrackNode.track?.trackId : peerTrackNode.audioTrack?.trackId; - _meetingStore.setSessionMetadata( + _meetingStore.setSessionMetadataForKey( key: SessionStoreKeyValues.getNameFromMethod( SessionStoreKey.SPOTLIGHT), metadata: metadata); @@ -275,7 +275,7 @@ class MoreOption extends StatelessWidget { setOnSpotlight: () { if (context.read().spotLightPeer?.uid == peerTrackNode.uid) { - _meetingStore.setSessionMetadata( + _meetingStore.setSessionMetadataForKey( key: SessionStoreKeyValues.getNameFromMethod( SessionStoreKey.SPOTLIGHT), metadata: null); @@ -287,7 +287,7 @@ class MoreOption extends StatelessWidget { String? metadata = (peerTrackNode.audioTrack == null) ? peerTrackNode.track?.trackId : peerTrackNode.audioTrack?.trackId; - _meetingStore.setSessionMetadata( + _meetingStore.setSessionMetadataForKey( key: SessionStoreKeyValues.getNameFromMethod( SessionStoreKey.SPOTLIGHT), metadata: metadata); diff --git a/example/lib/common/peer_widgets/tile_border.dart b/example/lib/common/peer_widgets/tile_border.dart index 9f710f2e5..02090b7ae 100644 --- a/example/lib/common/peer_widgets/tile_border.dart +++ b/example/lib/common/peer_widgets/tile_border.dart @@ -5,7 +5,6 @@ import 'package:hmssdk_flutter_example/model/peer_track_node.dart'; import 'package:provider/provider.dart'; //Project imports -import 'package:hmssdk_flutter_example/common/util/utility_function.dart'; class TileBorder extends StatelessWidget { final double itemHeight; @@ -30,7 +29,7 @@ class TileBorder extends StatelessWidget { decoration: BoxDecoration( border: Border.all( color: (audioLevel != -1) - ? Utilities.getBackgroundColour(name) + ? hmsdefaultColor : themeBottomSheetColor, width: (audioLevel != -1) ? 4.0 : 0.0), borderRadius: BorderRadius.all(Radius.circular(10)), diff --git a/example/lib/common/util/animated_text.dart b/example/lib/common/util/animated_text.dart new file mode 100644 index 000000000..bd7226942 --- /dev/null +++ b/example/lib/common/util/animated_text.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; + +class AnimatedTextWidget extends StatefulWidget { + final String text; + final Duration duration; + + AnimatedTextWidget({required this.text, required this.duration}); + + @override + _AnimatedTextWidgetState createState() => _AnimatedTextWidgetState(); +} + +class _AnimatedTextWidgetState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + + @override + void initState() { + super.initState(); + + // Create an animation controller + _controller = AnimationController( + vsync: this, + duration: widget.duration, + ); + + // Create a tween animation for vertical offset + _animation = Tween( + begin: Offset(0.0, 1.0), // Start from the bottom of the screen + end: Offset(0.0, 0.0), // Fly to the top of the screen + ).animate(CurvedAnimation( + parent: _controller, + curve: Curves.easeInOut, + )); + + // Start the animation + _controller.forward(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SlideTransition( + position: _animation, + child: Text( + widget.text, + style: TextStyle(fontSize: 24), + ), + ); + } +} diff --git a/example/lib/common/util/log_writer.dart b/example/lib/common/util/log_writer.dart index 06903132a..d0c2f7d44 100644 --- a/example/lib/common/util/log_writer.dart +++ b/example/lib/common/util/log_writer.dart @@ -4,19 +4,22 @@ import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:path_provider/path_provider.dart'; File? _logFile; -Future get _localPath async { +Future get _localPath async { final directory = Platform.isAndroid ? await getExternalStorageDirectory() : await getApplicationSupportDirectory(); - return directory!.path; + return directory?.path; } -Future get getLogFile async { +Future get getLogFile async { if (_logFile == null) { final path = await _localPath; + if (path == null) { + return null; + } _logFile = File('$path/hms_log.txt'); } - return _logFile!; + return _logFile; } Future deleteFile() async { @@ -28,7 +31,9 @@ Future deleteFile() async { Future writeLogs(HMSLogList? logsList) async { final file = await getLogFile; // Write the file - logsList?.hmsLog.forEach((element) { - file.writeAsString(element + "\n", mode: FileMode.append); - }); + if (file != null) { + logsList?.hmsLog.forEach((element) { + file.writeAsString(element + "\n", mode: FileMode.append); + }); + } } diff --git a/example/lib/common/util/utility_components.dart b/example/lib/common/util/utility_components.dart index 79e94eb1e..23b4cf12f 100644 --- a/example/lib/common/util/utility_components.dart +++ b/example/lib/common/util/utility_components.dart @@ -566,7 +566,6 @@ class UtilityComponents { static showDialogForBulkRoleChange(BuildContext context, List roles, MeetingStore _meetingStore) async { - double width = MediaQuery.of(context).size.width; List _selectedRoles = []; HMSRole toRole = roles[0]; @@ -1363,7 +1362,6 @@ class UtilityComponents { static void showChangeAudioMixingModeDialog(BuildContext context) { HMSAudioMixingMode valueChoose = HMSAudioMixingMode.TALK_AND_MUSIC; - double width = MediaQuery.of(context).size.width; MeetingStore _meetingStore = context.read(); void _updateDropDownValue(dynamic newValue) { diff --git a/example/lib/common/util/utility_function.dart b/example/lib/common/util/utility_function.dart index d11b58386..e62688513 100644 --- a/example/lib/common/util/utility_function.dart +++ b/example/lib/common/util/utility_function.dart @@ -3,21 +3,23 @@ import 'dart:io'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:hmssdk_flutter_example/common/util/app_color.dart'; +import 'package:hmssdk_flutter_example/common/util/animated_text.dart'; import 'package:hmssdk_flutter_example/service/constant.dart'; import 'package:hmssdk_flutter_example/enum/meeting_flow.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; class Utilities { - static RegExp REGEX_EMOJI = RegExp( + static RegExp regexEmoji = RegExp( r'(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])'); static String getAvatarTitle(String name) { - if (name.contains(REGEX_EMOJI)) { - name = name.replaceAll(REGEX_EMOJI, ''); + if (name.contains(regexEmoji)) { + name = name.replaceAll(regexEmoji, ''); if (name.trim().isEmpty) { return '😄'; } @@ -37,8 +39,8 @@ class Utilities { } static Color getBackgroundColour(String name) { - if (name.contains(REGEX_EMOJI)) { - name = name.replaceAll(REGEX_EMOJI, ''); + if (name.contains(regexEmoji)) { + name = name.replaceAll(regexEmoji, ''); if (name.trim().isEmpty) { return Color(0xFF6554C0); } @@ -55,6 +57,15 @@ class Utilities { Colors.redAccent ]; + /// List of alignments for timed metadata toasts + static List timedMetadataAlignment = [ + Alignment(-0.9, 0.4), + Alignment(-0.4, 0.2), + Alignment(0, 0.8), + Alignment(0.4, 0.9), + Alignment(0.8, 0.5) + ]; + static double getRatio(Size size, BuildContext context) { EdgeInsets viewPadding = MediaQuery.of(context).viewPadding; return (size.height - @@ -144,6 +155,20 @@ class Utilities { duration: Duration(seconds: time)); } + static void showTimedMetadata(String message, + {int time = 1, Alignment align = const Alignment(0, 0.8)}) { + BotToast.showText( + align: align, + wrapToastAnimation: (controller, cancelFunc, widget) => + AnimatedTextWidget( + text: message, duration: Duration(seconds: time)), + onlyOne: false, + textStyle: GoogleFonts.inter(fontSize: 14), + text: message, + contentColor: Colors.black87, + duration: Duration(seconds: time)); + } + static Future getStringData({required String key}) async { final prefs = await SharedPreferences.getInstance(); @@ -257,4 +282,50 @@ class Utilities { : HMSTrackInitState.UNMUTED, forceSoftwareDecoder: isSoftwareDecoderDisabled)); } + + static String getTimedMetadataEmojiFromId(String emojiId) { + switch (emojiId) { + case "+1": + return "👍"; + case "-1": + return "👎"; + case "wave": + return "👋"; + case "clap": + return "👏"; + case "fire": + return "🔥"; + case "tada": + return "🎉"; + case "heart_eyes": + return "😍"; + case "joy": + return "😂"; + case "open_mouth": + return "😮"; + case "sob": + return "😭"; + default: + return ""; + } + } + + static void initForegroundTask() { + FlutterForegroundTask.init( + androidNotificationOptions: AndroidNotificationOptions( + channelId: '100ms_flutter_notification', + channelName: '100ms Flutter Notification', + channelDescription: + 'This notification appears when the foreground service is running.', + channelImportance: NotificationChannelImportance.LOW, + priority: NotificationPriority.LOW, + iconData: const NotificationIconData( + resType: ResourceType.mipmap, + resPrefix: ResourcePrefix.ic, + name: 'launcher', + )), + iosNotificationOptions: + const IOSNotificationOptions(showNotification: false), + foregroundTaskOptions: const ForegroundTaskOptions()); + } } diff --git a/example/lib/common/widgets/message_container.dart b/example/lib/common/widgets/message_container.dart index a529d05c9..7875d4e64 100644 --- a/example/lib/common/widgets/message_container.dart +++ b/example/lib/common/widgets/message_container.dart @@ -188,7 +188,7 @@ class MessageContainer extends StatelessWidget { child: Text('Pin Message'), onTap: () => context .read() - .setSessionMetadata( + .setSessionMetadataForKey( key: SessionStoreKeyValues.getNameFromMethod( SessionStoreKey.PINNED_MESSAGE_SESSION_KEY), metadata: senderName! + ": " + message), diff --git a/example/lib/common/widgets/pip_view.dart b/example/lib/common/widgets/pip_view.dart index 132fa8b8a..873dd9f3b 100644 --- a/example/lib/common/widgets/pip_view.dart +++ b/example/lib/common/widgets/pip_view.dart @@ -44,8 +44,7 @@ class _PipViewState extends State { "REGULAR") ? ScaleType.SCALE_ASPECT_FIT : ScaleType.SCALE_ASPECT_FILL, - setMirror: false, - matchParent: false), + setMirror: false), ); }, )) diff --git a/example/lib/common/widgets/share_link_option.dart b/example/lib/common/widgets/share_link_option.dart index 7ab7ee571..d26203553 100644 --- a/example/lib/common/widgets/share_link_option.dart +++ b/example/lib/common/widgets/share_link_option.dart @@ -38,7 +38,6 @@ class _ShareLinkOptionDialogState extends State { @override Widget build(BuildContext context) { - double width = MediaQuery.of(context).size.width; return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), actionsPadding: EdgeInsets.only(left: 20, right: 20, bottom: 10), diff --git a/example/lib/common/widgets/video_view.dart b/example/lib/common/widgets/video_view.dart index b6304af2e..b9ddb4199 100644 --- a/example/lib/common/widgets/video_view.dart +++ b/example/lib/common/widgets/video_view.dart @@ -52,7 +52,6 @@ class _VideoViewState extends State { scaleType: widget.scaleType, track: data.item1!, setMirror: false, - matchParent: false, disableAutoSimulcastLayerSelect: !(context.read().isAutoSimulcast), ), @@ -67,6 +66,7 @@ class _VideoViewState extends State { width: widget.itemWidth, // [key] property can be used to forcefully rebuild the video widget by setting a unique key everytime. // Similarly to avoid rebuilding the key should be kept the same for particular HMSVideoView. + child: HMSVideoView( key: Key(data.item1!.trackId), scaleType: ScaleType.SCALE_ASPECT_FILL, @@ -74,7 +74,6 @@ class _VideoViewState extends State { setMirror: data.item1.runtimeType == HMSLocalVideoTrack ? context.read().isMirror : false, - matchParent: false, disableAutoSimulcastLayerSelect: !(context.read().isAutoSimulcast), ), diff --git a/example/lib/hls_viewer/hls_player.dart b/example/lib/hls_viewer/hls_player.dart index 7d5282059..58ac37543 100644 --- a/example/lib/hls_viewer/hls_player.dart +++ b/example/lib/hls_viewer/hls_player.dart @@ -1,15 +1,16 @@ import 'package:flutter/material.dart'; -import 'package:pip_flutter/pipflutter_player.dart'; -import 'package:pip_flutter/pipflutter_player_controller.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:hmssdk_flutter_example/common/util/app_color.dart'; +import 'package:hmssdk_flutter_example/hls_viewer/hls_stats_view.dart'; import 'package:provider/provider.dart'; //Project imports import 'package:hmssdk_flutter_example/meeting/meeting_store.dart'; class HLSPlayer extends StatefulWidget { - final String? streamUrl; final double? ratio; - HLSPlayer({Key? key, this.streamUrl, this.ratio}) : super(key: key); + HLSPlayer({Key? key, this.ratio}) : super(key: key); @override _HLSPlayerState createState() => _HLSPlayerState(); } @@ -18,80 +19,96 @@ class _HLSPlayerState extends State with TickerProviderStateMixin { late AnimationController animation; late Animation fadeInFadeOut; - @override - void initState() { - super.initState(); - animation = AnimationController( - vsync: this, - duration: Duration(milliseconds: 500), - ); - fadeInFadeOut = Tween(begin: 0.0, end: 1).animate(animation); - - context.read().setPIPVideoController(false, - aspectRatio: widget.ratio ?? (9 / 16), hlsStreamUrl: widget.streamUrl); - animation.forward(); - } - @override Widget build(BuildContext context) { - return Selector( - selector: (_, meetingStore) => meetingStore.hlsVideoController, - builder: (_, controller, __) { - if (controller == null) { - return Scaffold(); - } - return Scaffold( - key: GlobalKey(), - body: Stack( - children: [ - Center( - child: FadeTransition( - opacity: fadeInFadeOut, - child: AspectRatio( - aspectRatio: context.read().hlsAspectRatio, - child: PipFlutterPlayer( - controller: controller, - key: context.read().pipFlutterPlayerKey, - ), - ), - )), - if (!context.read().isPipActive) - Positioned( - bottom: 10, - right: 20, - child: GestureDetector( - onTap: () { - animation.reverse(); - context.read().setPIPVideoController( - true, - hlsStreamUrl: widget.streamUrl); - animation.forward(); - }, - child: Container( - padding: EdgeInsets.all(5), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon( - Icons.circle, - color: Colors.red, - size: 15, - ), - SizedBox( - width: 5, - ), - Text( - "Go Live", - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold), - ) - ]), + return Scaffold( + key: GlobalKey(), + body: Stack( + children: [ + Center( + child: Selector( + selector: (_, meetingStore) => meetingStore.hlsAspectRatio, + builder: (_, ratio, __) { + return AspectRatio( + aspectRatio: ratio, + child: HMSHLSPlayer( + showPlayerControls: true, + isHLSStatsRequired: + context.read().isHLSStatsEnabled, ), - ), - ) - ], - )); - }); + ); + })), + Selector( + selector: (_, meetingStore) => meetingStore.isHLSStatsEnabled, + builder: (_, isHLSStatsEnabled, __) { + return isHLSStatsEnabled + ? Align( + alignment: Alignment.topLeft, + child: ChangeNotifierProvider.value( + value: context.read(), + child: HLSStatsView(), + ), + ) + : Container(); + }), + if (!context.read().isPipActive) + Positioned( + bottom: 10, + right: 20, + child: GestureDetector( + onTap: () { + HMSHLSPlayerController.seekToLivePosition(); + }, + child: Container( + padding: EdgeInsets.all(5), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.circle, + color: Colors.red, + size: 15, + ), + SizedBox( + width: 5, + ), + Text( + "Go Live", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold), + ) + ]), + ), + ), + ), + Align( + alignment: Alignment.topRight, + child: GestureDetector( + onTap: () { + var _meetingStore = context.read(); + if (_meetingStore.isLandscapeLocked) { + _meetingStore.setLandscapeLock(false); + if (_meetingStore.isDefaultAspectRatioSelected) { + _meetingStore.setAspectRatio(9 / 16); + } + } else { + _meetingStore.setLandscapeLock(true); + if (_meetingStore.isDefaultAspectRatioSelected) { + _meetingStore.setAspectRatio(16 / 9); + } + } + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + "assets/icons/rotate.svg", + color: iconColor, + ), + ), + ), + ) + ], + )); } } diff --git a/example/lib/hls_viewer/hls_stats_view.dart b/example/lib/hls_viewer/hls_stats_view.dart new file mode 100644 index 000000000..4aa417f86 --- /dev/null +++ b/example/lib/hls_viewer/hls_stats_view.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:hmssdk_flutter_example/common/util/app_color.dart'; +import 'package:hmssdk_flutter_example/meeting/meeting_store.dart'; +import 'package:provider/provider.dart'; + +class HLSStatsView extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(5), + margin: EdgeInsets.all(2), + decoration: BoxDecoration( + color: Colors.black38.withOpacity(0.3), + borderRadius: BorderRadius.all(Radius.circular(10))), + child: ListView( + shrinkWrap: true, + children: [ + Selector( + builder: (_, bitrate, __) { + return Text( + "Bitrate : ${bitrate == null ? "-" : (bitrate / 8000)} KBps", + style: GoogleFonts.inter(color: iconColor, fontSize: 12)); + }, + selector: (_, meetingStore) => + meetingStore.hlsPlayerStats?.averageBitrate), + SizedBox( + height: 10, + ), + Selector( + builder: (_, bufferedDuration, __) { + return Text( + "Buffered Duration : ${bufferedDuration == null ? "-" : bufferedDuration / 1000}", + style: GoogleFonts.inter(color: iconColor, fontSize: 12)); + }, + selector: (_, meetingStore) => + meetingStore.hlsPlayerStats?.bufferedDuration), + SizedBox( + height: 10, + ), + Selector( + builder: (_, videoWidth, __) { + return Text( + "Video Width : ${videoWidth == null ? "-" : videoWidth} px", + style: GoogleFonts.inter(color: iconColor, fontSize: 12)); + }, + selector: (_, meetingStore) => + meetingStore.hlsPlayerStats?.videoWidth), + SizedBox( + height: 10, + ), + Selector( + builder: (_, videoHeight, __) { + return Text( + "Video Height : ${videoHeight == null ? "-" : videoHeight} px", + style: GoogleFonts.inter(color: iconColor, fontSize: 12)); + }, + selector: (_, meetingStore) => + meetingStore.hlsPlayerStats?.videoHeight), + SizedBox( + height: 10, + ), + Selector( + builder: (_, droppedFrameCount, __) { + return Text( + "Dropped Frames : ${droppedFrameCount == null ? "-" : droppedFrameCount} ", + style: GoogleFonts.inter(color: iconColor, fontSize: 12)); + }, + selector: (_, meetingStore) => + meetingStore.hlsPlayerStats?.droppedFrameCount), + SizedBox( + height: 10, + ), + Selector( + builder: (_, distanceFromLive, __) { + return Text( + "Distance from live edge : ${distanceFromLive == null ? "-" : distanceFromLive / 1000}s", + style: GoogleFonts.inter(color: iconColor, fontSize: 12)); + }, + selector: (_, meetingStore) => + meetingStore.hlsPlayerStats?.distanceFromLive), + ], + ), + ); + } +} diff --git a/example/lib/hls_viewer/hls_viewer_page.dart b/example/lib/hls_viewer/hls_viewer_page.dart index 83116c2be..ec0ff3dbd 100644 --- a/example/lib/hls_viewer/hls_viewer_page.dart +++ b/example/lib/hls_viewer/hls_viewer_page.dart @@ -18,10 +18,7 @@ import 'package:provider/provider.dart'; import 'package:tuple/tuple.dart'; class HLSViewerPage extends StatefulWidget { - final String? streamUrl; - HLSViewerPage({ - this.streamUrl, Key? key, }) : super(key: key); @override @@ -93,410 +90,440 @@ class _HLSViewerPageState extends State { Navigator.of(context).popUntil((route) => route.isFirst); }); } - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Stack( - children: [ - Selector>( - selector: (_, meetingStore) => Tuple2( - meetingStore.isPipActive, - meetingStore.hasHlsStarted), - builder: (_, data, __) { - if (!context - .read() - .isHLSPlayerRequired && - !data.item1) { - return Center( - child: Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: TitleText( - text: - "Please Switch to normal mode for viewing the stream", - textColor: themeDefaultColor)), - ); - } - return (data.item2 || widget.streamUrl != null) - ? Container( - height: MediaQuery.of(context).size.height, - child: Center( - child: HLSPlayer( - streamUrl: widget.streamUrl, - ratio: Utilities.getHLSPlayerDefaultRatio( - MediaQuery.of(context).size), - ), - )) - : Center( - child: Padding( - padding: - const EdgeInsets.only(bottom: 8.0), - child: TitleText( - text: "Waiting for HLS to start...", - textColor: themeDefaultColor)), - ); - }), - if (!context.read().isPipActive) - Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 15, right: 15, top: 5, bottom: 2), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + return Selector( + selector: (_, meetingStore) => meetingStore.isPipActive, + builder: (_, isPipActive, __) { + return isPipActive + ? HMSHLSPlayer() + : Scaffold( + resizeToAvoidBottomInset: false, + body: SafeArea( + child: Stack( children: [ - Row( - children: [ - HMSEmbeddedButton( - 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< - MeetingStore, - Tuple4< - bool, - bool, - Map, - Map>>( - selector: (_, meetingStore) => Tuple4( - ((meetingStore.streamingType[ - "rtmp"] ?? - false) || - (meetingStore.streamingType[ - "hls"] ?? - false)), - ((meetingStore.recordingType[ - "browser"] ?? - false) || - (meetingStore.recordingType[ - "server"] ?? - false) || - ((meetingStore - .recordingType["hls"] ?? - false))), - meetingStore.streamingType, - meetingStore.recordingType), - builder: (_, roomState, __) { - if (roomState.item1 || - roomState.item2) - return Column( + Selector( + selector: (_, meetingStore) => + meetingStore.hasHlsStarted, + builder: (_, hasHlsStarted, __) { + return (hasHlsStarted) + ? Padding( + padding: const EdgeInsets.only( + top: 50.0), + child: Container( + height: MediaQuery.of(context) + .size + .height, + child: Center( + child: HLSPlayer( + ratio: Utilities + .getHLSPlayerDefaultRatio( + MediaQuery.of( + context) + .size), + ), + )), + ) + : Center( + child: Padding( + padding: + const EdgeInsets.only( + bottom: 8.0), + child: TitleText( + text: + "Waiting for HLS to start...", + textColor: + themeDefaultColor)), + ); + }), + isPipActive + ? Container() + : Column( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 15, + right: 15, + top: 5, + bottom: 2), + child: Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, - crossAxisAlignment: - CrossAxisAlignment.start, children: [ Row( children: [ - Padding( - padding: - const EdgeInsets.only( - right: 5.0), + HMSEmbeddedButton( + 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/live_stream.svg", - color: errorColor, + "assets/icons/leave_hls.svg", + color: Colors.white, fit: BoxFit.scaleDown, - ), - ), - GestureDetector( - onTap: () { - if (!roomState.item1 && - roomState.item2) - Utilities.showToast( - recordingState()); - }, - child: Text( - (roomState.item1 && - roomState.item2) - ? "Live & Recording" - : (roomState.item1) - ? streamingState() - : (roomState - .item2) - ? "Recording" - : "", semanticsLabel: - "fl_live_stream_running", - style: GoogleFonts.inter( - fontSize: 16, - color: - themeDefaultColor, - letterSpacing: 0.5, - fontWeight: - FontWeight - .w600), + "leave_button", ), ), - ], - ), - roomState.item1 - ? Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - (roomState.item3[ - "hls"] ?? - false) - ? Row( + SizedBox( + width: 10, + ), + Selector< + MeetingStore, + Tuple4< + bool, + bool, + Map, + Map>>( + selector: (_, meetingStore) => Tuple4( + ((meetingStore.streamingType["rtmp"] ?? + false) || + (meetingStore.streamingType[ + "hls"] ?? + false)), + ((meetingStore.recordingType[ + "browser"] ?? + false) || + (meetingStore.recordingType[ + "server"] ?? + false) || + ((meetingStore + .recordingType[ + "hls"] ?? + false))), + meetingStore + .streamingType, + meetingStore + .recordingType), + builder: + (_, roomState, __) { + if (roomState.item1 || + roomState.item2) + return Column( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Row( children: [ - SvgPicture - .asset( - "assets/icons/clock.svg", - color: - themeSubHeadingColor, - fit: BoxFit - .scaleDown, + Padding( + padding: const EdgeInsets + .only( + right: + 5.0), + child: SvgPicture + .asset( + "assets/icons/live_stream.svg", + color: + errorColor, + fit: BoxFit + .scaleDown, + ), ), - SizedBox( - width: 6, + GestureDetector( + onTap: + () { + if (!roomState.item1 && + roomState.item2) + Utilities.showToast( + recordingState()); + }, + child: + Text( + (roomState.item1 && + roomState.item2) + ? "Live & Recording" + : (roomState.item1) + ? streamingState() + : (roomState.item2) + ? "Recording" + : "", + semanticsLabel: + "fl_live_stream_running", + style: GoogleFonts.inter( + fontSize: + 16, + color: + themeDefaultColor, + letterSpacing: + 0.5, + fontWeight: + FontWeight.w600), + ), ), - Selector< - MeetingStore, - HMSRoom?>( - selector: (_, - meetingStore) => - meetingStore - .hmsRoom, - builder: (_, - hmsRoom, - __) { - if (hmsRoom != null && - hmsRoom.hmshlsStreamingState != null && - hmsRoom.hmshlsStreamingState!.variants.length != 0 && - hmsRoom.hmshlsStreamingState!.variants[0]!.startedAt != null) { - return HMSStreamTimer(startedAt: hmsRoom.hmshlsStreamingState!.variants[0]!.startedAt!); - } - return SubtitleText( + ], + ), + roomState.item1 + ? Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + (roomState.item3["hls"] ?? false) + ? 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 HMSStreamTimer(startedAt: hmsRoom.hmshlsStreamingState!.variants[0]!.startedAt!); + } + return SubtitleText( + text: "00:00", + textColor: themeSubHeadingColor, + ); + }), + ], + ) + : Container(), + SubtitleText( text: - "00:00", + " | ", textColor: - themeSubHeadingColor, - ); - }), - ], - ) - : Container(), - SubtitleText( - text: " | ", - textColor: - dividerColor, - ), - Row( - children: [ - SvgPicture.asset( - "assets/icons/watching.svg", - color: - themeSubHeadingColor, - fit: BoxFit - .scaleDown, - ), - SizedBox( - width: 6, - ), - Selector< - MeetingStore, - int>( - selector: (_, - meetingStore) => - meetingStore - .peers - .length, - builder: (_, - length, - __) { - return SubtitleText( - text: length - .toString(), - textColor: - themeSubHeadingColor); - }) - ], - ) - ], - ) - : Container() - ], - ); - return SizedBox(); - }) - ], - ), - Row( - children: [ - Selector( - selector: (_, meetingStore) => - meetingStore.isNewMessageReceived, - builder: (_, isNewMessageReceived, __) { - return HMSEmbeddedButton( - onTap: () => { - context - .read() - .setNewMessageFalse(), - showModalBottomSheet( - isScrollControlled: true, - backgroundColor: - themeBottomSheetColor, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(20), + dividerColor, + ), + Row( + children: [ + SvgPicture.asset( + "assets/icons/watching.svg", + color: themeSubHeadingColor, + fit: BoxFit.scaleDown, + ), + SizedBox( + width: 6, + ), + Selector( + selector: (_, meetingStore) => meetingStore.peers.length, + builder: (_, length, __) { + return SubtitleText(text: length.toString(), textColor: themeSubHeadingColor); + }) + ], + ) + ], + ) + : Container() + ], + ); + return SizedBox(); + }) + ], ), - context: context, - builder: (ctx) => - ChangeNotifierProvider.value( - value: context.read< - MeetingStore>(), - child: - ChatBottomSheet()), - ) - }, - 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, - ), - HMSEmbeddedButton( - onTap: () => { - showModalBottomSheet( - isScrollControlled: true, - backgroundColor: - themeBottomSheetColor, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(20), + Row( + children: [ + Selector( + selector: (_, + meetingStore) => + meetingStore + .isNewMessageReceived, + builder: (_, + isNewMessageReceived, + __) { + return HMSEmbeddedButton( + onTap: () => { + context + .read< + MeetingStore>() + .setNewMessageFalse(), + showModalBottomSheet( + isScrollControlled: + true, + backgroundColor: + themeBottomSheetColor, + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius + .circular( + 20), + ), + context: + context, + builder: (ctx) => ChangeNotifierProvider.value( + value: context + .read< + MeetingStore>(), + child: + ChatBottomSheet()), + ) + }, + 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, + ), + HMSEmbeddedButton( + onTap: () => { + showModalBottomSheet( + isScrollControlled: + true, + backgroundColor: + themeBottomSheetColor, + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius + .circular( + 20), + ), + context: context, + builder: (ctx) => + ChangeNotifierProvider.value( + value: context + .read< + MeetingStore>(), + child: + ViewerSettingsBottomSheet())) + }, + 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", + ), + ), + ], + ) + ], ), - context: context, - builder: (ctx) => - ChangeNotifierProvider.value( - value: context - .read(), - child: - ViewerSettingsBottomSheet())) - }, - 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.reconnecting, + builder: (_, reconnecting, __) { + if (reconnecting) { + return UtilityComponents + .showReconnectingDialog(context); + } + 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/hms_sdk_interactor.dart b/example/lib/hms_sdk_interactor.dart index d2fb7790c..14760646b 100644 --- a/example/lib/hms_sdk_interactor.dart +++ b/example/lib/hms_sdk_interactor.dart @@ -329,17 +329,6 @@ class HMSSDKInteractor { hmsSDK.destroy(); } - void setSessionMetadata( - {required String? metadata, - HMSActionResultListener? hmsActionResultListener}) { - hmsSDK.setSessionMetadata( - metadata: metadata, hmsActionResultListener: hmsActionResultListener); - } - - Future getSessionMetadata() { - return hmsSDK.getSessionMetadata(); - } - void changeRoleOfPeersWithRoles( {required HMSRole toRole, required List ofRoles, @@ -359,4 +348,14 @@ class HMSSDKInteractor { return await hmsSDK.getAuthTokenByRoomCode( roomCode: roomCode, userId: userId, endPoint: endPoint); } + + void switchAudioOutputUsingiOSUI() { + return hmsSDK.switchAudioOutputUsingiOSUI(); + } + + void sendHLSTimedMetadata(List metadata, + HMSActionResultListener? hmsActionResultListener) { + hmsSDK.sendHLSTimedMetadata( + metadata: metadata, hmsActionResultListener: hmsActionResultListener); + } } diff --git a/example/lib/home_screen/screen_controller.dart b/example/lib/home_screen/screen_controller.dart index 31ac6e42b..2330734dd 100644 --- a/example/lib/home_screen/screen_controller.dart +++ b/example/lib/home_screen/screen_controller.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:hmssdk_flutter_example/common/util/utility_components.dart'; +import 'package:hmssdk_flutter_example/common/util/utility_function.dart'; import 'package:hmssdk_flutter_example/hls_viewer/hls_viewer_page.dart'; import 'package:hmssdk_flutter_example/meeting/meeting_store.dart'; import 'package:hmssdk_flutter_example/meeting/meeting_page.dart'; @@ -14,7 +15,6 @@ class ScreenController extends StatefulWidget { final bool isRoomMute; final bool showStats; final bool mirrorCamera; - final String? streamUrl; final HMSRole? role; final HMSConfig? config; const ScreenController( @@ -26,7 +26,6 @@ class ScreenController extends StatefulWidget { this.isRoomMute = false, this.showStats = false, this.mirrorCamera = true, - this.streamUrl, this.role, this.config}) : super(key: key); @@ -41,6 +40,7 @@ class _ScreenControllerState extends State { super.initState(); initMeeting(); setInitValues(); + Utilities.initForegroundTask(); } void initMeeting() async { @@ -65,23 +65,17 @@ class _ScreenControllerState extends State { @override Widget build(BuildContext context) { - if ((Provider.of(context).localPeer != null && - Provider.of(context) - .localPeer! - .role - .name - .contains("hls-")) || - ((widget.role?.name.contains("hls-") ?? false) && - widget.streamUrl != null)) { - return HLSViewerPage( - streamUrl: widget.streamUrl, - ); - } else { - return MeetingPage( - isStreamingLink: widget.isStreamingLink, - meetingLink: widget.meetingLink, - isRoomMute: widget.isRoomMute, - ); - } + return Selector( + builder: (_, data, __) { + if (data?.contains("hls-") ?? false) { + return HLSViewerPage(); + } + return MeetingPage( + isStreamingLink: widget.isStreamingLink, + meetingLink: widget.meetingLink, + isRoomMute: widget.isRoomMute, + ); + }, + selector: (_, meetingStore) => meetingStore.localPeer?.role.name); } } diff --git a/example/lib/main.dart b/example/lib/main.dart index d5e80377c..a4b57e50b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -19,7 +19,6 @@ import 'package:hmssdk_flutter_example/common/bottom_sheets/app_settings_bottom_ import 'package:hmssdk_flutter_example/home_screen/user_detail_screen.dart'; import 'package:hmssdk_flutter_example/home_screen/qr_code_screen.dart'; import 'package:provider/provider.dart'; -import 'package:wakelock/wakelock.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:uni_links/uni_links.dart'; @@ -41,13 +40,15 @@ void main() async { return true; }; - Wakelock.enable(); Provider.debugCheckInvalidValueType = null; // Get any initial links final PendingDynamicLinkData? initialLink = await FirebaseDynamicLinks.instance.getInitialLink(); + await SystemChrome.setPreferredOrientations( + [DeviceOrientation.portraitUp], + ); runZonedGuarded(() => runApp(HMSExampleApp(initialLink: initialLink?.link)), FirebaseCrashlytics.instance.recordError); } @@ -96,7 +97,6 @@ class _HMSExampleAppState extends State { _incomingLinkHandler(); initDynamicLinks(); setThemeMode(); - SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); } Future _initURIHandler() async { diff --git a/example/lib/meeting/meeting_page.dart b/example/lib/meeting/meeting_page.dart index 5aca1f838..2e58d7462 100644 --- a/example/lib/meeting/meeting_page.dart +++ b/example/lib/meeting/meeting_page.dart @@ -23,13 +23,13 @@ import 'package:hmssdk_flutter_example/enum/meeting_mode.dart'; import 'package:hmssdk_flutter_example/common/bottom_sheets/start_hls_bottom_sheet.dart'; import 'package:hmssdk_flutter_example/common/bottom_sheets/chat_bottom_sheet.dart'; import 'package:hmssdk_flutter_example/common/bottom_sheets/more_settings_bottom_sheet.dart'; -import 'package:hmssdk_flutter_example/hls_viewer/hls_player.dart'; import 'package:hmssdk_flutter_example/meeting/meeting_store.dart'; import 'package:hmssdk_flutter_example/meeting_modes/basic_grid_view.dart'; import 'package:hmssdk_flutter_example/common/widgets/pip_view.dart'; import 'package:hmssdk_flutter_example/model/peer_track_node.dart'; import 'package:provider/provider.dart'; import 'package:tuple/tuple.dart'; +import 'package:wakelock/wakelock.dart'; class MeetingPage extends StatefulWidget { final String meetingLink; @@ -52,26 +52,7 @@ class _MeetingPageState extends State { void initState() { super.initState(); checkAudioState(); - _initForegroundTask(); - } - - void _initForegroundTask() { - FlutterForegroundTask.init( - androidNotificationOptions: AndroidNotificationOptions( - channelId: '100ms_flutter_notification', - channelName: '100ms Flutter Notification', - channelDescription: - 'This notification appears when the foreground service is running.', - channelImportance: NotificationChannelImportance.LOW, - priority: NotificationPriority.LOW, - iconData: const NotificationIconData( - resType: ResourceType.mipmap, - resPrefix: ResourcePrefix.ic, - name: 'launcher', - )), - iosNotificationOptions: - const IOSNotificationOptions(showNotification: false), - foregroundTaskOptions: const ForegroundTaskOptions()); + Wakelock.enable(); } void checkAudioState() async { @@ -265,64 +246,6 @@ class _MeetingPageState extends State { .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( - ratio: Utilities - .getHLSPlayerDefaultRatio( - MediaQuery.of( - context) - .size), - ), - ), - ) - : 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 Center( child: Column( diff --git a/example/lib/meeting/meeting_store.dart b/example/lib/meeting/meeting_store.dart index 671f8e07a..3546ef554 100644 --- a/example/lib/meeting/meeting_store.dart +++ b/example/lib/meeting/meeting_store.dart @@ -1,5 +1,6 @@ //Package imports +import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'dart:math' as Math; @@ -11,8 +12,6 @@ import 'package:hmssdk_flutter_example/common/util/log_writer.dart'; import 'package:hmssdk_flutter_example/app_secrets.dart'; import 'package:hmssdk_flutter_example/enum/session_store_key.dart'; import 'package:hmssdk_flutter_example/service/constant.dart'; -import 'package:hmssdk_flutter_example/common/widgets/title_text.dart'; -import 'package:hmssdk_flutter_example/common/util/app_color.dart'; import 'package:hmssdk_flutter_example/common/util/utility_function.dart'; import 'package:hmssdk_flutter_example/enum/meeting_mode.dart'; import 'package:hmssdk_flutter_example/model/rtc_stats.dart'; @@ -24,14 +23,7 @@ import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:hmssdk_flutter_example/hms_sdk_interactor.dart'; import 'package:hmssdk_flutter_example/model/peer_track_node.dart'; import 'package:hmssdk_flutter_example/service/room_service.dart'; -import 'package:pip_flutter/pipflutter_player_configuration.dart'; -import 'package:pip_flutter/pipflutter_player_controller.dart'; -import 'package:pip_flutter/pipflutter_player_controls_configuration.dart'; -import 'package:pip_flutter/pipflutter_player_data_source.dart'; -import 'package:pip_flutter/pipflutter_player_data_source_type.dart'; -import 'package:pip_flutter/pipflutter_player_event.dart'; -import 'package:pip_flutter/pipflutter_player_event_type.dart'; -import 'package:pip_flutter/pipflutter_player_theme.dart'; +import 'package:wakelock/wakelock.dart'; class MeetingStore extends ChangeNotifier with WidgetsBindingObserver @@ -40,7 +32,8 @@ class MeetingStore extends ChangeNotifier HMSActionResultListener, HMSStatsListener, HMSLogListener, - HMSKeyChangeListener { + HMSKeyChangeListener, + HMSHLSPlaybackEventsListener { late HMSSDKInteractor _hmsSDKInteractor; MeetingStore({required HMSSDKInteractor hmsSDKInteractor}) { @@ -156,9 +149,6 @@ class MeetingStore extends ChangeNotifier int trackChange = -1; - // VideoPlayerController? hlsVideoController; - - PipFlutterPlayerController? hlsVideoController; final GlobalKey pipFlutterPlayerKey = GlobalKey(); bool hlsStreamingRetry = false; @@ -185,8 +175,6 @@ class MeetingStore extends ChangeNotifier HMSLogList applicationLogs = HMSLogList(hmsLog: []); - bool isHLSPlayerRequired = true; - bool isFlashOn = false; ///These variables are used in session metadata implementation ************************************************* @@ -197,6 +185,14 @@ class MeetingStore extends ChangeNotifier String? spotlightMetadata; + ///HLS Player Stats + + HMSHLSPlayerStats? hlsPlayerStats; + + bool isHLSStatsEnabled = false; + + bool isDefaultAspectRatioSelected = true; + Future join(String userName, String roomUrl, {HMSConfig? roomConfig}) async { //If roomConfig is null then only we call the methods to get the authToken @@ -244,6 +240,7 @@ class MeetingStore extends ChangeNotifier _hmsSDKInteractor.addUpdateListener(this); _hmsSDKInteractor.addLogsListener(this); + HMSHLSPlayerController.addHMSHLSPlaybackEventsListener(this); WidgetsBinding.instance.addObserver(this); _hmsSDKInteractor.join(config: roomConfig); this.meetingUrl = roomUrl; @@ -256,12 +253,7 @@ class MeetingStore extends ChangeNotifier _hmsSDKInteractor.removeStatsListener(this); WidgetsBinding.instance.removeObserver(this); hmsException = null; - if ((localPeer?.role.name.contains("hls-") ?? false) && hasHlsStarted) { - hlsVideoController?.dispose(forceDispose: true); - hlsVideoController = null; - } _hmsSDKInteractor.leave(hmsActionResultListener: this); - _hmsSDKInteractor.destroy(); } Future toggleMicMuteState() async { @@ -396,7 +388,7 @@ class MeetingStore extends ChangeNotifier } void startRtmpOrRecording( - {required String meetingUrl, + {String? meetingUrl, required bool toRecord, List? rtmpUrls}) async { HMSRecordingConfig hmsRecordingConfig = new HMSRecordingConfig( @@ -466,6 +458,10 @@ class MeetingStore extends ChangeNotifier _hmsSDKInteractor.stopHLSStreaming(hmsActionResultListener: this); } + void sendHLSTimedMetadata(List metadata) { + _hmsSDKInteractor.sendHLSTimedMetadata(metadata, this); + } + void changeTrackStateForRole(bool mute, List? roles) { _hmsSDKInteractor.changeTrackStateForRole( true, HMSTrackKind.kHMSTrackKindAudio, "regular", roles, this); @@ -570,15 +566,13 @@ class MeetingStore extends ChangeNotifier getAudioDevicesList(); notifyListeners(); - if (!(isHLSLink)) { - if (Platform.isIOS) { - HMSIOSPIPController.setup( - autoEnterPip: true, - aspectRatio: [9, 16], - backgroundColor: Colors.black); - } else if (Platform.isAndroid) { - HMSAndroidPIPController.setup(); - } + if (Platform.isIOS && !(localPeer?.role.name.contains("hls-") ?? false)) { + HMSIOSPIPController.setup( + autoEnterPip: true, + aspectRatio: [9, 16], + backgroundColor: Colors.black); + } else if (Platform.isAndroid) { + HMSAndroidPIPController.setup(); } FlutterForegroundTask.startService( @@ -647,7 +641,6 @@ class MeetingStore extends ChangeNotifier required HMSTrackUpdate trackUpdate, required HMSPeer peer}) { log("onTrackUpdate-> track: ${track.toString()} peer: ${peer.name} update: ${trackUpdate.name}"); - if (peer.role.name.contains("hls-")) return; if (!isSpeakerOn && track.kind == HMSTrackKind.kHMSTrackKindAudio && @@ -701,6 +694,8 @@ class MeetingStore extends ChangeNotifier if (meetingMode == MeetingMode.Single) { rearrangeTile(peerTrackNode, index); } + setSpotlightOnTrackUpdate(track); + return; } else { peerTracks.add(new PeerTrackNode( peer: peer, @@ -711,8 +706,6 @@ class MeetingStore extends ChangeNotifier setSpotlightOnTrackUpdate(track); return; } - - setSpotlightOnTrackUpdate(track); } peerOperationWithTrack(peer, trackUpdate, track); } @@ -1018,28 +1011,24 @@ class MeetingStore extends ChangeNotifier HMSLogList? _logsDump = await _hmsSDKInteractor.getAllogs(); await deleteFile(); writeLogs(_logsDump); - hlsVideoController?.dispose(forceDispose: true); - hlsVideoController = null; if (Platform.isAndroid) { HMSAndroidPIPController.destroy(); } else if (Platform.isIOS) { HMSIOSPIPController.destroy(); } + _hmsSDKInteractor.removeUpdateListener(this); + _hmsSDKInteractor.removeLogsListener(this); _hmsSessionStore?.removeKeyChangeListener(hmsKeyChangeListener: this); _hmsSDKInteractor.removeHMSLogger(); + HMSHLSPlayerController.removeHMSHLSPlaybackEventsListener(this); _hmsSDKInteractor.destroy(); + Wakelock.disable(); _hmsSessionStore = null; peerTracks.clear(); isRoomEnded = true; - screenShareCount = 0; - this.meetingMode = MeetingMode.ActiveSpeaker; - isScreenShareOn = false; - isAudioShareStarted = false; - _hmsSDKInteractor.removeUpdateListener(this); - _hmsSDKInteractor.removeLogsListener(this); setLandscapeLock(false); - notifyListeners(); FlutterForegroundTask.stopService(); + notifyListeners(); } void toggleScreenShare() { @@ -1103,11 +1092,7 @@ class MeetingStore extends ChangeNotifier SystemChrome.setPreferredOrientations( [DeviceOrientation.landscapeRight, DeviceOrientation.landscapeLeft]); } else { - SystemChrome.setPreferredOrientations([ - DeviceOrientation.portraitUp, - DeviceOrientation.landscapeLeft, - DeviceOrientation.landscapeRight - ]); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); } isLandscapeLocked = value; notifyListeners(); @@ -1143,10 +1128,6 @@ class MeetingStore extends ChangeNotifier if (peer.isLocal) { getSpotlightPeer(); localPeer = peer; - if (hlsVideoController != null && !peer.role.name.contains("hls-")) { - hlsVideoController?.dispose(forceDispose: true); - hlsVideoController = null; - } } if (peer.role.name.contains("hls-")) { isHLSLink = peer.isLocal; @@ -1158,9 +1139,6 @@ class MeetingStore extends ChangeNotifier } } - // Setup or destroy PIP controller on role basis - //Pip is not supported for player by default - //So we need to destroy the pip config if (peer.isLocal) { if (Platform.isIOS) { if (peer.role.name.contains("hls-")) { @@ -1171,13 +1149,6 @@ class MeetingStore extends ChangeNotifier aspectRatio: [9, 16], backgroundColor: Colors.black); } - } else { - if (peer.role.name.contains("hls-")) { - HMSAndroidPIPController.destroy(); - } else { - HMSAndroidPIPController.setup( - autoEnterPip: true, aspectRatio: [9, 16]); - } } } @@ -1281,6 +1252,10 @@ class MeetingStore extends ChangeNotifier notifyListeners(); changePIPWindowTextOnIOS(text: localPeer?.name, ratio: [9, 16]); } + peerTracks.removeWhere( + (element) => element.track?.trackId == track.trackId); + notifyListeners(); + return; } else { int peerIndex = peerTracks.indexWhere( (element) => element.uid == peer.peerId + "mainVideo"); @@ -1367,29 +1342,33 @@ class MeetingStore extends ChangeNotifier ///This method sets the peer to spotlight ///this also handles removing a peer from spotlight case void setPeerToSpotlight(String? value) { - int currentSpotlightPeerIndex = - peerTracks.indexWhere((node) => node.uid == spotLightPeer?.uid); - if (currentSpotlightPeerIndex != -1) { - peerTracks[currentSpotlightPeerIndex].pinTile = false; - spotLightPeer = null; - spotlightMetadata = null; - } - if (value != null) { - int index = peerTracks.indexWhere(((node) => - node.audioTrack?.trackId == (value) || - node.track?.trackId == (value))); - if (index != -1) { - Utilities.showToast("${peerTracks[index].peer.name} is in spotlight"); - spotLightPeer = peerTracks[index]; - changePinTileStatus(peerTracks[index]); + try { + int currentSpotlightPeerIndex = + peerTracks.indexWhere((node) => node.uid == spotLightPeer?.uid); + if (currentSpotlightPeerIndex != -1) { + peerTracks[currentSpotlightPeerIndex].pinTile = false; + spotLightPeer = null; + spotlightMetadata = null; + } + if (value != null) { + int index = peerTracks.indexWhere(((node) => + node.audioTrack?.trackId == (value) || + node.track?.trackId == (value))); + if (index != -1) { + Utilities.showToast("${peerTracks[index].peer.name} is in spotlight"); + spotLightPeer = peerTracks[index]; + changePinTileStatus(peerTracks[index]); + } else { + spotlightMetadata = value; + } } else { - spotlightMetadata = value; + spotlightMetadata = null; + spotLightPeer = null; } - } else { - spotlightMetadata = null; - spotLightPeer = null; + notifyListeners(); + } catch (e) { + log("setPeerToSpotlight: $e"); } - notifyListeners(); } void setMode(MeetingMode meetingMode) { @@ -1530,7 +1509,7 @@ class MeetingStore extends ChangeNotifier audioPlayerVolume = volume; } - void setSessionMetadata({required String key, String? metadata}) { + void setSessionMetadataForKey({required String key, String? metadata}) { _hmsSessionStore?.setSessionMetadataForKey( key: key, data: metadata, hmsActionResultListener: this); } @@ -1581,6 +1560,10 @@ class MeetingStore extends ChangeNotifier } } + void switchAudioOutputUsingiOSUI() { + _hmsSDKInteractor.switchAudioOutputUsingiOSUI(); + } + void changePIPWindowTrackOnIOS( {HMSVideoTrack? track, required String alternativeText, @@ -1611,62 +1594,9 @@ class MeetingStore extends ChangeNotifier } } - void setPIPVideoController(bool reinitialise, - {double? aspectRatio, String? hlsStreamUrl}) { - if (hlsVideoController != null) { - hlsVideoController?.dispose(forceDispose: true); - hlsVideoController = null; - } - if (aspectRatio != null) { - hlsAspectRatio = aspectRatio; - } - PipFlutterPlayerConfiguration pipFlutterPlayerConfiguration = - PipFlutterPlayerConfiguration( - //aspectRatio parameter can be used to set the player view based on the ratio selected from dashboard - //Stream aspectRatio can be selected from Dashboard->Templates->Destinations->Customise stream video output->Video aspect ratio - //The selected aspectRatio can be set here to get expected stream resolution - aspectRatio: hlsAspectRatio, - allowedScreenSleep: false, - fit: BoxFit.contain, - showPlaceholderUntilPlay: true, - deviceOrientationsAfterFullScreen: [ - DeviceOrientation.portraitUp, - DeviceOrientation.portraitDown - ], - autoDispose: false, - handleLifecycle: false, - placeholder: Center( - child: TitleText( - text: "Loading...", - textColor: themeDefaultColor, - ), - ), - eventListener: (PipFlutterPlayerEvent event) { - if (event.pipFlutterPlayerEventType == - PipFlutterPlayerEventType.initialized && - isPipActive) { - hlsVideoController?.enablePictureInPicture(pipFlutterPlayerKey); - } - }, - controlsConfiguration: PipFlutterPlayerControlsConfiguration( - controlBarColor: Colors.transparent, - enablePlayPause: false, - enableOverflowMenu: false, - enableSkips: false, - playerTheme: PipFlutterPlayerTheme.cupertino)); - if (streamUrl == null && hlsStreamUrl == null) { - Utilities.showToast("Stream URL is null", time: 5); - } - PipFlutterPlayerDataSource dataSource = PipFlutterPlayerDataSource( - PipFlutterPlayerDataSourceType.network, - ((streamUrl == null) ? hlsStreamUrl : streamUrl) ?? "", - liveStream: true); - hlsVideoController = - PipFlutterPlayerController(pipFlutterPlayerConfiguration); - hlsVideoController?.setupDataSource(dataSource); - hlsVideoController?.play(); - hlsVideoController?.setPipFlutterPlayerGlobalKey(pipFlutterPlayerKey); - if (reinitialise) notifyListeners(); + void setAspectRatio(double ratio) { + hlsAspectRatio = ratio; + notifyListeners(); } void changeRoleOfPeersWithRoles(HMSRole toRole, List ofRoles) { @@ -1741,9 +1671,12 @@ class MeetingStore extends ChangeNotifier break; case HMSActionResultListenerMethod.startRtmpOrRecording: if (arguments != null) { - if (arguments["rtmp_urls"].length == 0 && arguments["to_record"]) { + if (arguments["rtmp_urls"] != null && + arguments["rtmp_urls"].length == 0 && + arguments["to_record"]) { Utilities.showToast("Recording Started"); - } else if (arguments["rtmp_urls"].length != 0 && + } else if (arguments["rtmp_urls"] != null && + arguments["rtmp_urls"].length != 0 && arguments["to_record"] == false) { Utilities.showToast("RTMP Started"); } @@ -1759,46 +1692,27 @@ class MeetingStore extends ChangeNotifier Utilities.showToast("Change name successful"); break; case HMSActionResultListenerMethod.sendBroadcastMessage: - var message = HMSMessage( - sender: localPeer, - message: arguments!['message'], - type: arguments['type'], - time: DateTime.now(), - hmsMessageRecipient: HMSMessageRecipient( - recipientPeer: null, - recipientRoles: null, - hmsMessageRecipientType: HMSMessageRecipientType.BROADCAST)); - if (arguments['type'] != "metadata") { - addMessage(message); - notifyListeners(); + if (arguments != null) { + var message = HMSMessage.fromMap(arguments); + if (arguments['type'] != "metadata") { + addMessage(message); + notifyListeners(); + } } break; case HMSActionResultListenerMethod.sendGroupMessage: - var message = HMSMessage( - sender: localPeer, - message: arguments!['message'], - type: arguments['type'], - time: DateTime.now(), - hmsMessageRecipient: HMSMessageRecipient( - recipientPeer: null, - recipientRoles: arguments['roles'], - hmsMessageRecipientType: HMSMessageRecipientType.GROUP)); - addMessage(message); - notifyListeners(); - + if (arguments != null) { + var message = HMSMessage.fromMap(arguments); + addMessage(message); + notifyListeners(); + } break; case HMSActionResultListenerMethod.sendDirectMessage: - var message = HMSMessage( - sender: localPeer, - message: arguments!['message'], - type: arguments['type'], - time: DateTime.now(), - hmsMessageRecipient: HMSMessageRecipient( - recipientPeer: arguments['peer'], - recipientRoles: null, - hmsMessageRecipientType: HMSMessageRecipientType.DIRECT)); - addMessage(message); - notifyListeners(); + if (arguments != null) { + var message = HMSMessage.fromMap(arguments); + addMessage(message); + notifyListeners(); + } break; case HMSActionResultListenerMethod.hlsStreamingStarted: isHLSLoading = true; @@ -1829,13 +1743,6 @@ class MeetingStore extends ChangeNotifier isAudioShareStarted = false; notifyListeners(); break; - 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; @@ -1844,6 +1751,9 @@ class MeetingStore extends ChangeNotifier break; case HMSActionResultListenerMethod.setSessionMetadataForKey: break; + case HMSActionResultListenerMethod.sendHLSTimedMetadata: + Utilities.showToast("Metadata sent successfully"); + break; } } @@ -1916,8 +1826,6 @@ class MeetingStore extends ChangeNotifier break; case HMSActionResultListenerMethod.stopAudioShare: break; - case HMSActionResultListenerMethod.setSessionMetadata: - break; case HMSActionResultListenerMethod.switchCamera: Utilities.showToast("Camera switching failed"); break; @@ -1927,6 +1835,9 @@ class MeetingStore extends ChangeNotifier case HMSActionResultListenerMethod.setSessionMetadataForKey: Utilities.showToast("Set session metadata failed"); break; + case HMSActionResultListenerMethod.sendHLSTimedMetadata: + // TODO: Handle this case. + break; } notifyListeners(); } @@ -1938,7 +1849,6 @@ class MeetingStore extends ChangeNotifier return; } if (state == AppLifecycleState.resumed) { - isHLSPlayerRequired = true; if (Platform.isAndroid) { isPipActive = await HMSAndroidPIPController.isActive(); } else if (Platform.isIOS) { @@ -1974,7 +1884,6 @@ class MeetingStore extends ChangeNotifier if (Platform.isAndroid) { isPipActive = await HMSAndroidPIPController.isActive(); - isHLSPlayerRequired = false; notifyListeners(); } @@ -2004,13 +1913,11 @@ class MeetingStore extends ChangeNotifier } else if (state == AppLifecycleState.inactive) { if (Platform.isAndroid && !isPipActive) { isPipActive = await HMSAndroidPIPController.isActive(); - isHLSPlayerRequired = false; } notifyListeners(); } else if (state == AppLifecycleState.detached) { if (Platform.isAndroid && !isPipActive) { isPipActive = await HMSAndroidPIPController.isActive(); - isHLSPlayerRequired = false; } notifyListeners(); } @@ -2023,4 +1930,71 @@ class MeetingStore extends ChangeNotifier applicationLogs.hmsLog.addAll(hmsLogList.hmsLog); notifyListeners(); } + + @override + void onCue({required HMSHLSCue hlsCue}) { + /** + * Here we use a list of alignments and select an alignment at random and use it + * to position the toast for timed metadata + */ + + if (hlsCue.payload != null) { + /** + * Generally we are assuming that the timed metadata payload will be a JSON String + * but if it's a normal string then this throws the format exception + * Hence we catch it and display the payload as string on toast. + * The toast is displayed for the time duration hlsCue.endDate - hlsCue.startDate + * If endDate is null then toast is displayed for 2 seconds by default + */ + try { + final Map data = jsonDecode(hlsCue.payload!); + Utilities.showTimedMetadata( + Utilities.getTimedMetadataEmojiFromId(data["emojiId"]), + time: hlsCue.endDate == null + ? 2 + : (hlsCue.endDate!.difference(hlsCue.startDate)).inSeconds, + align: Utilities.timedMetadataAlignment[Math.Random() + .nextInt(Utilities.timedMetadataAlignment.length)]); + } catch (e) { + Utilities.showTimedMetadata(hlsCue.payload!, + time: hlsCue.endDate == null + ? 2 + : (hlsCue.endDate!.difference(hlsCue.startDate)).inSeconds, + align: Utilities.timedMetadataAlignment[Math.Random() + .nextInt(Utilities.timedMetadataAlignment.length)]); + } + } + } + + @override + void onPlaybackFailure({required String? error}) { + Utilities.showToast("Playback failure $error"); + } + + @override + void onPlaybackStateChanged({required HMSHLSPlaybackState playbackState}) { + Utilities.showToast("Playback state changed to ${playbackState.name}"); + } + + @override + void onHLSError({required HMSException hlsException}) { + // TODO: implement onHLSError + } + + @override + void onHLSEventUpdate({required HMSHLSPlayerStats playerStats}) { + log("onHLSEventUpdate-> bitrate:${playerStats.averageBitrate} buffered duration: ${playerStats.bufferedDuration}"); + hlsPlayerStats = playerStats; + notifyListeners(); + } + + void setHLSPlayerStats(bool value) { + isHLSStatsEnabled = value; + if (!value) { + HMSHLSPlayerController.removeHLSStatsListener(); + } else { + HMSHLSPlayerController.addHLSStatsListener(); + } + notifyListeners(); + } } diff --git a/example/lib/meeting_modes/audio_mode.dart b/example/lib/meeting_modes/audio_mode.dart index 680dd83da..8be759264 100644 --- a/example/lib/meeting_modes/audio_mode.dart +++ b/example/lib/meeting_modes/audio_mode.dart @@ -43,7 +43,7 @@ List portraitPattern( List tiles = []; int pinTileCount = 0; - while (peerTrack[pinTileCount].pinTile) { + while ((pinTileCount < peerTrack.length) && peerTrack[pinTileCount].pinTile) { tiles.add(StairedGridTile(1, ratio)); pinTileCount++; } diff --git a/example/lib/meeting_modes/basic_grid_view.dart b/example/lib/meeting_modes/basic_grid_view.dart index dbc738d3b..ec947add0 100644 --- a/example/lib/meeting_modes/basic_grid_view.dart +++ b/example/lib/meeting_modes/basic_grid_view.dart @@ -118,7 +118,8 @@ List portraitPattern(List peerTrack, tiles.add(StairedGridTile(1, ratio)); } int pinTileCount = 0; - while (peerTrack[pinTileCount + screenShareCount].pinTile) { + while ((pinTileCount + screenShareCount < peerTrack.length) && + peerTrack[pinTileCount + screenShareCount].pinTile) { tiles.add(StairedGridTile(1, ratio)); pinTileCount++; } diff --git a/example/lib/meeting_modes/hero_mode.dart b/example/lib/meeting_modes/hero_mode.dart index 0f9fed4fa..8bbe73233 100644 --- a/example/lib/meeting_modes/hero_mode.dart +++ b/example/lib/meeting_modes/hero_mode.dart @@ -126,7 +126,6 @@ List portraitPattern( tiles.add(StairedGridTile(0.33, ratio / 0.6)); tiles.add(StairedGridTile(0.33, ratio / 0.6)); } - int gridView = normalTile ~/ 4; int tileLeft = normalTile % 4; for (int i = 0; i < (normalTile - tileLeft - 4); i++) { tiles.add(StairedGridTile(0.5, ratio)); diff --git a/example/lib/preview/preview_page.dart b/example/lib/preview/preview_page.dart index e4114e754..c8eb3e25e 100644 --- a/example/lib/preview/preview_page.dart +++ b/example/lib/preview/preview_page.dart @@ -140,7 +140,6 @@ class _PreviewPageState extends State { ScaleType.SCALE_ASPECT_FILL, track: _previewStore.localTracks[0], setMirror: true, - matchParent: false, ) : Container( child: Center( @@ -432,14 +431,6 @@ class _PreviewPageState extends State { ListenableProvider.value( value: _meetingStore, child: ScreenController( - streamUrl: _previewStore - .isHLSStreamingStarted - ? _previewStore - .room - ?.hmshlsStreamingState - ?.variants[0] - ?.hlsStreamUrl - : null, isRoomMute: _previewStore .isRoomMute, diff --git a/example/lib/preview/preview_store.dart b/example/lib/preview/preview_store.dart index 65c359e06..2c5cecb73 100644 --- a/example/lib/preview/preview_store.dart +++ b/example/lib/preview/preview_store.dart @@ -230,6 +230,7 @@ class PreviewStore extends ChangeNotifier @override void onLogMessage({required HMSLogList hmsLogList}) { + log("preview_store: onLogMessage"); FirebaseCrashlytics.instance.log(hmsLogList.toString()); FirebaseAnalytics.instance.logEvent( name: "SDK_Logs", parameters: {"data": hmsLogList.toString()}); diff --git a/example/pubspec.lock b/example/pubspec.lock index e727d3665..fcd03c6a5 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "6a0ad72b2bcdb461749e40c01c478212a78db848dfcb2f10f2a461988bc5fb29" + sha256: "9ebe81588e666f7e2b21309f2b5653bd9642d7f27fd0a6894278d2ff40cb9481" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.3.2" archive: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: args - sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.2" async: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: "direct main" description: name: bot_toast - sha256: "19306147033316a7873c5d261b874fca3f341c05e4e1c12be56153ad11187edd" + sha256: db6950851aab00ef04b386eb3c76c83739eaffcb6b80d0dc42a675ef7584623b url: "https://pub.dev" source: hosted - version: "4.0.3" + version: "4.0.4" characters: dependency: transitive description: @@ -77,18 +77,18 @@ packages: dependency: transitive description: name: checked_yaml - sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" cli_util: dependency: transitive description: name: cli_util - sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c" + sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 url: "https://pub.dev" source: hosted - version: "0.3.5" + version: "0.4.0" clock: dependency: transitive description: @@ -125,18 +125,10 @@ packages: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.2" - csslib: - dependency: transitive - description: - name: csslib - sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 - url: "https://pub.dev" - source: hosted - version: "0.17.2" + version: "3.0.3" cupertino_icons: dependency: "direct main" description: @@ -157,10 +149,10 @@ packages: dependency: "direct main" description: name: dropdown_button2 - sha256: "193e97bfe9fd3d89317bddb6129653781fa9b62d99811d49f633e67ea449a62c" + sha256: "107444581478342c6511040c2eb52850748400df4f3295df3da8be75693180a5" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.4" fake_async: dependency: transitive description: @@ -173,10 +165,10 @@ packages: dependency: transitive description: name: ffi - sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" file: dependency: transitive description: @@ -197,106 +189,106 @@ packages: dependency: "direct main" description: name: firebase_analytics - sha256: "2d8f475f79658a8e1865ee8a9e2e8eea70de5e487e3a646d9073dc3d1520d3e8" + sha256: f9d130eb6cf04b58e94358053aba3aebe546dca541e9ac5f076f8a7bc1e5c21e url: "https://pub.dev" source: hosted - version: "10.2.1" + version: "10.4.2" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface - sha256: a667807edbcd3f5a6336275a21c8802de8f98f0a91b8c97abb76119b142aa91d + sha256: "16b3c49dbf26b25cd69b9dc52e8a2e21e48ee13d5b4bb563db55a82300213d1e" url: "https://pub.dev" source: hosted - version: "3.4.1" + version: "3.6.2" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web - sha256: "907285030d59570d7c1a8d721912db990957ab9b87736e50a9457491c16bcffe" + sha256: "8163a020ee358c64b6c4f686102d09e21f5abf361352addc68aa419fc4bc029c" url: "https://pub.dev" source: hosted - version: "0.5.2+1" + version: "0.5.4+2" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "239e4ac688674a7e7b5476fd16b0d8e2b5a453d464f32091af3ce1df4ebb7316" + sha256: e9b36b391690cf329c6fb1de220045e97c13784c303820cd33962319580a56c6 url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.13.1" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: "0df0a064ab0cad7f8836291ca6f3272edd7b83ad5b3540478ee46a0849d8022b" + sha256: b63e3be6c96ef5c33bdec1aab23c91eb00696f6452f0519401d640938c94cba2 url: "https://pub.dev" source: hosted - version: "4.6.0" + version: "4.8.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: "347351a8f0518f3343d79a9a0690fa67ad232fc32e2ea270677791949eac792b" + sha256: "0fd5c4b228de29b55fac38aed0d9e42514b3d3bd47675de52bf7f8fccaf922fa" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.6.0" firebase_crashlytics: dependency: "direct main" description: name: firebase_crashlytics - sha256: "02ce958507138d938b4611e3a4910667a44b969b0c20a810bbc97bbf967ba0d7" + sha256: "603f23a74995c193cae89a784b8da529b1e6a91c03bc63f885f36456e9e867a0" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.3.2" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface - sha256: b9c7b8498c877a2901ad323fc92c10f672be1597bc82d08f121f6228f321a7e0 + sha256: cefeeeb98abdb9d848581603bd1e33a2a8e6d3ed937586cb84437e606049071b url: "https://pub.dev" source: hosted - version: "3.4.1" + version: "3.6.2" firebase_dynamic_links: dependency: "direct main" description: name: firebase_dynamic_links - sha256: e23f9d7323efc4532226d18cf1b7c05d1e348267a1a52bc1a18576a4d5f76849 + sha256: "830b779cb77badc9d8b438c43b857dc1cd531691473e4592c2cc1c5bf9726d2b" url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "5.3.2" firebase_dynamic_links_platform_interface: dependency: transitive description: name: firebase_dynamic_links_platform_interface - sha256: afcefb6b49416f4c0631fb5a80f516729fc39cd8163de27e287f2d72ac017fbc + sha256: "8af7e6b9a95c653d84e2a395dc846ef5232531f386ea80b7a8edfa0735f78525" url: "https://pub.dev" source: hosted - version: "0.2.4+1" + version: "0.2.6+2" firebase_performance: dependency: "direct main" description: name: firebase_performance - sha256: "14f4d1e89e757f0aa1dec83c13ca390bbd728aca1ccc3ad19e6a23e2710f45b7" + sha256: "2fcada27e0150ec06fae6cb6eb89727525032d0af906b5cf49893ea352cc5312" url: "https://pub.dev" source: hosted - version: "0.9.1+1" + version: "0.9.2+2" firebase_performance_platform_interface: dependency: transitive description: name: firebase_performance_platform_interface - sha256: "0914a49c4ca2cc74ac4c7fde719f280896b4c1d1bbe5fa72e059da0803cbc493" + sha256: "0c020ecb79048a8647e8231837a5a9077b2f64b1e5dda069f7da504d6514553f" url: "https://pub.dev" source: hosted - version: "0.1.2+1" + version: "0.1.4+2" firebase_performance_web: dependency: transitive description: name: firebase_performance_web - sha256: "8192e4e112e9e09d2c9bbc8ab51d7c4e34ea618d15452d0ecfa6141e8babbd40" + sha256: "74072ca565adffaf332ce47c9faf2dc6d8060663046d4ac56c7f8f5ba4be0a03" url: "https://pub.dev" source: hosted - version: "0.1.2+1" + version: "0.1.4+2" flutter: dependency: "direct main" description: flutter @@ -314,26 +306,26 @@ packages: dependency: "direct main" description: name: flutter_launcher_icons - sha256: ce0e501cfc258907842238e4ca605e74b7fd1cdf04b3b43e86c43f3e40a1592c + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" url: "https://pub.dev" source: hosted - version: "0.11.0" + version: "0.13.1" flutter_linkify: dependency: "direct main" description: name: flutter_linkify - sha256: c89fe74de985ec22f23d3538d2249add085a4f37ac1c29fd79e1a207efb81d63 + sha256: "74669e06a8f358fee4512b4320c0b80e51cffc496607931de68d28f099254073" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "6.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "8ffe990dac54a4a5492747added38571a5ab474c8e5d196809ea08849c69b1bb" + sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" url: "https://pub.dev" source: hosted - version: "2.0.13" + version: "2.0.15" flutter_staggered_grid_view: dependency: "direct main" description: @@ -346,10 +338,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: c9bb2757b8a0bbf8e45f4069a90d2b9dbafc80b1a5e28d43e29088be533e6df4 + sha256: f991fdb1533c3caeee0cdc14b04f50f0c3916f0dbcbc05237ccbe4e3c6b93f3f url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "2.0.5" flutter_test: dependency: "direct dev" description: flutter @@ -360,14 +352,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_widget_from_html_core: - dependency: transitive - description: - name: flutter_widget_from_html_core - sha256: e8f4f8b461a140ffb7c71f938bc76efc758893e7468843d9dbf70cb0b9e900cb - url: "https://pub.dev" - source: hosted - version: "0.8.5+3" focus_detector: dependency: "direct main" description: @@ -376,14 +360,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" - fwfh_text_style: - dependency: transitive - description: - name: fwfh_text_style - sha256: "37806ee0222f79b6e8d4c698c322c897eae6a817258156f40aeece4e588fac60" - url: "https://pub.dev" - source: hosted - version: "2.22.08+1" google_fonts: dependency: "direct main" description: @@ -398,23 +374,15 @@ packages: path: ".." relative: true source: path - version: "1.6.0" - html: - dependency: transitive - description: - name: html - sha256: "79d498e6d6761925a34ee5ea8fa6dfef38607781d2fa91e37523474282af55cb" - url: "https://pub.dev" - source: hosted - version: "0.15.2" + version: "1.7.0" http: dependency: "direct main" description: name: http - sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" url: "https://pub.dev" source: hosted - version: "0.13.5" + version: "0.13.6" http_parser: dependency: transitive description: @@ -427,18 +395,18 @@ packages: dependency: transitive description: name: image - sha256: "02bafd3b4f399bfeb10034deba9753d93b55ce41cd0c4d3d8b355626f80e5b32" + sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "4.0.17" image_gallery_saver: dependency: "direct main" description: name: image_gallery_saver - sha256: be812580c7a320d3bf583af89cac6b376f170d48000aca75215a73285a3223a0 + sha256: "467de169167b5c4e1ddde65395e4653c336a82f760b6700ea295085b7f2dd248" url: "https://pub.dev" source: hosted - version: "1.7.1" + version: "2.0.2" intl: dependency: "direct main" description: @@ -459,18 +427,18 @@ packages: dependency: transitive description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" linkify: dependency: transitive description: name: linkify - sha256: bdfbdafec6cdc9cd0ebb333a868cafc046714ad508e48be8095208c54691d959 + sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "5.0.0" matcher: dependency: transitive description: @@ -535,14 +503,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.2" - path_drawing: - dependency: transitive - description: - name: path_drawing - sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 - url: "https://pub.dev" - source: hosted - version: "1.0.1" path_parsing: dependency: transitive description: @@ -555,10 +515,10 @@ packages: dependency: "direct main" description: name: path_provider - sha256: c7edf82217d4b2952b2129a61d3ad60f1075b9299e629e149a8d2e39c2e6aad4 + sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" url: "https://pub.dev" source: hosted - version: "2.0.14" + version: "2.0.15" path_provider_android: dependency: transitive description: @@ -571,18 +531,18 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: ad4c4d011830462633f03eb34445a45345673dfd4faf1ab0b4735fbd93b19183 + sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" + sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 url: "https://pub.dev" source: hosted - version: "2.1.10" + version: "2.1.11" path_provider_platform_interface: dependency: transitive description: @@ -595,50 +555,42 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6 - url: "https://pub.dev" - source: hosted - version: "2.1.6" - pedantic: - dependency: transitive - description: - name: pedantic - sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" + sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "2.1.7" permission_handler: dependency: "direct main" description: name: permission_handler - sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8" + sha256: "1b6b3e73f0bcbc856548bbdfb1c33084a401c4f143e220629a9055233d76c331" url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.3.0" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "8028362b40c4a45298f1cbfccd227c8dd6caf0e27088a69f2ba2ab15464159e2" + sha256: "8f6a95ccbca13766882f95d32684d7c9bfe6c45650c32bedba948ef1c6a4ddf7" url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.2.3" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: ee96ac32f5a8e6f80756e25b25b9f8e535816c8e6665a96b6d70681f8c4f7e85 + sha256: "08dcb6ce628ac0b257e429944b4c652c2a4e6af725bdf12b498daa2c6b2b1edb" url: "https://pub.dev" source: hosted - version: "9.0.8" + version: "9.1.0" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84" + sha256: de20a5c3269229c1ae2e5a6b822f6cb59578b23e8255c93fbeebfc82116e6b11 url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "3.10.0" permission_handler_windows: dependency: transitive description: @@ -655,14 +607,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" - pip_flutter: - dependency: "direct main" - description: - name: pip_flutter - sha256: "1e5ac5c8c33199039399fb630586a75ea5f1612f2c1818f337c54d4e92033fa0" - url: "https://pub.dev" - source: hosted - version: "0.0.3" platform: dependency: transitive description: @@ -731,10 +675,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "858aaa72d8f61637d64e776aca82e1c67e6d9ee07979123c5d17115031c1b13b" + sha256: "396f85b8afc6865182610c0a2fc470853d56499f75f7499e2a73a9f0539d23d0" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" shared_preferences_android: dependency: transitive description: @@ -747,10 +691,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "0c1c16c56c9708aa9c361541a6f0e5cc6fc12a3232d866a687a7b7db30032b07" + sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_linux: dependency: transitive description: @@ -840,18 +784,18 @@ packages: dependency: "direct main" description: name: tuple - sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" typed_data: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" uni_links: dependency: "direct main" description: @@ -880,18 +824,18 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e" + sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 url: "https://pub.dev" source: hosted - version: "6.1.10" + version: "6.1.11" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "22f8db4a72be26e9e3a4aa3f194b1f7afbc76d20ec141f84be1d787db2155cbd" + sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51 url: "https://pub.dev" source: hosted - version: "6.0.31" + version: "6.0.35" url_launcher_ios: dependency: transitive description: @@ -920,18 +864,18 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" + sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" + sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab" url: "https://pub.dev" source: hosted - version: "2.0.16" + version: "2.0.17" url_launcher_windows: dependency: transitive description: @@ -948,6 +892,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.7" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: ea8d3fc7b2e0f35de38a7465063ecfcf03d8217f7962aa2a6717132cb5d43a79 + url: "https://pub.dev" + source: hosted + version: "1.1.5" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: a5eaa5d19e123ad4f61c3718ca1ed921c4e6254238d9145f82aa214955d9aced + url: "https://pub.dev" + source: hosted + version: "1.1.5" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "15edc42f7eaa478ce854eaf1fbb9062a899c0e4e56e775dd73b7f4709c97c4ca" + url: "https://pub.dev" + source: hosted + version: "1.1.5" vector_math: dependency: transitive description: @@ -968,10 +936,10 @@ packages: dependency: "direct main" description: name: wakelock - sha256: da22c0789e1f849bec43688a52f2290f4e66268056f7cd77cb71245aef4917a0 + sha256: b18a1a7b1ec71b1a9105766e06a73a2411e3eb5564bc569d99df92b57530bb4b url: "https://pub.dev" source: hosted - version: "0.5.6" + version: "0.6.1+2" wakelock_macos: dependency: transitive description: @@ -1024,18 +992,18 @@ packages: dependency: transitive description: name: xml - sha256: "80d494c09849dc3f899d227a78c30c5b949b985ededf884cb3f3bcd39f4b447a" + sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" url: "https://pub.dev" source: hosted - version: "5.4.1" + version: "6.2.2" yaml: dependency: transitive description: name: yaml - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" sdks: - dart: ">=2.18.0 <3.0.0" - flutter: ">=3.3.0" + dart: ">=2.19.0 <3.0.0" + flutter: ">=3.7.0-0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index ba4806bec..6e2b3b513 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,9 +4,9 @@ description: Demonstrates how to use the hmssdk_flutter plugin. # The following line prevents the package from being accidentally published to # pub.dev using `pub publish`. This is preferred for private packages. publish_to: "none" # Remove this line if you wish to publish to pub.dev -version: 1.6.0 +version: 1.6.1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.16.0 <4.0.0" dependencies: flutter: @@ -15,16 +15,15 @@ dependencies: hmssdk_flutter: path: ../ - wakelock: cupertino_icons: provider: http: permission_handler: intl: - firebase_crashlytics: - firebase_core: - firebase_analytics: - firebase_performance: + firebase_crashlytics: 3.3.2 + firebase_core: 2.13.1 + firebase_analytics: 10.4.2 + firebase_performance: 0.9.2+2 firebase_dynamic_links: flutter_launcher_icons: package_info_plus: @@ -44,11 +43,11 @@ dependencies: draggable_widget: badges: url_launcher: - pip_flutter: share_plus: flutter_linkify: - flutter_foreground_task: + flutter_foreground_task: 4.1.0 image_gallery_saver: + wakelock: 0.6.1+2 dev_dependencies: flutter_test: diff --git a/ios/Classes/Actions/HMSAudioDeviceAction.swift b/ios/Classes/Actions/HMSAudioDeviceAction.swift index 357012c98..2e5e3de5d 100644 --- a/ios/Classes/Actions/HMSAudioDeviceAction.swift +++ b/ios/Classes/Actions/HMSAudioDeviceAction.swift @@ -7,6 +7,7 @@ import Foundation import HMSSDK +import AVKit class HMSAudioDeviceAction { static func audioActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { @@ -17,6 +18,9 @@ class HMSAudioDeviceAction { case "switch_audio_output": switchAudioOutput(call, result, hmsSDK) + case "switch_audio_output_using_ios_ui": + switchAudioOutputUsingiOSUI(result) + default: result(FlutterMethodNotImplemented) } @@ -74,4 +78,18 @@ class HMSAudioDeviceAction { return "SPEAKER_PHONE" } } + + /** + This method is used to show native iOS UI to change + audio output device + */ + static private func switchAudioOutputUsingiOSUI(_ result: @escaping FlutterResult) { + let routerPicker = AVRoutePickerView() + for view in routerPicker.subviews { + if let button = view as? UIButton { + button.sendActions(for: .allEvents) + } + } + result(nil) + } } diff --git a/ios/Classes/Actions/HMSHLSAction.swift b/ios/Classes/Actions/HMSHLSAction.swift index 048b9f8b2..ea768f93e 100644 --- a/ios/Classes/Actions/HMSHLSAction.swift +++ b/ios/Classes/Actions/HMSHLSAction.swift @@ -16,6 +16,9 @@ class HMSHLSAction { case "hls_stop_streaming": stopHlsStreaming(call, result, hmsSDK) + + case "send_hls_timed_metadata": + sendHLSTimedMetadata(call, result, hmsSDK) default: result(FlutterMethodNotImplemented) } @@ -72,4 +75,40 @@ class HMSHLSAction { return HMSHLSConfig(variants: meetingUrlVariant) } + + /** + * This method is used to send timed metadata to the HLS Player + * + * This takes a list of [HMSHLSTimedMetadata] objects + * From flutter we receive a list of Map and then we + * convert it to list of HMSHLSTimedMetadata objects + */ + static private func sendHLSTimedMetadata(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { + + let arguments = call.arguments as? [AnyHashable: Any] + + guard let metadata = arguments?["metadata"] as? [[String: Any]] else { + HMSErrorLogger.returnArgumentsError("metadata parameter is null") + return + } + + var hlsMetadata: [HMSHLSTimedMetadata] = [] + + /*** + * Here we parse the Map from flutter to + * [HMSHLSTimedMetadata] objects + */ + metadata.forEach { timedMetadata in + hlsMetadata.append(HMSHLSTimedMetadata(payload: timedMetadata["metadata"] as! String, duration: timedMetadata["duration"] as! Int)) + } + + hmsSDK?.sendHLSTimedMetadata(hlsMetadata) { _, error in + if let error = error { + result(HMSErrorExtension.toDictionary(error)) + } else { + result(nil) + } + } + + } } diff --git a/ios/Classes/Actions/HMSMessageAction.swift b/ios/Classes/Actions/HMSMessageAction.swift index d4b85ab22..4ba40bc0d 100644 --- a/ios/Classes/Actions/HMSMessageAction.swift +++ b/ios/Classes/Actions/HMSMessageAction.swift @@ -35,11 +35,18 @@ class HMSMessageAction { let type = arguments["type"] as? String ?? "chat" - hmsSDK?.sendBroadcastMessage(type: type, message: message) { _, error in + hmsSDK?.sendBroadcastMessage(type: type, message: message) {data, error in if let error = error { result(HMSErrorExtension.toDictionary(error)) } else { - result(nil) + guard let data + else { + result(nil) + return + } + var dict = [String: Any]() + dict["message"] = HMSMessageExtension.toDictionary(data) + result(dict) } } } @@ -58,11 +65,18 @@ class HMSMessageAction { let type = arguments["type"] as? String ?? "chat" - hmsSDK?.sendDirectMessage(type: type, message: message, peer: peer) { _, error in + hmsSDK?.sendDirectMessage(type: type, message: message, peer: peer) { data, error in if let error = error { result(HMSErrorExtension.toDictionary(error)) } else { - result(nil) + guard let data + else { + result(nil) + return + } + var dict = [String: Any]() + dict["message"] = HMSMessageExtension.toDictionary(data) + result(dict) } } } @@ -81,11 +95,18 @@ class HMSMessageAction { let type = arguments["type"] as? String ?? "chat" - hmsSDK?.sendGroupMessage(type: type, message: message, roles: roles) { _, error in + hmsSDK?.sendGroupMessage(type: type, message: message, roles: roles) { data, error in if let error = error { result(HMSErrorExtension.toDictionary(error)) } else { - result(nil) + guard let data + else { + result(nil) + return + } + var dict = [String: Any]() + dict["message"] = HMSMessageExtension.toDictionary(data) + result(dict) } } } diff --git a/ios/Classes/Actions/HMSRecordingAction.swift b/ios/Classes/Actions/HMSRecordingAction.swift index 26e078056..3ab9b389c 100644 --- a/ios/Classes/Actions/HMSRecordingAction.swift +++ b/ios/Classes/Actions/HMSRecordingAction.swift @@ -32,8 +32,10 @@ class HMSRecordingAction { } var meetingURL: URL? - if let meetingURLStr = arguments["meeting_url"] as? String { - meetingURL = URL(string: meetingURLStr) + let meetingURLStr = arguments["meeting_url"] as? String + + if meetingURLStr != nil { + meetingURL = URL(string: meetingURLStr!) } var rtmpURLs: [URL]? diff --git a/ios/Classes/Actions/HMSSessionStoreAction.swift b/ios/Classes/Actions/HMSSessionStoreAction.swift index 2381c5998..931d26c38 100644 --- a/ios/Classes/Actions/HMSSessionStoreAction.swift +++ b/ios/Classes/Actions/HMSSessionStoreAction.swift @@ -61,7 +61,7 @@ class HMSSessionStoreAction { guard let store = plugin.sessionStore else { - HMSErrorLogger.logError(#function,"Session Store is null.","NULL Error") + HMSErrorLogger.logError(#function, "Session Store is null.", "NULL Error") result(HMSErrorExtension.getError("Session Store is null.")) return } @@ -69,7 +69,7 @@ class HMSSessionStoreAction { guard let arguments = call.arguments as? [AnyHashable: Any], let key = arguments["key"] as? String else { - HMSErrorLogger.logError(#function,"Key for the object to be set in Session Store is null.","NULL Error") + HMSErrorLogger.logError(#function, "Key for the object to be set in Session Store is null.", "NULL Error") result(HMSErrorExtension.getError("Key for the object to be set in Session Store is null.")) return } @@ -79,7 +79,7 @@ class HMSSessionStoreAction { store.set(data as Any, forKey: key) { _, error in if let error = error { - HMSErrorLogger.logError(#function,"Error in setting data: \(data ?? "null") for key: \(key) to the Session Store. Error: \(error.localizedDescription)","Key Error") + HMSErrorLogger.logError(#function, "Error in setting data: \(data ?? "null") for key: \(key) to the Session Store. Error: \(error.localizedDescription)", "Key Error") result(HMSErrorExtension.getError("Error in setting data: \(data ?? "null") for key: \(key) to the Session Store. Error: \(error.localizedDescription)")) return } diff --git a/ios/Classes/HLSPlayer/HMSHLSCueExtension.swift b/ios/Classes/HLSPlayer/HMSHLSCueExtension.swift new file mode 100644 index 000000000..5267f5d19 --- /dev/null +++ b/ios/Classes/HLSPlayer/HMSHLSCueExtension.swift @@ -0,0 +1,26 @@ +// +// HMSHLSCueExtension.swift +// hmssdk_flutter +// +// Created by Pushpam on 24/05/23. +// + +import Foundation +import HMSHLSPlayerSDK + +class HMSHLSCueExtension { + + static func toDictionary(_ hmsHlsCue: HMSHLSCue) -> [String: Any?] { + + var dict = [String: Any?]() + + dict["id"] = hmsHlsCue.id + dict["start_date"] = "\(hmsHlsCue.startDate)" + if let endDate = hmsHlsCue.endDate { + dict["end_date"] = "\(endDate)" + } + dict["payload"] = hmsHlsCue.payload + + return dict + } +} diff --git a/ios/Classes/HLSPlayer/HMSHLSPlaybackStateExtension.swift b/ios/Classes/HLSPlayer/HMSHLSPlaybackStateExtension.swift new file mode 100644 index 000000000..69ff98c16 --- /dev/null +++ b/ios/Classes/HLSPlayer/HMSHLSPlaybackStateExtension.swift @@ -0,0 +1,30 @@ +// +// HMSHLSPlaybackStateExtension.swift +// hmssdk_flutter +// +// Created by Pushpam on 24/05/23. +// + +import Foundation +import HMSHLSPlayerSDK + +class HMSHLSPlaybackStateExtension { + static func toDictionary(_ hmsHlsPlaybackState: HMSHLSPlaybackState) -> [String: String] { + var args = [String: String]() + switch hmsHlsPlaybackState { + case .playing: + args["playback_state"] = "playing" + case .stopped: + args["playback_state"] = "stopped" + case .paused: + args["playback_state"] = "paused" + case .buffering: + args["playback_state"] = "buffering" + case .failed: + args["playback_state"] = "failed" + default: + args["playback_state"] = "unknown" + } + return args + } +} diff --git a/ios/Classes/HLSPlayer/HMSHLSPlayerAction.swift b/ios/Classes/HLSPlayer/HMSHLSPlayerAction.swift new file mode 100644 index 000000000..9fe24fdca --- /dev/null +++ b/ios/Classes/HLSPlayer/HMSHLSPlayerAction.swift @@ -0,0 +1,198 @@ +// +// HMSHLSPlayerAction.swift +// DKImagePickerController +// +// Created by Pushpam on 24/05/23. +// + +import Foundation +import HMSSDK + +/** + * This class is used to send actions from flutter plugin to HLS Player + * We use notifications to forward the request to HMSHLSPlayer + */ +class HMSHLSPlayerAction { + + static let HLS_PLAYER_METHOD = "HLS_PLAYER" + static let METHOD_CALL = "method_name" + + static func hlsPlayerAction(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + switch call.method { + case "start_hls_player": + start(call, result) + + case "stop_hls_player": + stop(result) + + case "pause_hls_player": + pause(result) + + case "resume_hls_player": + resume(result) + + case "seek_to_live_position": + seekToLivePosition(result) + + case "seek_forward": + seekForward(call, result) + + case "seek_backward": + seekBackward(call, result) + + case "set_hls_player_volume": + setVolume(call, result) + + case "add_hls_stats_listener": + addHLSStatsListener(result) + + case "remove_hls_stats_listener": + removeHLSStatsListener(result) + + default: + result(FlutterMethodNotImplemented) + } + + } + + /** + * Starts the HLS player by posting a notification with the specified method call and HLS URL. + * + * - Parameters: + * - call: The method call object containing the HLS URL as an argument. + * - result: The result object to be returned after starting the player. + */ + static private func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + + guard let arguments = call.arguments as? [AnyHashable: Any] else { + HMSErrorLogger.logError(#function, "No arguments found", "NULL_ERROR") + result(nil) + return + } + + let hlsUrl = arguments["hls_url"] as? String + + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "start_hls_player", "hls_url": hlsUrl]) + result(nil) + } + + /** + * Stops the HLS player by posting a notification with the specified method call. + * + * - Parameter result: The result object to be returned after stopping the player. + */ + static private func stop(_ result: @escaping FlutterResult) { + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "stop_hls_player"]) + result(nil) + } + + /** + * Pauses the HLS player by posting a notification with the specified method call. + * + * - Parameter result: The result object to be returned after pausing the player. + */ + static private func pause(_ result: @escaping FlutterResult) { + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "pause_hls_player"]) + result(nil) + } + + /** + * Resumes the HLS player by posting a notification with the specified method call. + * + * - Parameter result: The result object to be returned after resuming the player. + */ + static private func resume(_ result: @escaping FlutterResult) { + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "resume_hls_player"]) + result(nil) + } + + /** + * Seeks to the live position in the HLS player by posting a notification with the specified method call. + * + * - Parameter result: The result object to be returned after seeking to the live position. + */ + static private func seekToLivePosition(_ result: @escaping FlutterResult) { + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "seek_to_live_position"]) + result(nil) + } + + /** + * Seeks forward in the HLS player by the specified number of seconds, posting a notification with the seek duration. + * + * - Parameters: + * - call: The method call object containing the number of seconds to seek forward as an argument. + * - result: The result object to be returned after seeking forward. + */ + static private func seekForward(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + + guard let arguments = call.arguments as? [AnyHashable: Any], + let seconds = arguments["seconds"] as? Int else { + HMSErrorLogger.logError(#function, "seconds parameter is null", "NULL_ERROR") + result(nil) + return + } + + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "seek_forward", "seconds": seconds]) + result(nil) + } + + /** + * Seeks backward in the HLS player by the specified number of seconds, posting a notification with the seek duration. + * + * - Parameters: + * - call: The method call object containing the number of seconds to seek backward as an argument. + * - result: The result object to be returned after seeking backward. + */ + static private func seekBackward(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + + guard let arguments = call.arguments as? [AnyHashable: Any], + let seconds = arguments["seconds"] as? Int else { + HMSErrorLogger.logError(#function, "seconds parameter is null", "NULL_ERROR") + result(nil) + return + } + + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "seek_backward", "seconds": seconds]) + result(nil) + } + + /** + * Sets the volume level of the HLS player by posting a notification with the specified volume value. + * + * - Parameters: + * - call: The method call object containing the volume level as an argument. + * - result: The result object to be returned after setting the volume. + */ + static private func setVolume(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + + guard let arguments = call.arguments as? [AnyHashable: Any], + let volume = arguments["volume"] as? Int else { + HMSErrorLogger.logError(#function, "volume parameter is null", "NULL_ERROR") + result(nil) + return + } + + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "set_hls_player_volume", "volume": volume]) + result(nil) + } + + /** + * Adds a listener to receive HLS player statistics by posting a notification with the corresponding method call. + * + * - Parameter result: The result object to be returned after adding the HLS stats listener. + */ + static private func addHLSStatsListener(_ result: @escaping FlutterResult) { + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "add_hls_stats_listener"]) + result(nil) + } + + /** + * Removes the listener for HLS player statistics by posting a notification with the corresponding method call. + * + * - Parameter result: The result object to be returned after removing the HLS stats listener. + */ + static private func removeHLSStatsListener(_ result: @escaping FlutterResult) { + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "remove_hls_stats_listener"]) + result(nil) + } +} diff --git a/ios/Classes/HLSPlayer/HMSHLSPlayerStatsExtension.swift b/ios/Classes/HLSPlayer/HMSHLSPlayerStatsExtension.swift new file mode 100644 index 000000000..e569bb535 --- /dev/null +++ b/ios/Classes/HLSPlayer/HMSHLSPlayerStatsExtension.swift @@ -0,0 +1,26 @@ +// +// HLSPlayerStatsExtension.swift +// hmssdk_flutter +// +// Created by Pushpam on 24/05/23. +// + +import Foundation +import HMSHLSPlayerSDK + +class HMSHLSPlayerStatsExtension { + static func toDictionary(_ hlsStats: HMSHLSStatsMonitor) -> [String: Any?] { + var args = [String: Any?]() + + args["bandwidth_estimate"] = hlsStats.estimatedBandwidth + args["total_bytes_loaded"] = hlsStats.bytesDownloaded + args["buffered_duration"] = hlsStats.bufferedDuration + args["distance_from_live"] = hlsStats.distanceFromLiveEdge + args["dropped_frame_count"] = hlsStats.droppedFrames + args["video_height"] = hlsStats.videoSize.height + args["video_width"] = hlsStats.videoSize.width + args["average_bitrate"] = hlsStats.bitrate + + return args + } +} diff --git a/ios/Classes/HLSPlayer/HMSHLSStatsHandler.swift b/ios/Classes/HLSPlayer/HMSHLSStatsHandler.swift new file mode 100644 index 000000000..2e382c525 --- /dev/null +++ b/ios/Classes/HLSPlayer/HMSHLSStatsHandler.swift @@ -0,0 +1,66 @@ +// +// HLSStatsHandler.swift +// hmssdk_flutter +// +// Created by Pushpam on 24/05/23. +// + +import Foundation +import HMSHLSPlayerSDK + +/** + This class handles the HLS Player stats + */ +class HMSHLSStatsHandler { + + static var statsMonitor: HMSHLSStatsMonitor? + static private var statsTimer: Timer? + + /** + Adds an HLS stats listener to the specified HLS player. + We send the HLS Stats to flutter layer in every 2 seconds using the statsTimer we declared above + + @param hlsPlayer The HMSHLSPlayer instance to attach the stats listener to. + @param hmssdkFlutterPlugin The SwiftHmssdkFlutterPlugin instance for sending the stats data. + */ + static func addHLSStatsListener(_ hlsPlayer: HMSHLSPlayer?, _ hmssdkFlutterPlugin: SwiftHmssdkFlutterPlugin?) { + + guard let hlsPlayer else { + HMSErrorLogger.logError(#function, "hlsPlayer is null", "NULL_ERROR") + return + } + + statsMonitor = HMSHLSStatsMonitor(player: hlsPlayer._nativePlayer) + + /** + Here we set the time interval at which the stats needs to be sent to flutter layer + Currently we have set it to 2 seconds. + */ + statsTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { _ in + + guard let statsMonitor else { + return + } + + let data = [ + "event_name": "on_hls_event_update", + "data": HMSHLSPlayerStatsExtension.toDictionary(statsMonitor) + ] as [String: Any?] + + guard let hmssdkFlutterPlugin else { + HMSErrorLogger.logError(#function, "hmssdkFlutterPlugin is null", "NULL_ERROR") + return + } + hmssdkFlutterPlugin.hlsPlayerSink?(data) + + } + } + + /** + Removes the HLS stats listener and stops the stats timer. + Here we invalidate the statsTimer + */ + static func removeHLSStatsListener() { + statsTimer?.invalidate() + } +} diff --git a/ios/Classes/HLSPlayer/HMSHLSStreamViewController.swift b/ios/Classes/HLSPlayer/HMSHLSStreamViewController.swift new file mode 100644 index 000000000..dea2ba7cf --- /dev/null +++ b/ios/Classes/HLSPlayer/HMSHLSStreamViewController.swift @@ -0,0 +1,88 @@ +// +// HLSStreamViewController.swift +// hmssdk_flutter +// +// Created by Pushpam on 24/05/23. +// + +import Foundation +import HMSHLSPlayerSDK + +class HMSHLSStreamViewController: HMSHLSPlayerDelegate { + + private let hlsPlayer: HMSHLSPlayer? + private let hmssdkFlutterPlugin: SwiftHmssdkFlutterPlugin? + + init(hlsPlayer: HMSHLSPlayer?, hmssdkFlutterPlugin: SwiftHmssdkFlutterPlugin?) { + self.hlsPlayer = hlsPlayer + self.hmssdkFlutterPlugin = hmssdkFlutterPlugin + } + + /* + This method is called when the playback state of the HMSHLSPlayer changes + + Parameters: + - state: The HMSHLSPlaybackState representing the new playback state + */ + func onPlaybackStateChanged(state: HMSHLSPlaybackState) { + let data = [ + "event_name": "on_playback_state_changed", + "data": HMSHLSPlaybackStateExtension.toDictionary(state) + ] as [String: Any?] + + guard let hmssdkFlutterPlugin else { + HMSErrorLogger.logError(#function, "hmssdkFlutterPlugin is null", "NULL_ERROR") + return + } + hmssdkFlutterPlugin.hlsPlayerSink?(data) + } + + /* + This method is called when a cue is received from the HMSHLSPlayer + + Parameters: + - cue: The HMSHLSCue representing the received cue + */ + func onCue(cue: HMSHLSCue) { + let data = [ + "event_name": "on_cue", + "data": HMSHLSCueExtension.toDictionary(cue) + ] as [String: Any?] + + guard let hmssdkFlutterPlugin else { + HMSErrorLogger.logError(#function, "hmssdkFlutterPlugin is null", "NULL_ERROR") + return + } + hmssdkFlutterPlugin.hlsPlayerSink?(data) + + } + + /* + This method is called when a playback failure occurs in the HMSHLSPlayer + + Parameters: + - error: The Error representing the encountered playback failure + */ + func onPlaybackFailure(error: Error) { + guard let error = error as? HMSHLSError else { return } + + if error.isTerminal { + HMSErrorLogger.logError(#function, "Player has encountered a terminal error, we need to restart the player: \(error.localizedDescription)", "PLAYBACK_TERMINAL_ERROR") + } else { + HMSErrorLogger.logError(#function, "Player has encountered an error, but it's non-fatal and player might recover \(error.localizedDescription)", "PLAYBACK_ERROR") + } + + let data = [ + "event_name": "on_playback_failure", + "data": ["error": error.localizedDescription] as [String: String?] + ] as [String: Any?] + + guard let hmssdkFlutterPlugin else { + HMSErrorLogger.logError(#function, "hmssdkFlutterPlugin is null", "NULL_ERROR") + return + } + hmssdkFlutterPlugin.hlsPlayerSink?(data) + + } + +} diff --git a/ios/Classes/Models/HMSMessageExtension.swift b/ios/Classes/Models/HMSMessageExtension.swift index 23a5778fc..d384b3a28 100644 --- a/ios/Classes/Models/HMSMessageExtension.swift +++ b/ios/Classes/Models/HMSMessageExtension.swift @@ -14,6 +14,8 @@ class HMSMessageExtension { var dict = [String: Any]() + dict["message_id"] = message.messageID + dict["message"] = message.message dict["type"] = message.type diff --git a/ios/Classes/Models/HMSResultExtension.swift b/ios/Classes/Models/HMSResultExtension.swift index 0c6a2c92b..b01d4cd0d 100644 --- a/ios/Classes/Models/HMSResultExtension.swift +++ b/ios/Classes/Models/HMSResultExtension.swift @@ -10,11 +10,15 @@ import Foundation class HMSResultExtension { static func toDictionary(_ success: Bool, _ data: Any?) -> [String: Any?] { - let dict = [ - "success": success, - "data": data - ] + + /** + We add `data` field in the map only when it is non null + otherwise the application might crash. + */ + var dict: [String: Any] = ["success": success] + if let data = data { + dict["data"] = data + } return dict } - } diff --git a/ios/Classes/SwiftHmssdkFlutterPlugin.swift b/ios/Classes/SwiftHmssdkFlutterPlugin.swift index ae03fe79e..d7844342c 100644 --- a/ios/Classes/SwiftHmssdkFlutterPlugin.swift +++ b/ios/Classes/SwiftHmssdkFlutterPlugin.swift @@ -15,12 +15,14 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene var logsEventChannel: FlutterEventChannel? var rtcStatsEventChannel: FlutterEventChannel? var sessionEventChannel: FlutterEventChannel? + var hlsPlayerChannel: FlutterEventChannel? var eventSink: FlutterEventSink? var previewSink: FlutterEventSink? var logsSink: FlutterEventSink? var rtcSink: FlutterEventSink? var sessionSink: FlutterEventSink? + var hlsPlayerSink: FlutterEventSink? var roleChangeRequest: HMSRoleChangeRequest? @@ -32,6 +34,8 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene private var sessionStoreChangeObservers = [HMSSessionStoreKeyChangeListener]() + var hlsStreamUrl: String? + // MARK: - Flutter Setup public static func register(with registrar: FlutterPluginRegistrar) { @@ -43,22 +47,28 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene let logsChannel = FlutterEventChannel(name: "logs_event_channel", binaryMessenger: registrar.messenger()) let rtcChannel = FlutterEventChannel(name: "rtc_event_channel", binaryMessenger: registrar.messenger()) let sessionChannel = FlutterEventChannel(name: "session_event_channel", binaryMessenger: registrar.messenger()) + let hlsChannel = FlutterEventChannel(name: "hls_player_channel", binaryMessenger: registrar.messenger()) let instance = SwiftHmssdkFlutterPlugin(channel: channel, meetingEventChannel: eventChannel, previewEventChannel: previewChannel, logsEventChannel: logsChannel, rtcStatsEventChannel: rtcChannel, - sessionEventChannel: sessionChannel) + sessionEventChannel: sessionChannel, + hlsPlayerChannel: hlsChannel) let videoViewFactory = HMSFlutterPlatformViewFactory(plugin: instance) registrar.register(videoViewFactory, withId: "HMSFlutterPlatformView") + let hlsPlayerFactory = HMSHLSPlayerViewFactory(plugin: instance) + registrar.register(hlsPlayerFactory, withId: "HMSHLSPlayer") + eventChannel.setStreamHandler(instance) previewChannel.setStreamHandler(instance) logsChannel.setStreamHandler(instance) rtcChannel.setStreamHandler(instance) sessionChannel.setStreamHandler(instance) + hlsChannel.setStreamHandler(instance) registrar.addMethodCallDelegate(instance, channel: channel) } @@ -68,7 +78,8 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene previewEventChannel: FlutterEventChannel, logsEventChannel: FlutterEventChannel, rtcStatsEventChannel: FlutterEventChannel, - sessionEventChannel: FlutterEventChannel) { + sessionEventChannel: FlutterEventChannel, + hlsPlayerChannel: FlutterEventChannel) { self.channel = channel self.meetingEventChannel = meetingEventChannel @@ -76,6 +87,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene self.logsEventChannel = logsEventChannel self.rtcStatsEventChannel = rtcStatsEventChannel self.sessionEventChannel = sessionEventChannel + self.hlsPlayerChannel = hlsPlayerChannel } public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { @@ -96,6 +108,8 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene rtcSink = events case "session_store": sessionSink = events + case "hls_player": + hlsPlayerSink = events default: return FlutterError(code: #function, message: "invalid event sink name", details: arguments) } @@ -138,6 +152,12 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene } else { print(#function, "sessionEventChannel not found") } + if hlsPlayerChannel != nil { + hlsPlayerChannel!.setStreamHandler(nil) + hlsPlayerSink = nil + } else { + print(#function, "hlsPlayerChannel not found") + } } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { @@ -188,7 +208,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene // MARK: - HLS - case "hls_start_streaming", "hls_stop_streaming": + case "hls_start_streaming", "hls_stop_streaming", "send_hls_timed_metadata": HMSHLSAction.hlsActions(call, result, hmsSDK) // MARK: - Logging @@ -219,14 +239,9 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene // MARK: - Switch Audio Output - case "switch_audio_output", "get_audio_devices_list": + case "switch_audio_output", "get_audio_devices_list", "switch_audio_output_using_ios_ui": HMSAudioDeviceAction.audioActions(call, result, hmsSDK) - // MARK: - Session Metadata - - case "get_session_metadata", "set_session_metadata": - sessionMetadataAction(call, result) - // MARK: - Simulcast case "set_simulcast_layer", "get_layer", "get_layer_definition": @@ -262,6 +277,11 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene case "remove_key_change_listener": removeKeyChangeListener(call, result) + // MARK: - HLS Player + + case "start_hls_player", "stop_hls_player", "pause_hls_player", "resume_hls_player", "seek_to_live_position", "seek_forward", "seek_backward", "set_hls_player_volume", "add_hls_stats_listener", "remove_hls_stats_listener": + HMSHLSPlayerAction.hlsPlayerAction(call, result) + default: result(FlutterMethodNotImplemented) } @@ -469,21 +489,6 @@ 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: - Session Store /** @@ -498,7 +503,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene guard let store = sessionStore else { - HMSErrorLogger.logError(#function,"Session Store is null","NULL ERROR") + HMSErrorLogger.logError(#function, "Session Store is null", "NULL ERROR") result(HMSErrorExtension.getError("Session Store is null")) return } @@ -512,14 +517,14 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene guard let keys = arguments["keys"] as? [String] else { - HMSErrorLogger.logError(#function,"No keys passed which can be attached to Key Change Listener on the Session Store. Available arguments: \(arguments)","NULL ERROR") + HMSErrorLogger.logError(#function, "No keys passed which can be attached to Key Change Listener on the Session Store. Available arguments: \(arguments)", "NULL ERROR") result(HMSErrorExtension.getError("No keys passed which can be attached to Key Change Listener on the Session Store. Available arguments: \(arguments)")) return } guard let uid = arguments["uid"] as? String else { - HMSErrorLogger.logError(#function,"No uid passed for key change listener Available arguments: \(arguments)", "NULL ERROR") + HMSErrorLogger.logError(#function, "No uid passed for key change listener Available arguments: \(arguments)", "NULL ERROR") result(HMSErrorExtension.getError("No uid passed for key change listener Available arguments: \(arguments)")) return } @@ -547,21 +552,21 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene }) { [weak self] observer, error in if let error = error { - HMSErrorLogger.logError(#function,"Error in observing changes for key: \(keys) in the Session Store. Error: \(error.localizedDescription)","KEY CHANGE ERROR") + HMSErrorLogger.logError(#function, "Error in observing changes for key: \(keys) in the Session Store. Error: \(error.localizedDescription)", "KEY CHANGE ERROR") result(HMSErrorExtension.getError("Error in observing changes for key: \(keys) in the Session Store. Error: \(error.localizedDescription)")) return } guard let observer = observer else { - HMSErrorLogger.logError(#function,"Unknown Error in observing changes for key: \(keys) in the Session Store.","KEY CHANGE ERROR") + HMSErrorLogger.logError(#function, "Unknown Error in observing changes for key: \(keys) in the Session Store.", "KEY CHANGE ERROR") result(HMSErrorExtension.getError("Unknown Error in observing changes for key: \(keys) in the Session Store.")) return } guard let self = self else { - HMSErrorLogger.logError(#function,"Could not find self instance while observing changes for key: \(keys) in the Session Store.","KEY CHANGE ERROR") + HMSErrorLogger.logError(#function, "Could not find self instance while observing changes for key: \(keys) in the Session Store.", "KEY CHANGE ERROR") result(HMSErrorExtension.getError("Could not find self instance while observing changes for key: \(keys) in the Session Store.")) return } @@ -1041,7 +1046,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene * send the [logsDump] through the platform channel **/ var logsBuffer = [Any]() - var logsDump = [Any?]() + var logsDump = [Any]() public func log(_ message: String, _ level: HMSLogLevel) { /** * Here we filter the logs based on the level we have set @@ -1051,6 +1056,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene logsBuffer.append(message) logsDump.append(message) + if logsBuffer.count >= 512 { var args = [String: Any]() args["event_name"] = "on_logs_update" @@ -1058,7 +1064,6 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene logsSink?(args) logsBuffer = [] } - } private func getAllLogs(_ result: @escaping FlutterResult) { @@ -1073,42 +1078,6 @@ 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) - } - } - ) - } - private func setPlaybackAllowedForTrack(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { let arguments = call.arguments as! [AnyHashable: Any] @@ -1172,6 +1141,20 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene var previewEnded = false public func on(join room: HMSRoom) { previewEnded = true + + /** + * This sets the [hlsStreamUrl] variable to + * fetch the stream URL directly from onRoomUpdate + * This helps to play the HLS Stream even if user doesn't send + * the stream URL. + */ + + if room.hlsStreamingState.running { + if !room.hlsStreamingState.variants.isEmpty { + hlsStreamUrl = room.hlsStreamingState.variants[0].url.absoluteString + } + } + let data = [ "event_name": "on_join_room", "data": [ @@ -1183,6 +1166,20 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene } public func on(room: HMSRoom, update: HMSRoomUpdate) { + + /** + * This sets the [hlsStreamUrl] variable to + * fetch the stream URL directly from onRoomUpdate + * This helps to play the HLS Stream even if user doesn't send + * the stream URL. + */ + + if room.hlsStreamingState.running { + if !room.hlsStreamingState.variants.isEmpty { + hlsStreamUrl = room.hlsStreamingState.variants[0].url.absoluteString + } + } + let data = [ "event_name": "on_room_update", "data": [ @@ -1496,5 +1493,6 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene private func performCleanupOnLeavingRoom() { destroyPIPController() removeAllKeyChangeListener() + removeHMSLogger() } } diff --git a/ios/Classes/Views/HMSHLSPlayerView.swift b/ios/Classes/Views/HMSHLSPlayerView.swift new file mode 100644 index 000000000..36f8489bb --- /dev/null +++ b/ios/Classes/Views/HMSHLSPlayerView.swift @@ -0,0 +1,210 @@ +// +// HMSHLSPlayerView.swift +// hmssdk_flutter +// +// Created by Pushpam on 23/05/23. +// + +import Foundation +import HMSHLSPlayerSDK + +class HMSHLSPlayerView: NSObject, FlutterPlatformView { + + private let frame: CGRect + private let viewIdentifier: Int64 + private let hlsURL: String? + private let hmssdkFlutterPlugin: SwiftHmssdkFlutterPlugin? + private let isHLSStatsRequired: Bool + private let showPlayerControls: Bool + private var hlsPlayer: HMSHLSPlayer? + var statMonitor: HMSHLSStatsMonitor? + private var hmsHLSStreamViewController: HMSHLSStreamViewController? + + /** + * Initializes the HLS player view with the specified parameters. + * - Parameters: + * - frame: The CGRect defining the frame of the HLS player view. + * - viewIdentifier: The unique identifier for the HLS player view. + * - hlsURL: The HLS URL for the media content to be played. + * - hmssdkFlutterPlugin: The instance of the SwiftHmssdkFlutterPlugin. + * - isHLSStatsRequired: A boolean indicating whether HLS stats are required. + * - showPlayerControls: A boolean indicating whether to show player controls. + */ + init(frame: CGRect, viewIdentifier: Int64, hlsURL: String?, hmssdkFlutterPlugin: SwiftHmssdkFlutterPlugin?, isHLSStatsRequired: Bool, showPlayerControls: Bool) { + self.frame = frame + self.viewIdentifier = viewIdentifier + self.hlsURL = hlsURL + self.hmssdkFlutterPlugin = hmssdkFlutterPlugin + self.isHLSStatsRequired = isHLSStatsRequired + self.showPlayerControls = showPlayerControls + } + + func view() -> UIView { + initialisePlayer() + } + + /** + Initializes the HLS player and returns the player's view. + + - Returns: The UIView representing the HLS player. + */ + private func initialisePlayer() -> UIView { + + hlsPlayer = HMSHLSPlayer() + let playerViewController = hlsPlayer!.videoPlayerViewController(showsPlayerControls: showPlayerControls) + if #available(iOS 14.2, *) { + playerViewController.canStartPictureInPictureAutomaticallyFromInline = true + } + playerViewController.allowsPictureInPicturePlayback = true + playerViewController.view.frame = frame + guard let hmssdkFlutterPlugin else { + HMSErrorLogger.logError(#function, "hmssdkFlutterPlugin is null", "NULL_ERROR") + return playerViewController.view + } + + /** + * Here we start the player + * We first check whether the user has passed the streamUrl or not + * If not, we fetch the URL from SDK + * else we use the URL sent by the user + */ + if hlsURL != nil { + + /** + * Here we play the stream using streamUrl using the stream from + * url passed by the user + */ + guard let hlsStreamURL = URL(string: hlsURL!) else { + HMSErrorLogger.logError(#function, "Cannot convert hlsURL to URL", "PARSE_ERROR") + return playerViewController.view + } + + /** + * Here we play the stream using streamUrl using the stream from + * onRoomUpdate or onJoin + */ + hlsPlayer?.play(hlsStreamURL) + } else { + if hmssdkFlutterPlugin.hlsStreamUrl != nil { + guard let hlsStreamURL = URL(string: hmssdkFlutterPlugin.hlsStreamUrl!) else { + HMSErrorLogger.logError(#function, "hlsStreamURL not found", "NULL_ERROR") + return playerViewController.view + } + + /** + * Here we add the event listener to listen to the events + * of HLS Player + */ + hmsHLSStreamViewController = HMSHLSStreamViewController(hlsPlayer: hlsPlayer, hmssdkFlutterPlugin: hmssdkFlutterPlugin) + hlsPlayer?.delegate = hmsHLSStreamViewController + hlsPlayer?.play(hlsStreamURL) + NotificationCenter.default.addObserver(self, selector: #selector(handleHLSPlayerOperations), name: NSNotification.Name(HMSHLSPlayerAction.HLS_PLAYER_METHOD), object: nil) + + /** + * Here we add the stats listener to the + * HLS Player + */ + if isHLSStatsRequired { + HMSHLSStatsHandler.addHLSStatsListener(hlsPlayer, hmssdkFlutterPlugin) + } + } + } + + return playerViewController.view + + } + + /** + Deinitializes the HLS player and performs necessary cleanup. + */ + deinit { + + // Remove the HLS stats listener + HMSHLSStatsHandler.removeHLSStatsListener() + + // Set the hlsPlayer to nil to release its resources + hlsPlayer = nil + } + + /** + * Below methods handles the HLS Player controller calls + */ + @objc func handleHLSPlayerOperations(_ notification: Notification) { + switch notification.userInfo?[HMSHLSPlayerAction.METHOD_CALL] as? String? { + case "start_hls_player": + start(notification.userInfo!["hls_url"] as? String) + case "stop_hls_player": + hlsPlayer?.stop() + + case "pause_hls_player": + hlsPlayer?.pause() + + case "resume_hls_player": + hlsPlayer?.resume() + + case "seek_to_live_position": + hlsPlayer?.seekToLivePosition() + + case "seek_forward": + hlsPlayer?.seekForward(seconds: TimeInterval(notification.userInfo!["seconds"] as! Int)) + + case "seek_backward": + hlsPlayer?.seekBackward(seconds: TimeInterval(notification.userInfo!["seconds"] as! Int)) + + case "set_hls_player_volume": + hlsPlayer?.volume = notification.userInfo!["volume"] as! Int + + case "add_hls_stats_listener": + addHLSStatsListener() + + case "remove_hls_stats_listener": + removeHLSStatsListener() + + default: + return + } + } + + /** + Starts the HLS player with the specified HLS URL. + + - Parameters: + - hlsUrl: The HLS URL to play. If nil, the HLS URL from hmssdkFlutterPlugin will be used. + */ + private func start(_ hlsUrl: String?) { + + if hlsUrl != nil { + guard let hlsStreamURL = URL(string: hlsUrl!) else { + HMSErrorLogger.logError(#function, "hlsUrl not found", "NULL_ERROR") + return + } + hlsPlayer?.play(hlsStreamURL) + return + } else { + guard let hmssdkFlutterPlugin else { + HMSErrorLogger.logError(#function, "hmssdkFlutterPlugin is null", "NULL_ERROR") + return + } + + if hmssdkFlutterPlugin.hlsStreamUrl != nil { + guard let hlsStreamURL = URL(string: hmssdkFlutterPlugin.hlsStreamUrl!) else { + HMSErrorLogger.logError(#function, "hlsStreamURL not found", "NULL_ERROR") + return + } + hlsPlayer?.play(hlsStreamURL) + } + } + } + + private func addHLSStatsListener() { + guard let hlsPlayer else { + HMSErrorLogger.logError(#function, "hlsPlayer is null", "NULL_ERROR") + return + } + HMSHLSStatsHandler.addHLSStatsListener(hlsPlayer, hmssdkFlutterPlugin) + } + + private func removeHLSStatsListener() { + HMSHLSStatsHandler.removeHLSStatsListener() + } +} diff --git a/ios/Classes/Views/HMSHLSPlayerViewFactory.swift b/ios/Classes/Views/HMSHLSPlayerViewFactory.swift new file mode 100644 index 000000000..4654fe20a --- /dev/null +++ b/ios/Classes/Views/HMSHLSPlayerViewFactory.swift @@ -0,0 +1,46 @@ +// +// HMSHLSPlayerViewFactory.swift +// hmssdk_flutter +// +// Created by Pushpam on 23/05/23. +// + +import Foundation + +import Flutter +import HMSSDK +import UIKit + +class HMSHLSPlayerViewFactory: NSObject, FlutterPlatformViewFactory { + + let plugin: SwiftHmssdkFlutterPlugin + + init(plugin: SwiftHmssdkFlutterPlugin) { + self.plugin = plugin + super.init() + } + + func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { + + let arguments = args as? [String: AnyObject] + + let hlsUrl = arguments?["hls_url"] as? String + let isHLSStatsRequired = arguments?["is_hls_stats_required"] as? Bool ?? false + + let showPlayerControls = arguments?["show_player_controls"] as? Bool ?? false + + // This instantiates an HMSHLSPlayerView object with the provided parameters. + return HMSHLSPlayerView(frame: frame, + viewIdentifier: viewId, + hlsURL: hlsUrl, + hmssdkFlutterPlugin: plugin, + isHLSStatsRequired: isHLSStatsRequired, + showPlayerControls: showPlayerControls + ) + } + + func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { + return FlutterStandardMessageCodec.sharedInstance() + } + +} diff --git a/ios/hmssdk_flutter.podspec b/ios/hmssdk_flutter.podspec index b14dd69e0..86aa188cc 100644 --- a/ios/hmssdk_flutter.podspec +++ b/ios/hmssdk_flutter.podspec @@ -17,6 +17,7 @@ Pod::Spec.new do |s| s.dependency 'Flutter' s.dependency "HMSSDK", sdkVersions["ios"] s.dependency 'HMSBroadcastExtensionSDK', sdkVersions['iOSBroadcastExtension'] + s.dependency 'HMSHLSPlayerSDK', sdkVersions['iOSHLSPlayerSDK'] s.platform = :ios, '12.0' s.ios.deployment_target = '12.0' # Flutter.framework does not contain a i386 slice. diff --git a/lib/assets/sdk-versions.json b/lib/assets/sdk-versions.json index 1c2f57684..8e2ac0c23 100644 --- a/lib/assets/sdk-versions.json +++ b/lib/assets/sdk-versions.json @@ -1,6 +1,7 @@ { - "flutter": "1.6.0", - "ios": "0.9.3", + "flutter": "1.7.0", + "ios": "0.9.5", "iOSBroadcastExtension": "0.0.9", - "android": "2.6.2" + "iOSHLSPlayerSDK": "0.0.2", + "android": "2.6.7" } diff --git a/lib/hmssdk_flutter.dart b/lib/hmssdk_flutter.dart index 7256839ea..365d12f3c 100644 --- a/lib/hmssdk_flutter.dart +++ b/lib/hmssdk_flutter.dart @@ -21,6 +21,8 @@ export 'src/enum/hms_track_init_state.dart'; export 'src/enum/hms_Quality_limitation_reason.dart'; export 'src/enum/hms_simulcast_layer.dart'; export 'src/enum/hms_audio_mode.dart'; +export 'src/enum/hms_hls_playback_state.dart'; + //EXCEPTIONS export 'src/exceptions/hms_exception.dart'; export 'src/exceptions/hms_in_sufficient_data.dart'; @@ -55,7 +57,6 @@ export 'src/model/hms_video_setting.dart'; export 'src/model/hms_video_track.dart'; export 'src/model/hms_video_track_setting.dart'; export 'src/model/platform_method_response.dart'; -export 'src/ui/meeting/hms_video_view.dart'; export 'src/model/hms_track_change_request.dart'; export 'src/model/hms_peer_removed_from_room.dart'; export 'src/model/hms_message_recipient.dart'; @@ -92,3 +93,12 @@ export 'src/model/hms_log_list.dart'; export 'src/model/hms_camera_controls.dart'; export 'src/model/hms_session_store.dart'; export 'src/model/hms_key_change_listener.dart'; +export 'src/model/hls_player/hms_hls_playback_event_listener.dart'; +export 'src/model/hls_player/hms_hls_player_controller.dart'; +export 'src/model/hls_player/hms_hls_player_stats.dart'; +export 'src/model/hls_player/hms_hls_cue.dart'; +export 'src/model/hls_player/hms_hls_timed_metadata.dart'; + +//Views +export 'src/ui/meeting/hms_video_view.dart'; +export 'src/ui/meeting/hms_hls_player.dart'; diff --git a/lib/src/common/platform_methods.dart b/lib/src/common/platform_methods.dart index c505e8258..9ddb69378 100644 --- a/lib/src/common/platform_methods.dart +++ b/lib/src/common/platform_methods.dart @@ -146,8 +146,6 @@ enum PlatformMethod { audioShareDuration, getTrackSettings, destroy, - setSessionMetadata, - getSessionMetadata, setPlaybackAllowedForTrack, enterPipMode, isPipActive, @@ -173,7 +171,19 @@ enum PlatformMethod { getSessionMetadataForKey, setSessionMetadataForKey, addKeyChangeListener, - removeKeyChangeListener + removeKeyChangeListener, + start, + stop, + pause, + resume, + seekToLivePosition, + seekForward, + seekBackward, + setHLSPlayerVolume, + addHLSStatsListener, + removeHLSStatsListener, + switchAudioOutputUsingiOSUI, + sendHLSTimedMetadata } extension PlatformMethodValues on PlatformMethod { @@ -365,10 +375,6 @@ extension PlatformMethodValues on PlatformMethod { return "get_track_settings"; case PlatformMethod.destroy: return "destroy"; - case PlatformMethod.setSessionMetadata: - return "set_session_metadata"; - case PlatformMethod.getSessionMetadata: - return "get_session_metadata"; case PlatformMethod.setPlaybackAllowedForTrack: return "set_playback_allowed_for_track"; case PlatformMethod.enterPipMode: @@ -421,6 +427,30 @@ extension PlatformMethodValues on PlatformMethod { return "add_key_change_listener"; case PlatformMethod.removeKeyChangeListener: return "remove_key_change_listener"; + case PlatformMethod.start: + return "start_hls_player"; + case PlatformMethod.stop: + return "stop_hls_player"; + case PlatformMethod.pause: + return "pause_hls_player"; + case PlatformMethod.resume: + return "resume_hls_player"; + case PlatformMethod.seekToLivePosition: + return "seek_to_live_position"; + case PlatformMethod.seekForward: + return "seek_forward"; + case PlatformMethod.seekBackward: + return "seek_backward"; + case PlatformMethod.setHLSPlayerVolume: + return "set_hls_player_volume"; + case PlatformMethod.addHLSStatsListener: + return "add_hls_stats_listener"; + case PlatformMethod.removeHLSStatsListener: + return "remove_hls_stats_listener"; + case PlatformMethod.switchAudioOutputUsingiOSUI: + return "switch_audio_output_using_ios_ui"; + case PlatformMethod.sendHLSTimedMetadata: + return "send_hls_timed_metadata"; default: return 'unknown'; } @@ -618,10 +648,6 @@ extension PlatformMethodValues on PlatformMethod { return PlatformMethod.getTrackSettings; case "destroy": return PlatformMethod.destroy; - case "set_session_metadata": - return PlatformMethod.setSessionMetadata; - case "get_session_metadata": - return PlatformMethod.getSessionMetadata; case "set_playback_allowed_for_track": return PlatformMethod.setPlaybackAllowedForTrack; case "enter_pip_mode": @@ -670,6 +696,30 @@ extension PlatformMethodValues on PlatformMethod { return PlatformMethod.addKeyChangeListener; case "remove_key_change_listener": return PlatformMethod.removeKeyChangeListener; + case "start_hls_player": + return PlatformMethod.start; + case "stop_hls_player": + return PlatformMethod.stop; + case "pause_hls_player": + return PlatformMethod.pause; + case "resume_hls_player": + return PlatformMethod.resume; + case "seek_to_live_position": + return PlatformMethod.seekToLivePosition; + case "seek_forward": + return PlatformMethod.seekForward; + case "seek_backward": + return PlatformMethod.seekBackward; + case "set_hls_player_volume": + return PlatformMethod.setHLSPlayerVolume; + case "add_hls_stats_listener": + return PlatformMethod.addHLSStatsListener; + case "remove_hls_stats_listener": + return PlatformMethod.removeHLSStatsListener; + case "switch_audio_output_using_ios_ui": + return PlatformMethod.switchAudioOutputUsingiOSUI; + case "send_hls_timed_metadata": + return PlatformMethod.sendHLSTimedMetadata; 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 b3b9f935f..6886643b1 100644 --- a/lib/src/enum/hms_action_result_listener_method.dart +++ b/lib/src/enum/hms_action_result_listener_method.dart @@ -21,10 +21,9 @@ enum HMSActionResultListenerMethod { stopScreenShare, startAudioShare, stopAudioShare, - @Deprecated('use [setSessionMetadataForKey]') - setSessionMetadata, switchCamera, changeRoleOfPeersWithRoles, setSessionMetadataForKey, + sendHLSTimedMetadata, unknown } diff --git a/lib/src/enum/hms_hls_playback_event_method.dart b/lib/src/enum/hms_hls_playback_event_method.dart new file mode 100644 index 000000000..073cc64c5 --- /dev/null +++ b/lib/src/enum/hms_hls_playback_event_method.dart @@ -0,0 +1,28 @@ +///[HMSHLSPlaybackEventMethod] is an enum containing the HLS Player callbacks +enum HMSHLSPlaybackEventMethod { + onPlaybackFailure, + onPlaybackStateChanged, + onCue, + onHLSError, + onHLSEventUpdate, + unknown +} + +extension HMSHLSPlaybackEventMethodValues on HMSHLSPlaybackEventMethod { + static HMSHLSPlaybackEventMethod getMethodFromName(String name) { + switch (name) { + case "on_playback_failure": + return HMSHLSPlaybackEventMethod.onPlaybackFailure; + case "on_playback_state_changed": + return HMSHLSPlaybackEventMethod.onPlaybackStateChanged; + case "on_cue": + return HMSHLSPlaybackEventMethod.onCue; + case "on_hls_error": + return HMSHLSPlaybackEventMethod.onHLSError; + case "on_hls_event_update": + return HMSHLSPlaybackEventMethod.onHLSEventUpdate; + default: + return HMSHLSPlaybackEventMethod.unknown; + } + } +} diff --git a/lib/src/enum/hms_hls_playback_state.dart b/lib/src/enum/hms_hls_playback_state.dart new file mode 100644 index 000000000..73edb7e90 --- /dev/null +++ b/lib/src/enum/hms_hls_playback_state.dart @@ -0,0 +1,28 @@ +/// [HMSHLSPlaybackState] is an enum containing different HLS Player states +enum HMSHLSPlaybackState { + PLAYING, + STOPPED, + PAUSED, + BUFFERING, + FAILED, + UNKNOWN +} + +extension HMSHLSPlaybackStateValues on HMSHLSPlaybackState { + static HMSHLSPlaybackState getMethodFromName(String name) { + switch (name) { + case "playing": + return HMSHLSPlaybackState.PLAYING; + case "stopped": + return HMSHLSPlaybackState.STOPPED; + case "paused": + return HMSHLSPlaybackState.PAUSED; + case "buffering": + return HMSHLSPlaybackState.BUFFERING; + case "failed": + return HMSHLSPlaybackState.FAILED; + default: + return HMSHLSPlaybackState.UNKNOWN; + } + } +} diff --git a/lib/src/hmssdk.dart b/lib/src/hmssdk.dart index 1d09cf9d1..9c0b51494 100644 --- a/lib/src/hmssdk.dart +++ b/lib/src/hmssdk.dart @@ -1,7 +1,6 @@ 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'; @@ -380,6 +379,22 @@ class HMSSDK { return listOfPeers; } + /// Utility Function to parse messages from + /// [sendBroadcastMessage], [sendGroupMessage], [sendDirectMessage] + /// to send the HMSMessage object from the server back to the application + Map? _hmsMessageToMap(Map? result) { + return result == null + ? null + : { + 'message_id': result["message"]["message_id"], + 'sender': result["message"]["sender"], + 'message': result["message"]["message"], + 'hms_message_recipient': result["message"]["hms_message_recipient"], + 'type': result["message"]["type"], + 'time': result["message"]["time"], + }; + } + /// Sends a message to everyone on the call. /// /// **Parameters**: @@ -409,7 +424,7 @@ class HMSSDK { } else { hmsActionResultListener.onSuccess( methodType: HMSActionResultListenerMethod.sendBroadcastMessage, - arguments: arguments); + arguments: _hmsMessageToMap(result)); } } } @@ -449,7 +464,7 @@ class HMSSDK { } else { hmsActionResultListener.onSuccess( methodType: HMSActionResultListenerMethod.sendGroupMessage, - arguments: {"message": message, "type": type, "roles": hmsRolesTo}); + arguments: _hmsMessageToMap(result)); } } } @@ -490,7 +505,7 @@ class HMSSDK { } else { hmsActionResultListener.onSuccess( methodType: HMSActionResultListenerMethod.sendDirectMessage, - arguments: {"message": message, "peer": peerTo, "type": type}); + arguments: _hmsMessageToMap(result)); } } } @@ -931,6 +946,37 @@ class HMSSDK { } } + ///Method to send Timed metadata for HLS Stream + /// + ///**Parameters**: + /// + /// **metadata** - [metadata] the data which you wish to send as metadata + /// + /// **hmsActionResultListener** - [hmsActionResultListener] is a callback whose [HMSActionResultListener.onSuccess] will be called when the action completes successfully. + /// + ///Refer TODO: ADD Docs Link + Future sendHLSTimedMetadata( + {required List metadata, + HMSActionResultListener? hmsActionResultListener}) async { + var args = {"metadata": metadata.map((e) => e.toMap()).toList()}; + + var result = await PlatformService.invokeMethod( + PlatformMethod.sendHLSTimedMetadata, + arguments: args); + + if (hmsActionResultListener != null) { + if (result == null) { + hmsActionResultListener.onSuccess( + methodType: HMSActionResultListenerMethod.sendHLSTimedMetadata, + arguments: args); + } else { + hmsActionResultListener.onException( + methodType: HMSActionResultListenerMethod.sendHLSTimedMetadata, + hmsException: HMSException.fromMap(result["error"])); + } + } + } + /// Change the metadata that appears inside [HMSPeer.metadata]. This change is persistent and all peers joining after the change will still see these values. /// /// **Parameters**: @@ -1147,6 +1193,15 @@ class HMSSDK { arguments: {"audio_device_name": audioDevice.name}); } + ///**** Only for iOS **** + /// Method to show the native iOS UI for switching the audio output device. + /// This method natively switches the audio output to the selected device. + void switchAudioOutputUsingiOSUI() { + if (Platform.isIOS) { + PlatformService.invokeMethod(PlatformMethod.switchAudioOutputUsingiOSUI); + } + } + ///Method to start audio share of other apps. (Android Only) /// ///**Parameter**: @@ -1226,51 +1281,6 @@ class HMSSDK { PlatformService.invokeMethod(PlatformMethod.destroy); } - /// Method to update the value of the session metadata. - /// - ///**Parameters**: - /// - ///**metadata** - passing string value you want to set as Session Metadata. - /// - /// **hmsActionResultListener** - [hmsActionResultListener] is a callback instance on which [HMSActionResultListener.onSuccess] and [HMSActionResultListener.onException] will be called. - /// - ///Refer [session metadata guide here](https://www.100ms.live/docs/flutter/v2/features/session-metadata) - @Deprecated('Use [setSessionMetadataForKey]') - Future setSessionMetadata( - {required String? metadata, - HMSActionResultListener? hmsActionResultListener}) async { - var arguments = {"session_metadata": metadata}; - var result = await PlatformService.invokeMethod( - PlatformMethod.setSessionMetadata, - arguments: arguments); - - if (hmsActionResultListener != null) { - if (result != null && result["error"] != null) { - hmsActionResultListener.onException( - methodType: HMSActionResultListenerMethod.setSessionMetadata, - arguments: arguments, - hmsException: HMSException.fromMap(result["error"])); - } else { - hmsActionResultListener.onSuccess( - methodType: HMSActionResultListenerMethod.setSessionMetadata, - arguments: arguments); - } - } - } - - ///Method to fetch the latest metadata from the server and returns it - /// - ///Refer [session metadata guide here](https://www.100ms.live/docs/flutter/v2/features/session-metadata) - @Deprecated('Use [getSessionMetadataForKey]') - Future getSessionMetadata() async { - var result = - await PlatformService.invokeMethod(PlatformMethod.getSessionMetadata); - if (result != null) { - return HMSSessionMetadata.fromMap(result).metadata; - } - return null; - } - ///Method to activate pipMode in the application /// ///**Parameters**: diff --git a/lib/src/model/hls_player/hms_hls_cue.dart b/lib/src/model/hls_player/hms_hls_cue.dart new file mode 100644 index 000000000..ac9abf47c --- /dev/null +++ b/lib/src/model/hls_player/hms_hls_cue.dart @@ -0,0 +1,31 @@ +//Project imports +import 'package:hmssdk_flutter/src/model/hms_date_extension.dart'; + +///100ms HMSHLSCue +/// +///[HMSHLSCue] is used to parse the timed metadata sent across the platforms. +class HMSHLSCue { + // Unique id of the timed event + final String? id; + + // startDate of the timed event + final DateTime startDate; + + // endDate of the timed event + final DateTime? endDate; + + // String payload of the timed event + final String? payload; + + HMSHLSCue({this.id, required this.startDate, this.endDate, this.payload}); + + factory HMSHLSCue.fromMap(Map map) { + return HMSHLSCue( + startDate: HMSDateExtension.convertDate(map["start_date"]), + endDate: map["end_date"] == null + ? null + : HMSDateExtension.convertDate(map["end_date"]), + id: map["id"], + payload: map["payload"]); + } +} diff --git a/lib/src/model/hls_player/hms_hls_playback_event_listener.dart b/lib/src/model/hls_player/hms_hls_playback_event_listener.dart new file mode 100644 index 000000000..905245506 --- /dev/null +++ b/lib/src/model/hls_player/hms_hls_playback_event_listener.dart @@ -0,0 +1,32 @@ +//Project imports +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +///100ms HMSHLSPlaybackEventsListener +/// +///100ms provides callbacks to the client app about any changes in HLS Player +abstract class HMSHLSPlaybackEventsListener { + ///Callback to know about the errors that happen during playback. + /// + ///- Parameter: error: Error string containing the reason for failure + void onPlaybackFailure({required String? error}) {} + + ///Callback to know about the state change event during the playback. + /// + ///- Parameter: playbackState: An enum of type [HMSHLSPlaybackState] containing the current playback state + void onPlaybackStateChanged({required HMSHLSPlaybackState playbackState}) {} + + ///Callback to know about any HLS Timed Metadata cue and use it's data to show any UI like quizzes, poll etc. to HLS viewers. + /// + ///- Parameter: hlsCue: A [HMSHLSCue] object containing details of the HLS Metadata payload, time etc. + void onCue({required HMSHLSCue hlsCue}) {} + + /// Callback to know about failures in HLS Stats. + /// + ///- Parameter: hlsException: A [HMSException] object containing details about the failure cause. + void onHLSError({required HMSException hlsException}) {} + + /// Callback to get HLS Player stats + /// + /// - Parameter: playerStats: A [HMSHLSPlayerStats] object containing info about HLS Player stats + void onHLSEventUpdate({required HMSHLSPlayerStats playerStats}) {} +} diff --git a/lib/src/model/hls_player/hms_hls_player_controller.dart b/lib/src/model/hls_player/hms_hls_player_controller.dart new file mode 100644 index 000000000..6a0735f00 --- /dev/null +++ b/lib/src/model/hls_player/hms_hls_player_controller.dart @@ -0,0 +1,102 @@ +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:hmssdk_flutter/src/service/platform_service.dart'; + +///100ms HMSHLSPlayerController +/// +/// [HMSHLSPlayerController] class contains methods to control the HLS Playback +class HMSHLSPlayerController { + /// Adds an [HMSHLSPlaybackEventsListener] to listen for HLS playback events. + /// **parameters**: + /// + /// **hmshlsPlaybackEventsListener** - hls playback event listener to be attached + static void addHMSHLSPlaybackEventsListener( + HMSHLSPlaybackEventsListener hmshlsPlaybackEventsListener) { + PlatformService.addHLSPlaybackEventListener(hmshlsPlaybackEventsListener); + } + + /// Removes an [HMSHLSPlaybackEventsListener] that was previously added. + /// **parameters**: + /// + /// **hmshlsPlaybackEventsListener** - hls playback event listener to be removed + static void removeHMSHLSPlaybackEventsListener( + HMSHLSPlaybackEventsListener hmshlsPlaybackEventsListener) { + PlatformService.removeHLSPlaybackEventListener( + hmshlsPlaybackEventsListener); + } + + /// Starts the HLS playback with the specified [hlsUrl]. + /// **parameters**: + /// + /// **hlsUrl** - hls stream m3u8 url to be played + static Future start({String? hlsUrl}) async { + await PlatformService.invokeMethod(PlatformMethod.start, + arguments: {"hls_url": hlsUrl}); + } + + /// Stops the HLS playback. + static Future stop() async { + await PlatformService.invokeMethod(PlatformMethod.stop); + } + + /// Pauses the HLS playback. + static Future pause() async { + await PlatformService.invokeMethod(PlatformMethod.pause); + } + + /// Resumes the paused HLS playback. + static Future resume() async { + await PlatformService.invokeMethod(PlatformMethod.resume); + } + + /// Seeks to the live position in the HLS playback. + static Future seekToLivePosition() async { + await PlatformService.invokeMethod(PlatformMethod.seekToLivePosition); + } + + /// Seeks forward in the HLS playback by the specified [seconds]. + /// **parameters**: + /// + /// **seconds** - seek forward in the stream by specified seconds + static Future seekForward({required int seconds}) async { + final Map arguments = {'seconds': seconds}; + await PlatformService.invokeMethod(PlatformMethod.seekForward, + arguments: arguments); + } + + /// Seeks backward in the HLS playback by the specified [seconds]. + /// **parameters**: + /// + /// **seconds** - seek backward in the stream by specified seconds + static Future seekBackward({required int seconds}) async { + final Map arguments = { + 'seconds': seconds, + }; + await PlatformService.invokeMethod(PlatformMethod.seekBackward, + arguments: arguments); + } + + /// Sets the volume of the HLS player to the specified [volume]. + /// **parameters**: + /// + /// **volume** - volume for HLS player + /// The [volume] value should be between 0 and 100. + static Future setVolume({required int volume}) async { + /// Asserting that volume can be between 0 and 100. + assert(volume >= 0 && volume <= 100, + "Volume must be between 0 and 100 currently it is provided as $volume"); + final Map arguments = {'volume': volume}; + + await PlatformService.invokeMethod(PlatformMethod.setHLSPlayerVolume, + arguments: arguments); + } + + /// Adds an HLS stats listener to receive HLS playback statistics. + static Future addHLSStatsListener() async { + await PlatformService.invokeMethod(PlatformMethod.addHLSStatsListener); + } + + /// Removes the HLS stats listener that was previously added. + static Future removeHLSStatsListener() async { + await PlatformService.invokeMethod(PlatformMethod.removeHLSStatsListener); + } +} diff --git a/lib/src/model/hls_player/hms_hls_player_stats.dart b/lib/src/model/hls_player/hms_hls_player_stats.dart new file mode 100644 index 000000000..5d1a88093 --- /dev/null +++ b/lib/src/model/hls_player/hms_hls_player_stats.dart @@ -0,0 +1,64 @@ +/// 100ms HMSHLSPlayerStats +/// +///[HMSHLSPlayerStats] is a data class, confirming to the HLSPlayerStats +/// +///Whenever the player stats are attached, we get an object of [HMSHLSPlayerStats] in [onHLSEventUpdate] callback. +class HMSHLSPlayerStats { + /// The current bandwidth, as estimated by the player + final double bandWidthEstimate; + + /// The total bytes downloaded within the given poll duration + final int totalBytesLoaded; + + /// An estimate of the total buffered duration from the current position + final double bufferedDuration; + + /// Distance of current playing position from live edge + final double distanceFromLive; + + /// bitrate of the current layer being played + final double averageBitrate; + + /// The height of the video + final double videoHeight; + + /// The width of the video + final double videoWidth; + + /// The number of dropped frames since the last call to this method + final int droppedFrameCount; + + HMSHLSPlayerStats( + {required this.bandWidthEstimate, + required this.totalBytesLoaded, + required this.bufferedDuration, + required this.distanceFromLive, + required this.averageBitrate, + required this.droppedFrameCount, + required this.videoHeight, + required this.videoWidth}); + + factory HMSHLSPlayerStats.fromMap(Map map) { + return HMSHLSPlayerStats( + bandWidthEstimate: (map["bandwidth_estimate"] is int) + ? map["bandwidth_estimate"].toDouble() + : map["bandwidth_estimate"], + totalBytesLoaded: map["total_bytes_loaded"], + bufferedDuration: (map["buffered_duration"] is int) + ? map["buffered_duration"].toDouble() + : map["buffered_duration"], + distanceFromLive: (map["distance_from_live"] is int) + ? map["distance_from_live"].toDouble() + : map["distance_from_live"], + droppedFrameCount: map["dropped_frame_count"], + averageBitrate: (map["average_bitrate"] is int) + ? map["average_bitrate"].toDouble() + : map["average_bitrate"], + videoHeight: (map["video_height"] is int) + ? map["video_height"].toDouble() + : map["video_height"], + videoWidth: (map["video_width"] is int) + ? map["video_width"].toDouble() + : map["video_width"]); + } +} diff --git a/lib/src/model/hls_player/hms_hls_timed_metadata.dart b/lib/src/model/hls_player/hms_hls_timed_metadata.dart new file mode 100644 index 000000000..3dd1b9f72 --- /dev/null +++ b/lib/src/model/hls_player/hms_hls_timed_metadata.dart @@ -0,0 +1,16 @@ +///100ms HMSHLSTimedMetadata +/// +/// [HMSHLSTimedMetadata] contains data to be sent as metadata for HLS Stream +class HMSHLSTimedMetadata { + ///[metadata] contains the message/data which can be sent to a HLS Stream + final String metadata; + + ///[duration] is the time interval for which the metadata is valid + final int duration; + + HMSHLSTimedMetadata({required this.metadata, this.duration = 1}); + + Map toMap() { + return {"metadata": metadata, "duration": duration}; + } +} diff --git a/lib/src/model/hms_message.dart b/lib/src/model/hms_message.dart index 7b2dcac2c..4cb69fb37 100644 --- a/lib/src/model/hms_message.dart +++ b/lib/src/model/hms_message.dart @@ -18,6 +18,9 @@ import 'package:hmssdk_flutter/src/model/hms_date_extension.dart'; /// ///Refer [chat guide here](https://www.100ms.live/docs/flutter/v2/features/chat) class HMSMessage { + ///[messageId] id to uniquely identify the message + final String messageId; + ///[sender] id basically it is the peerId who is sending message. final HMSPeer? sender; @@ -32,7 +35,8 @@ class HMSMessage { HMSMessageRecipient? hmsMessageRecipient; HMSMessage( - {required this.sender, + {required this.messageId, + required this.sender, required this.message, required this.type, required this.time, @@ -40,12 +44,15 @@ class HMSMessage { factory HMSMessage.fromMap(Map map) { Map messageMap = map; + String messageId = + messageMap.containsKey("message_id") ? messageMap["message_id"] : ""; HMSPeer? sender = messageMap.containsKey("sender") ? HMSPeer.fromMap(messageMap['sender']) : null; HMSMessageRecipient recipient = HMSMessageRecipient.fromMap(messageMap['hms_message_recipient']); return HMSMessage( + messageId: messageId, sender: sender, message: messageMap['message'] as String, type: messageMap['type'] as String, @@ -55,6 +62,7 @@ class HMSMessage { Map toMap(Map map) { return { + 'message_id': messageId, 'sender': sender, 'message': message, 'type': type, diff --git a/lib/src/model/hms_publish_setting.dart b/lib/src/model/hms_publish_setting.dart index b43a8e73c..4cf2cf079 100644 --- a/lib/src/model/hms_publish_setting.dart +++ b/lib/src/model/hms_publish_setting.dart @@ -23,15 +23,15 @@ class HMSPublishSetting { allowed = []; HMSAudioSetting? audioSetting; - if (map.containsKey('audio_setting')) - audioSetting = HMSAudioSetting.fromMap(map['audio_setting']); + if (map.containsKey('audio')) + audioSetting = HMSAudioSetting.fromMap(map['audio']); HMSVideoSetting? videoSetting; - if (map.containsKey('video_setting')) { - videoSetting = HMSVideoSetting.fromMap(map['video_setting']); + if (map.containsKey('video')) { + videoSetting = HMSVideoSetting.fromMap(map['video']); } HMSVideoSetting? screenSetting; - if (map.containsKey('screen_setting')) { - screenSetting = HMSVideoSetting.fromMap(map['screen_setting']); + if (map.containsKey('screen')) { + screenSetting = HMSVideoSetting.fromMap(map['screen']); } HMSSimulcastSettings? simulcastSettings; if (map.containsKey('simulcast')) { diff --git a/lib/src/model/hms_recording_config.dart b/lib/src/model/hms_recording_config.dart index 0664236d8..db765751d 100644 --- a/lib/src/model/hms_recording_config.dart +++ b/lib/src/model/hms_recording_config.dart @@ -5,7 +5,7 @@ import 'package:hmssdk_flutter/hmssdk_flutter.dart'; ///[HMSRecordingConfig] contains meeting Url, record status, rtmp Urls list and video resolution. class HMSRecordingConfig { ///Single click meeting url to start/stop recording/streaming. - final String meetingUrl; + final String? meetingUrl; ///true if recording/streaming should be started. false if recording/streaming should be stoppped. final bool toRecord; @@ -22,7 +22,7 @@ class HMSRecordingConfig { HMSResolution? resolution; HMSRecordingConfig( - {required this.meetingUrl, + {this.meetingUrl, required this.toRecord, this.rtmpUrls, this.resolution}); diff --git a/lib/src/model/hms_video_track_setting.dart b/lib/src/model/hms_video_track_setting.dart index abbb711f9..9fd0fc676 100644 --- a/lib/src/model/hms_video_track_setting.dart +++ b/lib/src/model/hms_video_track_setting.dart @@ -16,7 +16,7 @@ class HMSVideoTrackSetting { /// [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) + /// [forceSoftwareDecoder] property to use software decoder. By default it's set to false. This property is available only on Android. final bool? forceSoftwareDecoder; HMSVideoTrackSetting( diff --git a/lib/src/model/platform_method_response.dart b/lib/src/model/platform_method_response.dart index f53865bb5..130cb25cd 100644 --- a/lib/src/model/platform_method_response.dart +++ b/lib/src/model/platform_method_response.dart @@ -1,5 +1,6 @@ // Project imports: import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:hmssdk_flutter/src/enum/hms_hls_playback_event_method.dart'; import 'package:hmssdk_flutter/src/enum/hms_key_change_listener_method.dart'; import 'package:hmssdk_flutter/src/enum/hms_logs_update_listener.dart'; @@ -71,3 +72,13 @@ class HMSKeyChangeListenerMethodResponse { HMSKeyChangeListenerMethodResponse( {required this.method, required this.data}); } + +///HMSHLSPlayerPlaybackEventResponse contains all the responses sent from the hls player channel +/// +/// Checkout different responses in [HMSHLSPlaybackEventMethod] enum +class HMSHLSPlayerPlaybackEventResponse { + final HMSHLSPlaybackEventMethod method; + final Map data; + + HMSHLSPlayerPlaybackEventResponse({required this.method, required this.data}); +} diff --git a/lib/src/service/platform_service.dart b/lib/src/service/platform_service.dart index f7f292235..f25b6a755 100644 --- a/lib/src/service/platform_service.dart +++ b/lib/src/service/platform_service.dart @@ -13,6 +13,7 @@ import 'dart:async'; import 'package:flutter/services.dart'; // Project imports: import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:hmssdk_flutter/src/enum/hms_hls_playback_event_method.dart'; import 'package:hmssdk_flutter/src/enum/hms_key_change_listener_method.dart'; import 'package:hmssdk_flutter/src/enum/hms_logs_update_listener.dart'; import 'package:hmssdk_flutter/src/model/hms_key_change_observer.dart'; @@ -40,6 +41,10 @@ class PlatformService { static const EventChannel _sessionStoreChannel = const EventChannel("session_event_channel"); + ///used to get stream of session store changes + static const EventChannel _hlsPlayerChannel = + const EventChannel("hls_player_channel"); + ///add meeting listeners. static List updateListeners = []; @@ -50,6 +55,8 @@ class PlatformService { static List keyChangeObservers = []; + static List hlsPlaybackEventListener = []; + ///List for event Listener static List statsListeners = []; static bool isStartedListening = false; @@ -127,6 +134,16 @@ class PlatformService { } } + static void addHLSPlaybackEventListener( + HMSHLSPlaybackEventsListener hmshlsPlaybackEventsListener) { + hlsPlaybackEventListener.add(hmshlsPlaybackEventsListener); + } + + static void removeHLSPlaybackEventListener( + HMSHLSPlaybackEventsListener hmshlsPlaybackEventsListener) { + hlsPlaybackEventListener.remove(hmshlsPlaybackEventsListener); + } + ///used to invoke different methods at platform side and returns something but not neccessarily static Future invokeMethod(PlatformMethod method, {Map? arguments}) async { @@ -478,6 +495,17 @@ class PlatformService { break; } }); + + _hlsPlayerChannel + .receiveBroadcastStream({'name': 'hls_player'}).map((event) { + HMSHLSPlaybackEventMethod method = + HMSHLSPlaybackEventMethodValues.getMethodFromName( + event['event_name']); + Map data = event['data']; + return HMSHLSPlayerPlaybackEventResponse(method: method, data: data); + }).listen((event) { + notifyHLSPlaybackEventListeners(event.method, event.data); + }); } static void notifyLogsUpdateListeners( @@ -654,4 +682,33 @@ class PlatformService { break; } } + + static void notifyHLSPlaybackEventListeners( + HMSHLSPlaybackEventMethod method, Map arguments) { + switch (method) { + case HMSHLSPlaybackEventMethod.onPlaybackFailure: + hlsPlaybackEventListener + .forEach((e) => e.onPlaybackFailure(error: arguments["error"])); + break; + case HMSHLSPlaybackEventMethod.onPlaybackStateChanged: + hlsPlaybackEventListener.forEach((e) => e.onPlaybackStateChanged( + playbackState: HMSHLSPlaybackStateValues.getMethodFromName( + arguments["playback_state"]))); + break; + case HMSHLSPlaybackEventMethod.onCue: + hlsPlaybackEventListener + .forEach((e) => e.onCue(hlsCue: HMSHLSCue.fromMap(arguments))); + break; + case HMSHLSPlaybackEventMethod.onHLSError: + hlsPlaybackEventListener.forEach( + (e) => e.onHLSError(hlsException: HMSException.fromMap(arguments))); + break; + case HMSHLSPlaybackEventMethod.onHLSEventUpdate: + hlsPlaybackEventListener.forEach((e) => e.onHLSEventUpdate( + playerStats: HMSHLSPlayerStats.fromMap(arguments))); + break; + case HMSHLSPlaybackEventMethod.unknown: + break; + } + } } diff --git a/lib/src/ui/meeting/hms_hls_player.dart b/lib/src/ui/meeting/hms_hls_player.dart new file mode 100644 index 000000000..1bb4a9a59 --- /dev/null +++ b/lib/src/ui/meeting/hms_hls_player.dart @@ -0,0 +1,98 @@ +// Dart imports: +import 'dart:io' show Platform; + +// Flutter imports: +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show StandardMessageCodec; + +///100ms HMSHLSPlayer +/// +///HMSHLSPlayer used to render hls stream in ios and android devices +/// +/// To use,import package:`hmssdk_flutter/ui/meeting/hms_hls_player.dart`. +/// +/// [HMSHLSPlayer] will render video using streamURL +/// +/// **parameters** +/// +/// **hlsUrl** - m3u8 Stream URL, if not passed HMSSDK tries to get it internally +/// +/// **isHLSStatsRequired** - If HLS stats are required set this to true. Default is false +/// +/// **showPlayerControls** - To show the default player UI set this to true. Default is false +/// +/// Refer [HMSVideoView guide here](https://www.100ms.live/docs/flutter/v2/features/render-video) +class HMSHLSPlayer extends StatelessWidget { + /// This will render stream with the given stream URL + /// [hlsUrl] - the video track to be displayed + final String? hlsUrl; + + final bool? isHLSStatsRequired; + + final bool? showPlayerControls; + + HMSHLSPlayer( + {Key? key, this.hlsUrl, this.isHLSStatsRequired, this.showPlayerControls}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return _PlatformView( + hlsUrl: hlsUrl, + isHLSStatsRequired: isHLSStatsRequired, + showPlayerControls: showPlayerControls, + key: key, + ); + } +} + +class _PlatformView extends StatelessWidget { + final String? hlsUrl; + final bool? isHLSStatsRequired; + + final bool? showPlayerControls; + + _PlatformView( + {Key? key, + this.hlsUrl, + this.isHLSStatsRequired = false, + this.showPlayerControls = false}) + : super(key: key); + + void onPlatformViewCreated(int id) {} + + @override + Widget build(BuildContext context) { + ///AndroidView for android it uses surfaceRenderer provided internally by webrtc. + if (Platform.isAndroid) { + return AndroidView( + key: key, + viewType: 'HMSHLSPlayer', + onPlatformViewCreated: onPlatformViewCreated, + creationParamsCodec: StandardMessageCodec(), + creationParams: { + 'hls_url': hlsUrl, + 'is_hls_stats_required': isHLSStatsRequired, + 'show_player_controls': showPlayerControls + }, + gestureRecognizers: {}, + ); + } else if (Platform.isIOS) { + ///UIKitView for ios it uses VideoView provided by 100ms ios_sdk internally. + return UiKitView( + viewType: 'HMSHLSPlayer', + onPlatformViewCreated: onPlatformViewCreated, + creationParamsCodec: StandardMessageCodec(), + creationParams: { + 'hls_url': hlsUrl, + 'is_hls_stats_required': isHLSStatsRequired, + 'show_player_controls': showPlayerControls + }, + gestureRecognizers: {}, + ); + } else { + throw UnimplementedError( + 'Video View is not implemented for this platform ${Platform.localHostname}'); + } + } +} diff --git a/lib/src/ui/meeting/hms_video_view.dart b/lib/src/ui/meeting/hms_video_view.dart index b4da795d6..659fb0c73 100644 --- a/lib/src/ui/meeting/hms_video_view.dart +++ b/lib/src/ui/meeting/hms_video_view.dart @@ -83,7 +83,8 @@ class HMSVideoView extends StatelessWidget { {Key? key, required this.track, this.setMirror = false, - this.matchParent = true, + @Deprecated("matchParent is not longer necessary and will be removed in future version") + this.matchParent = true, this.scaleType = ScaleType.SCALE_ASPECT_FIT, this.disableAutoSimulcastLayerSelect = false}) : super(key: key); diff --git a/pubspec.lock b/pubspec.lock index 4ac96dbbd..96fecdde3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -162,4 +162,4 @@ packages: version: "2.1.4" sdks: dart: ">=2.18.0 <3.0.0" - flutter: ">=1.20.0" + flutter: ">=2.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 76beae5ba..81ad80742 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,14 +1,14 @@ name: hmssdk_flutter description: Add Real Time Audio & Video calls, Interactive Live Streaming & Recording, Chat, HLS, RTMP, PiP, CallKit, VoIP, Video conferencing, Stream Player & WebRTC-based communications API -version: 1.6.0 +version: 1.7.0 homepage: https://www.100ms.live/ repository: https://github.com/100mslive/100ms-flutter issue_tracker: https://github.com/100mslive/100ms-flutter/issues documentation: https://www.100ms.live/docs environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=1.20.0" + sdk: ">=2.16.0 <4.0.0" + flutter: ">=2.10.0" dependencies: flutter: diff --git a/sample apps/bloc/android/app/src/main/kotlin/com/example/demo_app_with_100ms_and_bloc/MainActivity.kt b/sample apps/bloc/android/app/src/main/kotlin/com/example/demo_app_with_100ms_and_bloc/MainActivity.kt index 4521925f8..c189b717c 100644 --- a/sample apps/bloc/android/app/src/main/kotlin/com/example/demo_app_with_100ms_and_bloc/MainActivity.kt +++ b/sample apps/bloc/android/app/src/main/kotlin/com/example/demo_app_with_100ms_and_bloc/MainActivity.kt @@ -1,18 +1,17 @@ package com.example.demo_app_with_100ms_and_bloc -import io.flutter.embedding.android.FlutterActivity -import live.hms.hmssdk_flutter.HmssdkFlutterPlugin import android.app.Activity import android.content.Intent +import io.flutter.embedding.android.FlutterActivity import live.hms.hmssdk_flutter.Constants +import live.hms.hmssdk_flutter.HmssdkFlutterPlugin -class MainActivity: FlutterActivity() { +class MainActivity : FlutterActivity() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { -super.onActivityResult(requestCode, resultCode, data) + super.onActivityResult(requestCode, resultCode, data) - if (requestCode == Constants.SCREEN_SHARE_INTENT_REQUEST_CODE && resultCode == Activity.RESULT_OK){ - HmssdkFlutterPlugin.hmssdkFlutterPlugin?.requestScreenShare(data) + if (requestCode == Constants.SCREEN_SHARE_INTENT_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + HmssdkFlutterPlugin.hmssdkFlutterPlugin?.requestScreenShare(data) + } } - -} } diff --git a/sample apps/bloc/lib/home_page.dart b/sample apps/bloc/lib/home_page.dart index 96c92792f..939e33374 100644 --- a/sample apps/bloc/lib/home_page.dart +++ b/sample apps/bloc/lib/home_page.dart @@ -93,9 +93,9 @@ class HomePage extends StatelessWidget { padding: const EdgeInsets.all(4.0), decoration: const BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(16))), - child: Row( + child: const Row( mainAxisAlignment: MainAxisAlignment.center, - children: const [ + children: [ Icon(Icons.video_call_outlined, size: 48), SizedBox( width: 8, diff --git a/sample apps/bloc/pubspec.lock b/sample apps/bloc/pubspec.lock index 212a31894..d4d3d733e 100644 --- a/sample apps/bloc/pubspec.lock +++ b/sample apps/bloc/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" bloc: dependency: "direct main" description: @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" cupertino_icons: dependency: "direct main" description: @@ -113,7 +113,7 @@ packages: path: "../.." relative: true source: path - version: "1.6.0" + version: "1.7.0" http: dependency: "direct main" description: @@ -134,10 +134,10 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" lints: dependency: transitive description: @@ -150,10 +150,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -166,10 +166,10 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" nested: dependency: transitive description: @@ -182,10 +182,10 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" permission_handler: dependency: "direct main" description: @@ -299,10 +299,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" typed_data: dependency: transitive description: @@ -328,5 +328,5 @@ packages: source: hosted version: "0.2.2" sdks: - dart: ">=2.18.0 <3.0.0" - flutter: ">=2.8.0" + dart: ">=3.0.0-0 <4.0.0" + flutter: ">=2.10.0" diff --git a/sample apps/flutter-audio-room-quickstart/android/app/src/main/kotlin/com/example/flutter_audio_room_quickstart/MainActivity.kt b/sample apps/flutter-audio-room-quickstart/android/app/src/main/kotlin/com/example/flutter_audio_room_quickstart/MainActivity.kt index 9aa257d65..d35527b20 100644 --- a/sample apps/flutter-audio-room-quickstart/android/app/src/main/kotlin/com/example/flutter_audio_room_quickstart/MainActivity.kt +++ b/sample apps/flutter-audio-room-quickstart/android/app/src/main/kotlin/com/example/flutter_audio_room_quickstart/MainActivity.kt @@ -2,5 +2,4 @@ package com.example.flutter_audio_room_quickstart import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity : FlutterActivity() diff --git a/sample apps/flutter-audio-room-quickstart/pubspec.lock b/sample apps/flutter-audio-room-quickstart/pubspec.lock index 7bb80953e..3babe174b 100644 --- a/sample apps/flutter-audio-room-quickstart/pubspec.lock +++ b/sample apps/flutter-audio-room-quickstart/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" cupertino_icons: dependency: "direct main" description: @@ -87,10 +87,10 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" lints: dependency: transitive description: @@ -103,10 +103,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -119,18 +119,18 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" path: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" permission_handler: dependency: "direct main" description: @@ -228,10 +228,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" vector_math: dependency: transitive description: @@ -241,5 +241,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.19.2 <3.0.0" + dart: ">=3.0.0-0 <4.0.0" flutter: ">=2.8.0" diff --git a/sample apps/flutter-hls-quickstart/android/app/src/main/kotlin/com/example/flutter_hls_quickstart/MainActivity.kt b/sample apps/flutter-hls-quickstart/android/app/src/main/kotlin/com/example/flutter_hls_quickstart/MainActivity.kt index f8dc93a30..69fd8a679 100644 --- a/sample apps/flutter-hls-quickstart/android/app/src/main/kotlin/com/example/flutter_hls_quickstart/MainActivity.kt +++ b/sample apps/flutter-hls-quickstart/android/app/src/main/kotlin/com/example/flutter_hls_quickstart/MainActivity.kt @@ -2,5 +2,4 @@ package com.example.flutter_hls_quickstart import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity : FlutterActivity() diff --git a/sample apps/flutter-hls-quickstart/pubspec.lock b/sample apps/flutter-hls-quickstart/pubspec.lock index 8cbd914fc..af15a5a66 100644 --- a/sample apps/flutter-hls-quickstart/pubspec.lock +++ b/sample apps/flutter-hls-quickstart/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" csslib: dependency: transitive description: @@ -108,10 +108,10 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" lints: dependency: transitive description: @@ -124,10 +124,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -140,18 +140,18 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" path: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" permission_handler: dependency: "direct main" description: @@ -249,10 +249,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" vector_math: dependency: transitive description: @@ -302,5 +302,5 @@ packages: source: hosted version: "2.0.16" sdks: - dart: ">=2.19.2 <3.0.0" + dart: ">=3.0.0-0 <4.0.0" flutter: ">=3.3.0" diff --git a/sample apps/flutter-quickstart-app/android/app/src/main/kotlin/com/example/flutter_integration_guide/MainActivity.kt b/sample apps/flutter-quickstart-app/android/app/src/main/kotlin/com/example/flutter_integration_guide/MainActivity.kt index c5e536eb2..c6a8a7608 100644 --- a/sample apps/flutter-quickstart-app/android/app/src/main/kotlin/com/example/flutter_integration_guide/MainActivity.kt +++ b/sample apps/flutter-quickstart-app/android/app/src/main/kotlin/com/example/flutter_integration_guide/MainActivity.kt @@ -2,5 +2,4 @@ package com.example.flutter_integration_guide import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity : FlutterActivity() diff --git a/sample apps/flutter-quickstart-app/lib/main.dart b/sample apps/flutter-quickstart-app/lib/main.dart index a432d1680..5d9822d4b 100644 --- a/sample apps/flutter-quickstart-app/lib/main.dart +++ b/sample apps/flutter-quickstart-app/lib/main.dart @@ -268,6 +268,11 @@ class _MeetingPageState extends State // Checkout the docs for handling the updates regarding who is currently speaking here: https://www.100ms.live/docs/flutter/v2/how--to-guides/set-up-video-conferencing/render-video/show-audio-level } + @override + void onSessionStoreAvailable({HMSSessionStore? hmsSessionStore}) { + // Checkout the docs for sessions store here: https://www.100ms.live/docs/flutter/v2/how-to-guides/interact-with-room/room/session-store + } + @override Widget build(BuildContext context) { return WillPopScope( diff --git a/sample apps/flutter-quickstart-app/pubspec.lock b/sample apps/flutter-quickstart-app/pubspec.lock index 930f6d16f..d856677f9 100644 --- a/sample apps/flutter-quickstart-app/pubspec.lock +++ b/sample apps/flutter-quickstart-app/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" cupertino_icons: dependency: "direct main" description: @@ -79,18 +79,18 @@ packages: dependency: "direct main" description: name: hmssdk_flutter - sha256: "1323678b739bb0768adea396d618eed839c3c2c15eb135166988a06a92562baa" + sha256: bb96354acae74e734e80b775303eaf69a50c804c62fd34b21e254825a387a972 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.6.0" js: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" lints: dependency: transitive description: @@ -103,10 +103,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -119,18 +119,18 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" path: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" permission_handler: dependency: "direct main" description: @@ -228,10 +228,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" vector_math: dependency: transitive description: @@ -241,5 +241,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.18.4 <3.0.0" + dart: ">=3.0.0-0 <4.0.0" flutter: ">=2.8.0" diff --git a/sample apps/flutter-quickstart-app/pubspec.yaml b/sample apps/flutter-quickstart-app/pubspec.yaml index cca3ce3f2..fdc592431 100644 --- a/sample apps/flutter-quickstart-app/pubspec.yaml +++ b/sample apps/flutter-quickstart-app/pubspec.yaml @@ -3,7 +3,7 @@ description: A new Flutter project. # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -20,7 +20,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: '>=2.18.4 <3.0.0' + sdk: ">=2.18.4 <4.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -32,11 +32,10 @@ dependencies: flutter: sdk: flutter - # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 - hmssdk_flutter: 1.3.2 + hmssdk_flutter: 1.6.0 permission_handler: 10.2.0 dev_dependencies: @@ -55,7 +54,6 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. diff --git a/sample apps/getx/android/app/src/main/kotlin/com/example/demo_with_getx_and_100ms/MainActivity.kt b/sample apps/getx/android/app/src/main/kotlin/com/example/demo_with_getx_and_100ms/MainActivity.kt index 289782076..d38cacb32 100644 --- a/sample apps/getx/android/app/src/main/kotlin/com/example/demo_with_getx_and_100ms/MainActivity.kt +++ b/sample apps/getx/android/app/src/main/kotlin/com/example/demo_with_getx_and_100ms/MainActivity.kt @@ -1,19 +1,17 @@ package com.example.demo_with_getx_and_100ms -import io.flutter.embedding.android.FlutterActivity -import live.hms.hmssdk_flutter.HmssdkFlutterPlugin import android.app.Activity import android.content.Intent +import io.flutter.embedding.android.FlutterActivity import live.hms.hmssdk_flutter.Constants +import live.hms.hmssdk_flutter.HmssdkFlutterPlugin - -class MainActivity: FlutterActivity() { +class MainActivity : FlutterActivity() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { -super.onActivityResult(requestCode, resultCode, data) + super.onActivityResult(requestCode, resultCode, data) - if (requestCode == Constants.SCREEN_SHARE_INTENT_REQUEST_CODE && resultCode == Activity.RESULT_OK){ - HmssdkFlutterPlugin.hmssdkFlutterPlugin?.requestScreenShare(data) + if (requestCode == Constants.SCREEN_SHARE_INTENT_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + HmssdkFlutterPlugin.hmssdkFlutterPlugin?.requestScreenShare(data) + } } - -} } diff --git a/sample apps/getx/pubspec.lock b/sample apps/getx/pubspec.lock index e6166d449..e9d139d66 100644 --- a/sample apps/getx/pubspec.lock +++ b/sample apps/getx/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" charcode: dependency: transitive description: @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" cupertino_icons: dependency: "direct main" description: @@ -127,10 +127,10 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" lints: dependency: transitive description: @@ -143,10 +143,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -159,18 +159,18 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" path: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" permission_handler: dependency: "direct main" description: @@ -268,10 +268,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" typed_data: dependency: transitive description: @@ -297,5 +297,5 @@ packages: source: hosted version: "0.2.2" sdks: - dart: ">=2.18.0 <3.0.0" + dart: ">=3.0.0-0 <4.0.0" flutter: ">=2.8.0" diff --git a/sample apps/hms-callkit-app/README.md b/sample apps/hms-callkit-app/README.md new file mode 100644 index 000000000..f56d3562e --- /dev/null +++ b/sample apps/hms-callkit-app/README.md @@ -0,0 +1,78 @@ +# One To One Call Application + +A sample project for calling made with 100ms and flutter_callkit_incoming. + +https://github.com/Decoder07/hms-callkit-demo/assets/93931528/69bd9d0f-65e9-46cd-a50c-4ac7ea9a6446 + +## Getting Started + +- Clone the repo +- Run flutter pub get +- Setup 100ms token service +- Setup firebase service for notifications + +That's it now to run the project execute `flutter run` + +## How to test + +To test the application install the app in two devices. + +- Copy the code(FCM token) from one device. Let's call it Device-1 +- Paste this token on different device. Let's call this Device-2 +- Press the Call Now button on Device-1 +- You will receive a notification on Device-2 +- Accept the call on Device-2 + +### Setup 100ms token service + +100ms token service takes care of joining room once you receive a call or you wish to call someone. +We will need an authentication token to join the room which we will also send to other peer through payload by which the receiver can also join the room. You can find the code for this in `join_service.dart`. + +Here's the code for this: + +```dart +Future getAuthToken({required String roomId,required String tokenEndpoint,required String userId,required String role}) async { + Uri endPoint = Uri.parse( + tokenEndpoint); + http.Response response = await http.post(endPoint, + body: {'user_id': userId, 'room_id': roomId, 'role': role}); + var body = json.decode(response.body); + return body['token']; +} +``` + +`getAuthToken` returns the authentication token which we will use for joining the room and also share with the receiver for him to join the room. + +Let's understand the parameters of this function: + +- roomId + +`roomId` refers to the room which you wish to join. You can find the roomId in dashboard's rooms section. + +- tokenEndpoint + +`tokenEndpoint` is the url which is used to get the authentication token. You can find the `tokenEndpoint` in developer section of 100ms dashboard. + +- userId + +`userId` can be used to uniquely identify user to perform any specific actions on that user later on. + +- role + +`role` refers the role which you wish to join the room. Ensure that the given role is present in the room template for given roomId. + +### Setup firebase service for notifications + +First create a project on firebase. You can find the steps [here](https://medium.com/enappd/adding-firebase-to-your-flutter-app-281b8f391b47) + +Since we will be using firebase messaging to deliver notifications ensure that you have a `blaze plan` enabled on firebase and please enable `cloud messaging` and `Firebase Cloud Messaging API` from firebase cloud console. + +![cloud-console](https://user-images.githubusercontent.com/93931528/218379651-d35036ff-98f2-4b6c-a298-4a229d3326b7.jpeg) + +For setting up firebase notifications please follow [this](https://quickcoder.org/flutter-push-notifications/) + +The repo already contains a `functions` folder which has the firebase functions so you can directly deploy them. + +That's it you are all set for running the application. + +Have any issues. Please reach out to us over [discord](https://100ms.live/discord) diff --git a/sample apps/hms-callkit-app/analysis_options.yaml b/sample apps/hms-callkit-app/analysis_options.yaml new file mode 100644 index 000000000..fd16f9219 --- /dev/null +++ b/sample apps/hms-callkit-app/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/sample apps/hms-callkit-app/android/.gitignore b/sample apps/hms-callkit-app/android/.gitignore new file mode 100644 index 000000000..d03bb8d48 --- /dev/null +++ b/sample apps/hms-callkit-app/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +google-services.json +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/sample apps/hms-callkit-app/android/app/build.gradle b/sample apps/hms-callkit-app/android/app/build.gradle new file mode 100644 index 000000000..dca0498c4 --- /dev/null +++ b/sample apps/hms-callkit-app/android/app/build.gradle @@ -0,0 +1,74 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'com.google.gms.google-services' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 33 + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.hms_callkit" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion 21 + targetSdkVersion 33 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation platform('com.google.firebase:firebase-bom:31.2.0') + implementation 'com.google.firebase:firebase-analytics-ktx' +} diff --git a/sample apps/hms-callkit-app/android/app/src/debug/AndroidManifest.xml b/sample apps/hms-callkit-app/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..c380ad5a7 --- /dev/null +++ b/sample apps/hms-callkit-app/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/sample apps/hms-callkit-app/android/app/src/main/AndroidManifest.xml b/sample apps/hms-callkit-app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..18e128e1e --- /dev/null +++ b/sample apps/hms-callkit-app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/sample apps/hms-callkit-app/android/app/src/main/kotlin/com/example/hms_callkit/MainActivity.kt b/sample apps/hms-callkit-app/android/app/src/main/kotlin/com/example/hms_callkit/MainActivity.kt new file mode 100644 index 000000000..8c590ff72 --- /dev/null +++ b/sample apps/hms-callkit-app/android/app/src/main/kotlin/com/example/hms_callkit/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.hms_callkit + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/sample apps/hms-callkit-app/android/app/src/main/res/drawable-v21/launch_background.xml b/sample apps/hms-callkit-app/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/sample apps/hms-callkit-app/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/sample apps/hms-callkit-app/android/app/src/main/res/drawable/launch_background.xml b/sample apps/hms-callkit-app/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/sample apps/hms-callkit-app/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..64f764c13 Binary files /dev/null and b/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..5aa38115a Binary files /dev/null and b/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..c7142758e Binary files /dev/null and b/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..5f6c23978 Binary files /dev/null and b/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..d65690717 Binary files /dev/null and b/sample apps/hms-callkit-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/sample apps/hms-callkit-app/android/app/src/main/res/values-night/styles.xml b/sample apps/hms-callkit-app/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/sample apps/hms-callkit-app/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/sample apps/hms-callkit-app/android/app/src/main/res/values/styles.xml b/sample apps/hms-callkit-app/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..cb1ef8805 --- /dev/null +++ b/sample apps/hms-callkit-app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/sample apps/hms-callkit-app/android/app/src/profile/AndroidManifest.xml b/sample apps/hms-callkit-app/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..c380ad5a7 --- /dev/null +++ b/sample apps/hms-callkit-app/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/sample apps/hms-callkit-app/android/build.gradle b/sample apps/hms-callkit-app/android/build.gradle new file mode 100644 index 000000000..a08d0dfc1 --- /dev/null +++ b/sample apps/hms-callkit-app/android/build.gradle @@ -0,0 +1,32 @@ +buildscript { + ext.kotlin_version = '+' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.2.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.google.gms:google-services:4.3.15' + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/sample apps/hms-callkit-app/android/gradle.properties b/sample apps/hms-callkit-app/android/gradle.properties new file mode 100644 index 000000000..94adc3a3f --- /dev/null +++ b/sample apps/hms-callkit-app/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/sample apps/hms-callkit-app/android/gradle/wrapper/gradle-wrapper.properties b/sample apps/hms-callkit-app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..3c472b99c --- /dev/null +++ b/sample apps/hms-callkit-app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/sample apps/hms-callkit-app/android/settings.gradle b/sample apps/hms-callkit-app/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/sample apps/hms-callkit-app/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/sample apps/hms-callkit-app/firebase.json b/sample apps/hms-callkit-app/firebase.json new file mode 100644 index 000000000..aab022add --- /dev/null +++ b/sample apps/hms-callkit-app/firebase.json @@ -0,0 +1,15 @@ +{ + "functions": [ + { + "source": "functions", + "codebase": "default", + "ignore": [ + "node_modules", + ".git", + "firebase-debug.log", + "firebase-debug.*.log" + ], + "predeploy": [] + } + ] +} diff --git a/sample apps/hms-callkit-app/functions/.eslintrc.js b/sample apps/hms-callkit-app/functions/.eslintrc.js new file mode 100644 index 000000000..cf232ad57 --- /dev/null +++ b/sample apps/hms-callkit-app/functions/.eslintrc.js @@ -0,0 +1,11 @@ +module.exports = { + root: true, + env: { + es6: true, + node: true, + }, + extends: ["eslint:recommended", "google"], + rules: { + quotes: ["error", "double"], + }, +}; diff --git a/sample apps/hms-callkit-app/functions/.gitignore b/sample apps/hms-callkit-app/functions/.gitignore new file mode 100644 index 000000000..40b878db5 --- /dev/null +++ b/sample apps/hms-callkit-app/functions/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/sample apps/hms-callkit-app/functions/index.js b/sample apps/hms-callkit-app/functions/index.js new file mode 100644 index 000000000..e36e7bba0 --- /dev/null +++ b/sample apps/hms-callkit-app/functions/index.js @@ -0,0 +1,26 @@ +const functions = require("firebase-functions"); +const admin = require("firebase-admin"); + +admin.initializeApp(); + +const messaging = admin.messaging(); + +exports.notifySubscribers = functions.https.onCall(async (data, _) => { + try { + console.log(data.targetDevices); + await messaging.sendToDevice(data.targetDevices, { + notification: { + title: data.messageTitle, + body: data.messageBody, + }, + data: { + params: data.callkitParams, + }, + }); + + return true; + } catch (ex) { + console.log(ex); + return false; + } +}); diff --git a/sample apps/hms-callkit-app/functions/package-lock.json b/sample apps/hms-callkit-app/functions/package-lock.json new file mode 100644 index 000000000..56381c927 --- /dev/null +++ b/sample apps/hms-callkit-app/functions/package-lock.json @@ -0,0 +1,3307 @@ +{ + "name": "functions", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "functions", + "dependencies": { + "firebase-admin": "^10.0.2", + "firebase-functions": "^3.18.0" + }, + "devDependencies": { + "eslint": "^8.9.0", + "eslint-config-google": "^0.14.0", + "firebase-functions-test": "^0.2.0" + }, + "engines": { + "node": "16" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@fastify/busboy": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", + "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", + "dependencies": { + "text-decoding": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==", + "peer": true + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", + "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", + "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", + "dependencies": { + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", + "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", + "dependencies": { + "@firebase/auth-interop-types": "0.1.7", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", + "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/database": "0.13.10", + "@firebase/database-types": "0.9.17", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", + "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", + "dependencies": { + "@firebase/app-types": "0.8.1", + "@firebase/util": "1.7.3" + } + }, + "node_modules/@firebase/database-types/node_modules/@firebase/app-types": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", + "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==" + }, + "node_modules/@firebase/logger": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", + "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", + "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@google-cloud/firestore": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", + "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^2.24.1", + "protobufjs": "^6.8.6" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", + "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", + "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", + "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/storage": { + "version": "5.20.5", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.5.tgz", + "integrity": "sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw==", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "abort-controller": "^3.0.0", + "arrify": "^2.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "configstore": "^5.0.0", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "google-auth-library": "^7.14.1", + "hash-stream-validation": "^0.2.2", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "pumpify": "^2.0.0", + "retry-request": "^4.2.2", + "stream-events": "^1.0.4", + "teeny-request": "^7.1.3", + "uuid": "^8.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.12.tgz", + "integrity": "sha512-JmvQ03OTSpVd9JTlj/K3IWHSz4Gk/JMLUTtW7Zb0KvO1LcOYGATh5cNuRYzCAeDR3O8wq+q8FZe97eO9MBrkUw==", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", + "optional": true, + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.2.tgz", + "integrity": "sha512-++PrQIjrom+bFDPpfmqXfAGSQs40116JRrqqyf53dymUMvvb5d/LMRyicRoF1AUKoXVS1/IgJXlEgcpr4gTF3Q==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", + "optional": true + }, + "node_modules/@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "optional": true, + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "optional": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "optional": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "optional": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "optional": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "optional": true + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "optional": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "optional": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "optional": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "optional": true + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", + "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.33", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", + "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz", + "integrity": "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.14.191", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", + "dev": true + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "optional": true + }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" + }, + "node_modules/@types/node": { + "version": "18.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", + "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "devOptional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "optional": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "optional": true, + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "optional": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "optional": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "optional": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", + "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-google": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", + "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "devOptional": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", + "optional": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/firebase-admin": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-10.3.0.tgz", + "integrity": "sha512-A0wgMLEjyVyUE+heyMJYqHRkPVjpebhOYsa47RHdrTM4ltApcx8Tn86sUmjqxlfh09gNnILAm7a8q5+FmgBYpg==", + "dependencies": { + "@fastify/busboy": "^1.1.0", + "@firebase/database-compat": "^0.2.0", + "@firebase/database-types": "^0.9.7", + "@types/node": ">=12.12.47", + "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^2.0.2", + "node-forge": "^1.3.1", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^4.15.1", + "@google-cloud/storage": "^5.18.3" + } + }, + "node_modules/firebase-functions": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.24.1.tgz", + "integrity": "sha512-GYhoyOV0864HFMU1h/JNBXYNmDk2MlbvU7VO/5qliHX6u/6vhSjTJjlyCG4leDEI8ew8IvmkIC5QquQ1U8hAuA==", + "dependencies": { + "@types/cors": "^2.8.5", + "@types/express": "4.17.3", + "cors": "^2.8.5", + "express": "^4.17.1", + "lodash": "^4.17.14", + "node-fetch": "^2.6.7" + }, + "bin": { + "firebase-functions": "lib/bin/firebase-functions.js" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + }, + "peerDependencies": { + "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + } + }, + "node_modules/firebase-functions-test": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-0.2.3.tgz", + "integrity": "sha512-zYX0QTm53wCazuej7O0xqbHl90r/v1PTXt/hwa0jo1YF8nDM+iBKnLDlkIoW66MDd0R6aGg4BvKzTTdJpvigUA==", + "dev": true, + "dependencies": { + "@types/lodash": "^4.14.104", + "lodash": "^4.17.5" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "firebase-admin": ">=6.0.0", + "firebase-functions": ">=2.0.0" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "optional": true + }, + "node_modules/gaxios": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", + "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", + "optional": true, + "dependencies": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gcp-metadata": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "optional": true, + "dependencies": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "optional": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-auth-library": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", + "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-gax": { + "version": "2.30.5", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz", + "integrity": "sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ==", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "~1.6.0", + "@grpc/proto-loader": "^0.6.12", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^7.14.0", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^0.1.8", + "protobufjs": "6.11.3", + "retry-request": "^4.0.0" + }, + "bin": { + "compileProtos": "build/tools/compileProtos.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-p12-pem": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", + "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", + "optional": true, + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "optional": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/gtoken": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", + "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", + "optional": true, + "dependencies": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-stream-validation": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", + "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", + "optional": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "devOptional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "optional": true + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "optional": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jose": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz", + "integrity": "sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg==", + "dependencies": { + "@panva/asn1.js": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0 < 13 || >=13.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "optional": true, + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.1.5.tgz", + "integrity": "sha512-IODtn1SwEm7n6GQZnQLY0oxKDrMh7n/jRH1MzE8mlxWMrh2NnMyOsXTebu8vJ1qCpmuTJcL4DdiE0E4h8jnwsA==", + "dependencies": { + "@types/express": "^4.17.14", + "@types/jsonwebtoken": "^8.5.9", + "debug": "^4.3.4", + "jose": "^2.0.6", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "engines": { + "node": ">=10 < 13 || >=14" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "optional": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "optional": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz", + "integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "devOptional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "devOptional": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proto3-json-serializer": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", + "integrity": "sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ==", + "optional": true, + "dependencies": { + "protobufjs": "^6.11.2" + } + }, + "node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "optional": true, + "dependencies": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", + "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", + "optional": true, + "dependencies": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "optional": true + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "optional": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "optional": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/teeny-request": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz", + "integrity": "sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw==", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "optional": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "optional": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "optional": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "devOptional": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "optional": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "devOptional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/sample apps/hms-callkit-app/functions/package.json b/sample apps/hms-callkit-app/functions/package.json new file mode 100644 index 000000000..70089faf0 --- /dev/null +++ b/sample apps/hms-callkit-app/functions/package.json @@ -0,0 +1,26 @@ +{ + "name": "functions", + "description": "Cloud Functions for Firebase", + "scripts": { + "lint": "eslint .", + "serve": "firebase emulators:start --only functions", + "shell": "firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log" + }, + "engines": { + "node": "16" + }, + "main": "index.js", + "dependencies": { + "firebase-admin": "^10.0.2", + "firebase-functions": "^3.18.0" + }, + "devDependencies": { + "eslint": "^8.9.0", + "eslint-config-google": "^0.14.0", + "firebase-functions-test": "^0.2.0" + }, + "private": true +} diff --git a/sample apps/hms-callkit-app/ios/.gitignore b/sample apps/hms-callkit-app/ios/.gitignore new file mode 100644 index 000000000..b4188efa3 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/.gitignore @@ -0,0 +1,36 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* +firebase_app_id_file.json +GoogleService-info.plist + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/sample apps/hms-callkit-app/ios/Flutter/AppFrameworkInfo.plist b/sample apps/hms-callkit-app/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..9625e105d --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/sample apps/hms-callkit-app/ios/Flutter/Debug.xcconfig b/sample apps/hms-callkit-app/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..ec97fc6f3 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/sample apps/hms-callkit-app/ios/Flutter/Release.xcconfig b/sample apps/hms-callkit-app/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..c4855bfe2 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/sample apps/hms-callkit-app/ios/Podfile b/sample apps/hms-callkit-app/ios/Podfile new file mode 100644 index 000000000..2c068c404 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/sample apps/hms-callkit-app/ios/Podfile.lock b/sample apps/hms-callkit-app/ios/Podfile.lock new file mode 100644 index 000000000..42e34a63f --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Podfile.lock @@ -0,0 +1,191 @@ +PODS: + - cloud_functions (4.0.8): + - Firebase/Functions (= 10.3.0) + - firebase_core + - Flutter + - CryptoSwift (1.6.0) + - Firebase/CoreOnly (10.3.0): + - FirebaseCore (= 10.3.0) + - Firebase/Functions (10.3.0): + - Firebase/CoreOnly + - FirebaseFunctions (~> 10.3.0) + - Firebase/Messaging (10.3.0): + - Firebase/CoreOnly + - FirebaseMessaging (~> 10.3.0) + - firebase_core (2.5.0): + - Firebase/CoreOnly (= 10.3.0) + - Flutter + - firebase_messaging (14.2.2): + - Firebase/Messaging (= 10.3.0) + - firebase_core + - Flutter + - FirebaseAppCheckInterop (10.5.0) + - FirebaseAuthInterop (10.5.0) + - FirebaseCore (10.3.0): + - FirebaseCoreInternal (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/Logger (~> 7.8) + - FirebaseCoreExtension (10.5.0): + - FirebaseCore (~> 10.0) + - FirebaseCoreInternal (10.5.0): + - "GoogleUtilities/NSData+zlib (~> 7.8)" + - FirebaseFunctions (10.3.0): + - FirebaseAppCheckInterop (~> 10.0) + - FirebaseAuthInterop (~> 10.0) + - FirebaseCore (~> 10.0) + - FirebaseCoreExtension (~> 10.0) + - FirebaseMessagingInterop (~> 10.0) + - FirebaseSharedSwift (~> 10.0) + - GTMSessionFetcher/Core (< 4.0, >= 2.1) + - FirebaseInstallations (10.5.0): + - FirebaseCore (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/UserDefaults (~> 7.8) + - PromisesObjC (~> 2.1) + - FirebaseMessaging (10.3.0): + - FirebaseCore (~> 10.0) + - FirebaseInstallations (~> 10.0) + - GoogleDataTransport (~> 9.2) + - GoogleUtilities/AppDelegateSwizzler (~> 7.8) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/Reachability (~> 7.8) + - GoogleUtilities/UserDefaults (~> 7.8) + - nanopb (< 2.30910.0, >= 2.30908.0) + - FirebaseMessagingInterop (10.5.0) + - FirebaseSharedSwift (10.5.0) + - Flutter (1.0.0) + - flutter_callkit_incoming (0.0.1): + - CryptoSwift + - Flutter + - GoogleDataTransport (9.2.1): + - GoogleUtilities/Environment (~> 7.7) + - nanopb (< 2.30910.0, >= 2.30908.0) + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/AppDelegateSwizzler (7.11.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (7.11.0): + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/Logger (7.11.0): + - GoogleUtilities/Environment + - GoogleUtilities/Network (7.11.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (7.11.0)" + - GoogleUtilities/Reachability (7.11.0): + - GoogleUtilities/Logger + - GoogleUtilities/UserDefaults (7.11.0): + - GoogleUtilities/Logger + - GTMSessionFetcher/Core (3.1.0) + - HMSBroadcastExtensionSDK (0.0.7) + - HMSSDK (0.6.2): + - HMSWebRTC (= 1.0.5113) + - hmssdk_flutter (1.3.0): + - Flutter + - HMSBroadcastExtensionSDK (= 0.0.7) + - HMSSDK (= 0.6.2) + - HMSWebRTC (1.0.5113) + - nanopb (2.30909.0): + - nanopb/decode (= 2.30909.0) + - nanopb/encode (= 2.30909.0) + - nanopb/decode (2.30909.0) + - nanopb/encode (2.30909.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - permission_handler_apple (9.0.4): + - Flutter + - PromisesObjC (2.1.1) + - share_plus (0.0.1): + - Flutter + +DEPENDENCIES: + - cloud_functions (from `.symlinks/plugins/cloud_functions/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) + - Flutter (from `Flutter`) + - flutter_callkit_incoming (from `.symlinks/plugins/flutter_callkit_incoming/ios`) + - hmssdk_flutter (from `.symlinks/plugins/hmssdk_flutter/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - share_plus (from `.symlinks/plugins/share_plus/ios`) + +SPEC REPOS: + trunk: + - CryptoSwift + - Firebase + - FirebaseAppCheckInterop + - FirebaseAuthInterop + - FirebaseCore + - FirebaseCoreExtension + - FirebaseCoreInternal + - FirebaseFunctions + - FirebaseInstallations + - FirebaseMessaging + - FirebaseMessagingInterop + - FirebaseSharedSwift + - GoogleDataTransport + - GoogleUtilities + - GTMSessionFetcher + - HMSBroadcastExtensionSDK + - HMSSDK + - HMSWebRTC + - nanopb + - PromisesObjC + +EXTERNAL SOURCES: + cloud_functions: + :path: ".symlinks/plugins/cloud_functions/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + firebase_messaging: + :path: ".symlinks/plugins/firebase_messaging/ios" + Flutter: + :path: Flutter + flutter_callkit_incoming: + :path: ".symlinks/plugins/flutter_callkit_incoming/ios" + hmssdk_flutter: + :path: ".symlinks/plugins/hmssdk_flutter/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/ios" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" + share_plus: + :path: ".symlinks/plugins/share_plus/ios" + +SPEC CHECKSUMS: + cloud_functions: a2726f70f64877eb8ba47e3bd0e4e52f6e8bfc8e + CryptoSwift: 562f8eceb40e80796fffc668b0cad9313284cfa6 + Firebase: f92fc551ead69c94168d36c2b26188263860acd9 + firebase_core: f95c8bbe65213d406d592573ad42a12d64849cb8 + firebase_messaging: 3daef9f9ee5b91de2f282895ec91cc5e5ca78556 + FirebaseAppCheckInterop: 95bc238f8755d597cad95815b3c448f8617f720f + FirebaseAuthInterop: e1a53afba01599095100f92d6d3ecb75da6b5fa8 + FirebaseCore: 988754646ab3bd4bdcb740f1bfe26b9f6c0d5f2a + FirebaseCoreExtension: d9fa427f1ae1edccf2368ce5e8d567e4c1f0ebc8 + FirebaseCoreInternal: e463f41bb935cd049505bf7e9a5bdd7dcea90df6 + FirebaseFunctions: d8415d2237cc807d05fa0a921d645f50a0d9d803 + FirebaseInstallations: 935bc4abb6f7a035cab7a0c31cb777b2be3dd254 + FirebaseMessaging: e345b219fd15d325f0cf2fef28cb8ce00d851b3f + FirebaseMessagingInterop: 1fc10aa48ca7bd70747d8211190890dccdfb9c65 + FirebaseSharedSwift: 30289ed76ccb441067a2cd3472cc5b2f491b9d8f + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + flutter_callkit_incoming: 417dd1b46541cdd5d855ad795ccbe97d1c18155e + GoogleDataTransport: ea169759df570f4e37bdee1623ec32a7e64e67c4 + GoogleUtilities: c2bdc4cf2ce786c4d2e6b3bcfd599a25ca78f06f + GTMSessionFetcher: c9e714f7eec91a55641e2bab9f45fd83a219b882 + HMSBroadcastExtensionSDK: d22df4b2c22843efe4a1779478889076db8eaabd + HMSSDK: c995b000287512ae30cba17605f5dd0cec7cd994 + hmssdk_flutter: 0b06e99ae9fd36a43777b37485096397e75d7091 + HMSWebRTC: 6bd851709766bf4b28fd89046a65654e86741aed + nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 + path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 + permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce + PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb + share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 + +PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048 + +COCOAPODS: 1.11.3 diff --git a/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.pbxproj b/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..9c4039b1c --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,569 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 5591585E1B195DA41EDD827C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C62DE3DB7AA5EAF48460307E /* Pods_Runner.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + B41EAFA62994C8C500A77921 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B41EAFA52994C8C500A77921 /* GoogleService-Info.plist */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1492280945658C3943234759 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 69DD47EE60C537104AF5C2BC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B41EAFA52994C8C500A77921 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + B41EAFB42994DAC600A77921 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + C62DE3DB7AA5EAF48460307E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E4A18052FF88149A1C2D748E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5591585E1B195DA41EDD827C /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + B41EAFA52994C8C500A77921 /* GoogleService-Info.plist */, + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + BEB3B576775832673B7AC62F /* Pods */, + 9F08784B10B35CE030B38E70 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + B41EAFB42994DAC600A77921 /* Runner.entitlements */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 9F08784B10B35CE030B38E70 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C62DE3DB7AA5EAF48460307E /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + BEB3B576775832673B7AC62F /* Pods */ = { + isa = PBXGroup; + children = ( + 69DD47EE60C537104AF5C2BC /* Pods-Runner.debug.xcconfig */, + 1492280945658C3943234759 /* Pods-Runner.release.xcconfig */, + E4A18052FF88149A1C2D748E /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + FA0F422EAE174B1811F4111E /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 0CD5B6BF545EDDAD81CC48BB /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + packageProductDependencies = ( + ); + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + ); + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + B41EAFA62994C8C500A77921 /* GoogleService-Info.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0CD5B6BF545EDDAD81CC48BB /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + FA0F422EAE174B1811F4111E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 5N85PP82A9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.hmsCallkit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 5N85PP82A9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.hmsCallkit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 5N85PP82A9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.hmsCallkit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..d2aa88575 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,113 @@ +{ + "pins" : [ + { + "identity" : "abseil-cpp-swiftpm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git", + "state" : { + "revision" : "583de9bd60f66b40e78d08599cc92036c2e7e4e1", + "version" : "0.20220203.2" + } + }, + { + "identity" : "boringssl-swiftpm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/boringssl-SwiftPM.git", + "state" : { + "revision" : "dd3eda2b05a3f459fc3073695ad1b28659066eab", + "version" : "0.9.1" + } + }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk", + "state" : { + "branch" : "master", + "revision" : "8c684750bf32b71bc81e15c87e0cd284dca43c2b" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "9a09ece724128e8d1e14c5133b87c0e236844ac0", + "version" : "10.4.0" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "f6b558e3f801f2cac336b04f615ce111fa9ddaa0", + "version" : "9.2.1" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "0543562f85620b5b7c510c6bcbef75b562a5127b", + "version" : "7.11.0" + } + }, + { + "identity" : "grpc-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/grpc/grpc-ios.git", + "state" : { + "revision" : "8440b914756e0d26d4f4d054a1c1581daedfc5b6", + "version" : "1.44.3-grpc" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "96d7cc73a71ce950723aa3c50ce4fb275ae180b8", + "version" : "3.1.0" + } + }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", + "version" : "1.22.2" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692", + "version" : "2.30909.0" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "3e4e743631e86c8c70dbc6efdc7beaa6e90fd3bb", + "version" : "2.1.1" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "ab3a58b7209a17d781c0d1dbb3e1ff3da306bae8", + "version" : "1.20.3" + } + } + ], + "version" : 2 +} diff --git a/sample apps/hms-callkit-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/sample apps/hms-callkit-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..c87d15a33 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample apps/hms-callkit-app/ios/Runner.xcworkspace/contents.xcworkspacedata b/sample apps/hms-callkit-app/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/sample apps/hms-callkit-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/sample apps/hms-callkit-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/sample apps/hms-callkit-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/sample apps/hms-callkit-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/sample apps/hms-callkit-app/ios/Runner/AppDelegate.swift b/sample apps/hms-callkit-app/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..bf7ef7ad5 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..e882ab988 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images": [ + { + "size": "20x20", + "idiom": "iphone", + "filename": "Icon-App-20x20@2x.png", + "scale": "2x" + }, + { + "size": "20x20", + "idiom": "iphone", + "filename": "Icon-App-20x20@3x.png", + "scale": "3x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "Icon-App-29x29@1x.png", + "scale": "1x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "Icon-App-29x29@2x.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "Icon-App-29x29@3x.png", + "scale": "3x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "Icon-App-40x40@2x.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "Icon-App-40x40@3x.png", + "scale": "3x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "Icon-App-60x60@2x.png", + "scale": "2x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "Icon-App-60x60@3x.png", + "scale": "3x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "Icon-App-20x20@1x.png", + "scale": "1x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "Icon-App-20x20@2x.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "Icon-App-29x29@1x.png", + "scale": "1x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "Icon-App-29x29@2x.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "Icon-App-40x40@1x.png", + "scale": "1x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "Icon-App-40x40@2x.png", + "scale": "2x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "Icon-App-76x76@1x.png", + "scale": "1x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "Icon-App-76x76@2x.png", + "scale": "2x" + }, + { + "size": "83.5x83.5", + "idiom": "ipad", + "filename": "Icon-App-83.5x83.5@2x.png", + "scale": "2x" + }, + { + "size": "1024x1024", + "idiom": "ios-marketing", + "filename": "Icon-App-1024x1024@1x.png", + "scale": "1x" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..6b3f08fb3 Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..7353c41ec Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..96b3bb0ee Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..6ed2d933e Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4a1a34e1e Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..fe730945a Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..abc1056df Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..96b3bb0ee Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..3a337733a Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..c010541e1 Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..cda9be009 Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..1148cc633 Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..0467bf12a Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..781d7cdca --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "idiom": "universal", + "filename": "LaunchImage.png", + "scale": "1x" + }, + { + "idiom": "universal", + "filename": "LaunchImage@2x.png", + "scale": "2x" + }, + { + "idiom": "universal", + "filename": "LaunchImage@3x.png", + "scale": "3x" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..b5b843ad3 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. diff --git a/sample apps/hms-callkit-app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/sample apps/hms-callkit-app/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample apps/hms-callkit-app/ios/Runner/Base.lproj/Main.storyboard b/sample apps/hms-callkit-app/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample apps/hms-callkit-app/ios/Runner/Info.plist b/sample apps/hms-callkit-app/ios/Runner/Info.plist new file mode 100644 index 000000000..427514624 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Hms Callkit + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + hms_callkit + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/sample apps/hms-callkit-app/ios/Runner/Runner-Bridging-Header.h b/sample apps/hms-callkit-app/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/sample apps/hms-callkit-app/ios/Runner/Runner.entitlements b/sample apps/hms-callkit-app/ios/Runner/Runner.entitlements new file mode 100644 index 000000000..903def2af --- /dev/null +++ b/sample apps/hms-callkit-app/ios/Runner/Runner.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/sample apps/hms-callkit-app/lib/app_navigation/app_router.dart b/sample apps/hms-callkit-app/lib/app_navigation/app_router.dart new file mode 100644 index 000000000..5c6ed4915 --- /dev/null +++ b/sample apps/hms-callkit-app/lib/app_navigation/app_router.dart @@ -0,0 +1,43 @@ +//Package imports + +import 'package:flutter/material.dart'; +import 'package:hms_callkit/home_page.dart'; +import 'package:hms_callkit/hmssdk/meeting_page.dart'; +import 'package:hms_callkit/hmssdk/preview_page.dart'; +import 'package:hms_callkit/receive_call.dart'; + +class AppRoute { + static const homePage = '/home_page'; + + static const callingPage = '/meeting_page'; + static const previewPage = '/preview_page'; + static const receiveCallPage = '/receive-call-page'; + + static Route? generateRoute(RouteSettings settings) { + switch (settings.name) { + case homePage: + return MaterialPageRoute( + builder: (_) => const HomePage(), settings: settings); + case callingPage: + return MaterialPageRoute( + builder: (_) => MeetingPage( + authToken: settings.arguments as String?, + userName: 'Test User', + ), + settings: settings); + case previewPage: + return MaterialPageRoute( + builder: (_) => PreviewPage( + authToken: settings.arguments as String?, + userName: 'Test User', + ), + settings: settings); + case receiveCallPage: + return MaterialPageRoute( + builder: (_) => ReceiveCall( + callKitParams: settings.arguments as Map)); + default: + return null; + } + } +} diff --git a/sample apps/hms-callkit-app/lib/app_navigation/navigation_service.dart b/sample apps/hms-callkit-app/lib/app_navigation/navigation_service.dart new file mode 100644 index 000000000..f2588071d --- /dev/null +++ b/sample apps/hms-callkit-app/lib/app_navigation/navigation_service.dart @@ -0,0 +1,119 @@ +//Package imports +import 'package:flutter/material.dart'; + +class NavigationService { + // Global navigation key for whole application + GlobalKey navigationKey = GlobalKey(); + + /// Get app context + BuildContext? get appContext => navigationKey.currentContext; + + /// App route observer + RouteObserver> routeObserver = RouteObserver>(); + + static final NavigationService _instance = NavigationService._private(); + factory NavigationService() { + return _instance; + } + NavigationService._private(); + + static NavigationService get instance => _instance; + + /// Pushing new page into navigation stack + /// + /// `routeName` is page's route name defined in [AppRoute] + /// `args` is optional data to be sent to new page + Future pushNamed(String routeName, + {Object? args}) async { + print(navigationKey); + print(navigationKey.currentState); + return navigationKey.currentState?.pushNamed( + routeName, + arguments: args, + ); + } + + Future pushNamedIfNotCurrent(String routeName, + {Object? args}) async { + if (!isCurrent(routeName)) { + return pushNamed(routeName, args: args); + } + return null; + } + + bool isCurrent(String routeName) { + bool isCurrent = false; + navigationKey.currentState!.popUntil((route) { + if (route.settings.name == routeName) { + isCurrent = true; + } + return true; + }); + return isCurrent; + } + + /// Pushing new page into navigation stack + /// + /// `route` is route generator + Future push(Route route) async { + return navigationKey.currentState?.push(route); + } + + /// Replace the current route of the navigator by pushing the given route and + /// then disposing the previous route once the new route has finished + /// animating in. + Future pushReplacementNamed( + String routeName, + {Object? args}) async { + return navigationKey.currentState?.pushReplacementNamed( + routeName, + arguments: args, + ); + } + + /// Push the route with the given name onto the navigator, and then remove all + /// the previous routes until the `predicate` returns true. + Future pushNamedAndRemoveUntil( + String routeName, { + Object? args, + bool Function(Route)? predicate, + }) async { + return navigationKey.currentState?.pushNamedAndRemoveUntil( + routeName, + predicate ?? (_) => false, + arguments: args, + ); + } + + /// Push the given route onto the navigator, and then remove all the previous + /// routes until the `predicate` returns true. + Future pushAndRemoveUntil( + Route route, { + bool Function(Route)? predicate, + }) async { + return navigationKey.currentState?.pushAndRemoveUntil( + route, + predicate ?? (_) => false, + ); + } + + /// Consults the current route's [Route.willPop] method, and acts accordingly, + /// potentially popping the route as a result; returns whether the pop request + /// should be considered handled. + Future maybePop([Object? args]) async { + return navigationKey.currentState!.maybePop(args as T); + } + + /// Whether the navigator can be popped. + bool canPop() => navigationKey.currentState!.canPop(); + + /// Pop the top-most route off the navigator. + void goBack({T? result}) { + navigationKey.currentState?.pop(result); + } + + /// Calls [pop] repeatedly until the predicate returns true. + void popUntil(String route) { + navigationKey.currentState!.popUntil(ModalRoute.withName(route)); + } +} diff --git a/sample apps/hms-callkit-app/lib/firebase_options.dart b/sample apps/hms-callkit-app/lib/firebase_options.dart new file mode 100644 index 000000000..dd6e0644b --- /dev/null +++ b/sample apps/hms-callkit-app/lib/firebase_options.dart @@ -0,0 +1,70 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for web - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyBHWLPFpOUzUcy7v-ci_zAL9jYEBW9Mim0', + appId: '1:639484339298:android:3a8154b3773841737c9d3b', + messagingSenderId: '639484339298', + projectId: 'hms-callkit-dd0c9', + storageBucket: 'hms-callkit-dd0c9.appspot.com', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyChFexqq7iHsUAqZcZF3zOW0yrUD9o2dTI', + appId: '1:639484339298:ios:e2e461e93cd90c217c9d3b', + messagingSenderId: '639484339298', + projectId: 'hms-callkit-dd0c9', + storageBucket: 'hms-callkit-dd0c9.appspot.com', + iosClientId: + '639484339298-q450qsqg6t7uopupiklago0me8mq0al7.apps.googleusercontent.com', + iosBundleId: 'com.example.hmsCallkit', + ); +} diff --git a/sample apps/hms-callkit-app/lib/hmssdk/hmssdk_interactor.dart b/sample apps/hms-callkit-app/lib/hmssdk/hmssdk_interactor.dart new file mode 100644 index 000000000..59aef7e77 --- /dev/null +++ b/sample apps/hms-callkit-app/lib/hmssdk/hmssdk_interactor.dart @@ -0,0 +1,11 @@ +//Package imports + +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class HMSSDKInteractor { + static HMSSDK? hmsSDK; + + HMSSDKInteractor() { + hmsSDK = HMSSDK(); + } +} diff --git a/sample apps/hms-callkit-app/lib/hmssdk/join_service.dart b/sample apps/hms-callkit-app/lib/hmssdk/join_service.dart new file mode 100644 index 000000000..68f196f7d --- /dev/null +++ b/sample apps/hms-callkit-app/lib/hmssdk/join_service.dart @@ -0,0 +1,23 @@ +//Dart imports +import 'dart:convert'; +import 'dart:developer'; + +//Package imports +import 'package:http/http.dart' as http; + +Future getAuthToken( + {required String roomId, + required String tokenEndpoint, + required String userId, + required String role}) async { + Uri endPoint = Uri.parse(tokenEndpoint); + try { + http.Response response = await http.post(endPoint, + body: {'user_id': userId, 'room_id': roomId, 'role': role}); + var body = json.decode(response.body); + return body['token']; + } catch (e) { + log(e.toString()); + return null; + } +} diff --git a/sample apps/hms-callkit-app/lib/hmssdk/meeting_page.dart b/sample apps/hms-callkit-app/lib/hmssdk/meeting_page.dart new file mode 100644 index 000000000..7bbd9eb62 --- /dev/null +++ b/sample apps/hms-callkit-app/lib/hmssdk/meeting_page.dart @@ -0,0 +1,387 @@ +//Dart imports +import 'dart:developer'; + +//Package imports +import 'package:draggable_widget/draggable_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:hms_callkit/utility_functions.dart'; +import 'package:hms_callkit/app_navigation/app_router.dart'; +import 'package:hms_callkit/hmssdk/hmssdk_interactor.dart'; +import 'package:hms_callkit/app_navigation/navigation_service.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class MeetingPage extends StatefulWidget { + final String? authToken; + final String userName; + const MeetingPage({ + super.key, + this.authToken, + required this.userName, + }); + + @override + State createState() => _MeetingPageState(); +} + +class _MeetingPageState extends State + with WidgetsBindingObserver + implements HMSUpdateListener, HMSActionResultListener { + Offset position = const Offset(5, 5); + bool isJoinSuccessful = false; + HMSPeer? localPeer, remotePeer; + HMSVideoTrack? localPeerVideoTrack, remotePeerVideoTrack; + bool isLocalVideoOn = true, isLocalAudioOn = true; + + @override + Future didChangeAppLifecycleState(AppLifecycleState state) async {} + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + joinCall(); + } + + void joinCall() async { + if (!isJoinSuccessful) { + HMSSDKInteractor.hmsSDK ??= HMSSDK(); + print("HMSSDK instance created"); + await HMSSDKInteractor.hmsSDK?.build(); + HMSSDKInteractor.hmsSDK?.addUpdateListener(listener: this); + if (widget.authToken != null) { + log("Join called..."); + isJoinSuccessful = true; + HMSSDKInteractor.hmsSDK?.join( + config: HMSConfig( + authToken: widget.authToken!, userName: widget.userName)); + } else { + log("authToken is null"); + NavigationService.instance.pushNamedIfNotCurrent(AppRoute.homePage); + } + } + } + + @override + void dispose() { + remotePeer = null; + remotePeerVideoTrack = null; + localPeer = null; + localPeerVideoTrack = null; + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void onJoin({required HMSRoom room}) { + room.peers?.forEach((peer) { + if (peer.isLocal) { + localPeer = peer; + if (peer.videoTrack != null) { + localPeerVideoTrack = peer.videoTrack; + } + if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() {}); + }); + } + } + }); + } + + @override + void onPeerUpdate({required HMSPeer peer, required HMSPeerUpdate update}) { + if (update == HMSPeerUpdate.networkQualityUpdated) { + return; + } + if (update == HMSPeerUpdate.peerJoined) { + if (!peer.isLocal) { + if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + remotePeer = peer; + }); + }); + } + } + } else if (update == HMSPeerUpdate.peerLeft) { + if (!peer.isLocal) { + if (mounted) { + setState(() { + remotePeer = null; + }); + } + } else { + if (mounted) { + setState(() { + localPeer = null; + }); + } + } + } + } + + @override + void onTrackUpdate( + {required HMSTrack track, + required HMSTrackUpdate trackUpdate, + required HMSPeer peer}) { + if (track.kind == HMSTrackKind.kHMSTrackKindVideo) { + if (trackUpdate == HMSTrackUpdate.trackRemoved) { + if (peer.isLocal) { + if (mounted) { + setState(() { + localPeerVideoTrack = null; + }); + } + } else { + if (mounted) { + setState(() { + remotePeerVideoTrack = null; + }); + } + } + return; + } + if (peer.isLocal) { + if (mounted) { + setState(() { + localPeerVideoTrack = track as HMSVideoTrack; + }); + } + } else { + if (mounted) { + setState(() { + remotePeerVideoTrack = track as HMSVideoTrack; + }); + } + } + } + } + + @override + void onAudioDeviceChanged( + {HMSAudioDevice? currentAudioDevice, + List? availableAudioDevice}) {} + + @override + void onChangeTrackStateRequest( + {required HMSTrackChangeRequest hmsTrackChangeRequest}) {} + + @override + void onHMSError({required HMSException error}) {} + + @override + void onMessage({required HMSMessage message}) {} + + @override + void onReconnected() {} + + @override + void onReconnecting() {} + + @override + void onRemovedFromRoom( + {required HMSPeerRemovedFromPeer hmsPeerRemovedFromPeer}) {} + + @override + void onRoleChangeRequest({required HMSRoleChangeRequest roleChangeRequest}) {} + + @override + void onRoomUpdate({required HMSRoom room, required HMSRoomUpdate update}) {} + + @override + void onUpdateSpeakers({required List updateSpeakers}) {} + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + HMSSDKInteractor.hmsSDK?.leave(hmsActionResultListener: this); + return true; + }, + child: SafeArea( + child: Scaffold( + body: SizedBox( + height: MediaQuery.of(context).size.height, + width: MediaQuery.of(context).size.width, + child: Stack( + children: [ + (remotePeerVideoTrack != null && remotePeer != null) + ? peerTile( + Key(remotePeerVideoTrack?.trackId ?? "" "mainVideo"), + remotePeerVideoTrack, + remotePeer, + context, + height: MediaQuery.of(context).size.height, + width: MediaQuery.of(context).size.width) + : Container( + width: MediaQuery.of(context).size.width, + color: Colors.black, + height: MediaQuery.of(context).size.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + Text( + "Waiting for other peer to join", + style: TextStyle(color: Colors.white, fontSize: 20), + ), + SizedBox(height: 10), + CircularProgressIndicator( + strokeWidth: 2, + ) + ], + )), + Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.only(bottom: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: () async { + HMSSDKInteractor.hmsSDK + ?.leave(hmsActionResultListener: this); + }, + child: Container( + decoration: + BoxDecoration(shape: BoxShape.circle, boxShadow: [ + BoxShadow( + color: Colors.red.withAlpha(60), + blurRadius: 3.0, + spreadRadius: 5.0, + ), + ]), + child: const CircleAvatar( + radius: 25, + backgroundColor: Colors.red, + child: Icon(Icons.call_end, color: Colors.white), + ), + ), + ), + GestureDetector( + onTap: () => { + HMSSDKInteractor.hmsSDK?.toggleCameraMuteState(), + if (mounted) + { + setState(() { + isLocalVideoOn = !isLocalVideoOn; + }) + } + }, + child: CircleAvatar( + radius: 25, + backgroundColor: Colors.grey.withOpacity(0.3), + child: Icon( + isLocalVideoOn + ? Icons.videocam + : Icons.videocam_off_rounded, + color: Colors.white, + ), + ), + ), + GestureDetector( + onTap: () => { + HMSSDKInteractor.hmsSDK?.toggleMicMuteState(), + if (mounted) + { + setState(() { + isLocalAudioOn = !isLocalAudioOn; + }) + } + }, + child: CircleAvatar( + radius: 25, + backgroundColor: Colors.grey.withOpacity(0.3), + child: Icon( + isLocalAudioOn ? Icons.mic : Icons.mic_off, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ), + DraggableWidget( + topMargin: 10, + bottomMargin: 130, + horizontalSpace: 10, + child: peerTile( + Key(localPeerVideoTrack?.trackId ?? "" "mainVideo"), + localPeerVideoTrack, + localPeer, + context), + ) + ], + ), + ), + )), + ); + } + + Widget peerTile( + Key key, HMSVideoTrack? videoTrack, HMSPeer? peer, BuildContext context, + {double height = 150, double width = 100}) { + return Container( + height: height, + width: width, + key: key, + color: Colors.grey.shade900, + child: (videoTrack != null && !(videoTrack.isMute)) + ? HMSVideoView( + scaleType: ScaleType.SCALE_ASPECT_FILL, + track: videoTrack, + ) + : Center( + child: Container( + decoration: BoxDecoration( + color: Colors.blue.withAlpha(4), + shape: BoxShape.circle, + boxShadow: const [ + BoxShadow( + color: Colors.blue, + blurRadius: 20.0, + spreadRadius: 5.0, + ), + ], + ), + child: Text( + peer?.name.substring(0, 1) ?? "D", + style: const TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.w600), + ), + ), + ), + ); + } + + @override + void onException( + {required HMSActionResultListenerMethod methodType, + Map? arguments, + required HMSException hmsException}) { + if (methodType == HMSActionResultListenerMethod.leave) { + log("Error occured in leave"); + } + } + + @override + void onSuccess( + {required HMSActionResultListenerMethod methodType, + Map? arguments}) { + if (methodType == HMSActionResultListenerMethod.leave) { + isJoinSuccessful = false; + endAllCalls(); + NavigationService.instance.pushNamedAndRemoveUntil(AppRoute.homePage); + } + } + + @override + void onSessionStoreAvailable({HMSSessionStore? hmsSessionStore}) { + // TODO: implement onSessionStoreAvailable + } +} diff --git a/sample apps/hms-callkit-app/lib/hmssdk/preview_page.dart b/sample apps/hms-callkit-app/lib/hmssdk/preview_page.dart new file mode 100644 index 000000000..61cb98fa2 --- /dev/null +++ b/sample apps/hms-callkit-app/lib/hmssdk/preview_page.dart @@ -0,0 +1,266 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:hms_callkit/utility_functions.dart'; +import 'package:hms_callkit/app_navigation/app_router.dart'; +import 'package:hms_callkit/hmssdk/hmssdk_interactor.dart'; +import 'package:hms_callkit/app_navigation/navigation_service.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class PreviewPage extends StatefulWidget { + final String? authToken; + final String userName; + const PreviewPage({ + super.key, + this.authToken, + required this.userName, + }); + + @override + State createState() => _PreviewPageState(); +} + +class _PreviewPageState extends State + implements HMSPreviewListener, HMSActionResultListener { + Offset position = const Offset(5, 5); + bool isPreviewSuccessful = false; + HMSPeer? localPeer; + HMSVideoTrack? localPeerVideoTrack; + bool isLocalVideoOn = true, isLocalAudioOn = true; + @override + void initState() { + super.initState(); + joinCall(); + } + + void joinCall() async { + HMSSDKInteractor.hmsSDK ??= HMSSDK(); + await HMSSDKInteractor.hmsSDK?.build(); + if (widget.authToken != null) { + HMSSDKInteractor.hmsSDK?.addPreviewListener(listener: this); + HMSSDKInteractor.hmsSDK?.preview( + config: HMSConfig( + authToken: widget.authToken!, userName: widget.userName)); + } else { + log("authToken is null"); + NavigationService.instance.pushNamedIfNotCurrent(AppRoute.homePage); + } + } + + @override + void onPreview({required HMSRoom room, required List localTracks}) { + room.peers?.forEach((peer) { + if (peer.isLocal) { + log("Peer is ${peer.toString()}"); + localPeer = peer; + } + }); + for (var track in localTracks) { + if (track.kind == HMSTrackKind.kHMSTrackKindVideo) { + log("Peer track is ${track.toString()}"); + localPeerVideoTrack = track as HMSVideoTrack?; + isLocalVideoOn = !(localPeerVideoTrack?.isMute ?? false); + } else { + isLocalAudioOn = ((track as HMSAudioTrack?)?.isMute ?? false); + } + } + if (mounted) { + setState(() {}); + } + } + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + HMSSDKInteractor.hmsSDK?.removePreviewListener(listener: this); + } + + @override + void onPeerUpdate({required HMSPeer peer, required HMSPeerUpdate update}) { + log("On peer Update $update"); + if (update == HMSPeerUpdate.networkQualityUpdated) { + return; + } + if (update == HMSPeerUpdate.peerJoined) { + if (!peer.isLocal) { + NavigationService.instance.pushNamedAndRemoveUntil(AppRoute.callingPage, + args: widget.authToken); + } + } else if (update == HMSPeerUpdate.peerLeft) { + if (peer.isLocal) { + if (mounted) { + setState(() { + localPeer = null; + }); + } + } else { + if (mounted) { + setState(() { + localPeer = null; + }); + } + } + } + } + + @override + void onAudioDeviceChanged( + {HMSAudioDevice? currentAudioDevice, + List? availableAudioDevice}) {} + + @override + void onHMSError({required HMSException error}) {} + + @override + void onRoomUpdate({required HMSRoom room, required HMSRoomUpdate update}) { + log("On Room Update $update"); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + HMSSDKInteractor.hmsSDK?.leave(hmsActionResultListener: this); + return true; + }, + child: SafeArea( + child: Scaffold( + body: SizedBox( + height: MediaQuery.of(context).size.height, + width: MediaQuery.of(context).size.width, + child: Stack( + children: [ + peerTile(Key(localPeerVideoTrack?.trackId ?? "" "mainVideo"), + localPeerVideoTrack, localPeer, context, + height: MediaQuery.of(context).size.height, + width: MediaQuery.of(context).size.width), + Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.only(bottom: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: () => { + HMSSDKInteractor.hmsSDK?.toggleCameraMuteState(), + if (mounted) + { + setState(() { + isLocalVideoOn = !isLocalVideoOn; + }) + } + }, + child: CircleAvatar( + radius: 25, + backgroundColor: Colors.grey.withOpacity(0.3), + child: Icon( + isLocalVideoOn + ? Icons.videocam + : Icons.videocam_off_rounded, + color: Colors.white, + ), + ), + ), + GestureDetector( + onTap: () => { + HMSSDKInteractor.hmsSDK?.toggleMicMuteState(), + if (mounted) + { + setState(() { + isLocalAudioOn = !isLocalAudioOn; + }) + } + }, + child: CircleAvatar( + radius: 25, + backgroundColor: Colors.grey.withOpacity(0.3), + child: Icon( + isLocalAudioOn ? Icons.mic : Icons.mic_off, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ), + const Padding( + padding: EdgeInsets.only(top: 100.0), + child: Align( + alignment: Alignment.topCenter, + child: Text( + "Ringing...", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white), + ), + ), + ) + ], + ), + ), + )), + ); + } + + Widget peerTile( + Key key, HMSVideoTrack? videoTrack, HMSPeer? peer, BuildContext context, + {double height = 150, double width = 100}) { + return Container( + height: height, + width: width, + key: key, + color: Colors.grey.shade900, + child: (videoTrack != null && !(videoTrack.isMute)) + ? HMSVideoView( + scaleType: ScaleType.SCALE_ASPECT_FILL, + track: videoTrack, + ) + : Center( + child: Container( + decoration: BoxDecoration( + color: Colors.blue.withAlpha(4), + shape: BoxShape.circle, + boxShadow: const [ + BoxShadow( + color: Colors.blue, + blurRadius: 20.0, + spreadRadius: 5.0, + ), + ], + ), + child: Text( + peer?.name.substring(0, 1) ?? "D", + style: const TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.w600), + ), + ), + ), + ); + } + + @override + void onException( + {required HMSActionResultListenerMethod methodType, + Map? arguments, + required HMSException hmsException}) { + if (methodType == HMSActionResultListenerMethod.leave) { + log("Error occured in leave"); + } + } + + @override + void onSuccess( + {required HMSActionResultListenerMethod methodType, + Map? arguments}) { + if (methodType == HMSActionResultListenerMethod.leave) { + endCurrentCall(); + NavigationService.instance.pushNamedIfNotCurrent(AppRoute.homePage); + } + } +} diff --git a/sample apps/hms-callkit-app/lib/home_page.dart b/sample apps/hms-callkit-app/lib/home_page.dart new file mode 100644 index 000000000..73ff7807b --- /dev/null +++ b/sample apps/hms-callkit-app/lib/home_page.dart @@ -0,0 +1,346 @@ +//Package imports + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_callkit_incoming/entities/entities.dart'; +import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; +import 'package:hms_callkit/utility_functions.dart'; +import 'package:hms_callkit/app_navigation/app_router.dart'; +import 'package:hms_callkit/hmssdk/join_service.dart'; +import 'package:hms_callkit/app_navigation/navigation_service.dart'; +import 'package:share_plus/share_plus.dart'; + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + TextEditingController fcmTokenController = TextEditingController(); + TextEditingController roomIdController = TextEditingController(); + + Future listenerEvent(Function? callback) async { + try { + FlutterCallkitIncoming.onEvent.listen((event) async { + print(' HMSSDK HOME: $event'); + switch (event!.event) { + case Event.actionDidUpdateDevicePushTokenVoip: + // TODO: Handle this case. + break; + case Event.actionCallIncoming: + // TODO: Handle this case. + break; + case Event.actionCallStart: + // TODO: Handle this case. + break; + case Event.actionCallAccept: + // TODO: Handle this case. + var data = event.body; + String authToken = data["extra"]["authToken"]; + String userName = data["nameCaller"]; + bool res = await getPermissions(); + if (res) { + startOutGoingCall(); + NavigationService.instance + .pushNamed(AppRoute.callingPage, args: authToken); + } + break; + case Event.actionCallDecline: + // TODO: Handle this case. + break; + case Event.actionCallEnded: + // TODO: Handle this case. + break; + case Event.actionCallTimeout: + // TODO: Handle this case. + break; + case Event.actionCallCallback: + // TODO: Handle this case. + break; + case Event.actionCallToggleHold: + // TODO: Handle this case. + break; + case Event.actionCallToggleMute: + // TODO: Handle this case. + break; + case Event.actionCallToggleDmtf: + // TODO: Handle this case. + break; + case Event.actionCallToggleGroup: + // TODO: Handle this case. + break; + case Event.actionCallToggleAudioSession: + // TODO: Handle this case. + break; + case Event.actionCallCustom: + // TODO: Handle this case. + break; + } + if (callback != null) { + callback(event.toString()); + } + }); + } on Exception { + print("HMSSDK Exception"); + } + } + + @override + void initState() { + super.initState(); + listenerEvent(onEvent); + } + + onEvent(event) { + if (!mounted) return; + setState(() { + onEventLogs += "${event.toString()}\n"; + }); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + body: SingleChildScrollView( + child: Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + color: Colors.black, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0, vertical: 30.0), + child: Column( + children: [ + Align( + alignment: Alignment.topLeft, + child: RichText( + text: const TextSpan( + text: "Wanna try out", + style: TextStyle( + fontSize: 30, fontWeight: FontWeight.bold), + children: [ + TextSpan( + text: "\nCalling", + style: TextStyle( + fontStyle: FontStyle.italic, fontSize: 35)), + TextSpan( + text: "\nShare the code below", + style: TextStyle( + fontSize: 30, fontWeight: FontWeight.bold), + ) + ])), + ), + const SizedBox( + height: 10, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: MediaQuery.of(context).size.width - 80, + height: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.grey.shade900), + child: const Center( + child: Text( + "XXXX-XXXX-XXXX", + overflow: TextOverflow.clip, + style: + TextStyle(fontSize: 20, color: Colors.white), + ), + ), + ), + InkWell( + onTap: () { + Clipboard.setData( + ClipboardData(text: deviceFCMToken)); + Share.share( + deviceFCMToken, + subject: 'Share device FCM Token', + ); + }, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + border: Border.all( + color: Colors.grey.shade900, width: 1), + borderRadius: const BorderRadius.all( + Radius.circular(12)), + color: Colors.black), + child: const Icon( + Icons.copy, + color: Colors.white, + )), + ), + ], + ), + const SizedBox( + height: 100, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: const [ + Text("Paste the FCM Token here", + key: Key('fcm_token_text'), + style: TextStyle(color: Colors.white)) + ], + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.95, + child: TextField( + key: const Key('fcm_token_field'), + textInputAction: TextInputAction.done, + onSubmitted: (value) {}, + controller: fcmTokenController, + onChanged: (value) { + setState(() {}); + }, + decoration: InputDecoration( + focusColor: hmsdefaultColor, + contentPadding: const EdgeInsets.only(left: 16), + fillColor: Colors.white, + filled: true, + hintText: 'Enter the FCM token here', + suffixIcon: fcmTokenController.text.isEmpty + ? null + : IconButton( + onPressed: () { + fcmTokenController.text = ""; + setState(() {}); + }, + icon: const Icon(Icons.clear), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: hmsdefaultColor, width: 1), + borderRadius: + const BorderRadius.all(Radius.circular(8))), + border: const OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(8)))), + ), + ), + const SizedBox( + height: 30, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: const [ + Text("Paste the room-id here", + key: Key('room_id_text'), + style: TextStyle(color: Colors.white)) + ], + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.95, + child: TextField( + key: const Key('room_id_field'), + textInputAction: TextInputAction.done, + onSubmitted: (value) {}, + controller: roomIdController, + onChanged: (value) { + setState(() {}); + }, + decoration: InputDecoration( + focusColor: hmsdefaultColor, + contentPadding: const EdgeInsets.only(left: 16), + fillColor: Colors.white, + filled: true, + hintText: 'Enter room-id here', + suffixIcon: roomIdController.text.isEmpty + ? null + : IconButton( + onPressed: () { + roomIdController.text = ""; + setState(() {}); + }, + icon: const Icon(Icons.clear), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: hmsdefaultColor, width: 1), + borderRadius: + const BorderRadius.all(Radius.circular(8))), + border: const OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(8)))), + ), + ), + const SizedBox( + height: 30, + ), + ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(hmsdefaultColor), + shape: + MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ))), + onPressed: () async { + if (fcmTokenController.text.isEmpty || + roomIdController.text.isEmpty) { + return; + } + await getPermissions(); + //Enter the tokenEndPoint, role and userId here + String? authToken = await getAuthToken( + roomId: roomIdController.text, + role: "host", + tokenEndpoint: "Enter your token endpoint here", + userId: "Enter the user Id here"); + //Checking whether authentication token is null or not + if (authToken != null) { + call( + receiverFCMToken: fcmTokenController.text, + authToken: authToken); + NavigationService.instance.pushNamedIfNotCurrent( + AppRoute.previewPage, + args: authToken); + } + }, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon( + Icons.call, + color: Colors.white, + ), + SizedBox( + width: 5, + ), + Text( + 'Call Now', + style: TextStyle(color: Colors.white), + ), + ], + ), + ), + ) + ], + ), + )), + ), + ), + ); + } +} diff --git a/sample apps/hms-callkit-app/lib/main.dart b/sample apps/hms-callkit-app/lib/main.dart new file mode 100644 index 000000000..ccc3df710 --- /dev/null +++ b/sample apps/hms-callkit-app/lib/main.dart @@ -0,0 +1,47 @@ +//Package imports +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:hms_callkit/utility_functions.dart'; +import 'package:hms_callkit/app_navigation/app_router.dart'; +import 'package:hms_callkit/app_navigation/navigation_service.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + void initState() { + super.initState(); + initFirebase(); + //Checks call when open app from terminated + checkAndNavigationCallingPage("Navigation called from main.dart"); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: "HMS-Callkit Demo", + onGenerateRoute: AppRoute.generateRoute, + initialRoute: AppRoute.homePage, + navigatorKey: NavigationService.instance.navigationKey, + navigatorObservers: [ + NavigationService.instance.routeObserver + ], + theme: ThemeData( + primarySwatch: Colors.blue, + ), + ); + } +} diff --git a/sample apps/hms-callkit-app/lib/receive_call.dart b/sample apps/hms-callkit-app/lib/receive_call.dart new file mode 100644 index 000000000..278f5ea7a --- /dev/null +++ b/sample apps/hms-callkit-app/lib/receive_call.dart @@ -0,0 +1,77 @@ +//Package imports +import 'package:flutter/material.dart'; +import 'package:hms_callkit/utility_functions.dart'; +import 'package:hms_callkit/app_navigation/app_router.dart'; +import 'package:hms_callkit/app_navigation/navigation_service.dart'; + +class ReceiveCall extends StatefulWidget { + final Map? callKitParams; + const ReceiveCall({super.key, required this.callKitParams}); + + @override + State createState() => _ReceiveCallState(); +} + +class _ReceiveCallState extends State { + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + body: Center( + child: Container( + color: Colors.black, + width: MediaQuery.of(context).size.width, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(50), + child: Image( + image: NetworkImage(widget.callKitParams?["avatar"]), + fit: BoxFit.fill, + height: 100, + width: 100, + )), + const SizedBox( + height: 60, + ), + ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(hmsdefaultColor), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ))), + onPressed: () async { + NavigationService.instance.pushNamedIfNotCurrent( + AppRoute.callingPage, + args: widget.callKitParams!["extra"]["authToken"]); + }, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon( + Icons.call, + color: Colors.white, + ), + SizedBox( + width: 5, + ), + Text( + 'Join Call', + style: TextStyle(color: Colors.white), + ), + ], + ), + ), + ) + ], + ), + ), + ), + )); + } +} diff --git a/sample apps/hms-callkit-app/lib/utility_functions.dart b/sample apps/hms-callkit-app/lib/utility_functions.dart new file mode 100644 index 000000000..1a164be76 --- /dev/null +++ b/sample apps/hms-callkit-app/lib/utility_functions.dart @@ -0,0 +1,282 @@ +//Dart imports +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; + +//Package imports +import 'package:cloud_functions/cloud_functions.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_callkit_incoming/entities/entities.dart'; +import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; +import 'package:hms_callkit/app_navigation/app_router.dart'; +import 'package:hms_callkit/app_navigation/navigation_service.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:uuid/uuid.dart'; + +Uuid? _uniqueCallId; +String? _currentCallId; +String onEventLogs = ""; +late final FirebaseMessaging _firebaseMessaging; +String deviceFCMToken = ""; +Color hmsdefaultColor = const Color.fromRGBO(36, 113, 237, 1); + +//Handles when app is in background or terminated +Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { + var response = jsonDecode(message.data["params"]); + CallKitParams data = CallKitParams.fromJson(response); + if (data.extra?.containsKey("authToken") ?? false) { + placeCall(data.extra!["authToken"]); + } else { + log("No Valid authToken found"); + } +} + +//This initializes the firebase +void initFirebase() async { + print("Firebase init"); + _uniqueCallId = const Uuid(); + _firebaseMessaging = FirebaseMessaging.instance; + NotificationSettings settings = await _firebaseMessaging.requestPermission( + alert: true, + badge: true, + provisional: false, + sound: true, + ); + if (settings.authorizationStatus != AuthorizationStatus.authorized) { + return; + } + deviceFCMToken = await _firebaseMessaging.getToken() ?? ""; + FirebaseMessaging.onMessage.listen((RemoteMessage message) async { + print("HMSSDK Value is ${message.data["params"]}"); + var response = jsonDecode(message.data["params"]); + CallKitParams data = CallKitParams.fromJson(response); + if (data.extra?.containsKey("authToken") ?? false) { + print("HMSSDK Received Notification"); + placeCall(data.extra!["authToken"]); + } else { + log("No Valid authToken found"); + } + }); + _firebaseMessaging.getToken().then((token) { + log('Device Token FCM: $token'); + }); + initCurrentCall(); +} + +//This function returns the currently running call if any +_getCurrentCall() async { + var calls = await FlutterCallkitIncoming.activeCalls(); + if (calls is List) { + if (calls.isNotEmpty) { + log('DATA: $calls'); + _currentCallId = calls[0]['id']; + return calls[0]; + } else { + _currentCallId = ""; + return null; + } + } +} + +//Method to place the call +Future placeCall(String authToken) async { + await FlutterCallkitIncoming.showCallkitIncoming(getCallInfo(authToken)); +} + +//This function navigates to the call screen if a call is currently running +void checkAndNavigationCallingPage(String message) async { + var currentCall = await _getCurrentCall(); + bool res = await getPermissions(); + if (currentCall != null) { + Map callData = {}; + currentCall.forEach((key, value) { + callData[key] = value; + }); + if (res == true && currentCall != null && currentCall["extra"] != null) { + NavigationService.instance.pushNamedIfNotCurrent(AppRoute.callingPage, + args: currentCall["extra"]["authToken"]); + } + } +} + +//To make a fake call on same device +Future makeFakeCallInComing() async { + await Future.delayed(const Duration(seconds: 5), () async { + _currentCallId = _uniqueCallId?.v4(); + + final params = CallKitParams( + id: _currentCallId, + nameCaller: 'Test User', + appName: 'Callkit', + avatar: 'https://i.pravatar.cc/100', + handle: '0123456789', + type: 1, + duration: 30000, + textAccept: 'Accept', + textDecline: 'Decline', + extra: { + 'userId': '1a2b3c4d', + 'authToken': "Enter your authToken here", + }, + headers: {'apiKey': 'Abc@123!', 'platform': 'flutter'}, + android: const AndroidParams( + isCustomNotification: true, + isShowLogo: false, + ringtonePath: 'system_ringtone_default', + backgroundColor: '#0955fa', + backgroundUrl: 'assets/test.png', + actionColor: '#4CAF50', + incomingCallNotificationChannelName: 'Incoming Call', + missedCallNotificationChannelName: 'Missed Call', + ), + ios: const IOSParams( + iconName: 'CallKitLogo', + handleType: '', + supportsVideo: true, + maximumCallGroups: 2, + maximumCallsPerCallGroup: 1, + audioSessionMode: 'default', + audioSessionActive: true, + audioSessionPreferredSampleRate: 44100.0, + audioSessionPreferredIOBufferDuration: 0.005, + supportsDTMF: true, + supportsHolding: true, + supportsGrouping: false, + supportsUngrouping: false, + ringtonePath: 'system_ringtone_default', + ), + ); + await FlutterCallkitIncoming.showCallkitIncoming(params); + }); +} + +//To start a call but we are directly logging into the meeting +Future startOutGoingCall() async { + _currentCallId = _uniqueCallId?.v4(); + final params = CallKitParams( + id: _currentCallId, + nameCaller: 'Test user', + handle: '0123456789', + type: 1, + extra: {'userId': '1a2b3c4d'}, + ios: const IOSParams(handleType: 'number'), + ); + await FlutterCallkitIncoming.startCall(params); +} + +//This method returns all the calls running currently +Future activeCalls() async { + var calls = await FlutterCallkitIncoming.activeCalls(); + log(calls); +} + +//This method ends all the calls that are running currently +Future endAllCalls() async { + await FlutterCallkitIncoming.endAllCalls(); +} + +//This function fetches the calls that are currently active and set the _currentCallId to that call +initCurrentCall() async { + var calls = await FlutterCallkitIncoming.activeCalls(); + if (calls is List) { + if (calls.isNotEmpty) { + log('DATA: $calls'); + _currentCallId = calls[0]['id']; + return calls[0]; + } else { + _currentCallId = ""; + return null; + } + } +} + +//This method ends the currently running call +Future endCurrentCall() async { + initCurrentCall(); + await FlutterCallkitIncoming.endCall(_currentCallId!); +} + +//To get microphone and camera permissions +Future getPermissions() async { + if (Platform.isIOS) return true; + await Permission.camera.request(); + await Permission.microphone.request(); + await Permission.bluetoothConnect.request(); + + while ((await Permission.camera.isDenied)) { + await Permission.camera.request(); + } + while ((await Permission.microphone.isDenied)) { + await Permission.microphone.request(); + } + while ((await Permission.bluetoothConnect.isDenied)) { + await Permission.bluetoothConnect.request(); + } + return true; +} + +//This is used to get the deviceFCM token just for printing in logs +Future getDevicePushTokenVoIP() async { + var devicePushTokenVoIP = + await FlutterCallkitIncoming.getDevicePushTokenVoIP(); + log("Device token is $devicePushTokenVoIP"); + return devicePushTokenVoIP; +} + +//This method sends the notification to the receiver's device +Future call( + {required String receiverFCMToken, required String authToken}) async { + var func = FirebaseFunctions.instance.httpsCallable("notifySubscribers"); + startOutGoingCall(); + await func.call({ + "targetDevices": [receiverFCMToken], //Enter the device fcmToken here + "messageTitle": "Incoming Call", + "messageBody": "Someone is calling you...", + "callkitParams": json.encode(getCallInfo(authToken).toJson()) + }); +} + +//This method is used to set the info which needs to be sent to the receiver for joining the room and showing the caller UI +CallKitParams getCallInfo(String authToken) { + if (_uniqueCallId == null) { + _uniqueCallId = const Uuid(); + _currentCallId = _uniqueCallId?.v4(); + } + return CallKitParams( + id: _uniqueCallId?.v4(), + nameCaller: 'Test User', + appName: 'HMS Call', + avatar: 'https://i.pravatar.cc/100', + handle: '0123456789', + type: 1, + duration: 60000, + textAccept: 'Accept', + textDecline: 'Decline', + extra: {'authToken': authToken}, + android: const AndroidParams( + isCustomNotification: true, + isShowLogo: false, + ringtonePath: 'system_ringtone_default', + backgroundColor: '#0955fa', + backgroundUrl: 'assets/test.png', + actionColor: '#4CAF50', + ), + ios: const IOSParams( + iconName: 'CallKitLogo', + handleType: '', + supportsVideo: true, + maximumCallGroups: 2, + maximumCallsPerCallGroup: 1, + audioSessionMode: 'default', + audioSessionActive: true, + audioSessionPreferredSampleRate: 44100.0, + audioSessionPreferredIOBufferDuration: 0.005, + supportsDTMF: true, + supportsHolding: true, + supportsGrouping: false, + supportsUngrouping: false, + ringtonePath: 'system_ringtone_default', + ), + ); +} diff --git a/sample apps/hms-callkit-app/pubspec.lock b/sample apps/hms-callkit-app/pubspec.lock new file mode 100644 index 000000000..20c617faf --- /dev/null +++ b/sample apps/hms-callkit-app/pubspec.lock @@ -0,0 +1,554 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "8eb354cb8ebed8a9fdf63699d15deff533bc133128898afaf754926b57d611b6" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + cloud_functions: + dependency: "direct main" + description: + name: cloud_functions + sha256: f539964f0db7153bbb39cf1dd32fadbfd59e46672aaec595221c9bd6a4571ec4 + url: "https://pub.dev" + source: hosted + version: "4.3.1" + cloud_functions_platform_interface: + dependency: transitive + description: + name: cloud_functions_platform_interface + sha256: e8b1d86d4d847c627de42198f8b6a16e02386e1ca31e58fb6f6d2e077a4f9299 + url: "https://pub.dev" + source: hosted + version: "5.4.1" + cloud_functions_web: + dependency: transitive + description: + name: cloud_functions_web + sha256: "5c371fc9ad7bc1262ed9303d01dc443c5b790f396cd6404c9cf5c157bda41a7f" + url: "https://pub.dev" + source: hosted + version: "4.5.1" + collection: + dependency: transitive + description: + name: collection + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + url: "https://pub.dev" + source: hosted + version: "1.17.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" + url: "https://pub.dev" + source: hosted + version: "0.3.3+4" + crypto: + dependency: transitive + description: + name: crypto + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" + source: hosted + version: "3.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + url: "https://pub.dev" + source: hosted + version: "1.0.5" + draggable_widget: + dependency: "direct main" + description: + name: draggable_widget + sha256: d7f6b1eb9cb79b724b02dc2ac699f82d1ab20b99920b8b1f25812054d72ca803 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "250678b816279b3240c3a33e1f76bf712c00718f1fbeffc85873a5da8c077379" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: b63e3be6c96ef5c33bdec1aab23c91eb00696f6452f0519401d640938c94cba2 + url: "https://pub.dev" + source: hosted + version: "4.8.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "8c0f4c87d20e2d001a5915df238c1f9c88704231f591324205f5a5d2a7740a45" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "9cfe5c4560fb83393511ca7620f8fb3f22c9a80303052f10290e732fcfb801bd" + url: "https://pub.dev" + source: hosted + version: "14.6.1" + firebase_messaging_platform_interface: + dependency: "direct main" + description: + name: firebase_messaging_platform_interface + sha256: "7e25cb71019ccef8b1fd7b37969af79f04c467974cce4dfc291fa36974edd7ba" + url: "https://pub.dev" + source: hosted + version: "4.5.1" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "5d9840cc8126ea723b1bda901389cb542902f664f2653c16d4f8114e95f13cec" + url: "https://pub.dev" + source: hosted + version: "3.5.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_callkit_incoming: + dependency: "direct main" + description: + name: flutter_callkit_incoming + sha256: "46074904ff9d70aee05ee7d16b8638fb0f7d56f57ab6dc5b387bed66539986f0" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" + source: hosted + version: "2.0.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + hmssdk_flutter: + dependency: "direct main" + description: + name: hmssdk_flutter + sha256: bb96354acae74e734e80b775303eaf69a50c804c62fd34b21e254825a387a972 + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http: + dependency: "direct main" + description: + name: http + sha256: "2ed163531e071c2c6b7c659635112f24cb64ecbebf6af46b550d536c0b1aa112" + url: "https://pub.dev" + source: hosted + version: "0.13.4" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + url: "https://pub.dev" + source: hosted + version: "4.8.0" + lints: + dependency: transitive + description: + name: lints + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + url: "https://pub.dev" + source: hosted + version: "0.12.15" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: dcea5feb97d8abf90cab9e9030b497fb7c3cbf26b7a1fe9e3ef7dcb0a1ddec95 + url: "https://pub.dev" + source: hosted + version: "2.0.12" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e + url: "https://pub.dev" + source: hosted + version: "2.0.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "62a68e7e1c6c459f9289859e2fae58290c981ce21d1697faf54910fe1faa4c74" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379 + url: "https://pub.dev" + source: hosted + version: "2.1.7" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c + url: "https://pub.dev" + source: hosted + version: "2.1.3" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8" + url: "https://pub.dev" + source: hosted + version: "10.2.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "8028362b40c4a45298f1cbfccd227c8dd6caf0e27088a69f2ba2ab15464159e2" + url: "https://pub.dev" + source: hosted + version: "10.2.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: "9c370ef6a18b1c4b2f7f35944d644a56aa23576f23abee654cf73968de93f163" + url: "https://pub.dev" + source: hosted + version: "9.0.7" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84" + url: "https://pub.dev" + source: hosted + version: "3.9.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b + url: "https://pub.dev" + source: hosted + version: "0.1.2" + platform: + dependency: transitive + description: + name: platform + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + url: "https://pub.dev" + source: hosted + version: "2.1.3" + process: + dependency: transitive + description: + name: process + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" + source: hosted + version: "4.2.4" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: e387077716f80609bb979cd199331033326033ecd1c8f200a90c5f57b1c9f55e + url: "https://pub.dev" + source: hosted + version: "6.3.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "82ddd4ab9260c295e6e39612d4ff00390b9a7a21f1bb1da771e2f232d80ab8a1" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" + source: hosted + version: "1.9.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + url: "https://pub.dev" + source: hosted + version: "0.5.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "318c42cba924e18180c029be69caf0a1a710191b9ec49bb42b5998fdcccee3cc" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "4eae912628763eb48fc214522e58e942fd16ce195407dbf45638239523c759a6" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "44d79408ce9f07052095ef1f9a693c258d6373dc3944249374e30eff7219ccb0" + url: "https://pub.dev" + source: hosted + version: "2.0.14" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: b6217370f8eb1fd85c8890c539f5a639a01ab209a36db82c921ebeacefc7a615 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "2469694ad079893e3b434a627970c33f2fa5adc46dfe03c9617546969a9a8afc" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + win32: + dependency: transitive + description: + name: win32 + sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + url: "https://pub.dev" + source: hosted + version: "3.1.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + url: "https://pub.dev" + source: hosted + version: "0.2.0+3" +sdks: + dart: ">=3.0.0-0 <4.0.0" + flutter: ">=3.3.0" diff --git a/sample apps/hms-callkit-app/pubspec.yaml b/sample apps/hms-callkit-app/pubspec.yaml new file mode 100644 index 000000000..cb6576545 --- /dev/null +++ b/sample apps/hms-callkit-app/pubspec.yaml @@ -0,0 +1,98 @@ +name: hms_callkit +description: A new Flutter project. +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ">=2.19.0 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + firebase_messaging: 14.6.1 + firebase_core: 2.13.0 + firebase_messaging_platform_interface: 4.5.1 + uuid: 3.0.6 + http: 0.13.4 + hmssdk_flutter: 1.6.0 + permission_handler: 10.2.0 + flutter_callkit_incoming: 2.0.0 + cloud_functions: 4.3.1 + draggable_widget: 2.0.0 + share_plus: 6.3.0 +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/sample apps/hms-callkit-app/test/widget_test.dart b/sample apps/hms-callkit-app/test/widget_test.dart new file mode 100644 index 000000000..df2bb5f45 --- /dev/null +++ b/sample apps/hms-callkit-app/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:hms_callkit/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/sample apps/mobx/android/app/src/main/kotlin/com/example/zoom/MainActivity.kt b/sample apps/mobx/android/app/src/main/kotlin/com/example/zoom/MainActivity.kt index 0c724ab6a..eaf224435 100644 --- a/sample apps/mobx/android/app/src/main/kotlin/com/example/zoom/MainActivity.kt +++ b/sample apps/mobx/android/app/src/main/kotlin/com/example/zoom/MainActivity.kt @@ -1,18 +1,17 @@ package com.example.mobx -import io.flutter.embedding.android.FlutterActivity -import live.hms.hmssdk_flutter.HmssdkFlutterPlugin import android.app.Activity import android.content.Intent +import io.flutter.embedding.android.FlutterActivity import live.hms.hmssdk_flutter.Constants +import live.hms.hmssdk_flutter.HmssdkFlutterPlugin -class MainActivity: FlutterActivity() { +class MainActivity : FlutterActivity() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { -super.onActivityResult(requestCode, resultCode, data) + super.onActivityResult(requestCode, resultCode, data) - if (requestCode == Constants.SCREEN_SHARE_INTENT_REQUEST_CODE && resultCode == Activity.RESULT_OK){ - HmssdkFlutterPlugin.hmssdkFlutterPlugin?.requestScreenShare(data) + if (requestCode == Constants.SCREEN_SHARE_INTENT_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + HmssdkFlutterPlugin.hmssdkFlutterPlugin?.requestScreenShare(data) + } } - -} } diff --git a/sample apps/mobx/pubspec.lock b/sample apps/mobx/pubspec.lock index 93d0a01c7..35b5f02f0 100644 --- a/sample apps/mobx/pubspec.lock +++ b/sample apps/mobx/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -109,10 +109,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" checked_yaml: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" convert: dependency: transitive description: @@ -311,10 +311,10 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" json_annotation: dependency: transitive description: @@ -343,10 +343,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -359,10 +359,10 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" mime: dependency: transitive description: @@ -399,10 +399,10 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" permission_handler: dependency: "direct main" description: @@ -556,10 +556,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" timing: dependency: transitive description: @@ -617,5 +617,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.18.0 <3.0.0" + dart: ">=3.0.0-0 <4.0.0" flutter: ">=2.8.0" diff --git a/sample apps/riverpod/android/app/src/main/kotlin/com/example/example_riverpod/MainActivity.kt b/sample apps/riverpod/android/app/src/main/kotlin/com/example/example_riverpod/MainActivity.kt index 207363b1f..f739c71c0 100644 --- a/sample apps/riverpod/android/app/src/main/kotlin/com/example/example_riverpod/MainActivity.kt +++ b/sample apps/riverpod/android/app/src/main/kotlin/com/example/example_riverpod/MainActivity.kt @@ -1,18 +1,17 @@ package com.example.example_riverpod -import io.flutter.embedding.android.FlutterActivity -import live.hms.hmssdk_flutter.HmssdkFlutterPlugin import android.app.Activity import android.content.Intent +import io.flutter.embedding.android.FlutterActivity import live.hms.hmssdk_flutter.Constants +import live.hms.hmssdk_flutter.HmssdkFlutterPlugin -class MainActivity: FlutterActivity() { - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { -super.onActivityResult(requestCode, resultCode, data) +class MainActivity : FlutterActivity() { + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) - if (requestCode == Constants.SCREEN_SHARE_INTENT_REQUEST_CODE && resultCode == Activity.RESULT_OK){ - HmssdkFlutterPlugin.hmssdkFlutterPlugin?.requestScreenShare(data) + if (requestCode == Constants.SCREEN_SHARE_INTENT_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + HmssdkFlutterPlugin.hmssdkFlutterPlugin?.requestScreenShare(data) + } } - -} } diff --git a/sample apps/riverpod/pubspec.lock b/sample apps/riverpod/pubspec.lock index d2190648a..a39e3d4f3 100644 --- a/sample apps/riverpod/pubspec.lock +++ b/sample apps/riverpod/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" cupertino_icons: dependency: "direct main" description: @@ -111,10 +111,10 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" lints: dependency: transitive description: @@ -127,10 +127,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -143,18 +143,18 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" path: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" permission_handler: dependency: "direct main" description: @@ -268,10 +268,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" typed_data: dependency: transitive description: @@ -289,5 +289,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.18.0 <3.0.0" + dart: ">=3.0.0-0 <4.0.0" flutter: ">=3.0.0"