[mob] streaming patches (#5122)
## Description Quality of Life fixes: - [x] Queue fixes - [x] Android Impeller fix - [x] No video_player_media_kit proxy, just using media_kit directory Quality of Dev fixes: - [x] Use master branch of media_kit - [x] extract common functions from native player and media kit for seconds to duration.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<void> _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");
|
||||
|
||||
@@ -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<void> 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);
|
||||
|
||||
@@ -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<PreviewVideoWidget> createState() => _PreviewVideoWidgetState();
|
||||
}
|
||||
|
||||
class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
|
||||
final _logger = Logger("PreviewVideoWidget");
|
||||
VideoPlayerController? _videoPlayerController;
|
||||
ChewieController? _chewieController;
|
||||
final _progressNotifier = ValueNotifier<double?>(null);
|
||||
bool _isPlaying = false;
|
||||
final EnteWakeLock _wakeLock = EnteWakeLock();
|
||||
bool _isFileSwipeLocked = false;
|
||||
late final StreamSubscription<GuestViewEvent> _fileSwipeLockEventSubscription;
|
||||
File? previewFile;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_checkForPreview();
|
||||
_fileSwipeLockEventSubscription =
|
||||
Bus.instance.on<GuestViewEvent>().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<void> _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>(
|
||||
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<void> _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!),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<StatefulWidget> createState() {
|
||||
return _VideoControlsState();
|
||||
}
|
||||
}
|
||||
|
||||
class _VideoControlsState extends State<VideoControls>
|
||||
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<PlayerNotifier>(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: <Widget>[
|
||||
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<void> _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<double> 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<VideoWidget> {
|
||||
?.containsKey(widget.file.uploadedFileID!) ??
|
||||
false);
|
||||
if (isPreviewVideoPlayable && selectPreviewForPlay) {
|
||||
return PreviewVideoWidget(
|
||||
return VideoWidgetMediaKitPreview(
|
||||
widget.file,
|
||||
tagPrefix: widget.tagPrefix,
|
||||
playbackCallback: widget.playbackCallback,
|
||||
|
||||
458
mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart
Normal file
458
mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart
Normal file
@@ -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<VideoWidget> createState() => _VideoWidgetState();
|
||||
}
|
||||
|
||||
class _VideoWidgetState extends State<VideoWidget> {
|
||||
final showControlsNotifier = ValueNotifier<bool>(true);
|
||||
static const verticalMargin = 72.0;
|
||||
final _hideControlsDebouncer = Debouncer(
|
||||
const Duration(milliseconds: 2000),
|
||||
);
|
||||
final _isSeekingNotifier = ValueNotifier<bool>(false);
|
||||
late final StreamSubscription<bool> _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<PlayPauseButtonMediaKit> createState() => _PlayPauseButtonState();
|
||||
}
|
||||
|
||||
class _PlayPauseButtonState extends State<PlayPauseButtonMediaKit> {
|
||||
bool _isPlaying = true;
|
||||
late final StreamSubscription<bool>? 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<double> 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<bool> 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<bool> isSeekingNotifier;
|
||||
const SeekBar(
|
||||
this.controller,
|
||||
this.isSeekingNotifier, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SeekBar> createState() => _SeekBarState();
|
||||
}
|
||||
|
||||
class _SeekBarState extends State<SeekBar> {
|
||||
double _sliderValue = 0.0;
|
||||
late final StreamSubscription<Duration> _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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<VideoWidgetMediaKitNew>
|
||||
},
|
||||
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>(
|
||||
// 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<VideoWidgetMediaKitNew>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<bool>(true);
|
||||
static const verticalMargin = 72.0;
|
||||
final _hideControlsDebouncer = Debouncer(
|
||||
const Duration(milliseconds: 2000),
|
||||
);
|
||||
final _isSeekingNotifier = ValueNotifier<bool>(false);
|
||||
late final StreamSubscription<bool> _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<PlayPauseButtonMediaKit> createState() => _PlayPauseButtonState();
|
||||
}
|
||||
|
||||
class _PlayPauseButtonState extends State<PlayPauseButtonMediaKit> {
|
||||
bool _isPlaying = true;
|
||||
late final StreamSubscription<bool>? 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<double> 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<bool> 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<bool> isSeekingNotifier;
|
||||
const _SeekBar(
|
||||
this.controller,
|
||||
this.isSeekingNotifier,
|
||||
);
|
||||
|
||||
@override
|
||||
State<_SeekBar> createState() => _SeekBarState();
|
||||
}
|
||||
|
||||
class _SeekBarState extends State<_SeekBar> {
|
||||
double _sliderValue = 0.0;
|
||||
late final StreamSubscription<Duration> _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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
172
mobile/lib/ui/viewer/file/video_widget_media_kit_preview.dart
Normal file
172
mobile/lib/ui/viewer/file/video_widget_media_kit_preview.dart
Normal file
@@ -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<VideoWidgetMediaKitPreview> createState() =>
|
||||
_VideoWidgetMediaKitPreviewState();
|
||||
}
|
||||
|
||||
class _VideoWidgetMediaKitPreviewState extends State<VideoWidgetMediaKitPreview>
|
||||
with WidgetsBindingObserver {
|
||||
final Logger _logger = Logger("VideoWidgetMediaKitNew");
|
||||
late final player = Player();
|
||||
VideoController? controller;
|
||||
final _progressNotifier = ValueNotifier<double?>(null);
|
||||
bool _isAppInFG = true;
|
||||
late StreamSubscription<PauseVideoEvent> pauseVideoSubscription;
|
||||
bool isGuestView = false;
|
||||
late final StreamSubscription<GuestViewEvent> _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<PauseVideoEvent>().listen((event) {
|
||||
player.pause();
|
||||
});
|
||||
_guestViewEventSubscription =
|
||||
Bus.instance.on<GuestViewEvent>().listen((event) {
|
||||
setState(() {
|
||||
_isGuestView = event.isGuestView;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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')}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user