diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 6769a1fcc81..93a14d304a1 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -450,84 +450,84 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wakelock_plus/ios" SPEC CHECKSUMS: - background_fetch: 39f11371c0dce04b001c4bfd5e782bcccb0a85e2 - battery_info: 09f5c9ee65394f2291c8c6227bedff345b8a730c - connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db - cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba - dart_ui_isolate: d5bcda83ca4b04f129d70eb90110b7a567aece14 - device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 + background_fetch: 94b36ee293e82972852dba8ede1fbcd3bd3d9d57 + battery_info: a06b00c06a39bc94c92beebf600f1810cb6c8c87 + connectivity_plus: 3f6c9057f4cd64198dc826edfb0542892f825343 + cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c + dart_ui_isolate: 46f6714abe6891313267153ef6f9748d8ecfcab1 + device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89 ffmpeg-kit-ios-full-gpl: 80adc341962e55ef709e36baa8ed9a70cf4ea62b - ffmpeg_kit_flutter_full_gpl: 8d15c14c0c3aba616fac04fe44b3d27d02e3c330 - file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 + ffmpeg_kit_flutter_full_gpl: ce18b888487c05c46ed252cd2e7956812f2e3bd1 + file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 Firebase: 98e6bf5278170668a7983e12971a66b2cd57fc8c - firebase_core: 2bedc3136ec7c7b8561c6123ed0239387b53f2af - firebase_messaging: 15d114e1a41fc31e4fbabcd48d765a19eec94a38 + firebase_core: 085320ddfaacb80d1a96eac3a87857afcc150db1 + firebase_messaging: d398edc15fe825f832836e74f6ac61e8cd2f3ad3 FirebaseCore: a282032ae9295c795714ded2ec9c522fc237f8da FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2 FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414 FirebaseMessaging: c9ec7b90c399c7a6100297e9d16f8a27fc7f7152 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b - flutter_image_compress: 5a5e9aee05b6553048b8df1c3bc456d0afaac433 - flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 - flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 - flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 - flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be - flutter_sodium: c84426b4de738514b5b66cfdeb8a06634e72fe0b - fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c + flutter_email_sender: cd533cdc7ea5eda6fabb2c7f78521c71207778a4 + flutter_image_compress: 4b058288a81f76e5e80340af37c709abafff34c4 + flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 + flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100 + flutter_native_splash: 35ddbc7228eafcb3969dcc5f1fbbe27c1145a4f0 + flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418 + flutter_sodium: 152647449ba89a157fd48d7e293dcd6d29c6ab0e + fluttertoast: 76fea30fcf04176325f6864c87306927bd7d2038 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d - home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57 - image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43 - image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 - in_app_purchase_storekit: 8c3b0b3eb1b0f04efbff401c3de6266d4258d433 - integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 + home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f + image_editor_common: 3de87e7c4804f4ae24c8f8a998362b98c105cac1 + image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a + in_app_purchase_storekit: e126ef1b89e4a9fdf07e28f005f82632b4609437 + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 - local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3 - local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9 + local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391 + local_auth_ios: f7a1841beef3151d140a967c2e46f30637cdf451 Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d - maps_launcher: 2e5b6a2d664ec6c27f82ffa81b74228d770ab203 - media_extension: 6d30dc1431ebaa63f43c397c37917b1a0a597a4c - media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 - media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a - media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e - motion_sensors: 03f55b7c637a7e365a0b5f9697a449f9059d5d91 - motionphoto: d4a432b8c8f22fb3ad966258597c0103c9c5ff16 - move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d + 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 + move_to_background: 155f7bfbd34d43ad847cb630d2d2d87c17199710 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - native_video_player: d12af78a1a4a8cf09775a5177d5b392def6fd23c - objective_c: 77e887b5ba1827970907e10e832eec1683f3431d - onnxruntime: e7c2ae44385191eaad5ae64c935a72debaddc997 + native_video_player: b65c58951ede2f93d103a25366bdebca95081265 + objective_c: 89e720c30d716b036faf9c9684022048eee1eee2 + onnxruntime: f9b296392c96c42882be020a59dbeac6310d81b2 onnxruntime-c: a909204639a1f035f575127ac406f781ac797c9c onnxruntime-objc: b6fab0f1787aa6f7190c2013f03037df4718bd8b - open_mail_app: 794172f6a22cd16319d3ddaf45e945b2f74952b0 + open_mail_app: 06d5a4162866388a92b1df3deb96e56be20cf45c OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 - photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a - privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + photo_manager: d2fbcc0f2d82458700ee6256a15018210a81d413 + privacy_screen: 3159a541f5d3a31bea916cfd4e58f9dc722b3fd4 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - receive_sharing_intent: df9c334dc9feadcbd3266e5cb49c8443405e1c9f - screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 + receive_sharing_intent: f6a12b7e8f7ed745f61c982de8a65de88db44a44 + screen_brightness_ios: 5ed898fa50fa82a26171c086ca5e28228f932576 SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57 - sentry_flutter: 0eb93e5279eb41e2392212afe1ccd2fecb4f8cbe - share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13 + sentry_flutter: 0a211008f52553ba5dd81ceb71f48d78f0f1f6ab + share_plus: 011d6fb4f9d2576b83179a3a5c5e323202cdabcf + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite_darwin: 44bb54cc302bff1fbe5752293aba1820b157cf1c sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb - sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b - system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa + sqlite3_flutter_libs: 9379996d65aa23dcda7585a5b58766cebe0aa042 + system_info_plus: 555ce7047fbbf29154726db942ae785c29211740 Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e - ua_client_hints: 46bb5817a868f9e397c0ba7e3f2f5c5d90c35156 - uni_links: d97da20c7701486ba192624d99bffaaffcfc298a - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 - video_thumbnail: c4e2a3c539e247d4de13cd545344fd2d26ffafd1 - volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9 - wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47 + ua_client_hints: 0b48eae1134283f5b131ee0871fa878377f07a01 + uni_links: ed8c961e47ed9ce42b6d91e1de8049e38a4b3152 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b + video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140 + volume_controller: ca1cde542ee70fad77d388f82e9616488110942b + wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49 PODFILE CHECKSUM: 20e086e6008977d43a3d40260f3f9bffcac748dd diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 906654c7939..b907203cf90 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -239,7 +239,7 @@ Future _init(bool isBackground, {String via = ''}) async { .init(preferences, NetworkClient.instance.enteDio, packageInfo); if (!isBackground) { - VideoPlayerMediaKit.ensureInitialized(iOS: true); + VideoPlayerMediaKit.ensureInitialized(iOS: true, android: true); } _logger.info("UserService init $tlog"); diff --git a/mobile/lib/services/preview_video_store.dart b/mobile/lib/services/preview_video_store.dart index 6881577b418..7a72a93a9d3 100644 --- a/mobile/lib/services/preview_video_store.dart +++ b/mobile/lib/services/preview_video_store.dart @@ -50,7 +50,7 @@ class PreviewVideoStore { final cacheManager = DefaultCacheManager(); final videoCacheManager = VideoCacheManager.instance; - LinkedHashSet files = LinkedHashSet(); + LinkedHashSet fileQueue = LinkedHashSet(); int uploadingFileId = -1; final _dio = NetworkClient.instance.enteDio; @@ -60,7 +60,7 @@ class PreviewVideoStore { Future.delayed( const Duration(seconds: 10), - PreviewVideoStore.instance.putFilesForPreviewCreation, + _putFilesForPreviewCreation, ); } @@ -82,16 +82,17 @@ class PreviewVideoStore { Bus.instance.fire(VideoStreamingChanged()); if (isVideoStreamingEnabled) { - putFilesForPreviewCreation().ignore(); + await FileDataService.instance.syncFDStatus(); + _putFilesForPreviewCreation().ignore(); } else { clearQueue(); } } - clearQueue() { + void clearQueue() { + fileQueue.clear(); _items.clear(); Bus.instance.fire(PreviewUpdatedEvent(_items)); - files.clear(); } DateTime? get videoStreamingCutoff { @@ -111,36 +112,39 @@ class PreviewVideoStore { } try { - if (!enteFile.isUploaded) return; - final file = await getFile(enteFile, isOrigin: true); - if (file == null) return; + if (!enteFile.isUploaded) { + _removeFile(enteFile); + return; + } try { // check if playlist already exist await getPlaylist(enteFile); - final resultUrl = await getPreviewUrl(enteFile); + final _ = await getPreviewUrl(enteFile); + if (ctx != null && ctx.mounted) { showShortToast(ctx, 'Video preview already exists'); } - debugPrint("previewUrl $resultUrl"); - _items.removeWhere((key, value) => value.file == enteFile); - Bus.instance.fire(PreviewUpdatedEvent(_items)); + _removeFile(enteFile); return; } catch (e, s) { if (e is DioException && e.response?.statusCode == 404) { _logger.info("No preview found for $enteFile"); } else { _logger.warning("Failed to get playlist for $enteFile", e, s); - rethrow; + _retryFile(enteFile, e); + return; } } - var (props, result) = await checkFileForPreviewCreation(enteFile); - + // elimination case for <=10 MB with H.264 + var (props, result, file) = await _checkFileForPreviewCreation(enteFile); if (result) { + _removeFile(enteFile); return; } + // check if there is already a preview in processing if (uploadingFileId >= 0) { if (uploadingFileId == enteFile.uploadedFileID) return; @@ -153,9 +157,11 @@ class PreviewVideoStore { collectionID: enteFile.collectionID ?? 0, ); Bus.instance.fire(PreviewUpdatedEvent(_items)); - files.add(enteFile); + fileQueue.add(enteFile); return; } + + // everything is fine, let's process uploadingFileId = enteFile.uploadedFileID!; _items[enteFile.uploadedFileID!] = PreviewItem( status: PreviewItemStatus.compressing, @@ -166,17 +172,31 @@ class PreviewVideoStore { ); Bus.instance.fire(PreviewUpdatedEvent(_items)); - props = await getVideoPropsAsync(file); + // get file + file ??= await getFile(enteFile, isOrigin: true); + if (file == null) { + _retryFile(enteFile, "Unable to fetch file"); + return; + } + + // check metadata for bitrate, codec, color space + props ??= await getVideoPropsAsync(file); final fileSize = enteFile.fileSize ?? file.lengthSync(); final videoData = List.from(props?.propData?["streams"] ?? []) .firstWhereOrNull((e) => e["type"] == "video"); final codec = videoData["codec_name"]?.toString().toLowerCase(); + final codecIsH264 = codec?.contains("h264") ?? false; + final bitrate = props?.duration?.inSeconds != null ? (fileSize * 8) / props!.duration!.inSeconds : null; + final colorSpace = videoData["color_space"]?.toString().toLowerCase(); + final isColorGood = colorSpace == "bt709"; + + // create temp file & directory for preview generation final String tempDir = Configuration.instance.getTempDirectory(); final String prefix = "${tempDir}_${enteFile.uploadedFileID}_${newID("pv")}"; @@ -190,15 +210,14 @@ class PreviewVideoStore { 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}', ); FFmpegSession? session; - final colorSpace = videoData["color_space"]?.toString().toLowerCase(); - final isColorGood = colorSpace == "bt709"; - final codecIsH264 = codec?.contains("h264") ?? false; + // case 1, if it's already a good stream if (bitrate != null && bitrate <= 4000 * 1000 && codecIsH264) { session = await FFmpegKit.execute( '-i "${file.path}" ' @@ -208,7 +227,8 @@ class PreviewVideoStore { '-hls_list_size 0 -hls_key_info_file ${keyinfo.path} ' '$prefix/output.m3u8', ); - } else if (bitrate != null && + } // case 2, if it's bitrate is good, but codec is not + else if (bitrate != null && codec != null && bitrate <= 2000 * 1000 && !codecIsH264) { @@ -223,10 +243,9 @@ class PreviewVideoStore { '-hls_list_size 0 -hls_key_info_file ${keyinfo.path} ' '$prefix/output.m3u8', ); - } - - if (colorSpace != null && isColorGood) { - session ??= await FFmpegKit.execute( + } // case 3, if it's color space is good + else if (colorSpace != null && isColorGood) { + session = await FFmpegKit.execute( '-i "${file.path}" ' '-metadata:s:v:0 rotate=0 ' '-vf "scale=-2:720,fps=30" ' @@ -235,20 +254,21 @@ class PreviewVideoStore { '-hls_list_size 0 -hls_key_info_file ${keyinfo.path} ' '$prefix/output.m3u8', ); + } // case 4, make it compatible + else { + session = await FFmpegKit.execute( + '-i "${file.path}" ' + '-metadata:s:v:0 rotate=0 ' + '-vf "scale=-2:720,fps=30,format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" ' + '-color_primaries bt709 -color_trc bt709 -colorspace bt709 ' + '-x264-params "colorprim=bt709:transfer=bt709:colormatrix=bt709" ' + '-c:v libx264 -b:v 2000k -crf 23 -preset medium ' + '-c:a aac -b:a 128k -f hls -hls_time 2 -hls_flags single_file ' + '-hls_list_size 0 -hls_key_info_file ${keyinfo.path} ' + '$prefix/output.m3u8', + ); } - session ??= await FFmpegKit.execute( - '-i "${file.path}" ' - '-metadata:s:v:0 rotate=0 ' - '-vf "scale=-2:720,fps=30,format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" ' - '-color_primaries bt709 -color_trc bt709 -colorspace bt709 ' - '-x264-params "colorprim=bt709:transfer=bt709:colormatrix=bt709" ' - '-c:v libx264 -b:v 2000k -crf 23 -preset medium ' - '-c:a aac -b:a 128k -f hls -hls_time 2 -hls_flags single_file ' - '-hls_list_size 0 -hls_key_info_file ${keyinfo.path} ' - '$prefix/output.m3u8', - ); - final returnCode = await session.getReturnCode(); String? error; @@ -264,14 +284,15 @@ class PreviewVideoStore { Bus.instance.fire(PreviewUpdatedEvent(_items)); _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; - // Logic to fetch width & height of preview - //-allowed_extensions ALL -i "https://example.com/stream.m3u8" -frames:v 1 -c copy frame.ts + // Fetch resolution of generated stream by decrypting a single frame final FFmpegSession session2 = await FFmpegKit.execute( '-allowed_extensions ALL -i "$prefix/output.m3u8" -frames:v 1 -c copy "$prefix/frame.ts"', ); @@ -286,8 +307,8 @@ class PreviewVideoStore { width = props2?.width; height = props2?.height; } - } catch (_) { - _logger.warning("Failed to get width and height", _); + } catch (err, sT) { + _logger.warning("Failed to fetch resolution of stream", err, sT); } await _reportVideoPreview( @@ -302,7 +323,7 @@ class PreviewVideoStore { _logger.info("Video preview uploaded for $enteFile"); } catch (err, sT) { error = "Failed to upload video preview\nError: $err"; - _logger.shout("Video preview uploaded for $enteFile", err, sT); + _logger.shout("Something went wrong with preview upload", err, sT); } } else if (ReturnCode.isCancel(returnCode)) { _logger.warning("FFmpeg command cancelled"); @@ -313,14 +334,13 @@ class PreviewVideoStore { "FFmpeg command failed with return code $returnCode", output ?? "Error not found", ); - if (kDebugMode) { - _logger.severe(output); - } error = "Failed to generate video preview\nError: $output"; } if (error == null) { + // update previewIds FileDataService.instance.syncFDStatus().ignore(); + _items[enteFile.uploadedFileID!] = PreviewItem( status: PreviewItemStatus.uploaded, file: enteFile, @@ -328,37 +348,49 @@ class PreviewVideoStore { collectionID: enteFile.collectionID ?? 0, ); } else { - if (_items[enteFile.uploadedFileID!]!.retryCount < 3) { - _items[enteFile.uploadedFileID!] = PreviewItem( - status: PreviewItemStatus.retry, - file: enteFile, - retryCount: _items[enteFile.uploadedFileID!]!.retryCount + 1, - collectionID: enteFile.collectionID ?? 0, - ); - files.add(enteFile); - } else { - _items[enteFile.uploadedFileID!] = PreviewItem( - status: PreviewItemStatus.failed, - file: enteFile, - retryCount: _items[enteFile.uploadedFileID!]!.retryCount, - collectionID: enteFile.collectionID ?? 0, - error: error, - ); - } + _retryFile(enteFile, error); } Bus.instance.fire(PreviewUpdatedEvent(_items)); } finally { + // reset uploading status if this was getting processed if (uploadingFileId == enteFile.uploadedFileID!) { uploadingFileId = -1; } - if (files.isNotEmpty) { - final file = files.first; - files.remove(file); + _logger.info("[chunk] Processing ${_items.length} items for streaming"); + // process next file + if (fileQueue.isNotEmpty) { + final file = fileQueue.first; + fileQueue.remove(file); await chunkAndUploadVideo(ctx, file); } } } + void _removeFile(EnteFile enteFile) { + _items.remove(enteFile.uploadedFileID!); + Bus.instance.fire(PreviewUpdatedEvent(_items)); + } + + void _retryFile(EnteFile enteFile, Object error) { + if (_items[enteFile.uploadedFileID!]!.retryCount < 3) { + _items[enteFile.uploadedFileID!] = PreviewItem( + status: PreviewItemStatus.retry, + file: enteFile, + retryCount: _items[enteFile.uploadedFileID!]!.retryCount + 1, + collectionID: enteFile.collectionID ?? 0, + ); + fileQueue.add(enteFile); + } else { + _items[enteFile.uploadedFileID!] = PreviewItem( + status: PreviewItemStatus.failed, + file: enteFile, + retryCount: _items[enteFile.uploadedFileID!]!.retryCount, + collectionID: enteFile.collectionID ?? 0, + error: error, + ); + } + } + Future _reportVideoPreview( EnteFile file, File playlist, { @@ -528,7 +560,7 @@ class PreviewVideoStore { final previewURL = response2.data["url"]; if (objectKey != null) { unawaited( - downloadAndCacheVideo( + _downloadAndCacheVideo( previewURL, _getVideoPreviewKey(objectKey), ), @@ -557,7 +589,7 @@ class PreviewVideoStore { } } - Future downloadAndCacheVideo(String url, String key) async { + Future _downloadAndCacheVideo(String url, String key) async { final file = await videoCacheManager.downloadFile(url, key: key); return file; } @@ -579,37 +611,35 @@ class PreviewVideoStore { } } - Future<(FFProbeProps?, bool)> checkFileForPreviewCreation( + Future<(FFProbeProps?, bool, File?)> _checkFileForPreviewCreation( EnteFile enteFile, ) async { final fileSize = enteFile.fileSize; FFProbeProps? props; + File? file; + bool result = false; - if (fileSize != null && fileSize <= 10 * 1024 * 1024) { - final file = await getFile(enteFile, isOrigin: true); - if (file != null) { - props = await getVideoPropsAsync(file); - final videoData = List.from(props?.propData?["streams"] ?? []) - .firstWhereOrNull((e) => e["type"] == "video"); - - final codec = videoData["codec_name"]?.toString().toLowerCase(); - final codecIsH264 = codec?.contains("h264") ?? false; - - if (codecIsH264) { - if (_items.containsKey(enteFile.uploadedFileID!)) { - _items.remove(enteFile.uploadedFileID!); - Bus.instance.fire(PreviewUpdatedEvent(_items)); - } - return (props, true); + try { + final isFileUnder10MB = fileSize != null && fileSize <= 10 * 1024 * 1024; + if (isFileUnder10MB) { + file = await getFile(enteFile, isOrigin: true); + if (file != null) { + props = await getVideoPropsAsync(file); + final videoData = List.from(props?.propData?["streams"] ?? []) + .firstWhereOrNull((e) => e["type"] == "video"); + + final codec = videoData["codec_name"]?.toString().toLowerCase(); + result = codec?.contains("h264") ?? false; } } + } catch (e, sT) { + _logger.warning("Failed to check props", e, sT); } - return (props, false); + return (props, result, file); } - // get all files after cutoff date and add it to queue for preview creation - // only run when video streaming is enabled - Future putFilesForPreviewCreation() async { + // generate stream for all files after cutoff date + Future _putFilesForPreviewCreation() async { if (!isVideoStreamingEnabled) return; final cutoff = videoStreamingCutoff; @@ -625,16 +655,18 @@ class PreviewVideoStore { final allFiles = files .where((file) => previewIds?[file.uploadedFileID] == null) .sorted((a, b) { - // put higher duration videos last - final first = a.duration == null || a.duration! >= 10 * 60 ? 1 : 0; - final second = b.duration == null || b.duration! >= 10 * 60 ? 1 : 0; + // put higher duration videos last along with remote files + final first = (a.localID == null ? 2 : 0) + + (a.duration == null || a.duration! >= 10 * 60 ? 1 : 0); + final second = (b.localID == null ? 2 : 0) + + (b.duration == null || b.duration! >= 10 * 60 ? 1 : 0); return first.compareTo(second); }).toList(); - // set all video status to be in queue + // set all video status to in queue for (final enteFile in allFiles) { - final (_, result) = await checkFileForPreviewCreation(enteFile); - + // elimination case for <=10 MB with H.264 + final (_, result, _) = await _checkFileForPreviewCreation(enteFile); if (result) { allFiles.remove(enteFile); continue; @@ -646,14 +678,13 @@ class PreviewVideoStore { collectionID: enteFile.collectionID ?? 0, ); } - Bus.instance.fire(PreviewUpdatedEvent(_items)); - final file = allFiles.first; - allFiles.remove(file); - - this.files.addAll(allFiles); + _logger.info("[init] Processing ${_items.length} items for streaming"); + // take first file and put it for stream generation + final file = allFiles.removeAt(0); + fileQueue.addAll(allFiles); await chunkAndUploadVideo(null, file); } } diff --git a/mobile/lib/ui/viewer/file/preview_video_widget.dart b/mobile/lib/ui/viewer/file/preview_video_widget.dart index dbaa409ed4a..be2c7034a89 100644 --- a/mobile/lib/ui/viewer/file/preview_video_widget.dart +++ b/mobile/lib/ui/viewer/file/preview_video_widget.dart @@ -244,6 +244,7 @@ class _PreviewVideoWidgetState extends State { } }); _chewieController = ChewieController( + progressIndicatorDelay: const Duration(milliseconds: 200), videoPlayerController: _videoPlayerController!, aspectRatio: _videoPlayerController!.value.aspectRatio, autoPlay: widget.autoPlay!, @@ -254,6 +255,7 @@ class _PreviewVideoWidgetState extends State { customControls: VideoControls( file: widget.file, onStreamChange: widget.onStreamChange, + playbackCallback: widget.playbackCallback, ), ); return Container( diff --git a/mobile/lib/ui/viewer/file/video_control.dart b/mobile/lib/ui/viewer/file/video_control.dart index d6628190036..4048a28836f 100644 --- a/mobile/lib/ui/viewer/file/video_control.dart +++ b/mobile/lib/ui/viewer/file/video_control.dart @@ -1,13 +1,18 @@ +// ignore_for_file: implementation_imports + import 'dart:async'; -import 'package:chewie/chewie.dart'; +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/utils/debouncer.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 { @@ -15,9 +20,11 @@ class VideoControls extends StatefulWidget { super.key, required this.file, required this.onStreamChange, + required this.playbackCallback, }); final EnteFile file; final void Function()? onStreamChange; + final void Function(bool)? playbackCallback; @override State createState() { @@ -25,9 +32,10 @@ class VideoControls extends StatefulWidget { } } -class _VideoControlsState extends State { - VideoPlayerValue? _latestValue; - bool _hideStuff = true; +class _VideoControlsState extends State + with SingleTickerProviderStateMixin { + late PlayerNotifier notifier; + late VideoPlayerValue _latestValue; Timer? _hideTimer; Timer? _initTimer; Timer? _showAfterExpandCollapseTimer; @@ -36,34 +44,35 @@ class _VideoControlsState extends State { Timer? _bufferingDisplayTimer; bool _displayBufferingIndicator = false; - final barHeight = 120.0; + final barHeight = 48.0 * 1.5; final marginSize = 5.0; late VideoPlayerController controller; - ChewieController? chewieController; + ChewieController? _chewieController; - void _bufferingTimerTimeout() { - _displayBufferingIndicator = true; - if (mounted) { - setState(() {}); - } + // We know that _chewieController is set in didChangeDependencies + ChewieController get chewieController => _chewieController!; + + @override + void initState() { + super.initState(); + notifier = Provider.of(context, listen: false); } @override Widget build(BuildContext context) { - if (_latestValue!.hasError) { - return chewieController!.errorBuilder != null - ? chewieController!.errorBuilder!( - context, - chewieController!.videoPlayerController.value.errorDescription!, - ) - : Center( - child: Icon( - Icons.error, - color: Theme.of(context).colorScheme.onSurface, - size: 42, - ), - ); + 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( @@ -73,43 +82,35 @@ class _VideoControlsState extends State { child: GestureDetector( onTap: () => _cancelAndRestartTimer(), child: AbsorbPointer( - absorbing: _hideStuff, + absorbing: notifier.hideStuff, child: Stack( - children: [ - if (_latestValue != null && - !_latestValue!.isPlaying && - _latestValue!.isBuffering || - _displayBufferingIndicator) - const Align( - alignment: Alignment.center, - child: Center( - child: EnteLoadingWidget( - size: 32, - color: fillBaseDark, - padding: 0, - ), - ), - ) - else - Positioned.fill(child: _buildHitArea()), - Align( - alignment: Alignment.bottomCenter, - child: SafeArea( - top: false, - left: false, - right: false, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - PreviewStatusWidget( - showControls: !_hideStuff, - file: widget.file, - isPreviewPlayer: true, - onStreamChange: widget.onStreamChange, + children: [ + if (_displayBufferingIndicator) + _chewieController?.bufferingBuilder?.call(context) ?? + const Center( + child: EnteLoadingWidget( + size: 32, + color: fillBaseDark, + padding: 0, ), - _buildBottomBar(context), - ], - ), + ) + else + _buildHitArea(), + SafeArea( + top: false, + left: false, + right: false, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + PreviewStatusWidget( + showControls: !notifier.hideStuff, + file: widget.file, + isPreviewPlayer: true, + onStreamChange: widget.onStreamChange, + ), + if (!chewieController.isLive) _buildBottomBar(context), + ], ), ), ], @@ -134,9 +135,9 @@ class _VideoControlsState extends State { @override void didChangeDependencies() { - final oldController = chewieController; - chewieController = ChewieController.of(context); - controller = chewieController!.videoPlayerController; + final oldController = _chewieController; + _chewieController = ChewieController.of(context); + controller = chewieController.videoPlayerController; if (oldController != chewieController) { _dispose(); @@ -146,35 +147,75 @@ class _VideoControlsState extends State { super.didChangeDependencies(); } - Widget _buildBottomBar( + AnimatedOpacity _buildBottomBar( BuildContext context, ) { - return Container( - padding: const EdgeInsets.only(bottom: 60), - height: 100, - child: AnimatedOpacity( - opacity: _hideStuff ? 0.0 : 1.0, - duration: const Duration(milliseconds: 300), - child: _SeekBarAndDuration( - controller: controller, - latestValue: _latestValue, - updateDragging: (bool value) { - setState(() { - _dragging = value; - }); - }, + 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 != null) { + if (_latestValue.isPlaying) { if (_displayTapped) { setState(() { - _hideStuff = !_hideStuff; + notifier.hideStuff = true; }); } else { _cancelAndRestartTimer(); @@ -183,19 +224,32 @@ class _VideoControlsState extends State { _playPause(); setState(() { - _hideStuff = true; + notifier.hideStuff = true; }); } + widget.playbackCallback?.call(notifier.hideStuff); }, - behavior: HitTestBehavior.opaque, - child: AnimatedOpacity( - opacity: _latestValue != null && !_hideStuff && !_dragging ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: Center( - child: _PlayPauseButton( - _playPause, - _latestValue!.isPlaying, - ), + 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, + ), + ), + ], ), ), ); @@ -206,9 +260,10 @@ class _VideoControlsState extends State { _startHideTimer(); setState(() { - _hideStuff = false; + notifier.hideStuff = false; _displayTapped = true; }); + widget.playbackCallback?.call(notifier.hideStuff); } Future _initialize() async { @@ -216,25 +271,28 @@ class _VideoControlsState extends State { _updateState(); - if ((controller.value.isPlaying) || chewieController!.autoPlay) { + if (controller.value.isPlaying || chewieController.autoPlay) { _startHideTimer(); } - if (chewieController!.showControlsOnInitialize) { + if (chewieController.showControlsOnInitialize) { _initTimer = Timer(const Duration(milliseconds: 200), () { setState(() { - _hideStuff = false; + notifier.hideStuff = false; }); + widget.playbackCallback?.call(notifier.hideStuff); }); } } void _playPause() { - final bool isFinished = _latestValue!.position >= _latestValue!.duration; + final bool isFinished = (_latestValue.position >= _latestValue.duration) && + _latestValue.duration.inSeconds > 0; setState(() { if (controller.value.isPlaying) { - _hideStuff = false; + notifier.hideStuff = false; + widget.playbackCallback?.call(notifier.hideStuff); _hideTimer?.cancel(); controller.pause(); } else { @@ -246,7 +304,7 @@ class _VideoControlsState extends State { }); } else { if (isFinished) { - controller.seekTo(const Duration(seconds: 0)); + controller.seekTo(Duration.zero); } controller.play(); } @@ -255,19 +313,32 @@ class _VideoControlsState extends State { } void _startHideTimer() { - _hideTimer = Timer(const Duration(seconds: 2), () { + final hideControlsTimer = chewieController.hideControlsTimer.isNegative + ? ChewieController.defaultHideControlsTimer + : chewieController.hideControlsTimer; + _hideTimer = Timer(hideControlsTimer, () { setState(() { - _hideStuff = true; + 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 (chewieController.progressIndicatorDelay != null) { if (controller.value.isBuffering) { _bufferingDisplayTimer ??= Timer( - chewieController!.progressIndicatorDelay!, + chewieController.progressIndicatorDelay!, _bufferingTimerTimeout, ); } else { @@ -278,239 +349,104 @@ class _VideoControlsState extends State { } else { _displayBufferingIndicator = controller.value.isBuffering; } + setState(() { _latestValue = controller.value; }); } -} -class _SeekBarAndDuration extends StatelessWidget { - final VideoPlayerController? controller; - final VideoPlayerValue? latestValue; - final Function(bool) updateDragging; - - const _SeekBarAndDuration({ - required this.controller, - required this.latestValue, - required this.updateDragging, - }); - - @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: [ - if (latestValue?.position == null) - Text( - "0:00", - style: getEnteTextTheme( - context, - ).mini.copyWith( - color: textBaseDark, - ), - ) - else - Text( - _secondsToDuration(latestValue!.position.inSeconds), - style: getEnteTextTheme( - context, - ).mini.copyWith( - color: textBaseDark, - ), - ), - Expanded( - child: _SeekBar(controller!, updateDragging), - ), - Text( - _secondsToDuration( - latestValue?.duration.inSeconds ?? 0, - ), - 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 VideoPlayerController controller; - final Function(bool) updateDragging; - const _SeekBar( - this.controller, - this.updateDragging, - ); - - @override - State<_SeekBar> createState() => _SeekBarState(); -} - -class _SeekBarState extends State<_SeekBar> { - double _sliderValue = 0.0; - final _debouncer = Debouncer( - const Duration(milliseconds: 300), - executionInterval: const Duration(milliseconds: 300), - ); - bool _controllerWasPlaying = false; - - @override - void initState() { - super.initState(); - widget.controller.addListener(updateSlider); - } - - void updateSlider() { - if (widget.controller.value.isInitialized) { - setState(() { - _sliderValue = widget.controller.value.position.inSeconds.toDouble(); - }); - } - } - - @override - void dispose() { - _debouncer.cancelDebounceTimer(); - widget.controller.removeListener(updateSlider); - super.dispose(); - } - - @override - Widget build(BuildContext context) { + Widget _buildProgressBar() { 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: widget.controller.value.duration.inSeconds.toDouble(), - value: _sliderValue, - onChangeStart: (value) async { - widget.updateDragging(true); - _controllerWasPlaying = widget.controller.value.isPlaying; - if (_controllerWasPlaying) { - await widget.controller.pause(); - } - }, - onChanged: (value) { - if (mounted) { - setState(() { - _sliderValue = value; - }); - } - - _debouncer.run(() async { - await widget.controller.seekTo(Duration(seconds: value.toInt())); + return Expanded( + child: CustomProgressBar( + controller, + onDragStart: () { + setState(() { + _dragging = true; }); + + _hideTimer?.cancel(); + }, + onDragUpdate: () { + _hideTimer?.cancel(); }, - divisions: 4500, - onChangeEnd: (value) async { - await widget.controller.seekTo(Duration(seconds: value.toInt())); + onDragEnd: () { + setState(() { + _dragging = false; + }); - if (_controllerWasPlaying) { - await widget.controller.play(); - } - widget.updateDragging(false); + _startHideTimer(); }, - allowedInteraction: SliderInteraction.tapAndSlide, + colors: ChewieProgressColors( + playedColor: colorScheme.primary300, + handleColor: backgroundElevatedLight, + bufferedColor: backgroundElevatedLight.withOpacity(0.5), + backgroundColor: fillMutedDark, + ), + draggableProgressBar: chewieController.draggableProgressBar, ), ); } } -class _PlayPauseButton extends StatefulWidget { - final void Function() playPause; - final bool isPlaying; - const _PlayPauseButton( - this.playPause, - this.isPlaying, - ); +class CenterPlayButton extends StatelessWidget { + const CenterPlayButton({ + super.key, + required this.backgroundColor, + this.iconColor, + required this.show, + required this.isPlaying, + required this.isFinished, + this.onPressed, + }); - @override - State<_PlayPauseButton> createState() => _PlayPauseButtonState(); -} + final Color backgroundColor; + final Color? iconColor; + final bool show; + final bool isPlaying; + final bool isFinished; + final VoidCallback? onPressed; -class _PlayPauseButtonState extends State<_PlayPauseButton> { @override Widget build(BuildContext context) { - return Container( - width: 54, - height: 54, - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - shape: BoxShape.circle, - border: Border.all( - color: strokeFaintDark, - width: 1, + 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: widget.playPause, - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 250), - transitionBuilder: (Widget child, Animation animation) { - return ScaleTransition(scale: animation, child: child); - }, - switchInCurve: Curves.easeInOutQuart, - switchOutCurve: Curves.easeInOutQuart, - child: widget.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, - ), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onPressed, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 250), + transitionBuilder: (Widget child, Animation animation) { + return ScaleTransition(scale: animation, child: child); + }, + switchInCurve: Curves.easeInOutQuart, + switchOutCurve: Curves.easeInOutQuart, + child: isPlaying + ? const Icon( + Icons.pause, + size: 32, + key: ValueKey("pause"), + color: Colors.white, + ) + : const Icon( + Icons.play_arrow, + size: 36, + key: ValueKey("play"), + color: Colors.white, + ), + ), ), ), ); diff --git a/mobile/lib/ui/viewer/file/video_control/custom_progress_bar.dart b/mobile/lib/ui/viewer/file/video_control/custom_progress_bar.dart new file mode 100644 index 00000000000..563f6d1a071 --- /dev/null +++ b/mobile/lib/ui/viewer/file/video_control/custom_progress_bar.dart @@ -0,0 +1,41 @@ +// ignore_for_file: implementation_imports + +import "package:chewie/src/chewie_progress_colors.dart"; +import "package:chewie/src/progress_bar.dart"; +import "package:flutter/material.dart"; +import "package:flutter/widgets.dart"; +import "package:video_player/video_player.dart"; + +class CustomProgressBar extends StatelessWidget { + CustomProgressBar( + this.controller, { + ChewieProgressColors? colors, + this.onDragEnd, + this.onDragStart, + this.onDragUpdate, + super.key, + this.draggableProgressBar = true, + }) : colors = colors ?? ChewieProgressColors(); + + final VideoPlayerController controller; + final ChewieProgressColors colors; + final Function()? onDragStart; + final Function()? onDragEnd; + final Function()? onDragUpdate; + final bool draggableProgressBar; + + @override + Widget build(BuildContext context) { + return VideoProgressBar( + controller, + barHeight: 1.5, + handleHeight: 8, + drawShadow: true, + colors: colors, + onDragEnd: onDragEnd, + onDragStart: onDragStart, + onDragUpdate: onDragUpdate, + draggableProgressBar: draggableProgressBar, + ); + } +} diff --git a/mobile/lib/ui/viewer/file/video_widget_native.dart b/mobile/lib/ui/viewer/file/video_widget_native.dart index 810bca4dd6a..8eee0112337 100644 --- a/mobile/lib/ui/viewer/file/video_widget_native.dart +++ b/mobile/lib/ui/viewer/file/video_widget_native.dart @@ -210,10 +210,10 @@ class _VideoWidgetNativeState extends State behavior: HitTestBehavior.opaque, onTap: () { _showControls.value = !_showControls.value; - _elTooltipController.hide(); if (widget.playbackCallback != null) { widget.playbackCallback!(!_showControls.value); } + _elTooltipController.hide(); }, child: Container( constraints: const BoxConstraints.expand(), diff --git a/mobile/plugins/ente_cast/pubspec.lock b/mobile/plugins/ente_cast/pubspec.lock index 8de07d1e38e..7c17037ed2d 100644 --- a/mobile/plugins/ente_cast/pubspec.lock +++ b/mobile/plugins/ente_cast/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" characters: dependency: transitive description: @@ -21,10 +29,18 @@ packages: dependency: "direct main" description: name: dio - sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8" + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a url: "https://pub.dev" source: hosted - version: "4.0.6" + version: "2.1.0" ffi: dependency: transitive description: diff --git a/mobile/plugins/ente_cast_none/pubspec.lock b/mobile/plugins/ente_cast_none/pubspec.lock index ee83b2bd964..aeb3c20955a 100644 --- a/mobile/plugins/ente_cast_none/pubspec.lock +++ b/mobile/plugins/ente_cast_none/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" characters: dependency: transitive description: @@ -21,10 +29,18 @@ packages: dependency: transitive description: name: dio - sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8" + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a url: "https://pub.dev" source: hosted - version: "4.0.6" + version: "2.1.0" ente_cast: dependency: "direct main" description: diff --git a/mobile/plugins/ente_cast_normal/pubspec.lock b/mobile/plugins/ente_cast_normal/pubspec.lock index 2d37f86c870..e1786a9a8f8 100644 --- a/mobile/plugins/ente_cast_normal/pubspec.lock +++ b/mobile/plugins/ente_cast_normal/pubspec.lock @@ -38,10 +38,18 @@ packages: dependency: transitive description: name: dio - sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8" + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" url: "https://pub.dev" source: hosted - version: "4.0.6" + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a + url: "https://pub.dev" + source: hosted + version: "2.1.0" ente_cast: dependency: "direct main" description: diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index bc7f1692147..0c14e7b23ad 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -259,11 +259,11 @@ packages: dependency: "direct main" description: path: "." - ref: forked_video_player_plus - resolved-ref: "2d8908efe9d7533ec76abe2e59444547c4031f28" + ref: mybranched + resolved-ref: "539079ac2758086ef4dfb602a5f8785bf5295fb3" url: "https://github.com/ente-io/chewie.git" source: git - version: "1.7.1" + version: "1.10.0" cli_util: dependency: transitive description: @@ -1842,18 +1842,18 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" + sha256: "67eae327b1b0faf761964a1d2e5d323c797f3799db0e85aa232db8d9e922bc35" url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "8.2.1" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.1.0" page_transition: dependency: "direct main" description: @@ -2922,18 +2922,18 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d + sha256: "36c88af0b930121941345306d259ec4cc4ecca3b151c02e3a9e71aede83c615e" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.2.10" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16" + sha256: "70e780bc99796e1db82fe764b1e7dcb89a86f1e5b3afb1db354de50f2e41eb7a" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" wallpaper_manager_flutter: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 933e203bc7b..b7429832070 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.9.99+999 +version: 0.9.99+1002 publish_to: none environment: @@ -31,7 +31,7 @@ dependencies: chewie: git: url: https://github.com/ente-io/chewie.git - ref: forked_video_player_plus + ref: mybranched collection: # dart computer: git: "https://github.com/ente-io/computer.git" @@ -144,7 +144,7 @@ dependencies: url: https://github.com/ente-io/onnxruntime.git ref: ios_only open_mail_app: ^0.4.5 - package_info_plus: ^4.1.0 + package_info_plus: ^8.2.1 page_transition: ^2.0.2 panorama: git: