diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index b37affd9c..e8be4fec8 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -4,15 +4,15 @@ cli: plugins: sources: - id: trunk - ref: v1.2.6 + ref: v1.3.0 uri: https://github.com/trunk-io/plugins lint: enabled: - actionlint@1.6.26 - - checkov@3.0.37 + - checkov@3.1.9 - osv-scanner@1.4.3 - trivy@0.47.0 - - trufflehog@3.62.1 + - trufflehog@3.63.2-rc0 - oxipng@9.0.0 - yamllint@1.33.0 - markdownlint@0.37.0 @@ -20,8 +20,8 @@ lint: - git-diff-check - shfmt@3.6.0 - shellcheck@0.9.0 - - gitleaks@8.18.0 - - svgo@3.0.3 + - gitleaks@8.18.1 + - svgo@3.0.5 - ktlint@1.0.1 runtimes: enabled: diff --git a/packages/hms_room_kit/CHANGELOG.md b/packages/hms_room_kit/CHANGELOG.md index b525226ac..ee07a4e65 100644 --- a/packages/hms_room_kit/CHANGELOG.md +++ b/packages/hms_room_kit/CHANGELOG.md @@ -5,6 +5,27 @@ | hms_room_kit | [![Pub Version](https://img.shields.io/pub/v/hms_room_kit)](https://pub.dev/packages/hms_room_kit) | | hmssdk_flutter | [![Pub Version](https://img.shields.io/pub/v/hmssdk_flutter)](https://pub.dev/packages/hmssdk_flutter) | +## 1.0.7 - 2023-12-08 + +| Package | Version | +| -------------- | ------------------------------------------------------------------------------------------------------ | +| hms_room_kit | 1.0.7 | +| hmssdk_flutter | 1.9.4 | + +### 🚀 Added + +- If `userName` is specified in `HMSPrebuiltOptions`, username editing on the preview screen will be disabled. + +### 🛠️ Fixed + +- Active speaker not updating on first page with active screenshare. + +- Improved overall performance of the application. + +### 🔄 Changed + +- `hms_room_kit` now uses `HMSTextureView` instead of `HMSVideoView` for rendering video. + ## 1.0.6 - 2023-11-17 ### 🚀 Added diff --git a/packages/hms_room_kit/README.md b/packages/hms_room_kit/README.md index fbb05e9b8..54d97eace 100644 --- a/packages/hms_room_kit/README.md +++ b/packages/hms_room_kit/README.md @@ -1,6 +1,6 @@ [![100ms-svg](https://user-images.githubusercontent.com/93931528/205858417-8c0a0d1b-2d46-4710-9316-7418092fd3d6.svg)](https://100ms.live/) -[![HMSLive Room Kit](https://img.shields.io/pub/v/hms_room_kit)](https://pub.dev/packages/hms_room_kit) +[![Pub Version](https://img.shields.io/pub/v/hms_room_kit)](https://pub.dev/packages/hms_room_kit) [![License](https://img.shields.io/github/license/100mslive/100ms-flutter)](https://www.100ms.live/) [![Documentation](https://img.shields.io/badge/Read-Documentation-blue)](https://docs.100ms.live/flutter/v2/foundation/basics) [![Discord](https://img.shields.io/discord/843749923060711464?label=Join%20on%20Discord)](https://100ms.live/discord) @@ -14,6 +14,13 @@ A powerful prebuilt UI library for audio/video conferencing, live streaming, and one-to-one calls. This package provides developers with a comprehensive set of tools and components to quickly integrate high-quality audio and video communication features into their Flutter applications. + +| Package | Version | +| -------------- | ------------------------------------------------------------------------------------------------------ | +| hms_room_kit | [![Pub Version](https://img.shields.io/pub/v/hms_room_kit)](https://pub.dev/packages/hms_room_kit) | +| hmssdk_flutter | [![Pub Version](https://img.shields.io/pub/v/hmssdk_flutter)](https://pub.dev/packages/hmssdk_flutter) | + + 📖 Read the Complete Documentation here: https://www.100ms.live/docs/flutter/v2/guides/quickstart 📲 Download the Sample iOS app here: @@ -104,7 +111,7 @@ Please follow the below instructions to test the app for the Android Platform: 1. Add minimum SDK version (`minSdkVersion 21`) in `android/app/build.gradle` file (inside `defaultConfig`). -```json +``` ... defaultConfig { ... @@ -116,7 +123,7 @@ defaultConfig { 2. To add PIP support in your app manifest files, add: -```xml +``` ``` @@ -162,11 +169,44 @@ Please follow the below instructions to test the app for iOS Platform 1. Add the target platform version as (`platform :ios, '12.0'`) in `ios/Podfile` -```json +``` platform :ios, '12.0' ``` +2. Allow camera, recording audio and internet permissions by adding the below snippet to the `ios/Runner/info.plist` file. + + ``` + NSMicrophoneUsageDescription + {YourAppName} wants to use your microphone + + NSCameraUsageDescription + {YourAppName} wants to use your camera + + NSLocalNetworkUsageDescription + {YourAppName} App wants to use your local network + + NSBluetoothAlwaysUsageDescription + {YourAppName} needs access to bluetooth to connect to nearby devices. + ``` + +3. Add the below snippet to the `ios/Podfile` in post_install section: + + ``` + target.build_configurations.each do |config| + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + '$(inherited)', + ## dart: PermissionGroup.camera + 'PERMISSION_CAMERA=1', + + ## dart: PermissionGroup.microphone + 'PERMISSION_MICROPHONE=1', + + ## dart: PermissionGroup.bluetooth + 'PERMISSION_BLUETOOTH=1', + ] + end + ``` -2. To add PIP support in your iOS app: +4. To add PIP support in your iOS app: - Minimum Requirements: - Minimum iOS version required to support PiP is iOS 15 @@ -179,9 +219,9 @@ After you receive permission from Apple, add the Entitlement to your app by open ![Entitlements](https://www.100ms.live/docs/docs/v2/flutter-multitasking-camera-entitlement.png) -3. To add screen share support in iOS app, checkout the docs [here](https://www.100ms.live/docs/flutter/v2/how-to-guides/set-up-video-conferencing/screen-share#ios-setup) +5. To add screen share support in iOS app, checkout the docs [here](https://www.100ms.live/docs/flutter/v2/how-to-guides/set-up-video-conferencing/screen-share#ios-setup) -4. Pass the `iOSScreenshareConfig` in `HMSPrebuiltOptions` parameter of `HMSPrebuilt` widget to enable screen share in your app. +6. Pass the `iOSScreenshareConfig` in `HMSPrebuiltOptions` parameter of `HMSPrebuilt` widget to enable screen share in your app. ```dart // Pass the correct App Group & Preferred Extension parameters in HMSIOSScreenshareConfig class. diff --git a/packages/hms_room_kit/example/ios/Podfile.lock b/packages/hms_room_kit/example/ios/Podfile.lock index 96a60894a..5c31dbc5f 100644 --- a/packages/hms_room_kit/example/ios/Podfile.lock +++ b/packages/hms_room_kit/example/ios/Podfile.lock @@ -6,14 +6,14 @@ PODS: - HMSBroadcastExtensionSDK (0.0.9) - HMSHLSPlayerSDK (0.0.2): - HMSAnalyticsSDK (= 0.0.2) - - HMSSDK (1.2.1): + - HMSSDK (1.3.0): - HMSAnalyticsSDK (= 0.0.2) - HMSWebRTC (= 1.0.5116) - hmssdk_flutter (1.9.3): - Flutter - HMSBroadcastExtensionSDK (= 0.0.9) - HMSHLSPlayerSDK (= 0.0.2) - - HMSSDK (= 1.2.1) + - HMSSDK (= 1.3.0) - HMSWebRTC (1.0.5116) - path_provider_foundation (0.0.1): - Flutter @@ -70,15 +70,15 @@ SPEC CHECKSUMS: HMSAnalyticsSDK: 4d2a88a729b1eb42f3d25f217c28937ec318a5b7 HMSBroadcastExtensionSDK: d80fe325f6c928bd8e5176290b5a4b7ae15d6fbb HMSHLSPlayerSDK: 6a54ad4d12f3dc2270d1ecd24019d71282a4f6a3 - HMSSDK: 6e176238d9972b78b7831398bbfefc3fddadad55 - hmssdk_flutter: ceffbce79af4203f64def3fefe79f8f87c75a49c + HMSSDK: 631908d772646b66b3c4f7f4e1fb2681f7abb990 + hmssdk_flutter: 0b17359aefb7ec222e1b0cc44a595759a317ff98 HMSWebRTC: ae54e9dd91b869051b283b43b14f57d43b7bf8e1 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 - url_launcher_ios: 68d46cc9766d0c41dbdc884310529557e3cd7a86 + url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b -COCOAPODS: 1.14.2 +COCOAPODS: 1.14.3 diff --git a/packages/hms_room_kit/example/pubspec.lock b/packages/hms_room_kit/example/pubspec.lock index 2a4a4c279..49e421dac 100644 --- a/packages/hms_room_kit/example/pubspec.lock +++ b/packages/hms_room_kit/example/pubspec.lock @@ -41,14 +41,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - bot_toast: - dependency: transitive - description: - name: bot_toast - sha256: "6b93030a99a98335b8827ecd83021e92e885ffc61d261d3825ffdecdd17f3bdf" - url: "https://pub.dev" - source: hosted - version: "4.1.3" characters: dependency: transitive description: @@ -85,10 +77,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "445db18de832dba8d851e287aff8ccf169bed30d2e94243cb54c7d2f1ed2142c" + sha256: "2f9d2cbccb76127ba28528cb3ae2c2326a122446a83de5a056aaa3880d3882c5" url: "https://pub.dev" source: hosted - version: "0.3.3+6" + version: "0.3.3+7" crypto: dependency: transitive description: @@ -222,15 +214,15 @@ packages: path: ".." relative: true source: path - version: "1.0.6" + version: "1.0.7" hmssdk_flutter: dependency: transitive description: name: hmssdk_flutter - sha256: "70d00820ab5cb02c03c2d4f41be70dd9dc3406521a469b8989d1e650232efd9a" + sha256: "27dfc516ee4673ff092fc4e18487c315780b8470a2fbcf0f486bdfa529fc53cb" url: "https://pub.dev" source: hosted - version: "1.9.3" + version: "1.9.4" http: dependency: transitive description: @@ -451,10 +443,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.7" pointycastle: dependency: transitive description: @@ -640,10 +632,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a" + sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.2.1" url_launcher_linux: dependency: transitive description: @@ -744,10 +736,10 @@ packages: dependency: transitive description: name: win32 - sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f" + sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.1.1" xdg_directories: dependency: transitive description: diff --git a/packages/hms_room_kit/example/pubspec.yaml b/packages/hms_room_kit/example/pubspec.yaml index c7ad020a2..60a14146d 100644 --- a/packages/hms_room_kit/example/pubspec.yaml +++ b/packages/hms_room_kit/example/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # 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.6 +version: 1.0.7 environment: sdk: ">=2.19.6 <3.0.0" diff --git a/packages/hms_room_kit/lib/src/common/utility_functions.dart b/packages/hms_room_kit/lib/src/common/utility_functions.dart index 0981692f0..6049c2b87 100644 --- a/packages/hms_room_kit/lib/src/common/utility_functions.dart +++ b/packages/hms_room_kit/lib/src/common/utility_functions.dart @@ -1,13 +1,12 @@ -//Package imports +///Dart imports import 'dart:io'; import 'dart:math' as math; -import 'package:bot_toast/bot_toast.dart'; +///Package imports import 'package:flutter/material.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:hms_room_kit/hms_room_kit.dart'; import 'package:hmssdk_flutter/hmssdk_flutter.dart'; -import 'package:hms_room_kit/src/common/animated_text.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -266,16 +265,16 @@ class Utilities { 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: HMSTextStyle.setTextStyle(fontSize: 14), - text: message, - contentColor: Colors.black87, - duration: Duration(seconds: time)); + // BotToast.showText( + // align: align, + // wrapToastAnimation: (controller, cancelFunc, widget) => + // AnimatedTextWidget( + // text: message, duration: Duration(seconds: time)), + // onlyOne: false, + // textStyle: HMSTextStyle.setTextStyle(fontSize: 14), + // text: message, + // contentColor: Colors.black87, + // duration: Duration(seconds: time)); } static Future getStringData({required String key}) async { diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_header.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_header.dart index 33d8f8a5f..9e9c139b0 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_header.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_header.dart @@ -42,7 +42,10 @@ class HLSViewerHeader extends StatelessWidget { ? Container() : HMSRoomLayout.roleLayoutData!.logo!.url!.contains("svg") ? SvgPicture.network( - HMSRoomLayout.roleLayoutData!.logo!.url!) + HMSRoomLayout.roleLayoutData!.logo!.url!, + height: 30, + width: 30, + ) : Image.network( HMSRoomLayout.roleLayoutData!.logo!.url!, errorBuilder: (context, exception, _) { diff --git a/packages/hms_room_kit/lib/src/meeting/meeting_page.dart b/packages/hms_room_kit/lib/src/meeting/meeting_page.dart index 5bbc81f4d..e4d869fa2 100644 --- a/packages/hms_room_kit/lib/src/meeting/meeting_page.dart +++ b/packages/hms_room_kit/lib/src/meeting/meeting_page.dart @@ -292,9 +292,11 @@ class _MeetingPageState extends State { context: context, size: size) - : const CustomOneToOneGrid( + : CustomOneToOneGrid( isLocalInsetPresent: false, + peerTracks: + data.item1, ), )); }); diff --git a/packages/hms_room_kit/lib/src/meeting/meeting_store.dart b/packages/hms_room_kit/lib/src/meeting/meeting_store.dart index 49381c034..0c5e6509c 100644 --- a/packages/hms_room_kit/lib/src/meeting/meeting_store.dart +++ b/packages/hms_room_kit/lib/src/meeting/meeting_store.dart @@ -231,6 +231,12 @@ class MeetingStore extends ChangeNotifier ///Check whether recording is in intialising state bool isRecordingInInitialisingState = false; + ///Pool of video views + List viewControllers = []; + + ///Video View for screenshare + HMSTextureViewController? screenshareViewController; + Future join(String userName, String roomCode, {HMSConfig? roomConfig}) async { //If roomConfig is null then only we call the methods to get the authToken @@ -831,7 +837,7 @@ class MeetingStore extends ChangeNotifier getCurrentAudioDevice(); getAudioDevicesList(); notifyListeners(); - + setViewControllers(); // if (Platform.isIOS && // HMSRoomLayout.roleLayoutData?.screens?.conferencing?.defaultConf != // null) { @@ -844,6 +850,12 @@ class MeetingStore extends ChangeNotifier // } } + void setViewControllers() { + for (var i = 0; i < 6; i++) { + viewControllers.add(HMSTextureViewController(addTrackByDefault: false)); + } + } + void setParticipantsList(List roles) { String? onStageRoles = HMSRoomLayout.roleLayoutData?.screens?.conferencing ?.defaultConf?.elements?.onStageExp?.onStageRole; @@ -993,7 +1005,7 @@ class MeetingStore extends ChangeNotifier @override void onHMSError({required HMSException error}) { - log("onHMSError-> error: ${error.code} ${error.message}"); + log("onHMSError-> error: ${error.code?.errorCode} ${error.message}"); hmsException = error; Utilities.showNotification(error.message ?? "", "error"); notifyListeners(); @@ -1039,10 +1051,21 @@ class MeetingStore extends ChangeNotifier @override void onUpdateSpeakers({required List updateSpeakers}) { //To handle the active speaker mode scenario + + ///This is to handle whether to bring the user to first index + ///In case of normal layout if the user is on the first page i.e + ///index < 6 we don't move the peer to first page. Similarly, if + ///screenshare is on and index < 2 we don't update the position + ///of the peer + int peersInActiveSpeakerLayout = 6; + if (screenShareCount > 0) { + peersInActiveSpeakerLayout = 2; + } + if ((currentPage == 0) && (meetingMode == MeetingMode.activeSpeakerWithInset || meetingMode == MeetingMode.activeSpeakerWithoutInset) && - peerTracks.length > 6) { + peerTracks.length > peersInActiveSpeakerLayout) { /* Here we iterate through the updateSpeakers list * and do the following: * Find the index of the peer @@ -1052,7 +1075,7 @@ class MeetingStore extends ChangeNotifier for (var speaker in updateSpeakers) { int index = peerTracks.indexWhere((previousSpeaker) => previousSpeaker.uid == "${speaker.peer.peerId}mainVideo"); - if (index > 5) { + if (index > (peersInActiveSpeakerLayout - 1)) { PeerTrackNode activeSpeaker = peerTracks[index]; peerTracks.removeAt(index); peerTracks.insert(screenShareCount, activeSpeaker); @@ -1062,8 +1085,7 @@ class MeetingStore extends ChangeNotifier notifyListeners(); } - //This is to handle the borders around the tiles of peers who are currently speaking - //Reseting the borders of the tile everytime the update is received + //This is to handle the audio level ui on the tiles of peers who are currently speaking if (activeSpeakerIds.isNotEmpty) { for (var key in activeSpeakerIds) { int index = peerTracks.indexWhere((element) => element.uid == key); @@ -1074,7 +1096,6 @@ class MeetingStore extends ChangeNotifier activeSpeakerIds.clear(); } - //Setting the border for peers who are speaking for (var element in updateSpeakers) { activeSpeakerIds.add("${element.peer.peerId}mainVideo"); int index = peerTracks @@ -1262,6 +1283,13 @@ class MeetingStore extends ChangeNotifier isRoomEnded = true; resetForegroundTaskAndOrientation(); + for (var element in viewControllers) { + element.disposeTextureView(); + } + screenshareViewController?.disposeTextureView(); + viewControllers.clear(); + screenshareViewController = null; + ///Here we call the method passed by the user in HMSPrebuilt as a callback if (Constant.onLeave != null) { Constant.onLeave!(); @@ -1327,7 +1355,9 @@ class MeetingStore extends ChangeNotifier } void addMessage(HMSMessage message) { - messages.add(message); + if (message.type == "chat") { + messages.add(message); + } } void updatePeerAt(HMSPeer peer) { @@ -1592,6 +1622,9 @@ class MeetingStore extends ChangeNotifier int peerIndex = peerTracks.indexWhere( (element) => element.uid == peer.peerId + track.trackId); if (peerIndex != -1) { + if ((screenShareCount - 1) == currentScreenSharePage) { + currentScreenSharePage--; + } screenShareCount--; peerTracks.removeAt(peerIndex); notifyListeners(); diff --git a/packages/hms_room_kit/lib/src/model/peer_track_node.dart b/packages/hms_room_kit/lib/src/model/peer_track_node.dart index afbfee2e3..826c10a01 100644 --- a/packages/hms_room_kit/lib/src/model/peer_track_node.dart +++ b/packages/hms_room_kit/lib/src/model/peer_track_node.dart @@ -10,18 +10,17 @@ class PeerTrackNode extends ChangeNotifier { String uid; HMSVideoTrack? track; HMSAudioTrack? audioTrack; - bool isOffscreen; int? networkQuality; RTCStats? stats; int audioLevel; bool pinTile; + bool isOffscreen = true; PeerTrackNode( {required this.peer, this.track, this.audioTrack, required this.uid, - this.isOffscreen = true, this.networkQuality = -1, this.stats, this.audioLevel = -1, @@ -29,7 +28,7 @@ class PeerTrackNode extends ChangeNotifier { @override String toString() { - return 'PeerTrackNode{peerId: ${peer.peerId}, name: ${peer.name}, track: $track}, isVideoOn: $isOffscreen }'; + return 'PeerTrackNode{peerId: ${peer.peerId}, name: ${peer.name}, track: $track} }'; } void notify() { diff --git a/packages/hms_room_kit/lib/src/preview/preview_page.dart b/packages/hms_room_kit/lib/src/preview/preview_page.dart index 9b8c70ab7..3215c8e03 100644 --- a/packages/hms_room_kit/lib/src/preview/preview_page.dart +++ b/packages/hms_room_kit/lib/src/preview/preview_page.dart @@ -228,7 +228,7 @@ class _PreviewPageState extends State { ///Otherwise it will render the circular avatar child: (previewStore.isVideoOn) ? Center( - child: HMSVideoView( + child: HMSTextureView( scaleType: ScaleType .SCALE_ASPECT_FILL, track: previewStore @@ -322,6 +322,9 @@ class _PreviewPageState extends State { height: 48, width: width * 0.50, child: TextField( + enabled: widget.name + .trim() + .isEmpty, cursorColor: HMSThemeColors .onSurfaceHighEmphasis, diff --git a/packages/hms_room_kit/lib/src/screen_controller.dart b/packages/hms_room_kit/lib/src/screen_controller.dart index a7ab8d809..9b0090649 100644 --- a/packages/hms_room_kit/lib/src/screen_controller.dart +++ b/packages/hms_room_kit/lib/src/screen_controller.dart @@ -158,7 +158,7 @@ class _ScreenControllerState extends State { value: _previewStore, child: PreviewPage( roomCode: Constant.roomCode, - name: widget.options?.userName ?? "", + name: widget.options?.userName?.trim() ?? "", options: widget.options, )) : PreviewPermissions( diff --git a/packages/hms_room_kit/lib/src/widgets/common_widgets/degrade_tile.dart b/packages/hms_room_kit/lib/src/widgets/common_widgets/degrade_tile.dart index 433b48d15..750f726f3 100644 --- a/packages/hms_room_kit/lib/src/widgets/common_widgets/degrade_tile.dart +++ b/packages/hms_room_kit/lib/src/widgets/common_widgets/degrade_tile.dart @@ -1,6 +1,3 @@ -///Dart imports -import 'dart:ui'; - ///Package imports import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -16,67 +13,51 @@ import 'package:hms_room_kit/src/widgets/common_widgets/hms_subtitle_text.dart'; ///when the connection is poor ///The tile is shown when the track is degraded ///The tile is hidden when the track is not degraded -class DegradeTile extends StatefulWidget { - const DegradeTile({Key? key}) : super(key: key); +class DegradeTile extends StatelessWidget { + final BoxConstraints constraints; + const DegradeTile({Key? key, required this.constraints}) : super(key: key); - @override - State createState() => _DegradeTileState(); -} - -class _DegradeTileState extends State { @override Widget build(BuildContext context) { return Selector( builder: (_, data, __) { - return Visibility( - visible: data, - child: - LayoutBuilder(builder: (context, BoxConstraints constraints) { - return Container( + return data + ? Container( decoration: const BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(10))), child: Stack( children: [ - ClipRRect( - ///Here we are using a backdrop filter to blur the background - ///when the connection is poor - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), - child: Container( - color: Colors.transparent, - alignment: Alignment.center, - child: Align( - alignment: Alignment.center, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - HMSSubheadingText( - text: "Poor connection", - textColor: - HMSThemeColors.onSurfaceHighEmphasis, - fontWeight: FontWeight.w600, - letterSpacing: 0.1, - ), - const SizedBox( - height: 4, - ), - HMSSubtitleText( - text: - "The video will resume\n automatically when the\n connection improves", - textColor: - HMSThemeColors.onSurfaceHighEmphasis, - ) - ], + Container( + color: Colors.black.withOpacity(0.5), + alignment: Alignment.center, + child: Align( + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + HMSSubheadingText( + text: "Poor connection", + textColor: HMSThemeColors.onSurfaceHighEmphasis, + fontWeight: FontWeight.w600, + letterSpacing: 0.1, + ), + const SizedBox( + height: 4, ), - ), + HMSSubtitleText( + text: + "The video will resume\n automatically when the\n connection improves", + textColor: HMSThemeColors.onSurfaceHighEmphasis, + ) + ], ), ), ), NameAndNetwork(maxWidth: constraints.maxWidth), ], ), - ); - })); + ) + : const SizedBox(); }, selector: (_, peerTrackNode) => peerTrackNode.track?.isDegraded ?? false); diff --git a/packages/hms_room_kit/lib/src/widgets/common_widgets/peer_tile.dart b/packages/hms_room_kit/lib/src/widgets/common_widgets/peer_tile.dart index 9c50c366a..f131045ac 100644 --- a/packages/hms_room_kit/lib/src/widgets/common_widgets/peer_tile.dart +++ b/packages/hms_room_kit/lib/src/widgets/common_widgets/peer_tile.dart @@ -1,4 +1,6 @@ // Package imports +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:focus_detector/focus_detector.dart'; @@ -34,13 +36,15 @@ class PeerTile extends StatefulWidget { final double avatarRadius; final double avatarTitleFontSize; final double avatarTitleTextLineHeight; + final HMSTextureViewController? videoViewController; const PeerTile( {Key? key, this.scaleType = ScaleType.SCALE_ASPECT_FILL, this.islongPressEnabled = true, this.avatarRadius = 34, this.avatarTitleFontSize = 34, - this.avatarTitleTextLineHeight = 40}) + this.avatarTitleTextLineHeight = 40, + this.videoViewController}) : super(key: key); @override @@ -56,314 +60,332 @@ class _PeerTileState extends State { return Semantics( label: "fl_${context.read().peer.name}_video_tile", child: FocusDetector( - onFocusLost: () { - if (mounted) { - Provider.of(context, listen: false) - .setOffScreenStatus(true); - } - }, - onFocusGained: () { + onFocusLost: () { + if (mounted) { Provider.of(context, listen: false) - .setOffScreenStatus(false); - }, - key: Key(context.read().uid), - //Here we check whether the video track is a regular - //video track or a screen share track - //We check this by checking the uid of the track - //If it contains `mainVideo` then it is a regular video track - //else it is a screen share track - child: LayoutBuilder(builder: (context, BoxConstraints constraints) { - return context.read().uid.contains("mainVideo") - ? Container( - key: key, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: HMSThemeColors.backgroundDefault, - ), - child: Semantics( - label: - "fl_${context.read().peer.name}_video_on", - child: Stack( - children: [ - VideoView( + .setOffScreenStatus(true); + } + }, + onFocusGained: () { + Provider.of(context, listen: false) + .setOffScreenStatus(false); + if (context.read().track != null) { + log("HMSVideoViewController add video track ${context.read().peer.name} trackType: ${context.read().track?.source}"); + widget.videoViewController + ?.addTrack(track: context.read().track!); + } + }, + child: LayoutBuilder(builder: (context, BoxConstraints constraints) { + return context.read().uid.contains("mainVideo") + ? Container( + key: key, + decoration: BoxDecoration( + color: HMSThemeColors.backgroundDefault, + ), + child: Semantics( + label: + "fl_${context.read().peer.name}_video_on", + child: Stack( + children: [ + ///ClipRRect is used to round the video edges + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: VideoView( uid: context.read().uid, scaleType: widget.scaleType, avatarTitleFontSize: widget.avatarTitleFontSize, avatarRadius: widget.avatarRadius, avatarTitleTextLineHeight: widget.avatarTitleTextLineHeight, + videoViewController: widget.videoViewController, ), - Semantics( - label: - "fl_${context.read().peer.name}_degraded_tile", - child: const DegradeTile(), + ), + Semantics( + label: + "fl_${context.read().peer.name}_degraded_tile", + child: DegradeTile( + constraints: constraints, ), - NameAndNetwork(maxWidth: constraints.maxWidth), - const HandRaise(), //top left - const BRBTag(), //top left - const AudioMuteStatus(), //top right - context.read().peer.isLocal - ? const LocalPeerMoreOption( - isInsetTile: false, - ) - : const MoreOption(), //bottom right - Semantics( - label: "fl_stats_on_tile", - child: RTCStatsView( - isLocal: - context.read().peer.isLocal), - ) - ], - ), + ), + NameAndNetwork(maxWidth: constraints.maxWidth), + const HandRaise(), //top left + const BRBTag(), //top left + const AudioMuteStatus(), //top right + context.read().peer.isLocal + ? const LocalPeerMoreOption( + isInsetTile: false, + ) + : const MoreOption(), //bottom right + Semantics( + label: "fl_stats_on_tile", + child: RTCStatsView( + isLocal: + context.read().peer.isLocal), + ) + ], ), - ) - : Semantics( - label: - "fl_${context.read().peer.name}_screen_share_tile", - child: LayoutBuilder( - builder: (context, BoxConstraints constraints) { - return Container( - decoration: BoxDecoration( - border: Border.all( - color: HMSThemeColors.surfaceDim, width: 1.0), - color: Colors.transparent, - borderRadius: - const BorderRadius.all(Radius.circular(10))), - key: key, - child: Stack( - children: [ - VideoView( - uid: context.read().uid, - scaleType: widget.scaleType, - ), - Positioned( - top: 5, - right: 5, - child: GestureDetector( - ///This is to show the screenshare in full screen - onTap: () { - showGeneralDialog( - context: context, - transitionBuilder: (dialogContext, - animation, - secondaryAnimation, - value) { - if (mounted) { - ///Setting the screenshare context - ///in the meeting store to store the current context - ///so that we can pop the dialog from the meeting store when screenshare is stopped - context - .read() - .screenshareContext = - dialogContext; - } + ), + ) + : Semantics( + label: + "fl_${context.read().peer.name}_screen_share_tile", + child: LayoutBuilder( + builder: (context, BoxConstraints constraints) { + return Container( + decoration: BoxDecoration( + border: Border.all( + color: HMSThemeColors.surfaceDim, width: 1.0), + color: Colors.transparent, + borderRadius: + const BorderRadius.all(Radius.circular(10))), + key: key, + child: Stack( + children: [ + VideoView( + uid: context.read().uid, + scaleType: widget.scaleType, + ), + Positioned( + top: 5, + right: 5, + child: GestureDetector( + ///This is to show the screenshare in full screen + onTap: () { + showGeneralDialog( + context: context, + transitionBuilder: (dialogContext, + animation, + secondaryAnimation, + value) { + if (mounted) { + ///Setting the screenshare context + ///in the meeting store to store the current context + ///so that we can pop the dialog from the meeting store when screenshare is stopped + context + .read() + .screenshareContext = + dialogContext; + } - ///Here we check whether the full screen screenshare is mounted or not - return context.mounted - ? Transform.scale( - scale: animation.value, - child: Opacity( - opacity: animation.value, - child: ListenableProvider - .value( - value: context.read< - PeerTrackNode>(), - child: Scaffold( - body: SafeArea( - child: Container( - color: HMSThemeColors - .backgroundDim, - height: - MediaQuery.of( - context) - .size - .height, - width: - MediaQuery.of( - context) - .size - .width, - child: Stack( - children: [ - InteractiveViewer( - child: ListenableProvider - .value( - value: context + ///Here we check whether the full screen screenshare is mounted or not + return context.mounted + ? Transform.scale( + scale: animation.value, + child: Opacity( + opacity: animation.value, + child: ListenableProvider + .value( + value: context.read< + PeerTrackNode>(), + child: Scaffold( + body: SafeArea( + child: Container( + color: HMSThemeColors + .backgroundDim, + height: + MediaQuery.of( + context) + .size + .height, + width: + MediaQuery.of( + context) + .size + .width, + child: Stack( + children: [ + InteractiveViewer( + child: + ListenableProvider + .value( + value: context + .read< + MeetingStore>(), + child: + VideoView( + uid: context .read< - MeetingStore>(), - child: - VideoView( - uid: context - .read() - .uid, - scaleType: - widget.scaleType, - ), + PeerTrackNode>() + .uid, + scaleType: + widget + .scaleType, ), ), - Positioned( - top: 5, - right: 5, + ), + Positioned( + top: 5, + right: 5, + child: + GestureDetector( + onTap: + () { + Navigator.pop( + dialogContext); + context + .read() + .screenshareContext = null; + }, child: - GestureDetector( - onTap: - () { - Navigator.pop( - dialogContext); - context - .read() - .screenshareContext = null; - }, + Container( + height: + 40, + width: + 40, + decoration: BoxDecoration( + color: + HMSThemeColors.backgroundDim.withAlpha(64), + borderRadius: BorderRadius.circular(8)), child: - Container( - height: - 40, - width: - 40, - decoration: BoxDecoration( - color: HMSThemeColors.backgroundDim.withAlpha(64), - borderRadius: BorderRadius.circular(8)), + Center( child: - Center( - child: - SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/minimize.svg", - height: 16, - width: 16, - semanticsLabel: "minimize_label", - colorFilter: ColorFilter.mode(HMSThemeColors.onSurfaceHighEmphasis, BlendMode.srcIn), - ), + SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/minimize.svg", + height: + 16, + width: + 16, + semanticsLabel: + "minimize_label", + colorFilter: + ColorFilter.mode(HMSThemeColors.onSurfaceHighEmphasis, BlendMode.srcIn), ), ), - )), - Positioned( - //Bottom left - bottom: 5, - left: 5, + ), + )), + Positioned( + //Bottom left + bottom: 5, + left: 5, + child: + Container( + decoration: BoxDecoration( + color: HMSThemeColors + .backgroundDim + .withOpacity( + 0.64), + borderRadius: + BorderRadius.circular(8)), child: - Container( - decoration: BoxDecoration( - color: HMSThemeColors.backgroundDim.withOpacity( - 0.64), - borderRadius: - BorderRadius.circular(8)), + Center( child: - Center( + Padding( + padding: const EdgeInsets + .only( + left: + 8.0, + right: + 4, + top: + 4, + bottom: + 4), child: - Padding( - padding: const EdgeInsets - .only( - left: 8.0, - right: 4, - top: 4, - bottom: 4), - child: - Row( - mainAxisSize: - MainAxisSize.min, - crossAxisAlignment: - CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/screen_share.svg", - height: 20, - width: 20, - colorFilter: ColorFilter.mode(HMSThemeColors.onSurfaceHighEmphasis, BlendMode.srcIn), - ), - const SizedBox( - width: 6, - ), - ScreenshareTileName(maxWidth: constraints.maxWidth) - ], - ), + Row( + mainAxisSize: + MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/screen_share.svg", + height: 20, + width: 20, + colorFilter: ColorFilter.mode(HMSThemeColors.onSurfaceHighEmphasis, BlendMode.srcIn), + ), + const SizedBox( + width: 6, + ), + ScreenshareTileName(maxWidth: constraints.maxWidth) + ], ), ), ), ), - ], - ), + ), + ], ), ), ), - )), - ) - : Container(); - }, - pageBuilder: (ctx, animation, - secondaryAnimation) { - return Container(); - }); - }, - child: Container( - height: 40, - width: 40, - decoration: BoxDecoration( - color: HMSThemeColors.backgroundDim - .withAlpha(64), - borderRadius: BorderRadius.circular(8)), - child: Center( - child: SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/maximize.svg", - height: 16, - width: 16, - semanticsLabel: "maximize_label", + ), + )), + ) + : Container(); + }, + pageBuilder: + (ctx, animation, secondaryAnimation) { + return Container(); + }); + }, + child: Container( + height: 40, + width: 40, + decoration: BoxDecoration( + color: HMSThemeColors.backgroundDim + .withAlpha(64), + borderRadius: BorderRadius.circular(8)), + child: Center( + child: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/maximize.svg", + height: 16, + width: 16, + semanticsLabel: "maximize_label", + colorFilter: ColorFilter.mode( + HMSThemeColors.onSurfaceHighEmphasis, + BlendMode.srcIn), + ), + ), + ), + )), + Positioned( + //Bottom left + bottom: 5, + left: 5, + child: Container( + decoration: BoxDecoration( + color: HMSThemeColors.backgroundDim + .withOpacity(0.64), + borderRadius: BorderRadius.circular(8)), + child: Center( + child: Padding( + padding: const EdgeInsets.only( + left: 8.0, right: 4, top: 4, bottom: 4), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/screen_share.svg", + height: 20, + width: 20, colorFilter: ColorFilter.mode( HMSThemeColors .onSurfaceHighEmphasis, BlendMode.srcIn), ), - ), - ), - )), - Positioned( - //Bottom left - bottom: 5, - left: 5, - child: Container( - decoration: BoxDecoration( - color: HMSThemeColors.backgroundDim - .withOpacity(0.64), - borderRadius: BorderRadius.circular(8)), - child: Center( - child: Padding( - padding: const EdgeInsets.only( - left: 8.0, right: 4, top: 4, bottom: 4), - child: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/screen_share.svg", - height: 20, - width: 20, - colorFilter: ColorFilter.mode( - HMSThemeColors - .onSurfaceHighEmphasis, - BlendMode.srcIn), - ), - const SizedBox( - width: 6, - ), - ScreenshareTileName( - maxWidth: constraints.maxWidth) - ], - ), + const SizedBox( + width: 6, + ), + ScreenshareTileName( + maxWidth: constraints.maxWidth) + ], ), ), ), ), - const RTCStatsView(isLocal: false), - ], - ), - ); - }), - ); - })), + ), + const RTCStatsView(isLocal: false), + ], + ), + ); + }), + ); + }), + ), ); } } diff --git a/packages/hms_room_kit/lib/src/widgets/common_widgets/video_view.dart b/packages/hms_room_kit/lib/src/widgets/common_widgets/video_view.dart index 9bcc19af0..c6c3234cd 100644 --- a/packages/hms_room_kit/lib/src/widgets/common_widgets/video_view.dart +++ b/packages/hms_room_kit/lib/src/widgets/common_widgets/video_view.dart @@ -33,6 +33,8 @@ class VideoView extends StatefulWidget { final double avatarRadius; final double avatarTitleFontSize; final double avatarTitleTextLineHeight; + final HMSTextureViewController? videoViewController; + const VideoView( {Key? key, this.viewSize, @@ -42,7 +44,8 @@ class VideoView extends StatefulWidget { this.scaleType = ScaleType.SCALE_ASPECT_FILL, this.avatarRadius = 34, this.avatarTitleFontSize = 34, - this.avatarTitleTextLineHeight = 32}) + this.avatarTitleTextLineHeight = 32, + this.videoViewController}) : super(key: key); @override @@ -75,43 +78,39 @@ class _VideoViewState extends State { ///If the video track source is REGULAR i.e. it is a camera video track ///we set the scaletype as FILL return (data.item1?.source != "REGULAR") - ? ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(10)), - child: InteractiveViewer( - // [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: widget.scaleType, - track: data.item1!, - setMirror: false, - disableAutoSimulcastLayerSelect: - !(context.read().isAutoSimulcast), - ), + ? InteractiveViewer( + // [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: HMSTextureView( + controller: widget.videoViewController, + addTrackByDefault: + !context.read().isOffscreen, + key: Key(data.item1!.trackId), + scaleType: ScaleType.SCALE_ASPECT_FIT, + track: data.item1!, + setMirror: false, + disableAutoSimulcastLayerSelect: + !(context.read().isAutoSimulcast), ), ) - : ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular(10), - ), - child: SizedBox( - // [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, - track: data.item1!, - setMirror: data.item1.runtimeType == HMSLocalVideoTrack, - disableAutoSimulcastLayerSelect: - !(context.read().isAutoSimulcast), - ), + : SizedBox( + // [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: HMSTextureView( + controller: widget.videoViewController, + addTrackByDefault: + !context.read().isOffscreen, + key: Key(data.item1!.trackId), + scaleType: ScaleType.SCALE_ASPECT_FILL, + track: data.item1!, + setMirror: data.item1.runtimeType == HMSLocalVideoTrack, + disableAutoSimulcastLayerSelect: + !(context.read().isAutoSimulcast), ), ); } }, - selector: (_, peerTrackNode) => Tuple3( - peerTrackNode.track, - (peerTrackNode.isOffscreen), - (peerTrackNode.track?.isMute ?? true))); + selector: (_, peerTrackNode) => Tuple3(peerTrackNode.track, + peerTrackNode.isOffscreen, (peerTrackNode.track?.isMute ?? true))); } } diff --git a/packages/hms_room_kit/lib/src/widgets/grid_layouts/listenable_peer_widget.dart b/packages/hms_room_kit/lib/src/widgets/grid_layouts/listenable_peer_widget.dart index 557f4a512..47c29fc16 100644 --- a/packages/hms_room_kit/lib/src/widgets/grid_layouts/listenable_peer_widget.dart +++ b/packages/hms_room_kit/lib/src/widgets/grid_layouts/listenable_peer_widget.dart @@ -1,5 +1,6 @@ ///Package imports import 'package:flutter/cupertino.dart'; +import 'package:hms_room_kit/src/meeting/meeting_store.dart'; import 'package:provider/provider.dart'; ///Project imports @@ -24,9 +25,23 @@ class ListenablePeerWidget extends StatelessWidget { return ChangeNotifierProvider.value( key: ValueKey("${peerTracks[index].uid}video_view"), value: peerTracks[index], - child: PeerTile( - key: ValueKey("${peerTracks[index].uid}audio_view"), - scaleType: scaleType, - )); + child: Selector( + selector: (_, meetingStore) { + ///Here we check if the track is of a screenshare + ///we render it using screenshareViewController + ///while for other tracks we render it using viewControllers list + if (peerTracks[index].track?.source == "SCREEN") { + meetingStore.screenshareViewController ??= + HMSTextureViewController(addTrackByDefault: false); + return meetingStore.screenshareViewController!; + } + return meetingStore.viewControllers[index % 6]; + }, builder: (_, viewController, __) { + return PeerTile( + videoViewController: viewController, + key: ValueKey("${peerTracks[index].uid}audio_view"), + scaleType: scaleType, + ); + })); } } diff --git a/packages/hms_room_kit/lib/src/widgets/grid_layouts/screen_share_grid_layout.dart b/packages/hms_room_kit/lib/src/widgets/grid_layouts/screen_share_grid_layout.dart index b85c5796a..92b4430f9 100644 --- a/packages/hms_room_kit/lib/src/widgets/grid_layouts/screen_share_grid_layout.dart +++ b/packages/hms_room_kit/lib/src/widgets/grid_layouts/screen_share_grid_layout.dart @@ -67,22 +67,25 @@ class _ScreenshareGridLayoutState extends State { ///The active dot is the current page ///The inactive dots are the pages other than the current page if (widget.screenshareCount > 1) - Selector( - selector: (_, meetingStore) => - meetingStore.currentScreenSharePage, - builder: (_, currentScreenSharePage, __) { - return Padding( - padding: const EdgeInsets.only(top: 8.0), - child: DotsIndicator( - dotsCount: widget.screenshareCount, - position: currentScreenSharePage, - decorator: DotsDecorator( - activeColor: - HMSThemeColors.onSurfaceHighEmphasis, - color: HMSThemeColors.onSurfaceLowEmphasis), - ), - ); - }) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Selector( + selector: (_, meetingStore) => + meetingStore.currentScreenSharePage, + builder: (_, currentScreenSharePage, __) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: DotsIndicator( + dotsCount: widget.screenshareCount, + position: currentScreenSharePage, + decorator: DotsDecorator( + activeColor: + HMSThemeColors.onSurfaceHighEmphasis, + color: HMSThemeColors.onSurfaceLowEmphasis), + ), + ); + }), + ) ], )), @@ -129,20 +132,26 @@ class _ScreenshareGridLayoutState extends State { Selector( selector: (_, meetingStore) => meetingStore.currentPage, builder: (_, currentPage, __) { + int dotsCount = (((widget.peerTracks.length - + widget.screenshareCount) ~/ + 2) + + (widget.peerTracks.length - + widget.screenshareCount) % + 2); return Padding( padding: const EdgeInsets.only(top: 8.0), - child: DotsIndicator( - dotsCount: (((widget.peerTracks.length - - widget.screenshareCount) ~/ - 2) + - (widget.peerTracks.length - - widget.screenshareCount) % - 2), - position: currentPage, - decorator: DotsDecorator( - activeColor: - HMSThemeColors.onSurfaceHighEmphasis, - color: HMSThemeColors.onSurfaceLowEmphasis), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: DotsIndicator( + mainAxisSize: MainAxisSize.min, + dotsCount: dotsCount, + position: + currentPage > dotsCount ? 0 : currentPage, + decorator: DotsDecorator( + activeColor: + HMSThemeColors.onSurfaceHighEmphasis, + color: HMSThemeColors.onSurfaceLowEmphasis), + ), ), ); }) diff --git a/packages/hms_room_kit/lib/src/widgets/grid_layouts/texture_view_grid.dart b/packages/hms_room_kit/lib/src/widgets/grid_layouts/texture_view_grid.dart new file mode 100644 index 000000000..dae0af083 --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/grid_layouts/texture_view_grid.dart @@ -0,0 +1,30 @@ +import 'package:flutter/cupertino.dart'; +import 'package:hms_room_kit/src/model/peer_track_node.dart'; +import 'package:hms_room_kit/src/widgets/grid_layouts/listenable_peer_widget.dart'; + +class TextureViewGrid extends StatefulWidget { + final List peerTracks; + const TextureViewGrid({super.key, required this.peerTracks}); + + @override + State createState() => _TextureViewGridState(); +} + +class _TextureViewGridState extends State { + @override + Widget build(BuildContext context) { + return GridView.builder( + scrollDirection: Axis.horizontal, + physics: const PageScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + mainAxisExtent: MediaQuery.of(context).size.width / 2), + itemCount: widget.peerTracks.length, + itemBuilder: (context, index) { + return ListenablePeerWidget( + peerTracks: widget.peerTracks, + index: index, + ); + }); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/meeting_modes/custom_grid_view.dart b/packages/hms_room_kit/lib/src/widgets/meeting_modes/custom_grid_view.dart deleted file mode 100644 index 9d236c242..000000000 --- a/packages/hms_room_kit/lib/src/widgets/meeting_modes/custom_grid_view.dart +++ /dev/null @@ -1,314 +0,0 @@ -///Package imports -import 'package:dots_indicator/dots_indicator.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; - -///Project imports -import 'package:hms_room_kit/hms_room_kit.dart'; -import 'package:hms_room_kit/src/meeting/meeting_store.dart'; -import 'package:hms_room_kit/src/model/peer_track_node.dart'; -import 'package:hms_room_kit/src/widgets/common_widgets/peer_tile.dart'; - -///This widget renders the grid view of the meeting screen without inset tile -///The grid view is rendered based on the number of peers in the meeting -///The grid view is rendered using the [PageView] widget -class CustomGridView extends StatefulWidget { - const CustomGridView({super.key}); - - @override - State createState() => _CustomGridViewState(); -} - -class _CustomGridViewState extends State { - PageController controller = PageController(); - int tileNumber = 0; - - @override - Widget build(BuildContext context) { - ///The grid view is rendered using the [PageView] widget - ///The number of pages in the [PageView] is equal to [numberOfPeers/6 + (if number of peers is not divisible by 6 then we add 1 else we add 0)] - return Selector, int, int>>( - selector: (_, meetingStore) => Tuple3(meetingStore.peerTracks, - meetingStore.peerTracks.length, meetingStore.currentPage), - builder: (_, data, __) { - int pageCount = (data.item2 ~/ 6) + (data.item2 % 6 == 0 ? 0 : 1); - return Column( - children: [ - Expanded( - child: PageView.builder( - clipBehavior: Clip.none, - physics: const PageScrollPhysics(), - scrollDirection: Axis.horizontal, - controller: controller, - allowImplicitScrolling: true, - itemCount: pageCount, - onPageChanged: (newPage) { - context.read().setCurrentPage(newPage); - }, - itemBuilder: (context, index) => - _generateGrid(data.item2, index, data.item1)), - ), - - ///This renders the dots at the bottom of the grid view - ///This is only rendered if the number of pages is greater than 1 - ///The number of dots is equal to [numberOfPeers/6 + (if number of peers is not divisible by 6 then we add 1 else we add 0)] - ///The active dot is the current page - ///The inactive dots are the pages other than the current page - if (pageCount > 1) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: DotsIndicator( - dotsCount: - (data.item2 ~/ 6) + (data.item2 % 6 == 0 ? 0 : 1), - position: data.item3, - decorator: DotsDecorator( - activeColor: HMSThemeColors.onSurfaceHighEmphasis, - color: HMSThemeColors.onSurfaceLowEmphasis), - ), - ) - ], - ); - }); - } - - ///This function generates the grid view based on the number of peers in the meeting - Widget _generateGrid( - int numberOfTiles, int index, List peerTrackNode) { - int tileToBeRendered = 0; - - ///Here we check how many tiles we need to render - ///So if still there are 6 or more tiles to be rendered then we render 6 tiles - ///else we render the remaining tiles - /// - ///This is done to decide which layout we need to render - if ((6 * (index + 1) > numberOfTiles)) { - tileToBeRendered = numberOfTiles - 6 * (index); - } else { - tileToBeRendered = 6; - } - - ///This contains the starting index of tile to be rendered - tileNumber = 6 * index; - - ///Here we render the tile layout based on how many tiles we need to render - ///If we need to render 1 tile then we render the [ListenablePeerWidget] - ///If we need to render 2 tiles then we render the [TwoTileLayout] - ///If we need to render 3 tiles then we render the [ThreeTileLayout] - ///If we need to render 4 tiles then we render the [FourTileLayout] - ///If we need to render 5 tiles then we render the [FiveTileLayout] - ///If we need to render 6 tiles then we render the [SixTileLayout] - if (tileToBeRendered == 6) { - return sixTileLayout(peerTrackNode); - } - switch (tileToBeRendered % 6) { - case 1: - return singleTileLayout(peerTrackNode); - - case 2: - return twoTileLayout(peerTrackNode); - - case 3: - return threeTileLayout(peerTrackNode); - - case 4: - return fourTileLayout(peerTrackNode); - - case 5: - return fiveTileLayout(peerTrackNode); - } - return sixTileLayout(peerTrackNode); - } - - Widget sixTileLayout(List peerTracks) { - return Column( - children: [ - Expanded( - child: Row(children: [ - Expanded( - child: Container(child: peerWidget(tileNumber, peerTracks)), - ), - const SizedBox( - width: 8, - ), - Expanded( - child: Container(child: peerWidget(tileNumber + 1, peerTracks)), - ) - ]), - ), - const SizedBox( - height: 8, - ), - Expanded( - child: Row(children: [ - Expanded( - child: Container(child: peerWidget(tileNumber + 2, peerTracks)), - ), - const SizedBox( - width: 8, - ), - Expanded( - child: Container(child: peerWidget(tileNumber + 3, peerTracks)), - ) - ]), - ), - const SizedBox( - height: 8, - ), - Expanded( - child: Row(children: [ - Expanded( - child: Container(child: peerWidget(tileNumber + 4, peerTracks)), - ), - const SizedBox( - width: 8, - ), - Expanded( - child: Container(child: peerWidget(tileNumber + 5, peerTracks)), - ) - ]), - ), - ], - ); - } - - Widget fiveTileLayout(List peerTracks) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Row(children: [ - Expanded( - child: Container(child: peerWidget(tileNumber, peerTracks)), - ), - const SizedBox( - width: 8, - ), - Expanded( - child: Container(child: peerWidget(tileNumber + 1, peerTracks)), - ), - ]), - ), - const SizedBox( - height: 8, - ), - Expanded( - child: Row(children: [ - Expanded( - child: Container(child: peerWidget(tileNumber + 2, peerTracks)), - ), - const SizedBox( - width: 8, - ), - Expanded( - child: Container(child: peerWidget(tileNumber + 3, peerTracks)), - ), - ]), - ), - const SizedBox( - height: 8, - ), - Expanded( - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: MediaQuery.of(context).size.width / 4), - child: Container( - child: peerWidget(tileNumber + 4, peerTracks), - ), - ), - ), - ], - ); - } - - Widget fourTileLayout(List peerTracks) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Row(children: [ - Expanded( - child: Container(child: peerWidget(tileNumber, peerTracks)), - ), - const SizedBox( - width: 8, - ), - Expanded( - child: Container(child: peerWidget(tileNumber + 1, peerTracks)), - ), - ]), - ), - const SizedBox( - height: 8, - ), - Expanded( - child: Row(children: [ - Expanded( - child: Container(child: peerWidget(tileNumber + 2, peerTracks)), - ), - const SizedBox( - width: 8, - ), - Expanded( - child: Container(child: peerWidget(tileNumber + 3, peerTracks)), - ), - ]), - ), - ], - ); - } - - Widget threeTileLayout(List peerTracks) { - return Column( - children: [ - Expanded( - child: Container(child: peerWidget(tileNumber, peerTracks)), - ), - const SizedBox( - height: 8, - ), - Expanded( - child: Container(child: peerWidget(tileNumber + 1, peerTracks)), - ), - const SizedBox( - height: 8, - ), - Expanded( - child: Container(child: peerWidget(tileNumber + 2, peerTracks)), - ), - ], - ); - } - - Widget twoTileLayout(List peerTracks) { - return SizedBox( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: Container(child: peerWidget(tileNumber, peerTracks)), - ), - const SizedBox( - height: 8, - ), - Expanded( - child: Container(child: peerWidget(tileNumber + 1, peerTracks)), - ), - ], - ), - ); - } - - Widget singleTileLayout(List peerTracks) { - return Container(child: peerWidget(tileNumber, peerTracks)); - } - - Widget peerWidget(int index, List peerTracks) { - return ChangeNotifierProvider.value( - key: ValueKey("${peerTracks[index].uid}video_view"), - value: peerTracks[index], - child: PeerTile( - key: ValueKey("${peerTracks[index].uid}audio_view"), - )); - } -} diff --git a/packages/hms_room_kit/lib/src/widgets/meeting_modes/custom_one_to_one_grid.dart b/packages/hms_room_kit/lib/src/widgets/meeting_modes/custom_one_to_one_grid.dart index d4e87d87d..99dac3e54 100644 --- a/packages/hms_room_kit/lib/src/widgets/meeting_modes/custom_one_to_one_grid.dart +++ b/packages/hms_room_kit/lib/src/widgets/meeting_modes/custom_one_to_one_grid.dart @@ -18,7 +18,9 @@ import 'package:hms_room_kit/src/widgets/grid_layouts/screen_share_grid_layout.d ///[isLocalInsetPresent] is used to check if the local inset tile is present or not class CustomOneToOneGrid extends StatefulWidget { final bool isLocalInsetPresent; - const CustomOneToOneGrid({super.key, this.isLocalInsetPresent = true}); + final List? peerTracks; + const CustomOneToOneGrid( + {super.key, this.isLocalInsetPresent = true, this.peerTracks}); @override State createState() => _CustomOneToOneGridState(); @@ -34,11 +36,10 @@ class _CustomOneToOneGridState extends State { ///One thing to note here is that in this view we filter out the local peer since we are rendering the local peer in the inset tile ///The inset tile is rendered at the top of the grid view return Selector, int, int, PeerTrackNode, int>>( - selector: (_, meetingStore) => Tuple5( + Tuple4, int, PeerTrackNode, int>>( + selector: (_, meetingStore) => Tuple4( meetingStore.peerTracks, meetingStore.peerTracks.length, - meetingStore.currentPage, meetingStore.peerTracks[0], meetingStore.screenShareCount), builder: (_, data, __) { @@ -48,7 +49,7 @@ class _CustomOneToOneGridState extends State { ///If the remote peer is sharing screen then we render the [ScreenshareGridLayout] with inset tile ///Else we render the normal layout with inset tile - return data.item5 > 0 + return data.item4 > 0 ? ScreenshareGridLayout( peerTracks: widget.isLocalInsetPresent ? data.item1 @@ -57,7 +58,7 @@ class _CustomOneToOneGridState extends State { element.track?.source == "SCREEN") .toList() : data.item1, - screenshareCount: data.item5, + screenshareCount: data.item4, ) : @@ -65,10 +66,17 @@ class _CustomOneToOneGridState extends State { Column( children: [ Expanded( + // child: TextureViewGrid( + // peerTracks: widget.isLocalInsetPresent + // ? data.item1 + // .where((element) => + // !(element.peer.isLocal) || + // element.track?.source == "SCREEN") + // .toList() + // : data.item1), child: PageView.builder( physics: const PageScrollPhysics(), controller: controller, - allowImplicitScrolling: true, itemCount: pageCount, onPageChanged: (newPage) { context @@ -101,13 +109,24 @@ class _CustomOneToOneGridState extends State { if (pageCount > 1) Padding( padding: const EdgeInsets.only(top: 8.0), - child: DotsIndicator( - dotsCount: pageCount, - position: data.item3, - decorator: DotsDecorator( - activeColor: HMSThemeColors.onSurfaceHighEmphasis, - color: HMSThemeColors.onSurfaceLowEmphasis), - ), + child: Selector( + selector: (_, meetingStore) => + meetingStore.currentPage, + builder: (_, currentPage, __) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: DotsIndicator( + dotsCount: pageCount, + position: + currentPage > pageCount ? 0 : currentPage, + decorator: DotsDecorator( + activeColor: + HMSThemeColors.onSurfaceHighEmphasis, + color: + HMSThemeColors.onSurfaceLowEmphasis), + ), + ); + }), ) ], ); diff --git a/packages/hms_room_kit/lib/src/widgets/meeting_modes/one_to_one_mode.dart b/packages/hms_room_kit/lib/src/widgets/meeting_modes/one_to_one_mode.dart index df926fd70..0c3f8b289 100644 --- a/packages/hms_room_kit/lib/src/widgets/meeting_modes/one_to_one_mode.dart +++ b/packages/hms_room_kit/lib/src/widgets/meeting_modes/one_to_one_mode.dart @@ -82,8 +82,9 @@ class _OneToOneModeState extends State { /// ///This is the case when the local peer is null or it doesn't have audio or videotrack (oneToOnePeer == null) - ? const CustomOneToOneGrid( + ? CustomOneToOneGrid( isLocalInsetPresent: false, + peerTracks: widget.peerTracks, ) ///This handles the case where local peer is the only peer in the room with audio or video track @@ -97,7 +98,9 @@ class _OneToOneModeState extends State { ///i.e. this handles the normal flow : Stack( children: [ - const CustomOneToOneGrid(), + CustomOneToOneGrid( + peerTracks: widget.peerTracks, + ), DraggableWidget( dragAnimationScale: 1, topMargin: 10, diff --git a/packages/hms_room_kit/lib/src/widgets/peer_widgets/peer_name.dart b/packages/hms_room_kit/lib/src/widgets/peer_widgets/peer_name.dart index 1fd0a3bad..571d37a0b 100644 --- a/packages/hms_room_kit/lib/src/widgets/peer_widgets/peer_name.dart +++ b/packages/hms_room_kit/lib/src/widgets/peer_widgets/peer_name.dart @@ -15,16 +15,16 @@ class PeerName extends StatelessWidget { @override Widget build(BuildContext context) { - return Selector>( - selector: (_, peerTrackNode) => - Tuple2(peerTrackNode.peer.name, peerTrackNode.peer.isLocal), - builder: (_, data, __) { - return Container( - constraints: BoxConstraints(maxWidth: maxWidth - 80), - child: HMSSubheadingText( + return Container( + constraints: BoxConstraints(maxWidth: maxWidth - 80), + child: Selector>( + selector: (_, peerTrackNode) => + Tuple2(peerTrackNode.peer.name, peerTrackNode.peer.isLocal), + builder: (_, data, __) { + return HMSSubheadingText( text: "${data.item1.trim()}${data.item2 ? " (You)" : ""}", textColor: HMSThemeColors.onSurfaceHighEmphasis, - )); - }); + ); + })); } } diff --git a/packages/hms_room_kit/lib/src/widgets/tab_widgets/chat_participants_tab_bar.dart b/packages/hms_room_kit/lib/src/widgets/tab_widgets/chat_participants_tab_bar.dart index 3672ed008..d41b3275c 100644 --- a/packages/hms_room_kit/lib/src/widgets/tab_widgets/chat_participants_tab_bar.dart +++ b/packages/hms_room_kit/lib/src/widgets/tab_widgets/chat_participants_tab_bar.dart @@ -47,7 +47,7 @@ class _ChatParticipantsTabBarState extends State mainAxisAlignment: MainAxisAlignment.start, children: [ Container( - width: MediaQuery.of(context).size.width * 0.78, + width: MediaQuery.of(context).size.width * 0.76, height: 36, decoration: BoxDecoration( color: HMSThemeColors.surfaceDefault, diff --git a/packages/hms_room_kit/lib/src/widgets/toasts/hms_bring_on_stage_toast.dart b/packages/hms_room_kit/lib/src/widgets/toasts/hms_bring_on_stage_toast.dart index 0b77cb457..55d54e0b1 100644 --- a/packages/hms_room_kit/lib/src/widgets/toasts/hms_bring_on_stage_toast.dart +++ b/packages/hms_room_kit/lib/src/widgets/toasts/hms_bring_on_stage_toast.dart @@ -83,7 +83,7 @@ class HMSBringOnStageToast extends StatelessWidget { } }, height: 36, - width: 135, + width: 128, buttonColor: HMSThemeColors.secondaryDefault, textColor: HMSThemeColors.onSecondaryHighEmphasis, ), diff --git a/packages/hms_room_kit/pubspec.lock b/packages/hms_room_kit/pubspec.lock index ae183bce6..e84a0bdc1 100644 --- a/packages/hms_room_kit/pubspec.lock +++ b/packages/hms_room_kit/pubspec.lock @@ -41,14 +41,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - bot_toast: - dependency: "direct main" - description: - name: bot_toast - sha256: "6b93030a99a98335b8827ecd83021e92e885ffc61d261d3825ffdecdd17f3bdf" - url: "https://pub.dev" - source: hosted - version: "4.1.3" characters: dependency: transitive description: @@ -85,10 +77,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "445db18de832dba8d851e287aff8ccf169bed30d2e94243cb54c7d2f1ed2142c" + sha256: "2f9d2cbccb76127ba28528cb3ae2c2326a122446a83de5a056aaa3880d3882c5" url: "https://pub.dev" source: hosted - version: "0.3.3+6" + version: "0.3.3+7" crypto: dependency: transitive description: @@ -212,10 +204,10 @@ packages: dependency: "direct main" description: name: hmssdk_flutter - sha256: "70d00820ab5cb02c03c2d4f41be70dd9dc3406521a469b8989d1e650232efd9a" + sha256: "27dfc516ee4673ff092fc4e18487c315780b8470a2fbcf0f486bdfa529fc53cb" url: "https://pub.dev" source: hosted - version: "1.9.3" + version: "1.9.4" http: dependency: transitive description: @@ -436,10 +428,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.7" pointycastle: dependency: transitive description: @@ -625,10 +617,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a" + sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.2.1" url_launcher_linux: dependency: transitive description: @@ -729,10 +721,10 @@ packages: dependency: transitive description: name: win32 - sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f" + sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.1.1" xdg_directories: dependency: transitive description: diff --git a/packages/hms_room_kit/pubspec.yaml b/packages/hms_room_kit/pubspec.yaml index 7c29af1ec..8036542eb 100644 --- a/packages/hms_room_kit/pubspec.yaml +++ b/packages/hms_room_kit/pubspec.yaml @@ -1,6 +1,6 @@ name: hms_room_kit description: 100ms Room Kit provides simple & easy to use UI components to build Live Streaming & Video Conferencing experiences in your apps. -version: 1.0.6 +version: 1.0.7 homepage: https://www.100ms.live/ repository: https://github.com/100mslive/100ms-flutter issue_tracker: https://github.com/100mslive/100ms-flutter/issues @@ -14,12 +14,11 @@ dependencies: flutter: sdk: flutter - hmssdk_flutter: ^1.9.3 + hmssdk_flutter: ^1.9.4 intl: ^0.18.1 permission_handler: ^11.0.0 provider: ^6.0.5 google_fonts: ^6.1.0 - bot_toast: ^4.0.4 draggable_widget: ^2.0.0 badges: ^3.1.1 shared_preferences: ^2.2.0 diff --git a/packages/hmssdk_flutter/CHANGELOG.md b/packages/hmssdk_flutter/CHANGELOG.md index 04a05d7de..a96440978 100644 --- a/packages/hmssdk_flutter/CHANGELOG.md +++ b/packages/hmssdk_flutter/CHANGELOG.md @@ -5,6 +5,23 @@ | hms_room_kit | [![Pub Version](https://img.shields.io/pub/v/hms_room_kit)](https://pub.dev/packages/hms_room_kit) | | hmssdk_flutter | [![Pub Version](https://img.shields.io/pub/v/hmssdk_flutter)](https://pub.dev/packages/hmssdk_flutter) | +# 1.9.4 - 2023-12-08 + +| Package | Version | +| -------------- | ------------------------------------------------------------------------------------------------------ | +| hms_room_kit | 1.0.7 | +| hmssdk_flutter | 1.9.4 | + +### Added + +- Added `HMSTextureView` for improved performance of video rendering on Android devices. This is an alternative to `HMSVideoView` which is based on `SurfaceView`. + + Learn more about `HMSTextureView` [here](https://www.100ms.live/docs/flutter/v2/how-to-guides/set-up-video-conferencing/render-video/hms-texture-view). + +Updated to Android SDK 2.8.3 & iOS SDK 1.3.1 + +**Full Changelog**: [1.9.3...1.9.4](https://github.com/100mslive/100ms-flutter/compare/1.9.3...1.9.4) + ## 1.9.3 - 2023-11-17 ### Added diff --git a/packages/hmssdk_flutter/README.md b/packages/hmssdk_flutter/README.md index 4cb384de8..ddd32dc2f 100644 --- a/packages/hmssdk_flutter/README.md +++ b/packages/hmssdk_flutter/README.md @@ -15,6 +15,13 @@ Integrate Real Time Audio and Video conferencing, Interactive Live Streaming, an With support for HLS and RTMP Live Streaming and Recording, Picture-in-Picture (PiP), CallKit, VoIP, one-to-one Video Call Modes, Audio Rooms, Video Player and much more, add immersive real-time communications to your apps. + +| Package | Version | +| -------------- | ------------------------------------------------------------------------------------------------------ | +| hms_room_kit | [![Pub Version](https://img.shields.io/pub/v/hms_room_kit)](https://pub.dev/packages/hms_room_kit) | +| hmssdk_flutter | [![Pub Version](https://img.shields.io/pub/v/hmssdk_flutter)](https://pub.dev/packages/hmssdk_flutter) | + + 📖 Read the Complete Documentation here: https://www.100ms.live/docs/flutter/v2/guides/quickstart 📲 Download the Sample iOS app here: 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 9a94405af..e80b5aea7 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 @@ -14,11 +14,14 @@ 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 +import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.view.TextureRegistry +import io.flutter.view.TextureRegistry.SurfaceTextureEntry import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -26,6 +29,7 @@ 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.HMSTextureView import live.hms.hmssdk_flutter.views.HMSVideoViewFactory import live.hms.video.audio.HMSAudioManager.* import live.hms.video.connection.stats.* @@ -78,6 +82,9 @@ class HmssdkFlutterPlugin : var hlsStreamUrl: String? = null private var isRoomAudioUnmutedLocally = true + private val renderers = HashMap() + private var hmsTextureRegistry: TextureRegistry? = null + private var hmsBinaryMessenger: BinaryMessenger? = null override fun onAttachedToEngine( @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding, ) { @@ -119,12 +126,17 @@ class HmssdkFlutterPlugin : "HMSHLSPlayer", hmsHLSPlayerFactory, ) + + hmsTextureRegistry = flutterPluginBinding.textureRegistry + hmsBinaryMessenger = flutterPluginBinding.binaryMessenger + hmssdkFlutterPlugin = this } else { Log.e("Plugin Warning", "hmssdkFlutterPlugin already exists in onAttachedToEngine") } } + override fun onMethodCall( @NonNull call: MethodCall, @NonNull result: Result, @@ -242,9 +254,30 @@ class HmssdkFlutterPlugin : "get_room_layout" -> { getRoomLayout(call, result) } + + "create_texture_view" -> { + createTextureView(call,result) + } + "dispose_texture_view" -> { + disposeTextureView(call, result) + } + + "add_track" -> { + addTrack(call,result) + } + + "remove_track" -> { + removeTrack(call,result) + } + + "set_display_resolution" -> { + setDisplayResolution(call,result) + } + "get_peer_list_iterator", "peer_list_iterator_has_next", "peer_list_iterator_next" -> { HMSPeerListIteratorAction.peerListIteratorAction(call, result, hmssdk!!) } + else -> { result.notImplemented() } @@ -440,6 +473,8 @@ class HmssdkFlutterPlugin : sessionStoreSink = null hlsPlayerSink = null hmssdkFlutterPlugin = null + hmsBinaryMessenger = null + hmsTextureRegistry = null } else { Log.e("Plugin Error", "hmssdkFlutterPlugin is null in onDetachedFromEngine") } @@ -582,6 +617,157 @@ class HmssdkFlutterPlugin : return null } + private fun createTextureView(call: MethodCall, result: Result){ + + val trackId = call.argument("track_id") + val addTrackByDefault = call.argument("add_track_by_def")?:false + val disableAutoSimulcastLayerSelect = call.argument("disable_auto_simulcast_layer_select")?:false + + val entry: SurfaceTextureEntry? = hmsTextureRegistry?.createSurfaceTexture() + entry?.let { surfaceTextureEntry -> + val surfaceTexture = surfaceTextureEntry.surfaceTexture() + val renderer = HMSTextureView(surfaceTexture,entry) + if(addTrackByDefault){ + val room = hmssdk?.getRoom() + room?.let { currentRoom -> + trackId?.let {currentTrackId -> + val track = HmsUtilities.getVideoTrack(currentTrackId,currentRoom) + track?.let { videoTrack -> + Log.i("HMSTextureView","Init Add Track called for track: ${track.trackId}") + renderer.addTrack(videoTrack,disableAutoSimulcastLayerSelect) + }?: run { + HMSErrorLogger.returnHMSException("createTextureView","No track with $trackId found","Track not found error",result) + return + } + }?: run { + HMSErrorLogger.returnHMSException("createTextureView","trackId is null"," NULL ERROR",result) + return + } + }?: run { + HMSErrorLogger.returnHMSException("createTextureView","Room is null","NULL Error",result) + return + } + } + renderers["${surfaceTextureEntry.id()}"] = renderer + val eventChannel = EventChannel( + hmsBinaryMessenger, + "HMSTextureView/Texture/" + entry.id() + ) + eventChannel.setStreamHandler(renderer) + renderer.setTextureViewEventChannel(eventChannel) + val data = HashMap() + data["texture_id"] = surfaceTextureEntry.id() + result.success(HMSResultExtension.toDictionary(true,data)) + + }?:run{ + HMSErrorLogger.returnHMSException("createTextureView","entry is null","NULL Error",result) + return + } + } + + private fun disposeTextureView(call: MethodCall,result: Result){ + val textureId = call.argument("texture_id") ?: HMSErrorLogger.returnArgumentsError("textureId is null") + + var renderer = renderers["$textureId"] + + if(renderer != null){ + renderer.disposeTextureView() + renderer = null + renderers.remove("$textureId") + result.success(HMSResultExtension.toDictionary(true,null)) + } + else { + HMSErrorLogger.returnHMSException( + "disposeTextureView", + "No textureView with given textureId found", + "Key not found error", + result + ) + } + } + + private fun addTrack(call: MethodCall, result: Result){ + val trackId = call.argument("track_id") + val textureId = call.argument("texture_id") + val disableAutoSimulcastLayerSelect = call.argument("disable_auto_simulcast_layer_select")?:false + val height = call.argument("height") + val width = call.argument("width") + + textureId?.let {texture -> + trackId?.let { + val renderer = renderers["$textureId"] + renderer?.let { textureRenderer -> + val room = hmssdk?.getRoom() + room?.let { currentRoom -> + val track = HmsUtilities.getVideoTrack(trackId,currentRoom) + track?.let {videoTrack -> + textureRenderer.addTrack(videoTrack,disableAutoSimulcastLayerSelect,height,width) + result.success(null) + }?: run { + HMSErrorLogger.returnHMSException( + "addTrack", + "track with given trackId not found", + "Track not found error", + result + ) + } + }?: run { + HMSErrorLogger.returnHMSException( + "addTrack", + "room not found", + "room not found error", + result + ) + } + }?: run { + HMSErrorLogger.returnHMSException( + "addTrack", + "renderer with given $texture not found", + "renderer not found error", + result + ) + } + }?: run { + HMSErrorLogger.returnHMSException( + "addTrack", + "trackId is null", + "NULL ERROR", + result + ) + } + }?:run { + HMSErrorLogger.returnHMSException( + "addTrack", + "textureId is null", + "NULL ERROR", + result + ) + } + } + + private fun removeTrack(call: MethodCall, result: Result){ + val textureId = call.argument("texture_id") + + val renderer = renderers["$textureId"] + renderer?.removeTrack() + result.success(null) + + } + + private fun setDisplayResolution(call: MethodCall, result: Result){ + val textureId = call.argument("texture_id") + val height = call.argument("height") + val width = call.argument("width") + val renderer = renderers[textureId] + + height?.let { videoViewHeight -> + width?.let { videoViewWidth -> + renderer?.setDisplayResolution(videoViewWidth,videoViewHeight) + } + } + result.success(null) + } + private fun getAllTracks(): ArrayList { val room = hmssdk!!.getRoom() val allTracks = ArrayList() diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSTextureView.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSTextureView.kt new file mode 100644 index 000000000..82186a16e --- /dev/null +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSTextureView.kt @@ -0,0 +1,94 @@ +package live.hms.hmssdk_flutter.views + +import android.graphics.SurfaceTexture +import android.util.Log +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.EventChannel.EventSink +import io.flutter.view.TextureRegistry +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import live.hms.hmssdk_flutter.HMSTrackUpdateExtension +import live.hms.video.media.tracks.HMSVideoTrack +import live.hms.videoview.VideoViewStateChangeListener +import live.hms.videoview.textureview.HMSTextureRenderer + +class HMSTextureView( + texture: SurfaceTexture, + private var entry: TextureRegistry.SurfaceTextureEntry? +):EventChannel.StreamHandler{ + + private var eventChannel: EventChannel? = null + private var eventSink: EventSink? = null + private var hmsTextureRenderer: HMSTextureRenderer? = null + private var uid: Long? = null + init { + hmsTextureRenderer = HMSTextureRenderer(texture) + uid = entry?.id() + } + + private val videoViewStateChangeListener = object : VideoViewStateChangeListener{ + override fun onResolutionChange(newWidth: kotlin.Int, newHeight: kotlin.Int) { + val args = HashMap() + args["event_name"] = "on_resolution_changed" + val data = HashMap() + data["height"] = newHeight + data["width"] = newWidth + args["data"] = data + if (args["data"] != null) { + CoroutineScope(Dispatchers.Main).launch { + eventSink?.success(args) + } + } + } + + override fun onFirstFrameRendered() { + super.onFirstFrameRendered() + } + } + + fun addTrack(track: HMSVideoTrack, disableAutoSimulcastLayerSelect: Boolean, height: Int? = null, width: Int? = null){ + Log.i("HMSTextureView","Add Track called for track: ${track.trackId}") + hmsTextureRenderer?.addVideoViewStateChangeListener(videoViewStateChangeListener) + hmsTextureRenderer?.disableAutoSimulcastLayerSelect(disableAutoSimulcastLayerSelect) + if(!disableAutoSimulcastLayerSelect){ + height?.let { videoViewHeight -> + width?.let { videoViewWidth -> + hmsTextureRenderer?.displayResolution(videoViewWidth,videoViewHeight) + } + } + } + hmsTextureRenderer?.addTrack(track,true) + } + + fun setDisplayResolution(width: Int, height: Int){ + hmsTextureRenderer?.displayResolution(width,height) + } + + fun removeTrack(){ + Log.i("HMSTextureView","Remove Track called") + hmsTextureRenderer?.removeTrack() + } + + fun disposeTextureView(){ + Log.i("HMSTextureView","disposeTextureView called") + removeTrack() + entry?.release() + entry = null + hmsTextureRenderer = null + this.eventChannel = null + eventSink = null + } + + fun setTextureViewEventChannel(eventChannel:EventChannel){ + this.eventChannel = eventChannel + } + + override fun onListen(arguments: Any?, events: EventSink?) { + eventSink = events + } + + override fun onCancel(arguments: Any?) { + eventSink = null + } +} \ No newline at end of file diff --git a/packages/hmssdk_flutter/example/ExampleAppChangelog.txt b/packages/hmssdk_flutter/example/ExampleAppChangelog.txt index 4394895ca..6292c0c06 100644 --- a/packages/hmssdk_flutter/example/ExampleAppChangelog.txt +++ b/packages/hmssdk_flutter/example/ExampleAppChangelog.txt @@ -1,13 +1,21 @@ Board: https://100ms.atlassian.net/jira/software/projects/FLUT/boards/34/ -Release: https://100ms.atlassian.net/projects/FLUT/versions/10598/ -Implement sending of Subscriber Stats -- https://100ms.atlassian.net/browse/FLUT-134 +- Added TextureView in place of SurfaceView for better performance +https://100ms.atlassian.net/browse/FLUT-121 -Prebuilt: Add options to Mute Session or change Audio Output -- https://100ms.atlassian.net/browse/FLUT-155 +- Disable editing username in preview screen if username is passed to prebuilt in options config +https://100ms.atlassian.net/browse/FLUT-158 -Room Kit: 1.0.6 -Core SDK: 1.9.3 -Android SDK: 2.8.1 -iOS SDK: 1.3.0 +- Getting 3 blank messages for flutter on stage user if declined 3 invite before +https://100ms.atlassian.net/browse/FLUT-172 + +- Active Speaker should come within first 2 tiles when doing Screenshare +https://100ms.atlassian.net/browse/FLUT-161 + +- Flutter iOS VNRT to stage, app crash. +https://100ms.atlassian.net/browse/FLUT-173 + +Room Kit: 1.0.7 +Core SDK: 1.9.4 +Android SDK: 2.8.3 +iOS SDK: 1.3.1 diff --git a/packages/hmssdk_flutter/example/android/Gemfile.lock b/packages/hmssdk_flutter/example/android/Gemfile.lock index 3e44e275a..b6be76cfe 100644 --- a/packages/hmssdk_flutter/example/android/Gemfile.lock +++ b/packages/hmssdk_flutter/example/android/Gemfile.lock @@ -12,21 +12,21 @@ GEM public_suffix (>= 2.0.2, < 6.0) artifactory (3.0.15) atomos (0.1.3) - aws-eventstream (1.2.0) - aws-partitions (1.851.0) - aws-sdk-core (3.186.0) - aws-eventstream (~> 1, >= 1.0.2) + aws-eventstream (1.3.0) + aws-partitions (1.862.0) + aws-sdk-core (3.190.0) + aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) + aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.72.0) - aws-sdk-core (~> 3, >= 3.184.0) + aws-sdk-kms (1.74.0) + aws-sdk-core (~> 3, >= 3.188.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.136.0) - aws-sdk-core (~> 3, >= 3.181.0) + aws-sdk-s3 (1.141.0) + aws-sdk-core (~> 3, >= 3.189.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.6) - aws-sigv4 (1.6.1) + aws-sigv4 (~> 1.8) + aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) @@ -41,7 +41,7 @@ GEM domain_name (0.6.20231109) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.104.0) + excon (0.105.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -114,7 +114,7 @@ GEM fastlane-plugin-firebase_app_distribution (0.7.4) google-apis-firebaseappdistribution_v1 (~> 0.3.0) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.52.0) + google-apis-androidpublisher_v3 (0.53.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-core (0.11.2) addressable (~> 2.5, >= 2.5.1) @@ -160,7 +160,7 @@ GEM i18n (1.14.1) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.6.3) + json (2.7.0) jwt (2.7.1) mini_magick (4.12.0) mini_mime (1.1.5) @@ -172,7 +172,7 @@ GEM optparse (0.1.1) os (1.1.4) plist (3.7.0) - public_suffix (5.0.3) + public_suffix (5.0.4) rake (13.1.0) representable (3.2.0) declarative (< 0.1.0) diff --git a/packages/hmssdk_flutter/example/android/app/build.gradle b/packages/hmssdk_flutter/example/android/app/build.gradle index 65f6c5cca..76dd2e826 100644 --- a/packages/hmssdk_flutter/example/android/app/build.gradle +++ b/packages/hmssdk_flutter/example/android/app/build.gradle @@ -32,8 +32,8 @@ android { applicationId "live.hms.flutter" minSdkVersion 21 targetSdkVersion 33 - versionCode 390 - versionName "1.5.90" + versionCode 402 + versionName "1.5.102" } signingConfigs { diff --git a/packages/hmssdk_flutter/example/ios/Gemfile.lock b/packages/hmssdk_flutter/example/ios/Gemfile.lock index fce1e3a84..61114f35a 100644 --- a/packages/hmssdk_flutter/example/ios/Gemfile.lock +++ b/packages/hmssdk_flutter/example/ios/Gemfile.lock @@ -12,21 +12,21 @@ GEM public_suffix (>= 2.0.2, < 6.0) artifactory (3.0.15) atomos (0.1.3) - aws-eventstream (1.2.0) - aws-partitions (1.851.0) - aws-sdk-core (3.186.0) - aws-eventstream (~> 1, >= 1.0.2) + aws-eventstream (1.3.0) + aws-partitions (1.862.0) + aws-sdk-core (3.190.0) + aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) + aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.72.0) - aws-sdk-core (~> 3, >= 3.184.0) + aws-sdk-kms (1.74.0) + aws-sdk-core (~> 3, >= 3.188.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.136.0) - aws-sdk-core (~> 3, >= 3.181.0) + aws-sdk-s3 (1.141.0) + aws-sdk-core (~> 3, >= 3.189.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.6) - aws-sigv4 (1.6.1) + aws-sigv4 (~> 1.8) + aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) @@ -41,7 +41,7 @@ GEM domain_name (0.6.20231109) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.104.0) + excon (0.105.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -115,7 +115,7 @@ GEM google-apis-firebaseappdistribution_v1 (~> 0.3.0) fastlane-plugin-versioning (0.5.2) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.52.0) + google-apis-androidpublisher_v3 (0.53.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-core (0.11.2) addressable (~> 2.5, >= 2.5.1) @@ -161,7 +161,7 @@ GEM i18n (1.14.1) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.6.3) + json (2.7.0) jwt (2.7.1) mini_magick (4.12.0) mini_mime (1.1.5) @@ -173,7 +173,7 @@ GEM optparse (0.1.1) os (1.1.4) plist (3.7.0) - public_suffix (5.0.3) + public_suffix (5.0.4) rake (13.1.0) representable (3.2.0) declarative (< 0.1.0) diff --git a/packages/hmssdk_flutter/example/ios/Podfile.lock b/packages/hmssdk_flutter/example/ios/Podfile.lock index 0cabf32a5..5ee7a9fc4 100644 --- a/packages/hmssdk_flutter/example/ios/Podfile.lock +++ b/packages/hmssdk_flutter/example/ios/Podfile.lock @@ -79,7 +79,7 @@ PODS: - Flutter (1.0.0) - flutter_foreground_task (0.0.1): - Flutter - - GoogleDataTransport (9.2.5): + - GoogleDataTransport (9.3.0): - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) @@ -100,7 +100,7 @@ PODS: - HMSSDK (1.3.0): - HMSAnalyticsSDK (= 0.0.2) - HMSWebRTC (= 1.0.5116) - - hmssdk_flutter (1.9.3): + - hmssdk_flutter (1.9.4): - Flutter - HMSBroadcastExtensionSDK (= 0.0.9) - HMSHLSPlayerSDK (= 0.0.2) @@ -230,13 +230,13 @@ SPEC CHECKSUMS: FirebaseSharedSwift: 62e248642c0582324d0390706cadd314687c116b Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_foreground_task: 21ef182ab0a29a3005cc72cd70e5f45cb7f7f817 - GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2 + GoogleDataTransport: 57c22343ab29bc686febbf7cbb13bad167c2d8fe GoogleUtilities: 0759d1a57ebb953965c2dfe0ba4c82e95ccc2e34 HMSAnalyticsSDK: 4d2a88a729b1eb42f3d25f217c28937ec318a5b7 HMSBroadcastExtensionSDK: d80fe325f6c928bd8e5176290b5a4b7ae15d6fbb HMSHLSPlayerSDK: 6a54ad4d12f3dc2270d1ecd24019d71282a4f6a3 HMSSDK: 631908d772646b66b3c4f7f4e1fb2681f7abb990 - hmssdk_flutter: 0b17359aefb7ec222e1b0cc44a595759a317ff98 + hmssdk_flutter: ce3c54bbda2a8e1d893be672a877510a546da012 HMSWebRTC: ae54e9dd91b869051b283b43b14f57d43b7bf8e1 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5 @@ -249,8 +249,8 @@ SPEC CHECKSUMS: share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 uni_links: d97da20c7701486ba192624d99bffaaffcfc298a - url_launcher_ios: 68d46cc9766d0c41dbdc884310529557e3cd7a86 + url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b PODFILE CHECKSUM: 919064996fff867cd85dbf9e7730ff45bac23884 -COCOAPODS: 1.14.2 +COCOAPODS: 1.14.3 diff --git a/packages/hmssdk_flutter/example/ios/Runner/Info.plist b/packages/hmssdk_flutter/example/ios/Runner/Info.plist index e01349c8a..07f71b7bd 100644 --- a/packages/hmssdk_flutter/example/ios/Runner/Info.plist +++ b/packages/hmssdk_flutter/example/ios/Runner/Info.plist @@ -21,7 +21,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.5.91 + 1.5.101 CFBundleSignature ???? CFBundleURLTypes @@ -48,7 +48,7 @@ CFBundleVersion - 391 + 401 ITSAppUsesNonExemptEncryption LSApplicationCategoryType diff --git a/packages/hmssdk_flutter/example/pubspec.lock b/packages/hmssdk_flutter/example/pubspec.lock index 21f970300..ba7af1473 100644 --- a/packages/hmssdk_flutter/example/pubspec.lock +++ b/packages/hmssdk_flutter/example/pubspec.lock @@ -49,14 +49,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - bot_toast: - dependency: transitive - description: - name: bot_toast - sha256: "6b93030a99a98335b8827ecd83021e92e885ffc61d261d3825ffdecdd17f3bdf" - url: "https://pub.dev" - source: hosted - version: "4.1.3" characters: dependency: transitive description: @@ -93,10 +85,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "445db18de832dba8d851e287aff8ccf169bed30d2e94243cb54c7d2f1ed2142c" + sha256: "2f9d2cbccb76127ba28528cb3ae2c2326a122446a83de5a056aaa3880d3882c5" url: "https://pub.dev" source: hosted - version: "0.3.3+6" + version: "0.3.3+7" crypto: dependency: transitive description: @@ -294,15 +286,15 @@ packages: path: "../../hms_room_kit" relative: true source: path - version: "1.0.6" + version: "1.0.7" hmssdk_flutter: dependency: transitive description: name: hmssdk_flutter - sha256: "70d00820ab5cb02c03c2d4f41be70dd9dc3406521a469b8989d1e650232efd9a" + sha256: "27dfc516ee4673ff092fc4e18487c315780b8470a2fbcf0f486bdfa529fc53cb" url: "https://pub.dev" source: hosted - version: "1.9.3" + version: "1.9.4" http: dependency: transitive description: @@ -515,10 +507,10 @@ packages: dependency: transitive description: name: petitparser - sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "5.4.0" platform: dependency: transitive description: @@ -531,10 +523,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.7" pointycastle: dependency: transitive description: @@ -752,10 +744,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a" + sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.2.1" url_launcher_linux: dependency: transitive description: @@ -856,10 +848,10 @@ packages: dependency: transitive description: name: win32 - sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f" + sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.1.1" xdg_directories: dependency: transitive description: @@ -872,10 +864,10 @@ packages: dependency: transitive description: name: xml - sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" url: "https://pub.dev" source: hosted - version: "6.4.2" + version: "6.3.0" sdks: dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.13.0" diff --git a/packages/hmssdk_flutter/example/pubspec.yaml b/packages/hmssdk_flutter/example/pubspec.yaml index 8f8f1521c..ae66f6208 100644 --- a/packages/hmssdk_flutter/example/pubspec.yaml +++ b/packages/hmssdk_flutter/example/pubspec.yaml @@ -4,7 +4,7 @@ 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.9.3 +version: 1.9.4 environment: sdk: ">=2.16.0 <4.0.0" diff --git a/packages/hmssdk_flutter/ios/Classes/Models/HMSHLSVariantExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/HMSHLSVariantExtension.swift index ce7ffb2ff..8d9e71ffc 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/HMSHLSVariantExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/HMSHLSVariantExtension.swift @@ -18,7 +18,12 @@ class HMSHLSVariantExtension { dict["metadata"] = hmshlsVariant.metadata - dict["hls_stream_url"] = hmshlsVariant.url?.absoluteString + if let url = hmshlsVariant.url{ + dict["hls_stream_url"] = url.absoluteString + } + else{ + dict["hls_stream_url"] = nil + } if let startedAt = hmshlsVariant.startedAt { dict["started_at"] = "\(startedAt)" diff --git a/packages/hmssdk_flutter/lib/assets/sdk-versions.json b/packages/hmssdk_flutter/lib/assets/sdk-versions.json index c75c1e7a4..56b239ded 100644 --- a/packages/hmssdk_flutter/lib/assets/sdk-versions.json +++ b/packages/hmssdk_flutter/lib/assets/sdk-versions.json @@ -1,7 +1,7 @@ { - "flutter": "1.9.3", - "ios": "1.3.0", + "flutter": "1.9.4", + "ios": "1.3.1", "iOSBroadcastExtension": "0.0.9", "iOSHLSPlayerSDK": "0.0.2", - "android": "2.8.1" + "android": "2.8.3" } diff --git a/packages/hmssdk_flutter/lib/hmssdk_flutter.dart b/packages/hmssdk_flutter/lib/hmssdk_flutter.dart index a2ed8ad45..691329cea 100644 --- a/packages/hmssdk_flutter/lib/hmssdk_flutter.dart +++ b/packages/hmssdk_flutter/lib/hmssdk_flutter.dart @@ -102,5 +102,7 @@ export 'src/model/hms_peer_list_iterator.dart'; export 'src/model/peer_list_iterator_options.dart'; //Views +export 'src/ui/meeting/hms_texture_view.dart'; export 'src/ui/meeting/hms_video_view.dart'; export 'src/ui/meeting/hms_hls_player.dart'; +export 'src/ui/meeting/hms_texture_view_controller.dart'; diff --git a/packages/hmssdk_flutter/lib/src/common/platform_methods.dart b/packages/hmssdk_flutter/lib/src/common/platform_methods.dart index 1e62e30ef..1d22ec82c 100644 --- a/packages/hmssdk_flutter/lib/src/common/platform_methods.dart +++ b/packages/hmssdk_flutter/lib/src/common/platform_methods.dart @@ -195,7 +195,12 @@ enum PlatformMethod { peerListIteratorNext, lowerLocalPeerHand, lowerRemotePeerHand, - raiseLocalPeerHand + raiseLocalPeerHand, + createTextureView, + disposeTextureView, + addTrack, + removeTrack, + setDisplayResolution } extension PlatformMethodValues on PlatformMethod { @@ -488,6 +493,16 @@ extension PlatformMethodValues on PlatformMethod { return "lower_remote_peer_hand"; case PlatformMethod.raiseLocalPeerHand: return "raise_local_peer_hand"; + case PlatformMethod.createTextureView: + return "create_texture_view"; + case PlatformMethod.disposeTextureView: + return "dispose_texture_view"; + case PlatformMethod.addTrack: + return "add_track"; + case PlatformMethod.removeTrack: + return "remove_track"; + case PlatformMethod.setDisplayResolution: + return "set_display_resolution"; default: return 'unknown'; } @@ -782,6 +797,16 @@ extension PlatformMethodValues on PlatformMethod { return PlatformMethod.lowerRemotePeerHand; case "raise_local_peer_hand": return PlatformMethod.raiseLocalPeerHand; + case "create_texture_view": + return PlatformMethod.createTextureView; + case "dispose_texture_view": + return PlatformMethod.disposeTextureView; + case "add_track": + return PlatformMethod.addTrack; + case "remove_track": + return PlatformMethod.removeTrack; + case "set_display_resolution": + return PlatformMethod.setDisplayResolution; default: return PlatformMethod.unknown; } diff --git a/packages/hmssdk_flutter/lib/src/enum/hms_video_view_event.dart b/packages/hmssdk_flutter/lib/src/enum/hms_video_view_event.dart new file mode 100644 index 000000000..a20402ef7 --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/enum/hms_video_view_event.dart @@ -0,0 +1,12 @@ +enum HMSVideoViewEvent { onResolutionChanged, unknown } + +extension HMSVideoViewValues on HMSVideoViewEvent { + static HMSVideoViewEvent getHMSVideoViewEventFromString(String event) { + switch (event) { + case "on_resolution_changed": + return HMSVideoViewEvent.onResolutionChanged; + default: + return HMSVideoViewEvent.unknown; + } + } +} diff --git a/packages/hmssdk_flutter/lib/src/ui/meeting/hms_texture_view.dart b/packages/hmssdk_flutter/lib/src/ui/meeting/hms_texture_view.dart new file mode 100644 index 000000000..35e188382 --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/ui/meeting/hms_texture_view.dart @@ -0,0 +1,248 @@ +// Dart imports: +import 'dart:io' show Platform; +import 'dart:math'; + +// Flutter imports: +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show StandardMessageCodec; + +// Project imports: +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +///100ms HMSTextureView +/// +///HMSTextureView is used to render video tracks +/// +///In android devices, [HMSTextureView] uses texture to render videos while [HMSVideoView] uses surfaceView to render videos. +///In iOS there is no difference between [HMSTextureView] and [HMSVideoView]. +/// +/// To use,import package:`hmssdk_flutter/ui/meeting/hms_texture_view.dart`. +/// +/// [HMSTextureView] renders video using trackId from HMSTrack +/// +/// **parameters** +/// +/// **track** - This will render video with trackId present in the track. Use video track only. +/// +/// **scaleType** - To set the video scaling. [SCALE_ASPECT_FIT, SCALE_ASPECT_FILL, SCALE_ASPECT_BALANCED] +/// +/// **setMirror** - To set mirroring of video +/// +/// **disableAutoSimulcastLayerSelect** - To disable auto simulcast (Adaptive Bitrate) +/// +/// **key** - [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. +/// +/// **addTrackByDefault** - To call addTrack by default as HMSTextureView is attached to the tree. Default value is [true] +/// +/// **controller** - To control the video view, this is useful for custom usecases when you wish to control the addTrack and removeTrack +/// track functionalities on your own. +/// +/// Refer [HMSTextureView guide here](https://www.100ms.live/docs/flutter/v2/features/render-video) +class HMSTextureView extends StatelessWidget { + /// This will render video with trackId present in the track + /// [track] - the video track to be displayed + final HMSVideoTrack track; + + /// [scaleType] - To set the video scaling. + /// + /// ScaleType can be one of the following: [SCALE_ASPECT_FIT, SCALE_ASPECT_FILL, SCALE_ASPECT_BALANCED] + /// Default is [ScaleType.SCALE_ASPECT_FIT] + final ScaleType scaleType; + + /// [setMirror] - To set mirroring of video + final bool setMirror; + + /// [disableAutoSimulcastLayerSelect] - To disable auto simulcast (Adaptive Bitrate) + /// Default is [false] + final bool disableAutoSimulcastLayerSelect; + + /// [addTrackByDefault] - To call addTrack by default as HMSVideoView is attached to the tree. Default value is [true] + final bool addTrackByDefault; + + /// [controller] - To control the video view, this is useful for custom usecases when you wish to control the addTrack and removeTrack + /// track functionalities on your own. + final HMSTextureViewController? controller; + + HMSTextureView( + {Key? key, + required this.track, + this.setMirror = false, + this.scaleType = ScaleType.SCALE_ASPECT_FIT, + this.disableAutoSimulcastLayerSelect = false, + this.addTrackByDefault = true, + this.controller}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return _PlatformView( + track: track, + setMirror: setMirror, + scaleType: this.scaleType, + disableAutoSimulcastLayerSelect: disableAutoSimulcastLayerSelect, + addTrackByDefault: addTrackByDefault, + controller: controller, + ); + } +} + +class _PlatformView extends StatefulWidget { + final HMSTrack track; + final bool setMirror; + final ScaleType scaleType; + final bool disableAutoSimulcastLayerSelect; + final bool addTrackByDefault; + final HMSTextureViewController? controller; + + _PlatformView( + {Key? key, + required this.track, + this.setMirror = false, + required this.scaleType, + this.disableAutoSimulcastLayerSelect = false, + this.addTrackByDefault = true, + this.controller}) + : super(key: key); + + @override + State<_PlatformView> createState() => _PlatformViewState(); +} + +class _PlatformViewState extends State<_PlatformView> { + HMSTextureViewController? viewController; + + @override + void initState() { + ///If controller is null, then we create a new controller + ///else we use the controller provided by the app. + /// (Android Only) + if (Platform.isAndroid) { + if (widget.controller == null) { + viewController = + HMSTextureViewController(track: widget.track as HMSVideoTrack); + } else { + viewController = widget.controller; + } + + ///Here we set the callback method which gets called to set the view + viewController?.setCallbackMethod(setView); + } + super.initState(); + } + + ///This sets the view whenever any changes are performed in the properties of the view. + void setView() { + if (mounted) { + setState(() {}); + } + } + + @override + void dispose() { + ///Here we dispose the texture view + /// + ///Note that if the controller is created from app + ///then the application needs to call this method explicitly. + /// (Android Only) + if (Platform.isAndroid) { + if (widget.controller == null) { + viewController?.disposeTextureView(); + } + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (Platform.isAndroid) { + ///Here if the textureId is null we render an empty SizedBox + ///We get the textureId from createTextureView method in HMSTextureViewController + return viewController?.textureId == null + ? SizedBox() + : LayoutBuilder( + key: widget.key, + builder: (context, constraints) { + ///This method sets the height and width of the video based on the + ///size of video tile. + viewController?.setHeightWidth( + height: constraints.maxHeight, width: constraints.maxWidth); + return Center( + child: widget.scaleType != ScaleType.SCALE_ASPECT_FIT + ? Container( + width: constraints.maxWidth, + height: constraints.maxHeight, + child: TextureView( + scaleType: widget.scaleType, + viewController: viewController, + constraints: constraints, + setMirror: widget.setMirror, + ), + ) + : TextureView( + scaleType: widget.scaleType, + viewController: viewController, + constraints: constraints, + setMirror: widget.setMirror, + ), + ); + }, + ); + } else if (Platform.isIOS) { + ///UIKitView for ios it uses VideoView provided by 100ms ios_sdk internally. + return UiKitView( + viewType: 'HMSFlutterPlatformView', + creationParamsCodec: StandardMessageCodec(), + creationParams: { + 'track_id': widget.track.trackId, + 'set_mirror': + widget.track.source != "REGULAR" ? false : widget.setMirror, + 'scale_type': widget.scaleType.value, + 'disable_auto_simulcast_layer_select': + widget.disableAutoSimulcastLayerSelect + }, + gestureRecognizers: {}, + ); + } else { + throw UnimplementedError( + 'Video View is not implemented for this platform ${Platform.localHostname}'); + } + } +} + +///[TextureView] returns a Texture surface to render the video +class TextureView extends StatelessWidget { + const TextureView( + {Key? key, + required this.scaleType, + required this.viewController, + required this.constraints, + required this.setMirror}) + : super(key: key); + + final ScaleType scaleType; + final HMSTextureViewController? viewController; + final BoxConstraints constraints; + final bool setMirror; + + @override + Widget build(BuildContext context) { + return FittedBox( + clipBehavior: Clip.hardEdge, + fit: scaleType == ScaleType.SCALE_ASPECT_FIT + ? BoxFit.contain + : BoxFit.cover, + child: SizedBox( + width: (constraints.maxHeight * + ((viewController != null) ? viewController!.aspectRatio : 1)), + height: constraints.maxHeight, + child: Center( + child: Transform( + transform: Matrix4.identity()..rotateY(setMirror ? -pi : 0.0), + alignment: FractionalOffset.center, + child: Texture(textureId: viewController!.textureId!)), + ), + ), + ); + } +} diff --git a/packages/hmssdk_flutter/lib/src/ui/meeting/hms_texture_view_controller.dart b/packages/hmssdk_flutter/lib/src/ui/meeting/hms_texture_view_controller.dart new file mode 100644 index 000000000..25477b271 --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/ui/meeting/hms_texture_view_controller.dart @@ -0,0 +1,176 @@ +///Dart imports +import 'dart:io'; + +///Package imports +import 'package:flutter/services.dart'; + +///Project imports +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:hmssdk_flutter/src/enum/hms_video_view_event.dart'; +import 'package:hmssdk_flutter/src/service/platform_service.dart'; + +/// (Android Only) +///[HMSTextureViewController] is used to control the video view. It helps in controlling addTrack, removeTrack functionalities manually. +///It is useful in custom usecases where you wish to control the addTrack and removeTrack functionalities on your own. +///Please note that if you control the view creation, addTrack etc. on application, then application has the responsibility +///to release the texture view as well by calling [disposeTextureView] +class HMSTextureViewController { + ///[_textureId] is the unique id of the texture view + int? _textureId; + + ///getter for [_textureId] + int? get textureId => _textureId; + + ///[_height] is the height of the view + int? _height; + + ///[_width] is the width of the view + int? _width; + + ///[_updateViewCallback] is the callback required for refreshing the view when certain + ///properties of the view changes. + Function? _updateViewCallback; + + ///[aspectRatio] is the aspect ratio of the view + double aspectRatio = 1; + + HMSTextureViewController( + {HMSVideoTrack? track, + bool addTrackByDefault = true, + bool? disableAutoSimulcastLayerSelect = false}) { + createTextureView( + track: track, + addTrackByDefault: addTrackByDefault, + disableAutoSimulcastLayerSelect: disableAutoSimulcastLayerSelect); + } + + /// (Android Only) + ///[createTextureView] is used to create the texture view. It takes [track] as an optional parameter. + ///If [track] is provided, then it will add the track to the texture view by default. + ///If [addTrackByDefault] is set to true, then it will add the track to the texture view by default. + ///If [disableAutoSimulcastLayerSelect] is set to true, then it will disable the auto simulcast layer selection. + void createTextureView( + {HMSTrack? track, + bool addTrackByDefault = true, + bool? disableAutoSimulcastLayerSelect}) async { + if (Platform.isAndroid) { + var result = await PlatformService.invokeMethod( + PlatformMethod.createTextureView, + arguments: { + "track_id": track?.trackId, + "add_track_by_def": addTrackByDefault, + "disable_auto_simulcast_layer_select": + disableAutoSimulcastLayerSelect ?? false + }); + if (result["success"]) { + _textureId = result["data"]["texture_id"]; + EventChannel('HMSTextureView/Texture/$textureId') + .receiveBroadcastStream() + .listen(_eventListener); + if (_updateViewCallback != null) { + _updateViewCallback!(); + } + } + } + } + + /// (Android Only) + ///[setCallbackMethod] is used to set the callback method for the texture view. + ///This callback method is used to refresh the view when certain properties of the view changes. + void setCallbackMethod(Function callback) { + if (Platform.isAndroid) { + _updateViewCallback = callback; + } + } + + /// (Android Only) + ///[disposeTextureView] is used to dispose the texture view. + ///It is the responsibility of the application to dispose the texture view if the controller is created + ///by the application. + void disposeTextureView() async { + if (Platform.isAndroid) { + var result = await PlatformService.invokeMethod( + PlatformMethod.disposeTextureView, + arguments: {"texture_id": textureId.toString()}); + if (result["success"]) { + _textureId = null; + } + } + } + + /// (Android Only) + ///[addTrack] is used to add the track to the texture view. + ///If [disableAutoSimulcastLayerSelect] is set to true, then it will disable the auto simulcast layer selection. + void addTrack( + {required HMSVideoTrack track, + bool? disableAutoSimulcastLayerSelect}) async { + if (Platform.isAndroid) { + await PlatformService.invokeMethod(PlatformMethod.addTrack, arguments: { + "track_id": track.trackId, + "texture_id": textureId.toString(), + "disable_auto_simulcast_layer_select": + disableAutoSimulcastLayerSelect ?? false, + "height": _height, + "width": _width + }); + } + } + + /// (Android Only) + ///[_setDisplayResolution] is used to set the display resolution of the texture view. + ///It is used internally by the SDK. + void _setDisplayResolution({required int height, required int width}) { + if (Platform.isAndroid) { + PlatformService.invokeMethod(PlatformMethod.setDisplayResolution, + arguments: { + "texture_id": textureId.toString(), + "height": height, + "width": width + }); + } + } + + /// (Android Only) + ///[removeTrack] is used to remove the track from the texture view. + void removeTrack() { + if (Platform.isAndroid) { + PlatformService.invokeMethod(PlatformMethod.removeTrack, + arguments: {"texture_id": textureId.toString()}); + } + } + + /// (Android Only) + ///[setHeightWidth] is used to set the height and width of the texture view. + void setHeightWidth({required double height, required double width}) { + if (Platform.isAndroid) { + if (_height != height.toInt() || _width != width.toInt()) { + _height = height.toInt(); + _width = width.toInt(); + _setDisplayResolution(height: _height!, width: _width!); + } + } + } + + ///[_eventListener] is the callback method for the texture view. + ///We get the native callbacks like [onResolutionChanged] from the texture view. + void _eventListener(dynamic event) { + HMSVideoViewEvent videoViewEvent = + HMSVideoViewValues.getHMSVideoViewEventFromString(event['event_name']); + switch (videoViewEvent) { + case HMSVideoViewEvent.onResolutionChanged: + int width = event['data']?['width']; + int height = event['data']?['height']; + + if (width == 0.0 || height == 0.0) { + aspectRatio = 1.0; + } + aspectRatio = width / height; + if (_updateViewCallback != null) { + _updateViewCallback!(); + } + break; + case HMSVideoViewEvent.unknown: + break; + } + } +} diff --git a/packages/hmssdk_flutter/pubspec.yaml b/packages/hmssdk_flutter/pubspec.yaml index 7346f2577..ecc8f88f7 100644 --- a/packages/hmssdk_flutter/pubspec.yaml +++ b/packages/hmssdk_flutter/pubspec.yaml @@ -1,6 +1,6 @@ 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.9.3 +version: 1.9.4 homepage: https://www.100ms.live/ repository: https://github.com/100mslive/100ms-flutter issue_tracker: https://github.com/100mslive/100ms-flutter/issues diff --git a/sample apps/flutterflow-prebuilt-quickstart/android/app/src/main/kotlin/com/example/my_project/MainActivity.kt b/sample apps/flutterflow-prebuilt-quickstart/android/app/src/main/kotlin/com/example/my_project/MainActivity.kt index fe90c14b6..bad404f07 100644 --- a/sample apps/flutterflow-prebuilt-quickstart/android/app/src/main/kotlin/com/example/my_project/MainActivity.kt +++ b/sample apps/flutterflow-prebuilt-quickstart/android/app/src/main/kotlin/com/example/my_project/MainActivity.kt @@ -2,5 +2,4 @@ package com.mycompany.hmsroomkitsample import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity : FlutterActivity() diff --git a/sample apps/flutterflow-prebuilt-quickstart/ios/ImageNotification/NotificationService.swift b/sample apps/flutterflow-prebuilt-quickstart/ios/ImageNotification/NotificationService.swift index d16342e0a..cecfa1a4f 100644 --- a/sample apps/flutterflow-prebuilt-quickstart/ios/ImageNotification/NotificationService.swift +++ b/sample apps/flutterflow-prebuilt-quickstart/ios/ImageNotification/NotificationService.swift @@ -15,7 +15,7 @@ class NotificationService: UNNotificationServiceExtension { bestAttemptContent, withContentHandler: contentHandler) } - + override func serviceExtensionTimeWillExpire() { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. diff --git a/sample apps/flutterflow-prebuilt-quickstart/lib/custom_code/widgets/test_prebuilt.dart b/sample apps/flutterflow-prebuilt-quickstart/lib/custom_code/widgets/test_prebuilt.dart index ba20efff6..b1e3ac01b 100644 --- a/sample apps/flutterflow-prebuilt-quickstart/lib/custom_code/widgets/test_prebuilt.dart +++ b/sample apps/flutterflow-prebuilt-quickstart/lib/custom_code/widgets/test_prebuilt.dart @@ -1,7 +1,7 @@ // Automatic FlutterFlow imports import '/flutter_flow/flutter_flow_theme.dart'; import '/flutter_flow/flutter_flow_util.dart'; -import 'index.dart'; // Imports other custom widgets +// Imports other custom widgets import 'package:flutter/material.dart'; // Begin custom widget code // DO NOT REMOVE OR MODIFY THE CODE ABOVE! diff --git a/sample apps/flutterflow-prebuilt-quickstart/lib/custom_code/widgets/test_widget.dart b/sample apps/flutterflow-prebuilt-quickstart/lib/custom_code/widgets/test_widget.dart index deb15610f..f1b5402e5 100644 --- a/sample apps/flutterflow-prebuilt-quickstart/lib/custom_code/widgets/test_widget.dart +++ b/sample apps/flutterflow-prebuilt-quickstart/lib/custom_code/widgets/test_widget.dart @@ -1,7 +1,7 @@ // Automatic FlutterFlow imports import '/flutter_flow/flutter_flow_theme.dart'; import '/flutter_flow/flutter_flow_util.dart'; -import 'index.dart'; // Imports other custom widgets +// Imports other custom widgets import 'package:flutter/material.dart'; // Begin custom widget code // DO NOT REMOVE OR MODIFY THE CODE ABOVE! diff --git a/sample apps/flutterflow-prebuilt-quickstart/lib/flutter_flow/flutter_flow_util.dart b/sample apps/flutterflow-prebuilt-quickstart/lib/flutter_flow/flutter_flow_util.dart index f5e13f359..2fa272d70 100644 --- a/sample apps/flutterflow-prebuilt-quickstart/lib/flutter_flow/flutter_flow_util.dart +++ b/sample apps/flutterflow-prebuilt-quickstart/lib/flutter_flow/flutter_flow_util.dart @@ -10,8 +10,6 @@ import 'package:url_launcher/url_launcher.dart'; import '../main.dart'; -import 'lat_lng.dart'; - export 'lat_lng.dart'; export 'place.dart'; export 'uploaded_file.dart'; diff --git a/sample apps/flutterflow-prebuilt-quickstart/lib/main.dart b/sample apps/flutterflow-prebuilt-quickstart/lib/main.dart index 4bdeea0e8..cf59fb127 100644 --- a/sample apps/flutterflow-prebuilt-quickstart/lib/main.dart +++ b/sample apps/flutterflow-prebuilt-quickstart/lib/main.dart @@ -1,4 +1,3 @@ -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -7,7 +6,6 @@ import 'flutter_flow/flutter_flow_theme.dart'; import 'flutter_flow/flutter_flow_util.dart'; import 'flutter_flow/internationalization.dart'; import 'flutter_flow/nav/nav.dart'; -import 'index.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); diff --git a/sample apps/flutterflow-prebuilt-quickstart/lib/pages/home_page/home_page_model.dart b/sample apps/flutterflow-prebuilt-quickstart/lib/pages/home_page/home_page_model.dart index 24216fb29..17357e771 100644 --- a/sample apps/flutterflow-prebuilt-quickstart/lib/pages/home_page/home_page_model.dart +++ b/sample apps/flutterflow-prebuilt-quickstart/lib/pages/home_page/home_page_model.dart @@ -3,7 +3,6 @@ import '/flutter_flow/flutter_flow_util.dart'; import '/flutter_flow/flutter_flow_widgets.dart'; import 'home_page_widget.dart' show HomePageWidget; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; diff --git a/sample apps/flutterflow-prebuilt-quickstart/lib/pages/prebuilt_page/prebuilt_page_model.dart b/sample apps/flutterflow-prebuilt-quickstart/lib/pages/prebuilt_page/prebuilt_page_model.dart index 97fe31f21..cd757256d 100644 --- a/sample apps/flutterflow-prebuilt-quickstart/lib/pages/prebuilt_page/prebuilt_page_model.dart +++ b/sample apps/flutterflow-prebuilt-quickstart/lib/pages/prebuilt_page/prebuilt_page_model.dart @@ -4,8 +4,6 @@ import '/flutter_flow/flutter_flow_widgets.dart'; import '/custom_code/widgets/index.dart' as custom_widgets; import 'prebuilt_page_widget.dart' show PrebuiltPageWidget; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; diff --git a/sample apps/flutterflow-prebuilt-quickstart/lib/pages/prebuilt_page/prebuilt_page_widget.dart b/sample apps/flutterflow-prebuilt-quickstart/lib/pages/prebuilt_page/prebuilt_page_widget.dart index 8616a535b..748ad6a82 100644 --- a/sample apps/flutterflow-prebuilt-quickstart/lib/pages/prebuilt_page/prebuilt_page_widget.dart +++ b/sample apps/flutterflow-prebuilt-quickstart/lib/pages/prebuilt_page/prebuilt_page_widget.dart @@ -3,7 +3,6 @@ import '/flutter_flow/flutter_flow_util.dart'; import '/flutter_flow/flutter_flow_widgets.dart'; import '/custom_code/widgets/index.dart' as custom_widgets; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart';