fixing compression issue

This commit is contained in:
otsmr 2025-07-11 01:16:02 +02:00
parent e9502c7ce9
commit 59528bf082
11 changed files with 140 additions and 143 deletions

View file

@ -45,7 +45,7 @@ final lockRetransStore = Mutex();
/// It handles errors and does automatically tries to reconnect on /// It handles errors and does automatically tries to reconnect on
/// errors or network changes. /// errors or network changes.
class ApiService { class ApiService {
final String apiHost = (kDebugMode) ? "192.168.178.89:3030" : "api.twonly.eu"; final String apiHost = (kDebugMode) ? "10.99.0.140:3030" : "api.twonly.eu";
final String apiSecure = (kDebugMode) ? "" : "s"; final String apiSecure = (kDebugMode) ? "" : "s";
bool appIsOutdated = false; bool appIsOutdated = false;

View file

@ -299,7 +299,7 @@ Future<File?> getVideoPath(int mediaId) async {
Future<Uint8List?> readMediaFile(int mediaId, String type) async { Future<Uint8List?> readMediaFile(int mediaId, String type) async {
String basePath = await getMediaFilePath(mediaId, "received"); String basePath = await getMediaFilePath(mediaId, "received");
File file = File("$basePath.$type"); File file = File("$basePath.$type");
Log.info("Reading: ${file}"); Log.info("Reading: $file");
if (!await file.exists()) { if (!await file.exists()) {
return null; return null;
} }

View file

@ -200,17 +200,25 @@ Future<bool> addVideoToUpload(int mediaUploadId, File videoFilePath) async {
Future<Uint8List> addOrModifyImageToUpload( Future<Uint8List> addOrModifyImageToUpload(
int mediaUploadId, Uint8List imageBytes) async { int mediaUploadId, Uint8List imageBytes) async {
Uint8List imageBytesCompressed; Uint8List imageBytesCompressed;
Stopwatch stopwatch = Stopwatch();
stopwatch.start();
Log.info("Raw images size in bytes: ${imageBytes.length}");
try { try {
imageBytesCompressed = await FlutterImageCompress.compressWithList( imageBytesCompressed = await FlutterImageCompress.compressWithList(
format: CompressFormat.png, format: CompressFormat.webp,
// minHeight: 0,
// minWidth: 0,
imageBytes, imageBytes,
quality: 90, quality: 90,
); );
if (imageBytesCompressed.length >= 2 * 1000 * 1000) { if (imageBytesCompressed.length >= 1 * 1000 * 1000) {
// if the media file is over 2MB compress it with 60% // if the media file is over 2MB compress it with 60%
imageBytesCompressed = await FlutterImageCompress.compressWithList( imageBytesCompressed = await FlutterImageCompress.compressWithList(
format: CompressFormat.png, format: CompressFormat.webp,
imageBytes, imageBytes,
quality: 60, quality: 60,
); );
@ -223,6 +231,26 @@ Future<Uint8List> addOrModifyImageToUpload(
imageBytesCompressed = imageBytes; imageBytesCompressed = imageBytes;
} }
stopwatch.stop();
Log.info(
'Compression the image took: ${stopwatch.elapsedMilliseconds} milliseconds');
Log.info("Raw images size in bytes: ${imageBytesCompressed.length}");
// stopwatch.reset();
// stopwatch.start();
// // var helper = MediaUploadHelper();
// try {
// final webpBytes =
// await convertAndCompressImage(pngRawImageBytes: imageBytes);
// Log.info(
// 'Compression the image in rust took: ${stopwatch.elapsedMilliseconds} milliseconds');
// Log.info("Raw images size in bytes using webp: ${webpBytes.length}");
// } catch (e) {
// Log.error("$e");
// }
/// in case the media file was already encrypted of even uploaded /// in case the media file was already encrypted of even uploaded
/// remove the data so it will be done again. /// remove the data so it will be done again.
await twonlyDB.mediaUploadsDao.updateMediaUpload( await twonlyDB.mediaUploadsDao.updateMediaUpload(

View file

@ -75,14 +75,13 @@ Future createThumbnailsForVideo(File file) async {
} }
try { try {
String? thumbnailFile = await VideoThumbnail.thumbnailFile( await VideoThumbnail.thumbnailFile(
video: file.path, video: file.path,
imageFormat: ImageFormat.PNG, imageFormat: ImageFormat.PNG,
thumbnailPath: getThumbnailPath(file).path,
maxWidth: 450, maxWidth: 450,
quality: 75, quality: 75,
); );
File(thumbnailFile!).rename(getThumbnailPath(file).path);
} catch (e) { } catch (e) {
Log.error("Could not create the video thumbnail: $e"); Log.error("Could not create the video thumbnail: $e");
} }
@ -92,6 +91,10 @@ File getThumbnailPath(File file) {
String originalFileName = file.uri.pathSegments.last; String originalFileName = file.uri.pathSegments.last;
String fileNameWithoutExtension = originalFileName.split('.').first; String fileNameWithoutExtension = originalFileName.split('.').first;
String fileExtension = originalFileName.split('.').last; String fileExtension = originalFileName.split('.').last;
if (fileExtension == "mp4") {
fileExtension = "png";
}
String newFileName = '$fileNameWithoutExtension.thumbnail.$fileExtension'; String newFileName = '$fileNameWithoutExtension.thumbnail.$fileExtension';
Directory(file.parent.path).createSync();
return File(join(file.parent.path, newFileName)); return File(join(file.parent.path, newFileName));
} }

View file

@ -54,7 +54,6 @@ class _ChatListViewState extends State<ChatListView> {
_contacts = contacts.where((x) => !x.pinned).toList(); _contacts = contacts.where((x) => !x.pinned).toList();
_pinnedContacts = contacts.where((x) => x.pinned).toList(); _pinnedContacts = contacts.where((x) => x.pinned).toList();
}); });
;
}); });
tutorial = Timer(Duration(seconds: 1), () async { tutorial = Timer(Duration(seconds: 1), () async {

View file

@ -170,9 +170,8 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
.reversed .reversed
.toList(); .toList();
final items = await MemoryItem.convertFromMessages(filteredMediaFiles); final items = await MemoryItem.convertFromMessages(filteredMediaFiles);
setState(() {
galleryItems = items.values.toList(); galleryItems = items.values.toList();
}); setState(() {});
}); });
} }

View file

@ -48,9 +48,11 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
return; return;
} }
if (await received.existsMediaFile(widget.message.messageId, "png")) { if (await received.existsMediaFile(widget.message.messageId, "png")) {
if (mounted) {
setState(() { setState(() {
canBeReopened = true; canBeReopened = true;
}); });
}
Future.delayed(Duration(seconds: 1), () { Future.delayed(Duration(seconds: 1), () {
if (!mounted) return; if (!mounted) return;
showReopenMediaFilesTutorial(context, reopenMediaFile); showReopenMediaFilesTutorial(context, reopenMediaFile);

View file

@ -52,8 +52,8 @@ class _ChatListEntryState extends State<ChatListEntry> {
if (content == null) return Container(); if (content == null) return Container();
bool right = widget.message.messageOtherId == null; bool right = widget.message.messageOtherId == null;
return Hero( return Container(
tag: "${widget.message.mediaUploadId ?? widget.message.messageId}", // tag: "${widget.message.mediaUploadId ?? widget.message.messageId}",
child: Align( child: Align(
alignment: right ? Alignment.centerRight : Alignment.centerLeft, alignment: right ? Alignment.centerRight : Alignment.centerLeft,
child: Padding( child: Padding(
@ -69,8 +69,7 @@ class _ChatListEntryState extends State<ChatListEntry> {
MessageActions( MessageActions(
message: widget.message, message: widget.message,
child: Stack( child: Stack(
alignment: alignment: right ? Alignment.centerRight : Alignment.centerLeft,
right ? Alignment.centerRight : Alignment.centerLeft,
children: [ children: [
(textMessage != null) (textMessage != null)
? ChatTextEntry( ? ChatTextEntry(

View file

@ -1,17 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/services/api/media_upload.dart' as send;
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/views/components/message_send_state_icon.dart'; import 'package:twonly/src/views/components/message_send_state_icon.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/model/memory_item.model.dart'; import 'package:twonly/src/model/memory_item.model.dart';
import 'package:twonly/src/views/memories/memories_item_thumbnail.dart';
import 'package:twonly/src/views/memories/memories_photo_slider.view.dart'; import 'package:twonly/src/views/memories/memories_photo_slider.view.dart';
import 'package:video_player/video_player.dart';
class InChatMediaViewer extends StatefulWidget { class InChatMediaViewer extends StatefulWidget {
const InChatMediaViewer({ const InChatMediaViewer({
@ -34,26 +28,48 @@ class InChatMediaViewer extends StatefulWidget {
} }
class _InChatMediaViewerState extends State<InChatMediaViewer> { class _InChatMediaViewerState extends State<InChatMediaViewer> {
File? image;
File? video;
bool isMounted = true;
bool mirrorVideo = false; bool mirrorVideo = false;
VideoPlayerController? videoController; int? galleryItemIndex;
StreamSubscription<Message?>? messageStream; StreamSubscription<Message?>? messageStream;
Timer? _timer;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initAsync(widget.message); loadIndexAsync();
initStream(); initStream();
} }
Future loadIndexAsync() async {
if (!widget.message.mediaStored) return;
_timer = Timer.periodic(Duration(milliseconds: 10), (timer) {
/// when the galleryItems are updated this widget is not reloaded
/// so using this timer as a workaround
if (loadIndex()) {
timer.cancel();
setState(() {});
}
});
}
bool loadIndex() {
if (widget.message.mediaStored) {
final index = widget.galleryItems.indexWhere((x) =>
x.id == (widget.message.mediaUploadId ?? widget.message.messageId));
if (index != -1) {
galleryItemIndex = index;
return true;
}
}
return false;
}
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
isMounted = false;
messageStream?.cancel(); messageStream?.cancel();
videoController?.dispose(); _timer?.cancel();
// videoController?.dispose();
} }
Future initStream() async { Future initStream() async {
@ -70,60 +86,20 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
if (updated != null) { if (updated != null) {
if (updated.mediaStored) { if (updated.mediaStored) {
messageStream?.cancel(); messageStream?.cancel();
initAsync(updated); loadIndexAsync();
} }
} }
}); });
} }
Future initAsync(Message message) async {
if (!message.mediaStored) return;
bool isSend = message.messageOtherId == null;
final basePath = await send.getMediaFilePath(
isSend ? message.mediaUploadId! : message.messageId,
isSend ? "send" : "received",
);
if (!isMounted) return;
final videoPath = File("$basePath.mp4");
final imagePath = File("$basePath.png");
if (videoPath.existsSync() && message.contentJson != null) {
MessageContent? content = MessageContent.fromJson(
MessageKind.media, jsonDecode(message.contentJson!));
if (content is MediaMessageContent) {
mirrorVideo = content.mirrorVideo;
}
videoController = VideoPlayerController.file(
videoPath,
videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
);
videoController?.initialize().then((_) {
videoController!.setVolume(0);
videoController!.play();
videoController!.setLooping(true);
});
setState(() {
image = imagePath;
});
}
if (imagePath.existsSync()) {
setState(() {
image = imagePath;
});
} else {
Log.error("file not found: $imagePath");
}
}
Future onTap() async { Future onTap() async {
if (galleryItemIndex == null) return;
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => MemoriesPhotoSliderView( builder: (context) => MemoriesPhotoSliderView(
galleryItems: widget.galleryItems, galleryItems: widget.galleryItems,
initialIndex: widget.galleryItems.indexWhere((x) => initialIndex: galleryItemIndex!,
x.id ==
(widget.message.mediaUploadId ?? widget.message.messageId)),
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
), ),
), ),
@ -132,7 +108,7 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (image == null && video == null) { if (galleryItemIndex == null) {
return Container( return Container(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: 39, minHeight: 39,
@ -164,20 +140,9 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
color: Colors.transparent, color: Colors.transparent,
borderRadius: BorderRadius.circular(12.0), borderRadius: BorderRadius.circular(12.0),
), ),
child: GestureDetector( child: MemoriesItemThumbnail(
onTap: ((image == null && videoController == null)) ? null : onTap, galleryItem: widget.galleryItems[galleryItemIndex!],
child: Stack( onTap: onTap,
children: [
if (image != null) Image.file(image!),
if (videoController != null)
Positioned.fill(
child: Transform.flip(
flipX: mirrorVideo,
child: VideoPlayer(videoController!),
),
),
],
),
), ),
); );
} }

View file

@ -56,10 +56,10 @@ class MemoriesViewState extends State<MemoriesView> {
} }
if (fileName.contains(".png")) { if (fileName.contains(".png")) {
imagePath = file; imagePath = file;
thumbnailFile = getThumbnailPath(imagePath); thumbnailFile = file;
if (!await thumbnailFile.exists()) { // if (!await thumbnailFile.exists()) {
await createThumbnailsForImage(imagePath); // await createThumbnailsForImage(imagePath);
} // }
} else if (fileName.contains(".mp4")) { } else if (fileName.contains(".mp4")) {
videoPath = file; videoPath = file;
thumbnailFile = getThumbnailPath(videoPath); thumbnailFile = getThumbnailPath(videoPath);

View file

@ -44,9 +44,11 @@ class _MemoriesItemThumbnailState extends State<MemoriesItemThumbnail> {
children: [ children: [
Image.file(widget.galleryItem.thumbnailPath), Image.file(widget.galleryItem.thumbnailPath),
if (widget.galleryItem.videoPath != null) if (widget.galleryItem.videoPath != null)
Center( Positioned.fill(
child: Center(
child: FaIcon(FontAwesomeIcons.circlePlay), child: FaIcon(FontAwesomeIcons.circlePlay),
) ),
),
], ],
), ),
), ),