diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index f460ab13dc..5eb373e6de 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -10,12 +10,12 @@ PODS: - Flutter - device_info_plus (0.0.1): - Flutter - - ffmpeg-kit-ios-min (6.0) - - ffmpeg_kit_flutter_min (6.0.3): - - ffmpeg_kit_flutter_min/min (= 6.0.3) + - ffmpeg-kit-ios-full-gpl (6.0) + - ffmpeg_kit_flutter_full_gpl (6.0.3): + - ffmpeg_kit_flutter_full_gpl/full-gpl (= 6.0.3) - Flutter - - ffmpeg_kit_flutter_min/min (6.0.3): - - ffmpeg-kit-ios-min (= 6.0) + - ffmpeg_kit_flutter_full_gpl/full-gpl (6.0.3): + - ffmpeg-kit-ios-full-gpl (= 6.0) - Flutter - file_saver (0.0.1): - Flutter @@ -232,6 +232,8 @@ PODS: - Flutter - url_launcher_ios (0.0.1): - Flutter + - video_compress (0.3.0): + - Flutter - video_player_avfoundation (0.0.1): - Flutter - FlutterMacOS @@ -249,7 +251,7 @@ DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) - dart_ui_isolate (from `.symlinks/plugins/dart_ui_isolate/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - - ffmpeg_kit_flutter_min (from `.symlinks/plugins/ffmpeg_kit_flutter_min/ios`) + - ffmpeg_kit_flutter_full_gpl (from `.symlinks/plugins/ffmpeg_kit_flutter_full_gpl/ios`) - file_saver (from `.symlinks/plugins/file_saver/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) @@ -296,6 +298,7 @@ DEPENDENCIES: - ua_client_hints (from `.symlinks/plugins/ua_client_hints/ios`) - uni_links (from `.symlinks/plugins/uni_links/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - video_compress (from `.symlinks/plugins/video_compress/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`) - volume_controller (from `.symlinks/plugins/volume_controller/ios`) @@ -303,7 +306,7 @@ DEPENDENCIES: SPEC REPOS: trunk: - - ffmpeg-kit-ios-min + - ffmpeg-kit-ios-full-gpl - Firebase - FirebaseCore - FirebaseCoreInternal @@ -335,8 +338,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/dart_ui_isolate/ios" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" - ffmpeg_kit_flutter_min: - :path: ".symlinks/plugins/ffmpeg_kit_flutter_min/ios" + ffmpeg_kit_flutter_full_gpl: + :path: ".symlinks/plugins/ffmpeg_kit_flutter_full_gpl/ios" file_saver: :path: ".symlinks/plugins/file_saver/ios" firebase_core: @@ -429,6 +432,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/uni_links/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" + video_compress: + :path: ".symlinks/plugins/video_compress/ios" video_player_avfoundation: :path: ".symlinks/plugins/video_player_avfoundation/darwin" video_thumbnail: @@ -444,8 +449,8 @@ SPEC CHECKSUMS: connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db dart_ui_isolate: d5bcda83ca4b04f129d70eb90110b7a567aece14 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 - ffmpeg-kit-ios-min: 4e9a088f4ee9629435960b9d68e54848975f1931 - ffmpeg_kit_flutter_min: 5eff47f4965bf9d1150e98961eb6129f5ae3f28c + ffmpeg-kit-ios-full-gpl: 80adc341962e55ef709e36baa8ed9a70cf4ea62b + ffmpeg_kit_flutter_full_gpl: 8d15c14c0c3aba616fac04fe44b3d27d02e3c330 file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 Firebase: 98e6bf5278170668a7983e12971a66b2cd57fc8c firebase_core: 2bedc3136ec7c7b8561c6123ed0239387b53f2af @@ -511,6 +516,7 @@ SPEC CHECKSUMS: ua_client_hints: 46bb5817a868f9e397c0ba7e3f2f5c5d90c35156 uni_links: d97da20c7701486ba192624d99bffaaffcfc298a url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 video_thumbnail: c4e2a3c539e247d4de13cd545344fd2d26ffafd1 volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9 diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 86d7aa2535..ee6a1f6c0b 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -338,18 +338,19 @@ "${BUILT_PRODUCTS_DIR}/ua_client_hints/ua_client_hints.framework", "${BUILT_PRODUCTS_DIR}/uni_links/uni_links.framework", "${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework", + "${BUILT_PRODUCTS_DIR}/video_compress/video_compress.framework", "${BUILT_PRODUCTS_DIR}/video_player_avfoundation/video_player_avfoundation.framework", "${BUILT_PRODUCTS_DIR}/video_thumbnail/video_thumbnail.framework", "${BUILT_PRODUCTS_DIR}/volume_controller/volume_controller.framework", "${BUILT_PRODUCTS_DIR}/wakelock_plus/wakelock_plus.framework", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-min/ffmpegkit.framework/ffmpegkit", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-min/libavcodec.framework/libavcodec", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-min/libavdevice.framework/libavdevice", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-min/libavfilter.framework/libavfilter", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-min/libavformat.framework/libavformat", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-min/libavutil.framework/libavutil", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-min/libswresample.framework/libswresample", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-min/libswscale.framework/libswscale", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-full-gpl/ffmpegkit.framework/ffmpegkit", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-full-gpl/libavcodec.framework/libavcodec", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-full-gpl/libavdevice.framework/libavdevice", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-full-gpl/libavfilter.framework/libavfilter", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-full-gpl/libavformat.framework/libavformat", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-full-gpl/libavutil.framework/libavutil", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-full-gpl/libswresample.framework/libswresample", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-full-gpl/libswscale.framework/libswscale", "${PODS_XCFRAMEWORKS_BUILD_DIR}/media_kit_libs_ios_video/Ass.framework/Ass", "${PODS_XCFRAMEWORKS_BUILD_DIR}/media_kit_libs_ios_video/Avcodec.framework/Avcodec", "${PODS_XCFRAMEWORKS_BUILD_DIR}/media_kit_libs_ios_video/Avfilter.framework/Avfilter", @@ -433,6 +434,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ua_client_hints.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/uni_links.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_compress.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_player_avfoundation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_thumbnail.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/volume_controller.framework", diff --git a/mobile/lib/db/ml/db_fields.dart b/mobile/lib/db/ml/db_fields.dart index 06f30ae9fd..5968215cf5 100644 --- a/mobile/lib/db/ml/db_fields.dart +++ b/mobile/lib/db/ml/db_fields.dart @@ -3,6 +3,7 @@ import 'package:photos/services/machine_learning/face_ml/face_filtering/face_fil const facesTable = 'faces'; const fileIDColumn = 'file_id'; +const objectIdColumn = 'obj_id'; const faceIDColumn = 'face_id'; const faceDetectionColumn = 'detection'; const embeddingColumn = 'embedding'; @@ -114,7 +115,7 @@ CREATE TABLE IF NOT EXISTS $fileDataTable ( user_id INTEGER NOT NULL, type TEXT NOT NULL, size INTEGER NOT NULL, - obj_id TEXT, + $objectIdColumn TEXT, obj_nonce TEXT, updated_at INTEGER NOT NULL, PRIMARY KEY ($fileIDColumn, type) diff --git a/mobile/lib/db/ml/filedata.dart b/mobile/lib/db/ml/filedata.dart index 477aca8b40..3ecb440a0e 100644 --- a/mobile/lib/db/ml/filedata.dart +++ b/mobile/lib/db/ml/filedata.dart @@ -26,6 +26,22 @@ extension FileDataTable on MLDataDB { ); } + Future> getFileIDsVidPreview() async { + final db = await MLDataDB.instance.asyncDB; + final res = await db.execute( + "SELECT $fileIDColumn, $objectIdColumn, size FROM $fileDataTable WHERE type='vid_preview'", + ); + return res.asMap().map( + (i, e) => MapEntry( + e[fileIDColumn] as int, + PreviewInfo( + objectId: e[objectIdColumn] as String, + objectSize: e['size'] as int, + ), + ), + ); + } + Future> getFileIDsWithFDData() async { final db = await MLDataDB.instance.asyncDB; final res = await db.execute('SELECT $fileIDColumn FROM $fileDataTable'); diff --git a/mobile/lib/l10n/intl_de.arb b/mobile/lib/l10n/intl_de.arb index 7b55f20b12..93e9ab29a0 100644 --- a/mobile/lib/l10n/intl_de.arb +++ b/mobile/lib/l10n/intl_de.arb @@ -817,7 +817,7 @@ "referFriendsAnd2xYourPlan": "Begeistere Freunde für uns und verdopple deinen Speicher", "shareAlbumHint": "Öffne ein Album und tippe auf den Teilen-Button oben rechts, um zu teilen.", "itemsShowTheNumberOfDaysRemainingBeforePermanentDeletion": "Elemente zeigen die Anzahl der Tage bis zum dauerhaften Löschen an", - "trashDaysLeft": "{count, plural, one {}=0 {Demnächst} =1 {1 Tag} other {{count} Tage}}", + "trashDaysLeft": "{count, plural, =0 {Demnächst} =1 {1 Tag} other {{count} Tage}}", "@trashDaysLeft": { "description": "Text to indicate number of days remaining before permanent deletion", "placeholders": { @@ -1384,7 +1384,7 @@ "enableMachineLearningBanner": "Aktiviere maschinelles Lernen für die magische Suche und Gesichtserkennung", "searchDiscoverEmptySection": "Bilder werden hier angezeigt, sobald die Verarbeitung abgeschlossen ist", "searchPersonsEmptySection": "Personen werden hier angezeigt, sobald die Verarbeitung abgeschlossen ist", - "viewersSuccessfullyAdded": "{count, plural, one {}=0 {0 Betrachter hinzugefügt} =1 {1 Betrachter hinzugefügt} other {{count} Betrachter hinzugefügt}}", + "viewersSuccessfullyAdded": "{count, plural, =0 {0 Betrachter hinzugefügt} one {1 Betrachter hinzugefügt} other {{count} Betrachter hinzugefügt}}", "@viewersSuccessfullyAdded": { "placeholders": { "count": { @@ -1394,7 +1394,7 @@ }, "description": "Number of viewers that were successfully added to an album." }, - "collaboratorsSuccessfullyAdded": "{count, plural, one {}=0 {0 Mitarbeiter hinzugefügt} =1 {1 Mitarbeiter hinzugefügt} other {{count} Mitarbeiter hinzugefügt}}", + "collaboratorsSuccessfullyAdded": "{count, plural, =0 {0 Mitarbeiter hinzugefügt} =1 {1 Mitarbeiter hinzugefügt} other {{count} Mitarbeiter hinzugefügt}}", "@collaboratorsSuccessfullyAdded": { "placeholders": { "count": { @@ -1469,7 +1469,7 @@ }, "currentlyRunning": "läuft gerade", "ignored": "ignoriert", - "photosCount": "{count, plural, one {}=0 {0 Fotos} =1 {1 Foto} other {{count} Fotos}}", + "photosCount": "{count, plural, one {1 Foto} =0 {0 Fotos} =1 {1 Foto} other {{count} Fotos}}", "@photosCount": { "placeholders": { "count": { diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index f44a3d799f..3ca16861e7 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -39,6 +39,7 @@ import 'package:photos/services/machine_learning/ml_service.dart'; import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart'; import 'package:photos/services/memories_service.dart'; import "package:photos/services/notification_service.dart"; +import "package:photos/services/preview_video_store.dart"; import 'package:photos/services/push_service.dart'; import 'package:photos/services/remote_sync_service.dart'; import 'package:photos/services/search_service.dart'; @@ -51,6 +52,7 @@ 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"); @@ -236,6 +238,10 @@ Future _init(bool isBackground, {String via = ''}) async { ServiceLocator.instance .init(preferences, NetworkClient.instance.enteDio, packageInfo); + if (!isBackground && flagService.internalUser) { + VideoPlayerMediaKit.ensureInitialized(iOS: true); + } + _logger.info("UserService init $tlog"); await UserService.instance.init(); _logger.info("UserService init done $tlog"); @@ -245,6 +251,10 @@ Future _init(bool isBackground, {String via = ''}) async { _logger.info("CollectionsService init done $tlog"); FavoritesService.instance.initFav().ignore(); + MemoriesService.instance.init(preferences); + LocalFileUpdateService.instance.init(preferences); + SearchService.instance.init(); + FileDataService.instance.init(preferences); _logger.info("FileUploader init $tlog"); await FileUploader.instance.init(preferences, isBackground); @@ -260,10 +270,6 @@ Future _init(bool isBackground, {String via = ''}) async { await SyncService.instance.init(preferences); _logger.info("SyncService init done $tlog"); - MemoriesService.instance.init(preferences); - LocalFileUpdateService.instance.init(preferences); - SearchService.instance.init(); - FileDataService.instance.init(preferences); _logger.info("RemoteFileMLService done $tlog"); if (!isBackground && Platform.isAndroid && @@ -280,6 +286,7 @@ Future _init(bool isBackground, {String via = ''}) async { }); } _logger.info("PushService/HomeWidget done $tlog"); + PreviewVideoStore.instance.init(); unawaited(SemanticSearchService.instance.init()); unawaited(MLService.instance.init()); PersonService.init( diff --git a/mobile/lib/models/base/id.dart b/mobile/lib/models/base/id.dart index b8e5647a0a..2b095e0274 100644 --- a/mobile/lib/models/base/id.dart +++ b/mobile/lib/models/base/id.dart @@ -8,6 +8,10 @@ String newClusterID() { return "cluster_${customAlphabet(enteWhiteListedAlphabet, clusterIDLength)}"; } +String newID(String prefix) { + return "${prefix}_${customAlphabet(enteWhiteListedAlphabet, clusterIDLength)}"; +} + String newIsolateTaskID(String task) { return "${task}_${customAlphabet(enteWhiteListedAlphabet, clusterIDLength)}"; } diff --git a/mobile/lib/models/metadata/file_magic.dart b/mobile/lib/models/metadata/file_magic.dart index 7599b7c82f..5326d52fab 100644 --- a/mobile/lib/models/metadata/file_magic.dart +++ b/mobile/lib/models/metadata/file_magic.dart @@ -14,6 +14,7 @@ const latKey = "lat"; const longKey = "long"; const motionVideoIndexKey = "mvi"; const noThumbKey = "noThumb"; +const previewVersionKey = "previewVersion"; class MagicMetadata { // 0 -> visible diff --git a/mobile/lib/services/filedata/filedata_service.dart b/mobile/lib/services/filedata/filedata_service.dart index cc629ed0fe..96d9668c4f 100644 --- a/mobile/lib/services/filedata/filedata_service.dart +++ b/mobile/lib/services/filedata/filedata_service.dart @@ -23,6 +23,7 @@ class FileDataService { final _logger = Logger("FileDataService"); final _dio = NetworkClient.instance.enteDio; late final SharedPreferences _prefs; + Map? previewIds = {}; void init(SharedPreferences prefs) { _prefs = prefs; @@ -136,6 +137,7 @@ class FileDataService { await _prefs.setInt("fd.lastSyncTime", maxUpdatedAt); _logger.info('found ${result.length} fd entries'); hasMoreData = result.isNotEmpty; + previewIds = await MLDataDB.instance.getFileIDsVidPreview(); } while (hasMoreData); } catch (e) { _logger.severe("Failed to syncDiff", e); diff --git a/mobile/lib/services/filedata/model/file_data.dart b/mobile/lib/services/filedata/model/file_data.dart index 4e479bdc7d..717eeba2dd 100644 --- a/mobile/lib/services/filedata/model/file_data.dart +++ b/mobile/lib/services/filedata/model/file_data.dart @@ -189,3 +189,14 @@ class FDStatus { ); } } + +class PreviewInfo { + final String objectId; + final int objectSize; + String? nonce; + PreviewInfo({ + required this.objectId, + required this.objectSize, + this.nonce, + }); +} diff --git a/mobile/lib/services/preview_video_store.dart b/mobile/lib/services/preview_video_store.dart new file mode 100644 index 0000000000..c416c04127 --- /dev/null +++ b/mobile/lib/services/preview_video_store.dart @@ -0,0 +1,328 @@ +import "dart:async"; +import "dart:io"; + +import "package:dio/dio.dart"; +import "package:encrypt/encrypt.dart" as enc; +import "package:ffmpeg_kit_flutter_full_gpl/ffmpeg_kit.dart"; +import "package:ffmpeg_kit_flutter_full_gpl/return_code.dart"; +import "package:flutter/foundation.dart"; +// import "package:flutter/wid.dart"; +import "package:flutter/widgets.dart"; +import "package:flutter_cache_manager/flutter_cache_manager.dart"; +import "package:logging/logging.dart"; +import "package:path_provider/path_provider.dart"; +import "package:photos/core/cache/video_cache_manager.dart"; +import "package:photos/core/configuration.dart"; +import "package:photos/core/network/network.dart"; +import "package:photos/models/base/id.dart"; +import "package:photos/models/file/file.dart"; +import "package:photos/models/file/file_type.dart"; +import "package:photos/services/filedata/filedata_service.dart"; +import "package:photos/utils/file_key.dart"; +import "package:photos/utils/file_util.dart"; +import "package:photos/utils/gzip.dart"; +import "package:photos/utils/toast_util.dart"; +import "package:video_compress/video_compress.dart"; + +class PreviewVideoStore { + PreviewVideoStore._privateConstructor(); + + static final PreviewVideoStore instance = + PreviewVideoStore._privateConstructor(); + + final _logger = Logger("PreviewVideoStore"); + final cacheManager = DefaultCacheManager(); + final videoCacheManager = VideoCacheManager.instance; + double _progress = 0; + + final _dio = NetworkClient.instance.enteDio; + void init() { + VideoCompress.compressProgress$.subscribe((progress) { + if (kDebugMode) { + _progress = progress; + _logger.info("Compression progress: $progress"); + } + }); + } + + List get qualities => VideoQuality.values; + + Future chunkAndUploadVideo(BuildContext ctx, EnteFile enteFile) async { + if (!enteFile.isUploaded) return; + final file = await getFile(enteFile, isOrigin: true); + if (file == null) return; + try { + // check if playlist already exist + await getPlaylist(enteFile); + final resultUrl = await getPreviewUrl(enteFile); + if (ctx.mounted) { + showShortToast(ctx, 'Video preview already exists'); + } + debugPrint("previewUrl $resultUrl"); + return; + } catch (e, s) { + if (e is DioError && e.response?.statusCode == 404) { + _logger.info("No preview found for $enteFile"); + } else { + _logger.warning("Failed to get playlist for $enteFile", e, s); + rethrow; + } + } + if (VideoCompress.isCompressing) { + showShortToast( + ctx, + "Another is being compressed ($_progress %), please wait", + ); + return; + } + final String tempDir = Configuration.instance.getTempDirectory(); + final String prefix = + "${tempDir}_${enteFile.uploadedFileID}_${newID("pv")}"; + Directory(prefix).createSync(); + _logger.info('Compressing video ${enteFile.displayName}'); + final mediaInfo = await VideoCompress.compressVideo( + file.path, + quality: VideoQuality.MediumQuality, + ); + if (mediaInfo?.path == null) return; + _logger.info('CompressionDone ${enteFile.displayName}'); + + final key = enc.Key.fromLength(16); + + final keyfile = File('$prefix/keyfile.key'); + keyfile.writeAsBytesSync(key.bytes); + + final keyinfo = File('$prefix/mykey.keyinfo'); + keyinfo.writeAsStringSync("data:text/plain;base64,${key.base64}\n" + "${keyfile.path}\n"); + _logger.info( + 'Generating HLS Playlist ${enteFile.displayName} at $prefix/output.m3u8}', + ); + final session = await FFmpegKit.execute( + '-i "${mediaInfo!.path}" ' + '-c copy -f hls -hls_time 10 -hls_flags single_file ' + '-hls_list_size 0 -hls_key_info_file ${keyinfo.path} ' + '$prefix/output.m3u8', + ); + + // final session = await FFmpegKit.execute('-i "${file.path}" ' + // // Video encoding settings + // '-c:v libx264 ' // Use H.264 codec + // '-preset medium ' // Encoding speed preset + // '-crf 23 ' // Quality setting (lower = better quality, 23 is a good balance) + // '-profile:v main ' // H.264 profile for better compatibility + // '-level:v 4.0 ' // H.264 level for device compatibility + // '-vf scale=-2:720 ' // Scale to 720p while maintaining aspect ratio + // // Audio encoding settings + // '-c:a aac ' // Use AAC audio codec + // '-b:a 128k ' // Audio bitrate + // // HLS specific settings + // '-f hls ' + // '-hls_time 10 ' + // '-hls_flags single_file ' + // '-hls_list_size 0 ' + // '-hls_key_info_file ${keyinfo.path} ' + // // Output + // '$prefix/output.m3u8'); + + final returnCode = await session.getReturnCode(); + + if (ReturnCode.isSuccess(returnCode)) { + _logger.info('Playlist Generated ${enteFile.displayName}'); + final playlistFile = File("$prefix/output.m3u8"); + final previewFile = File("$prefix/output.ts"); + final result = await _uploadPreviewVideo(enteFile, previewFile); + final String objectID = result.$1; + final objectSize = result.$2; + await _reportVideoPreview( + enteFile, + playlistFile, + objectID: objectID, + objectSize: objectSize, + ); + _logger.info("Video preview uploaded for $enteFile"); + } else if (ReturnCode.isCancel(returnCode)) { + _logger.warning("FFmpeg command cancelled"); + } else { + _logger.severe("FFmpeg command failed with return code $returnCode"); + if (kDebugMode) { + final output = await session.getOutput(); + _logger.severe(output); + } + } + } + + Future _reportVideoPreview( + EnteFile file, + File playlist, { + required String objectID, + required int objectSize, + }) async { + _logger.info("Pushing playlist for ${file.uploadedFileID}"); + try { + final encryptionKey = getFileKey(file); + final playlistContent = playlist.readAsStringSync(); + final result = await gzipAndEncryptJson( + { + "playlist": playlistContent, + 'type': 'hls_video', + }, + encryptionKey, + ); + final _ = await _dio.put( + "/files/video-data", + data: { + "fileID": file.uploadedFileID!, + "objectID": objectID, + "objectSize": objectSize, + "playlist": result.encData, + "playlistHeader": result.header, + }, + ); + } catch (e, s) { + _logger.severe("Failed to report video preview", e, s); + } + } + + Future<(String, int)> _uploadPreviewVideo(EnteFile file, File preview) async { + _logger.info("Pushing preview for $file"); + try { + final response = await _dio.get( + "/files/data/preview-upload-url", + queryParameters: { + "fileID": file.uploadedFileID!, + "type": "vid_preview", + }, + ); + final uploadURL = response.data["url"]; + final String objectID = response.data["objectID"]; + final objectSize = preview.lengthSync(); + final _ = await _dio.put( + uploadURL, + data: preview.openRead(), + options: Options( + headers: { + Headers.contentLengthHeader: objectSize, + }, + ), + ); + return (objectID, objectSize); + } catch (e) { + _logger.warning("failed to upload previewVideo", e); + rethrow; + } + } + + String _getCacheKey(String objectKey) { + return "video_playlist_$objectKey"; + } + + String _getVideoPreviewKey(String objectKey) { + return "video_preview_$objectKey"; + } + + Future getPlaylist(EnteFile file) async { + return await _getPlaylist(file); + } + + Future _getPlaylist(EnteFile file) async { + _logger.info("Getting playlist for $file"); + try { + final objectKey = + FileDataService.instance.previewIds![file.uploadedFileID!]?.objectId; + final FileInfo? playlistCache = (objectKey == null) + ? null + : await cacheManager.getFileFromCache(_getCacheKey(objectKey)); + String finalPlaylist; + if (playlistCache != null) { + finalPlaylist = playlistCache.file.readAsStringSync(); + } else { + final response = await _dio.get( + "/files/data/fetch/", + queryParameters: { + "fileID": file.uploadedFileID, + "type": "vid_preview", + }, + ); + final encryptedData = response.data["data"]["encryptedData"]; + final header = response.data["data"]["decryptionHeader"]; + final encryptionKey = getFileKey(file); + final playlistData = await decryptAndUnzipJson( + encryptionKey, + encryptedData: encryptedData, + header: header, + ); + finalPlaylist = playlistData["playlist"]; + if (objectKey != null) { + unawaited( + cacheManager.putFile( + _getCacheKey(objectKey), + Uint8List.fromList( + (playlistData["playlist"] as String).codeUnits, + ), + ), + ); + } + } + + final videoFile = objectKey == null + ? null + : (await videoCacheManager + .getFileFromCache(_getVideoPreviewKey(objectKey))) + ?.file; + if (videoFile == null) { + final response2 = await _dio.get( + "/files/data/preview", + queryParameters: { + "fileID": file.uploadedFileID, + "type": "vid_preview", + }, + ); + final previewURL = response2.data["url"]; + if (objectKey != null) { + unawaited( + downloadAndCacheVideo( + previewURL, + _getVideoPreviewKey(objectKey), + ), + ); + } + finalPlaylist = + finalPlaylist.replaceAll('\noutput.ts', '\n$previewURL'); + } else { + finalPlaylist = + finalPlaylist.replaceAll('\noutput.ts', '\n${videoFile.path}'); + } + + final tempDir = await getTemporaryDirectory(); + final playlistFile = File("${tempDir.path}/${file.uploadedFileID}.m3u8"); + await playlistFile.writeAsString(finalPlaylist); + _logger.info("Writing playlist to ${playlistFile.path}"); + return playlistFile; + } catch (_) { + rethrow; + } + } + + Future downloadAndCacheVideo(String url, String key) async { + final file = await videoCacheManager.downloadFile(url, key: key); + return file; + } + + Future getPreviewUrl(EnteFile file) async { + try { + final response = await _dio.get( + "/files/data/preview", + queryParameters: { + "fileID": file.uploadedFileID, + "type": + file.fileType == FileType.video ? "vid_preview" : "img_preview", + }, + ); + return response.data["url"]; + } catch (e) { + _logger.warning("Failed to get preview url", e); + rethrow; + } + } +} diff --git a/mobile/lib/services/sync_service.dart b/mobile/lib/services/sync_service.dart index 873270f349..2eaafe2545 100644 --- a/mobile/lib/services/sync_service.dart +++ b/mobile/lib/services/sync_service.dart @@ -18,6 +18,7 @@ import 'package:photos/events/sync_status_update_event.dart'; import 'package:photos/events/trigger_logout_event.dart'; import 'package:photos/models/backup_status.dart'; import 'package:photos/models/file/file_type.dart'; +import "package:photos/services/filedata/filedata_service.dart"; import "package:photos/services/files_service.dart"; import 'package:photos/services/local_sync_service.dart'; import 'package:photos/services/notification_service.dart'; @@ -86,6 +87,7 @@ class SyncService { _existingSync = Completer(); bool successful = false; try { + FileDataService.instance.syncFDStatus().ignore(); await _doSync(); if (_lastSyncStatusEvent != null && _lastSyncStatusEvent!.status != diff --git a/mobile/lib/ui/tabs/home_widget.dart b/mobile/lib/ui/tabs/home_widget.dart index 73ed33d428..adf1cf1476 100644 --- a/mobile/lib/ui/tabs/home_widget.dart +++ b/mobile/lib/ui/tabs/home_widget.dart @@ -475,7 +475,7 @@ class _HomeWidgetState extends State { .getInitialMedia() .then((List value) { if (mounted) { - if (value[0].path.contains("albums.ente.io")) { + if (value.isNotEmpty && value[0].path.contains("albums.ente.io")) { final uri = Uri.parse(value[0].path); _handlePublicAlbumLink(uri); return; diff --git a/mobile/lib/ui/tools/editor/export_video_service.dart b/mobile/lib/ui/tools/editor/export_video_service.dart index 7fe4ae767e..38d98514bd 100644 --- a/mobile/lib/ui/tools/editor/export_video_service.dart +++ b/mobile/lib/ui/tools/editor/export_video_service.dart @@ -1,11 +1,11 @@ import 'dart:developer'; import 'dart:io'; -import 'package:ffmpeg_kit_flutter_min/ffmpeg_kit.dart'; -import 'package:ffmpeg_kit_flutter_min/ffmpeg_kit_config.dart'; -import 'package:ffmpeg_kit_flutter_min/ffmpeg_session.dart'; -import 'package:ffmpeg_kit_flutter_min/return_code.dart'; -import 'package:ffmpeg_kit_flutter_min/statistics.dart'; +import 'package:ffmpeg_kit_flutter_full_gpl/ffmpeg_kit.dart'; +import 'package:ffmpeg_kit_flutter_full_gpl/ffmpeg_kit_config.dart'; +import 'package:ffmpeg_kit_flutter_full_gpl/ffmpeg_session.dart'; +import 'package:ffmpeg_kit_flutter_full_gpl/return_code.dart'; +import 'package:ffmpeg_kit_flutter_full_gpl/statistics.dart'; import 'package:video_editor/video_editor.dart'; class ExportService { diff --git a/mobile/lib/ui/viewer/file/file_app_bar.dart b/mobile/lib/ui/viewer/file/file_app_bar.dart index 37b79cbd68..96a5a25f7d 100644 --- a/mobile/lib/ui/viewer/file/file_app_bar.dart +++ b/mobile/lib/ui/viewer/file/file_app_bar.dart @@ -20,6 +20,7 @@ import "package:photos/service_locator.dart"; import 'package:photos/services/collections_service.dart'; import 'package:photos/services/hidden_service.dart'; import "package:photos/services/local_authentication_service.dart"; +import "package:photos/services/preview_video_store.dart"; import "package:photos/theme/ente_theme.dart"; import 'package:photos/ui/collections/collection_action_sheet.dart'; import 'package:photos/ui/viewer/file/custom_app_bar.dart'; @@ -299,6 +300,25 @@ class FileAppBarState extends State { ); } } + if (flagService.internalUser) { + items.add( + PopupMenuItem( + value: 99, + child: Row( + children: [ + Icon( + Icons.video_collection, + color: Theme.of(context).iconTheme.color, + ), + const Padding( + padding: EdgeInsets.all(8), + ), + const Text("Cache Preview"), + ], + ), + ), + ); + } items.add( PopupMenuItem( value: 6, @@ -379,6 +399,17 @@ class FileAppBarState extends State { await _handleUnHideRequest(context); } else if (value == 6) { await _onTapGuestView(); + } else if (value == 99) { + try { + await PreviewVideoStore.instance.chunkAndUploadVideo( + context, + widget.file, + ); + } catch (e) { + if (mounted) { + await showGenericErrorDialog(context: context, error: e); + } + } } else if (value == 7) { _onToggleLoopVideo(); } diff --git a/mobile/lib/ui/viewer/file/file_widget.dart b/mobile/lib/ui/viewer/file/file_widget.dart index 9cae3d328f..165f84e627 100644 --- a/mobile/lib/ui/viewer/file/file_widget.dart +++ b/mobile/lib/ui/viewer/file/file_widget.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:photos/models/file/file.dart'; import 'package:photos/models/file/file_type.dart'; +import "package:photos/service_locator.dart"; +import "package:photos/services/filedata/filedata_service.dart"; +import "package:photos/ui/viewer/file/preview_video_widget.dart"; import "package:photos/ui/viewer/file/video_widget.dart"; import "package:photos/ui/viewer/file/zoomable_live_image_new.dart"; @@ -38,6 +41,19 @@ class FileWidget extends StatelessWidget { key: key ?? ValueKey(fileKey), ); } else if (file.fileType == FileType.video) { + if (file.isUploaded && + flagService.internalUser && + (FileDataService.instance.previewIds + ?.containsKey(file.uploadedFileID!) ?? + false)) { + return PreviewVideoWidget( + file, + tagPrefix: tagPrefix, + playbackCallback: playbackCallback, + key: key ?? ValueKey(fileKey), + ); + } + // use old video widget on iOS simulator as the new one crashes while // playing certain videos on iOS simulator // if (kDebugMode && Platform.isIOS) { diff --git a/mobile/lib/ui/viewer/file/preview_video_widget.dart b/mobile/lib/ui/viewer/file/preview_video_widget.dart new file mode 100644 index 0000000000..f1dfe8e7f2 --- /dev/null +++ b/mobile/lib/ui/viewer/file/preview_video_widget.dart @@ -0,0 +1,257 @@ +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; + + const PreviewVideoWidget( + this.file, { + this.autoPlay = false, + this.tagPrefix, + this.playbackCallback, + super.key, + }); + + @override + State createState() => _PreviewVideoWidgetState(); +} + +class _PreviewVideoWidgetState extends State { + final _logger = Logger("PreviewVideoWidget"); + VideoPlayerController? _videoPlayerController; + ChewieController? _chewieController; + final _progressNotifier = ValueNotifier(null); + bool _isPlaying = false; + final EnteWakeLock _wakeLock = EnteWakeLock(); + bool _isFileSwipeLocked = false; + late final StreamSubscription _fileSwipeLockEventSubscription; + File? previewFile; + + @override + void initState() { + super.initState(); + + _checkForPreview(); + _fileSwipeLockEventSubscription = + Bus.instance.on().listen((event) { + setState(() { + _isFileSwipeLocked = event.swipeLocked; + }); + }); + } + + @override + void dispose() { + _fileSwipeLockEventSubscription.cancel(); + removeCallBack(widget.file); + _videoPlayerController?.dispose(); + _chewieController?.dispose(); + _progressNotifier.dispose(); + _wakeLock.dispose(); + super.dispose(); + } + + Future _checkForPreview() async { + final file = 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 (file != null) { + 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 = file; + _setVideoPlayerController(); + } + } + + void _setVideoPlayerController() { + if (!mounted) { + // Note: Do not initiale video player if widget is not mounted. + // On Android, if multiple instance of ExoPlayer is created, it will start + // resulting in playback errors for videos. See https://github.com/google/ExoPlayer/issues/6168 + return; + } + VideoPlayerController videoPlayerController; + videoPlayerController = VideoPlayerController.file(previewFile!); + + debugPrint("videoPlayerController: $videoPlayerController"); + _videoPlayerController = videoPlayerController + ..initialize().whenComplete(() { + if (mounted) { + setState(() {}); + } + }).onError( + (error, stackTrace) { + if (mounted && flagService.internalUser) { + if (error is Exception) { + showErrorDialogForException( + context: context, + exception: error, + message: "Failed to play video\n ${error.toString()}", + ); + } else { + showToast(context, "Failed to play video"); + } + } + }, + ); + } + + @override + Widget build(BuildContext context) { + final content = _videoPlayerController != null && + _videoPlayerController!.value.isInitialized + ? _getVideoPlayer() + : _getLoadingWidget(); + final contentWithDetector = GestureDetector( + onVerticalDragUpdate: _isFileSwipeLocked + ? null + : (d) => { + if (d.delta.dy > dragSensitivity) + { + Navigator.of(context).pop(), + } + else if (d.delta.dy < (dragSensitivity * -1)) + { + showDetailsSheet(context, widget.file), + }, + }, + child: content, + ); + return VisibilityDetector( + key: Key(widget.file.tag), + onVisibilityChanged: (info) { + if (info.visibleFraction < 1) { + if (mounted && _chewieController != null) { + _chewieController!.pause(); + } + } + }, + child: Hero( + tag: widget.tagPrefix! + widget.file.tag, + child: contentWithDetector, + ), + ); + } + + Widget _getLoadingWidget() { + return Stack( + children: [ + _getThumbnail(), + Container( + color: Colors.black12, + constraints: const BoxConstraints.expand(), + ), + Center( + child: SizedBox.fromSize( + size: const Size.square(20), + child: ValueListenableBuilder( + valueListenable: _progressNotifier, + builder: (BuildContext context, double? progress, _) { + return progress == null || progress == 1 + ? const CupertinoActivityIndicator( + color: Colors.white, + ) + : CircularProgressIndicator( + backgroundColor: Colors.black, + value: progress, + valueColor: const AlwaysStoppedAnimation( + Color.fromRGBO(45, 194, 98, 1.0), + ), + ); + }, + ), + ), + ), + ], + ); + } + + Widget _getThumbnail() { + return Container( + color: Colors.black, + constraints: const BoxConstraints.expand(), + child: ThumbnailWidget( + widget.file, + fit: BoxFit.contain, + ), + ); + } + + Future _keepScreenAliveOnPlaying(bool isPlaying) async { + if (isPlaying) { + _wakeLock.enable(); + } + if (!isPlaying) { + _wakeLock.disable(); + } + } + + Widget _getVideoPlayer() { + _videoPlayerController!.addListener(() { + if (_isPlaying != _videoPlayerController!.value.isPlaying) { + _isPlaying = _videoPlayerController!.value.isPlaying; + if (widget.playbackCallback != null) { + widget.playbackCallback!(_isPlaying); + } + unawaited(_keepScreenAliveOnPlaying(_isPlaying)); + } + }); + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController!, + aspectRatio: _videoPlayerController!.value.aspectRatio, + autoPlay: widget.autoPlay!, + autoInitialize: true, + looping: true, + allowMuting: true, + allowFullScreen: false, + customControls: const VideoControls(), + ); + return Container( + color: Colors.black, + child: Chewie(controller: _chewieController!), + ); + } +} diff --git a/mobile/lib/ui/viewer/file/video_control.dart b/mobile/lib/ui/viewer/file/video_control.dart new file mode 100644 index 0000000000..3ce9b66e84 --- /dev/null +++ b/mobile/lib/ui/viewer/file/video_control.dart @@ -0,0 +1,310 @@ +import 'dart:async'; + +import 'package:chewie/chewie.dart'; +import 'package:chewie/src/material/material_progress_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:photos/ente_theme_data.dart'; +import 'package:photos/utils/date_time_util.dart'; +import 'package:video_player/video_player.dart'; + +class VideoControls extends StatefulWidget { + const VideoControls({Key? key}) : super(key: key); + + @override + State createState() { + return _VideoControlsState(); + } +} + +class _VideoControlsState extends State { + VideoPlayerValue? _latestValue; + bool _hideStuff = true; + Timer? _hideTimer; + Timer? _initTimer; + Timer? _showAfterExpandCollapseTimer; + bool _dragging = false; + bool _displayTapped = false; + + final barHeight = 120.0; + final marginSize = 5.0; + + late VideoPlayerController controller; + ChewieController? chewieController; + + @override + Widget build(BuildContext context) { + if (_latestValue!.hasError) { + return chewieController!.errorBuilder != null + ? chewieController!.errorBuilder!( + 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: _hideStuff, + child: Stack( + children: [ + Column( + children: [ + _latestValue != null && + !_latestValue!.isPlaying && + _latestValue!.isBuffering + ? const Center( + child: CircularProgressIndicator(), + ) + : _buildHitArea(), + ], + ), + Align( + alignment: Alignment.bottomCenter, + child: _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(); + } + + Widget _buildBottomBar( + BuildContext context, + ) { + final iconColor = Theme.of(context).textTheme.labelLarge!.color; + + return Container( + padding: const EdgeInsets.only(bottom: 60), + child: AnimatedOpacity( + opacity: _hideStuff ? 0.0 : 1.0, + duration: const Duration(milliseconds: 300), + child: Container( + height: barHeight, + color: Colors.transparent, + child: Row( + children: [ + _buildCurrentPosition(iconColor), + chewieController!.isLive ? const SizedBox() : _buildProgressBar(), + _buildTotalDuration(iconColor), + ], + ), + ), + ), + ); + } + + Expanded _buildHitArea() { + return Expanded( + child: GestureDetector( + onTap: () { + if (_latestValue != null) { + if (_displayTapped) { + setState(() { + _hideStuff = true; + }); + } else { + _cancelAndRestartTimer(); + } + } else { + _playPause(); + + setState(() { + _hideStuff = true; + }); + } + }, + child: Container( + color: Colors.transparent, + child: Center( + child: AnimatedOpacity( + opacity: + _latestValue != null && !_hideStuff && !_dragging ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), + child: GestureDetector( + onTap: _playPause, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Icon( + _latestValue!.isPlaying ? Icons.pause : Icons.play_arrow, + color: Colors.white, // same for both themes + size: 64.0, + ), + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildCurrentPosition(Color? iconColor) { + final position = + _latestValue != null ? _latestValue!.position : Duration.zero; + + return Container( + margin: const EdgeInsets.only(left: 20.0, right: 16.0), + child: Text( + formatDuration(position), + style: const TextStyle( + fontSize: 12.0, + color: Colors.white, + ), + ), + ); + } + + Widget _buildTotalDuration(Color? iconColor) { + final duration = + _latestValue != null ? _latestValue!.duration : Duration.zero; + + return Padding( + padding: const EdgeInsets.only(right: 20.0), + child: Text( + formatDuration(duration), + style: const TextStyle( + fontSize: 12.0, + color: Colors.white, + ), + ), + ); + } + + void _cancelAndRestartTimer() { + _hideTimer?.cancel(); + _startHideTimer(); + + setState(() { + _hideStuff = false; + _displayTapped = true; + }); + } + + Future _initialize() async { + controller.addListener(_updateState); + + _updateState(); + + if ((controller.value.isPlaying) || chewieController!.autoPlay) { + _startHideTimer(); + } + + if (chewieController!.showControlsOnInitialize) { + _initTimer = Timer(const Duration(milliseconds: 200), () { + setState(() { + _hideStuff = false; + }); + }); + } + } + + void _playPause() { + final bool isFinished = _latestValue!.position >= _latestValue!.duration; + + setState(() { + if (controller.value.isPlaying) { + _hideStuff = false; + _hideTimer?.cancel(); + controller.pause(); + } else { + _cancelAndRestartTimer(); + + if (!controller.value.isInitialized) { + controller.initialize().then((_) { + controller.play(); + }); + } else { + if (isFinished) { + controller.seekTo(const Duration(seconds: 0)); + } + controller.play(); + } + } + }); + } + + void _startHideTimer() { + _hideTimer = Timer(const Duration(seconds: 2), () { + setState(() { + _hideStuff = true; + }); + }); + } + + void _updateState() { + setState(() { + _latestValue = controller.value; + }); + } + + Widget _buildProgressBar() { + return Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 16.0), + child: MaterialVideoProgressBar( + controller, + onDragStart: () { + setState(() { + _dragging = true; + }); + + _hideTimer?.cancel(); + }, + onDragEnd: () { + setState(() { + _dragging = false; + }); + + _startHideTimer(); + }, + colors: chewieController!.materialProgressColors ?? + ChewieProgressColors( + playedColor: Theme.of(context).colorScheme.greenAlternative, + handleColor: Colors.white, + bufferedColor: Colors.white, + backgroundColor: Theme.of(context).disabledColor, + ), + ), + ), + ); + } +} diff --git a/mobile/lib/utils/exif_util.dart b/mobile/lib/utils/exif_util.dart index 3ab9b6e443..652336ebee 100644 --- a/mobile/lib/utils/exif_util.dart +++ b/mobile/lib/utils/exif_util.dart @@ -4,9 +4,9 @@ import "dart:io"; import "package:computer/computer.dart"; import 'package:exif/exif.dart'; -import "package:ffmpeg_kit_flutter_min/ffprobe_kit.dart"; -import "package:ffmpeg_kit_flutter_min/media_information.dart"; -import "package:ffmpeg_kit_flutter_min/media_information_session.dart"; +import "package:ffmpeg_kit_flutter_full_gpl/ffprobe_kit.dart"; +import "package:ffmpeg_kit_flutter_full_gpl/media_information.dart"; +import "package:ffmpeg_kit_flutter_full_gpl/media_information_session.dart"; import "package:flutter/foundation.dart"; import 'package:intl/intl.dart'; import 'package:logging/logging.dart'; diff --git a/mobile/lib/utils/ffprobe_util.dart b/mobile/lib/utils/ffprobe_util.dart index 6da87d8fd6..ae49b295de 100644 --- a/mobile/lib/utils/ffprobe_util.dart +++ b/mobile/lib/utils/ffprobe_util.dart @@ -1,6 +1,6 @@ // Adapted from: https://github.com/deckerst/aves -import "package:ffmpeg_kit_flutter_min/media_information.dart"; +import "package:ffmpeg_kit_flutter_full_gpl/media_information.dart"; import "package:logging/logging.dart"; import "package:photos/models/ffmpeg/ffprobe_keys.dart"; import "package:photos/models/ffmpeg/ffprobe_props.dart"; diff --git a/mobile/plugins/ente_cast/pubspec.lock b/mobile/plugins/ente_cast/pubspec.lock new file mode 100644 index 0000000000..6cd4bf68b4 --- /dev/null +++ b/mobile/plugins/ente_cast/pubspec.lock @@ -0,0 +1,269 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + collection: + dependency: "direct main" + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + dio: + dependency: "direct main" + description: + name: dio + sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8" + url: "https://pub.dev" + source: hosted + version: "4.0.6" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: c272f9cabca5a81adc9b0894381e9c1def363e980f960fa903c604c471b22f68 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + url: "https://pub.dev" + source: hosted + version: "2.5.2" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: "direct main" + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" +sdks: + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/mobile/plugins/ente_cast_none/pubspec.lock b/mobile/plugins/ente_cast_none/pubspec.lock new file mode 100644 index 0000000000..57a80587b0 --- /dev/null +++ b/mobile/plugins/ente_cast_none/pubspec.lock @@ -0,0 +1,276 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + dio: + dependency: transitive + description: + name: dio + sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8" + url: "https://pub.dev" + source: hosted + version: "4.0.6" + ente_cast: + dependency: "direct main" + description: + path: "../ente_cast" + relative: true + source: path + version: "0.0.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: c272f9cabca5a81adc9b0894381e9c1def363e980f960fa903c604c471b22f68 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + url: "https://pub.dev" + source: hosted + version: "2.5.2" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: "direct main" + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" +sdks: + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/mobile/plugins/ente_cast_normal/pubspec.lock b/mobile/plugins/ente_cast_normal/pubspec.lock index 86051800c6..b65fe49c94 100644 --- a/mobile/plugins/ente_cast_normal/pubspec.lock +++ b/mobile/plugins/ente_cast_normal/pubspec.lock @@ -127,10 +127,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" multicast_dns: dependency: transitive description: diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 28219c70d9..2f04ee67ed 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -94,6 +94,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.5.0" + asn1lib: + dependency: transitive + description: + name: asn1lib + sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5" + url: "https://pub.dev" + source: hosted + version: "1.5.8" async: dependency: transitive description: @@ -457,6 +465,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.17" + encrypt: + dependency: "direct main" + description: + name: encrypt + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" + url: "https://pub.dev" + source: hosted + version: "5.0.3" ente_cast: dependency: "direct main" description: @@ -566,11 +582,11 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" - ffmpeg_kit_flutter_min: + ffmpeg_kit_flutter_full_gpl: dependency: "direct main" description: - name: ffmpeg_kit_flutter_min - sha256: "123bfbc0e0b9e7cf6d32d8ba8e08b666d66af0f52c07683dd2305fbfc13f494a" + name: ffmpeg_kit_flutter_full_gpl + sha256: "4f269bcb636bfcb544e5b4d65c706a3d311839970cb42638e72406410c1b5b7b" url: "https://pub.dev" source: hosted version: "6.0.3" @@ -1540,7 +1556,7 @@ packages: source: hosted version: "1.3.6" media_kit_libs_ios_video: - dependency: transitive + dependency: "direct main" description: name: media_kit_libs_ios_video sha256: b5382994eb37a4564c368386c154ad70ba0cc78dacdd3fb0cd9f30db6d837991 @@ -2749,6 +2765,15 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + video_compress: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: "7d37839d90d4410528d5da4cba28937f19122894" + url: "https://github.com/RmanAkbarzadeh/VideoCompress.git" + source: git + version: "3.1.3" video_editor: dependency: "direct main" description: @@ -2783,6 +2808,14 @@ 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: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 9cc4e00dd0..f570c570d6 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.9.72+972 +version: 0.9.74+974 publish_to: none environment: @@ -48,6 +48,7 @@ dependencies: dropdown_button2: ^2.0.0 el_tooltip: ^2.2.1 email_validator: ^2.0.1 + encrypt: ^5.0.3 ente_cast: path: plugins/ente_cast ente_cast_normal: @@ -62,7 +63,7 @@ dependencies: extended_image: ^8.1.1 fade_indexed_stack: ^0.2.2 fast_base58: ^0.2.1 - ffmpeg_kit_flutter_min: ^6.0.3 + ffmpeg_kit_flutter_full_gpl: ^6.0.3 figma_squircle: ^0.5.3 file_saver: # Use forked version till this PR is merged: https://github.com/incrediblezayed/file_saver/pull/87 @@ -80,7 +81,7 @@ dependencies: flutter_image_compress: ^1.1.0 flutter_inappwebview: ^6.1.4 flutter_launcher_icons: ^0.13.1 - flutter_local_notifications: ^17.2.1+2 + flutter_local_notifications: ^17.2.2 flutter_localizations: sdk: flutter flutter_map: ^6.2.0 @@ -118,6 +119,7 @@ dependencies: 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 ml_linalg: ^13.11.31 @@ -181,8 +183,10 @@ dependencies: ua_client_hints: ^1.4.0 uni_links: ^0.5.1 url_launcher: ^6.3.0 - uuid: ^4.5.0 + video_compress: + git: + url: https://github.com/RmanAkbarzadeh/VideoCompress.git video_editor: git: url: https://github.com/prateekmedia/video_editor.git @@ -191,6 +195,7 @@ 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 @@ -211,7 +216,7 @@ dependency_overrides: ref: android_video_roation_fix path: packages/video_player/video_player/ watcher: ^1.1.0 - win32: ^5.5.3 + win32: ^5.5.4 flutter_intl: enabled: true