diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 93a14d304a..fa4e5f3a09 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -144,8 +144,6 @@ PODS: - Flutter - media_kit_libs_ios_video (1.0.4): - Flutter - - media_kit_native_event_loop (1.0.0): - - Flutter - media_kit_video (0.0.1): - Flutter - motion_sensors (0.0.1): @@ -189,8 +187,6 @@ PODS: - PromisesObjC (2.4.0) - receive_sharing_intent (1.8.0): - Flutter - - screen_brightness_ios (0.1.0): - - Flutter - SDWebImage (5.20.0): - SDWebImage/Core (= 5.20.0) - SDWebImage/Core (5.20.0) @@ -278,7 +274,6 @@ DEPENDENCIES: - maps_launcher (from `.symlinks/plugins/maps_launcher/ios`) - media_extension (from `.symlinks/plugins/media_extension/ios`) - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) - - media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`) - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) - motion_sensors (from `.symlinks/plugins/motion_sensors/ios`) - motionphoto (from `.symlinks/plugins/motionphoto/ios`) @@ -293,7 +288,6 @@ DEPENDENCIES: - photo_manager (from `.symlinks/plugins/photo_manager/ios`) - privacy_screen (from `.symlinks/plugins/privacy_screen/ios`) - receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`) - - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`) - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -390,8 +384,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/media_extension/ios" media_kit_libs_ios_video: :path: ".symlinks/plugins/media_kit_libs_ios_video/ios" - media_kit_native_event_loop: - :path: ".symlinks/plugins/media_kit_native_event_loop/ios" media_kit_video: :path: ".symlinks/plugins/media_kit_video/ios" motion_sensors: @@ -420,8 +412,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/privacy_screen/ios" receive_sharing_intent: :path: ".symlinks/plugins/receive_sharing_intent/ios" - screen_brightness_ios: - :path: ".symlinks/plugins/screen_brightness_ios/ios" sentry_flutter: :path: ".symlinks/plugins/sentry_flutter/ios" share_plus: @@ -472,7 +462,7 @@ SPEC CHECKSUMS: flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100 flutter_native_splash: 35ddbc7228eafcb3969dcc5f1fbbe27c1145a4f0 - flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418 + flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 flutter_sodium: 152647449ba89a157fd48d7e293dcd6d29c6ab0e fluttertoast: 76fea30fcf04176325f6864c87306927bd7d2038 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 @@ -489,7 +479,6 @@ SPEC CHECKSUMS: maps_launcher: edf829809ba9e894d70e569bab11c16352dedb45 media_extension: a1fec16ee9c8241a6aef9613578ebf097d6c5e64 media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854 - media_kit_native_event_loop: 5fba1a849a6c87a34985f1e178a0de5bd444a0cf media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474 motion_sensors: 741e702c17467b9569a92165dda8d4d88c6167f1 motionphoto: 584b43031ead3060225cdff08fa49818879801d2 @@ -509,7 +498,6 @@ SPEC CHECKSUMS: privacy_screen: 3159a541f5d3a31bea916cfd4e58f9dc722b3fd4 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 receive_sharing_intent: f6a12b7e8f7ed745f61c982de8a65de88db44a44 - screen_brightness_ios: 5ed898fa50fa82a26171c086ca5e28228f932576 SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57 @@ -526,7 +514,7 @@ SPEC CHECKSUMS: url_launcher_ios: 694010445543906933d732453a59da0a173ae33d video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140 - volume_controller: ca1cde542ee70fad77d388f82e9616488110942b + volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12 wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49 PODFILE CHECKSUM: 20e086e6008977d43a3d40260f3f9bffcac748dd diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index de19bf899f..74a0b8ae7a 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -315,7 +315,6 @@ "${BUILT_PRODUCTS_DIR}/maps_launcher/maps_launcher.framework", "${BUILT_PRODUCTS_DIR}/media_extension/media_extension.framework", "${BUILT_PRODUCTS_DIR}/media_kit_libs_ios_video/media_kit_libs_ios_video.framework", - "${BUILT_PRODUCTS_DIR}/media_kit_native_event_loop/media_kit_native_event_loop.framework", "${BUILT_PRODUCTS_DIR}/media_kit_video/media_kit_video.framework", "${BUILT_PRODUCTS_DIR}/motion_sensors/motion_sensors.framework", "${BUILT_PRODUCTS_DIR}/motionphoto/motionphoto.framework", @@ -329,7 +328,6 @@ "${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework", "${BUILT_PRODUCTS_DIR}/privacy_screen/privacy_screen.framework", "${BUILT_PRODUCTS_DIR}/receive_sharing_intent/receive_sharing_intent.framework", - "${BUILT_PRODUCTS_DIR}/screen_brightness_ios/screen_brightness_ios.framework", "${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework", "${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework", "${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework", @@ -412,7 +410,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/maps_launcher.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_extension.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_kit_libs_ios_video.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_kit_native_event_loop.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_kit_video.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/motion_sensors.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/motionphoto.framework", @@ -426,7 +423,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/privacy_screen.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/receive_sharing_intent.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/screen_brightness_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework", diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index b907203cf9..b2e00dfbdf 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -52,7 +52,6 @@ import "package:photos/utils/email_util.dart"; import 'package:photos/utils/file_uploader.dart'; import "package:photos/utils/lock_screen_settings.dart"; import 'package:shared_preferences/shared_preferences.dart'; -import "package:video_player_media_kit/video_player_media_kit.dart"; final _logger = Logger("main"); @@ -238,10 +237,6 @@ Future _init(bool isBackground, {String via = ''}) async { ServiceLocator.instance .init(preferences, NetworkClient.instance.enteDio, packageInfo); - if (!isBackground) { - VideoPlayerMediaKit.ensureInitialized(iOS: true, android: true); - } - _logger.info("UserService init $tlog"); await UserService.instance.init(); _logger.info("UserService init done $tlog"); diff --git a/mobile/lib/services/preview_video_store.dart b/mobile/lib/services/preview_video_store.dart index 7a72a93a9d..556eb5193e 100644 --- a/mobile/lib/services/preview_video_store.dart +++ b/mobile/lib/services/preview_video_store.dart @@ -58,10 +58,9 @@ class PreviewVideoStore { void init(SharedPreferences prefs) { _prefs = prefs; - Future.delayed( - const Duration(seconds: 10), - _putFilesForPreviewCreation, - ); + FileDataService.instance.syncFDStatus().then( + (_) => _putFilesForPreviewCreation(), + ); } late final SharedPreferences _prefs; @@ -74,11 +73,13 @@ class PreviewVideoStore { Future setIsVideoStreamingEnabled(bool value) async { final oneMonthBack = DateTime.now().subtract(const Duration(days: 30)); - await _prefs.setBool(_videoStreamingEnabled, value); - await _prefs.setInt( - _videoStreamingCutoff, - oneMonthBack.millisecondsSinceEpoch, - ); + _prefs.setBool(_videoStreamingEnabled, value).ignore(); + _prefs + .setInt( + _videoStreamingCutoff, + oneMonthBack.millisecondsSinceEpoch, + ) + .ignore(); Bus.instance.fire(VideoStreamingChanged()); if (isVideoStreamingEnabled) { @@ -664,23 +665,24 @@ class PreviewVideoStore { }).toList(); // set all video status to in queue - for (final enteFile in allFiles) { + final n = allFiles.length; + for (int i = 0; i < n; i++) { + final enteFile = allFiles[i]; // elimination case for <=10 MB with H.264 final (_, result, _) = await _checkFileForPreviewCreation(enteFile); if (result) { - allFiles.remove(enteFile); - continue; + allFiles.removeAt(i); + } else { + _items[enteFile.uploadedFileID!] = PreviewItem( + status: PreviewItemStatus.inQueue, + file: enteFile, + collectionID: enteFile.collectionID ?? 0, + ); } - - _items[enteFile.uploadedFileID!] = PreviewItem( - status: PreviewItemStatus.inQueue, - file: enteFile, - collectionID: enteFile.collectionID ?? 0, - ); } Bus.instance.fire(PreviewUpdatedEvent(_items)); - _logger.info("[init] Processing ${_items.length} items for streaming"); + _logger.info("[init] Processing ${allFiles.length} items for streaming"); // take first file and put it for stream generation final file = allFiles.removeAt(0); diff --git a/mobile/lib/ui/viewer/file/preview_video_widget.dart b/mobile/lib/ui/viewer/file/preview_video_widget.dart deleted file mode 100644 index be2c7034a8..0000000000 --- a/mobile/lib/ui/viewer/file/preview_video_widget.dart +++ /dev/null @@ -1,266 +0,0 @@ -import 'dart:async'; -import "dart:io"; - -import 'package:chewie/chewie.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import "package:fluttertoast/fluttertoast.dart"; -import "package:logging/logging.dart"; -import 'package:photos/core/constants.dart'; -import "package:photos/core/event_bus.dart"; -import "package:photos/events/guest_view_event.dart"; -import 'package:photos/models/file/file.dart'; -import "package:photos/service_locator.dart"; -import "package:photos/services/filedata/filedata_service.dart"; -import "package:photos/services/preview_video_store.dart"; -import "package:photos/ui/actions/file/file_actions.dart"; -import 'package:photos/ui/viewer/file/thumbnail_widget.dart'; -import "package:photos/ui/viewer/file/video_control.dart"; -import "package:photos/utils/data_util.dart"; -// import 'package:photos/ui/viewer/file/video_controls.dart'; -import "package:photos/utils/dialog_util.dart"; -import 'package:photos/utils/file_util.dart'; -import 'package:photos/utils/toast_util.dart'; -import "package:photos/utils/wakelock_util.dart"; -import 'package:video_player/video_player.dart'; -import 'package:visibility_detector/visibility_detector.dart'; - -class PreviewVideoWidget extends StatefulWidget { - final EnteFile file; - final bool? autoPlay; - final String? tagPrefix; - final Function(bool)? playbackCallback; - final void Function()? onStreamChange; - - const PreviewVideoWidget( - this.file, { - this.autoPlay = true, - this.tagPrefix, - this.playbackCallback, - this.onStreamChange, - super.key, - }); - - @override - State createState() => _PreviewVideoWidgetState(); -} - -class _PreviewVideoWidgetState extends State { - final _logger = Logger("PreviewVideoWidget"); - VideoPlayerController? _videoPlayerController; - ChewieController? _chewieController; - final _progressNotifier = ValueNotifier(null); - bool _isPlaying = false; - final EnteWakeLock _wakeLock = EnteWakeLock(); - bool _isFileSwipeLocked = false; - late final StreamSubscription _fileSwipeLockEventSubscription; - File? previewFile; - - @override - void initState() { - super.initState(); - - _checkForPreview(); - _fileSwipeLockEventSubscription = - Bus.instance.on().listen((event) { - setState(() { - _isFileSwipeLocked = event.swipeLocked; - }); - }); - } - - @override - void dispose() { - _fileSwipeLockEventSubscription.cancel(); - removeCallBack(widget.file); - _videoPlayerController?.dispose(); - _chewieController?.dispose(); - _progressNotifier.dispose(); - _wakeLock.dispose(); - super.dispose(); - } - - Future _checkForPreview() async { - final data = await PreviewVideoStore.instance - .getPlaylist(widget.file) - .onError((error, stackTrace) { - if (!mounted) return; - _logger.warning("Failed to download preview video", error, stackTrace); - Fluttertoast.showToast(msg: "Failed to download preview!"); - return null; - }); - if (!mounted) return; - if (data != null) { - if (flagService.internalUser) { - final d = - FileDataService.instance.previewIds?[widget.file.uploadedFileID!]; - if (d != null && widget.file.fileSize != null) { - // show toast with human readable size - final size = formatBytes(widget.file.fileSize!); - showToast( - context, - "Preview OG Size ($size), previewSize: ${formatBytes(d.objectSize)}", - ); - } else { - showShortToast(context, "Playing preview"); - } - } - previewFile = data.preview; - _setVideoPlayerController(); - } - } - - void _setVideoPlayerController() { - if (!mounted) { - // Note: Do not initiale video player if widget is not mounted. - // On Android, if multiple instance of ExoPlayer is created, it will start - // resulting in playback errors for videos. See https://github.com/google/ExoPlayer/issues/6168 - return; - } - VideoPlayerController videoPlayerController; - videoPlayerController = VideoPlayerController.file(previewFile!); - - debugPrint("videoPlayerController: $videoPlayerController"); - _videoPlayerController = videoPlayerController - ..initialize().whenComplete(() { - if (mounted) { - setState(() {}); - } - }).onError( - (error, stackTrace) { - if (mounted && flagService.internalUser) { - if (error is Exception) { - showErrorDialogForException( - context: context, - exception: error, - message: "Failed to play video\n ${error.toString()}", - ); - } else { - showToast(context, "Failed to play video"); - } - } - }, - ); - } - - @override - Widget build(BuildContext context) { - final content = _videoPlayerController != null && - _videoPlayerController!.value.isInitialized - ? _getVideoPlayer() - : _getLoadingWidget(); - final contentWithDetector = GestureDetector( - onVerticalDragUpdate: _isFileSwipeLocked - ? null - : (d) => { - if (d.delta.dy > dragSensitivity) - { - Navigator.of(context).pop(), - } - else if (d.delta.dy < (dragSensitivity * -1)) - { - showDetailsSheet(context, widget.file), - }, - }, - child: content, - ); - return VisibilityDetector( - key: Key(widget.file.tag), - onVisibilityChanged: (info) { - if (info.visibleFraction < 1) { - if (mounted && _chewieController != null) { - _chewieController!.pause(); - } - } - }, - child: Hero( - tag: widget.tagPrefix! + widget.file.tag, - child: contentWithDetector, - ), - ); - } - - Widget _getLoadingWidget() { - return Stack( - children: [ - _getThumbnail(), - Container( - color: Colors.black12, - constraints: const BoxConstraints.expand(), - ), - Center( - child: SizedBox.fromSize( - size: const Size.square(20), - child: ValueListenableBuilder( - valueListenable: _progressNotifier, - builder: (BuildContext context, double? progress, _) { - return progress == null || progress == 1 - ? const CupertinoActivityIndicator( - color: Colors.white, - ) - : CircularProgressIndicator( - backgroundColor: Colors.black, - value: progress, - valueColor: const AlwaysStoppedAnimation( - Color.fromRGBO(45, 194, 98, 1.0), - ), - ); - }, - ), - ), - ), - ], - ); - } - - Widget _getThumbnail() { - return Container( - color: Colors.black, - constraints: const BoxConstraints.expand(), - child: ThumbnailWidget( - widget.file, - fit: BoxFit.contain, - ), - ); - } - - Future _keepScreenAliveOnPlaying(bool isPlaying) async { - if (isPlaying) { - _wakeLock.enable(); - } - if (!isPlaying) { - _wakeLock.disable(); - } - } - - Widget _getVideoPlayer() { - _videoPlayerController!.addListener(() { - if (_isPlaying != _videoPlayerController!.value.isPlaying) { - _isPlaying = _videoPlayerController!.value.isPlaying; - if (widget.playbackCallback != null) { - widget.playbackCallback!(_isPlaying); - } - unawaited(_keepScreenAliveOnPlaying(_isPlaying)); - } - }); - _chewieController = ChewieController( - progressIndicatorDelay: const Duration(milliseconds: 200), - videoPlayerController: _videoPlayerController!, - aspectRatio: _videoPlayerController!.value.aspectRatio, - autoPlay: widget.autoPlay!, - autoInitialize: true, - looping: true, - allowMuting: true, - allowFullScreen: false, - customControls: VideoControls( - file: widget.file, - onStreamChange: widget.onStreamChange, - playbackCallback: widget.playbackCallback, - ), - ); - return Container( - color: Colors.black, - child: Chewie(controller: _chewieController!), - ); - } -} diff --git a/mobile/lib/ui/viewer/file/video_control.dart b/mobile/lib/ui/viewer/file/video_control.dart deleted file mode 100644 index 4048a28836..0000000000 --- a/mobile/lib/ui/viewer/file/video_control.dart +++ /dev/null @@ -1,454 +0,0 @@ -// ignore_for_file: implementation_imports - -import 'dart:async'; - -import "package:chewie/chewie.dart"; -import "package:chewie/src/helpers/utils.dart"; -import "package:chewie/src/notifiers/index.dart"; -import 'package:flutter/material.dart'; -import "package:photos/models/file/file.dart"; -import "package:photos/theme/colors.dart"; -import "package:photos/theme/ente_theme.dart"; -import "package:photos/ui/common/loading_widget.dart"; -import "package:photos/ui/viewer/file/preview_status_widget.dart"; -import "package:photos/ui/viewer/file/video_control/custom_progress_bar.dart"; -import 'package:provider/provider.dart'; -import 'package:video_player/video_player.dart'; - -class VideoControls extends StatefulWidget { - const VideoControls({ - super.key, - required this.file, - required this.onStreamChange, - required this.playbackCallback, - }); - final EnteFile file; - final void Function()? onStreamChange; - final void Function(bool)? playbackCallback; - - @override - State createState() { - return _VideoControlsState(); - } -} - -class _VideoControlsState extends State - with SingleTickerProviderStateMixin { - late PlayerNotifier notifier; - late VideoPlayerValue _latestValue; - Timer? _hideTimer; - Timer? _initTimer; - Timer? _showAfterExpandCollapseTimer; - bool _dragging = false; - bool _displayTapped = false; - Timer? _bufferingDisplayTimer; - bool _displayBufferingIndicator = false; - - final barHeight = 48.0 * 1.5; - final marginSize = 5.0; - - late VideoPlayerController controller; - ChewieController? _chewieController; - - // We know that _chewieController is set in didChangeDependencies - ChewieController get chewieController => _chewieController!; - - @override - void initState() { - super.initState(); - notifier = Provider.of(context, listen: false); - } - - @override - Widget build(BuildContext context) { - if (_latestValue.hasError) { - return chewieController.errorBuilder?.call( - context, - chewieController.videoPlayerController.value.errorDescription!, - ) ?? - Center( - child: Icon( - Icons.error, - color: Theme.of(context).colorScheme.onSurface, - size: 42, - ), - ); - } - - return MouseRegion( - onHover: (_) { - _cancelAndRestartTimer(); - }, - child: GestureDetector( - onTap: () => _cancelAndRestartTimer(), - child: AbsorbPointer( - absorbing: notifier.hideStuff, - child: Stack( - children: [ - if (_displayBufferingIndicator) - _chewieController?.bufferingBuilder?.call(context) ?? - const Center( - child: EnteLoadingWidget( - size: 32, - color: fillBaseDark, - padding: 0, - ), - ) - else - _buildHitArea(), - SafeArea( - top: false, - left: false, - right: false, - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - PreviewStatusWidget( - showControls: !notifier.hideStuff, - file: widget.file, - isPreviewPlayer: true, - onStreamChange: widget.onStreamChange, - ), - if (!chewieController.isLive) _buildBottomBar(context), - ], - ), - ), - ], - ), - ), - ), - ); - } - - @override - void dispose() { - _dispose(); - super.dispose(); - } - - void _dispose() { - controller.removeListener(_updateState); - _hideTimer?.cancel(); - _initTimer?.cancel(); - _showAfterExpandCollapseTimer?.cancel(); - } - - @override - void didChangeDependencies() { - final oldController = _chewieController; - _chewieController = ChewieController.of(context); - controller = chewieController.videoPlayerController; - - if (oldController != chewieController) { - _dispose(); - _initialize(); - } - - super.didChangeDependencies(); - } - - AnimatedOpacity _buildBottomBar( - BuildContext context, - ) { - return AnimatedOpacity( - opacity: notifier.hideStuff ? 0.0 : 1.0, - duration: const Duration(milliseconds: 300), - child: Container( - height: 40, - margin: const EdgeInsets.only(bottom: 60), - child: Container( - padding: const EdgeInsets.fromLTRB( - 16, - 4, - 16, - 4, - ), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - borderRadius: const BorderRadius.all( - Radius.circular(8), - ), - border: Border.all( - color: strokeFaintDark, - width: 1, - ), - ), - child: Row( - children: [ - Text( - formatDuration(_latestValue.position), - style: getEnteTextTheme( - context, - ).mini.copyWith( - color: textBaseDark, - ), - ), - const SizedBox(width: 16), - Expanded( - child: _buildProgressBar(), - ), - const SizedBox(width: 16), - Text( - formatDuration( - _latestValue.duration, - ), - style: getEnteTextTheme( - context, - ).mini.copyWith( - color: textBaseDark, - ), - ), - ], - ), - ), - ), - ); - } - - Widget _buildHitArea() { - final bool isFinished = (_latestValue.position >= _latestValue.duration) && - _latestValue.duration.inSeconds > 0; - final bool showPlayButton = true && !_dragging && !notifier.hideStuff; - - return GestureDetector( - onTap: () { - if (_latestValue.isPlaying) { - if (_displayTapped) { - setState(() { - notifier.hideStuff = true; - }); - } else { - _cancelAndRestartTimer(); - } - } else { - _playPause(); - - setState(() { - notifier.hideStuff = true; - }); - } - widget.playbackCallback?.call(notifier.hideStuff); - }, - child: Container( - alignment: Alignment.center, - color: Colors - .transparent, // The Gesture Detector doesn't expand to the full size of the container without this; Not sure why! - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - margin: EdgeInsets.symmetric( - horizontal: marginSize, - ), - child: CenterPlayButton( - backgroundColor: Colors.black54, - iconColor: Colors.white, - isFinished: isFinished, - isPlaying: controller.value.isPlaying, - show: showPlayButton, - onPressed: _playPause, - ), - ), - ], - ), - ), - ); - } - - void _cancelAndRestartTimer() { - _hideTimer?.cancel(); - _startHideTimer(); - - setState(() { - notifier.hideStuff = false; - _displayTapped = true; - }); - widget.playbackCallback?.call(notifier.hideStuff); - } - - Future _initialize() async { - controller.addListener(_updateState); - - _updateState(); - - if (controller.value.isPlaying || chewieController.autoPlay) { - _startHideTimer(); - } - - if (chewieController.showControlsOnInitialize) { - _initTimer = Timer(const Duration(milliseconds: 200), () { - setState(() { - notifier.hideStuff = false; - }); - widget.playbackCallback?.call(notifier.hideStuff); - }); - } - } - - void _playPause() { - final bool isFinished = (_latestValue.position >= _latestValue.duration) && - _latestValue.duration.inSeconds > 0; - - setState(() { - if (controller.value.isPlaying) { - notifier.hideStuff = false; - widget.playbackCallback?.call(notifier.hideStuff); - _hideTimer?.cancel(); - controller.pause(); - } else { - _cancelAndRestartTimer(); - - if (!controller.value.isInitialized) { - controller.initialize().then((_) { - controller.play(); - }); - } else { - if (isFinished) { - controller.seekTo(Duration.zero); - } - controller.play(); - } - } - }); - } - - void _startHideTimer() { - final hideControlsTimer = chewieController.hideControlsTimer.isNegative - ? ChewieController.defaultHideControlsTimer - : chewieController.hideControlsTimer; - _hideTimer = Timer(hideControlsTimer, () { - setState(() { - notifier.hideStuff = true; - widget.playbackCallback?.call(notifier.hideStuff); - }); - }); - } - - void _bufferingTimerTimeout() { - _displayBufferingIndicator = true; - if (mounted) { - setState(() {}); - } - } - - void _updateState() { - if (!mounted) return; - - // display the progress bar indicator only after the buffering delay if it has been set - if (chewieController.progressIndicatorDelay != null) { - if (controller.value.isBuffering) { - _bufferingDisplayTimer ??= Timer( - chewieController.progressIndicatorDelay!, - _bufferingTimerTimeout, - ); - } else { - _bufferingDisplayTimer?.cancel(); - _bufferingDisplayTimer = null; - _displayBufferingIndicator = false; - } - } else { - _displayBufferingIndicator = controller.value.isBuffering; - } - - setState(() { - _latestValue = controller.value; - }); - } - - Widget _buildProgressBar() { - final colorScheme = getEnteColorScheme(context); - return Expanded( - child: CustomProgressBar( - controller, - onDragStart: () { - setState(() { - _dragging = true; - }); - - _hideTimer?.cancel(); - }, - onDragUpdate: () { - _hideTimer?.cancel(); - }, - onDragEnd: () { - setState(() { - _dragging = false; - }); - - _startHideTimer(); - }, - colors: ChewieProgressColors( - playedColor: colorScheme.primary300, - handleColor: backgroundElevatedLight, - bufferedColor: backgroundElevatedLight.withOpacity(0.5), - backgroundColor: fillMutedDark, - ), - draggableProgressBar: chewieController.draggableProgressBar, - ), - ); - } -} - -class CenterPlayButton extends StatelessWidget { - const CenterPlayButton({ - super.key, - required this.backgroundColor, - this.iconColor, - required this.show, - required this.isPlaying, - required this.isFinished, - this.onPressed, - }); - - final Color backgroundColor; - final Color? iconColor; - final bool show; - final bool isPlaying; - final bool isFinished; - final VoidCallback? onPressed; - - @override - Widget build(BuildContext context) { - return AnimatedOpacity( - opacity: show ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: Container( - width: 54, - height: 54, - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - shape: BoxShape.circle, - border: Border.all( - color: strokeFaintDark, - width: 1, - ), - ), - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: onPressed, - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 250), - transitionBuilder: (Widget child, Animation animation) { - return ScaleTransition(scale: animation, child: child); - }, - switchInCurve: Curves.easeInOutQuart, - switchOutCurve: Curves.easeInOutQuart, - child: isPlaying - ? const Icon( - Icons.pause, - size: 32, - key: ValueKey("pause"), - color: Colors.white, - ) - : const Icon( - Icons.play_arrow, - size: 36, - key: ValueKey("play"), - color: Colors.white, - ), - ), - ), - ), - ); - } -} diff --git a/mobile/lib/ui/viewer/file/video_widget.dart b/mobile/lib/ui/viewer/file/video_widget.dart index 237e4cdaca..e93c353617 100644 --- a/mobile/lib/ui/viewer/file/video_widget.dart +++ b/mobile/lib/ui/viewer/file/video_widget.dart @@ -7,8 +7,8 @@ import "package:photos/events/use_media_kit_for_video.dart"; import "package:photos/models/file/file.dart"; import "package:photos/services/filedata/filedata_service.dart"; import "package:photos/services/preview_video_store.dart"; -import "package:photos/ui/viewer/file/preview_video_widget.dart"; import "package:photos/ui/viewer/file/video_widget_media_kit_new.dart"; +import "package:photos/ui/viewer/file/video_widget_media_kit_preview.dart"; import "package:photos/ui/viewer/file/video_widget_native.dart"; class VideoWidget extends StatefulWidget { @@ -60,7 +60,7 @@ class _VideoWidgetState extends State { ?.containsKey(widget.file.uploadedFileID!) ?? false); if (isPreviewVideoPlayable && selectPreviewForPlay) { - return PreviewVideoWidget( + return VideoWidgetMediaKitPreview( widget.file, tagPrefix: widget.tagPrefix, playbackCallback: widget.playbackCallback, diff --git a/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart b/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart new file mode 100644 index 0000000000..e2b0229591 --- /dev/null +++ b/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart @@ -0,0 +1,458 @@ +import "dart:async"; + +import "package:flutter/material.dart"; +import "package:media_kit_video/media_kit_video.dart"; +import "package:photos/models/file/file.dart"; +import "package:photos/theme/colors.dart"; +import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/viewer/file/preview_status_widget.dart"; +import "package:photos/utils/date_time_util.dart"; +import "package:photos/utils/debouncer.dart"; + +class VideoWidget extends StatefulWidget { + final EnteFile file; + final VideoController controller; + final Function(bool)? playbackCallback; + final bool isFromMemories; + final void Function() onStreamChange; + + const VideoWidget( + this.file, + this.controller, + this.playbackCallback, { + super.key, + required this.isFromMemories, + // ignore: unused_element + required this.onStreamChange, + }); + + @override + State createState() => _VideoWidgetState(); +} + +class _VideoWidgetState extends State { + final showControlsNotifier = ValueNotifier(true); + static const verticalMargin = 72.0; + final _hideControlsDebouncer = Debouncer( + const Duration(milliseconds: 2000), + ); + final _isSeekingNotifier = ValueNotifier(false); + late final StreamSubscription _isPlayingStreamSubscription; + + @override + void initState() { + _isPlayingStreamSubscription = + widget.controller.player.stream.playing.listen((isPlaying) { + if (isPlaying && !_isSeekingNotifier.value) { + _hideControlsDebouncer.run(() async { + showControlsNotifier.value = false; + widget.playbackCallback?.call(true); + }); + } + }); + + _isSeekingNotifier.addListener(isSeekingListener); + super.initState(); + } + + @override + void dispose() { + showControlsNotifier.dispose(); + _isPlayingStreamSubscription.cancel(); + _hideControlsDebouncer.cancelDebounceTimer(); + _isSeekingNotifier.removeListener(isSeekingListener); + _isSeekingNotifier.dispose(); + super.dispose(); + } + + void isSeekingListener() { + if (_isSeekingNotifier.value) { + _hideControlsDebouncer.cancelDebounceTimer(); + } else { + if (widget.controller.player.state.playing) { + _hideControlsDebouncer.run(() async { + showControlsNotifier.value = false; + widget.playbackCallback?.call(false); + }); + } + } + } + + @override + Widget build(BuildContext context) { + return Video( + controller: widget.controller, + controls: (state) { + return ValueListenableBuilder( + valueListenable: showControlsNotifier, + builder: (context, value, _) { + return AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: value ? 1 : 0, + curve: Curves.easeInOutQuad, + child: Stack( + alignment: Alignment.center, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + showControlsNotifier.value = !showControlsNotifier.value; + if (widget.playbackCallback != null) { + widget.playbackCallback!( + !showControlsNotifier.value, + ); + } + }, + child: Container( + constraints: const BoxConstraints.expand(), + ), + ), + IgnorePointer( + ignoring: !value, + child: PlayPauseButtonMediaKit(widget.controller), + ), + Positioned( + bottom: verticalMargin, + right: 0, + left: 0, + child: IgnorePointer( + ignoring: !value, + child: SafeArea( + top: false, + left: false, + right: false, + child: Padding( + padding: EdgeInsets.only( + bottom: widget.isFromMemories ? 32 : 0, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + widget.file.caption != null + ? Padding( + padding: const EdgeInsets.fromLTRB( + 16, + 12, + 16, + 8, + ), + child: Text( + widget.file.caption!, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: getEnteTextTheme(context) + .mini + .copyWith( + color: textBaseDark, + ), + textAlign: TextAlign.center, + ), + ) + : const SizedBox.shrink(), + PreviewStatusWidget( + showControls: value, + file: widget.file, + isPreviewPlayer: true, + onStreamChange: widget.onStreamChange, + ), + SeekBarAndDuration( + controller: widget.controller, + isSeekingNotifier: _isSeekingNotifier, + ), + ], + ), + ), + ), + ), + ), + ], + ), + ); + }, + ); + }, + ); + } +} + +class PlayPauseButtonMediaKit extends StatefulWidget { + final VideoController? controller; + const PlayPauseButtonMediaKit( + this.controller, { + super.key, + }); + + @override + State createState() => _PlayPauseButtonState(); +} + +class _PlayPauseButtonState extends State { + bool _isPlaying = true; + late final StreamSubscription? isPlayingStreamSubscription; + + @override + void initState() { + super.initState(); + + isPlayingStreamSubscription = + widget.controller?.player.stream.playing.listen((isPlaying) { + setState(() { + _isPlaying = isPlaying; + }); + }); + } + + @override + void dispose() { + isPlayingStreamSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (widget.controller?.player.state.playing ?? false) { + widget.controller?.player.pause(); + } else { + widget.controller?.player.play(); + } + }, + child: Container( + width: 54, + height: 54, + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + shape: BoxShape.circle, + border: Border.all( + color: strokeFaintDark, + width: 1, + ), + ), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 250), + transitionBuilder: (Widget child, Animation animation) { + return ScaleTransition(scale: animation, child: child); + }, + switchInCurve: Curves.easeInOutQuart, + switchOutCurve: Curves.easeInOutQuart, + child: _isPlaying + ? const Icon( + Icons.pause, + size: 32, + key: ValueKey("pause"), + color: Colors.white, + ) + : const Icon( + Icons.play_arrow, + size: 36, + key: ValueKey("play"), + color: Colors.white, + ), + ), + ), + ); + } +} + +class SeekBarAndDuration extends StatelessWidget { + final VideoController? controller; + final ValueNotifier isSeekingNotifier; + + const SeekBarAndDuration({ + super.key, + required this.controller, + required this.isSeekingNotifier, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + ), + child: Container( + padding: const EdgeInsets.fromLTRB( + 16, + 4, + 16, + 4, + ), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: const BorderRadius.all( + Radius.circular(8), + ), + border: Border.all( + color: strokeFaintDark, + width: 1, + ), + ), + child: Row( + children: [ + StreamBuilder( + stream: controller?.player.stream.position, + builder: (context, snapshot) { + if (snapshot.data == null) { + return Text( + "0:00", + style: getEnteTextTheme( + context, + ).mini.copyWith( + color: textBaseDark, + ), + ); + } + return Text( + secondsToDuration(snapshot.data!.inSeconds), + style: getEnteTextTheme( + context, + ).mini.copyWith( + color: textBaseDark, + ), + ); + }, + ), + Expanded( + child: SeekBar( + controller!, + isSeekingNotifier, + ), + ), + Text( + _secondsToDuration( + controller!.player.state.duration.inSeconds, + ), + style: getEnteTextTheme( + context, + ).mini.copyWith( + color: textBaseDark, + ), + ), + ], + ), + ), + ); + } + + /// Returns the duration in the format "h:mm:ss" or "m:ss". + String _secondsToDuration(int totalSeconds) { + final hours = totalSeconds ~/ 3600; + final minutes = (totalSeconds % 3600) ~/ 60; + final seconds = totalSeconds % 60; + + if (hours > 0) { + return '${hours.toString().padLeft(1, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; + } else { + return '${minutes.toString().padLeft(1, '0')}:${seconds.toString().padLeft(2, '0')}'; + } + } +} + +class SeekBar extends StatefulWidget { + final VideoController controller; + final ValueNotifier isSeekingNotifier; + const SeekBar( + this.controller, + this.isSeekingNotifier, { + super.key, + }); + + @override + State createState() => _SeekBarState(); +} + +class _SeekBarState extends State { + double _sliderValue = 0.0; + late final StreamSubscription _positionStreamSubscription; + final _debouncer = Debouncer( + const Duration(milliseconds: 300), + executionInterval: const Duration(milliseconds: 300), + ); + @override + void initState() { + super.initState(); + _positionStreamSubscription = + widget.controller.player.stream.position.listen((event) { + if (widget.isSeekingNotifier.value) return; + if (mounted) { + setState(() { + _sliderValue = event.inMilliseconds / + widget.controller.player.state.duration.inMilliseconds; + if (_sliderValue.isNaN) { + _sliderValue = 0.0; + } + }); + } + }); + } + + @override + void dispose() { + _positionStreamSubscription.cancel(); + _debouncer.cancelDebounceTimer(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final colorScheme = getEnteColorScheme(context); + return SliderTheme( + data: SliderTheme.of(context).copyWith( + trackHeight: 1.0, + thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8.0), + overlayShape: const RoundSliderOverlayShape(overlayRadius: 14.0), + activeTrackColor: colorScheme.primary300, + inactiveTrackColor: fillMutedDark, + thumbColor: backgroundElevatedLight, + overlayColor: fillMutedDark, + ), + child: Slider( + min: 0.0, + max: 1.0, + value: _sliderValue, + onChangeStart: (value) { + if (mounted) { + setState(() { + widget.isSeekingNotifier.value = true; + }); + } + }, + onChanged: (value) { + if (mounted) { + setState(() { + _sliderValue = value; + }); + } + + _debouncer.run(() async { + await widget.controller.player.seek( + Duration( + milliseconds: (value * + widget.controller.player.state.duration.inMilliseconds) + .round(), + ), + ); + }); + }, + divisions: 4500, + onChangeEnd: (value) async { + await widget.controller.player.seek( + Duration( + milliseconds: (value * + widget.controller.player.state.duration.inMilliseconds) + .round(), + ), + ); + if (mounted) { + setState(() { + widget.isSeekingNotifier.value = false; + }); + } + }, + allowedInteraction: SliderInteraction.tapAndSlide, + ), + ); + } +} diff --git a/mobile/lib/ui/viewer/file/video_widget_media_kit_new.dart b/mobile/lib/ui/viewer/file/video_widget_media_kit_new.dart index a4f5651004..13d45bd57a 100644 --- a/mobile/lib/ui/viewer/file/video_widget_media_kit_new.dart +++ b/mobile/lib/ui/viewer/file/video_widget_media_kit_new.dart @@ -14,10 +14,10 @@ import "package:photos/models/file/extensions/file_props.dart"; import "package:photos/models/file/file.dart"; import "package:photos/services/files_service.dart"; import "package:photos/theme/colors.dart"; -import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/actions/file/file_actions.dart"; -import "package:photos/ui/viewer/file/preview_status_widget.dart"; -import "package:photos/utils/debouncer.dart"; +import "package:photos/ui/common/loading_widget.dart"; +import "package:photos/ui/viewer/file/video_widget_media_kit_common.dart" + as common; import "package:photos/utils/dialog_util.dart"; import "package:photos/utils/file_util.dart"; import "package:photos/utils/toast_util.dart"; @@ -27,14 +27,14 @@ class VideoWidgetMediaKitNew extends StatefulWidget { final String? tagPrefix; final Function(bool)? playbackCallback; final bool isFromMemories; - final void Function()? onStreamChange; + final void Function() onStreamChange; const VideoWidgetMediaKitNew( this.file, { this.tagPrefix, this.playbackCallback, this.isFromMemories = false, - this.onStreamChange, + required this.onStreamChange, super.key, }); @@ -137,44 +137,20 @@ class _VideoWidgetMediaKitNewState extends State }, child: Center( child: controller != null - ? _VideoWidget( + ? common.VideoWidget( widget.file, controller!, widget.playbackCallback, isFromMemories: widget.isFromMemories, + onStreamChange: widget.onStreamChange, ) - // : Stack( - // children: [ - // _getThumbnail(), - // Container( - // color: Colors.black12, - // constraints: const BoxConstraints.expand(), - // ), - // Center( - // child: SizedBox.fromSize( - // size: const Size.square(20), - // child: ValueListenableBuilder( - // valueListenable: _progressNotifier, - // builder: (BuildContext context, double? progress, _) { - // return progress == null || progress == 1 - // ? const CupertinoActivityIndicator( - // color: Colors.white, - // ) - // : CircularProgressIndicator( - // backgroundColor: Colors.black, - // value: progress, - // valueColor: - // const AlwaysStoppedAnimation( - // Color.fromRGBO(45, 194, 98, 1.0), - // ), - // ); - // }, - // ), - // ), - // ), - // ], - // ), - : const SizedBox.shrink(), + : const Center( + child: EnteLoadingWidget( + size: 32, + color: fillBaseDark, + padding: 0, + ), + ), ), ); } @@ -229,452 +205,3 @@ class _VideoWidgetMediaKitNewState extends State } } } - -class _VideoWidget extends StatefulWidget { - final EnteFile file; - final VideoController controller; - final Function(bool)? playbackCallback; - final bool isFromMemories; - final void Function()? onStreamChange; - - const _VideoWidget( - this.file, - this.controller, - this.playbackCallback, { - required this.isFromMemories, - // ignore: unused_element - this.onStreamChange, - }); - - @override - State<_VideoWidget> createState() => __VideoWidgetState(); -} - -class __VideoWidgetState extends State<_VideoWidget> { - final showControlsNotifier = ValueNotifier(true); - static const verticalMargin = 72.0; - final _hideControlsDebouncer = Debouncer( - const Duration(milliseconds: 2000), - ); - final _isSeekingNotifier = ValueNotifier(false); - late final StreamSubscription _isPlayingStreamSubscription; - - @override - void initState() { - _isPlayingStreamSubscription = - widget.controller.player.stream.playing.listen((isPlaying) { - if (isPlaying && !_isSeekingNotifier.value) { - _hideControlsDebouncer.run(() async { - showControlsNotifier.value = false; - widget.playbackCallback?.call(true); - }); - } - }); - - _isSeekingNotifier.addListener(isSeekingListener); - super.initState(); - } - - @override - void dispose() { - showControlsNotifier.dispose(); - _isPlayingStreamSubscription.cancel(); - _hideControlsDebouncer.cancelDebounceTimer(); - _isSeekingNotifier.removeListener(isSeekingListener); - _isSeekingNotifier.dispose(); - super.dispose(); - } - - void isSeekingListener() { - if (_isSeekingNotifier.value) { - _hideControlsDebouncer.cancelDebounceTimer(); - } else { - if (widget.controller.player.state.playing) { - _hideControlsDebouncer.run(() async { - showControlsNotifier.value = false; - widget.playbackCallback?.call(false); - }); - } - } - } - - @override - Widget build(BuildContext context) { - return Video( - controller: widget.controller, - controls: (state) { - return ValueListenableBuilder( - valueListenable: showControlsNotifier, - builder: (context, value, _) { - return AnimatedOpacity( - duration: const Duration(milliseconds: 200), - opacity: value ? 1 : 0, - curve: Curves.easeInOutQuad, - child: Stack( - alignment: Alignment.center, - children: [ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - showControlsNotifier.value = !showControlsNotifier.value; - if (widget.playbackCallback != null) { - widget.playbackCallback!( - !showControlsNotifier.value, - ); - } - }, - child: Container( - constraints: const BoxConstraints.expand(), - ), - ), - IgnorePointer( - ignoring: !value, - child: PlayPauseButtonMediaKit(widget.controller), - ), - Positioned( - bottom: verticalMargin, - right: 0, - left: 0, - child: IgnorePointer( - ignoring: !value, - child: SafeArea( - top: false, - left: false, - right: false, - child: Padding( - padding: EdgeInsets.only( - bottom: widget.isFromMemories ? 32 : 0, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - widget.file.caption != null - ? Padding( - padding: const EdgeInsets.fromLTRB( - 16, - 12, - 16, - 8, - ), - child: Text( - widget.file.caption!, - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: getEnteTextTheme(context) - .mini - .copyWith( - color: textBaseDark, - ), - textAlign: TextAlign.center, - ), - ) - : const SizedBox.shrink(), - ValueListenableBuilder( - valueListenable: showControlsNotifier, - builder: (context, value, _) { - return PreviewStatusWidget( - showControls: value, - file: widget.file, - onStreamChange: widget.onStreamChange, - ); - }, - ), - _SeekBarAndDuration( - controller: widget.controller, - isSeekingNotifier: _isSeekingNotifier, - ), - ], - ), - ), - ), - ), - ), - ], - ), - ); - }, - ); - }, - ); - } -} - -class PlayPauseButtonMediaKit extends StatefulWidget { - final VideoController? controller; - const PlayPauseButtonMediaKit( - this.controller, { - super.key, - }); - - @override - State createState() => _PlayPauseButtonState(); -} - -class _PlayPauseButtonState extends State { - bool _isPlaying = true; - late final StreamSubscription? isPlayingStreamSubscription; - - @override - void initState() { - super.initState(); - - isPlayingStreamSubscription = - widget.controller?.player.stream.playing.listen((isPlaying) { - setState(() { - _isPlaying = isPlaying; - }); - }); - } - - @override - void dispose() { - isPlayingStreamSubscription?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - if (widget.controller?.player.state.playing ?? false) { - widget.controller?.player.pause(); - } else { - widget.controller?.player.play(); - } - }, - child: Container( - width: 54, - height: 54, - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - shape: BoxShape.circle, - border: Border.all( - color: strokeFaintDark, - width: 1, - ), - ), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 250), - transitionBuilder: (Widget child, Animation animation) { - return ScaleTransition(scale: animation, child: child); - }, - switchInCurve: Curves.easeInOutQuart, - switchOutCurve: Curves.easeInOutQuart, - child: _isPlaying - ? const Icon( - Icons.pause, - size: 32, - key: ValueKey("pause"), - color: Colors.white, - ) - : const Icon( - Icons.play_arrow, - size: 36, - key: ValueKey("play"), - color: Colors.white, - ), - ), - ), - ); - } -} - -class _SeekBarAndDuration extends StatelessWidget { - final VideoController? controller; - final ValueNotifier isSeekingNotifier; - - const _SeekBarAndDuration({ - required this.controller, - required this.isSeekingNotifier, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - ), - child: Container( - padding: const EdgeInsets.fromLTRB( - 16, - 4, - 16, - 4, - ), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - borderRadius: const BorderRadius.all( - Radius.circular(8), - ), - border: Border.all( - color: strokeFaintDark, - width: 1, - ), - ), - child: Row( - children: [ - StreamBuilder( - stream: controller?.player.stream.position, - builder: (context, snapshot) { - if (snapshot.data == null) { - return Text( - "0:00", - style: getEnteTextTheme( - context, - ).mini.copyWith( - color: textBaseDark, - ), - ); - } - return Text( - _secondsToDuration(snapshot.data!.inSeconds), - style: getEnteTextTheme( - context, - ).mini.copyWith( - color: textBaseDark, - ), - ); - }, - ), - Expanded( - child: _SeekBar( - controller!, - isSeekingNotifier, - ), - ), - Text( - _secondsToDuration( - controller!.player.state.duration.inSeconds, - ), - style: getEnteTextTheme( - context, - ).mini.copyWith( - color: textBaseDark, - ), - ), - ], - ), - ), - ); - } - - /// Returns the duration in the format "h:mm:ss" or "m:ss". - String _secondsToDuration(int totalSeconds) { - final hours = totalSeconds ~/ 3600; - final minutes = (totalSeconds % 3600) ~/ 60; - final seconds = totalSeconds % 60; - - if (hours > 0) { - return '${hours.toString().padLeft(1, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; - } else { - return '${minutes.toString().padLeft(1, '0')}:${seconds.toString().padLeft(2, '0')}'; - } - } -} - -class _SeekBar extends StatefulWidget { - final VideoController controller; - final ValueNotifier isSeekingNotifier; - const _SeekBar( - this.controller, - this.isSeekingNotifier, - ); - - @override - State<_SeekBar> createState() => _SeekBarState(); -} - -class _SeekBarState extends State<_SeekBar> { - double _sliderValue = 0.0; - late final StreamSubscription _positionStreamSubscription; - final _debouncer = Debouncer( - const Duration(milliseconds: 300), - executionInterval: const Duration(milliseconds: 300), - ); - @override - void initState() { - super.initState(); - _positionStreamSubscription = - widget.controller.player.stream.position.listen((event) { - if (widget.isSeekingNotifier.value) return; - if (mounted) { - setState(() { - _sliderValue = event.inMilliseconds / - widget.controller.player.state.duration.inMilliseconds; - if (_sliderValue.isNaN) { - _sliderValue = 0.0; - } - }); - } - }); - } - - @override - void dispose() { - _positionStreamSubscription.cancel(); - _debouncer.cancelDebounceTimer(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final colorScheme = getEnteColorScheme(context); - return SliderTheme( - data: SliderTheme.of(context).copyWith( - trackHeight: 1.0, - thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8.0), - overlayShape: const RoundSliderOverlayShape(overlayRadius: 14.0), - activeTrackColor: colorScheme.primary300, - inactiveTrackColor: fillMutedDark, - thumbColor: backgroundElevatedLight, - overlayColor: fillMutedDark, - ), - child: Slider( - min: 0.0, - max: 1.0, - value: _sliderValue, - onChangeStart: (value) { - if (mounted) { - setState(() { - widget.isSeekingNotifier.value = true; - }); - } - }, - onChanged: (value) { - if (mounted) { - setState(() { - _sliderValue = value; - }); - } - - _debouncer.run(() async { - await widget.controller.player.seek( - Duration( - milliseconds: (value * - widget.controller.player.state.duration.inMilliseconds) - .round(), - ), - ); - }); - }, - divisions: 4500, - onChangeEnd: (value) async { - await widget.controller.player.seek( - Duration( - milliseconds: (value * - widget.controller.player.state.duration.inMilliseconds) - .round(), - ), - ); - if (mounted) { - setState(() { - widget.isSeekingNotifier.value = false; - }); - } - }, - allowedInteraction: SliderInteraction.tapAndSlide, - ), - ); - } -} diff --git a/mobile/lib/ui/viewer/file/video_widget_media_kit_preview.dart b/mobile/lib/ui/viewer/file/video_widget_media_kit_preview.dart new file mode 100644 index 0000000000..39ff6e491f --- /dev/null +++ b/mobile/lib/ui/viewer/file/video_widget_media_kit_preview.dart @@ -0,0 +1,172 @@ +import "dart:async"; + +import "package:flutter/material.dart"; +import "package:fluttertoast/fluttertoast.dart"; +import "package:logging/logging.dart"; +import "package:media_kit/media_kit.dart"; +import "package:media_kit_video/media_kit_video.dart"; +import "package:photos/core/constants.dart"; +import "package:photos/core/event_bus.dart"; +import "package:photos/events/guest_view_event.dart"; +import "package:photos/events/pause_video_event.dart"; +import "package:photos/models/file/file.dart"; +import "package:photos/service_locator.dart"; +import "package:photos/services/filedata/filedata_service.dart"; +import "package:photos/services/preview_video_store.dart"; +import "package:photos/theme/colors.dart"; +import "package:photos/ui/actions/file/file_actions.dart"; +import "package:photos/ui/common/loading_widget.dart"; +import "package:photos/ui/viewer/file/video_widget_media_kit_common.dart" + as common; +import "package:photos/utils/data_util.dart"; +import "package:photos/utils/file_util.dart"; +import "package:photos/utils/toast_util.dart"; + +class VideoWidgetMediaKitPreview extends StatefulWidget { + final EnteFile file; + final String? tagPrefix; + final Function(bool)? playbackCallback; + final bool isFromMemories; + final void Function() onStreamChange; + + const VideoWidgetMediaKitPreview( + this.file, { + this.tagPrefix, + this.playbackCallback, + this.isFromMemories = false, + required this.onStreamChange, + super.key, + }); + + @override + State createState() => + _VideoWidgetMediaKitPreviewState(); +} + +class _VideoWidgetMediaKitPreviewState extends State + with WidgetsBindingObserver { + final Logger _logger = Logger("VideoWidgetMediaKitNew"); + late final player = Player(); + VideoController? controller; + final _progressNotifier = ValueNotifier(null); + bool _isAppInFG = true; + late StreamSubscription pauseVideoSubscription; + bool isGuestView = false; + late final StreamSubscription _guestViewEventSubscription; + bool _isGuestView = false; + + @override + void initState() { + _logger.info( + 'initState for ${widget.file.generatedID} with tag ${widget.file.tag} and name ${widget.file.displayName}', + ); + super.initState(); + WidgetsBinding.instance.addObserver(this); + _checkForPreview(); + + pauseVideoSubscription = Bus.instance.on().listen((event) { + player.pause(); + }); + _guestViewEventSubscription = + Bus.instance.on().listen((event) { + setState(() { + _isGuestView = event.isGuestView; + }); + }); + } + + Future _checkForPreview() async { + widget.playbackCallback?.call(false); + final data = await PreviewVideoStore.instance + .getPlaylist(widget.file) + .onError((error, stackTrace) { + if (!mounted) return; + _logger.warning("Failed to download preview video", error, stackTrace); + Fluttertoast.showToast(msg: "Failed to download preview!"); + return null; + }); + if (!mounted) return; + if (data != null) { + if (flagService.internalUser) { + final d = + FileDataService.instance.previewIds?[widget.file.uploadedFileID!]; + if (d != null && widget.file.fileSize != null) { + // show toast with human readable size + final size = formatBytes(widget.file.fileSize!); + showToast( + context, + "[i] Preview OG Size ($size), previewSize: ${formatBytes(d.objectSize)}", + ); + } else { + showShortToast(context, "Playing preview"); + } + } + _setVideoController(data.preview.path); + } + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + _isAppInFG = true; + } else { + _isAppInFG = false; + } + } + + @override + void dispose() { + _guestViewEventSubscription.cancel(); + pauseVideoSubscription.cancel(); + removeCallBack(widget.file); + _progressNotifier.dispose(); + WidgetsBinding.instance.removeObserver(this); + player.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onVerticalDragUpdate: _isGuestView + ? null + : (d) => { + if (d.delta.dy > dragSensitivity) + { + Navigator.of(context).pop(), + } + else if (d.delta.dy < (dragSensitivity * -1)) + { + showDetailsSheet(context, widget.file), + }, + }, + child: Center( + child: controller != null + ? common.VideoWidget( + widget.file, + controller!, + widget.playbackCallback, + isFromMemories: widget.isFromMemories, + onStreamChange: widget.onStreamChange, + ) + : const Center( + child: EnteLoadingWidget( + size: 32, + color: fillBaseDark, + padding: 0, + ), + ), + ), + ); + } + + void _setVideoController(String url) { + if (mounted) { + setState(() { + player.setPlaylistMode(PlaylistMode.single); + controller = VideoController(player); + player.open(Media(url), play: _isAppInFG); + }); + } + } +} diff --git a/mobile/lib/ui/viewer/file/video_widget_native.dart b/mobile/lib/ui/viewer/file/video_widget_native.dart index 8eee011233..c3d1bb8c83 100644 --- a/mobile/lib/ui/viewer/file/video_widget_native.dart +++ b/mobile/lib/ui/viewer/file/video_widget_native.dart @@ -24,6 +24,7 @@ import "package:photos/ui/viewer/file/native_video_player_controls/play_pause_bu import "package:photos/ui/viewer/file/native_video_player_controls/seek_bar.dart"; import "package:photos/ui/viewer/file/preview_status_widget.dart"; import "package:photos/ui/viewer/file/thumbnail_widget.dart"; +import "package:photos/utils/date_time_util.dart"; import "package:photos/utils/debouncer.dart"; import "package:photos/utils/dialog_util.dart"; import "package:photos/utils/exif_util.dart"; @@ -615,9 +616,7 @@ class _SeekBarAndDuration extends StatelessWidget { _, ) { return Text( - _secondsToDuration( - value, - ), + secondsToDuration(value), style: getEnteTextTheme( context, ).mini.copyWith( @@ -630,17 +629,13 @@ class _SeekBarAndDuration extends StatelessWidget { Expanded( child: SeekBar( controller!, - _durationToSeconds( - duration, - ), + durationToSeconds(duration), isSeeking, ), ), Text( duration ?? "0:00", - style: getEnteTextTheme( - context, - ).mini.copyWith( + style: getEnteTextTheme(context).mini.copyWith( color: textBaseDark, ), ), @@ -653,43 +648,6 @@ class _SeekBarAndDuration extends StatelessWidget { }, ); } - - /// Returns the duration in the format "h:mm:ss" or "m:ss". - String _secondsToDuration(int totalSeconds) { - final hours = totalSeconds ~/ 3600; - final minutes = (totalSeconds % 3600) ~/ 60; - final seconds = totalSeconds % 60; - - if (hours > 0) { - return '${hours.toString().padLeft(1, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; - } else { - return '${minutes.toString().padLeft(1, '0')}:${seconds.toString().padLeft(2, '0')}'; - } - } - - /// Returns the duration in seconds from the format "h:mm:ss" or "m:ss". - int? _durationToSeconds(String? duration) { - if (duration == null) { - return null; - } - final parts = duration.split(':'); - int seconds = 0; - - if (parts.length == 3) { - // Format: "h:mm:ss" - seconds += int.parse(parts[0]) * 3600; // Hours to seconds - seconds += int.parse(parts[1]) * 60; // Minutes to seconds - seconds += int.parse(parts[2]); // Seconds - } else if (parts.length == 2) { - // Format: "m:ss" - seconds += int.parse(parts[0]) * 60; // Minutes to seconds - seconds += int.parse(parts[1]); // Seconds - } else { - throw FormatException('Invalid duration format: $duration'); - } - - return seconds; - } } class _VideoDescriptionAndSwitchToMediaKitButton extends StatelessWidget { diff --git a/mobile/lib/utils/date_time_util.dart b/mobile/lib/utils/date_time_util.dart index afcd92aa16..bb29960319 100644 --- a/mobile/lib/utils/date_time_util.dart +++ b/mobile/lib/utils/date_time_util.dart @@ -216,3 +216,40 @@ bool isNumeric(String? s) { } return double.tryParse(s) != null; } + +/// Returns the duration in seconds from the format "h:mm:ss" or "m:ss". +int? durationToSeconds(String? duration) { + if (duration == null) { + return null; + } + final parts = duration.split(':'); + int seconds = 0; + + if (parts.length == 3) { + // Format: "h:mm:ss" + seconds += int.parse(parts[0]) * 3600; // Hours to seconds + seconds += int.parse(parts[1]) * 60; // Minutes to seconds + seconds += int.parse(parts[2]); // Seconds + } else if (parts.length == 2) { + // Format: "m:ss" + seconds += int.parse(parts[0]) * 60; // Minutes to seconds + seconds += int.parse(parts[1]); // Seconds + } else { + throw FormatException('Invalid duration format: $duration'); + } + + return seconds; +} + +/// Returns the duration in the format "h:mm:ss" or "m:ss". +String secondsToDuration(int totalSeconds) { + final hours = totalSeconds ~/ 3600; + final minutes = (totalSeconds % 3600) ~/ 60; + final seconds = totalSeconds % 60; + + if (hours > 0) { + return '${hours.toString().padLeft(1, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; + } else { + return '${minutes.toString().padLeft(1, '0')}:${seconds.toString().padLeft(2, '0')}'; + } +} diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 0c14e7b23a..286c154ba7 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -977,26 +977,26 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "22dbf16f23a4bcf9d35e51be1c84ad5bb6f627750565edd70dab70f3ff5fff8f" + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "9.2.4" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" + sha256: bf7404619d7ab5c0a1151d7c4e802edad8f33535abfbeff2f9e1fe1274e2d705 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81" + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" flutter_secure_storage_platform_interface: dependency: transitive description: @@ -1017,10 +1017,10 @@ packages: dependency: transitive description: name: flutter_secure_storage_windows - sha256: "38f9501c7cb6f38961ef0e1eacacee2b2d4715c63cc83fe56449c4d3d0b47255" + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "3.1.2" flutter_shaders: dependency: transitive description: @@ -1386,7 +1386,7 @@ packages: source: hosted version: "0.10.1" js: - dependency: transitive + dependency: "direct overridden" description: name: js sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 @@ -1589,10 +1589,11 @@ packages: media_kit: dependency: "direct main" description: - name: media_kit - sha256: "1f1deee148533d75129a6f38251ff8388e33ee05fc2d20a6a80e57d6051b7b62" - url: "https://pub.dev" - source: hosted + path: media_kit + ref: HEAD + resolved-ref: "3c4ff28c43d20e68f8d587956b2f525292c25a80" + url: "https://github.com/media-kit/media-kit" + source: git version: "1.1.11" media_kit_libs_android_video: dependency: transitive @@ -1605,10 +1606,11 @@ packages: media_kit_libs_ios_video: dependency: "direct main" description: - name: media_kit_libs_ios_video - sha256: b5382994eb37a4564c368386c154ad70ba0cc78dacdd3fb0cd9f30db6d837991 - url: "https://pub.dev" - source: hosted + path: "libs/ios/media_kit_libs_ios_video" + ref: HEAD + resolved-ref: "3c4ff28c43d20e68f8d587956b2f525292c25a80" + url: "https://github.com/media-kit/media-kit" + source: git version: "1.1.4" media_kit_libs_linux: dependency: transitive @@ -1629,10 +1631,11 @@ packages: media_kit_libs_video: dependency: "direct main" description: - name: media_kit_libs_video - sha256: "20bb4aefa8fece282b59580e1cd8528117297083a6640c98c2e98cfc96b93288" - url: "https://pub.dev" - source: hosted + path: "libs/universal/media_kit_libs_video" + ref: HEAD + resolved-ref: "3c4ff28c43d20e68f8d587956b2f525292c25a80" + url: "https://github.com/media-kit/media-kit" + source: git version: "1.0.5" media_kit_libs_windows_video: dependency: transitive @@ -1642,21 +1645,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.10" - media_kit_native_event_loop: - dependency: transitive - description: - name: media_kit_native_event_loop - sha256: "7d82e3b3e9ded5c35c3146c5ba1da3118d1dd8ac3435bac7f29f458181471b40" - url: "https://pub.dev" - source: hosted - version: "1.0.9" media_kit_video: dependency: "direct main" description: - name: media_kit_video - sha256: "2cc3b966679963ba25a4ce5b771e532a521ebde7c6aa20e9802bec95d9916c8f" - url: "https://pub.dev" - source: hosted + path: media_kit_video + ref: HEAD + resolved-ref: "3c4ff28c43d20e68f8d587956b2f525292c25a80" + url: "https://github.com/media-kit/media-kit" + source: git version: "1.2.5" meta: dependency: transitive @@ -2171,58 +2167,26 @@ packages: dependency: transitive description: name: safe_local_storage - sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440 + sha256: e9a21b6fec7a8aa62cc2585ff4c1b127df42f3185adbd2aca66b47abe2e80236 url: "https://pub.dev" source: hosted - version: "1.0.2" - screen_brightness: - dependency: transitive - description: - name: screen_brightness - sha256: ed8da4a4511e79422fc1aa88138e920e4008cd312b72cdaa15ccb426c0faaedd - url: "https://pub.dev" - source: hosted - version: "0.2.2+1" + version: "2.0.1" screen_brightness_android: dependency: transitive description: name: screen_brightness_android - sha256: "3df10961e3a9e968a5e076fe27e7f4741fa8a1d3950bdeb48cf121ed529d0caf" + sha256: ff9141bed547db02233e7dd88f990ab01973a0c8a8c04ddb855c7b072f33409a url: "https://pub.dev" source: hosted - version: "0.1.0+2" - screen_brightness_ios: - dependency: transitive - description: - name: screen_brightness_ios - sha256: "99adc3ca5490b8294284aad5fcc87f061ad685050e03cf45d3d018fe398fd9a2" - url: "https://pub.dev" - source: hosted - version: "0.1.0" - screen_brightness_macos: - dependency: transitive - description: - name: screen_brightness_macos - sha256: "64b34e7e3f4900d7687c8e8fb514246845a73ecec05ab53483ed025bd4a899fd" - url: "https://pub.dev" - source: hosted - version: "0.1.0+1" + version: "2.1.0" screen_brightness_platform_interface: dependency: transitive description: name: screen_brightness_platform_interface - sha256: b211d07f0c96637a15fb06f6168617e18030d5d74ad03795dd8547a52717c171 + sha256: "737bd47b57746bc4291cab1b8a5843ee881af499514881b0247ec77447ee769c" url: "https://pub.dev" source: hosted - version: "0.1.0" - screen_brightness_windows: - dependency: transitive - description: - name: screen_brightness_windows - sha256: "9261bf33d0fc2707d8cf16339ce25768100a65e70af0fcabaf032fc12408ba86" - url: "https://pub.dev" - source: hosted - version: "0.1.3" + version: "2.1.0" screenshot: dependency: "direct main" description: @@ -2712,10 +2676,10 @@ packages: dependency: transitive description: name: uri_parser - sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835" + sha256: ff4d2c720aca3f4f7d5445e23b11b2d15ef8af5ddce5164643f38ff962dcb270 url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "3.0.0" url_launcher: dependency: "direct main" description: @@ -2862,14 +2826,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.2" - video_player_media_kit: - dependency: "direct main" - description: - name: video_player_media_kit - sha256: eadf78b85d0ecc6f65bb5ca84c5ad9546a8609c6c0ee207e81673f7969461f3b - url: "https://pub.dev" - source: hosted - version: "1.0.5" video_player_platform_interface: dependency: transitive description: @@ -2914,10 +2870,10 @@ packages: dependency: transitive description: name: volume_controller - sha256: c71d4c62631305df63b72da79089e078af2659649301807fa746088f365cb48e + sha256: "68975792fbd2ac36cd2aa387273669772a452785d94b6db5808bf2dc3e44a0f1" url: "https://pub.dev" source: hosted - version: "2.0.8" + version: "3.3.0" wakelock_plus: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index b742983207..7b9da3ccd5 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.9.99+1002 +version: 0.9.99+1003 publish_to: none environment: @@ -90,7 +90,7 @@ dependencies: flutter_map_marker_cluster: ^1.3.6 flutter_native_splash: ^2.2.0+1 flutter_password_strength: ^0.1.6 - flutter_secure_storage: ^8.0.0 + flutter_secure_storage: ^9.2.4 flutter_sodium: ^0.2.0 flutter_staggered_grid_view: ^0.6.2 flutter_svg: ^2.0.10+1 @@ -120,10 +120,22 @@ dependencies: git: url: "https://github.com/ente-io/media_extension.git" ref: deeplink_fixes - media_kit: ^1.1.10+1 - media_kit_libs_ios_video: ^1.1.4 - media_kit_libs_video: ^1.0.4 - media_kit_video: ^1.2.4 + media_kit: + git: + url: https://github.com/media-kit/media-kit + path: media_kit + media_kit_libs_ios_video: + git: + url: https://github.com/media-kit/media-kit + path: libs/ios/media_kit_libs_ios_video + media_kit_libs_video: + git: + url: https://github.com/media-kit/media-kit + path: libs/universal/media_kit_libs_video + media_kit_video: + git: + url: https://github.com/media-kit/media-kit + path: media_kit_video ml_linalg: ^13.11.31 modal_bottom_sheet: ^3.0.0-pre motion_photos: @@ -195,7 +207,6 @@ dependencies: url: https://github.com/ente-io/packages.git ref: android_video_roation_fix path: packages/video_player/video_player/ - video_player_media_kit: ^1.0.5 video_thumbnail: ^0.5.3 visibility_detector: ^0.3.3 wakelock_plus: ^1.1.1 @@ -210,6 +221,23 @@ dependency_overrides: # Newer flutter packages depends on ffi > 2.0.0 while flutter_sodium depends on ffi < 2.0.0 ffi: 2.1.0 intl: 0.18.1 + js: ^0.6.7 + media_kit: + git: + url: https://github.com/media-kit/media-kit + path: media_kit + media_kit_libs_ios_video: + git: + url: https://github.com/media-kit/media-kit + path: libs/ios/media_kit_libs_ios_video + media_kit_libs_video: + git: + url: https://github.com/media-kit/media-kit + path: libs/universal/media_kit_libs_video + media_kit_video: + git: + url: https://github.com/media-kit/media-kit + path: media_kit_video video_player: git: url: https://github.com/ente-io/packages.git