diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index bb53f1c98..94eb978a1 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,8 +9,8 @@ plugins: lint: enabled: - actionlint@1.6.27 - - checkov@3.2.60 - - osv-scanner@1.7.0 + - checkov@3.2.71 + - osv-scanner@1.7.2 - trivy@0.50.1 - trufflehog@3.71.0 - oxipng@9.0.0 diff --git a/packages/hms_room_kit/CHANGELOG.md b/packages/hms_room_kit/CHANGELOG.md index ef767db42..2dabfff25 100644 --- a/packages/hms_room_kit/CHANGELOG.md +++ b/packages/hms_room_kit/CHANGELOG.md @@ -5,7 +5,26 @@ | 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.1.0 - 2024-04-19 +## 1.1.1 - 2024-04-26 + +| Package | Version | +| -------------- | ------------------------------------------------------------------------------------------------------ | +| hms_room_kit | 1.1.1 | +| hmssdk_flutter | 1.10.1 | + +### 🚀 Added + +- Support for captions in HLS Player + + HLS Player now supports captions for better accessibility. This can be enabled or disabled from the player settings. + +- Introducing Landscape Mode for HLS Player + + HLS Player now supports landscape mode for better viewing experience. + +Uses `hmssdk_flutter` package version 1.10.1 + +## 1.1.0 - 2024-04-22 | Package | Version | | -------------- | ------------------------------------------------------------------------------------------------------ | @@ -32,7 +51,7 @@ Prebuilt no longer uses `flutter_foreground_task` package. For apps that require foreground service, the package can be added on the application level. -Uses `hmssdk_flutter` package version to 1.1.10 +Uses `hmssdk_flutter` package version 1.10.0 ## 1.0.17 - 2024-04-01 @@ -41,7 +60,7 @@ Uses `hmssdk_flutter` package version to 1.1.10 | hms_room_kit | 1.0.17 | | hmssdk_flutter | 1.9.14 | -Uses `hmssdk_flutter` package version to 1.9.14 +Uses `hmssdk_flutter` package version 1.9.14 ## 1.0.16 - 2024-03-15 diff --git a/packages/hms_room_kit/README.md b/packages/hms_room_kit/README.md index 85fb04eed..28592a53d 100644 --- a/packages/hms_room_kit/README.md +++ b/packages/hms_room_kit/README.md @@ -1,6 +1,7 @@ # 100ms Room Kit 🎉 [![Pub Version](https://img.shields.io/pub/v/hms_room_kit)](https://pub.dev/packages/hms_room_kit) +[![Build](https://github.com/100mslive/100ms-flutter/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/100mslive/100ms-flutter/actions/workflows/build.yml) [![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) diff --git a/packages/hms_room_kit/example/pubspec.lock b/packages/hms_room_kit/example/pubspec.lock index a43e05a3d..5013a3708 100644 --- a/packages/hms_room_kit/example/pubspec.lock +++ b/packages/hms_room_kit/example/pubspec.lock @@ -93,10 +93,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" dots_indicator: dependency: transitive description: @@ -206,14 +206,15 @@ packages: path: ".." relative: true source: path - version: "1.1.0" + version: "1.1.1" hmssdk_flutter: dependency: transitive description: - path: "../../hmssdk_flutter" - relative: true - source: path - version: "1.10.0" + name: hmssdk_flutter + sha256: bfa6e6ec411d6f86f6cc054936fb2163c4cd3f8703f8848099689652b3794376 + url: "https://pub.dev" + source: hosted + version: "1.10.1" http: dependency: transitive description: diff --git a/packages/hms_room_kit/example/pubspec.yaml b/packages/hms_room_kit/example/pubspec.yaml index 2b69549d4..9a41feb9d 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.1.0 +version: 1.1.1 environment: sdk: ">=2.19.6 <3.0.0" diff --git a/packages/hms_room_kit/lib/src/assets/icons/no_network.png b/packages/hms_room_kit/lib/src/assets/icons/no_network.png deleted file mode 100644 index 3199decb9..000000000 Binary files a/packages/hms_room_kit/lib/src/assets/icons/no_network.png and /dev/null differ diff --git a/packages/hms_room_kit/lib/src/assets/icons/pause.svg b/packages/hms_room_kit/lib/src/assets/icons/pause.svg new file mode 100644 index 000000000..f7fc7e65f --- /dev/null +++ b/packages/hms_room_kit/lib/src/assets/icons/pause.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/hms_room_kit/lib/src/assets/icons/play.svg b/packages/hms_room_kit/lib/src/assets/icons/play.svg new file mode 100644 index 000000000..5183c98df --- /dev/null +++ b/packages/hms_room_kit/lib/src/assets/icons/play.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/hms_room_kit/lib/src/assets/icons/seek_backward.svg b/packages/hms_room_kit/lib/src/assets/icons/seek_backward.svg new file mode 100644 index 000000000..0879692ba --- /dev/null +++ b/packages/hms_room_kit/lib/src/assets/icons/seek_backward.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/hms_room_kit/lib/src/assets/icons/seek_forward.svg b/packages/hms_room_kit/lib/src/assets/icons/seek_forward.svg new file mode 100644 index 000000000..d17b2fff3 --- /dev/null +++ b/packages/hms_room_kit/lib/src/assets/icons/seek_forward.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/hms_room_kit/lib/src/common/utility_components.dart b/packages/hms_room_kit/lib/src/common/utility_components.dart index 7bc5d99ad..bc56087d9 100644 --- a/packages/hms_room_kit/lib/src/common/utility_components.dart +++ b/packages/hms_room_kit/lib/src/common/utility_components.dart @@ -992,10 +992,10 @@ class UtilityComponents { MeetingStore meetingStore = Provider.of(context); return GestureDetector( onTap: () { - if (meetingStore.isLandscapeLocked) { - meetingStore.setLandscapeLock(false); + if (meetingStore.isScreenRotationAllowed) { + meetingStore.allowScreenRotation(false); } else { - meetingStore.setLandscapeLock(true); + meetingStore.allowScreenRotation(true); } }, child: Padding( @@ -1003,7 +1003,7 @@ class UtilityComponents { child: SvgPicture.asset( "packages/hms_room_kit/lib/src/assets/icons/rotate.svg", colorFilter: ColorFilter.mode( - meetingStore.isLandscapeLocked ? Colors.blue : iconColor, + meetingStore.isScreenRotationAllowed ? Colors.blue : iconColor, BlendMode.srcIn)), ), ); diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_hand_raise_menu.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_hand_raise_menu.dart index 20aafc5af..7d76ff6b3 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_hand_raise_menu.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_hand_raise_menu.dart @@ -1,12 +1,18 @@ +library; + +///Package imports import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:provider/provider.dart'; + +///Project imports import 'package:hms_room_kit/hms_room_kit.dart'; import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; import 'package:hms_room_kit/src/meeting/meeting_store.dart'; import 'package:hms_room_kit/src/widgets/bottom_sheets/hls_app_utilities_bottom_sheet.dart'; import 'package:hms_room_kit/src/widgets/common_widgets/hms_embedded_button.dart'; -import 'package:provider/provider.dart'; +///[HLSHandRaiseMenu] is a widget that is used to render the hand raise menu in case of Viewer near realtime class HLSHandRaiseMenu extends StatelessWidget { const HLSHandRaiseMenu({Key? key}) : super(key: key); diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_player.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_player.dart index e756e567e..0632aad64 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_player.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_player.dart @@ -28,56 +28,54 @@ class HLSPlayer extends StatelessWidget { ///Renders the HLS Player if the HLS has started ///Otherwise renders the waiting UI hasHLSStarted - ? Align( - alignment: Alignment.center, - child: Selector( - selector: (_, hlsPlayerStore) => - hlsPlayerStore.hlsPlayerSize, - builder: (_, hlsPlayerSize, __) { - return AspectRatio( - aspectRatio: - hlsPlayerSize.width / hlsPlayerSize.height, - child: InkWell( - onTap: () => context - .read() - .toggleButtonsVisibility(), - splashFactory: NoSplash.splashFactory, - splashColor: HMSThemeColors.backgroundDim, - child: IgnorePointer( - child: const HMSHLSPlayer( - showPlayerControls: false, - ), - ), - ), - ); - }), - ) + ? Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.isFullScreen, + builder: (_, isFullScreen, __) { + return InteractiveViewer( + minScale: 1, + maxScale: 8, + child: Align( + alignment: Alignment.center, + child: Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.hlsPlayerSize, + builder: (_, hlsPlayerSize, __) { + return InkWell( + onTap: () => context + .read() + .toggleButtonsVisibility(), + splashFactory: NoSplash.splashFactory, + splashColor: HMSThemeColors.backgroundDim, + child: AspectRatio( + aspectRatio: hlsPlayerSize.width / + hlsPlayerSize.height, + child: Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.isFullScreen, + builder: (_, isFullScreen, __) { + return IgnorePointer( + child: const HMSHLSPlayer( + showPlayerControls: false, + ), + ); + }), + ), + ); + }), + ), + ); + }) : Center(child: const HLSWaitingUI()), ///This renders the overlay controls for HLS Player Align( - alignment: Alignment.center, - child: Selector( - selector: (_, hlsPlayerStore) => - hlsPlayerStore.isFullScreen, - builder: (_, isFullScreen, __) { - return isFullScreen - ? Selector( - selector: (_, hlsPlayerStore) => - hlsPlayerStore.hlsPlayerSize, - builder: (_, hlsPlayerSize, __) { - return AspectRatio( - aspectRatio: hlsPlayerSize.width / - hlsPlayerSize.height, - child: HLSPlayerOverlayOptions( - hasHLSStarted: hasHLSStarted, - ), - ); - }) - : HLSPlayerOverlayOptions( - hasHLSStarted: hasHLSStarted); - }), - ), + alignment: Alignment.center, + child: HLSPlayerOverlayOptions( + hasHLSStarted: hasHLSStarted, + )), + + ///This renders the circular progress indicator when the player is buffering or failed Selector( selector: (_, hlsPlayerStore) => hlsPlayerStore.playerPlaybackState, diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_player_desktop_controls.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_desktop_controls.dart index 9ebba4811..e616c351c 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_player_desktop_controls.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_desktop_controls.dart @@ -9,6 +9,10 @@ import 'package:hms_room_kit/src/widgets/bottom_sheets/chat_bottom_sheet.dart'; ///[HLSPlayerDesktopControls] is the desktop controls for the HLS Player class HLSPlayerDesktopControls extends StatefulWidget { + final Orientation orientation; + const HLSPlayerDesktopControls({Key? key, required this.orientation}) + : super(key: key); + @override State createState() => _HLSPlayerDesktopControlsState(); @@ -26,25 +30,25 @@ class _HLSPlayerDesktopControlsState extends State { @override Widget build(BuildContext context) { - return Expanded( - child: Column( - children: [ - ///Renders HLS Stream Description and Chat Bottom Sheet - HLSStreamDescription( - showDescription: showDescription, - toggleDescription: toggleDescription), - - ///Renders Chat Bottom Sheet only is the description is not visible - if (!showDescription) - Expanded( - child: Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: ChatBottomSheet( - isHLSChat: true, - ), - )) - ], - ), + return Column( + children: [ + ///Renders HLS Stream Description and Chat Bottom Sheet + widget.orientation == Orientation.portrait + ? HLSStreamDescription( + showDescription: showDescription, + toggleDescription: toggleDescription) + : const SizedBox(), + + ///Renders Chat Bottom Sheet only if the description is not visible + if (!showDescription) + Expanded( + child: Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: const ChatBottomSheet( + isHLSChat: true, + ), + )) + ], ); } } diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_player_overlay_options.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_overlay_options.dart index 58dec346e..339d85fce 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_player_overlay_options.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_overlay_options.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; ///Project imports import 'package:hms_room_kit/src/hls_viewer/hls_viewer_bottom_navigation_bar.dart'; import 'package:hms_room_kit/src/hls_viewer/hls_viewer_header.dart'; +import 'package:hms_room_kit/src/hls_viewer/hls_viewer_mid_section.dart'; ///[HLSPlayerOverlayOptions] renders the overlay options for the HLS Player class HLSPlayerOverlayOptions extends StatelessWidget { @@ -14,16 +15,30 @@ class HLSPlayerOverlayOptions extends StatelessWidget { const HLSPlayerOverlayOptions({super.key, required this.hasHLSStarted}); @override Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + ///Here top and bottom navigation bar are in Column + ///while mid section is above the Column + ///This is done to avoid overflowing in case of + ///large transcription + return Stack( children: [ - HLSViewerHeader( - hasHLSStarted: hasHLSStarted, + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + HLSViewerHeader( + hasHLSStarted: hasHLSStarted, + ), + + ///Renders the bottom navigation bar if the HLS has started + ///Otherwise does not render the bottom navigation bar + hasHLSStarted ? HLSViewerBottomNavigationBar() : const SizedBox() + ], ), - ///Renders the bottom navigation bar if the HLS has started - ///Otherwise does not render the bottom navigation bar - hasHLSStarted ? HLSViewerBottomNavigationBar() : const SizedBox() + ///This renders the pause/play button + Align( + alignment: Alignment.center, + child: hasHLSStarted ? HLSViewerMidSection() : const SizedBox(), + ) ], ); } diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_player_seekbar.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_seekbar.dart new file mode 100644 index 000000000..fe28dc1b4 --- /dev/null +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_seekbar.dart @@ -0,0 +1,92 @@ +library; + +///Package imports +import 'package:flutter/material.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; + +///Project imports +import 'package:hms_room_kit/src/meeting/meeting_store.dart'; +import 'package:hms_room_kit/hms_room_kit.dart'; +import 'package:hms_room_kit/src/hls_viewer/hls_player_store.dart'; + +///[HLSPlayerSeekbar] is the seekbar for the HLS Player +class HLSPlayerSeekbar extends StatefulWidget { + @override + State createState() => _HLSPlayerSeekbarState(); +} + +class _HLSPlayerSeekbarState extends State { + int seekBarValue = 0; + int minValue = 0, maxValue = 300; + bool isInteracting = false; + + ///[_toggleIsInteracting] toggles the [isInteracting] variable + void _toggleIsInteracting(bool value) { + setState(() { + isInteracting = value; + }); + } + + @override + void initState() { + super.initState(); + maxValue = context.read().rollingWindow.inSeconds; + } + + @override + Widget build(BuildContext context) { + return (context + .read() + .hmsRoom + ?.hmshlsStreamingState + ?.variants[0] + ?.playlistType == + HMSHLSPlaylistType.dvr) + ? Selector>( + selector: (_, hlsPlayerStore) => Tuple2( + hlsPlayerStore.timeFromLive, hlsPlayerStore.rollingWindow), + builder: (_, data, __) { + maxValue = data.item2.inSeconds; + seekBarValue = maxValue > 0 ? maxValue - data.item1.inSeconds : 0; + minValue = 0; + return (maxValue > 0 && seekBarValue > 0) + ? SliderTheme( + data: SliderThemeData( + trackHeight: isInteracting ? 6 : 4, + trackShape: RoundedRectSliderTrackShape(), + inactiveTrackColor: HMSThemeColors.baseWhite, + activeTrackColor: HMSThemeColors.primaryDefault, + thumbColor: HMSThemeColors.primaryDefault, + thumbShape: RoundSliderThumbShape( + enabledThumbRadius: isInteracting ? 10 : 6), + overlayShape: + RoundSliderOverlayShape(overlayRadius: 0)), + child: Slider( + value: seekBarValue.toDouble(), + onChanged: (value) {}, + onChangeEnd: (value) { + if (value > seekBarValue) { + HMSHLSPlayerController.seekForward( + seconds: (value - seekBarValue).toInt()); + } else { + HMSHLSPlayerController.seekBackward( + seconds: (seekBarValue - value).toInt()); + } + HMSHLSPlayerController.resume(); + _toggleIsInteracting(false); + }, + onChangeStart: (_) { + HMSHLSPlayerController.pause(); + _toggleIsInteracting(true); + }, + min: minValue.toDouble(), + max: maxValue.toDouble(), + ), + ) + : const SizedBox(); + }) + : const SizedBox(); + } +} diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_player_store.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_store.dart index f48fab29b..c91f32a35 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_player_store.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_store.dart @@ -3,6 +3,7 @@ library; ///Dart imports import 'dart:async'; import 'dart:developer'; +import 'dart:io'; ///Package imports import 'package:flutter/material.dart'; @@ -14,6 +15,11 @@ import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; ///[HLSPlayerStore] is a store that stores the state of the HLS Player class HLSPlayerStore extends ChangeNotifier implements HMSHLSPlaybackEventsListener { + ///[timeBeforeLive] is the time to show the go live button + ///It is set to 10 seconds for Android and 1 second for iOS + ///Basically `Go Live` will only be shown if the distance from live is greater than this value + final int timeBeforeLive = Platform.isAndroid ? 10000 : 5000; + ///This variable stores whether the application is in full screen or not bool isFullScreen = false; @@ -29,6 +35,15 @@ class HLSPlayerStore extends ChangeNotifier ///This variable stores whether the captions are supported or not bool areCaptionsSupported = false; + ///This variable stores whether the stream is at live edge or not + bool isLive = true; + + ///This variable stores the time from live edge + Duration timeFromLive = Duration(milliseconds: 0); + + ///This variable stores the stream rolling window time + Duration rollingWindow = Duration(milliseconds: 0); + ///This variable stores whether the chat is opened or not ///The initial value is taken from the [HMSRoomLayout.chatData] bool isChatOpened = (HMSRoomLayout.chatData?.isOpenInitially ?? false) && @@ -49,10 +64,17 @@ class HLSPlayerStore extends ChangeNotifier ///[hlsPlayerSize] stores the resolution of HLS Stream Size hlsPlayerSize = Size(1, 1); + ///This variable stores whether the HLS Stats are enabled or not bool isHLSStatsEnabled = false; + ///This variable stores the player playback state HMSHLSPlaybackState playerPlaybackState = HMSHLSPlaybackState.PLAYING; + ///This variable handles the timer for fetching the stream properties + Timer? _timer; + + String? caption; + ///This method starts a timer for 5 seconds and then hides the buttons /// ///[isStreamPlaying] is used to check if the video is playing or not @@ -90,6 +112,17 @@ class HLSPlayerStore extends ChangeNotifier notifyListeners(); } + ///[toggleStreamPlaying] toggles the stream playing + void toggleStreamPlaying() { + if (isStreamPlaying) { + HMSHLSPlayerController.pause(); + } else { + HMSHLSPlayerController.resume(); + } + isStreamPlaying = !isStreamPlaying; + notifyListeners(); + } + ///This method toggles the visibility of the chat void toggleIsChatOpened() { isChatOpened = !isChatOpened; @@ -98,6 +131,11 @@ class HLSPlayerStore extends ChangeNotifier ///This method toggles the visibility of the captions void toggleCaptions() { + if (isCaptionEnabled) { + HMSHLSPlayerController.disableClosedCaptions(); + } else { + HMSHLSPlayerController.enableClosedCaptions(); + } isCaptionEnabled = !isCaptionEnabled; notifyListeners(); } @@ -128,6 +166,11 @@ class HLSPlayerStore extends ChangeNotifier void areClosedCaptionsSupported() async { areCaptionsSupported = await HMSHLSPlayerController.areClosedCaptionsSupported(); + + ///If isCaptionEnabled is true we enable the captions + if (isCaptionEnabled) { + HMSHLSPlayerController.enableClosedCaptions(); + } notifyListeners(); } @@ -141,6 +184,37 @@ class HLSPlayerStore extends ChangeNotifier notifyListeners(); } + ///[startTimer] starts a timer to get the stream properties every 3 seconds + void startTimer() { + if (_timer != null) { + _timer?.cancel(); + } + _timer = Timer.periodic(Duration(seconds: 3), (timer) { + getStreamProperties(); + }); + } + + ///[cancelTimer] cancels the timer + void cancelTimer() { + _timer?.cancel(); + } + + ///[getStreamProperties] gets the stream properties + void getStreamProperties() async { + HLSStreamProperties result = + await HMSHLSPlayerController.getStreamProperties(); + + ///If the [rollingWindowTime] is not null we set the [rollingWindow] to the value of [rollingWindowTime] + ///If the [streamDuration] is not null we set the [rollingWindow] to the value of [streamDuration] + ///If both are null we set the [rollingWindow] to 0 + if (result.rollingWindowTime != null && result.streamDuration == null) { + rollingWindow = Duration(seconds: result.rollingWindowTime!.toInt()); + } else if (result.streamDuration != null) { + rollingWindow = Duration(seconds: result.streamDuration!.toInt()); + } + notifyListeners(); + } + @override void onCue({required HMSHLSCue hlsCue}) {} @@ -149,7 +223,9 @@ class HLSPlayerStore extends ChangeNotifier @override void onHLSEventUpdate({required HMSHLSPlayerStats playerStats}) { - log("onHLSEventUpdate-> bitrate:${playerStats.averageBitrate} buffered duration: ${playerStats.bufferedDuration}"); + log("onHLSEventUpdate-> distanceFromLive: ${playerStats.distanceFromLive} buffered duration: ${playerStats.bufferedDuration}"); + isLive = playerStats.distanceFromLive < timeBeforeLive; + timeFromLive = Duration(milliseconds: playerStats.distanceFromLive.toInt()); hlsPlayerStats = playerStats; notifyListeners(); } @@ -163,12 +239,26 @@ class HLSPlayerStore extends ChangeNotifier void onPlaybackStateChanged({required HMSHLSPlaybackState playbackState}) { log("Playback state changed to ${playbackState.name}"); playerPlaybackState = playbackState; - if (playerPlaybackState == HMSHLSPlaybackState.PLAYING) { - isPlayerFailed = false; - areClosedCaptionsSupported(); - } - if (playerPlaybackState == HMSHLSPlaybackState.FAILED) { - isPlayerFailed = true; + switch (playbackState) { + case HMSHLSPlaybackState.PLAYING: + areClosedCaptionsSupported(); + setHLSPlayerStats(true); + startTimer(); + isStreamPlaying = true; + isPlayerFailed = false; + break; + case HMSHLSPlaybackState.STOPPED: + break; + case HMSHLSPlaybackState.PAUSED: + isStreamPlaying = false; + break; + case HMSHLSPlaybackState.BUFFERING: + break; + case HMSHLSPlaybackState.FAILED: + isPlayerFailed = true; + break; + case HMSHLSPlaybackState.UNKNOWN: + break; } notifyListeners(); } @@ -179,4 +269,14 @@ class HLSPlayerStore extends ChangeNotifier hlsPlayerSize = size; notifyListeners(); } + + @override + void onCues({required List subtitles}) { + log("onCues -> $subtitles"); + String newSubtitles = subtitles.join(" "); + if (newSubtitles != caption) { + caption = newSubtitles; + } + notifyListeners(); + } } diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_bottom_navigation_bar.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_bottom_navigation_bar.dart index 76584868a..ddb2c8192 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_bottom_navigation_bar.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_bottom_navigation_bar.dart @@ -1,11 +1,17 @@ library; +///Dart imports +import 'dart:io'; + ///Package imports import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_subheading_text.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:provider/provider.dart'; ///Project imports +import 'package:hms_room_kit/src/hls_viewer/hls_player_seekbar.dart'; import 'package:hms_room_kit/hms_room_kit.dart'; import 'package:hms_room_kit/src/hls_viewer/hls_player_store.dart'; @@ -13,6 +19,13 @@ import 'package:hms_room_kit/src/hls_viewer/hls_player_store.dart'; class HLSViewerBottomNavigationBar extends StatelessWidget { const HLSViewerBottomNavigationBar({super.key}); + String _setTimeFromLive(Duration time) { + int minutes = time.inMinutes; + int seconds = time.inSeconds.remainder(60); + + return "-${minutes > 0 ? "${minutes.toString().padLeft(2, '0')}:" : ""}${seconds.toString().padLeft(2, '0')}s"; + } + @override Widget build(BuildContext context) { return Container( @@ -23,27 +36,32 @@ class HLSViewerBottomNavigationBar extends StatelessWidget { colors: [Colors.black.withAlpha(0), Colors.black.withAlpha(64)])), child: Padding( padding: EdgeInsets.only(left: 12, right: 12), - - ///Here we render the chat component if the chat is opened - ///We also render the leave button, hand raise button, chat button and the menu button child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - ///Chat Component only visible when the chat is opened - // if (HMSRoomLayout.chatData != null) - // Selector( - // selector: (_, hlsPlayerStore) => hlsPlayerStore.isChatOpened, - // builder: (_, isChatOpened, __) { - // if (isChatOpened) { - // Provider.of(context, listen: true) - // .isNewMessageReceived = false; - // } - // return isChatOpened - // ? const OverlayChatComponent() - // : Container(); - // }), + ///This renders the captions only in case of android since iOS provides caption out of the box + if (Platform.isAndroid) + Selector( + selector: (_, hlsPlayerStore) => hlsPlayerStore.caption, + builder: (_, caption, __) { + return caption != null + ? Padding( + padding: const EdgeInsets.only( + bottom: 4.0, left: 4, right: 4), + child: Center( + child: HMSSubheadingText( + text: caption, + textColor: HMSThemeColors.baseWhite, + fontWeight: FontWeight.w600, + maxLines: 5, + ), + ), + ) + : SizedBox(); + }), ///Bottom Navigation Bar - ///We render the leave button, hand raise button, chat button and the menu button + ///We render the stream controls here ///We only render the bottom navigation bar when the stream controls are visible Selector( selector: (_, hlsPlayerStore) => @@ -53,28 +71,90 @@ class HLSViewerBottomNavigationBar extends StatelessWidget { ? Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - ///This renders the go live button - Row( - children: [ - GestureDetector( - onTap: () => {}, - child: Row( + ///This renders the go live/live button + Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.isLive, + builder: (_, isLive, __) { + return Row( children: [ - Padding( - padding: - const EdgeInsets.only(right: 8.0), - child: SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/red_dot.svg"), - ), - HMSTitleText( - text: "LIVE", - textColor: HMSThemeColors - .onSurfaceHighEmphasis) + GestureDetector( + onTap: () => { + if (!isLive) + { + HMSHLSPlayerController + .seekToLivePosition() + } + }, + child: isLive + ? Row( + children: [ + Padding( + padding: + const EdgeInsets.only( + right: 8.0), + child: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/red_dot.svg", + height: 8, + width: 8, + ), + ), + HMSTitleText( + text: "LIVE", + textColor: HMSThemeColors + .onSurfaceHighEmphasis) + ], + ) + : Row( + children: [ + Padding( + padding: + const EdgeInsets.only( + right: 8.0), + child: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/red_dot.svg", + height: 8, + width: 8, + colorFilter: ColorFilter.mode( + HMSThemeColors + .onSurfaceLowEmphasis, + BlendMode.srcIn), + ), + ), + HMSTitleText( + text: "GO LIVE", + textColor: HMSThemeColors + .onSurfaceMediumEmphasis), + Selector( + selector: + (_, hlsPlayerStore) => + hlsPlayerStore + .timeFromLive, + builder: (_, timeFromLive, + __) { + return Padding( + padding: + const EdgeInsets + .only( + left: 8.0), + child: HMSTitleText( + text: _setTimeFromLive( + timeFromLive), + textColor: + HMSThemeColors + .baseWhite, + fontWeight: + FontWeight.w400, + ), + ); + }) + ], + ), + ) ], - ), - ) - ], - ), + ); + }), ///This renders the minimize/maximize button ///to toggle the full screen mode @@ -98,6 +178,19 @@ class HLSViewerBottomNavigationBar extends StatelessWidget { ) : const SizedBox(); }), + + ///This renders the seekbar + Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.areStreamControlsVisible, + builder: (_, areStreamControlsVisible, __) { + return areStreamControlsVisible + ? Padding( + padding: const EdgeInsets.only(top: 8, bottom: 4), + child: HLSPlayerSeekbar(), + ) + : const SizedBox(); + }) ], ), ), 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 121f4dcd3..f72197030 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 @@ -2,12 +2,14 @@ library; ///Package imports import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; ///Project imports import 'package:hms_room_kit/src/common/utility_components.dart'; import 'package:hms_room_kit/src/hls_viewer/hls_player_store.dart'; import 'package:hms_room_kit/src/layout_api/hms_theme_colors.dart'; +import 'package:tuple/tuple.dart'; ///[HLSViewerHeader] is the header of the HLS Viewer screen class HLSViewerHeader extends StatelessWidget { @@ -53,50 +55,52 @@ class HLSViewerHeader extends StatelessWidget { ///This renders the [Caption Button] and [Settings Button] only if the controls are visible ///and the HLS has started - // if (hasHLSStarted) - // Row( - // mainAxisAlignment: MainAxisAlignment.end, - // children: [ - // ///The caption button is only rendered when closed captions are supported - // ///and the HLS has started - // Selector>( - // selector: (_, hlsPlayerStore) => Tuple2( - // hlsPlayerStore.isCaptionEnabled, - // hlsPlayerStore.areCaptionsSupported), - // builder: (_, captionsData, __) { - // return captionsData.item2 - // ? InkWell( - // onTap: () { - // context - // .read() - // .toggleCaptions(); - // }, - // child: SvgPicture.asset( - // "packages/hms_room_kit/lib/src/assets/icons/caption_${captionsData.item1 ? "on" : "off"}.svg", - // colorFilter: ColorFilter.mode( - // HMSThemeColors - // .onSurfaceHighEmphasis, - // BlendMode.srcIn), - // semanticsLabel: - // "caption_toggle_button", - // ), - // ) - // : const SizedBox(); - // }), - // const SizedBox( - // width: 16, - // ), + if (hasHLSStarted) + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ///The caption button is only rendered when closed captions are supported + ///and the HLS has started + Selector>( + selector: (_, hlsPlayerStore) => Tuple2( + hlsPlayerStore.isCaptionEnabled, + hlsPlayerStore.areCaptionsSupported), + builder: (_, captionsData, __) { + return captionsData.item2 + ? InkWell( + onTap: () { + context + .read() + .toggleCaptions(); + }, + child: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/caption_${captionsData.item1 ? "on" : "off"}.svg", + colorFilter: ColorFilter.mode( + HMSThemeColors + .onSurfaceHighEmphasis, + BlendMode.srcIn), + semanticsLabel: + "caption_toggle_button", + ), + ) + : const SizedBox(); + }), - // ///This renders the settings button - // SvgPicture.asset( - // "packages/hms_room_kit/lib/src/assets/icons/settings.svg", - // colorFilter: ColorFilter.mode( - // HMSThemeColors.onSurfaceHighEmphasis, - // BlendMode.srcIn), - // semanticsLabel: "caption_toggle_button", - // ) - // ], - // ) + ///This will be added later + // const SizedBox( + // width: 16, + // ), + + // ///This renders the settings button + // SvgPicture.asset( + // "packages/hms_room_kit/lib/src/assets/icons/settings.svg", + // colorFilter: ColorFilter.mode( + // HMSThemeColors.onSurfaceHighEmphasis, + // BlendMode.srcIn), + // semanticsLabel: "caption_toggle_button", + // ) + ], + ) ], ) : const SizedBox(); diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_mid_section.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_mid_section.dart new file mode 100644 index 000000000..3b02dace6 --- /dev/null +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_mid_section.dart @@ -0,0 +1,80 @@ +library; + +///Package imports +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:provider/provider.dart'; + +///Project imports +import 'package:hms_room_kit/src/hls_viewer/hls_player_store.dart'; +import 'package:hms_room_kit/src/layout_api/hms_theme_colors.dart'; + +///[HLSViewerMidSection] renders the mid section of the HLS Viewer containing the pause/play button +class HLSViewerMidSection extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.areStreamControlsVisible, + builder: (_, areStreamControlsVisible, __) { + return areStreamControlsVisible + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(right: 16.0), + child: InkWell( + onTap: () => + {HMSHLSPlayerController.seekBackward(seconds: 10)}, + child: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/seek_backward.svg", + colorFilter: ColorFilter.mode( + HMSThemeColors.baseWhite, BlendMode.srcIn), + ), + ), + ), + GestureDetector( + onTap: () => { + context.read().toggleStreamPlaying(), + }, + child: CircleAvatar( + radius: 32, + backgroundColor: + HMSThemeColors.backgroundDim.withOpacity(0.64), + child: Center( + child: Container( + child: Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.isStreamPlaying, + builder: (_, isStreamPlaying, __) { + return SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/${isStreamPlaying ? "pause" : "play"}.svg", + colorFilter: ColorFilter.mode( + HMSThemeColors.baseWhite, + BlendMode.srcIn), + ); + }), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 16.0), + child: InkWell( + onTap: () => + {HMSHLSPlayerController.seekForward(seconds: 10)}, + child: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/seek_forward.svg", + colorFilter: ColorFilter.mode( + HMSThemeColors.baseWhite, BlendMode.srcIn), + ), + ), + ), + ], + ) + : const SizedBox(); + }); + } +} diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_page.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_page.dart index 7b778d983..fd229d60f 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_page.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_page.dart @@ -70,6 +70,8 @@ class _HLSViewerPageState extends State { void deactivate() { HMSHLSPlayerController.removeHMSHLSPlaybackEventsListener( context.read()); + context.read().setHLSPlayerStats(false); + context.read().cancelTimer(); super.deactivate(); } @@ -104,6 +106,7 @@ class _HLSViewerPageState extends State { : SafeArea( child: Scaffold( backgroundColor: HMSThemeColors.backgroundDim, + resizeToAvoidBottomInset: false, body: Theme( data: ThemeData( brightness: Brightness.dark, @@ -125,33 +128,54 @@ class _HLSViewerPageState extends State { selector: (_, hlsPlayerStore) => hlsPlayerStore.isFullScreen, builder: (_, isFullScreen, __) { - double widgetHeight = height - - MediaQuery.of(context) - .viewPadding - .top - - MediaQuery.of(context) - .viewPadding - .bottom; - return Column( - mainAxisAlignment: - isFullScreen - ? MainAxisAlignment - .center - : MainAxisAlignment - .start, - children: [ - ///Renders HLS Player - SizedBox( - width: width, - height: isFullScreen - ? widgetHeight - : widgetHeight * 0.27, - child: const HLSPlayer(), - ), - if (!isFullScreen) - HLSPlayerDesktopControls() - ], - ); + return OrientationBuilder( + builder: + (context, orientation) { + return orientation == + Orientation.portrait + ? Column( + mainAxisAlignment: + isFullScreen + ? MainAxisAlignment + .center + : MainAxisAlignment + .start, + children: [ + ///Renders HLS Player + Expanded( + flex: 1, + child: + const HLSPlayer(), + ), + if (!isFullScreen) + Expanded( + flex: 3, + child: + HLSPlayerDesktopControls( + orientation: + orientation, + ), + ) + ], + ) + : Row( + children: [ + ///Renders HLS Player + Expanded( + flex: 2, + child: + const HLSPlayer(), + ), + if (!isFullScreen) + Expanded( + flex: 1, + child: HLSPlayerDesktopControls( + orientation: + orientation), + ) + ], + ); + }); }); }), 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 c205caf92..95a80691a 100644 --- a/packages/hms_room_kit/lib/src/meeting/meeting_store.dart +++ b/packages/hms_room_kit/lib/src/meeting/meeting_store.dart @@ -150,7 +150,7 @@ class MeetingStore extends ChangeNotifier MeetingMode meetingMode = MeetingMode.activeSpeakerWithInset; - bool isLandscapeLocked = false; + bool isScreenRotationAllowed = false; bool isMessageInfoShown = true; @@ -857,7 +857,9 @@ class MeetingStore extends ChangeNotifier addPeer(localPeer!); if (HMSRoomLayout .roleLayoutData?.screens?.conferencing?.hlsLiveStreaming != - null) isHLSLink = true; + null) { + isHLSLink = true; + } index = peerTracks .indexWhere((element) => element.uid == "${each.peerId}mainVideo"); if (each.videoTrack != null) { @@ -1343,7 +1345,7 @@ class MeetingStore extends ChangeNotifier _hmsSessionStore = null; peerTracks.clear(); isRoomEnded = true; - resetForegroundTaskAndOrientation(); + resetOrientation(); for (var element in viewControllers) { element.disposeTextureView(); @@ -1359,8 +1361,8 @@ class MeetingStore extends ChangeNotifier notifyListeners(); } - void resetForegroundTaskAndOrientation() { - setLandscapeLock(false); + void resetOrientation() { + allowScreenRotation(false); } // void clearPIPState() { @@ -1519,14 +1521,18 @@ class MeetingStore extends ChangeNotifier } } - void setLandscapeLock(bool value) { - if (value) { - SystemChrome.setPreferredOrientations( - [DeviceOrientation.landscapeRight, DeviceOrientation.landscapeLeft]); + void allowScreenRotation(bool allowRotation) { + if (allowRotation) { + SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeRight, + DeviceOrientation.landscapeLeft, + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown + ]); } else { SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); } - isLandscapeLocked = value; + isScreenRotationAllowed = allowRotation; notifyListeners(); } @@ -2894,4 +2900,9 @@ class MeetingStore extends ChangeNotifier break; } } + + @override + void onCues({required List subtitles}) { + // TODO: implement onCues + } } diff --git a/packages/hms_room_kit/lib/src/meeting_screen_controller.dart b/packages/hms_room_kit/lib/src/meeting_screen_controller.dart index 7a98943a5..5dc70ce02 100644 --- a/packages/hms_room_kit/lib/src/meeting_screen_controller.dart +++ b/packages/hms_room_kit/lib/src/meeting_screen_controller.dart @@ -126,6 +126,18 @@ class _MeetingScreenControllerState extends State { _meetingStore.setSettings(); } + void setScreenRotation() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (HMSRoomLayout + .roleLayoutData?.screens?.conferencing?.hlsLiveStreaming != + null) { + _meetingStore.allowScreenRotation(true); + } else { + _meetingStore.allowScreenRotation(false); + } + }); + } + @override Widget build(BuildContext context) { return showLoader @@ -136,6 +148,7 @@ class _MeetingScreenControllerState extends State { selector: (_, meetingStore) => meetingStore.localPeer?.role.name, builder: (_, data, __) { + setScreenRotation(); return (HMSRoomLayout.roleLayoutData?.screens?.conferencing ?.hlsLiveStreaming != null) 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 1bd588901..08531289b 100644 --- a/packages/hms_room_kit/lib/src/preview/preview_page.dart +++ b/packages/hms_room_kit/lib/src/preview/preview_page.dart @@ -62,6 +62,9 @@ class _PreviewPageState extends State { bool isRoomMute = previewStore.isRoomMute; HMSAudioDevice currentAudioDeviceMode = previewStore.currentAudioDeviceMode; + if (nameController.text.trim().isEmpty) { + return; + } previewStore.removePreviewListener(); Navigator.of(context).pushReplacement(MaterialPageRoute( @@ -271,9 +274,14 @@ class _PreviewPageState extends State { textController: nameController, width: width * 0.38, - onPressed: () => - _navigateToMeeting( - previewStore), + onPressed: () { + nameController.text + .trim() + .isEmpty + ? null + : _navigateToMeeting( + previewStore); + }, childWidget: PreviewJoinButton( isEmpty: nameController.text diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_bottom_sheet.dart index d7d66bf06..457bfab82 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_bottom_sheet.dart @@ -1,7 +1,7 @@ +library; + //Package imports import 'package:flutter/material.dart'; -import 'package:hms_room_kit/src/hls_viewer/hls_hand_raise_menu.dart'; -import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:tuple/tuple.dart'; @@ -11,13 +11,14 @@ import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:hms_room_kit/src/widgets/chat_widgets/hms_empty_chat_widget.dart'; import 'package:hms_room_kit/src/widgets/common_widgets/message_container.dart'; import 'package:hms_room_kit/src/meeting/meeting_store.dart'; -import 'package:hms_room_kit/src/widgets/chat_widgets/chat_text_field.dart'; import 'package:hms_room_kit/src/widgets/chat_widgets/pin_chat_widget.dart'; import 'package:hms_room_kit/hms_room_kit.dart'; import 'package:hms_room_kit/src/widgets/chat_widgets/recipient_selector_chip.dart'; import 'package:hms_room_kit/src/widgets/toasts/hms_error_toast.dart'; import 'package:hms_room_kit/src/widgets/toasts/hms_toast_model.dart'; import 'package:hms_room_kit/src/widgets/toasts/hms_toasts_type.dart'; +import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; +import 'package:hms_room_kit/src/widgets/chat_widgets/chat_text_utility.dart'; ///[ChatBottomSheet] is a bottom sheet that is used to render the bottom sheet for chat class ChatBottomSheet extends StatefulWidget { @@ -29,7 +30,6 @@ class ChatBottomSheet extends StatefulWidget { } class _ChatBottomSheetState extends State { - late double widthOfScreen; String currentlySelectedValue = "Choose a Recipient"; String? currentlySelectedpeerId; @@ -50,10 +50,15 @@ class _ChatBottomSheetState extends State { void _scrollToEnd() { if (_scrollController.hasClients) { - WidgetsBinding.instance.addPostFrameCallback((_) => _scrollController - .animateTo(_scrollController.position.maxScrollExtent, - duration: const Duration(milliseconds: 200), - curve: Curves.easeInOut)); + WidgetsBinding.instance.addPostFrameCallback((_) => { + if (_scrollController.positions.isNotEmpty) + { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut) + } + }); } } @@ -104,8 +109,6 @@ class _ChatBottomSheetState extends State { @override Widget build(BuildContext context) { - widthOfScreen = MediaQuery.of(context).size.width; - return WillPopScope( onWillPop: () async { context.read().setNewMessageFalse(); @@ -152,10 +155,13 @@ class _ChatBottomSheetState extends State { child: const HMSEmptyChatWidget()))) : Expanded( child: Column(children: [ - const PinChatWidget(), + Expanded( + flex: 1, + child: const PinChatWidget()), /// List containing chats Expanded( + flex: 3, child: SingleChildScrollView( reverse: true, child: Column( @@ -194,31 +200,8 @@ class _ChatBottomSheetState extends State { currentlySelectedValue: currentlySelectedValue, updateSelectedValue: _updateValueChoose), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - ///Text Field - if ((HMSRoomLayout.chatData?.isPrivateChatEnabled ?? - false) || - (HMSRoomLayout.chatData?.isPublicChatEnabled ?? - false) || - (HMSRoomLayout - .chatData?.rolesWhitelist.isNotEmpty ?? - false)) - Expanded( - child: Row( - children: [ - ChatTextField( - sendMessage: sendMessage, - isHLSChat: widget.isHLSChat, - ), - ], - ), - ), - if (widget.isHLSChat) HLSHandRaiseMenu() - ], - ) + ChatTextUtility( + sendMessage: sendMessage, isHLSChat: widget.isHLSChat) ], ), ), diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/end_service_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/end_service_bottom_sheet.dart index 54e89f46a..6e7819beb 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/end_service_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/end_service_bottom_sheet.dart @@ -55,7 +55,9 @@ class _EndServiceBottomSheetState extends State { @override Widget build(BuildContext context) { return FractionallySizedBox( - heightFactor: 0.25, + heightFactor: MediaQuery.of(context).orientation == Orientation.portrait + ? 0.25 + : 0.45, child: Padding( padding: const EdgeInsets.only(top: 16.0, left: 20, right: 20), child: SingleChildScrollView( diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/participants_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/participants_bottom_sheet.dart index fbafaa443..df5b53833 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/participants_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/participants_bottom_sheet.dart @@ -1,6 +1,6 @@ -///Dart imports library; +///Dart imports import 'dart:async'; import 'dart:convert'; import 'dart:developer'; @@ -272,8 +272,7 @@ class _ParticipantsBottomSheetState extends State { ), if (mutePermission && peerTrackNode != null && - !peerTrackNode.peer.isLocal && - peerTrackNode.peer.type == HMSPeerType.regular) + !peerTrackNode.peer.isLocal) PopupMenuItem( value: 4, child: Row(children: [ diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/remote_peer_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/remote_peer_bottom_sheet.dart index 2e1b6f244..30e5525cb 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/remote_peer_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/remote_peer_bottom_sheet.dart @@ -1,6 +1,6 @@ -///Package imports library; +///Package imports import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -159,8 +159,7 @@ class _RemotePeerBottomSheetState extends State { // textColor: HMSThemeColors.onSurfaceHighEmphasis)), if ((widget.meetingStore.localPeer?.role.permissions.mute ?? - false) && - widget.peerTrackNode.peer.type == HMSPeerType.regular) + false)) ListTile( horizontalTitleGap: 2, onTap: () async { diff --git a/packages/hms_room_kit/lib/src/widgets/chat_widgets/chat_text_field.dart b/packages/hms_room_kit/lib/src/widgets/chat_widgets/chat_text_field.dart index 30fbdc3c8..ea56e4814 100644 --- a/packages/hms_room_kit/lib/src/widgets/chat_widgets/chat_text_field.dart +++ b/packages/hms_room_kit/lib/src/widgets/chat_widgets/chat_text_field.dart @@ -1,6 +1,6 @@ -///Dart imports library; +///Dart imports import 'dart:math' as math; ///Package imports @@ -27,8 +27,8 @@ class ChatTextField extends StatefulWidget { const ChatTextField( {Key? key, required this.sendMessage, - this.toastBackgroundColor, - this.isHLSChat = false}) + this.isHLSChat = false, + this.toastBackgroundColor}) : super(key: key); @override @@ -52,10 +52,8 @@ class _ChatTextFieldState extends State { @override Widget build(BuildContext context) { - double width = MediaQuery.of(context).size.width - 32; return SizedBox( height: 40, - width: widget.isHLSChat ? width * 0.7 : width, child: Selector>>( ///item1: whether chat is resumed or not diff --git a/packages/hms_room_kit/lib/src/widgets/chat_widgets/chat_text_utility.dart b/packages/hms_room_kit/lib/src/widgets/chat_widgets/chat_text_utility.dart new file mode 100644 index 000000000..e1212e41f --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/chat_widgets/chat_text_utility.dart @@ -0,0 +1,102 @@ +library; + +///Dart imports +import 'dart:io'; + +///Package imports +import 'package:flutter/material.dart'; + +///Project imports +import 'package:hms_room_kit/src/hls_viewer/hls_hand_raise_menu.dart'; +import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; +import 'package:hms_room_kit/src/layout_api/hms_theme_colors.dart'; +import 'package:hms_room_kit/src/widgets/chat_widgets/chat_text_field.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_subheading_text.dart'; + +///[ChatTextUtility] is a utility widget that renders the chat text field and hand raise menu +class ChatTextUtility extends StatefulWidget { + final Function sendMessage; + final bool isHLSChat; + + const ChatTextUtility( + {super.key, required this.sendMessage, required this.isHLSChat}); + + @override + State createState() => _ChatTextUtilityState(); +} + +class _ChatTextUtilityState extends State + with WidgetsBindingObserver { + bool showMenu = true; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeMetrics() { + final bool isKeyboardOpen = + (MediaQuery.of(context).viewInsets.bottom).toInt() > 0; + if (Platform.isAndroid) { + showMenu = isKeyboardOpen; + } else { + showMenu = !isKeyboardOpen; + } + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (HMSRoomLayout.chatData == null) + Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Container( + decoration: BoxDecoration( + color: HMSThemeColors.surfaceDefault, + borderRadius: BorderRadius.circular(8), + ), + height: 40, + child: Padding( + padding: const EdgeInsets.only(top: 8.0, bottom: 8, left: 8), + child: HMSSubheadingText( + text: "Chat disabled.", + textColor: HMSThemeColors.onSurfaceLowEmphasis), + ), + ), + ), + ), + + ///Text Field + if ((HMSRoomLayout.chatData?.isPrivateChatEnabled ?? false) || + (HMSRoomLayout.chatData?.isPublicChatEnabled ?? false) || + (HMSRoomLayout.chatData?.rolesWhitelist.isNotEmpty ?? false)) + Expanded( + child: Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: ChatTextField(sendMessage: widget.sendMessage), + ), + ), + ], + ), + ), + if (widget.isHLSChat && showMenu) HLSHandRaiseMenu() + ], + ); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/chat_widgets/pin_chat_widget.dart b/packages/hms_room_kit/lib/src/widgets/chat_widgets/pin_chat_widget.dart index efff9f9a1..3f209a2b7 100644 --- a/packages/hms_room_kit/lib/src/widgets/chat_widgets/pin_chat_widget.dart +++ b/packages/hms_room_kit/lib/src/widgets/chat_widgets/pin_chat_widget.dart @@ -1,6 +1,6 @@ -///Package imports library; +///Package imports import 'package:dots_indicator/dots_indicator.dart'; import 'package:flutter/material.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; @@ -28,6 +28,7 @@ class _PinChatWidgetState extends State { int currentPage = 0; PageController? _pageController; bool isExpanded = false; + double height = 0, containerHeight = 0, dotsHeight = 0; @override void initState() { @@ -51,7 +52,22 @@ class _PinChatWidgetState extends State { }); } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + height = MediaQuery.of(context).size.height; + containerHeight = height * 0.09; + dotsHeight = containerHeight * 0.5; + } + void toggleExpand() { + if (isExpanded) { + containerHeight = height * 0.09; + dotsHeight = containerHeight * 0.5; + } else { + containerHeight = height * 0.12; + dotsHeight = containerHeight * 0.5; + } setState(() { isExpanded = !isExpanded; }); @@ -73,94 +89,99 @@ class _PinChatWidgetState extends State { child: Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - AnimatedContainer( - height: MediaQuery.of(context).size.height * - (isExpanded ? 0.13 : 0.09), - width: - (HMSRoomLayout.chatData?.allowPinningMessages ?? - false) - ? MediaQuery.of(context).size.width * 0.83 - : MediaQuery.of(context).size.width * 0.9, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: widget.backgroundColor ?? - HMSThemeColors.surfaceDefault), - duration: const Duration(milliseconds: 0), - child: Padding( - padding: const EdgeInsets.all(8.0), + Expanded( + child: AnimatedContainer( + height: containerHeight, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: widget.backgroundColor ?? + HMSThemeColors.surfaceDefault), + duration: const Duration(milliseconds: 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, children: [ if (data.item2 > 1) - DotsIndicator( - axis: Axis.vertical, - mainAxisSize: MainAxisSize.min, - dotsCount: data.item2, - position: currentPage >= data.item2 - ? 0 - : currentPage, - decorator: DotsDecorator( - spacing: const EdgeInsets.only( - bottom: 3.0, right: 8), - size: Size(2.0, isExpanded ? 24 : 9.0), - activeSize: - Size(2.0, isExpanded ? 24 : 9.0), - color: - HMSThemeColors.onSurfaceLowEmphasis, - activeColor: - HMSThemeColors.onSurfaceHighEmphasis, - activeShape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16.0)), - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16.0)), + Padding( + padding: const EdgeInsets.only( + left: 8.0, top: 2, bottom: 2), + child: SingleChildScrollView( + physics: NeverScrollableScrollPhysics(), + child: DotsIndicator( + axis: Axis.vertical, + mainAxisSize: MainAxisSize.max, + dotsCount: data.item2, + position: currentPage >= data.item2 + ? 0 + : currentPage, + decorator: DotsDecorator( + spacing: const EdgeInsets.only( + bottom: 3.0, right: 8), + size: Size( + 2.0, dotsHeight / data.item2), + activeSize: Size( + 2.0, dotsHeight / data.item2), + color: HMSThemeColors + .onSurfaceLowEmphasis, + activeColor: HMSThemeColors + .onSurfaceHighEmphasis, + activeShape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16.0)), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16.0)), + ), + onTap: (position) => + setCurrentPage(position), + ), ), - onTap: (position) => - setCurrentPage(position), ), Expanded( - child: PageView.builder( - scrollDirection: Axis.vertical, - controller: _pageController, - itemCount: data.item2, - physics: const PageScrollPhysics(), - onPageChanged: (value) => - setCurrentPage(value), - itemBuilder: (context, index) => - SelectableLinkify( - maxLines: 3, - scrollPhysics: isExpanded - ? const BouncingScrollPhysics() - : const NeverScrollableScrollPhysics(), - text: data.item1[index]["text"], - onOpen: (link) async { - Uri url = Uri.parse(link.url); - if (await canLaunchUrl(url)) { - await launchUrl(url, - mode: LaunchMode - .externalApplication); - } - }, - onTap: () => toggleExpand(), - options: - const LinkifyOptions(humanize: false), - style: HMSTextStyle.setTextStyle( - fontSize: 14.0, - color: HMSThemeColors - .onSurfaceHighEmphasis, - letterSpacing: 0.25, - height: 20 / 14, - fontWeight: FontWeight.w400, - ), - linkStyle: HMSTextStyle.setTextStyle( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: PageView.builder( + scrollDirection: Axis.vertical, + controller: _pageController, + itemCount: data.item2, + physics: const PageScrollPhysics(), + onPageChanged: (value) => + setCurrentPage(value), + itemBuilder: (context, index) => + SelectableLinkify( + maxLines: 3, + scrollPhysics: isExpanded + ? const BouncingScrollPhysics() + : const NeverScrollableScrollPhysics(), + text: data.item1[index]["text"], + onOpen: (link) async { + Uri url = Uri.parse(link.url); + if (await canLaunchUrl(url)) { + await launchUrl(url, + mode: LaunchMode + .externalApplication); + } + }, + onTap: () => toggleExpand(), + options: const LinkifyOptions( + humanize: false), + style: HMSTextStyle.setTextStyle( fontSize: 14.0, - color: HMSThemeColors.primaryDefault, + color: HMSThemeColors + .onSurfaceHighEmphasis, letterSpacing: 0.25, height: 20 / 14, - fontWeight: FontWeight.w400), + fontWeight: FontWeight.w400, + ), + linkStyle: HMSTextStyle.setTextStyle( + fontSize: 14.0, + color: + HMSThemeColors.primaryDefault, + letterSpacing: 0.25, + height: 20 / 14, + fontWeight: FontWeight.w400), + ), ), ), ), @@ -168,7 +189,6 @@ class _PinChatWidgetState extends State { ), ), ), - const SizedBox(width: 8), if (HMSRoomLayout.chatData?.allowPinningMessages ?? false) GestureDetector( diff --git a/packages/hms_room_kit/pubspec.lock b/packages/hms_room_kit/pubspec.lock index 00f594ef4..287ab5d9f 100644 --- a/packages/hms_room_kit/pubspec.lock +++ b/packages/hms_room_kit/pubspec.lock @@ -188,10 +188,10 @@ packages: dependency: "direct main" description: name: hmssdk_flutter - sha256: "88673b9ddd7eaa2e997976a4386374b0881b4485f4ba750689d3c9ca25032f65" + sha256: bfa6e6ec411d6f86f6cc054936fb2163c4cd3f8703f8848099689652b3794376 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" http: dependency: transitive description: diff --git a/packages/hms_room_kit/pubspec.yaml b/packages/hms_room_kit/pubspec.yaml index e54935c4f..c9ffea667 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.1.0 +version: 1.1.1 homepage: https://www.100ms.live/ repository: https://github.com/100mslive/100ms-flutter issue_tracker: https://github.com/100mslive/100ms-flutter/issues @@ -14,7 +14,7 @@ dependencies: flutter: sdk: flutter - hmssdk_flutter: 1.10.0 + hmssdk_flutter: 1.10.1 intl: ^0.18.0 permission_handler: ^11.0.0 provider: ^6.0.5 diff --git a/packages/hmssdk_flutter/CHANGELOG.md b/packages/hmssdk_flutter/CHANGELOG.md index af4cb0bed..eefddafd3 100644 --- a/packages/hmssdk_flutter/CHANGELOG.md +++ b/packages/hmssdk_flutter/CHANGELOG.md @@ -5,7 +5,25 @@ | 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.10.0 - 2024-04-19 +# 1.10.1 - 2024-04-26 + +| Package | Version | +| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| hms_room_kit | 1.1.1 | +| hmssdk_flutter | 1.10.1 | + +### ✨ Added + +- Support for captions in HLS Player + + HMSSDK now provides support for captions in HLS Player. You can now `enable` or `disable` captions in the HLS Player using the + `HMSHLSPlayerController` methods. Moreover HMSSDK provides a new `onCues` callback to get captions. Learn more about it [here](https://www.100ms.live/docs/flutter/v2/how-to-guides/record-and-live-stream/hls-player#how-to-enabledisable-captions) + +Uses Android SDK 2.9.54 & iOS SDK 1.9.0 + +**Full Changelog**: [1.10.0...1.10.1](https://github.com/100mslive/100ms-flutter/compare/1.10.0...1.10.1) + +# 1.10.0 - 2024-04-22 | Package | Version | | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSHLSVariantExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSHLSVariantExtension.kt index 7b8ff793d..62e1ce40e 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSHLSVariantExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSHLSVariantExtension.kt @@ -1,5 +1,6 @@ package live.hms.hmssdk_flutter +import live.hms.video.sdk.models.HMSHLSPlaylistType import live.hms.video.sdk.models.HMSHLSVariant class HMSHLSVariantExtension { @@ -13,7 +14,23 @@ class HMSHLSVariantExtension { hmshlsVariant.startedAt?.let { args["started_at"] = it } + args["playlist_type"] = getHMSHLSVariantToString(hmshlsVariant.playlistType) return args } + + private fun getHMSHLSVariantToString(hmsHlsVariantType: HMSHLSPlaylistType?): String{ + + return when(hmsHlsVariantType){ + HMSHLSPlaylistType.dvr -> { + "dvr" + } + HMSHLSPlaylistType.noDVR -> { + "noDvr" + } + else -> { + "noDvr" + } + } + } } } 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 6c7ff934b..d1dc5b060 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 @@ -252,7 +252,7 @@ class HmssdkFlutterPlugin : "remove_key_change_listener" -> { removeKeyChangeListener(call, result) } - "start_hls_player", "stop_hls_player", "pause_hls_player", "resume_hls_player", "seek_to_live_position", "seek_forward", "seek_backward", "set_hls_player_volume", "add_hls_stats_listener", "remove_hls_stats_listener", "are_closed_captions_supported", "enable_closed_captions", "disable_closed_captions" -> { + "start_hls_player", "stop_hls_player", "pause_hls_player", "resume_hls_player", "seek_to_live_position", "seek_forward", "seek_backward", "set_hls_player_volume", "add_hls_stats_listener", "remove_hls_stats_listener", "are_closed_captions_supported", "enable_closed_captions", "disable_closed_captions", "get_stream_properties" -> { HMSHLSPlayerAction.hlsPlayerAction(call, result) } "toggle_always_screen_on" -> { diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlayerAction.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlayerAction.kt index 5cac07bb0..55fb06fce 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlayerAction.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlayerAction.kt @@ -31,6 +31,7 @@ class HMSHLSPlayerAction { "are_closed_captions_supported" -> areClosedCaptionsSupported(result) "enable_closed_captions" -> enableClosedCaptions(result) "disable_closed_captions" -> disableClosedCaptions(result) + "get_stream_properties" -> getStreamProperties(result) else -> { result.notImplemented() } @@ -54,7 +55,7 @@ class HMSHLSPlayerAction { val hlsUrl = call.argument("hls_url") hlsActions?.let { it.get()?.start(hlsUrl, result) - }?:run{ + } ?: run { HMSErrorLogger.logError("start", "hlsActions is NULL", "NULL Error") } } @@ -67,7 +68,7 @@ class HMSHLSPlayerAction { private fun stop(result: Result) { hlsActions?.let { it.get()?.stop(result) - }?:run{ + } ?: run { HMSErrorLogger.logError("stop", "hlsActions is NULL", "NULL Error") } } @@ -80,7 +81,7 @@ class HMSHLSPlayerAction { private fun pause(result: Result) { hlsActions?.let { it.get()?.pause(result) - }?:run{ + } ?: run { HMSErrorLogger.logError("pause", "hlsActions is NULL", "NULL Error") } } @@ -93,7 +94,7 @@ class HMSHLSPlayerAction { private fun resume(result: Result) { hlsActions?.let { it.get()?.resume(result) - }?:run{ + } ?: run { HMSErrorLogger.logError("resume", "hlsActions is NULL", "NULL Error") } } @@ -106,7 +107,7 @@ class HMSHLSPlayerAction { private fun seekToLivePosition(result: Result) { hlsActions?.let { it.get()?.seekToLivePosition(result) - }?:run{ + } ?: run { HMSErrorLogger.logError("seekToLivePosition", "hlsActions is NULL", "NULL Error") } } @@ -128,9 +129,9 @@ class HMSHLSPlayerAction { } seconds?.let { - hlsActions?.let {_hlsActions -> + hlsActions?.let { _hlsActions -> _hlsActions.get()?.seekForward(it, result) - }?:run{ + } ?: run { HMSErrorLogger.logError("seekForward", "hlsActions is NULL", "NULL Error") } } @@ -155,7 +156,7 @@ class HMSHLSPlayerAction { seconds?.let { hlsActions?.let { _hlsActions -> _hlsActions.get()?.seekBackward(it, result) - }?:run{ + } ?: run { HMSErrorLogger.logError("seekBackward", "hlsActions is NULL", "NULL Error") } } @@ -178,9 +179,9 @@ class HMSHLSPlayerAction { } volume?.let { - hlsActions?.let {_hlsActions -> - _hlsActions.get()?.setVolume(it,result) - }?:run{ + hlsActions?.let { _hlsActions -> + _hlsActions.get()?.setVolume(it, result) + } ?: run { HMSErrorLogger.logError("setVolume", "hlsActions is NULL", "NULL Error") } } @@ -192,10 +193,9 @@ class HMSHLSPlayerAction { * @param result The result object to be returned after adding the HLS stats listener. */ private fun addHLSStatsListener(result: Result) { - hlsActions?.let { it.get()?.addHLSStatsListener(result) - }?:run{ + } ?: run { HMSErrorLogger.logError("addHLSStatsListener", "hlsActions is NULL", "NULL Error") } } @@ -208,7 +208,7 @@ class HMSHLSPlayerAction { private fun removeHLSStatsListener(result: Result) { hlsActions?.let { it.get()?.removeHLSStatsListener(result) - }?:run{ + } ?: run { HMSErrorLogger.logError("removeHLSStatsListener", "hlsActions is NULL", "NULL Error") } } @@ -222,7 +222,7 @@ class HMSHLSPlayerAction { private fun areClosedCaptionsSupported(result: Result) { hlsActions?.let { it.get()?.areClosedCaptionsSupported(result) - }?:run{ + } ?: run { HMSErrorLogger.logError("areClosedCaptionsSupported", "hlsActions is NULL", "NULL Error") } } @@ -235,7 +235,7 @@ class HMSHLSPlayerAction { private fun enableClosedCaptions(result: Result) { hlsActions?.let { it.get()?.enableClosedCaptions(result) - }?:run{ + } ?: run { HMSErrorLogger.logError("enableClosedCaptions", "hlsActions is NULL", "NULL Error") } } @@ -248,9 +248,17 @@ class HMSHLSPlayerAction { private fun disableClosedCaptions(result: Result) { hlsActions?.let { it.get()?.disableClosedCaptions(result) - }?:run{ + } ?: run { HMSErrorLogger.logError("disableClosedCaptions", "hlsActions is NULL", "NULL Error") } } + + private fun getStreamProperties(result:Result){ + hlsActions?.let { + it.get()?.getStreamProperties(result) + } ?: run { + HMSErrorLogger.logError("getStreamProperties", "hlsActions is NULL", "NULL Error") + } + } } } diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/IHLSPlayerActionInterface.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/IHLSPlayerActionInterface.kt index 4a3b20ef1..86bf25099 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/IHLSPlayerActionInterface.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/IHLSPlayerActionInterface.kt @@ -45,4 +45,6 @@ interface IHLSPlayerActionInterface { fun enableClosedCaptions(result: Result) fun disableClosedCaptions(result: Result) + + fun getStreamProperties(result: Result) } diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayer.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayer.kt index 727956d47..9cdb44355 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayer.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayer.kt @@ -2,6 +2,7 @@ package live.hms.hmssdk_flutter.views import android.content.Context import android.view.LayoutInflater +import android.view.View import android.widget.FrameLayout import androidx.media3.common.Player import androidx.media3.common.VideoSize @@ -40,7 +41,7 @@ class HMSHLSPlayer( var hlsPlayer: HmsHlsPlayer? = null private var hlsPlayerView: PlayerView? = null private var actions: IHLSPlayerActionInterface? = null - + private var areCaptionsEnabled = false /** * Inflate the HLS player view and initialize the HLS player. * Set the HLS player view and player if the inflation is successful. @@ -75,6 +76,10 @@ class HMSHLSPlayer( // Set the native player of the HLS player view. it.player = hlsPlayer?.getNativePlayer() + ///Hiding Subtitles + if(!showHLSControls){ + it.subtitleView?.visibility = View.GONE + } it.player?.addListener( object : Player.Listener { override fun onSurfaceSizeChanged( @@ -115,6 +120,23 @@ class HMSHLSPlayer( } override fun onCues(cueGroup: CueGroup) { + if(areCaptionsEnabled){ + val hashMap = HashMap() + val args = HashMap() + + hashMap["event_name"] = "on_cues" + val cues = ArrayList() + cueGroup.cues.forEach {_cues -> + _cues.text?.let { cue -> + cues.add(cue.toString()) + } + } + args["subtitles"] = cues + hashMap["data"] = args + CoroutineScope(Dispatchers.Main).launch { + hmssdkFlutterPlugin?.hlsPlayerSink?.success(hashMap) + } + } super.onCues(cueGroup) } }, @@ -242,6 +264,10 @@ class HMSHLSPlayer( } } + private fun areClosedCaptionSupported(): Boolean{ + return hlsPlayer?.areClosedCaptionsSupported()?:false + } + /** * This handles the method call from flutter channel * and return the values @@ -325,16 +351,33 @@ class HMSHLSPlayer( } override fun areClosedCaptionsSupported(result: Result) { - val areCaptionsSupported = hlsPlayer?.areClosedCaptionsSupported() + val areCaptionsSupported = areClosedCaptionSupported() result.success(areCaptionsSupported) } override fun enableClosedCaptions(result: Result) { - TODO("Not yet implemented") + if(areClosedCaptionSupported()){ + areCaptionsEnabled = true + result.success(null) + }else{ + HMSErrorLogger.logError("enableClosedCaptions", "Closed Captions are not supported", "SUPPORT ERROR") + result.success(null) + } } override fun disableClosedCaptions(result: Result) { - TODO("Not yet implemented") + areCaptionsEnabled = false + result.success(null) + } + + override fun getStreamProperties(result: Result) { + val map = HashMap() + + map["rolling_window_time"] = + hlsPlayer?.getNativePlayer()?.seekParameters?.toleranceAfterUs?.div(1000000); + map["stream_duration"] = hlsPlayer?.getNativePlayer()?.duration?.div(1000) + + result.success(map) } } } diff --git a/packages/hmssdk_flutter/example/ExampleAppChangelog.txt b/packages/hmssdk_flutter/example/ExampleAppChangelog.txt index 593ea824a..db9ffb8f0 100644 --- a/packages/hmssdk_flutter/example/ExampleAppChangelog.txt +++ b/packages/hmssdk_flutter/example/ExampleAppChangelog.txt @@ -1,34 +1,18 @@ Board: https://100ms.atlassian.net/jira/software/projects/FLUT/boards/34/ -- Added active noise cancellation feature -https://100ms.atlassian.net/browse/FLUT-259 +- Added Captions in HLS Player +https://100ms.atlassian.net/browse/FLUT-265 -- Added SIP Peer support -https://100ms.atlassian.net/browse/FLUT-260 +- Pinch & Zoom on HLS Player +https://100ms.atlassian.net/browse/FLUT-268 -- Fixed: UI inconsistent when using a Screenshare only Role -https://100ms.atlassian.net/browse/FLUT-279 +- Full Screen Landscape UI +https://100ms.atlassian.net/browse/FLUT-269 -- Removed `flutter_foreground_task` from `hms_room_kit` -https://100ms.atlassian.net/browse/FLUT-280 +- Seek Bar + Go Live Button behaviour (Polling) +https://100ms.atlassian.net/browse/FLUT-272 -- Main Player UI with Chat below -https://100ms.atlassian.net/browse/FLUT-262 - -- Description Pane -https://100ms.atlassian.net/browse/FLUT-263 - -- Remove webrtc header and footer + Bottom toolbar from HLS screen -https://100ms.atlassian.net/browse/FLUT-264 - -- New Player Controls (Auto hide/overlay) -https://100ms.atlassian.net/browse/FLUT-266 - -- Header + Description from Layout API in Description Pane -https://100ms.atlassian.net/browse/FLUT-271 - - -Room Kit: 1.1.0 -Core SDK: 1.10.0 -Android SDK: 2.9.53 -iOS SDK: 1.8.0 \ No newline at end of file +Room Kit: 1.1.1 +Core SDK: 1.10.1 +Android SDK: 2.9.54 +iOS SDK: 1.9.0 \ No newline at end of file diff --git a/packages/hmssdk_flutter/example/android/Gemfile.lock b/packages/hmssdk_flutter/example/android/Gemfile.lock index b491244fd..4e4043c3b 100644 --- a/packages/hmssdk_flutter/example/android/Gemfile.lock +++ b/packages/hmssdk_flutter/example/android/Gemfile.lock @@ -15,17 +15,17 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.913.0) - aws-sdk-core (3.191.6) + aws-partitions (1.916.0) + aws-sdk-core (3.192.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.78.0) + aws-sdk-kms (1.79.0) aws-sdk-core (~> 3, >= 3.191.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.146.1) - aws-sdk-core (~> 3, >= 3.191.0) + aws-sdk-s3 (1.147.0) + aws-sdk-core (~> 3, >= 3.192.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) @@ -177,7 +177,7 @@ GEM nanaimo (0.3.0) naturally (2.2.1) nkf (0.2.0) - optparse (0.4.0) + optparse (0.5.0) os (1.1.4) plist (3.7.1) public_suffix (5.0.5) diff --git a/packages/hmssdk_flutter/example/android/app/build.gradle b/packages/hmssdk_flutter/example/android/app/build.gradle index 9e4879be3..863fc41b4 100644 --- a/packages/hmssdk_flutter/example/android/app/build.gradle +++ b/packages/hmssdk_flutter/example/android/app/build.gradle @@ -36,8 +36,8 @@ android { applicationId "live.hms.flutter" minSdkVersion 21 targetSdkVersion 34 - versionCode 474 - versionName "1.5.174" + versionCode 483 + versionName "1.5.183" } signingConfigs { diff --git a/packages/hmssdk_flutter/example/ios/Gemfile.lock b/packages/hmssdk_flutter/example/ios/Gemfile.lock index 59ff0e0e1..85288e6db 100644 --- a/packages/hmssdk_flutter/example/ios/Gemfile.lock +++ b/packages/hmssdk_flutter/example/ios/Gemfile.lock @@ -15,17 +15,17 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.913.0) - aws-sdk-core (3.191.6) + aws-partitions (1.916.0) + aws-sdk-core (3.192.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.78.0) + aws-sdk-kms (1.79.0) aws-sdk-core (~> 3, >= 3.191.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.146.1) - aws-sdk-core (~> 3, >= 3.191.0) + aws-sdk-s3 (1.147.0) + aws-sdk-core (~> 3, >= 3.192.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) @@ -178,7 +178,7 @@ GEM nanaimo (0.3.0) naturally (2.2.1) nkf (0.2.0) - optparse (0.4.0) + optparse (0.5.0) os (1.1.4) plist (3.7.1) public_suffix (5.0.5) diff --git a/packages/hmssdk_flutter/example/ios/Podfile.lock b/packages/hmssdk_flutter/example/ios/Podfile.lock index 13f5497b9..64e0f64ba 100644 --- a/packages/hmssdk_flutter/example/ios/Podfile.lock +++ b/packages/hmssdk_flutter/example/ios/Podfile.lock @@ -129,16 +129,16 @@ PODS: - HMSHLSPlayerSDK (0.0.2): - HMSAnalyticsSDK (= 0.0.2) - HMSNoiseCancellationModels (1.0.0) - - HMSSDK (1.8.0): + - HMSSDK (1.9.0): - HMSAnalyticsSDK (= 0.0.2) - - HMSWebRTC (= 1.0.5118) + - HMSWebRTC (= 1.0.6168) - hmssdk_flutter (1.10.0): - Flutter - HMSBroadcastExtensionSDK (= 0.0.9) - HMSHLSPlayerSDK (= 0.0.2) - HMSNoiseCancellationModels (= 1.0.0) - - HMSSDK (= 1.8.0) - - HMSWebRTC (1.0.5118) + - HMSSDK (= 1.9.0) + - HMSWebRTC (1.0.6168) - MLImage (1.0.0-beta4) - MLKitBarcodeScanning (3.0.0): - MLKitCommon (~> 9.0) @@ -299,9 +299,9 @@ SPEC CHECKSUMS: HMSBroadcastExtensionSDK: d80fe325f6c928bd8e5176290b5a4b7ae15d6fbb HMSHLSPlayerSDK: 6a54ad4d12f3dc2270d1ecd24019d71282a4f6a3 HMSNoiseCancellationModels: a3bda1405a16015632f4bcabd46ce48f35103b02 - HMSSDK: c893d1381a47ed02760ef6d06625b9aa5251f998 - hmssdk_flutter: 997715f0bedfcb22750fb95549672bf3fea380ff - HMSWebRTC: 4487c7200f1e9358412c1d8cd974edd2766467dc + HMSSDK: 96bdafc1c610aabfecd1155ad7e3c1bc45b3a6cb + hmssdk_flutter: 3febf31ba806e9a5ee540fe299c7331fc43135cf + HMSWebRTC: a302f0d6c94f7bee94f3265adb7bb1c6569e7ee5 MLImage: 7bb7c4264164ade9bf64f679b40fb29c8f33ee9b MLKitBarcodeScanning: 04e264482c5f3810cb89ebc134ef6b61e67db505 MLKitCommon: c1b791c3e667091918d91bda4bba69a91011e390 @@ -319,4 +319,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 9fb9f6e431a2c6c79942252e94b241ac7972ac90 -COCOAPODS: 1.15.2 +COCOAPODS: 1.14.3 diff --git a/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj index c6527dce3..457887001 100644 --- a/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -793,9 +793,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = FlutterBroadcastUploadExtension/FlutterBroadcastUploadExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 185; - DEVELOPMENT_TEAM = 5N85PP82A9; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 5N85PP82A9; ENABLE_BITCODE = NO; EXCLUDED_ARCHS = x86_64; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -813,7 +815,8 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = live.100ms.flutter.FlutterBroadcastUploadExtension; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = FlutterAppStoreBroadcastUploadExtension; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = FlutterAppStoreBroadcastUploadExtension; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -834,9 +837,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = FlutterBroadcastUploadExtension/FlutterBroadcastUploadExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 185; - DEVELOPMENT_TEAM = 5N85PP82A9; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 5N85PP82A9; ENABLE_BITCODE = NO; EXCLUDED_ARCHS = x86_64; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -854,7 +859,8 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = live.100ms.flutter.FlutterBroadcastUploadExtension; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = FlutterAppStoreBroadcastUploadExtension; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = FlutterAppStoreBroadcastUploadExtension; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; diff --git a/packages/hmssdk_flutter/example/ios/Runner/Info.plist b/packages/hmssdk_flutter/example/ios/Runner/Info.plist index 4f8b85db3..e5a655037 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.174 + 1.5.183 CFBundleSignature ???? CFBundleURLTypes @@ -48,7 +48,7 @@ CFBundleVersion - 474 + 483 ITSAppUsesNonExemptEncryption LSApplicationCategoryType diff --git a/packages/hmssdk_flutter/example/pubspec.lock b/packages/hmssdk_flutter/example/pubspec.lock index c0c11e670..71150e5cd 100644 --- a/packages/hmssdk_flutter/example/pubspec.lock +++ b/packages/hmssdk_flutter/example/pubspec.lock @@ -302,15 +302,15 @@ packages: path: "../../hms_room_kit" relative: true source: path - version: "1.1.0" + version: "1.1.1" hmssdk_flutter: dependency: transitive description: name: hmssdk_flutter - sha256: "88673b9ddd7eaa2e997976a4386374b0881b4485f4ba750689d3c9ca25032f65" + sha256: bfa6e6ec411d6f86f6cc054936fb2163c4cd3f8703f8848099689652b3794376 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" http: dependency: transitive description: diff --git a/packages/hmssdk_flutter/example/pubspec.yaml b/packages/hmssdk_flutter/example/pubspec.yaml index 836a27fde..7be7095df 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.10.0 +version: 1.10.1 environment: sdk: ">=2.16.0 <4.0.0" diff --git a/packages/hmssdk_flutter/ios/Classes/HLSPlayer/HMSHLSPlayerAction.swift b/packages/hmssdk_flutter/ios/Classes/HLSPlayer/HMSHLSPlayerAction.swift index a8726877a..6bb5fac4e 100644 --- a/packages/hmssdk_flutter/ios/Classes/HLSPlayer/HMSHLSPlayerAction.swift +++ b/packages/hmssdk_flutter/ios/Classes/HLSPlayer/HMSHLSPlayerAction.swift @@ -57,7 +57,10 @@ class HMSHLSPlayerAction { case "disable_closed_captions": disableClosedCaptions(result) - + + case "get_stream_properties": + getStreamProperties(result) + default: result(FlutterMethodNotImplemented) } @@ -217,4 +220,8 @@ class HMSHLSPlayerAction { NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "disable_closed_captions"]) result(nil) } + + static private func getStreamProperties(_ result: @escaping FlutterResult){ + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "get_stream_properties", "result": result]) + } } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/HMSHLSVariantExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/HMSHLSVariantExtension.swift index 965b9bd34..e9699652d 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/HMSHLSVariantExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/HMSHLSVariantExtension.swift @@ -27,8 +27,23 @@ class HMSHLSVariantExtension { if let startedAt = hmshlsVariant.startedAt { dict["started_at"] = Int(startedAt.timeIntervalSince1970 * 1000) } + + if let playlistType = hmshlsVariant.playlistType { + dict["playlist_type"] = getStringFromHMSHLSPlaylistType(playlistType: playlistType) + } return dict } + + private static func getStringFromHMSHLSPlaylistType(playlistType: HMSHLSPlaylistType) -> String{ + switch playlistType{ + case .dvr: + return "dvr" + case .noDVR: + return "noDvr" + default: + return "noDvr" + } + } } diff --git a/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift b/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift index 5912dde91..67b265427 100644 --- a/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift +++ b/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift @@ -300,7 +300,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene // MARK: - HLS Player - case "start_hls_player", "stop_hls_player", "pause_hls_player", "resume_hls_player", "seek_to_live_position", "seek_forward", "seek_backward", "set_hls_player_volume", "add_hls_stats_listener", "remove_hls_stats_listener", "are_closed_captions_supported", "enable_closed_captions", "disable_closed_captions": + case "start_hls_player", "stop_hls_player", "pause_hls_player", "resume_hls_player", "seek_to_live_position", "seek_forward", "seek_backward", "set_hls_player_volume", "add_hls_stats_listener", "remove_hls_stats_listener", "are_closed_captions_supported", "enable_closed_captions", "disable_closed_captions", "get_stream_properties": HMSHLSPlayerAction.hlsPlayerAction(call, result) case "toggle_always_screen_on": diff --git a/packages/hmssdk_flutter/ios/Classes/Views/HMSHLSPlayerView.swift b/packages/hmssdk_flutter/ios/Classes/Views/HMSHLSPlayerView.swift index a54b0600d..fc86ffa8d 100644 --- a/packages/hmssdk_flutter/ios/Classes/Views/HMSHLSPlayerView.swift +++ b/packages/hmssdk_flutter/ios/Classes/Views/HMSHLSPlayerView.swift @@ -126,6 +126,27 @@ class HMSHLSPlayerView: NSObject, FlutterPlatformView { // Set the hlsPlayer to nil to release its resources hlsPlayer = nil } + + private func areClosedCaptionsSupported() -> Bool{ + guard let playerItem = hlsPlayer?._nativePlayer.currentItem else { + return false + } + + guard let availableSubtitleTracks = playerItem.asset.mediaSelectionGroup(forMediaCharacteristic: .legible) else { + return false + } + + guard let firstOption = availableSubtitleTracks.options.first else { + return false + } + + if firstOption.mediaType == .subtitle { + return true + } + else { + return false + } + } /** * Below methods handles the HLS Player controller calls @@ -163,37 +184,53 @@ class HMSHLSPlayerView: NSObject, FlutterPlatformView { case "are_closed_captions_supported": let result = notification.userInfo?["result"] as? FlutterResult - - var isSubtitleToggleShown = false - guard let playerItem = hlsPlayer?._nativePlayer.currentItem else { - isSubtitleToggleShown = false - return + result?(areClosedCaptionsSupported()) + + case "enable_closed_captions": + let result = notification.userInfo?["result"] as? FlutterResult + if(areClosedCaptionsSupported()){ + let player = hlsPlayer?._nativePlayer + guard let playerItem = player?.currentItem else {return } + + guard let availableSubtitleTracks = playerItem.asset.mediaSelectionGroup(forMediaCharacteristic: .legible) else { return } + + if let firstSubtitle = availableSubtitleTracks.options.first(where: {$0.mediaType == .subtitle}) { + + playerItem.select(firstSubtitle, in: availableSubtitleTracks) + + } + }else{ + HMSErrorLogger.logError("\(#function)", "Closed Captions are not supported", "SUPPORT ERROR") } + result?(nil) + case "disable_closed_captions": + let result = notification.userInfo?["result"] as? FlutterResult + let player = hlsPlayer?._nativePlayer + guard let playerItem = player?.currentItem else {return } - guard let availableSubtitleTracks = playerItem.asset.mediaSelectionGroup(forMediaCharacteristic: .legible) else { - isSubtitleToggleShown = false - return - } + guard let availableSubtitleTracks = playerItem.asset.mediaSelectionGroup(forMediaCharacteristic: .legible) else { return } - guard let firstOption = availableSubtitleTracks.options.first else { - isSubtitleToggleShown = false - return + if let _ = playerItem.currentMediaSelection.selectedMediaOption(in: availableSubtitleTracks) { + playerItem.select(nil, in: availableSubtitleTracks) } + result?(nil) + case "get_stream_properties": + let result = notification.userInfo?["result"] as? FlutterResult + let player = hlsPlayer?._nativePlayer + guard let playerItem = player?.currentItem else {return } - if firstOption.mediaType == .subtitle { - isSubtitleToggleShown = true - } - else { - isSubtitleToggleShown = false - } + var map = [String:Any?]() - result?(isSubtitleToggleShown) - - case "enable_closed_captions": - break + let duration = playerItem.duration - case "disable_closed_captions": - break + if duration.isIndefinite { + guard let timeRange = playerItem.seekableTimeRanges.last as? CMTimeRange else { return } + + map["rolling_window_time"] = timeRange.duration.seconds + }else{ + map["stream_duration"] = duration.seconds + } + result?(map) default: return diff --git a/packages/hmssdk_flutter/lib/assets/sdk-versions.json b/packages/hmssdk_flutter/lib/assets/sdk-versions.json index 6737887cb..17f99841f 100644 --- a/packages/hmssdk_flutter/lib/assets/sdk-versions.json +++ b/packages/hmssdk_flutter/lib/assets/sdk-versions.json @@ -1,6 +1,6 @@ { - "flutter": "1.10.0", - "ios": "1.8.0", + "flutter": "1.10.1", + "ios": "1.9.0", "iOSBroadcastExtension": "0.0.9", "iOSHLSPlayerSDK": "0.0.2", "iOSNoiseCancellationModels": "1.0.0", diff --git a/packages/hmssdk_flutter/lib/hmssdk_flutter.dart b/packages/hmssdk_flutter/lib/hmssdk_flutter.dart index 58bf560c6..0107e3c6a 100644 --- a/packages/hmssdk_flutter/lib/hmssdk_flutter.dart +++ b/packages/hmssdk_flutter/lib/hmssdk_flutter.dart @@ -24,6 +24,7 @@ export 'src/enum/hms_audio_mode.dart'; export 'src/enum/hms_hls_playback_state.dart'; export 'src/enum/hms_poll_enum.dart'; export 'src/enum/hms_peer_type.dart'; +export 'src/enum/hms_hls_playlist_type.dart'; //EXCEPTIONS export 'src/exceptions/hms_exception.dart'; @@ -114,6 +115,7 @@ export 'src/model/polls/hms_poll_leaderboard_response.dart'; export 'src/model/polls/hms_poll_leaderboard_summary.dart'; export 'src/model/polls/hms_poll_peer_info_response.dart'; export 'src/model/hms_noise_cancellation_controller.dart'; +export 'src/model/hls_stream_properties.dart'; //Views export 'src/ui/meeting/hms_texture_view.dart'; diff --git a/packages/hmssdk_flutter/lib/src/common/platform_methods.dart b/packages/hmssdk_flutter/lib/src/common/platform_methods.dart index b1280cb63..3b32138d3 100644 --- a/packages/hmssdk_flutter/lib/src/common/platform_methods.dart +++ b/packages/hmssdk_flutter/lib/src/common/platform_methods.dart @@ -187,6 +187,7 @@ enum PlatformMethod { areClosedCaptionsSupported, enableClosedCaptions, disableClosedCaptions, + getStreamProperties, switchAudioOutputUsingiOSUI, sendHLSTimedMetadata, @@ -506,6 +507,8 @@ extension PlatformMethodValues on PlatformMethod { return "enable_closed_captions"; case PlatformMethod.disableClosedCaptions: return "disable_closed_captions"; + case PlatformMethod.getStreamProperties: + return "get_stream_properties"; case PlatformMethod.switchAudioOutputUsingiOSUI: return "switch_audio_output_using_ios_ui"; @@ -851,6 +854,8 @@ extension PlatformMethodValues on PlatformMethod { return PlatformMethod.enableClosedCaptions; case "disable_closed_captions": return PlatformMethod.disableClosedCaptions; + case "get_stream_properties": + return PlatformMethod.getStreamProperties; case "switch_audio_output_using_ios_ui": return PlatformMethod.switchAudioOutputUsingiOSUI; diff --git a/packages/hmssdk_flutter/lib/src/enum/hms_hls_playback_event_method.dart b/packages/hmssdk_flutter/lib/src/enum/hms_hls_playback_event_method.dart index 72428d86d..68f2c4994 100644 --- a/packages/hmssdk_flutter/lib/src/enum/hms_hls_playback_event_method.dart +++ b/packages/hmssdk_flutter/lib/src/enum/hms_hls_playback_event_method.dart @@ -6,6 +6,7 @@ enum HMSHLSPlaybackEventMethod { onHLSError, onHLSEventUpdate, onVideoSizeChanged, + onCues, unknown } @@ -24,6 +25,8 @@ extension HMSHLSPlaybackEventMethodValues on HMSHLSPlaybackEventMethod { return HMSHLSPlaybackEventMethod.onHLSEventUpdate; case "on_video_size_changed": return HMSHLSPlaybackEventMethod.onVideoSizeChanged; + case "on_cues": + return HMSHLSPlaybackEventMethod.onCues; default: return HMSHLSPlaybackEventMethod.unknown; } diff --git a/packages/hmssdk_flutter/lib/src/enum/hms_hls_playlist_type.dart b/packages/hmssdk_flutter/lib/src/enum/hms_hls_playlist_type.dart new file mode 100644 index 000000000..d4fa404da --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/enum/hms_hls_playlist_type.dart @@ -0,0 +1,15 @@ +///[HMSHLSPlaylistType] is an enum which defines the type of playlist to be used in the HLS stream. +enum HMSHLSPlaylistType { dvr, noDvr } + +extension HMSHLSPlaylistTypeValues on HMSHLSPlaylistType { + static HMSHLSPlaylistType getHMSHLSPlaylistTypeFromString(String? name) { + switch (name) { + case "dvr": + return HMSHLSPlaylistType.dvr; + case "noDvr": + return HMSHLSPlaylistType.noDvr; + default: + return HMSHLSPlaylistType.noDvr; + } + } +} diff --git a/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_playback_event_listener.dart b/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_playback_event_listener.dart index 856a2b696..4033635b3 100644 --- a/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_playback_event_listener.dart +++ b/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_playback_event_listener.dart @@ -38,4 +38,9 @@ abstract class HMSHLSPlaybackEventsListener { /// /// - Parameter: size: A [Size] object containing the new height and width of the stream void onVideoSizeChanged({required Size size}) {} + + /// Callback to get the subtitles + /// + /// - Parameter: subtitles: A list of [String] containing the subtitles + void onCues({required List subtitles}) {} } diff --git a/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_player_controller.dart b/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_player_controller.dart index 46a92cc09..e57192357 100644 --- a/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_player_controller.dart +++ b/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_player_controller.dart @@ -105,6 +105,7 @@ class HMSHLSPlayerController { /// /// If closed captions are supported, you can enable/disable them using [enableClosedCaptions] and [disableClosedCaptions] respectively. /// If this returns null then we set it to false. + /// Refer [areClosedCaptionsSupported](https://www.100ms.live/docs/flutter/v2/how-to-guides/record-and-live-stream/hls-player#how-to-enabledisable-captions) static Future areClosedCaptionsSupported() async { bool? result = await PlatformService.invokeMethod( PlatformMethod.areClosedCaptionsSupported); @@ -112,11 +113,13 @@ class HMSHLSPlayerController { } ///[enableClosedCaptions] enables closed captions in the current HLS playback. + /// Refer [enableClosedCaptions](https://www.100ms.live/docs/flutter/v2/how-to-guides/record-and-live-stream/hls-player#how-to-enabledisable-captions) static Future enableClosedCaptions() async { await PlatformService.invokeMethod(PlatformMethod.enableClosedCaptions); } ///[disableClosedCaptions] disables closed captions in the current HLS playback. + /// Refer [disableClosedCaptions](https://www.100ms.live/docs/flutter/v2/how-to-guides/record-and-live-stream/hls-player#how-to-enabledisable-captions) static Future disableClosedCaptions() async { await PlatformService.invokeMethod(PlatformMethod.disableClosedCaptions); } @@ -132,4 +135,12 @@ class HMSHLSPlayerController { static Future removeHLSStatsListener() async { await PlatformService.invokeMethod(PlatformMethod.removeHLSStatsListener); } + + ///[getStreamProperties] gets the properties of the current HLS stream. + /// Refer [getStreamProperties](https://www.100ms.live/docs/flutter/v2/how-to-guides/record-and-live-stream/hls-player#how-to-get-stream-properties) + static Future getStreamProperties() async { + var result = + await PlatformService.invokeMethod(PlatformMethod.getStreamProperties); + return HLSStreamProperties.fromMap(result); + } } diff --git a/packages/hmssdk_flutter/lib/src/model/hls_stream_properties.dart b/packages/hmssdk_flutter/lib/src/model/hls_stream_properties.dart new file mode 100644 index 000000000..935de689e --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/model/hls_stream_properties.dart @@ -0,0 +1,20 @@ +///[HLSStreamProperties] class defines the properties of the HLS stream. +class HLSStreamProperties { + ///[rollingWindowTime] is the time interval for which user can seek the stream + double? rollingWindowTime; + + ///[streamDuration] is the total duration of the stream generally null in case of live stream + double? streamDuration; + + HLSStreamProperties({this.rollingWindowTime, this.streamDuration}); + + factory HLSStreamProperties.fromMap(Map map) { + return HLSStreamProperties( + rollingWindowTime: map['rolling_window_time'] != null + ? map["rolling_window_time"].toDouble() + : null, + streamDuration: map['stream_duration'] != null + ? map["stream_duration"].toDouble() + : null); + } +} diff --git a/packages/hmssdk_flutter/lib/src/model/hms_hls_variant.dart b/packages/hmssdk_flutter/lib/src/model/hms_hls_variant.dart index 70cd49f67..d2c1805f1 100644 --- a/packages/hmssdk_flutter/lib/src/model/hms_hls_variant.dart +++ b/packages/hmssdk_flutter/lib/src/model/hms_hls_variant.dart @@ -1,6 +1,8 @@ //Dart imports import 'dart:core'; +///Project imports +import 'package:hmssdk_flutter/src/enum/hms_hls_playlist_type.dart'; import 'package:hmssdk_flutter/src/model/hms_date_extension.dart'; ///100ms HMSHLSVarient @@ -11,12 +13,14 @@ class HMSHLSVariant { final String? meetingUrl; final String? metadata; final DateTime? startedAt; + final HMSHLSPlaylistType playlistType; HMSHLSVariant( {required this.hlsStreamUrl, required this.meetingUrl, required this.metadata, - required this.startedAt}); + required this.startedAt, + required this.playlistType}); Map toMap() { return { @@ -24,6 +28,7 @@ class HMSHLSVariant { 'meetingUrl': this.meetingUrl, 'metadata': this.metadata, 'startedAt': this.startedAt, + 'playlist_type': this.playlistType, }; } @@ -34,12 +39,13 @@ class HMSHLSVariant { factory HMSHLSVariant.fromMap(Map map) { return HMSHLSVariant( - hlsStreamUrl: map['hls_stream_url'] as String?, - meetingUrl: map['meeting_url'] as String?, - metadata: map['metadata'] as String?, - startedAt: map['started_at'] != null - ? HMSDateExtension.convertDateFromEpoch(map['started_at']) - : null, - ); + hlsStreamUrl: map['hls_stream_url'] as String?, + meetingUrl: map['meeting_url'] as String?, + metadata: map['metadata'] as String?, + startedAt: map['started_at'] != null + ? HMSDateExtension.convertDateFromEpoch(map['started_at']) + : null, + playlistType: HMSHLSPlaylistTypeValues.getHMSHLSPlaylistTypeFromString( + map["playlist_type"])); } } diff --git a/packages/hmssdk_flutter/lib/src/service/platform_service.dart b/packages/hmssdk_flutter/lib/src/service/platform_service.dart index 3e02fd860..959ba0557 100644 --- a/packages/hmssdk_flutter/lib/src/service/platform_service.dart +++ b/packages/hmssdk_flutter/lib/src/service/platform_service.dart @@ -11,6 +11,7 @@ import 'dart:async'; // Flutter imports: import 'package:flutter/services.dart'; + // Project imports: import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:hmssdk_flutter/src/enum/hms_hls_playback_event_method.dart'; @@ -779,6 +780,12 @@ abstract class PlatformService { arguments["height"].toDouble()))); } break; + case HMSHLSPlaybackEventMethod.onCues: + hlsPlaybackEventListener.forEach((e) => e.onCues( + subtitles: arguments["subtitles"] != null + ? List.from(arguments["subtitles"]) + : [])); + break; case HMSHLSPlaybackEventMethod.unknown: break; } diff --git a/packages/hmssdk_flutter/pubspec.yaml b/packages/hmssdk_flutter/pubspec.yaml index 23ea9d5ee..c6ebe82a8 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.10.0 +version: 1.10.1 homepage: https://www.100ms.live/ repository: https://github.com/100mslive/100ms-flutter issue_tracker: https://github.com/100mslive/100ms-flutter/issues