Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/stream_chat_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
✅ Added

- Added a `LastMessagePredicate` typedef for the `ChannelLastMessageText.lastMessagePredicate` filter.
- Added `StreamChatConfigurationData.videoPlayer`, letting apps override the video player used inside `StreamFullScreenMedia` (e.g. to plug in a `media_kit`-based player for desktop). The default implementation is now available as `DefaultStreamVideoPlayer`.

🔄 Changed

- Removed the `media_kit`/`media_kit_video` dependencies from the SDK. Windows and Linux full-screen video no longer works out of the box, since the new default player (`chewie`/`video_player`) doesn't support those platforms; apps that need desktop video playback should provide one via `StreamChatConfigurationData.videoPlayer` (see the sample app for a `media_kit`-based example).

🐞 Fixed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
list(APPEND FLUTTER_PLUGIN_LIST
desktop_drop
file_selector_linux
media_kit_video
record_linux
sqlite3_flutter_libs
url_launcher_linux
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
desktop_drop
file_selector_windows
gal
media_kit_video
record_windows
share_plus
sqlite3_flutter_libs
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import 'dart:async';
import 'dart:io';

import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:stream_chat_flutter/src/fullscreen_media/full_screen_media_widget.dart';
import 'package:stream_chat_flutter/src/fullscreen_media/gallery_navigation_item.dart';
import 'package:stream_chat_flutter/src/misc/empty_widget.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_player/video_player.dart';

/// A full screen image widget
class StreamFullScreenMedia extends FullScreenMediaWidget {
class StreamFullScreenMedia extends StatefulWidget {
/// Instantiate a new FullScreenImage
const StreamFullScreenMedia({
super.key,
Expand Down Expand Up @@ -61,50 +59,17 @@ class _FullScreenMediaState extends State<StreamFullScreenMedia> {
_isDisplayingDetail.value = !_isDisplayingDetail.value;
}

final videoPackages = <String, VideoPackage>{};

@override
void initState() {
super.initState();
_pageController = PageController(initialPage: widget.startIndex);
for (var i = 0; i < widget.mediaAttachmentPackages.length; i++) {
final attachment = widget.mediaAttachmentPackages[i].attachment;
if (attachment.type != AttachmentType.video) continue;
final package = VideoPackage(attachment, showControls: true);
videoPackages[attachment.id] = package;
}
initializePlayers();
}

Future<void> initializePlayers() async {
if (videoPackages.isEmpty) {
return;
}

final currentAttachment =
widget.mediaAttachmentPackages[widget.startIndex].attachment;

await Future.wait(videoPackages.values.map(
(it) => it.initialize(),
));

if (widget.autoplayVideos &&
currentAttachment.type == AttachmentType.video) {
final package = videoPackages.values
.firstWhere((e) => e._attachment == currentAttachment);
package._chewieController?.play();
}
setState(() {}); // ignore: no-empty-block
}

@override
void dispose() {
_currentPage.dispose();
_pageController.dispose();
_isDisplayingDetail.dispose();
for (final package in videoPackages.values) {
package.dispose();
}
super.dispose();
}

Expand Down Expand Up @@ -255,81 +220,64 @@ class _FullScreenMediaState extends State<StreamFullScreenMedia> {
);
}
},
child: PageView.builder(
controller: _pageController,
itemCount: widget.mediaAttachmentPackages.length,
onPageChanged: (val) {
_currentPage.value = val;
if (videoPackages.isEmpty) return;
final currentAttachment =
widget.mediaAttachmentPackages[val].attachment;
for (final e in videoPackages.values) {
if (e._attachment != currentAttachment) {
e._chewieController?.pause();
}
}
if (widget.autoplayVideos &&
currentAttachment.type == AttachmentType.video) {
final controller = videoPackages[currentAttachment.id]!;
controller._chewieController?.play();
}
},
itemBuilder: (context, index) {
final currentAttachmentPackage =
widget.mediaAttachmentPackages[index];
final attachment = currentAttachmentPackage.attachment;
return ValueListenableBuilder(
valueListenable: _isDisplayingDetail,
builder: (context, isDisplayingDetail, child) {
final padding = MediaQuery.paddingOf(context);

return AnimatedContainer(
duration: kThemeChangeDuration,
color: switch (isDisplayingDetail) {
true => StreamChannelHeaderTheme.of(context).color,
false => Colors.black,
},
padding: EdgeInsetsDirectional.only(
top: padding.top + kToolbarHeight,
bottom: padding.bottom + kToolbarHeight,
),
child: child,
);
},
child: Builder(
builder: (context) {
if (attachment.type == AttachmentType.image ||
attachment.type == AttachmentType.giphy) {
return PhotoView.customChild(
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
child: StreamMediaAttachmentThumbnail(
media: attachment,
width: double.infinity,
height: double.infinity,
),
);
} else if (attachment.type == AttachmentType.video) {
final controller = videoPackages[attachment.id]!;
if (!controller.initialized) {
return const Center(
child: CircularProgressIndicator.adaptive(),
child: FullScreenMediaScope(
activeIndex: _currentPage,
child: PageView.builder(
controller: _pageController,
itemCount: widget.mediaAttachmentPackages.length,
onPageChanged: (val) => _currentPage.value = val,
itemBuilder: (context, index) {
final currentAttachmentPackage =
widget.mediaAttachmentPackages[index];
final attachment = currentAttachmentPackage.attachment;
return ValueListenableBuilder(
valueListenable: _isDisplayingDetail,
builder: (context, isDisplayingDetail, child) {
final padding = MediaQuery.paddingOf(context);

return AnimatedContainer(
duration: kThemeChangeDuration,
color: switch (isDisplayingDetail) {
true => StreamChannelHeaderTheme.of(context).color,
false => Colors.black,
},
padding: EdgeInsetsDirectional.only(
top: padding.top + kToolbarHeight,
bottom: padding.bottom + kToolbarHeight,
),
child: child,
);
},
child: Builder(
builder: (context) {
if (attachment.type == AttachmentType.image ||
attachment.type == AttachmentType.giphy) {
return PhotoView.customChild(
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
child: StreamMediaAttachmentThumbnail(
media: attachment,
width: double.infinity,
height: double.infinity,
),
);
} else if (attachment.type == AttachmentType.video) {
return StreamVideoPlayer(
attachment: attachment,
pageIndex: index,
autoplay: widget.autoplayVideos,
);
}

return Chewie(
controller: controller.chewieController!,
);
}

return const Empty();
},
),
);
},
return const Empty();
},
),
);
},
),
),
),
),
Expand All @@ -342,20 +290,19 @@ class _FullScreenMediaState extends State<StreamFullScreenMedia> {
class VideoPackage {
/// Constructor for creating [VideoPackage]
VideoPackage(
this._attachment, {
Attachment attachment, {
bool showControls = false,
bool autoInitialize = true,
}) : _showControls = showControls,
_autoInitialize = autoInitialize,
_videoPlayerController = _attachment.localUri != null
_videoPlayerController = attachment.localUri != null
? VideoPlayerController.file(
File.fromUri(_attachment.localUri!),
File.fromUri(attachment.localUri!),
)
: VideoPlayerController.networkUrl(
Uri.parse(_attachment.assetUrl!),
Uri.parse(attachment.assetUrl!),
);

final Attachment _attachment;
final bool _showControls;
final bool _autoInitialize;
final VideoPlayerController _videoPlayerController;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/fullscreen_media/fsm_stub.dart'
if (dart.library.io) 'full_screen_media_desktop.dart' as desktop_fsm;
import 'package:stream_chat_flutter/stream_chat_flutter.dart';

/// {@template fsmBuilder}
/// A wrapper widget for conditionally providing the proper
/// StreamFullScreenMedia widget when writing an application that targets
/// all available Flutter platforms (Android, iOS, macOS, Windows, Linux,
/// & Web).
/// A wrapper widget for building [StreamFullScreenMedia].
///
/// This is required because:
/// * `package:video_player` and `package:chewie` do not support macOS, Windows,
/// & Linux, but _do_ support Android, iOS, & Web.
/// * `package:dart_vlc` _does_ support macOS, Windows, & Linux via FFI. This
/// has the unfortunate consequence of not supporting Web.
///
/// This widget makes use of dart's conditional imports to ensure that Stream's
/// desktop implementation of StreamFullScreenMedia is not imported when
/// building applications that target web. Additionally, this widget ensures
/// that applications targeting mobile platforms do not build the version of
/// StreamFullScreenMedia that targets desktop platforms (even though
/// `package:dart_vlc` technically supports iOS).
/// Video attachments are played by [StreamVideoPlayer], which uses the
/// `chewie`/`video_player`-backed [DefaultStreamVideoPlayer] by default.
/// `video_player` has no Windows or Linux implementation, so apps that
/// target those platforms and need video playback should override
/// [StreamChatConfigurationData.videoPlayer] (e.g. with a `media_kit`-based
/// player).
/// {@endtemplate}
class StreamFullScreenMediaBuilder extends StatelessWidget {
/// {@macro fsmBuilder}
Expand Down Expand Up @@ -61,18 +49,6 @@ class StreamFullScreenMediaBuilder extends StatelessWidget {

@override
Widget build(BuildContext context) {
if (!kIsWeb && isDesktopVideoPlayerSupported) {
return desktop_fsm.getFsm(
mediaAttachmentPackages: mediaAttachmentPackages,
startIndex: startIndex,
userName: userName,
autoplayVideos: autoplayVideos,
onShowMessage: onShowMessage,
onReplyMessage: onReplyMessage,
attachmentActionsModalBuilder: attachmentActionsModalBuilder,
);
}

return StreamFullScreenMedia(
mediaAttachmentPackages: mediaAttachmentPackages,
startIndex: startIndex,
Expand Down
Loading
Loading