diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/local_peer_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/local_peer_bottom_sheet.dart index 3095335f3..e946f184f 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/local_peer_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/local_peer_bottom_sheet.dart @@ -4,6 +4,7 @@ library; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:hms_room_kit/src/widgets/common_widgets/hms_cross_button.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:provider/provider.dart'; ///Project imports @@ -51,7 +52,10 @@ class _LocalPeerBottomSheetState extends State { @override Widget build(BuildContext context) { return FractionallySizedBox( - heightFactor: widget.isInsetTile ? 0.26 : 0.2, + heightFactor: + (AppDebugConfig.isDebugMode && widget.meetingStore.isVideoOn) + ? (widget.isInsetTile ? 0.46 : 0.4) + : (widget.isInsetTile ? 0.26 : 0.2), child: Padding( padding: const EdgeInsets.only(top: 16.0, left: 24, right: 24), child: Column( @@ -228,6 +232,53 @@ class _LocalPeerBottomSheetState extends State { widget.meetingStore.peerTracks.length > 1 ? HMSThemeColors.onSurfaceHighEmphasis : HMSThemeColors.onSurfaceLowEmphasis)), + if (AppDebugConfig.isDebugMode && + widget.meetingStore.isVideoOn) + ListTile( + horizontalTitleGap: 2, + onTap: () async { + Navigator.pop(context); + bool isZoomSupported = + await HMSCameraControls.isZoomSupported(); + if (isZoomSupported) { + HMSCameraControls.setZoom(zoomValue: 2); + } + }, + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/maximize.svg", + semanticsLabel: "fl_local_pin_tile", + height: 20, + width: 20, + colorFilter: ColorFilter.mode( + HMSThemeColors.onSurfaceHighEmphasis, + BlendMode.srcIn), + ), + title: HMSSubheadingText( + text: "Zoom In (2x)", + textColor: HMSThemeColors.onSurfaceHighEmphasis)), + + if (AppDebugConfig.isDebugMode && + widget.meetingStore.isVideoOn) + ListTile( + horizontalTitleGap: 2, + onTap: () async { + Navigator.pop(context); + HMSCameraControls.resetZoom(); + }, + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/minimize.svg", + semanticsLabel: "fl_local_pin_tile", + height: 20, + width: 20, + colorFilter: ColorFilter.mode( + HMSThemeColors.onSurfaceHighEmphasis, + BlendMode.srcIn), + ), + title: HMSSubheadingText( + text: "Reset zoom", + textColor: HMSThemeColors.onSurfaceHighEmphasis)), ], ), ), diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt index 3d7cb5cdd..51c2dc636 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt @@ -255,7 +255,7 @@ class HmssdkFlutterPlugin : "capture_snapshot" -> { captureSnapshot(call, result) } - "is_tap_to_focus_supported", "capture_image_at_max_supported_resolution", "is_zoom_supported", "is_flash_supported", "toggle_flash" -> { + "is_tap_to_focus_supported", "capture_image_at_max_supported_resolution", "is_zoom_supported", "is_flash_supported", "toggle_flash", "set_zoom", "reset_zoom", "get_max_zoom", "get_min_zoom" -> { HMSCameraControlsAction.cameraControlsAction(call, result, hmssdk!!, activity.applicationContext) } "get_session_metadata_for_key", "set_session_metadata_for_key" -> { diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSCameraControlsAction.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSCameraControlsAction.kt index 7a1040118..d31f130ff 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSCameraControlsAction.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSCameraControlsAction.kt @@ -24,6 +24,10 @@ class HMSCameraControlsAction { "capture_image_at_max_supported_resolution" -> captureImageAtMaxSupportedResolution(call, result, hmssdk, context) "is_flash_supported" -> isFlashSupported(result, hmssdk) "toggle_flash" -> toggleFlash(result, hmssdk) + "set_zoom" -> setZoom(call, result, hmssdk) + "reset_zoom" -> resetZoom(result, hmssdk) + "get_max_zoom" -> getMaxZoom(result, hmssdk) + "get_min_zoom" -> getMinZoom(result, hmssdk) else -> { result.notImplemented() } @@ -54,7 +58,7 @@ class HMSCameraControlsAction { hmssdk.getLocalPeer()?.let { localPeer -> localPeer.videoTrack?.let { localVideoTrack -> localVideoTrack.getCameraControl()?.let { cameraControl -> - HMSResultExtension.toDictionary(true, cameraControl.isZoomSupported()) + result.success(HMSResultExtension.toDictionary(true, cameraControl.isZoomSupported())) return } } @@ -206,5 +210,97 @@ class HMSCameraControlsAction { return } } + + private fun setZoom( + call: MethodCall, + result: Result, + hmssdk: HMSSDK + ){ + val zoomValue = call.argument("zoom_value") + zoomValue?.let { _zoomValue -> + hmssdk.getLocalPeer()?.let { localPeer -> + localPeer.videoTrack?.let { localVideoTrack -> + localVideoTrack.getCameraControl()?.setZoom( + _zoomValue.toFloat() + ) + } ?: run { + result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("local video track is null"))) + return + } + } ?: run { + result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("local peer is null"))) + return + } + }?:run{ + HMSErrorLogger.returnArgumentsError("zoom value parameter is null in setZoom method") + return + } + } + + private fun resetZoom( + result: Result, + hmssdk: HMSSDK, + ){ + hmssdk.getLocalPeer()?.let { localPeer -> + localPeer.videoTrack?.let { localVideoTrack -> + localVideoTrack.getCameraControl()?.resetZoom() ?: run { + result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("cameraControl is null"))) + return + } + } ?: run { + result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("local video track is null"))) + return + } + } ?: run { + result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("local peer is null"))) + return + } + } + + private fun getMinZoom( + result: Result, + hmssdk: HMSSDK, + ){ + hmssdk.getLocalPeer()?.let { localPeer -> + localPeer.videoTrack?.let { localVideoTrack -> + localVideoTrack.getCameraControl()?.let { cameraControl -> + result.success(HMSResultExtension.toDictionary(true, cameraControl.getMinZoom())) + return + } ?: run { + result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("cameraControl is null"))) + return + } + } ?: run { + result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("local video track is null"))) + return + } + } ?: run { + result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("local peer is null"))) + return + } + } + + private fun getMaxZoom( + result: Result, + hmssdk: HMSSDK, + ){ + hmssdk.getLocalPeer()?.let { localPeer -> + localPeer.videoTrack?.let { localVideoTrack -> + localVideoTrack.getCameraControl()?.let { cameraControl -> + result.success(HMSResultExtension.toDictionary(true, cameraControl.getMaxZoom())) + return + } ?: run { + result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("cameraControl is null"))) + return + } + } ?: run { + result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("local video track is null"))) + return + } + } ?: run { + result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("local peer is null"))) + return + } + } } } diff --git a/packages/hmssdk_flutter/example/ExampleAppChangelog.txt b/packages/hmssdk_flutter/example/ExampleAppChangelog.txt index 697559da5..f1a7ecf49 100644 --- a/packages/hmssdk_flutter/example/ExampleAppChangelog.txt +++ b/packages/hmssdk_flutter/example/ExampleAppChangelog.txt @@ -1,10 +1,7 @@ Board: https://app.devrev.ai/100ms/vistas/vista-250 -- Change Role on Prebuilt -https://app.devrev.ai/100ms/works/ISS-22877 - -- Update DateTime parsing with `convertDateFromEpoch` in SDK -https://app.devrev.ai/100ms/works/ISS-22778 +- Add support for zoom in camera controls +https://app.devrev.ai/100ms/works/ISS-23282 Room Kit: 1.1.6 Core SDK: 1.10.6 diff --git a/packages/hmssdk_flutter/example/android/app/build.gradle b/packages/hmssdk_flutter/example/android/app/build.gradle index cb01c87b0..17dd04245 100644 --- a/packages/hmssdk_flutter/example/android/app/build.gradle +++ b/packages/hmssdk_flutter/example/android/app/build.gradle @@ -40,8 +40,8 @@ android { applicationId "live.hms.flutter" minSdkVersion 21 targetSdkVersion 34 - versionCode 520 - versionName "1.5.220" + versionCode 523 + versionName "1.5.223" } signingConfigs { diff --git a/packages/hmssdk_flutter/example/ios/Podfile.lock b/packages/hmssdk_flutter/example/ios/Podfile.lock index 56ece5a0b..c7f975b7c 100644 --- a/packages/hmssdk_flutter/example/ios/Podfile.lock +++ b/packages/hmssdk_flutter/example/ios/Podfile.lock @@ -303,7 +303,7 @@ SPEC CHECKSUMS: FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d FirebaseSessions: dbd14adac65ce996228652c1fc3a3f576bdf3ecc FirebaseSharedSwift: 20530f495084b8d840f78a100d8c5ee613375f6e - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_foreground_task: 21ef182ab0a29a3005cc72cd70e5f45cb7f7f817 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleMLKit: 2bd0dc6253c4d4f227aad460f69215a504b2980e diff --git a/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj index fc0c44fb7..37fb2667c 100644 --- a/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -248,7 +248,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1340; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = 100ms; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a6b826db2..5e31d3d34 100644 --- a/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.5.220 + 1.5.223 CFBundleSignature ???? CFBundleURLTypes @@ -48,7 +48,7 @@ CFBundleVersion - 520 + 523 ITSAppUsesNonExemptEncryption LSApplicationCategoryType diff --git a/packages/hmssdk_flutter/ios/Classes/Actions/HMSCameraControlsAction.swift b/packages/hmssdk_flutter/ios/Classes/Actions/HMSCameraControlsAction.swift index 3d8c2b185..e519c3e23 100644 --- a/packages/hmssdk_flutter/ios/Classes/Actions/HMSCameraControlsAction.swift +++ b/packages/hmssdk_flutter/ios/Classes/Actions/HMSCameraControlsAction.swift @@ -32,9 +32,15 @@ class HMSCameraControlsAction { case "toggle_flash": toggleFlash(result, hmsSDK) + + case "set_zoom": + setZoom(call, result, hmsSDK) + + case "reset_zoom": + resetZoom(result, hmsSDK) // these method calls return FlutterMethodNotImplemented to indicate that the requested functionality is not yet implemented. - case "is_tap_to_focus_supported", "is_zoom_supported": + case "is_tap_to_focus_supported", "is_zoom_supported", "get_max_zoom", "get_min_zoom": result(FlutterMethodNotImplemented) // If the method call does not match any of the above cases, also return FlutterMethodNotImplemented. @@ -170,4 +176,51 @@ class HMSCameraControlsAction { formatter.timeStyle = .medium return formatter.string(from: Date()) } + + static private func setZoom(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?){ + guard let localPeer = hmsSDK?.localPeer else { + result(HMSResultExtension.toDictionary(false, HMSErrorExtension.getError("\(#function) An instance of Local Peer could not be found. Please check if a Room is joined."))) + return + } + + guard let localVideoTrack = localPeer.localVideoTrack() + else { + result(HMSResultExtension.toDictionary(false, HMSErrorExtension.getError("\(#function) Video Track of Local Peer could not be found. Please check if the Local Peer has permission to publish video & video is unmuted currently."))) + return + } + + let arguments = call.arguments as? [AnyHashable: Any] + + guard let zoomValue = arguments?["zoom_value"] as? CGFloat + else{ + HMSErrorLogger.returnArgumentsError("zoom value can't be empty") + return + } + + localVideoTrack.modifyCaptureDevice { device in + + guard let device = device else { return } + + device.videoZoomFactor = zoomValue + } + } + + static private func resetZoom(_ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?){ + guard let localPeer = hmsSDK?.localPeer else { + result(HMSResultExtension.toDictionary(false, HMSErrorExtension.getError("\(#function) An instance of Local Peer could not be found. Please check if a Room is joined."))) + return + } + + guard let localVideoTrack = localPeer.localVideoTrack() + else { + result(HMSResultExtension.toDictionary(false, HMSErrorExtension.getError("\(#function) Video Track of Local Peer could not be found. Please check if the Local Peer has permission to publish video & video is unmuted currently."))) + return + } + + localVideoTrack.modifyCaptureDevice { device in + + guard let device = device else { return } + device.videoZoomFactor = 1.0 + } + } } diff --git a/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift b/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift index 9d56521d1..9db792145 100644 --- a/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift +++ b/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift @@ -315,7 +315,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene // MARK: - Advanced Camera Controls - case "capture_image_at_max_supported_resolution", "is_tap_to_focus_supported", "is_zoom_supported", "is_flash_supported", "toggle_flash": + case "capture_image_at_max_supported_resolution", "is_tap_to_focus_supported", "is_zoom_supported", "is_flash_supported", "toggle_flash", "set_zoom", "reset_zoom", "get_max_zoom", "get_min_zoom": HMSCameraControlsAction.cameraControlsAction(call, result, hmsSDK) // MARK: - Session Store diff --git a/packages/hmssdk_flutter/lib/src/common/platform_methods.dart b/packages/hmssdk_flutter/lib/src/common/platform_methods.dart index 7cb9c51bf..d47cb6551 100644 --- a/packages/hmssdk_flutter/lib/src/common/platform_methods.dart +++ b/packages/hmssdk_flutter/lib/src/common/platform_methods.dart @@ -165,9 +165,17 @@ enum PlatformMethod { captureSnapshot, getAllLogs, getAuthTokenByRoomCode, + + ///Camera Controls captureImageAtMaxSupportedResolution, isFlashSupported, toggleFlash, + isZoomSupported, + setZoom, + resetZoom, + getMaxZoom, + getMinZoom, + getSessionMetadataForKey, setSessionMetadataForKey, addKeyChangeListener, @@ -494,6 +502,16 @@ extension PlatformMethodValues on PlatformMethod { return "is_flash_supported"; case PlatformMethod.toggleFlash: return "toggle_flash"; + case PlatformMethod.isZoomSupported: + return "is_zoom_supported"; + case PlatformMethod.setZoom: + return "set_zoom"; + case PlatformMethod.resetZoom: + return "reset_zoom"; + case PlatformMethod.getMaxZoom: + return "get_max_zoom"; + case PlatformMethod.getMinZoom: + return "get_min_zoom"; case PlatformMethod.getSessionMetadataForKey: return "get_session_metadata_for_key"; case PlatformMethod.setSessionMetadataForKey: @@ -881,6 +899,16 @@ extension PlatformMethodValues on PlatformMethod { return PlatformMethod.isFlashSupported; case "toggle_flash": return PlatformMethod.toggleFlash; + case "is_zoom_supported": + return PlatformMethod.isZoomSupported; + case "set_zoom": + return PlatformMethod.setZoom; + case "reset_zoom": + return PlatformMethod.resetZoom; + case "get_max_zoom": + return PlatformMethod.getMaxZoom; + case "get_min_zoom": + return PlatformMethod.getMinZoom; case "get_session_metadata_for_key": return PlatformMethod.getSessionMetadataForKey; case "set_session_metadata_for_key": diff --git a/packages/hmssdk_flutter/lib/src/model/hms_camera_controls.dart b/packages/hmssdk_flutter/lib/src/model/hms_camera_controls.dart index ed08cf467..c1c439ef7 100644 --- a/packages/hmssdk_flutter/lib/src/model/hms_camera_controls.dart +++ b/packages/hmssdk_flutter/lib/src/model/hms_camera_controls.dart @@ -1,3 +1,6 @@ +import 'dart:developer'; +import 'dart:io'; + import 'package:hmssdk_flutter/src/common/platform_methods.dart'; import 'package:hmssdk_flutter/src/exceptions/hms_exception.dart'; import 'package:hmssdk_flutter/src/service/platform_service.dart'; @@ -61,4 +64,67 @@ class HMSCameraControls { return HMSException.fromMap(result["data"]["error"]); } } + + static Future isZoomSupported() async { + if (Platform.isAndroid) { + var result = + await PlatformService.invokeMethod(PlatformMethod.isZoomSupported); + if (result["success"]) { + return result["data"]; + } else { + return false; + } + } else { + return true; + } + } + + static Future setZoom({required double zoomValue}) async { + var result = + await PlatformService.invokeMethod(PlatformMethod.setZoom, arguments: { + "zoom_value": zoomValue, + }); + if (result["success"]) { + return result["data"]; + } else { + return HMSException.fromMap(result["data"]["error"]); + } + } + + static Future resetZoom() async { + var result = await PlatformService.invokeMethod(PlatformMethod.resetZoom); + if (result["success"]) { + return result["data"]; + } else { + return HMSException.fromMap(result["data"]["error"]); + } + } + + static Future getMinZoom() async { + if (Platform.isAndroid) { + var result = + await PlatformService.invokeMethod(PlatformMethod.getMinZoom); + if (result["success"]) { + return result["data"]; + } else { + return HMSException.fromMap(result["data"]["error"]); + } + } else { + log("getMinZoom is only available on android"); + } + } + + static Future getMaxZoom() async { + if (Platform.isAndroid) { + var result = + await PlatformService.invokeMethod(PlatformMethod.getMaxZoom); + if (result["success"]) { + return result["data"]; + } else { + return HMSException.fromMap(result["data"]["error"]); + } + } else { + log("getMaxZoom is only available on android"); + } + } }