diff --git a/packages/stream_chat_flutter/lib/src/channel_media_display_screen.dart b/packages/stream_chat_flutter/lib/src/channel_media_display_screen.dart index 85b571bc..8e9f566c 100644 --- a/packages/stream_chat_flutter/lib/src/channel_media_display_screen.dart +++ b/packages/stream_chat_flutter/lib/src/channel_media_display_screen.dart @@ -33,6 +33,8 @@ class ChannelMediaDisplayScreen extends StatefulWidget { } class _ChannelMediaDisplayScreenState extends State { + Map controllerCache = {}; + @override void initState() { super.initState(); @@ -147,8 +149,15 @@ class _ChannelMediaDisplayScreenState extends State { .forEach((e) { VideoPlayerController controller; if (e.type == 'video') { - controller = VideoPlayerController.network(e.assetUrl); - controller.initialize(); + var cachedController = controllerCache[e.assetUrl]; + + if (cachedController == null) { + controller = VideoPlayerController.network(e.assetUrl); + controller.initialize(); + controllerCache[e.assetUrl] = controller; + } else { + controller = cachedController; + } } media.add(_AssetPackage(e, item.message, controller)); }); @@ -221,6 +230,14 @@ class _ChannelMediaDisplayScreenState extends State { stream: messageSearchBloc.messagesStream, ); } + + @override + void dispose() { + super.dispose(); + for (var c in controllerCache.values) { + c.dispose(); + } + } } class _AssetPackage { diff --git a/packages/stream_chat_flutter/lib/src/full_screen_media.dart b/packages/stream_chat_flutter/lib/src/full_screen_media.dart index 16ab0da5..bee8f2ba 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -61,9 +61,14 @@ class _FullScreenMediaState extends State .where((element) => element.type == 'video') .toList() .forEach((element) { - videoPackages.add(VideoPackage(context, element, () { - setState(() {}); - })); + videoPackages.add(VideoPackage( + context, + element, + () { + setState(() {}); + }, + showControls: true, + )); }); } @@ -233,15 +238,18 @@ class VideoPackage { bool initialised = false; VoidCallback onInit; BuildContext context; + bool showControls; /// - VideoPackage(this.context, Attachment attachment, this.onInit) { + VideoPackage(this.context, Attachment attachment, this.onInit, + {this.showControls = false}) { _videoPlayerController = VideoPlayerController.network(attachment.assetUrl); _videoPlayerController.initialize().whenComplete(() { initialised = true; _chewieController = ChewieController( videoPlayerController: _videoPlayerController, - autoInitialize: false, + autoInitialize: true, + showControls: showControls, aspectRatio: _videoPlayerController.value.aspectRatio, ); onInit(); diff --git a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart index 9551ff71..9214befb 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -4,10 +4,10 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'extension.dart'; import 'message_input.dart'; @@ -33,6 +33,7 @@ class MessageActionsModal extends StatefulWidget { final ShapeBorder messageShape; final ShapeBorder attachmentShape; final DisplayWidget showUserAvatar; + final Map videoPackages; const MessageActionsModal({ Key key, @@ -53,6 +54,7 @@ class MessageActionsModal extends StatefulWidget { this.messageShape, this.attachmentShape, this.reverse = false, + this.videoPackages, }) : super(key: key); @override @@ -180,6 +182,7 @@ class _MessageActionsModalState extends State { showSendingIndicator: false, shape: widget.messageShape, attachmentShape: widget.attachmentShape, + videoPackages: widget.videoPackages, ), ), SizedBox(height: 8), diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 3bb6b716..2929c2d7 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -239,6 +239,8 @@ class _MessageListViewState extends State { final MessageListController _messageListController = MessageListController(); + final Map videoPackages = {}; + @override Widget build(BuildContext context) { return MessageListCore( @@ -776,6 +778,7 @@ class _MessageListViewState extends State { break; } }, + videoPackages: videoPackages, ); } @@ -936,6 +939,7 @@ class _MessageListViewState extends State { break; } }, + videoPackages: videoPackages, ); if (!message.isDeleted && !message.isSystem && !message.isEphemeral) { @@ -1052,6 +1056,7 @@ class _MessageListViewState extends State { streamChannel.reloadChannel(); } _messageNewListener?.cancel(); + videoPackages.values.forEach((e) => e.dispose()); super.dispose(); } } diff --git a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart index 430602cb..c44022bf 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; @@ -22,6 +23,7 @@ class MessageReactionsModal extends StatelessWidget { final ShapeBorder messageShape; final ShapeBorder attachmentShape; final void Function(User) onUserAvatarTap; + final Map videoPackages; const MessageReactionsModal({ Key key, @@ -35,6 +37,7 @@ class MessageReactionsModal extends StatelessWidget { this.reverse = false, this.showUserAvatar = DisplayWidget.show, this.onUserAvatarTap, + this.videoPackages, }) : super(key: key); @override @@ -143,6 +146,7 @@ class MessageReactionsModal extends StatelessWidget { (message.status == MessageSendingStatus.sent || message.status == null), + videoPackages: videoPackages, ), ), if (message.latestReactions?.isNotEmpty == true) ...[ diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index c2cbdc35..d4a6702a 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -142,6 +142,9 @@ class MessageWidget extends StatefulWidget { /// Function called when quotedMessage is tapped final OnQuotedMessageTap onQuotedMessageTap; + /// The cache for the video controllers of attachments IDed as message ID + attachment index + final Map videoPackages; + /// MessageWidget({ Key key, @@ -190,6 +193,7 @@ class MessageWidget extends StatefulWidget { this.attachmentPadding = EdgeInsets.zero, this.allRead = false, this.onQuotedMessageTap, + this.videoPackages, }) : attachmentBuilders = { 'image': (context, message, attachment) { return ImageAttachment( @@ -740,6 +744,7 @@ class _MessageWidgetState extends State { !isFailedState && widget.onThreadTap != null, showFlagButton: widget.showFlagButton, + videoPackages: widget.videoPackages, ), ); }); @@ -768,6 +773,7 @@ class _MessageWidgetState extends State { editMessageInputBuilder: widget.editMessageInputBuilder, onThreadTap: widget.onThreadTap, showReactions: widget.showReactions, + videoPackages: widget.videoPackages, ), ); }); @@ -832,6 +838,41 @@ class _MessageWidgetState extends State { children: widget.message.attachments ?.where((element) => element.ogScrapeUrl == null) ?.map((attachment) { + if (attachment.type == 'video') { + VideoPackage package; + + if (widget.videoPackages == null) { + package = VideoPackage(context, attachment, () {}); + } else { + package = widget?.videoPackages[ + '${widget.message.id}${widget.message.attachments.indexOf(attachment)}'] ?? + VideoPackage(context, attachment, () {}); + } + + if (widget.videoPackages != null) { + widget.videoPackages[ + '${widget.message.id}${widget.message.attachments.indexOf(attachment)}'] = + package; + } + + return Transform( + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + alignment: Alignment.center, + child: VideoAttachment( + attachment: attachment, + messageTheme: widget.messageTheme, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), + message: widget.message, + onShowMessage: widget.onShowMessage, + onReturnAction: widget.onReturnAction, + videoPackage: package, + ), + ); + } + final attachmentBuilder = widget.attachmentBuilders[attachment.type]; diff --git a/packages/stream_chat_flutter/lib/src/video_attachment.dart b/packages/stream_chat_flutter/lib/src/video_attachment.dart index b775109f..107776bf 100644 --- a/packages/stream_chat_flutter/lib/src/video_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/video_attachment.dart @@ -1,12 +1,8 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/full_screen_media.dart'; -import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:video_player/video_player.dart'; -import 'attachment_error.dart'; import 'attachment_title.dart'; class VideoAttachment extends StatefulWidget { @@ -16,11 +12,13 @@ class VideoAttachment extends StatefulWidget { final Message message; final ShowMessageCallback onShowMessage; final ValueChanged onReturnAction; + final VideoPackage videoPackage; VideoAttachment({ Key key, @required this.attachment, @required this.messageTheme, + this.videoPackage, this.message, this.size, this.onShowMessage, @@ -32,13 +30,21 @@ class VideoAttachment extends StatefulWidget { } class _VideoAttachmentState extends State { - ChewieController _chewieController; - VideoPlayerController _videoPlayerController; bool initialized = false; + @override + void initState() { + super.initState(); + widget.videoPackage.onInit = () { + setState(() { + initialized = true; + }); + }; + } + @override Widget build(BuildContext context) { - if (!initialized) { + if (!widget.videoPackage.initialised) { return Container( height: widget.size?.height ?? 100, width: widget.size?.width ?? 100, @@ -47,43 +53,6 @@ class _VideoAttachmentState extends State { ), ); } - _chewieController = ChewieController( - videoPlayerController: _videoPlayerController, - autoInitialize: true, - showControls: false, - aspectRatio: _videoPlayerController.value.aspectRatio, - errorBuilder: (_, e) { - if (widget.attachment.thumbUrl != null) { - return Stack( - children: [ - Container( - height: widget.size?.height, - width: widget.size?.width, - decoration: BoxDecoration( - image: DecorationImage( - fit: BoxFit.cover, - image: CachedNetworkImageProvider( - widget.attachment.thumbUrl, - ), - ), - ), - ), - if (widget.attachment.titleLink != null) - Material( - color: Colors.transparent, - child: InkWell( - onTap: () => - launchURL(context, widget.attachment.titleLink), - ), - ), - ], - ); - } - return AttachmentError( - attachment: widget.attachment, - size: widget.size, - ); - }); return GestureDetector( onTap: () async { @@ -123,7 +92,7 @@ class _VideoAttachmentState extends State { child: Stack( children: [ Chewie( - controller: _chewieController, + controller: widget.videoPackage.chewieController, ), Positioned.fill( child: Center( @@ -153,23 +122,4 @@ class _VideoAttachmentState extends State { ), ); } - - @override - void initState() { - super.initState(); - _videoPlayerController = - VideoPlayerController.network(widget.attachment.assetUrl); - _videoPlayerController.initialize().whenComplete(() { - setState(() { - initialized = true; - }); - }); - } - - @override - void dispose() { - _videoPlayerController?.dispose(); - _chewieController?.dispose(); - super.dispose(); - } } diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index a8d7e227..40880dfc 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -86,7 +86,7 @@ void main() { testWidgets( 'StreamChatCore should disconnect on background', (WidgetTester tester) async { - await fakeAsync((_async) { + fakeAsync((_async) { final client = MockClient(); final clientState = MockClientState(); final channel = MockChannel(); @@ -123,6 +123,7 @@ void main() { _async.elapse(Duration(seconds: 5)); verify(client.disconnect()).called(1); + eventStreamController.close(); }); }, ); @@ -174,6 +175,7 @@ void main() { await untilCalled(showLocalNotificationMock(event)); verify(showLocalNotificationMock(event)).called(1); + eventStreamController.close(); }, ); }