Skip to content

Commit c1c8d8e

Browse files
committed
Add thumbHash support
1 parent f79b963 commit c1c8d8e

File tree

8 files changed

+171
-22
lines changed

8 files changed

+171
-22
lines changed

examples/flyer_chat/ios/Podfile.lock

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ PODS:
4040
- Flutter
4141
- isar_flutter_libs (1.0.0):
4242
- Flutter
43+
- libwebp (1.5.0):
44+
- libwebp/demux (= 1.5.0)
45+
- libwebp/mux (= 1.5.0)
46+
- libwebp/sharpyuv (= 1.5.0)
47+
- libwebp/webp (= 1.5.0)
48+
- libwebp/demux (1.5.0):
49+
- libwebp/webp
50+
- libwebp/mux (1.5.0):
51+
- libwebp/demux
52+
- libwebp/sharpyuv (1.5.0)
53+
- libwebp/webp (1.5.0):
54+
- libwebp/sharpyuv
4355
- package_info_plus (0.4.5):
4456
- Flutter
4557
- path_provider_foundation (0.0.1):
@@ -52,6 +64,9 @@ PODS:
5264
- video_player_avfoundation (0.0.1):
5365
- Flutter
5466
- FlutterMacOS
67+
- video_thumbnail (0.0.1):
68+
- Flutter
69+
- libwebp
5570
- wakelock_plus (0.0.1):
5671
- Flutter
5772

@@ -64,12 +79,14 @@ DEPENDENCIES:
6479
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
6580
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
6681
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
82+
- video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`)
6783
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
6884

6985
SPEC REPOS:
7086
trunk:
7187
- DKImagePickerController
7288
- DKPhotoGallery
89+
- libwebp
7390
- SDWebImage
7491
- SwiftyGif
7592

@@ -90,6 +107,8 @@ EXTERNAL SOURCES:
90107
:path: ".symlinks/plugins/path_provider_foundation/darwin"
91108
video_player_avfoundation:
92109
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
110+
video_thumbnail:
111+
:path: ".symlinks/plugins/video_thumbnail/ios"
93112
wakelock_plus:
94113
:path: ".symlinks/plugins/wakelock_plus/ios"
95114

@@ -101,11 +120,13 @@ SPEC CHECKSUMS:
101120
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
102121
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
103122
isar_flutter_libs: 9fc2cfb928c539e1b76c481ba5d143d556d94920
123+
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
104124
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
105125
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
106126
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
107127
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
108128
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
129+
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
109130
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
110131

111132
PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5

examples/flyer_chat/lib/local.dart

Lines changed: 104 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'dart:convert';
12
import 'dart:math';
23

34
import 'package:dio/dio.dart';
@@ -12,9 +13,12 @@ import 'package:flyer_chat_image_message/flyer_chat_image_message.dart';
1213
import 'package:flyer_chat_system_message/flyer_chat_system_message.dart';
1314
import 'package:flyer_chat_text_message/flyer_chat_text_message.dart';
1415
import 'package:flyer_chat_video_message/flyer_chat_video_message.dart';
16+
import 'package:image/image.dart' as img;
1517
import 'package:image_picker/image_picker.dart';
1618
import 'package:pull_down_button/pull_down_button.dart';
19+
import 'package:thumbhash/thumbhash.dart' show rgbaToThumbHash;
1720
import 'package:uuid/uuid.dart';
21+
import 'package:video_thumbnail/video_thumbnail.dart';
1822

1923
import 'create_message.dart';
2024
import 'widgets/composer_action_bar.dart';
@@ -417,44 +421,129 @@ class LocalState extends State<Local> {
417421
title: const Text('Video'),
418422
onTap: () async {
419423
Navigator.pop(context);
420-
// Uncomment to use proper path
421-
// Hardcoding for testing since the simulator library doesn't expose video files
424+
// Uncomment to use picker instead of hardcoding the video url
422425

423426
final picker = ImagePicker();
424427
final result = await picker.pickVideo(
425428
source: ImageSource.gallery,
426429
);
427430

428431
if (result != null) {
429-
// Optionally get the file size
430-
// final fileSizeInBytes = await result.length();
431-
// Note to get the height/width of the video, you can use the following:
432-
// final controller = VideoPlayerController.file(file);
433-
// await controller.initialize();
434-
// final width = controller.value.size.width;
435-
// final height = controller.value.size.height;
432+
String? thumbHash;
433+
int? width;
434+
int? height;
435+
int? fileSizeInBytes;
436+
try {
437+
// Optionally get the file size
438+
fileSizeInBytes = await result.length();
439+
440+
// Get the video width and height
441+
final fullSizeimageBytes =
442+
await VideoThumbnail.thumbnailData(
443+
video: result.path,
444+
imageFormat: ImageFormat.WEBP,
445+
quality: 1,
446+
);
447+
448+
final fullSizedecoded = img.decodeImage(
449+
fullSizeimageBytes!,
450+
);
451+
if (fullSizedecoded != null) {
452+
width = fullSizedecoded.width;
453+
height = fullSizedecoded.height;
454+
}
455+
456+
// Generate the thumbhash
457+
final thumbSizeImageBytes =
458+
await VideoThumbnail.thumbnailData(
459+
video: result.path,
460+
imageFormat: ImageFormat.WEBP,
461+
maxWidth: 100,
462+
maxHeight: 100,
463+
quality: 25,
464+
);
465+
final decoded = img.decodeImage(thumbSizeImageBytes!);
466+
if (decoded != null) {
467+
final thumbHashBytes = rgbaToThumbHash(
468+
decoded.width,
469+
decoded.height,
470+
decoded.getBytes(),
471+
);
472+
473+
thumbHash = base64.encode(thumbHashBytes);
474+
}
475+
} catch (e) {
476+
debugPrint(e.toString());
477+
}
436478

437479
// Create a proper file message
438480
final videoMessage = VideoMessage(
439481
id: _uuid.v4(),
440482
authorId: _currentUser.id,
441483
createdAt: DateTime.now().toUtc(),
442484
sentAt: DateTime.now().toUtc(),
443-
// Uncomment to use proper path
444-
// Hardcoding for testing since the simulator library doesn't expose video files
445485
source: result.path,
446-
447-
// size: fileSizeInBytes,
486+
thumbhash: thumbHash,
487+
width: width?.toDouble(),
488+
height: height?.toDouble(),
489+
size: fileSizeInBytes,
448490
);
449491
await _chatController.insertMessage(videoMessage);
450492
}
451493

494+
// const videoUrl =
495+
// 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4';
496+
// String? thumbHash;
497+
// int? width;
498+
// int? height;
499+
// try {
500+
// // Get the video width and height
501+
// final fullSizeimageBytes =
502+
// await VideoThumbnail.thumbnailData(
503+
// video: videoUrl,
504+
// imageFormat: ImageFormat.WEBP,
505+
// quality: 1,
506+
// );
507+
508+
// final fullSizedecoded = img.decodeImage(
509+
// fullSizeimageBytes!,
510+
// );
511+
// if (fullSizedecoded != null) {
512+
// width = fullSizedecoded.width;
513+
// height = fullSizedecoded.height;
514+
// }
515+
516+
// // Generate the thumbhash
517+
// final thumbSizeImageBytes =
518+
// await VideoThumbnail.thumbnailData(
519+
// video: videoUrl,
520+
// imageFormat: ImageFormat.WEBP,
521+
// maxWidth: 100,
522+
// maxHeight: 100,
523+
// quality: 25,
524+
// );
525+
// final decoded = img.decodeImage(thumbSizeImageBytes!);
526+
// if (decoded != null) {
527+
// final thumbHashBytes = rgbaToThumbHash(
528+
// decoded.width,
529+
// decoded.height,
530+
// decoded.getBytes(),
531+
// );
532+
533+
// thumbHash = base64.encode(thumbHashBytes);
534+
// }
535+
// } catch (e) {
536+
// debugPrint(e.toString());
537+
// }
538+
452539
// final videoMessage = VideoMessage(
453540
// id: _uuid.v4(),
454541
// authorId: _currentUser.id,
455542
// createdAt: DateTime.now().toUtc(),
456-
// source:
457-
// 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4',
543+
// source: videoUrl,
544+
// thumbhash: thumbHash,
545+
// width: width?.toDouble(),
546+
// height: height?.toDouble(),
458547
// );
459548
// await _chatController.insertMessage(videoMessage);
460549
},

examples/flyer_chat/pubspec.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,17 @@ dependencies:
5050
hive_ce: ^2.11.2
5151
hive_ce_flutter: ^2.3.1
5252
http: ^1.3.0
53+
image: ^4.5.4
5354
image_picker: ^1.1.2
5455
intl: ^0.20.2
5556
isar_flutter_libs: ^4.0.0-dev.14
5657
path: ^1.9.1
5758
path_provider: ^2.1.5
5859
provider: ^6.1.4
5960
pull_down_button: ^0.10.2
61+
thumbhash: ^0.1.0+1
6062
uuid: ^4.5.1
63+
video_thumbnail: ^0.5.6
6164
web_socket_channel: ^3.0.2
6265

6366
dev_dependencies:

packages/flutter_chat_core/lib/src/models/message.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@ sealed class Message with _$Message {
268268

269269
/// Height of the video in pixels.
270270
double? height,
271+
272+
/// ThumbHash string for a low-resolution placeholder.
273+
String? thumbhash,
271274
}) = VideoMessage;
272275

273276
/// Creates an audio message.

packages/flutter_chat_core/lib/src/models/message.freezed.dart

Lines changed: 10 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/flutter_chat_core/lib/src/models/message.g.dart

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)