From 445f199af4b4400af5ea5853a193fb01d2c036da Mon Sep 17 00:00:00 2001 From: Renarde-dev Date: Mon, 4 Aug 2025 13:42:19 +0200 Subject: [PATCH 01/15] Upgrade player - ported from receive-image-share Need specific fix from image-share branch, does not compile !!! --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 83524b9..b407d8d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,7 +56,7 @@ dependencies: # Utils mime_type: ^1.0.0 # Check mime type of files (differentiate photos from videos) - video_player: ^2.4.7 # Read video files in fullscreen mode + video_player: ^2.10.0 # Read video files in fullscreen mode chewie: ^1.8.1 # Video player with options flutter_image_compress: ^2.4.0 # Remove metadata permission_handler: ^12.0.0+1 # Check and asks for permissions From 336b5f7a3f0cdef1c532c79601ddb30d18c3eaa3 Mon Sep 17 00:00:00 2001 From: Renarde-dev Date: Fri, 22 Aug 2025 15:01:04 +0200 Subject: [PATCH 02/15] Updating flutter and video player seems to fix the issue without migrating to VLC --- .fvmrc | 2 +- l10n.yaml | 1 - pubspec.lock | 176 +++++++++++++++++++++++++-------------------------- pubspec.yaml | 2 +- 4 files changed, 90 insertions(+), 91 deletions(-) diff --git a/.fvmrc b/.fvmrc index 3ca65ff..00359a4 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.32.8" + "flutter": "3.35.1" } \ No newline at end of file diff --git a/l10n.yaml b/l10n.yaml index 96d3298..9c5154c 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,4 +1,3 @@ -synthetic-package: false arb-dir: l10n template-arb-file: app_en.arb untranslated-messages-file: l10n/untranslated.json diff --git a/pubspec.lock b/pubspec.lock index 4f65c87..827c98b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" cached_network_image: dependency: "direct main" description: @@ -125,10 +125,10 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: "051849e2bd7c7b3bc5844ea0d096609ddc3a859890ec3a9ac4a65a2620cc1f99" + sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "6.1.5" connectivity_plus_platform_interface: dependency: transitive description: @@ -221,18 +221,18 @@ packages: dependency: "direct main" description: name: dio - sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 url: "https://pub.dev" source: hosted - version: "5.8.0+1" + version: "5.9.0" dio_cookie_manager: dependency: "direct main" description: name: dio_cookie_manager - sha256: "47cacbf6a783c263bfa7cd7d08101e93127d87760ddb003ba289162f7be0f679" + sha256: d39c16abcc711c871b7b29bd51c6b5f3059ef39503916c6a9df7e22c4fc595e0 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.3.0" dio_web_adapter: dependency: transitive description: @@ -293,10 +293,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: ef9908739bdd9c476353d6adff72e88fd00c625f5b959ae23f7567bd5137db0a + sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22 url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.3.2" file_selector_linux: dependency: transitive description: @@ -309,10 +309,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" + sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" url: "https://pub.dev" source: hosted - version: "0.9.4+3" + version: "0.9.4+4" file_selector_platform_interface: dependency: transitive description: @@ -455,10 +455,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab" url: "https://pub.dev" source: hosted - version: "2.0.28" + version: "2.0.29" flutter_secure_storage: dependency: "direct main" description: @@ -527,10 +527,10 @@ packages: dependency: transitive description: name: flutter_spinkit - sha256: d2696eed13732831414595b98863260e33e8882fc069ee80ec35d4ac9ddb0472 + sha256: "77850df57c00dc218bfe96071d576a8babec24cf58b2ed121c83cca4a2fdce7f" url: "https://pub.dev" source: hosted - version: "5.2.1" + version: "5.2.2" flutter_test: dependency: "direct dev" description: flutter @@ -545,10 +545,10 @@ packages: dependency: "direct main" description: name: font_awesome_flutter - sha256: d3a89184101baec7f4600d58840a764d2ef760fe1c5a20ef9e6b0e9b24a07a3a + sha256: "27af5982e6c510dec1ba038eff634fa284676ee84e3fd807225c80c4ad869177" url: "https://pub.dev" source: hosted - version: "10.8.0" + version: "10.10.0" gal: dependency: "direct main" description: @@ -593,10 +593,10 @@ packages: dependency: transitive description: name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" http_parser: dependency: transitive description: @@ -609,66 +609,66 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "6fae381e6af2bbe0365a5e4ce1db3959462fa0c4d234facf070746024bb80c8d" + sha256: e83b2b05141469c5e19d77e1dfa11096b6b1567d09065b2265d7c6904560050c url: "https://pub.dev" source: hosted - version: "0.8.12+24" + version: "0.8.13" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.1.0" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e url: "https://pub.dev" source: hosted - version: "0.8.12+2" + version: "0.8.13" image_picker_linux: dependency: transitive description: name: image_picker_linux - sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" url: "https://pub.dev" source: hosted - version: "0.2.1+2" + version: "0.2.2" image_picker_macos: dependency: transitive description: name: image_picker_macos - sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 url: "https://pub.dev" source: hosted - version: "0.2.1+2" + version: "0.2.2" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665" url: "https://pub.dev" source: hosted - version: "2.10.1" + version: "2.11.0" image_picker_windows: dependency: transitive description: name: image_picker_windows - sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.2" intl: dependency: "direct main" description: @@ -705,26 +705,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.1" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -841,18 +841,18 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" url: "https://pub.dev" source: hosted - version: "8.3.0" + version: "8.3.1" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" path: dependency: "direct main" description: @@ -881,10 +881,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" path_provider_linux: dependency: transitive description: @@ -961,10 +961,10 @@ packages: dependency: transitive description: name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.0.1" photo_view: dependency: "direct main" description: @@ -993,10 +993,10 @@ packages: dependency: "direct main" description: name: provider - sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "6.1.5+1" pub_semver: dependency: transitive description: @@ -1042,18 +1042,18 @@ packages: dependency: "direct main" description: name: share_plus - sha256: b2961506569e28948d75ec346c28775bb111986bb69dc6a20754a457e3d97fa0 + sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1 url: "https://pub.dev" source: hosted - version: "11.0.0" + version: "11.1.0" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "1032d392bc5d2095a77447a805aa3f804d2ae6a4d5eef5e6ebb3bd94c1bc19ef" + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.0" shared_preferences: dependency: "direct main" description: @@ -1066,10 +1066,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" + sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e" url: "https://pub.dev" source: hosted - version: "2.4.10" + version: "2.4.11" shared_preferences_foundation: dependency: transitive description: @@ -1143,18 +1143,18 @@ packages: dependency: transitive description: name: sqflite_android - sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2+2" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" url: "https://pub.dev" source: hosted - version: "2.5.5" + version: "2.5.6" sqflite_darwin: dependency: transitive description: @@ -1215,10 +1215,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" timezone: dependency: transitive description: @@ -1255,18 +1255,18 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" + sha256: "0aedad096a85b49df2e4725fa32118f9fa580f3b14af7a2d2221896a02cd5656" url: "https://pub.dev" source: hosted - version: "6.3.16" + version: "6.3.17" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 url: "https://pub.dev" source: hosted - version: "6.3.3" + version: "6.3.4" url_launcher_linux: dependency: transitive description: @@ -1279,10 +1279,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.3" url_launcher_platform_interface: dependency: transitive description: @@ -1319,10 +1319,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" video_player: dependency: "direct main" description: @@ -1335,18 +1335,18 @@ packages: dependency: transitive description: name: video_player_android - sha256: "9d5af2d233ba69df803dcf2f31e66d9a4c6206f2d96178df06b09b0ec3056b39" + sha256: "53f3b57c7ac88c18e6074d0f94c7146e128c515f0a4503c3061b8e71dea3a0f2" url: "https://pub.dev" source: hosted - version: "2.8.9" + version: "2.8.12" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "9fedd55023249f3a02738c195c906b4e530956191febf0838e37d0dac912f953" + sha256: f9a780aac57802b2892f93787e5ea53b5f43cc57dc107bee9436458365be71cd url: "https://pub.dev" source: hosted - version: "2.8.0" + version: "2.8.4" video_player_platform_interface: dependency: transitive description: @@ -1367,10 +1367,10 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.0.2" wakelock_plus: dependency: transitive description: @@ -1415,26 +1415,26 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: "9573ad97890d199ac3ab32399aa33a5412163b37feb573eb5b0a76b35e9ffe41" + sha256: "0a42444056b24ed832bdf3442d65c5194f6416f7e782152384944053c2ecc9a3" url: "https://pub.dev" source: hosted - version: "4.8.2" + version: "4.10.0" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - sha256: f0dc2dc3a2b1e3a6abdd6801b9355ebfeb3b8f6cde6b9dc7c9235909c4a1f147 + sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0" url: "https://pub.dev" source: hosted - version: "2.13.1" + version: "2.14.0" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "71523b9048cf510cfa1fd4e0a3fa5e476a66e0884d5df51d59d5023dba237107" + sha256: fb46db8216131a3e55bcf44040ca808423539bc6732e7ed34fb6d8044e3d512f url: "https://pub.dev" source: hosted - version: "3.22.1" + version: "3.23.0" win32: dependency: transitive description: @@ -1495,10 +1495,10 @@ packages: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.6.1" yaml: dependency: transitive description: @@ -1508,5 +1508,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0 <4.0.0" - flutter: ">=3.32.8" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.1" diff --git a/pubspec.yaml b/pubspec.yaml index b407d8d..9fb024d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,7 +7,7 @@ version: 2.4.1+241 environment: sdk: ">=3.0.0 <4.0.0" - flutter: 3.32.8 + flutter: 3.35.1 dependencies: flutter: From 145197f7dce2a0c27cdf1a4079aa4a189233bbd9 Mon Sep 17 00:00:00 2001 From: Renarde-dev Date: Mon, 1 Sep 2025 09:45:31 +0200 Subject: [PATCH 03/15] Bump to flutter 3.35.2 and update dependencies Flutter minor bump https://github.com/flutter/flutter/blob/master/CHANGELOG.md#3352 workmanager 0.8 -> 0.9.0+3 (explicit upgrade) https://pub.dev/packages/workmanager/changelog --- .fvmrc | 2 +- pubspec.lock | 58 ++++++++++++++++++++++++++-------------------------- pubspec.yaml | 4 ++-- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.fvmrc b/.fvmrc index 00359a4..2bb4682 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.35.1" + "flutter": "3.35.2" } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 827c98b..423d202 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -418,10 +418,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: "20ca0a9c82ce0c855ac62a2e580ab867f3fbea82680a90647f7953832d0850ae" + sha256: a9966c850de5e445331b854fa42df96a8020066d67f125a5964cbc6556643f68 url: "https://pub.dev" source: hosted - version: "19.4.0" + version: "19.4.1" flutter_local_notifications_linux: dependency: transitive description: @@ -455,10 +455,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab" + sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31 url: "https://pub.dev" source: hosted - version: "2.0.29" + version: "2.0.30" flutter_secure_storage: dependency: "direct main" description: @@ -511,10 +511,10 @@ packages: dependency: "direct main" description: name: flutter_slidable - sha256: ab7dbb16f783307c9d7762ede2593ce32c220ba2ba0fd540a3db8e9a3acba71a + sha256: e6bd17290cf0d011f9ed66c74d4159b8fe3b3050afedac0f11fab1ba8687e710 url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.1" flutter_speed_dial: dependency: "direct main" description: @@ -617,10 +617,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: e83b2b05141469c5e19d77e1dfa11096b6b1567d09065b2265d7c6904560050c + sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" url: "https://pub.dev" source: hosted - version: "0.8.13" + version: "0.8.13+1" image_picker_for_web: dependency: transitive description: @@ -873,10 +873,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" url: "https://pub.dev" source: hosted - version: "2.2.17" + version: "2.2.18" path_provider_foundation: dependency: transitive description: @@ -1066,10 +1066,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e" + sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74 url: "https://pub.dev" source: hosted - version: "2.4.11" + version: "2.4.12" shared_preferences_foundation: dependency: transitive description: @@ -1255,10 +1255,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "0aedad096a85b49df2e4725fa32118f9fa580f3b14af7a2d2221896a02cd5656" + sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7" url: "https://pub.dev" source: hosted - version: "6.3.17" + version: "6.3.18" url_launcher_ios: dependency: transitive description: @@ -1335,10 +1335,10 @@ packages: dependency: transitive description: name: video_player_android - sha256: "53f3b57c7ac88c18e6074d0f94c7146e128c515f0a4503c3061b8e71dea3a0f2" + sha256: "59e5a457ddcc1688f39e9aef0efb62aa845cf0cbbac47e44ac9730dc079a2385" url: "https://pub.dev" source: hosted - version: "2.8.12" + version: "2.8.13" video_player_avfoundation: dependency: transitive description: @@ -1391,10 +1391,10 @@ packages: dependency: transitive description: name: watcher - sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" + sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" web: dependency: transitive description: @@ -1415,10 +1415,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: "0a42444056b24ed832bdf3442d65c5194f6416f7e782152384944053c2ecc9a3" + sha256: "9a25f6b4313978ba1c2cda03a242eea17848174912cfb4d2d8ee84a556f248e3" url: "https://pub.dev" source: hosted - version: "4.10.0" + version: "4.10.1" webview_flutter_platform_interface: dependency: transitive description: @@ -1455,34 +1455,34 @@ packages: dependency: "direct main" description: name: workmanager - sha256: dffcbbfd99af17271ca52d8a3767ba8a1000b357f2774db8ed5d58879cb9783a + sha256: "065673b2a465865183093806925419d311a9a5e0995aa74ccf8920fd695e2d10" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.9.0+3" workmanager_android: dependency: transitive description: name: workmanager_android - sha256: a6447816c41aa0ad8f6fa29b62d09a528891cf0737296397e1caae2523ced7dc + sha256: "9ae744db4ef891f5fcd2fb8671fccc712f4f96489a487a1411e0c8675e5e8cb7" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.9.0+2" workmanager_apple: dependency: transitive description: name: workmanager_apple - sha256: "9c99003682a8423bdc122ff4bae891e24574d1e676ea302d2ef0493266228461" + sha256: "1cc12ae3cbf5535e72f7ba4fde0c12dd11b757caf493a28e22d684052701f2ca" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.9.1+2" workmanager_platform_interface: dependency: transitive description: name: workmanager_platform_interface - sha256: b1f0a868f1117511a23b0eb4220f08134152433b51a83eed479b1d68bbe23789 + sha256: f40422f10b970c67abb84230b44da22b075147637532ac501729256fcea10a47 url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.9.1+1" xdg_directories: dependency: transitive description: @@ -1509,4 +1509,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.9.0 <4.0.0" - flutter: ">=3.35.1" + flutter: ">=3.35.2" diff --git a/pubspec.yaml b/pubspec.yaml index 9fb024d..65dafe7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,7 +7,7 @@ version: 2.4.1+241 environment: sdk: ">=3.0.0 <4.0.0" - flutter: 3.35.1 + flutter: 3.35.2 dependencies: flutter: @@ -51,7 +51,7 @@ dependencies: device_info_plus: ^11.5.0 # Get device info (version flutter_local_notifications: ^19.1.0 # Throws notifications on download or upload open_filex: ^4.4.0 # Open files with devices apps - workmanager: ^0.8.0 # Background processes (auto upload) + workmanager: ^0.9.0+3 # Background processes (auto upload) gal: ^2.3.1 # Download images # Utils From e2a745a5b16ae009a3ed4e8077aa973446502a69 Mon Sep 17 00:00:00 2001 From: Renarde-dev Date: Tue, 2 Sep 2025 09:46:24 +0200 Subject: [PATCH 04/15] Remove deprecated WorkManager debug flag Add a section about debugging in CONTRIBUTING.md --- docs/CONTRIBUTING.md | 17 ++++++++++------- lib/services/auto_upload_manager.dart | 5 +---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 4288a87..c3edd84 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -18,15 +18,13 @@ flutter config --no-analytics Using [fvm](https://fvm.app/) : ```sh -fvm install 2.27.4 +fvm install 3.27.4 cd -fvm use 2.27.4 +fvm use 3.27.4 ``` ### Android Studio -*(Optional - enable wayland support)* : Add `-Dawt.toolkit.name=WLToolkit` to the Custom VM Options. - Install the flutter plugin and restart, then go to **Settings > Languages & Framework > Android SDK > SDK Tools** and install the following : - Android SDK Build-Tools @@ -48,6 +46,13 @@ If Android studio doesn't pickup the path of the flutter toolchain or dart : In **Settings > Languages & Framework > Flutter** set the flutter SDK path using the absolute path of your install. And in **Settings > Languages & Framework > Dart** specify the Dark SDK path `/bin/cache/dart-sdk` +## Debugging + +By default running the app via Android Studio or `flutter run` will use the debug APK (located in `build/app/outputs/flutter-apk/app-debug.apk`). +Background tasks are done with [workmanager](https://docs.page/fluttercommunity/flutter_workmanager) debugging must be enabled via the kotlin flag `WorkmanagerDebug.setCurrent(LoggingDebugHandler())` + +See more info in https://docs.page/fluttercommunity/flutter_workmanager/debugging + ## Contributing guidelines Contribution must target the `develop` branch and must have documented changes. @@ -56,6 +61,4 @@ if you are fixing an existing issue or bug, tag it in your merge request (exampl if you are doing maintenance work, be sure to add the changelog of the libraries you are updating. -Translation are done via [crowdin](https://crowdin.com/project/piwigo-ng) - - +Translation are done via [crowdin](https://crowdin.com/project/piwigo-ng) \ No newline at end of file diff --git a/lib/services/auto_upload_manager.dart b/lib/services/auto_upload_manager.dart index ca9a94c..69194b6 100644 --- a/lib/services/auto_upload_manager.dart +++ b/lib/services/auto_upload_manager.dart @@ -462,8 +462,5 @@ void callbackDispatcher() { } void initializeWorkManager() { - Workmanager().initialize( - callbackDispatcher, // The top level function, aka callbackDispatcher - isInDebugMode: kDebugMode, - ); + Workmanager().initialize(callbackDispatcher); // The top level function, aka callbackDispatcher } From 94cf9491d3f78810dc3596fddae443efe878d16b Mon Sep 17 00:00:00 2001 From: Renarde-dev Date: Tue, 2 Sep 2025 15:28:54 +0200 Subject: [PATCH 05/15] Prepare migration to libvlc and bump AGP Add proguard rules, Pass modern minify args Ask gradle to shrink android resources Default to share libc++ lib Add vlc player as a dependency Remove experimental option android.jetifier.ignorelist=bcprov-jdk15on Since the toolchain is set to Java17 with a resolver it should be ok to remove it --- android/app/build.gradle.kts | 13 +++++++++++++ android/app/proguard-rules.pro | 1 + android/gradle.properties | 1 - android/settings.gradle.kts | 2 +- pubspec.lock | 16 ++++++++++++++++ pubspec.yaml | 7 +++++-- 6 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 android/app/proguard-rules.pro diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 334d35b..d444f75 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -21,6 +21,10 @@ if (keystorePropertiesFile.exists()) { } android { + packaging { + jniLibs.pickFirsts.add("lib/**/libc++_shared.so") + } + namespace = "com.remi.piwigo_ng" //compileSdkVersion flutter.compileSdkVersion //ndkVersion = flutter.ndkVersion @@ -55,6 +59,15 @@ android { buildTypes { release { + // Compress libs + isMinifyEnabled = true + isShrinkResources = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + getDefaultProguardFile("proguard-android.txt"), + "proguard-rules.pro" + ) + signingConfig = signingConfigs.getByName("release") } } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..b714dae --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1 @@ +-keep class org.videolan.libvlc.** { *; } \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties index 9e0749c..b1f0e39 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -12,7 +12,6 @@ # org.gradle.parallel=true #Tue Apr 22 14:46:56 CEST 2025 android.enableJetifier=true -android.jetifier.ignorelist=bcprov-jdk15on android.nonFinalResIds=false android.nonTransitiveRClass=false android.useAndroidX=true diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index e65186f..664923a 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -19,7 +19,7 @@ pluginManagement { plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.12.1" apply false + id("com.android.application") version "8.12.2" apply false id("org.jetbrains.kotlin.android") version "2.2.0" apply false } diff --git a/pubspec.lock b/pubspec.lock index 423d202..468d1af 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -536,6 +536,22 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_vlc_player: + dependency: "direct main" + description: + name: flutter_vlc_player + sha256: c2554ae0de8133ff5c6d174381f8097f482cdb51ea5aa75d03ee139b71490b53 + url: "https://pub.dev" + source: hosted + version: "7.4.3" + flutter_vlc_player_platform_interface: + dependency: transitive + description: + name: flutter_vlc_player_platform_interface + sha256: "99fbb806f86ce1c53ba007157c183104a100aa643eda0cec7e71118bf0460578" + url: "https://pub.dev" + source: hosted + version: "2.0.5" flutter_web_plugins: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 65dafe7..693ad92 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,8 +56,6 @@ dependencies: # Utils mime_type: ^1.0.0 # Check mime type of files (differentiate photos from videos) - video_player: ^2.10.0 # Read video files in fullscreen mode - chewie: ^1.8.1 # Video player with options flutter_image_compress: ^2.4.0 # Remove metadata permission_handler: ^12.0.0+1 # Check and asks for permissions share_plus: ^11.0.0 # Share files @@ -66,6 +64,11 @@ dependencies: provider: ^6.0.3 # Notifiers for theme and language changes listen_sharing_intent: ^1.9.2 # Receive sharing from other apps + # Video player + video_player: ^2.10.0 # Read video files in fullscreen mode + chewie: ^1.8.1 # Video player with options + flutter_vlc_player: ^7.4.3 # libvlc player + # Translations flutter_localizations: sdk: flutter From 16df65956e0d3100af5af15f929af4051a0a372b Mon Sep 17 00:00:00 2001 From: Renarde-dev Date: Wed, 1 Oct 2025 13:44:37 +0200 Subject: [PATCH 06/15] Update to flutter 3.35.5 --- .fvmrc | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.fvmrc b/.fvmrc index 2bb4682..1d108d2 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.35.2" + "flutter": "3.35.5" } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 693ad92..298cf78 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,7 +7,7 @@ version: 2.4.1+241 environment: sdk: ">=3.0.0 <4.0.0" - flutter: 3.35.2 + flutter: 3.35.5 dependencies: flutter: From 6d280db758ecbcd67bf1cd02d1e04ad525aae2fa Mon Sep 17 00:00:00 2001 From: Renarde-dev Date: Wed, 1 Oct 2025 13:45:31 +0200 Subject: [PATCH 07/15] Add Vlc Player This player implementation support fullscreen with media controls and seekbar --- lib/views/image/vlc_video_player.dart | 192 ++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 lib/views/image/vlc_video_player.dart diff --git a/lib/views/image/vlc_video_player.dart b/lib/views/image/vlc_video_player.dart new file mode 100644 index 0000000..a787743 --- /dev/null +++ b/lib/views/image/vlc_video_player.dart @@ -0,0 +1,192 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_vlc_player/flutter_vlc_player.dart'; + +class VlcVideoPlayer extends StatefulWidget { + const VlcVideoPlayer({ + super.key, + required this.videoUrl, + required this.thumbnailUrl, + }); + final String videoUrl; + final String? thumbnailUrl; + + @override + State createState() => VlcVideoPlayerState(); +} + +class VlcVideoPlayerState extends State { + late VlcPlayerController vlcController; + late Duration videoDuration; + late Duration videoPosition; + double opacityLevel = 0.0; // Start with controls faded out + + @override + void initState() { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); + super.initState(); + vlcController = VlcPlayerController.network( + widget.videoUrl, + hwAcc: HwAcc.auto, + autoPlay: true, + options: VlcPlayerOptions(), + ); + videoDuration = Duration(seconds: 0); // Pre init duration is null + videoPosition = Duration(seconds: 0); + vlcController.addListener(() { + // Only update if it's visible + if (vlcController.value.isInitialized || opacityLevel != 0) { + setState(() { + videoDuration = vlcController.value.duration; + videoPosition = vlcController.value.position; + }); + } + }); + } + + void _changeOpacity() { + setState(() => opacityLevel = opacityLevel == 0.0 ? 1.0 : 0.0); + } + + // Convert a Duration to h:mm:ss / mm:ss String format + String formatDuration(Duration d) { + var prettyDuration = (d.inHours > 1 ? "${d.inHours}:" : ''); + prettyDuration += "${d.inMinutes % 60 < 10 ? "0" : ''}${d.inMinutes % 60}:"; + prettyDuration += "${d.inSeconds % 60 < 10 ? "0" : ''}${d.inSeconds % 60}"; + return prettyDuration; + } + + Future _mediaAction() async { + switch (vlcController.value.playingState) { + case PlayingState.stopped || PlayingState.ended: + await vlcController.stop().then((_) => vlcController.play()); + _changeOpacity(); + case PlayingState.paused: + await vlcController.play(); + _changeOpacity(); + case PlayingState.playing || PlayingState.buffering: + await vlcController.pause(); + setState(() {}); + case _: // handle all other cases + {} + } + } + + @override + Future dispose() async { + // Reset fullscreen mode + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + super.dispose(); + // Also dispose VLC controller + await vlcController.stopRendererScanning(); + await vlcController.dispose(); + } + + @override + Widget build(BuildContext context) { + final screenSize = MediaQuery.of(context).size; + return Scaffold( + backgroundColor: Colors.black, + body: Stack( + children: [ + VlcPlayer( + controller: vlcController, + placeholder: Center(child: CircularProgressIndicator()), + aspectRatio: screenSize.width / screenSize.height, + ), + // Detect any tap to show or hide controls + GestureDetector(onTap: _changeOpacity), + IgnorePointer( + // Ignore input when not visible + ignoring: opacityLevel == 0.0, + child: AnimatedOpacity( + opacity: opacityLevel, + duration: Duration(milliseconds: 250), // Magic number :( + child: Column( + children: [ + Padding( + padding: EdgeInsetsGeometry.only(top: 8), + child: Align( + alignment: Alignment.topLeft, + child: IconButton( + onPressed: () => Navigator.pop(context), + icon: Icon(Icons.arrow_back, color: Colors.white), + ), + ), + ), + Expanded( // Media button + child: Center( + child: IconButton( + onPressed: _mediaAction, + icon: Icon( + switch (vlcController.value.playingState) { + PlayingState.playing => Icons.pause, + PlayingState.paused => Icons.play_arrow, + PlayingState.stopped => Icons.replay, + PlayingState.error => Icons.error, + _ => Icons.play_arrow, + }, + size: 85, // Magic number :( + color: Colors.white, + ), + ), + ), + ), + Row( // Bottom controls and seekbar + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + onPressed: _mediaAction, + icon: Icon(switch (vlcController.value.playingState) { + PlayingState.playing => Icons.pause, + PlayingState.paused => Icons.play_arrow, + PlayingState.stopped => Icons.replay, + PlayingState.error => Icons.error, + _ => Icons.play_arrow, + }, color: Colors.white), + ), + Text( // May cause issue if video is hour long + formatDuration(videoPosition), + style: TextStyle(color: Colors.white), + ), + Expanded( // Seekbar + child: Slider( + value: videoPosition.inSeconds.toDouble(), + max: videoDuration.inSeconds.toDouble(), + onChanged: (nv) { + setState(() { //convert to Milliseconds since VLC requires ms + vlcController.setTime( + nv.toInt() * Duration.millisecondsPerSecond, + ); + }); + }, + ), + ), + Text( // May cause issue if video is hour long + formatDuration(videoDuration), + style: TextStyle(color: Colors.white), + ), + IconButton( // volume on/off + onPressed: () { + vlcController.setVolume( + vlcController.value.volume == 0 ? 100 : 0, + ); + }, + icon: Icon( + vlcController.value.volume != 0 + ? Icons.volume_up + : Icons.volume_off, + color: Colors.white, + ), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ); + } +} From e19119ac7559192347d9eae9a950249bcb47a167 Mon Sep 17 00:00:00 2001 From: Renarde-dev Date: Wed, 1 Oct 2025 14:12:28 +0200 Subject: [PATCH 08/15] Remove dependencies to other players This commit does not check if the app works, in fact it simply remove chewie and video player from pubspec.yaml --- pubspec.lock | 126 ++++++++++----------------------------------------- pubspec.yaml | 2 - 2 files changed, 23 insertions(+), 105 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 468d1af..6130263 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -97,14 +97,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" - chewie: - dependency: "direct main" - description: - name: chewie - sha256: "19b93a1e60e4ba640a792208a6543f1c7d5b124d011ce0199e2f18802199d984" - url: "https://pub.dev" - source: hosted - version: "1.12.1" clock: dependency: transitive description: @@ -169,14 +161,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" - csslib: - dependency: transitive - description: - name: csslib - sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.dev" - source: hosted - version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -293,10 +277,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22 + sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f url: "https://pub.dev" source: hosted - version: "10.3.2" + version: "10.3.3" file_selector_linux: dependency: transitive description: @@ -418,10 +402,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: a9966c850de5e445331b854fa42df96a8020066d67f125a5964cbc6556643f68 + sha256: "7ed76be64e8a7d01dfdf250b8434618e2a028c9dfa2a3c41dc9b531d4b3fc8a5" url: "https://pub.dev" source: hosted - version: "19.4.1" + version: "19.4.2" flutter_local_notifications_linux: dependency: transitive description: @@ -442,10 +426,10 @@ packages: dependency: transitive description: name: flutter_local_notifications_windows - sha256: ed46d7ae4ec9d19e4c8fa2badac5fe27ba87a3fe387343ce726f927af074ec98 + sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.3" flutter_localizations: dependency: "direct main" description: flutter @@ -511,10 +495,10 @@ packages: dependency: "direct main" description: name: flutter_slidable - sha256: e6bd17290cf0d011f9ed66c74d4159b8fe3b3050afedac0f11fab1ba8687e710 + sha256: ea369262929d3cc6ebf9d8a00c196127966f117fe433a5e5cb47fb08008ca203 url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.3" flutter_speed_dial: dependency: "direct main" description: @@ -540,10 +524,10 @@ packages: dependency: "direct main" description: name: flutter_vlc_player - sha256: c2554ae0de8133ff5c6d174381f8097f482cdb51ea5aa75d03ee139b71490b53 + sha256: "32c0109cc191a97246df759a8804a1051c4ca6909597833506f13587cb3bf1be" url: "https://pub.dev" source: hosted - version: "7.4.3" + version: "7.4.4" flutter_vlc_player_platform_interface: dependency: transitive description: @@ -589,14 +573,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" - html: - dependency: transitive - description: - name: html - sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" - url: "https://pub.dev" - source: hosted - version: "0.15.6" html_unescape: dependency: "direct main" description: @@ -633,10 +609,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" + sha256: "8dfe08ea7fcf7467dbaf6889e72eebd5e0d6711caae201fdac780eb45232cd02" url: "https://pub.dev" source: hosted - version: "0.8.13+1" + version: "0.8.13+3" image_picker_for_web: dependency: transitive description: @@ -721,10 +697,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "11.0.1" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: @@ -1082,10 +1058,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74 + sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e url: "https://pub.dev" source: hosted - version: "2.4.12" + version: "2.4.13" shared_preferences_foundation: dependency: transitive description: @@ -1271,10 +1247,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7" + sha256: "199bc33e746088546a39cc5f36bac5a278c5e53b40cb3196f99e7345fdcfae6b" url: "https://pub.dev" source: hosted - version: "6.3.18" + version: "6.3.22" url_launcher_ios: dependency: transitive description: @@ -1339,46 +1315,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" - video_player: - dependency: "direct main" - description: - name: video_player - sha256: "0d55b1f1a31e5ad4c4967bfaa8ade0240b07d20ee4af1dfef5f531056512961a" - url: "https://pub.dev" - source: hosted - version: "2.10.0" - video_player_android: - dependency: transitive - description: - name: video_player_android - sha256: "59e5a457ddcc1688f39e9aef0efb62aa845cf0cbbac47e44ac9730dc079a2385" - url: "https://pub.dev" - source: hosted - version: "2.8.13" - video_player_avfoundation: - dependency: transitive - description: - name: video_player_avfoundation - sha256: f9a780aac57802b2892f93787e5ea53b5f43cc57dc107bee9436458365be71cd - url: "https://pub.dev" - source: hosted - version: "2.8.4" - video_player_platform_interface: - dependency: transitive - description: - name: video_player_platform_interface - sha256: cf2a1d29a284db648fd66cbd18aacc157f9862d77d2cc790f6f9678a46c1db5a - url: "https://pub.dev" - source: hosted - version: "6.4.0" - video_player_web: - dependency: transitive - description: - name: video_player_web - sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" - url: "https://pub.dev" - source: hosted - version: "2.4.0" vm_service: dependency: transitive description: @@ -1387,22 +1323,6 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" - wakelock_plus: - dependency: transitive - description: - name: wakelock_plus - sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678 - url: "https://pub.dev" - source: hosted - version: "1.3.2" - wakelock_plus_platform_interface: - dependency: transitive - description: - name: wakelock_plus_platform_interface - sha256: e10444072e50dbc4999d7316fd303f7ea53d31c824aa5eb05d7ccbdd98985207 - url: "https://pub.dev" - source: hosted - version: "1.2.3" watcher: dependency: transitive description: @@ -1431,10 +1351,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: "9a25f6b4313978ba1c2cda03a242eea17848174912cfb4d2d8ee84a556f248e3" + sha256: "3c4eb4fcc252b40c2b5ce7be20d0481428b70f3ff589b0a8b8aaeb64c6bed701" url: "https://pub.dev" source: hosted - version: "4.10.1" + version: "4.10.2" webview_flutter_platform_interface: dependency: transitive description: @@ -1447,10 +1367,10 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: fb46db8216131a3e55bcf44040ca808423539bc6732e7ed34fb6d8044e3d512f + sha256: fea63576b3b7e02b2df8b78ba92b48ed66caec2bb041e9a0b1cbd586d5d80bfd url: "https://pub.dev" source: hosted - version: "3.23.0" + version: "3.23.1" win32: dependency: transitive description: @@ -1525,4 +1445,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.9.0 <4.0.0" - flutter: ">=3.35.2" + flutter: ">=3.35.5" diff --git a/pubspec.yaml b/pubspec.yaml index 298cf78..de4b5fb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,8 +65,6 @@ dependencies: listen_sharing_intent: ^1.9.2 # Receive sharing from other apps # Video player - video_player: ^2.10.0 # Read video files in fullscreen mode - chewie: ^1.8.1 # Video player with options flutter_vlc_player: ^7.4.3 # libvlc player # Translations From e8f38b26ad6dc8056edf415c4e4bd9b1c02a1abc Mon Sep 17 00:00:00 2001 From: Renarde-dev Date: Wed, 1 Oct 2025 14:13:29 +0200 Subject: [PATCH 09/15] Remove video_player_page.dart and replace the route with VlcPlayer --- lib/app.dart | 6 +- lib/views/image/video_player_page.dart | 129 ------------------------- lib/views/image/vlc_video_player.dart | 2 + 3 files changed, 5 insertions(+), 132 deletions(-) delete mode 100644 lib/views/image/video_player_page.dart diff --git a/lib/app.dart b/lib/app.dart index fb08499..447ccd7 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -17,7 +17,7 @@ import 'package:piwigo_ng/views/image/image_favorites_page.dart'; import 'package:piwigo_ng/views/image/image_page.dart'; import 'package:piwigo_ng/views/image/image_search_page.dart'; import 'package:piwigo_ng/views/image/image_tags_page.dart'; -import 'package:piwigo_ng/views/image/video_player_page.dart'; +import 'package:piwigo_ng/views/image/vlc_video_player.dart'; import 'package:piwigo_ng/views/settings/auto_upload_page.dart'; import 'package:piwigo_ng/views/settings/privacy_policy_page.dart'; import 'package:piwigo_ng/views/settings/select_language_page.dart'; @@ -192,9 +192,9 @@ Route generateRoute(RouteSettings settings) { ), settings: settings, ); - case VideoPlayerPage.routeName: + case VlcVideoPlayer.routeName: return MaterialPageRoute( - builder: (_) => VideoPlayerPage( + builder: (_) => VlcVideoPlayer( videoUrl: arguments['videoUrl'], thumbnailUrl: arguments['thumbnailUrl'], ), diff --git a/lib/views/image/video_player_page.dart b/lib/views/image/video_player_page.dart deleted file mode 100644 index 0c903d0..0000000 --- a/lib/views/image/video_player_page.dart +++ /dev/null @@ -1,129 +0,0 @@ -import 'package:chewie/chewie.dart'; -import 'package:flutter/material.dart'; -import 'package:piwigo_ng/components/player_controls.dart'; -import 'package:piwigo_ng/services/player_provider.dart'; -import 'package:piwigo_ng/utils/localizations.dart'; -import 'package:provider/provider.dart'; -import 'package:video_player/video_player.dart'; - -class VideoPlayerPage extends StatefulWidget { - const VideoPlayerPage({ - Key? key, - this.videoUrl, - this.thumbnailUrl, - }) : super(key: key); - - static const String routeName = '/image/video_player'; - - final String? videoUrl; - final String? thumbnailUrl; - - @override - State createState() => _VideoPlayerPageState(); -} - -class _VideoPlayerPageState extends State { - /// Duration of overlay animation - final Duration _overlayAnimationDuration = const Duration(milliseconds: 300); - - /// Curve of overlay animation - final Curve _overlayAnimationCurve = Curves.ease; - - late VideoPlayerController _videoPlayerController; - late PlayerProvider _provider; - ChewieController? _chewieController; - bool _showOverlay = true; - - @override - void initState() { - super.initState(); - initializePlayer(); - _provider = PlayerProvider.init(true); - } - - @override - void dispose() { - _videoPlayerController.dispose(); - _chewieController?.dispose(); - super.dispose(); - } - - void _onToggleOverlay([bool? value]) { - setState(() { - if (value != null) { - _showOverlay = value; - } else { - _showOverlay = !_showOverlay; - } - }); - } - - Future initializePlayer() async { - if (widget.videoUrl == null) return; - _videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl!)); - await _videoPlayerController.initialize(); - _createChewieController(); - setState(() {}); - } - - void _createChewieController() { - _chewieController = ChewieController( - videoPlayerController: _videoPlayerController, - autoPlay: true, - allowFullScreen: false, - showControlsOnInitialize: true, - showOptions: false, - hideControlsTimer: const Duration(seconds: 3), - customControls: PlayerControls(onToggleOverlay: _onToggleOverlay), - )..setVolume(0); - } - - @override - Widget build(BuildContext context) { - if (widget.videoUrl == null) { - return Center( - child: Text(appStrings.errorHUD_label), - ); - } - if (_chewieController == null || !_chewieController!.videoPlayerController.value.isInitialized) { - return Center( - child: CircularProgressIndicator(), - ); - } - return Scaffold( - backgroundColor: Colors.black, - body: ChangeNotifierProvider.value( - value: _provider, - child: Stack( - children: [ - Positioned.fill( - child: Chewie( - controller: _chewieController!, - ), - ), - Align( - alignment: Alignment.topLeft, - child: AnimatedSlide( - duration: _overlayAnimationDuration, - curve: _overlayAnimationCurve, - offset: _showOverlay ? Offset.zero : Offset(-1, 0), - child: AnimatedOpacity( - duration: _overlayAnimationDuration, - curve: _overlayAnimationCurve, - opacity: _showOverlay ? 1 : 0, - child: SafeArea( - child: IconButton( - color: Theme.of(context).colorScheme.secondary, - onPressed: () => Navigator.of(context).pop(), - icon: Icon(Icons.close), - ), - ), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/views/image/vlc_video_player.dart b/lib/views/image/vlc_video_player.dart index a787743..52be287 100644 --- a/lib/views/image/vlc_video_player.dart +++ b/lib/views/image/vlc_video_player.dart @@ -11,6 +11,8 @@ class VlcVideoPlayer extends StatefulWidget { final String videoUrl; final String? thumbnailUrl; + static const String routeName = '/image/video_player'; + @override State createState() => VlcVideoPlayerState(); } From aea5468cd01b5ec50befd2a5e947f743c97d056b Mon Sep 17 00:00:00 2001 From: Renarde-dev Date: Wed, 1 Oct 2025 14:28:24 +0200 Subject: [PATCH 10/15] Remove player_controls.dart in favor of using the vlc solution --- lib/components/player_controls.dart | 728 ---------------------------- 1 file changed, 728 deletions(-) delete mode 100644 lib/components/player_controls.dart diff --git a/lib/components/player_controls.dart b/lib/components/player_controls.dart deleted file mode 100644 index 67ba1d9..0000000 --- a/lib/components/player_controls.dart +++ /dev/null @@ -1,728 +0,0 @@ -import 'dart:async'; - -import 'package:chewie/chewie.dart'; -import 'package:flutter/material.dart'; -import 'package:piwigo_ng/services/player_provider.dart'; -import 'package:provider/provider.dart'; -import 'package:video_player/video_player.dart'; - -class PlayerControls extends StatefulWidget { - const PlayerControls({ - Key? key, - this.showPlayButton = true, - this.onToggleOverlay, - }) : super(key: key); - - final Function(bool)? onToggleOverlay; - final bool showPlayButton; - - @override - State createState() { - return _PlayerControlsState(); - } -} - -class _PlayerControlsState extends State - with SingleTickerProviderStateMixin { - late PlayerProvider notifier; - late VideoPlayerValue _latestValue; - double? _latestVolume; - Timer? _hideTimer; - Timer? _initTimer; - late var _subtitlesPosition = Duration.zero; - bool _subtitleOn = false; - Timer? _showAfterExpandCollapseTimer; - bool _dragging = false; - bool _displayTapped = false; - Timer? _bufferingDisplayTimer; - bool _displayBufferingIndicator = false; - - final barHeight = 48.0 * 1.5; - final marginSize = 5.0; - - late VideoPlayerController controller; - ChewieController? _chewieController; - - // We know that _chewieController is set in didChangeDependencies - ChewieController get chewieController => _chewieController!; - - @override - void initState() { - super.initState(); - notifier = Provider.of(context, listen: false); - } - - @override - void didChangeDependencies() { - final oldController = _chewieController; - _chewieController = ChewieController.of(context); - controller = chewieController.videoPlayerController; - - if (oldController != chewieController) { - _dispose(); - _initialize(); - } - - super.didChangeDependencies(); - } - - @override - void dispose() { - _dispose(); - super.dispose(); - } - - void _dispose() { - controller.removeListener(_updateState); - _hideTimer?.cancel(); - _initTimer?.cancel(); - _showAfterExpandCollapseTimer?.cancel(); - } - - void _cancelAndRestartTimer() { - _hideTimer?.cancel(); - _startHideTimer(); - - setState(() { - notifier.hideStuff = false; - widget.onToggleOverlay?.call(true); - _displayTapped = true; - }); - } - - Future _initialize() async { - _subtitleOn = chewieController.subtitle?.isNotEmpty ?? false; - controller.addListener(_updateState); - - _updateState(); - - if (controller.value.isPlaying || chewieController.autoPlay) { - _startHideTimer(); - } - - if (chewieController.showControlsOnInitialize) { - _initTimer = Timer(const Duration(milliseconds: 200), () { - setState(() { - notifier.hideStuff = false; - widget.onToggleOverlay?.call(true); - }); - }); - } - } - - void _onExpandCollapse() { - setState(() { - notifier.hideStuff = true; - widget.onToggleOverlay?.call(false); - - chewieController.toggleFullScreen(); - _showAfterExpandCollapseTimer = - Timer(const Duration(milliseconds: 300), () { - setState(() { - _cancelAndRestartTimer(); - }); - }); - }); - } - - void _playPause() { - final isFinished = _latestValue.position >= _latestValue.duration; - - setState(() { - if (controller.value.isPlaying) { - notifier.hideStuff = false; - widget.onToggleOverlay?.call(true); - _hideTimer?.cancel(); - controller.pause(); - } else { - _cancelAndRestartTimer(); - - if (!controller.value.isInitialized) { - controller.initialize().then((_) { - controller.play(); - }); - } else { - if (isFinished) { - controller.seekTo(Duration.zero); - } - controller.play(); - } - } - }); - } - - void _startHideTimer() { - final hideControlsTimer = chewieController.hideControlsTimer.isNegative - ? ChewieController.defaultHideControlsTimer - : chewieController.hideControlsTimer; - _hideTimer = Timer(hideControlsTimer, () { - setState(() { - notifier.hideStuff = true; - widget.onToggleOverlay?.call(false); - }); - }); - } - - void _bufferingTimerTimeout() { - _displayBufferingIndicator = true; - if (mounted) { - setState(() {}); - } - } - - void _updateState() { - if (!mounted) return; - - // display the progress bar indicator only after the buffering delay if it has been set - if (chewieController.progressIndicatorDelay != null) { - if (controller.value.isBuffering) { - _bufferingDisplayTimer ??= Timer( - chewieController.progressIndicatorDelay!, - _bufferingTimerTimeout, - ); - } else { - _bufferingDisplayTimer?.cancel(); - _bufferingDisplayTimer = null; - _displayBufferingIndicator = false; - } - } else { - _displayBufferingIndicator = controller.value.isBuffering; - } - - setState(() { - _latestValue = controller.value; - _subtitlesPosition = controller.value.position; - }); - } - - String formatDuration(Duration position) { - final ms = position.inMilliseconds; - - int seconds = ms ~/ 1000; - final int hours = seconds ~/ 3600; - seconds = seconds % 3600; - final minutes = seconds ~/ 60; - seconds = seconds % 60; - - final hoursString = hours >= 10 - ? '$hours' - : hours == 0 - ? '00' - : '0$hours'; - - final minutesString = minutes >= 10 - ? '$minutes' - : minutes == 0 - ? '00' - : '0$minutes'; - - final secondsString = seconds >= 10 - ? '$seconds' - : seconds == 0 - ? '00' - : '0$seconds'; - - final formattedTime = - '${hoursString == '00' ? '' : '$hoursString:'}$minutesString:$secondsString'; - - return formattedTime; - } - - @override - Widget build(BuildContext context) { - if (_latestValue.hasError) { - return chewieController.errorBuilder?.call( - context, - chewieController.videoPlayerController.value.errorDescription!, - ) ?? - const Center( - child: Icon( - Icons.error, - color: Colors.white, - size: 42, - ), - ); - } - - return GestureDetector( - onTap: () => _cancelAndRestartTimer(), - child: AbsorbPointer( - absorbing: notifier.hideStuff, - child: Stack( - children: [ - Positioned.fill( - child: IgnorePointer( - child: AnimatedOpacity( - duration: const Duration(milliseconds: 300), - opacity: notifier.hideStuff ? 0.0 : 1.0, - child: Material( - color: Colors.black.withValues(alpha: 0.5), - ), - ), - ), - ), - if (_displayBufferingIndicator) - const Center( - child: CircularProgressIndicator(), - ) - else - _buildHitArea(), - Align( - alignment: Alignment.bottomCenter, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (_subtitleOn) - Transform.translate( - offset: Offset( - 0.0, - notifier.hideStuff ? barHeight * 0.8 : 0.0, - ), - child: - _buildSubtitles(context, chewieController.subtitle!), - ), - _buildBottomBar(context), - ], - ), - ), - ], - ), - ), - ); - } - - Widget _buildSubtitles(BuildContext context, Subtitles subtitles) { - if (!_subtitleOn) { - return const SizedBox(); - } - final currentSubtitle = subtitles.getByPosition(_subtitlesPosition); - if (currentSubtitle.isEmpty) { - return const SizedBox(); - } - - if (chewieController.subtitleBuilder != null) { - return chewieController.subtitleBuilder!( - context, - currentSubtitle.first!.text, - ); - } - - return Padding( - padding: EdgeInsets.all(marginSize), - child: Container( - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: const Color(0x96000000), - borderRadius: BorderRadius.circular(10.0), - ), - child: Text( - currentSubtitle.first!.text.toString(), - style: const TextStyle( - fontSize: 18, - ), - textAlign: TextAlign.center, - ), - ), - ); - } - - Widget _buildBottomBar( - BuildContext context, - ) { - final iconColor = Theme.of(context).textTheme.labelLarge!.color; - return AnimatedOpacity( - opacity: notifier.hideStuff ? 0.0 : 1.0, - duration: const Duration(milliseconds: 300), - child: Padding( - padding: EdgeInsets.only(bottom: 8.0), - child: SafeArea( - bottom: chewieController.isFullScreen, - top: false, - minimum: chewieController.controlsSafeAreaMinimum, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.only(left: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - _buildPosition(iconColor), - if (chewieController.allowMuting) - _buildMuteButton(controller), - const Spacer(), - if (chewieController.allowFullScreen) _buildExpandButton(), - ], - ), - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 16.0), - child: _buildProgressBar(), - ), - ], - ), - ), - ), - ); - } - - Widget _buildMuteButton( - VideoPlayerController controller, - ) { - return AnimatedOpacity( - opacity: notifier.hideStuff ? 0.0 : 1.0, - duration: const Duration(milliseconds: 300), - child: IconButton( - color: Colors.white, - icon: Icon(Icons.volume_up), - selectedIcon: Icon(Icons.volume_off), - isSelected: _latestValue.volume == 0, - onPressed: () { - _cancelAndRestartTimer(); - if (_latestValue.volume == 0) { - controller.setVolume(_latestVolume ?? 0.5); - } else { - _latestVolume = controller.value.volume; - controller.setVolume(0.0); - } - }, - ), - ); - } - - Widget _buildExpandButton() { - return AnimatedOpacity( - opacity: notifier.hideStuff ? 0.0 : 1.0, - duration: const Duration(milliseconds: 300), - child: IconButton( - color: Colors.white, - onPressed: _onExpandCollapse, - icon: Icon(Icons.fullscreen), - selectedIcon: Icon(Icons.fullscreen_exit), - isSelected: chewieController.isFullScreen, - ), - ); - } - - Widget _buildHitArea() { - final bool isFinished = _latestValue.position >= _latestValue.duration; - final bool showPlayButton = - widget.showPlayButton && !_dragging && !notifier.hideStuff; - - return GestureDetector( - onTap: () { - if (_latestValue.isPlaying) { - if (_displayTapped) { - setState(() { - notifier.hideStuff = true; - widget.onToggleOverlay?.call(false); - }); - } else { - _cancelAndRestartTimer(); - } - } else { - setState(() { - notifier.hideStuff = true; - widget.onToggleOverlay?.call(false); - }); - } - }, - child: ColoredBox( - color: Colors.transparent, - child: Center( - child: GestureDetector( - onTap: () { - _playPause(); - }, - child: UnconstrainedBox( - child: AnimatedOpacity( - opacity: showPlayButton ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: DecoratedBox( - decoration: BoxDecoration( - color: Colors.black54, - shape: BoxShape.circle, - ), - child: IconButton( - iconSize: 32, - padding: const EdgeInsets.all(12.0), - icon: isFinished - ? Icon(Icons.replay, color: Colors.white) - : controller.value.isPlaying - ? Icon(Icons.pause, color: Colors.white) - : Icon(Icons.play_arrow, color: Colors.white), - onPressed: _playPause, - ), - ), - ), - ), - ), - ), - ), - ); - } - - Widget _buildPosition(Color? iconColor) { - final position = _latestValue.position; - final duration = _latestValue.duration; - - return RichText( - text: TextSpan( - text: '${formatDuration(position)} ', - children: [ - TextSpan( - text: '/ ${formatDuration(duration)}', - style: TextStyle( - fontSize: 14.0, - color: Colors.white.withValues(alpha: 0.75), - fontWeight: FontWeight.normal, - ), - ) - ], - style: const TextStyle( - fontSize: 14.0, - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - ); - } - - Widget _buildProgressBar() { - return VideoProgressBar( - controller, - barHeight: 4, - handleHeight: 8, - drawShadow: true, - onDragStart: () { - setState(() { - _dragging = true; - }); - _hideTimer?.cancel(); - }, - onDragEnd: () { - setState(() { - _dragging = false; - }); - _startHideTimer(); - }, - colors: ChewieProgressColors( - playedColor: Theme.of(context).colorScheme.secondary.withValues(alpha: 0.7), - handleColor: Theme.of(context).colorScheme.secondary, - bufferedColor: Theme.of(context).colorScheme.primary.withValues(alpha: 0.5), - backgroundColor: Theme.of(context).disabledColor.withValues(alpha: 0.3), - ), - ); - } -} - -class VideoProgressBar extends StatefulWidget { - VideoProgressBar( - this.controller, { - ChewieProgressColors? colors, - this.onDragEnd, - this.onDragStart, - this.onDragUpdate, - Key? key, - required this.barHeight, - required this.handleHeight, - required this.drawShadow, - }) : colors = colors ?? ChewieProgressColors(), - super(key: key); - - final VideoPlayerController controller; - final ChewieProgressColors colors; - final Function()? onDragStart; - final Function()? onDragEnd; - final Function()? onDragUpdate; - - final double barHeight; - final double handleHeight; - final bool drawShadow; - - @override - // ignore: library_private_types_in_public_api - _VideoProgressBarState createState() { - return _VideoProgressBarState(); - } -} - -class _VideoProgressBarState extends State { - void listener() { - if (!mounted) return; - setState(() {}); - } - - bool _controllerWasPlaying = false; - - VideoPlayerController get controller => widget.controller; - - @override - void initState() { - super.initState(); - controller.addListener(listener); - } - - @override - void deactivate() { - controller.removeListener(listener); - super.deactivate(); - } - - void _seekToRelativePosition(Offset globalPosition) { - final box = context.findRenderObject()! as RenderBox; - final Offset tapPos = box.globalToLocal(globalPosition); - final double relative = tapPos.dx / box.size.width; - final Duration position = controller.value.duration * relative; - controller.seekTo(position); - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - onHorizontalDragStart: (DragStartDetails details) { - if (!controller.value.isInitialized) { - return; - } - _controllerWasPlaying = controller.value.isPlaying; - if (_controllerWasPlaying) { - controller.pause(); - } - - widget.onDragStart?.call(); - }, - onHorizontalDragUpdate: (DragUpdateDetails details) { - if (!controller.value.isInitialized) { - return; - } - // Should only seek if the VideoPlayerController cannot be buffering. - // final shouldSeekToRelativePosition = !controller.value.isBuffering; - // if (shouldSeekToRelativePosition) { - // _seekToRelativePosition(details.globalPosition); - // } - _seekToRelativePosition(details.globalPosition); - - widget.onDragUpdate?.call(); - }, - onHorizontalDragEnd: (DragEndDetails details) { - if (_controllerWasPlaying) { - controller.play(); - } - - widget.onDragEnd?.call(); - }, - onTapDown: (TapDownDetails details) { - if (!controller.value.isInitialized) { - return; - } - _seekToRelativePosition(details.globalPosition); - }, - child: Center( - child: Container( - padding: EdgeInsets.symmetric(vertical: 8.0), - width: MediaQuery.of(context).size.width, - color: Colors.transparent, - child: CustomPaint( - painter: _ProgressBarPainter( - value: controller.value, - colors: widget.colors, - barHeight: widget.barHeight, - handleHeight: widget.handleHeight, - drawShadow: widget.drawShadow, - ), - ), - ), - ), - ); - } -} - -class _ProgressBarPainter extends CustomPainter { - _ProgressBarPainter({ - required this.value, - required this.colors, - required this.barHeight, - required this.handleHeight, - required this.drawShadow, - }); - - VideoPlayerValue value; - ChewieProgressColors colors; - - final double barHeight; - final double handleHeight; - final bool drawShadow; - - @override - bool shouldRepaint(CustomPainter painter) { - return true; - } - - @override - void paint(Canvas canvas, Size size) { - final baseOffset = size.height / 2 - barHeight / 2; - - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromPoints( - Offset(0.0, baseOffset), - Offset(size.width, baseOffset + barHeight), - ), - const Radius.circular(4.0), - ), - colors.backgroundPaint, - ); - if (!value.isInitialized) { - return; - } - final double playedPartPercent = - value.position.inMilliseconds / value.duration.inMilliseconds; - final double playedPart = - playedPartPercent > 1 ? size.width : playedPartPercent * size.width; - for (final DurationRange range in value.buffered) { - final double start = range.startFraction(value.duration) * size.width; - final double end = range.endFraction(value.duration) * size.width; - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromPoints( - Offset(start, baseOffset), - Offset(end, baseOffset + barHeight), - ), - const Radius.circular(4.0), - ), - colors.bufferedPaint, - ); - } - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromPoints( - Offset(0.0, baseOffset), - Offset(playedPart, baseOffset + barHeight), - ), - const Radius.circular(4.0), - ), - colors.playedPaint, - ); - - if (drawShadow) { - final Path shadowPath = Path() - ..addOval( - Rect.fromCircle( - center: Offset(playedPart, baseOffset + barHeight / 2), - radius: handleHeight, - ), - ); - - canvas.drawShadow(shadowPath, Colors.black, 0.2, false); - } - - canvas.drawCircle( - Offset(playedPart, baseOffset + barHeight / 2), - handleHeight, - colors.handlePaint, - ); - } -} From 4ef10ecf99b08099dd93d7afabe6fa17425b44cd Mon Sep 17 00:00:00 2001 From: Renarde-dev Date: Wed, 1 Oct 2025 14:45:05 +0200 Subject: [PATCH 11/15] Remove video_view.dart with safe delete, no code seems to use it --- lib/views/image/video_view.dart | 523 -------------------------------- 1 file changed, 523 deletions(-) delete mode 100644 lib/views/image/video_view.dart diff --git a/lib/views/image/video_view.dart b/lib/views/image/video_view.dart deleted file mode 100644 index 0d00b70..0000000 --- a/lib/views/image/video_view.dart +++ /dev/null @@ -1,523 +0,0 @@ -import 'dart:math'; - -import 'package:chewie/chewie.dart'; -import 'package:flutter/material.dart'; -import 'package:piwigo_ng/components/player_controls.dart'; -import 'package:piwigo_ng/services/player_provider.dart'; -import 'package:piwigo_ng/utils/localizations.dart'; -import 'package:piwigo_ng/utils/resources.dart'; -import 'package:piwigo_ng/utils/themes.dart'; -import 'package:provider/provider.dart'; -import 'package:video_player/video_player.dart'; - -class VideoView extends StatefulWidget { - const VideoView({ - Key? key, - this.videoUrl, - this.thumbnailUrl, - this.onToggleOverlay, - this.showOverlay = false, - this.screenPadding = EdgeInsets.zero, - }) : super(key: key); - - final String? videoUrl; - final String? thumbnailUrl; - final Function(bool)? onToggleOverlay; - final bool showOverlay; - final EdgeInsets screenPadding; - - @override - State createState() => _VideoViewState(); -} - -class _VideoViewState extends State { - final Duration _overlayAnimationDuration = const Duration(milliseconds: 300); - final Curve _overlayAnimationCurve = Curves.ease; - late VideoPlayerController _controller; - double _progress = 0; - double _updateProgressInterval = 0.0; - bool _mute = false; - bool _isEnd = false; - - /// Initialize video controller - @override - void initState() { - super.initState(); - _controller = VideoPlayerController.networkUrl( - Uri.parse(widget.videoUrl!), - videoPlayerOptions: VideoPlayerOptions(), - )..initialize().then((_) { - debugPrint("---- controller initialized"); - _controller.addListener(_onControllerUpdated); - _controller.addListener(_checkControllerEnd); - setState(() {}); - }); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - /// Update video progress - /// * Listen to [_controller] - void _onControllerUpdated() async { - if (!mounted) return; - // blocking too many updates - // important !! - final int now = DateTime.now().millisecondsSinceEpoch; - if (_updateProgressInterval > now) return; - _updateProgressInterval = now + 300.0; - - final VideoPlayerController controller = _controller; - - if (!controller.value.isInitialized) return; - - // handle progress indicator - if (controller.value.isPlaying) { - final Duration position = controller.value.position; - if (!mounted) return; - setState(() { - _progress = position.inMilliseconds.ceilToDouble() / controller.value.duration.inMilliseconds.ceilToDouble(); - }); - } - } - - /// Check and handle video end - /// * Listen to [_controller] - void _checkControllerEnd() async { - if (!mounted) return; - if (_controller.value.position.inMilliseconds > 0 && - _controller.value.position.inSeconds >= _controller.value.duration.inSeconds) { - setState(() { - _isEnd = true; - if (!widget.showOverlay) { - widget.onToggleOverlay?.call(true); - } - setState(() { - _progress = 1; - }); - }); - } else { - if (_isEnd) { - setState(() { - _isEnd = false; - }); - } - } - } - - /// Fast rewind action - /// * Player rewind by 5sec - Future _onFastRewind() async { - // Pause player and update overlay - setState(() { - _controller.pause(); - }); - // Rewind 5sec on the player - await _controller.seekTo(_controller.value.position - Duration(seconds: 5)); - // Resume player and update overlay - setState(() { - _controller.play(); - }); - } - - /// Play / pause action - /// * If playing: pause the video - /// * Otherwise: resume / play the video - void _onPlayPause() { - setState(() { - if (_controller.value.isPlaying) { - _controller.pause(); - } else { - _controller.play(); - } - }); - } - - /// Fast forward action - /// * Player forward by 5sec - Future _onFastForward() async { - // Pause player and update overlay - setState(() { - _controller.pause(); - }); - // Rewind 5sec on the player - await _controller.seekTo(_controller.value.position + Duration(seconds: 5)); - setState(() { - // Resume player and update overlay if didn't reached the end - if (!_isEnd) { - _controller.play(); - } - }); - } - - /// Replay action - void _onReplay() { - setState(() { - _controller.play(); - }); - } - - /// Mute / Un-mute action - /// * Switches [_controller] volume from 0 (mute) to 1 (un-mute) - void _onMute() { - final double volume = _mute ? 1.0 : 0.0; - setState(() { - _controller.setVolume(volume); - _mute = !_mute; - }); - } - - /// Update [_progress] when time changed - void _onVideoTimeChanged(double value) { - setState(() { - _progress = value * 0.01; - }); - } - - /// Pauses video player [_controller] while changing time - void _onVideoTimeChangeStart(double value) { - setState(() { - _controller.pause(); - }); - } - - /// Update video player [_controller] when time changed - Future _onVideoTimeChangeEnd(double value) async { - // Parse slider time - final double newValue = max(0, min(value, 99)) * 0.01; - final int millis = (_controller.value.duration.inMilliseconds * newValue).toInt(); - // Change time - await _controller.seekTo(Duration(milliseconds: millis)); - // Resume player - setState(() { - _controller.play(); - }); - } - - /// Get parsed video duration text. - /// * If paused: returns video duration. - /// * If playing: returns video remaining time. - String get _durationText { - late final Duration duration; - if (!_controller.value.isPlaying) { - duration = _controller.value.duration; - } else { - duration = _controller.value.duration - _controller.value.position; - } - int hours = duration.inHours; - int minutes = (duration - Duration(hours: hours)).inMinutes; - int seconds = (duration - Duration(hours: hours) - Duration(minutes: minutes)).inSeconds; - return '${hours > 0 ? '$hours:' : ''}${minutes < 10 ? '0$minutes' : '$minutes'}:${seconds < 10 ? '0$seconds' : '$seconds'}'; - } - - @override - Widget build(BuildContext context) { - if (!_controller.value.isInitialized || _controller.value.hasError) { - return _thumbnail; - } - return LayoutBuilder(builder: (context, constraints) { - return Stack( - alignment: Alignment.center, - fit: StackFit.expand, - children: [ - FittedBox( - fit: BoxFit.contain, - child: SizedBox( - width: _controller.value.size.width, - height: _controller.value.size.height, - child: AspectRatio( - aspectRatio: _controller.value.aspectRatio, - child: VideoPlayer(_controller), - ), - ), - ), - _overlay, - ], - ); - }); - } - - /// Video player overlay - Widget get _overlay { - return IgnorePointer( - ignoring: !widget.showOverlay, - child: AnimatedOpacity( - duration: _overlayAnimationDuration, - curve: _overlayAnimationCurve, - opacity: widget.showOverlay ? 1 : 0, - child: Stack( - alignment: Alignment.center, - children: [ - _overlayBackground, - _overlayCenter, - _overlayBottom, - ], - ), - ), - ); - } - - /// Video player center overlay - /// * Fast rewind - /// * Play / Pause / Replay - /// * Fast forward - Widget get _overlayCenter { - // Check if the player is processing / loading. - if ((_controller.value.isBuffering && !_isEnd) || (_isEnd && _controller.value.isPlaying)) { - return Center(child: CircularProgressIndicator()); - } - // If player has ended, show the replay button - if (_isEnd && !_controller.value.isPlaying) { - return Center( - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: _onReplay, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Icon( - Icons.replay, - size: 48, - color: Colors.white, - shadows: AppShadows.icon, - ), - ), - ), - ); - } - // Video player is playing - return Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: _onFastRewind, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Icon( - Icons.fast_rewind, - size: 40, - color: Colors.white, - shadows: AppShadows.icon, - ), - ), - ), - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: _onPlayPause, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Icon( - _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, - size: 64, - color: Colors.white, - shadows: AppShadows.icon, - ), - ), - ), - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: _onFastForward, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Icon( - Icons.fast_forward, - size: 40, - color: Colors.white, - shadows: AppShadows.icon, - ), - ), - ), - ], - ); - } - - /// Video player bottom overlay - /// * Duration - /// * Timeline - /// * Mute / Un-mute - Widget get _overlayBottom { - return Positioned( - bottom: widget.screenPadding.bottom, - right: 0, - left: 0, - child: AnimatedSlide( - duration: _overlayAnimationDuration, - curve: _overlayAnimationCurve, - offset: widget.showOverlay ? Offset.zero : Offset(0, 1), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - _durationText, - style: TextStyle(fontSize: 14, color: Colors.white, fontWeight: FontWeight.w500), - ), - ), - Expanded( - child: Theme( - // Light slider theme - data: lightTheme, - child: Slider( - value: _isEnd ? 100 : max(0, min(_progress * 100, 100)), - min: 0, - max: 100, - onChanged: _onVideoTimeChanged, - onChangeStart: _onVideoTimeChangeStart, - onChangeEnd: _onVideoTimeChangeEnd, - ), - ), - ), - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: _onMute, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Icon( - _mute ? Icons.volume_off : Icons.volume_up, - color: Colors.white, - ), - ), - ), - ], - ), - ), - ), - ); - } - - /// Video player overlay's background color - Widget get _overlayBackground { - return Positioned.fill( - child: Container( - color: Colors.black.withValues(alpha: 0.5), - ), - ); - } - - /// Video thumbnail shown when loading - Widget get _thumbnail { - // Thumbnail doesn't exist - if (widget.thumbnailUrl == null) { - return Center( - child: Icon(Icons.image_not_supported), - ); - } - return Stack( - children: [ - // Thumbnail - Positioned.fill( - child: Image.network( - widget.thumbnailUrl!, - fit: BoxFit.contain, - errorBuilder: (context, o, s) { - debugPrint("$o"); - return Center(child: Icon(Icons.image_not_supported)); - }, - ), - ), - // Loading... - if (!_controller.value.isInitialized) Center(child: CircularProgressIndicator()), - // Error while loading the video - if (_controller.value.hasError) - Center( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - color: Colors.black.withValues(alpha: 0.5), - ), - child: Text( - appStrings.videoLoadError_message, - style: TextStyle(fontSize: 16, color: Colors.white), - ), - ), - ), - ], - ); - } -} - -class VideoPlayerView extends StatefulWidget { - const VideoPlayerView( - {Key? key, - this.videoUrl, - this.thumbnailUrl, - this.onToggleOverlay, - this.showOverlay = false, - this.screenPadding = EdgeInsets.zero}) - : super(key: key); - - final String? videoUrl; - final String? thumbnailUrl; - final Function(bool)? onToggleOverlay; - final bool showOverlay; - final EdgeInsets screenPadding; - - @override - State createState() => _VideoPlayerViewState(); -} - -class _VideoPlayerViewState extends State { - late VideoPlayerController _videoPlayerController; - late PlayerProvider _provider; - ChewieController? _chewieController; - - @override - void initState() { - super.initState(); - initializePlayer(); - _provider = PlayerProvider.init(widget.showOverlay); - } - - @override - void dispose() { - _videoPlayerController.dispose(); - _chewieController?.dispose(); - super.dispose(); - } - - Future initializePlayer() async { - if (widget.videoUrl == null) return; - _videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl!)); - await _videoPlayerController.initialize(); - _createChewieController(); - setState(() {}); - } - - void _createChewieController() { - _chewieController = ChewieController( - videoPlayerController: _videoPlayerController, - autoPlay: false, - allowFullScreen: false, - showOptions: false, - hideControlsTimer: const Duration(seconds: 3), - customControls: PlayerControls(onToggleOverlay: widget.onToggleOverlay), - )..setVolume(0); - } - - @override - Widget build(BuildContext context) { - if (widget.videoUrl == null) { - return Center( - child: Text(appStrings.errorHUD_label), - ); - } - if (_chewieController == null || !_chewieController!.videoPlayerController.value.isInitialized) { - return Center( - child: CircularProgressIndicator(), - ); - } - return ChangeNotifierProvider.value( - value: _provider, - child: Chewie( - controller: _chewieController!, - ), - ); - } -} From cb8fe4f3ca19c19607bbc9b5d116dd9294b06dd7 Mon Sep 17 00:00:00 2001 From: Renarde-dev Date: Fri, 3 Oct 2025 14:24:21 +0200 Subject: [PATCH 12/15] Purge old player code and add thumbnail system --- lib/components/cards/image_details_card.dart | 53 ++++---------------- lib/views/image/image_page.dart | 4 +- lib/views/image/vlc_video_player.dart | 1 + lib/views/upload/upload_page.dart | 38 +++----------- pubspec.lock | 8 +++ pubspec.yaml | 3 +- 6 files changed, 28 insertions(+), 79 deletions(-) diff --git a/lib/components/cards/image_details_card.dart b/lib/components/cards/image_details_card.dart index f078c66..3af8cd0 100644 --- a/lib/components/cards/image_details_card.dart +++ b/lib/components/cards/image_details_card.dart @@ -14,7 +14,7 @@ import 'package:piwigo_ng/services/preferences_service.dart'; import 'package:piwigo_ng/utils/resources.dart'; import 'package:piwigo_ng/utils/settings.dart'; import 'package:provider/provider.dart'; -import 'package:video_player/video_player.dart'; +import 'package:video_thumbnail/video_thumbnail.dart'; class ImageDetailsCard extends StatelessWidget { const ImageDetailsCard({Key? key, required this.image, this.onRemove}) : super(key: key); @@ -325,31 +325,19 @@ class LocalVideoDetailsCard extends StatefulWidget { } class _LocalVideoDetailsCardState extends State { - late final VideoPlayerController _controller; + late Image thumbnail; @override - void initState() { - _controller = VideoPlayerController.file( - File(widget.video.path), - videoPlayerOptions: VideoPlayerOptions(), - )..initialize().then((_) => setState(() {})); + Future initState() async { + thumbnail = Image.memory((await VideoThumbnail.thumbnailData(video: widget.video.path))!); super.initState(); } @override void dispose() { - _controller.dispose(); super.dispose(); } - String get _duration { - final Duration duration = _controller.value.duration; - int hours = duration.inHours; - int minutes = (duration - Duration(hours: hours)).inMinutes; - int seconds = (duration - Duration(hours: hours) - Duration(minutes: minutes)).inSeconds; - return '${hours > 0 ? '$hours:' : ''}${minutes < 10 ? '0$minutes' : '$minutes'}:${seconds < 10 ? '0$seconds' : '$seconds'}'; - } - @override Widget build(BuildContext context) { return Stack( @@ -385,16 +373,6 @@ class _LocalVideoDetailsCardState extends State { fit: StackFit.expand, children: [ LayoutBuilder(builder: (context, constraints) { - if (_controller.value.hasError) { - return Center( - child: Icon(Icons.image_not_supported), - ); - } - if (!_controller.value.isInitialized) { - return Center( - child: CircularProgressIndicator(), - ); - } return Stack( alignment: Alignment.center, fit: StackFit.expand, @@ -403,12 +381,9 @@ class _LocalVideoDetailsCardState extends State { child: FittedBox( fit: BoxFit.cover, child: SizedBox( - width: _controller.value.size.width, - height: _controller.value.size.height, - child: AspectRatio( - aspectRatio: _controller.value.aspectRatio, - child: VideoPlayer(_controller), - ), + width: thumbnail.width, + height: thumbnail.height, + child: thumbnail, ), ), ), @@ -420,7 +395,7 @@ class _LocalVideoDetailsCardState extends State { decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), color: AppColors.black.withValues(alpha: 0.7)), child: Text( - _duration, + "duration", style: TextStyle(color: AppColors.white, fontSize: 10, fontWeight: FontWeight.bold), ), ), @@ -452,21 +427,11 @@ class _LocalVideoDetailsCardState extends State { ), child: Builder( builder: (context) { - if (_controller.value.hasError) { - return Center( - child: Icon(Icons.image_not_supported), - ); - } - if (!_controller.value.isInitialized) { - return Center( - child: CircularProgressIndicator(), - ); - } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( - "${_controller.value.size.width.round()}x${_controller.value.size.height.round()} pixels", + "${thumbnail.width}x${thumbnail.height} pixels", maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodySmall, diff --git a/lib/views/image/image_page.dart b/lib/views/image/image_page.dart index 69c206b..07f43af 100644 --- a/lib/views/image/image_page.dart +++ b/lib/views/image/image_page.dart @@ -23,7 +23,7 @@ import 'package:piwigo_ng/utils/image_actions.dart'; import 'package:piwigo_ng/utils/localizations.dart'; import 'package:piwigo_ng/utils/resources.dart'; import 'package:piwigo_ng/utils/settings.dart'; -import 'package:piwigo_ng/views/image/video_player_page.dart'; +import 'package:piwigo_ng/views/image/vlc_video_player.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -511,7 +511,7 @@ class _ImagePageState extends State { ), onPressed: () { Navigator.of(context).pushNamed( - VideoPlayerPage.routeName, + VlcVideoPlayer.routeName, arguments: { 'videoUrl': image.elementUrl, 'thumbnailUrl': imageUrl, diff --git a/lib/views/image/vlc_video_player.dart b/lib/views/image/vlc_video_player.dart index 52be287..86dedf3 100644 --- a/lib/views/image/vlc_video_player.dart +++ b/lib/views/image/vlc_video_player.dart @@ -27,6 +27,7 @@ class VlcVideoPlayerState extends State { void initState() { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); super.initState(); + print(widget.videoUrl); vlcController = VlcPlayerController.network( widget.videoUrl, hwAcc: HwAcc.auto, diff --git a/lib/views/upload/upload_page.dart b/lib/views/upload/upload_page.dart index afae679..5d6dc45 100644 --- a/lib/views/upload/upload_page.dart +++ b/lib/views/upload/upload_page.dart @@ -22,7 +22,6 @@ import 'package:piwigo_ng/utils/image_actions.dart'; import 'package:piwigo_ng/utils/localizations.dart'; import 'package:piwigo_ng/utils/resources.dart'; import 'package:rounded_loading_button/rounded_loading_button.dart'; -import 'package:video_player/video_player.dart'; class UploadPage extends StatefulWidget { const UploadPage({Key? key, required this.imageList, this.albumId}) : super(key: key); @@ -396,43 +395,21 @@ class VideoUploadItem extends StatefulWidget { } class _VideoUploadItemState extends State { - late VideoPlayerController _controller; + late Image thumbnail; @override void initState() { super.initState(); - _controller = VideoPlayerController.file( - File(widget.path), - videoPlayerOptions: VideoPlayerOptions(), - )..initialize().then((_) => setState(() {})); + } @override void dispose() { - _controller.dispose(); super.dispose(); } - String get _duration { - final Duration duration = _controller.value.duration; - int hours = duration.inHours; - int minutes = (duration - Duration(hours: hours)).inMinutes; - int seconds = (duration - Duration(hours: hours) - Duration(minutes: minutes)).inSeconds; - return '${hours > 0 ? '$hours:' : ''}${minutes < 10 ? '0$minutes' : '$minutes'}:${seconds < 10 ? '0$seconds' : '$seconds'}'; - } - @override Widget build(BuildContext context) { - if (_controller.value.hasError) { - return Center( - child: Icon(Icons.image_not_supported), - ); - } - if (!_controller.value.isInitialized) { - return Center( - child: CircularProgressIndicator(), - ); - } return LayoutBuilder(builder: (context, constraints) { return Stack( alignment: Alignment.center, @@ -442,12 +419,9 @@ class _VideoUploadItemState extends State { child: FittedBox( fit: BoxFit.cover, child: SizedBox( - width: _controller.value.size.width, - height: _controller.value.size.height, - child: AspectRatio( - aspectRatio: _controller.value.aspectRatio, - child: VideoPlayer(_controller), - ), + width: thumbnail.width, + height: thumbnail.height, + child: thumbnail, ), ), ), @@ -459,7 +433,7 @@ class _VideoUploadItemState extends State { decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: AppColors.black.withValues(alpha: 0.7)), child: Text( - _duration, + "_duration", style: TextStyle(color: AppColors.white, fontSize: 10, fontWeight: FontWeight.bold), ), ), diff --git a/pubspec.lock b/pubspec.lock index 6130263..1b710db 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1315,6 +1315,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + video_thumbnail: + dependency: "direct main" + description: + name: video_thumbnail + sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b" + url: "https://pub.dev" + source: hosted + version: "0.5.6" vm_service: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index de4b5fb..ef5cf20 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,7 +65,8 @@ dependencies: listen_sharing_intent: ^1.9.2 # Receive sharing from other apps # Video player - flutter_vlc_player: ^7.4.3 # libvlc player + flutter_vlc_player: ^7.4.4 # LibVLC player + video_thumbnail: ^0.5.6 # Generate thumbnail # Translations flutter_localizations: From dc0b5f11d6df8be414ab8f13ff5f2e315afa06e4 Mon Sep 17 00:00:00 2001 From: Linty Date: Fri, 17 Oct 2025 10:58:10 +0200 Subject: [PATCH 13/15] fix vlc player integration --- lib/views/image/vlc_video_player.dart | 29 +++++++++++++++++++++------ pubspec.lock | 8 ++++---- pubspec.yaml | 2 +- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/views/image/vlc_video_player.dart b/lib/views/image/vlc_video_player.dart index 86dedf3..288d2a5 100644 --- a/lib/views/image/vlc_video_player.dart +++ b/lib/views/image/vlc_video_player.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_vlc_player/flutter_vlc_player.dart'; +import 'package:flutter_vlc_player_16kb/flutter_vlc_player.dart'; class VlcVideoPlayer extends StatefulWidget { const VlcVideoPlayer({ @@ -88,17 +88,34 @@ class VlcVideoPlayerState extends State { @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; + final vlcValue = vlcController.value; + // set aspect ratio depending on the screen if VLC doesn't get it right + final vlcAspectRatio = vlcValue.aspectRatio == 1 + ? screenSize.width / screenSize.height + : vlcValue.aspectRatio; + return Scaffold( backgroundColor: Colors.black, body: Stack( children: [ - VlcPlayer( - controller: vlcController, - placeholder: Center(child: CircularProgressIndicator()), - aspectRatio: screenSize.width / screenSize.height, + // player + Positioned.fill( + child: Center( + child: AspectRatio( + aspectRatio: vlcAspectRatio, + child: VlcPlayer( + controller: vlcController, + placeholder: const Center(child: CircularProgressIndicator()), + aspectRatio: vlcAspectRatio, + ), + ), + ), ), + // Detect any tap to show or hide controls - GestureDetector(onTap: _changeOpacity), + Positioned.fill(child: GestureDetector(onTap: _changeOpacity)), + + // controllers IgnorePointer( // Ignore input when not visible ignoring: opacityLevel == 0.0, diff --git a/pubspec.lock b/pubspec.lock index 1b710db..e2b26d8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -520,14 +520,14 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_vlc_player: + flutter_vlc_player_16kb: dependency: "direct main" description: - name: flutter_vlc_player - sha256: "32c0109cc191a97246df759a8804a1051c4ca6909597833506f13587cb3bf1be" + name: flutter_vlc_player_16kb + sha256: "69f44e648ad2c1b551d32be452b8e75d54169c9d7f58e23a853964f7e2aee5c5" url: "https://pub.dev" source: hosted - version: "7.4.4" + version: "7.4.7" flutter_vlc_player_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ef5cf20..5315b33 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,7 +65,7 @@ dependencies: listen_sharing_intent: ^1.9.2 # Receive sharing from other apps # Video player - flutter_vlc_player: ^7.4.4 # LibVLC player + flutter_vlc_player_16kb: ^7.4.6 # LibVLC player video_thumbnail: ^0.5.6 # Generate thumbnail # Translations From 635b95d8b8409840c1d3ec86cf417538b2ec893f Mon Sep 17 00:00:00 2001 From: RushLana Date: Fri, 17 Oct 2025 14:16:12 +0200 Subject: [PATCH 14/15] Fix video_thumnail not supported in A15+ / SDK 35+ --- lib/components/cards/image_details_card.dart | 4 +- pubspec.lock | 40 ++++++++------------ pubspec.yaml | 2 +- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/lib/components/cards/image_details_card.dart b/lib/components/cards/image_details_card.dart index 3af8cd0..5a7b4a1 100644 --- a/lib/components/cards/image_details_card.dart +++ b/lib/components/cards/image_details_card.dart @@ -14,7 +14,7 @@ import 'package:piwigo_ng/services/preferences_service.dart'; import 'package:piwigo_ng/utils/resources.dart'; import 'package:piwigo_ng/utils/settings.dart'; import 'package:provider/provider.dart'; -import 'package:video_thumbnail/video_thumbnail.dart'; +import 'package:flutter_video_thumbnail_plus/flutter_video_thumbnail_plus.dart'; class ImageDetailsCard extends StatelessWidget { const ImageDetailsCard({Key? key, required this.image, this.onRemove}) : super(key: key); @@ -329,7 +329,7 @@ class _LocalVideoDetailsCardState extends State { @override Future initState() async { - thumbnail = Image.memory((await VideoThumbnail.thumbnailData(video: widget.video.path))!); + thumbnail = Image.memory((await FlutterVideoThumbnailPlus.thumbnailData(video: widget.video.path))!); super.initState(); } diff --git a/pubspec.lock b/pubspec.lock index e2b26d8..1000b8c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -117,10 +117,10 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec + sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c" url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "7.0.0" connectivity_plus_platform_interface: dependency: transitive description: @@ -189,10 +189,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" + sha256: "49413c8ca514dea7633e8def233b25efdf83ec8522955cc2c0e3ad802927e7c6" url: "https://pub.dev" source: hosted - version: "11.5.0" + version: "12.1.0" device_info_plus_platform_interface: dependency: transitive description: @@ -520,6 +520,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_video_thumbnail_plus: + dependency: "direct main" + description: + name: flutter_video_thumbnail_plus + sha256: "0bf98547192483d0a61c57ec4ac6c5f09b9f50bc3b9982886b0181535a78cf9c" + url: "https://pub.dev" + source: hosted + version: "1.0.5" flutter_vlc_player_16kb: dependency: "direct main" description: @@ -565,14 +573,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" - heif_converter: - dependency: "direct main" - description: - name: heif_converter - sha256: a2fbbf3f795b19753d84a52223c60d3bcf3cf2261d6c0d41857f0ec198e6eef3 - url: "https://pub.dev" - source: hosted - version: "1.0.1" html_unescape: dependency: "direct main" description: @@ -833,10 +833,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" + sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d url: "https://pub.dev" source: hosted - version: "8.3.1" + version: "9.0.0" package_info_plus_platform_interface: dependency: transitive description: @@ -1034,10 +1034,10 @@ packages: dependency: "direct main" description: name: share_plus - sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1 + sha256: "3424e9d5c22fd7f7590254ba09465febd6f8827c8b19a44350de4ac31d92d3a6" url: "https://pub.dev" source: hosted - version: "11.1.0" + version: "12.0.0" share_plus_platform_interface: dependency: transitive description: @@ -1315,14 +1315,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" - video_thumbnail: - dependency: "direct main" - description: - name: video_thumbnail - sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b" - url: "https://pub.dev" - source: hosted - version: "0.5.6" vm_service: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4202aea..8c21cb7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,7 +65,7 @@ dependencies: # Video player flutter_vlc_player_16kb: ^7.4.6 # LibVLC player - video_thumbnail: ^0.5.6 # Generate thumbnail + flutter_video_thumbnail_plus: ^1.0.1 # Generate thumbnail # Translations flutter_localizations: From c88072189a27b5a11e5f971543f4d8007cf7b46d Mon Sep 17 00:00:00 2001 From: RushLana Date: Fri, 17 Oct 2025 14:48:25 +0200 Subject: [PATCH 15/15] Fix thumbnail Need to fix duration and size --- lib/components/cards/image_details_card.dart | 64 ++++++++++++++------ 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/lib/components/cards/image_details_card.dart b/lib/components/cards/image_details_card.dart index 5a7b4a1..8ee8797 100644 --- a/lib/components/cards/image_details_card.dart +++ b/lib/components/cards/image_details_card.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:typed_data'; import 'dart:ui' as ui show Image; import 'package:auto_size_text/auto_size_text.dart'; @@ -17,7 +18,8 @@ import 'package:provider/provider.dart'; import 'package:flutter_video_thumbnail_plus/flutter_video_thumbnail_plus.dart'; class ImageDetailsCard extends StatelessWidget { - const ImageDetailsCard({Key? key, required this.image, this.onRemove}) : super(key: key); + const ImageDetailsCard({Key? key, required this.image, this.onRemove}) + : super(key: key); final ImageModel image; final Function()? onRemove; @@ -56,7 +58,9 @@ class ImageDetailsCard extends StatelessWidget { child: ClipRRect( borderRadius: BorderRadius.circular(5.0), child: Builder(builder: (context) { - final String? imageUrl = image.getDerivativeFromString(Preferences.getImageThumbnailSize)?.url; + final String? imageUrl = image + .getDerivativeFromString(Preferences.getImageThumbnailSize) + ?.url; return ImageNetworkDisplay( imageUrl: imageUrl, ); @@ -88,7 +92,10 @@ class ImageDetailsCard extends StatelessWidget { children: [ Flexible( child: Text( - image.file.replaceAll('', '\u200B').split(path.extension(image.file)).first, + image.file + .replaceAll('', '\u200B') + .split(path.extension(image.file)) + .first, maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodySmall, @@ -106,11 +113,13 @@ class ImageDetailsCard extends StatelessWidget { const Spacer(), if (image.dateAvailable != null) Builder(builder: (context) { - LocaleNotifier localeNotifier = Provider.of(context, listen: false); + LocaleNotifier localeNotifier = + Provider.of(context, listen: false); String date = - DateFormat.yMMMMd(localeNotifier.locale.languageCode).format(DateTime.parse(image.dateAvailable!)); - String time = - DateFormat.Hms(localeNotifier.locale.languageCode).format(DateTime.parse(image.dateAvailable!)); + DateFormat.yMMMMd(localeNotifier.locale.languageCode) + .format(DateTime.parse(image.dateAvailable!)); + String time = DateFormat.Hms(localeNotifier.locale.languageCode) + .format(DateTime.parse(image.dateAvailable!)); return AutoSizeText( "$date $time", maxLines: 1, @@ -142,7 +151,8 @@ class ImageDetailsCard extends StatelessWidget { } class LocalImageDetailsCard extends StatefulWidget { - const LocalImageDetailsCard({Key? key, required this.image, this.onRemove, this.isDuplicate = false}) + const LocalImageDetailsCard( + {Key? key, required this.image, this.onRemove, this.isDuplicate = false}) : super(key: key); final File image; @@ -205,12 +215,17 @@ class _LocalImageDetailsCardState extends State { fit: StackFit.expand, children: [ LayoutBuilder(builder: (context, constraints) { - List? mimeType = mime(widget.image.path.split('/').last)?.split('/'); + List? mimeType = + mime(widget.image.path.split('/').last)?.split('/'); if (mimeType?.first == 'image') { _checkMemory(); - double? cacheWidth = constraints.maxWidth.isInfinite ? constraints.maxWidth : null; - double? cacheHeight = constraints.maxHeight.isInfinite ? constraints.maxHeight : null; + double? cacheWidth = constraints.maxWidth.isInfinite + ? constraints.maxWidth + : null; + double? cacheHeight = constraints.maxHeight.isInfinite + ? constraints.maxHeight + : null; return Image.file( widget.image, fit: BoxFit.cover, @@ -313,7 +328,8 @@ class _LocalImageDetailsCardState extends State { } class LocalVideoDetailsCard extends StatefulWidget { - const LocalVideoDetailsCard({Key? key, required this.video, this.onRemove, this.isDuplicate = false}) + const LocalVideoDetailsCard( + {Key? key, required this.video, this.onRemove, this.isDuplicate = false}) : super(key: key); final File video; @@ -328,11 +344,20 @@ class _LocalVideoDetailsCardState extends State { late Image thumbnail; @override - Future initState() async { - thumbnail = Image.memory((await FlutterVideoThumbnailPlus.thumbnailData(video: widget.video.path))!); + void initState() { + WidgetsBinding.instance.addPostFrameCallback((_) async { + thumbnail =Image.memory(Uint8List(0)); + thumbnail = Image.memory(await FlutterVideoThumbnailPlus.thumbnailData( + video: widget.video.path) ?? + ([] as Uint8List)); + }); super.initState(); } + Future getThumbnail() async { + setState(() {}); + } + @override void dispose() { super.dispose(); @@ -391,12 +416,17 @@ class _LocalVideoDetailsCardState extends State { bottom: 2.0, left: 2.0, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), + padding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 2), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), color: AppColors.black.withValues(alpha: 0.7)), + borderRadius: BorderRadius.circular(5), + color: AppColors.black.withValues(alpha: 0.7)), child: Text( "duration", - style: TextStyle(color: AppColors.white, fontSize: 10, fontWeight: FontWeight.bold), + style: TextStyle( + color: AppColors.white, + fontSize: 10, + fontWeight: FontWeight.bold), ), ), ),