11import 'dart:convert' ;
22import 'dart:io' ;
33
4+ import 'package:flutter/foundation.dart' ;
45import 'package:flutter/material.dart' ;
6+ import 'package:flutter/services.dart' ;
57import 'package:flutter_chat_core/flutter_chat_core.dart' ;
68import 'package:provider/provider.dart' ;
79import 'package:thumbhash/thumbhash.dart'
810 show rgbaToBmp, thumbHashToApproximateAspectRatio, thumbHashToRGBA;
911import 'package:video_player/video_player.dart' ;
12+ import 'package:video_thumbnail/video_thumbnail.dart' ;
1013import 'helpers/is_network_source.dart' ;
1114import 'widgets/full_screen_video_player.dart' ;
1215import 'widgets/hero_video_route.dart' ;
@@ -30,14 +33,11 @@ class FlyerChatVideoMessage extends StatefulWidget {
3033 /// Constraints for the video size.
3134 final BoxConstraints ? constraints;
3235
33- /// Color of the overlay shown during video loading for a sent message
34- final Color ? sentLoadingOverlayColor ;
36+ /// Background color for a sent message while image cover is generated
37+ final Color ? sentBackGroundColor ;
3538
36- /// Color of the overlay shown during video loading for a received message
37- final Color ? receivedLoadingOverlayColor;
38-
39- /// Color of the circular progress indicator shown during video loading.
40- final Color ? loadingIndicatorColor;
39+ /// Background color for a received message while image cover is generated
40+ final Color ? receivedBackgroundColor;
4141
4242 /// Color of the overlay shown during video upload.
4343 final Color ? uploadOverlayColor;
@@ -78,19 +78,15 @@ class FlyerChatVideoMessage extends StatefulWidget {
7878 /// Color of the play icon.
7979 final Color playIconColor;
8080
81- /// Background color used while the image thumbnail is visible.
82- final Color ? placeholderColor;
83-
8481 /// Creates a widget to display an video message.
8582 const FlyerChatVideoMessage ({
8683 super .key,
8784 required this .message,
8885 this .headers,
8986 this .borderRadius,
9087 this .constraints = const BoxConstraints (maxHeight: 300 ),
91- this .sentLoadingOverlayColor,
92- this .receivedLoadingOverlayColor,
93- this .loadingIndicatorColor,
88+ this .sentBackGroundColor,
89+ this .receivedBackgroundColor,
9490 this .uploadOverlayColor,
9591 this .uploadIndicatorColor,
9692 this .timeStyle,
@@ -104,7 +100,6 @@ class FlyerChatVideoMessage extends StatefulWidget {
104100 this .playIcon = Icons .play_circle_fill,
105101 this .playIconSize = 48 ,
106102 this .playIconColor = Colors .white,
107- this .placeholderColor,
108103 });
109104
110105 @override
@@ -115,7 +110,6 @@ class FlyerChatVideoMessage extends StatefulWidget {
115110/// State for [FlyerChatVideoMessage] .
116111class _FlyerChatVideoMessageState extends State <FlyerChatVideoMessage > {
117112 late final ChatController _chatController;
118- VideoPlayerController ? _videoPlayerController;
119113 ImageProvider ? _placeholderProvider;
120114 late double _aspectRatio;
121115
@@ -144,38 +138,51 @@ class _FlyerChatVideoMessageState extends State<FlyerChatVideoMessage> {
144138 }
145139
146140 _chatController = context.read <ChatController >();
147- _initalizeVideoPlayerAsync ();
141+ if (! false ) {
142+ try {
143+ _generateImageCover ();
144+ } catch (e) {
145+ debugPrint ('Could not generate image cover: ${e .toString ()}' );
146+ }
147+ }
148148 }
149149
150- Future <void > _initalizeVideoPlayerAsync () async {
151- if (isNetworkSource (widget.message.source)) {
152- _videoPlayerController = VideoPlayerController .networkUrl (
153- Uri .parse (widget.message.source),
154- httpHeaders: widget.headers ?? {},
155- );
156- } else {
157- _videoPlayerController = VideoPlayerController .file (
158- File (widget.message.source),
159- );
160- }
161- await _videoPlayerController! .initialize ().then ((_) {
162- setState (() {
163- _aspectRatio = _videoPlayerController! .value.aspectRatio;
164- });
150+ Future <void > _generateImageCover () async {
151+ final coverImageBytes = await VideoThumbnail .thumbnailData (
152+ video: widget.message.source,
153+ imageFormat: ImageFormat .WEBP ,
154+ quality: 25 ,
155+ headers: widget.headers,
156+ );
157+ setState (() {
158+ // TODO should we add 'image' package to decode and get height and width
159+ // to update _aspectRatio?
160+
161+ // import 'package:image/image.dart' as img;
162+
163+ // final decoded = img.decodeImage(coverImageBytes!);
164+ // if (decoded != null) {
165+ // final width = decoded.width;
166+ // final height = decoded.height;
167+ // }
168+
169+ _placeholderProvider = MemoryImage (coverImageBytes! );
165170 });
166171 }
167172
168173 @override
169174 void didUpdateWidget (FlyerChatVideoMessage oldWidget) {
170175 super .didUpdateWidget (oldWidget);
171176 if (oldWidget.message.source != widget.message.source) {
172- _initalizeVideoPlayerAsync ();
177+ _generateImageCover ();
173178 }
174179 }
175180
176181 @override
177182 void dispose () {
178- _videoPlayerController? .dispose ();
183+ _placeholderProvider? .evict ();
184+ // Evicting the image on dispose will result in images flickering
185+ // PaintingBinding.instance.imageCache.evict(_imageProvider);
179186 super .dispose ();
180187 }
181188
@@ -229,38 +236,25 @@ class _FlyerChatVideoMessageState extends State<FlyerChatVideoMessage> {
229236 child: Stack (
230237 fit: StackFit .expand,
231238 children: [
232- _placeholderProvider != null
233- ? Image (image: _placeholderProvider! , fit: BoxFit .fill)
234- : Container (
235- color:
236- widget.placeholderColor ??
237- theme.colors.surfaceContainerLow,
238- ),
239239 Hero (
240240 tag: widget.message.id,
241241 child:
242- _videoPlayerController? .value.isInitialized == true
243- ? VideoPlayer (_videoPlayerController! )
242+ _placeholderProvider != null
243+ ? Image (
244+ image: _placeholderProvider! ,
245+ fit: BoxFit .fill,
246+ )
244247 : Container (
245- color: _resolveBackgroundColor (isSentByMe, theme),
246- child: Center (
247- child: CircularProgressIndicator (
248- color:
249- widget
250- .fullScreenPlayerLoadingIndicatorColor ??
251- theme.colors.onSurface.withValues (
252- alpha: 0.8 ,
253- ),
254- ),
255- ),
248+ color:
249+ _resolveBackgroundColor (isSentByMe, theme) ??
250+ theme.colors.surfaceContainerLow,
256251 ),
257252 ),
258253 Icon (
259254 widget.playIcon,
260255 size: widget.playIconSize,
261256 color: widget.playIconColor,
262257 ),
263-
264258 if (_chatController is UploadProgressMixin )
265259 StreamBuilder <double >(
266260 stream: (_chatController as UploadProgressMixin )
@@ -316,8 +310,8 @@ class _FlyerChatVideoMessageState extends State<FlyerChatVideoMessage> {
316310
317311 Color ? _resolveBackgroundColor (bool isSentByMe, ChatTheme theme) {
318312 if (isSentByMe) {
319- return widget.sentLoadingOverlayColor ?? theme.colors.primary;
313+ return widget.sentBackGroundColor ?? theme.colors.primary;
320314 }
321- return widget.receivedLoadingOverlayColor ?? theme.colors.surfaceContainer;
315+ return widget.receivedBackgroundColor ?? theme.colors.surfaceContainer;
322316 }
323317}
0 commit comments