diff --git a/mobile/lib/ui/viewer/file/video_widget.dart b/mobile/lib/ui/viewer/file/video_widget.dart index d2fc48f879..1bbafd9e25 100644 --- a/mobile/lib/ui/viewer/file/video_widget.dart +++ b/mobile/lib/ui/viewer/file/video_widget.dart @@ -15,7 +15,7 @@ import "package:photos/services/preview_video_store.dart"; import "package:photos/theme/colors.dart"; import "package:photos/ui/common/loading_widget.dart"; import "package:photos/ui/notification/toast.dart"; -import "package:photos/ui/viewer/file/video_widget_media_kit_new.dart"; +import "package:photos/ui/viewer/file/video_widget_media_kit.dart"; import "package:photos/ui/viewer/file/video_widget_native.dart"; import "package:photos/utils/standalone/data.dart"; @@ -164,7 +164,7 @@ class _VideoWidgetState extends State { }, ); } - return VideoWidgetMediaKitNew( + return VideoWidgetMediaKit( widget.file, key: mediaKitKey, tagPrefix: widget.tagPrefix, diff --git a/mobile/lib/ui/viewer/file/video_widget_media_kit.dart b/mobile/lib/ui/viewer/file/video_widget_media_kit.dart index 50beceb5f8..57508c1221 100644 --- a/mobile/lib/ui/viewer/file/video_widget_media_kit.dart +++ b/mobile/lib/ui/viewer/file/video_widget_media_kit.dart @@ -1,24 +1,30 @@ import "dart:async"; import "dart:io"; -import "package:flutter/cupertino.dart"; import "package:flutter/material.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/file_caption_updated_event.dart"; import "package:photos/events/guest_view_event.dart"; import "package:photos/events/pause_video_event.dart"; +import "package:photos/events/stream_switched_event.dart"; import "package:photos/generated/l10n.dart"; import "package:photos/models/file/extensions/file_props.dart"; import "package:photos/models/file/file.dart"; +import "package:photos/module/download/task.dart"; +import "package:photos/service_locator.dart"; import "package:photos/services/files_service.dart"; +import "package:photos/services/wake_lock_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/common/loading_widget.dart"; import "package:photos/ui/notification/toast.dart"; -import "package:photos/ui/viewer/file/thumbnail_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"; @@ -26,10 +32,19 @@ class VideoWidgetMediaKit extends StatefulWidget { final EnteFile file; final String? tagPrefix; final Function(bool)? playbackCallback; + final bool isFromMemories; + final void Function() onStreamChange; + final File? preview; + final bool selectedPreview; + const VideoWidgetMediaKit( this.file, { this.tagPrefix, this.playbackCallback, + this.isFromMemories = false, + required this.onStreamChange, + this.preview, + required this.selectedPreview, super.key, }); @@ -39,16 +54,19 @@ class VideoWidgetMediaKit extends StatefulWidget { class _VideoWidgetMediaKitState extends State with WidgetsBindingObserver { - final Logger _logger = Logger("VideoWidgetMediaKit"); - static const verticalMargin = 72.0; + final Logger _logger = Logger("VideoWidgetMediaKitNew"); late final player = Player(); VideoController? controller; final _progressNotifier = ValueNotifier(null); - late StreamSubscription playingStreamSubscription; bool _isAppInFG = true; late StreamSubscription pauseVideoSubscription; bool isGuestView = false; late final StreamSubscription _guestViewEventSubscription; + bool _isGuestView = false; + StreamSubscription? _streamSwitchedSubscription; + StreamSubscription? _downloadTaskSubscription; + late final StreamSubscription + _captionUpdatedSubscription; @override void initState() { @@ -57,6 +75,61 @@ class _VideoWidgetMediaKitState extends State ); super.initState(); WidgetsBinding.instance.addObserver(this); + + if (widget.selectedPreview) { + loadPreview(); + } else { + loadOriginal(); + } + + pauseVideoSubscription = Bus.instance.on().listen((event) { + player.pause(); + }); + _guestViewEventSubscription = + Bus.instance.on().listen((event) { + setState(() { + _isGuestView = event.isGuestView; + }); + }); + if (widget.file.isUploaded) { + _downloadTaskSubscription = downloadManager + .watchDownload(widget.file.uploadedFileID!) + .listen((event) { + if (mounted) { + setState(() { + _progressNotifier.value = event.progress; + }); + } + }); + } + + _streamSwitchedSubscription = + Bus.instance.on().listen((event) { + if (event.type != PlayerType.mediaKit || !mounted) return; + if (event.selectedPreview) { + loadPreview(); + } else { + loadOriginal(); + } + }); + + _captionUpdatedSubscription = + Bus.instance.on().listen((event) { + if (event.fileGeneratedID == widget.file.generatedID) { + if (mounted) { + setState(() {}); + } + } + }); + EnteWakeLockService.instance + .updateWakeLock(enable: true, wakeLockFor: WakeLockFor.videoPlayback); + } + + void loadPreview() { + _setVideoController(widget.preview!.path); + } + + void loadOriginal() { if (widget.file.isRemoteFile) { _loadNetworkVideo(); _setFileSizeIfNull(); @@ -84,21 +157,6 @@ class _VideoWidgetMediaKitState extends State } }); } - playingStreamSubscription = player.stream.playing.listen((event) { - if (widget.playbackCallback != null && mounted) { - widget.playbackCallback!(event); - } - }); - - pauseVideoSubscription = Bus.instance.on().listen((event) { - player.pause(); - }); - _guestViewEventSubscription = - Bus.instance.on().listen((event) { - setState(() { - isGuestView = event.isGuestView; - }); - }); } @override @@ -112,71 +170,93 @@ class _VideoWidgetMediaKitState extends State @override void dispose() { + _streamSwitchedSubscription?.cancel(); _guestViewEventSubscription.cancel(); pauseVideoSubscription.cancel(); removeCallBack(widget.file); _progressNotifier.dispose(); WidgetsBinding.instance.removeObserver(this); - playingStreamSubscription.cancel(); + if (_downloadTaskSubscription != null) { + _downloadTaskSubscription!.cancel(); + downloadManager.pause(widget.file.uploadedFileID!).ignore(); + } player.dispose(); + _captionUpdatedSubscription.cancel(); + if (EnteWakeLockService.instance.shouldKeepAppAwakeAcrossSessions) { + EnteWakeLockService.instance.updateWakeLock( + enable: true, + wakeLockFor: WakeLockFor.handlingMediaKitEdgeCase, + ); + } else { + EnteWakeLockService.instance.updateWakeLock( + enable: false, + wakeLockFor: WakeLockFor.videoPlayback, + ); + } super.dispose(); } @override Widget build(BuildContext context) { - return Hero( - tag: widget.tagPrefix! + widget.file.tag, - child: MaterialVideoControlsTheme( - normal: MaterialVideoControlsThemeData( - backdropColor: null, - automaticallyImplySkipNextButton: false, - automaticallyImplySkipPreviousButton: false, - seekOnDoubleTap: false, - displaySeekBar: true, - seekBarMargin: const EdgeInsets.only(bottom: verticalMargin), - bottomButtonBarMargin: const EdgeInsets.only(bottom: 112), - controlsHoverDuration: const Duration(seconds: 3), - seekBarHeight: 2, - seekBarThumbSize: 16, - seekBarBufferColor: Colors.transparent, - seekBarThumbColor: backgroundElevatedLight, - seekBarColor: fillMutedDark, - seekBarPositionColor: backgroundElevatedLight, - seekBarContainerHeight: 56, - seekBarAlignment: Alignment.center, - - ///topButtonBarMargin is needed for keeping the buffering loading - ///indicator to be center aligned - topButtonBarMargin: const EdgeInsets.only(top: verticalMargin), - bottomButtonBar: [ - const Spacer(), - PausePlayAndDuration(controller?.player), - const Spacer(), - ], - primaryButtonBar: [], - ), - fullscreen: const MaterialVideoControlsThemeData(), - child: 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), - }, + 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 - ? Video( - controller: controller!, - ) - : _getLoadingWidget(), - ), - ), + }, + child: Center( + child: controller != null + ? common.VideoWidget( + widget.file, + controller!, + widget.playbackCallback, + isFromMemories: widget.isFromMemories, + onStreamChange: widget.onStreamChange, + isPreviewPlayer: widget.selectedPreview, + ) + : Center( + child: ValueListenableBuilder( + valueListenable: _progressNotifier, + builder: (BuildContext context, double? progress, _) { + return progress == null || progress == 1 + ? const EnteLoadingWidget( + size: 32, + color: fillBaseDark, + padding: 0, + ) + : Stack( + children: [ + CircularProgressIndicator( + backgroundColor: Colors.transparent, + value: progress, + valueColor: const AlwaysStoppedAnimation( + Color.fromRGBO(45, 194, 98, 1.0), + ), + strokeWidth: 2, + strokeCap: StrokeCap.round, + ), + if (flagService.internalUser) + Center( + child: Text( + "${(progress * 100).toStringAsFixed(0)}%", + style: + getEnteTextTheme(context).tiny.copyWith( + color: textBaseDark, + ), + ), + ), + ], + ); + }, + ), + ), ), ); } @@ -221,148 +301,19 @@ class _VideoWidgetMediaKitState extends State } } - 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, - ), - ); - } - void _setVideoController(String url) { if (mounted) { setState(() { - player.setPlaylistMode(PlaylistMode.single); - controller = VideoController(player); + if (controller == null) { + player.setPlaylistMode( + localSettings.shouldLoopVideo() + ? PlaylistMode.single + : PlaylistMode.none, + ); + controller = VideoController(player); + } player.open(Media(url), play: _isAppInFG); }); } } } - -class PausePlayAndDuration extends StatefulWidget { - final Player? player; - const PausePlayAndDuration(this.player, {super.key}); - - @override - State createState() => _PausePlayAndDurationState(); -} - -class _PausePlayAndDurationState extends State { - Color backgroundColor = fillStrongLight; - @override - Widget build(BuildContext context) { - return GestureDetector( - onTapDown: (details) { - setState(() { - backgroundColor = fillMutedDark; - }); - }, - onTapUp: (details) { - Future.delayed(const Duration(milliseconds: 175), () { - if (mounted) { - setState(() { - backgroundColor = fillStrongLight; - }); - } - }); - }, - onTapCancel: () { - Future.delayed(const Duration(milliseconds: 175), () { - if (mounted) { - setState(() { - backgroundColor = fillStrongLight; - }); - } - }); - }, - onTap: () => widget.player!.playOrPause(), - child: AnimatedContainer( - duration: const Duration(milliseconds: 150), - curve: Curves.easeInBack, - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), - decoration: BoxDecoration( - color: backgroundColor, - border: Border.all( - color: strokeFaintDark, - width: 1, - ), - borderRadius: BorderRadius.circular(24), - ), - child: AnimatedSize( - duration: const Duration(seconds: 2), - curve: Curves.easeInOutExpo, - child: Row( - children: [ - StreamBuilder( - builder: (context, snapshot) { - final bool isPlaying = snapshot.data ?? false; - return AnimatedSwitcher( - duration: const Duration(milliseconds: 350), - switchInCurve: Curves.easeInOutCirc, - switchOutCurve: Curves.easeInOutCirc, - child: Icon( - key: ValueKey( - isPlaying ? "pause_button" : "play_button", - ), - isPlaying - ? Icons.pause_rounded - : Icons.play_arrow_rounded, - color: backdropBaseLight, - size: 24, - ), - ); - }, - initialData: widget.player?.state.playing, - stream: widget.player?.stream.playing, - ), - const SizedBox(width: 8), - MaterialPositionIndicator( - style: getEnteTextTheme(context).tiny.copyWith( - color: textBaseDark, - ), - ), - const SizedBox(width: 10), - ], - ), - ), - ), - ); - } -} 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 deleted file mode 100644 index 72c5819fbe..0000000000 --- a/mobile/lib/ui/viewer/file/video_widget_media_kit_new.dart +++ /dev/null @@ -1,312 +0,0 @@ -import "dart:async"; -import "dart:io"; - -import "package:flutter/material.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/file_caption_updated_event.dart"; -import "package:photos/events/guest_view_event.dart"; -import "package:photos/events/pause_video_event.dart"; -import "package:photos/events/stream_switched_event.dart"; -import "package:photos/generated/l10n.dart"; -import "package:photos/models/file/extensions/file_props.dart"; -import "package:photos/models/file/file.dart"; -import "package:photos/module/download/task.dart"; -import "package:photos/service_locator.dart"; -import "package:photos/services/files_service.dart"; -import "package:photos/services/wake_lock_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/common/loading_widget.dart"; -import "package:photos/ui/notification/toast.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"; - -class VideoWidgetMediaKitNew extends StatefulWidget { - final EnteFile file; - final String? tagPrefix; - final Function(bool)? playbackCallback; - final bool isFromMemories; - final void Function() onStreamChange; - final File? preview; - final bool selectedPreview; - - const VideoWidgetMediaKitNew( - this.file, { - this.tagPrefix, - this.playbackCallback, - this.isFromMemories = false, - required this.onStreamChange, - this.preview, - required this.selectedPreview, - super.key, - }); - - @override - State createState() => _VideoWidgetMediaKitNewState(); -} - -class _VideoWidgetMediaKitNewState 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; - StreamSubscription? _streamSwitchedSubscription; - StreamSubscription? _downloadTaskSubscription; - late final StreamSubscription - _captionUpdatedSubscription; - - @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); - - if (widget.selectedPreview) { - loadPreview(); - } else { - loadOriginal(); - } - - pauseVideoSubscription = Bus.instance.on().listen((event) { - player.pause(); - }); - _guestViewEventSubscription = - Bus.instance.on().listen((event) { - setState(() { - _isGuestView = event.isGuestView; - }); - }); - if (widget.file.isUploaded) { - _downloadTaskSubscription = downloadManager - .watchDownload(widget.file.uploadedFileID!) - .listen((event) { - if (mounted) { - setState(() { - _progressNotifier.value = event.progress; - }); - } - }); - } - - _streamSwitchedSubscription = - Bus.instance.on().listen((event) { - if (event.type != PlayerType.mediaKit || !mounted) return; - if (event.selectedPreview) { - loadPreview(); - } else { - loadOriginal(); - } - }); - - _captionUpdatedSubscription = - Bus.instance.on().listen((event) { - if (event.fileGeneratedID == widget.file.generatedID) { - if (mounted) { - setState(() {}); - } - } - }); - } - - void loadPreview() { - _setVideoController(widget.preview!.path); - } - - void loadOriginal() { - if (widget.file.isRemoteFile) { - _loadNetworkVideo(); - _setFileSizeIfNull(); - } else if (widget.file.isSharedMediaToAppSandbox) { - final localFile = File(getSharedMediaFilePath(widget.file)); - if (localFile.existsSync()) { - _setVideoController(localFile.path); - } else if (widget.file.uploadedFileID != null) { - _loadNetworkVideo(); - } - } else { - widget.file.getAsset.then((asset) async { - if (asset == null || !(await asset.exists)) { - if (widget.file.uploadedFileID != null) { - _loadNetworkVideo(); - } - } else { - // ignore: unawaited_futures - asset.getMediaUrl().then((url) { - _setVideoController( - url ?? - 'https://user-images.githubusercontent.com/28951144/229373695-22f88f13-d18f-4288-9bf1-c3e078d83722.mp4', - ); - }); - } - }); - } - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - if (state == AppLifecycleState.resumed) { - _isAppInFG = true; - } else { - _isAppInFG = false; - } - } - - @override - void dispose() { - _streamSwitchedSubscription?.cancel(); - _guestViewEventSubscription.cancel(); - pauseVideoSubscription.cancel(); - removeCallBack(widget.file); - _progressNotifier.dispose(); - WidgetsBinding.instance.removeObserver(this); - if (_downloadTaskSubscription != null) { - _downloadTaskSubscription!.cancel(); - downloadManager.pause(widget.file.uploadedFileID!).ignore(); - } - player.dispose(); - _captionUpdatedSubscription.cancel(); - if (EnteWakeLockService.instance.shouldKeepAppAwakeAcrossSessions) { - EnteWakeLockService.instance.updateWakeLock( - enable: true, - wakeLockFor: WakeLockFor.handlingMediaKitEdgeCase, - ); - } - 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, - isPreviewPlayer: widget.selectedPreview, - ) - : Center( - child: ValueListenableBuilder( - valueListenable: _progressNotifier, - builder: (BuildContext context, double? progress, _) { - return progress == null || progress == 1 - ? const EnteLoadingWidget( - size: 32, - color: fillBaseDark, - padding: 0, - ) - : Stack( - children: [ - CircularProgressIndicator( - backgroundColor: Colors.transparent, - value: progress, - valueColor: const AlwaysStoppedAnimation( - Color.fromRGBO(45, 194, 98, 1.0), - ), - strokeWidth: 2, - strokeCap: StrokeCap.round, - ), - if (flagService.internalUser) - Center( - child: Text( - "${(progress * 100).toStringAsFixed(0)}%", - style: - getEnteTextTheme(context).tiny.copyWith( - color: textBaseDark, - ), - ), - ), - ], - ); - }, - ), - ), - ), - ); - } - - void _loadNetworkVideo() { - getFileFromServer( - widget.file, - progressCallback: (count, total) { - if (!mounted) { - return; - } - _progressNotifier.value = count / (widget.file.fileSize ?? total); - if (_progressNotifier.value == 1) { - if (mounted) { - showShortToast(context, S.of(context).decryptingVideo); - } - } - }, - ).then((file) { - if (file != null) { - _setVideoController(file.path); - } - }).onError((error, stackTrace) { - showErrorDialog( - context, - S.of(context).error, - S.of(context).failedToDownloadVideo, - ); - }); - } - - void _setFileSizeIfNull() { - if (widget.file.fileSize == null && widget.file.canEditMetaInfo) { - FilesService.instance - .getFileSize(widget.file.uploadedFileID!) - .then((value) { - widget.file.fileSize = value; - if (mounted) { - setState(() {}); - } - }); - } - } - - void _setVideoController(String url) { - if (mounted) { - setState(() { - if (controller == null) { - player.setPlaylistMode( - localSettings.shouldLoopVideo() - ? PlaylistMode.single - : PlaylistMode.none, - ); - controller = VideoController(player); - } - player.open(Media(url), play: _isAppInFG); - }); - } - } -} diff --git a/mobile/lib/utils/file_uploader.dart b/mobile/lib/utils/file_uploader.dart index 2ea8cf550d..6c16c14859 100644 --- a/mobile/lib/utils/file_uploader.dart +++ b/mobile/lib/utils/file_uploader.dart @@ -54,6 +54,7 @@ class FileUploader { static const kMaximumThumbnailCompressionAttempts = 2; static const kMaximumUploadAttempts = 4; static const kMaxFileSize5Gib = 5368709120; + static const kMaxFileSize10Gib = 10737418240; static const kBlockedUploadsPollFrequency = Duration(seconds: 2); static const kFileUploadTimeout = Duration(minutes: 50); static const k20MBStorageBuffer = 20 * 1024 * 1024; @@ -1086,10 +1087,12 @@ class FileUploader { 'freeStorage $freeStorage'); throw StorageLimitExceededError(); } - if (fileSize > kMaxFileSize5Gib) { - _logger.warning('File size exceeds 5GiB fileSize $fileSize'); + final int maxSize = + flagService.internalUser ? kMaxFileSize10Gib : kMaxFileSize5Gib; + if (fileSize > maxSize) { + _logger.warning('File size exceeds $maxSize fileSize $fileSize'); throw InvalidFileError( - 'file size above 5GiB', + 'file size above $maxSize', InvalidReason.tooLargeFile, ); }