[mob] streaming feedbacks resolved (#5112)

## Description

This PR deals with following:

- [x] Android Artifacts fixes
- [x] Queuing Fixes
- [x] Document functions better
- [x] Make UX similar to native video player
- [x] Check for seekbar changes


## Tests
This commit is contained in:
Neeraj 2025-02-20 11:36:39 +05:30 committed by GitHub
commit cdfdc83083
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 542 additions and 492 deletions

View File

@ -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

View File

@ -239,7 +239,7 @@ Future<void> _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");

View File

@ -50,7 +50,7 @@ class PreviewVideoStore {
final cacheManager = DefaultCacheManager();
final videoCacheManager = VideoCacheManager.instance;
LinkedHashSet<EnteFile> files = LinkedHashSet();
LinkedHashSet<EnteFile> 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<void> _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");
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();
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);
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<void> putFilesForPreviewCreation() async {
// generate stream for all files after cutoff date
Future<void> _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);
}
}

View File

@ -244,6 +244,7 @@ class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
}
});
_chewieController = ChewieController(
progressIndicatorDelay: const Duration(milliseconds: 200),
videoPlayerController: _videoPlayerController!,
aspectRatio: _videoPlayerController!.value.aspectRatio,
autoPlay: widget.autoPlay!,
@ -254,6 +255,7 @@ class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
customControls: VideoControls(
file: widget.file,
onStreamChange: widget.onStreamChange,
playbackCallback: widget.playbackCallback,
),
);
return Container(

View File

@ -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<StatefulWidget> createState() {
@ -25,9 +32,10 @@ class VideoControls extends StatefulWidget {
}
}
class _VideoControlsState extends State<VideoControls> {
VideoPlayerValue? _latestValue;
bool _hideStuff = true;
class _VideoControlsState extends State<VideoControls>
with SingleTickerProviderStateMixin {
late PlayerNotifier notifier;
late VideoPlayerValue _latestValue;
Timer? _hideTimer;
Timer? _initTimer;
Timer? _showAfterExpandCollapseTimer;
@ -36,34 +44,35 @@ class _VideoControlsState extends State<VideoControls> {
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<PlayerNotifier>(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<VideoControls> {
child: GestureDetector(
onTap: () => _cancelAndRestartTimer(),
child: AbsorbPointer(
absorbing: _hideStuff,
absorbing: notifier.hideStuff,
child: Stack(
children: <Widget>[
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: <Widget>[
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<VideoControls> {
@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<VideoControls> {
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<VideoControls> {
_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<VideoControls> {
_startHideTimer();
setState(() {
_hideStuff = false;
notifier.hideStuff = false;
_displayTapped = true;
});
widget.playbackCallback?.call(notifier.hideStuff);
}
Future<void> _initialize() async {
@ -216,25 +271,28 @@ class _VideoControlsState extends State<VideoControls> {
_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<VideoControls> {
});
} else {
if (isFinished) {
controller.seekTo(const Duration(seconds: 0));
controller.seekTo(Duration.zero);
}
controller.play();
}
@ -255,19 +313,32 @@ class _VideoControlsState extends State<VideoControls> {
}
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<VideoControls> {
} else {
_displayBufferingIndicator = controller.value.isBuffering;
}
setState(() {
_latestValue = controller.value;
});
}
Widget _buildProgressBar() {
final colorScheme = getEnteColorScheme(context);
return Expanded(
child: CustomProgressBar(
controller,
onDragStart: () {
setState(() {
_dragging = true;
});
_hideTimer?.cancel();
},
onDragUpdate: () {
_hideTimer?.cancel();
},
onDragEnd: () {
setState(() {
_dragging = false;
});
_startHideTimer();
},
colors: ChewieProgressColors(
playedColor: colorScheme.primary300,
handleColor: backgroundElevatedLight,
bufferedColor: backgroundElevatedLight.withOpacity(0.5),
backgroundColor: fillMutedDark,
),
draggableProgressBar: chewieController.draggableProgressBar,
),
);
}
}
class _SeekBarAndDuration extends StatelessWidget {
final VideoPlayerController? controller;
final VideoPlayerValue? latestValue;
final Function(bool) updateDragging;
const _SeekBarAndDuration({
required this.controller,
required this.latestValue,
required this.updateDragging,
class CenterPlayButton extends StatelessWidget {
const CenterPlayButton({
super.key,
required this.backgroundColor,
this.iconColor,
required this.show,
required this.isPlaying,
required this.isFinished,
this.onPressed,
});
final Color backgroundColor;
final Color? iconColor;
final bool show;
final bool isPlaying;
final bool isFinished;
final VoidCallback? onPressed;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
),
return AnimatedOpacity(
opacity: show ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: Container(
padding: const EdgeInsets.fromLTRB(
16,
4,
16,
4,
),
width: 54,
height: 54,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
borderRadius: const BorderRadius.all(
Radius.circular(8),
),
shape: BoxShape.circle,
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,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onPressed,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
switchInCurve: Curves.easeInOutQuart,
switchOutCurve: Curves.easeInOutQuart,
child: isPlaying
? const Icon(
Icons.pause,
size: 32,
key: ValueKey("pause"),
color: Colors.white,
)
: const Icon(
Icons.play_arrow,
size: 36,
key: ValueKey("play"),
color: Colors.white,
),
),
],
),
),
);
}
/// 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) {
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()));
});
},
divisions: 4500,
onChangeEnd: (value) async {
await widget.controller.seekTo(Duration(seconds: value.toInt()));
if (_controllerWasPlaying) {
await widget.controller.play();
}
widget.updateDragging(false);
},
allowedInteraction: SliderInteraction.tapAndSlide,
),
);
}
}
class _PlayPauseButton extends StatefulWidget {
final void Function() playPause;
final bool isPlaying;
const _PlayPauseButton(
this.playPause,
this.isPlaying,
);
@override
State<_PlayPauseButton> createState() => _PlayPauseButtonState();
}
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,
),
),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: widget.playPause,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
switchInCurve: Curves.easeInOutQuart,
switchOutCurve: Curves.easeInOutQuart,
child: 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,
),
),
),
),
);

View File

@ -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,
);
}
}

View File

@ -210,10 +210,10 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
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(),

View File

@ -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: "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"
ffi:
dependency: transitive
description:

View File

@ -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: "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:

View File

@ -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:

View File

@ -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:

View File

@ -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: