From 1a4ef800c6f52170e6b1f0ecd6ff6b2762b415f6 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 19 May 2026 16:05:45 -0400 Subject: [PATCH] placeholder widget for video support --- flake.lock | 12 +- lib/main.dart | 2 + lib/models/content/message.dart | 3 +- lib/widgets/chat_page/render_event.dart | 113 +++++++++------ lib/widgets/players/video.dart | 27 ++++ linux/flutter/generated_plugin_registrant.cc | 8 ++ linux/flutter/generated_plugins.cmake | 2 + linux/nix/devshell.nix | 9 +- pubspec.lock | 130 +++++++++++++++++- pubspec.yaml | 5 +- .../flutter/generated_plugin_registrant.cc | 6 + windows/flutter/generated_plugins.cmake | 2 + 12 files changed, 265 insertions(+), 54 deletions(-) create mode 100644 lib/widgets/players/video.dart diff --git a/flake.lock b/flake.lock index 0f824b0..d6167fb 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1777988971, - "narHash": "sha256-qIoWPDs+0/8JecyYgE3gpKQxW/4bLW/gp45vow9ioCQ=", + "lastModified": 1778716662, + "narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "0678d8986be1661af6bb555f3489f2fdfc31f6ff", + "rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb", "type": "github" }, "original": { @@ -88,11 +88,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1777954456, - "narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=", + "lastModified": 1778869304, + "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", "owner": "nixos", "repo": "nixpkgs", - "rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1", + "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", "type": "github" }, "original": { diff --git a/lib/main.dart b/lib/main.dart index 846f075..b687ebd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import "dart:io"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter/foundation.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:media_kit/media_kit.dart"; import "package:nexus/controllers/client_controller.dart"; import "package:nexus/controllers/client_state_controller.dart"; import "package:nexus/controllers/header_controller.dart"; @@ -56,6 +57,7 @@ void showError(Object error, [StackTrace? stackTrace]) { void main() async { WidgetsFlutterBinding.ensureInitialized(); + MediaKit.ensureInitialized(); if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) { await windowManager.ensureInitialized(); diff --git a/lib/models/content/message.dart b/lib/models/content/message.dart index 35802ed..c431324 100644 --- a/lib/models/content/message.dart +++ b/lib/models/content/message.dart @@ -3,6 +3,7 @@ import "package:nexus/models/info/audio.dart"; import "package:nexus/models/content/content.dart"; import "package:nexus/models/info/file.dart"; import "package:nexus/models/info/image.dart"; +import "package:nexus/models/info/video.dart"; part "message.freezed.dart"; part "message.g.dart"; @@ -72,7 +73,7 @@ abstract class MessageContent extends Content with _$MessageContent { String? formattedBody, // EncryptedFile? file String? filename, - AudioInfo? info, + VideoInfo? info, Uri? url, }) = VideoMessageContent; diff --git a/lib/widgets/chat_page/render_event.dart b/lib/widgets/chat_page/render_event.dart index b5439db..fa249d6 100644 --- a/lib/widgets/chat_page/render_event.dart +++ b/lib/widgets/chat_page/render_event.dart @@ -26,6 +26,7 @@ import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart"; import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart"; import "package:nexus/widgets/link_preview.dart"; import "package:nexus/widgets/loading.dart"; +import "package:nexus/widgets/players/video.dart"; import "package:timeago/timeago.dart"; import "package:flutter_linkify/flutter_linkify.dart"; @@ -153,6 +154,21 @@ class RenderEvent extends ConsumerWidget { :final body, :final formattedBody, :final format, + ) || + VideoMessageContent( + :final body, + :final formattedBody, + :final format, + ) || + AudioMessageContent( + :final body, + :final formattedBody, + :final format, + ) || + FileMessageContent( + :final body, + :final formattedBody, + :final format, ) => Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -199,9 +215,11 @@ class RenderEvent extends ConsumerWidget { if (!textOnly) if (event.content case ImageMessageContent( - :final url, - :final info, - )) + :final url, + ) || + FileMessageContent(:final url) || + VideoMessageContent(:final url) || + AudioMessageContent(:final url)) switch (url?.mxcToHttps( ref.watch( ClientStateController.provider @@ -215,32 +233,37 @@ class RenderEvent extends ConsumerWidget { constraints: BoxConstraints.loose( Size.fromWidth(500), ), - child: ExpandableImage( - url.toString(), - child: ClipRRect( - borderRadius: BorderRadius.all( - Radius.circular(8), - ), - child: Image( - image: CachedNetworkImage( - url.toString(), - ref.watch( - CrossCacheController - .provider, + child: switch (event.content) { + VideoMessageContent( + :final info, + ) => + VideoPlayer(url, info), + ImageMessageContent(:final info) => ExpandableImage( + url.toString(), + child: ClipRRect( + borderRadius: + BorderRadius.all( + Radius.circular(8), + ), + child: Image( + image: CachedNetworkImage( + url.toString(), + ref.watch( + CrossCacheController + .provider, + ), + headers: ref.headers, ), - headers: ref.headers, - ), - width: info?.width, - loadingBuilder: - ( - _, - child, - loadingProgress, - ) => loadingProgress == null - ? child - : switch (info?.blurHash) { - final blurHash? => - SizedBox( + width: info?.width, + loadingBuilder: + ( + _, + child, + loadingProgress, + ) => loadingProgress == null + ? child + : switch (info?.blurHash) { + final blurHash? => SizedBox( width: info?.width ?? info?.height ?? @@ -253,26 +276,28 @@ class RenderEvent extends ConsumerWidget { hash: blurHash, ), ), - _ => Loading(), - }, - errorBuilder: - ( - context, - error, - stackTrace, - ) => Center( - child: Text( - "Image Failed to Load", - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.error, + _ => Loading(), + }, + errorBuilder: + ( + context, + error, + stackTrace, + ) => Center( + child: Text( + "Image Failed to Load", + style: TextStyle( + color: Theme.of( + context, + ).colorScheme.error, + ), ), ), - ), + ), ), ), - ), + _ => SizedBox.shrink(), + }, ), _ => Text( "Nexus currently cannot handle encrypted media", diff --git a/lib/widgets/players/video.dart b/lib/widgets/players/video.dart new file mode 100644 index 0000000..809ea22 --- /dev/null +++ b/lib/widgets/players/video.dart @@ -0,0 +1,27 @@ +import "package:flutter/material.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:nexus/models/info/video.dart"; +// import "package:flutter_hooks/flutter_hooks.dart"; +// import "package:media_kit/media_kit.dart"; +// import "package:media_kit_video/media_kit_video.dart"; +// import "package:nexus/helpers/extensions/get_headers.dart"; + +class VideoPlayer extends HookConsumerWidget { + final VideoInfo? info; + final Uri url; + const VideoPlayer(this.url, this.info, {super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // final player = useMemoized(Player.new); + // final controller = useMemoized(() => VideoController(player), [player]); + + // useEffect(() { + // player.open(Media(url.toString(), httpHeaders: ref.headers), play: false); + + // return player.dispose; + // }, []); + + return Placeholder(); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 5485b95..45c0f94 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,8 @@ #include #include +#include +#include #include #include #include @@ -19,6 +21,12 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin"); + media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar); + g_autoptr(FlPluginRegistrar) media_kit_video_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitVideoPlugin"); + media_kit_video_plugin_register_with_registrar(media_kit_video_registrar); g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 13ef2de..4e3b41b 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,6 +5,8 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_system_colors file_selector_linux + media_kit_libs_linux + media_kit_video screen_retriever_linux url_launcher_linux window_manager diff --git a/linux/nix/devshell.nix b/linux/nix/devshell.nix index 91ba95a..ae77467 100644 --- a/linux/nix/devshell.nix +++ b/linux/nix/devshell.nix @@ -22,7 +22,14 @@ pkgs.mkShell { go git jdk17 - flutter + libGL + wayland + (flutter.override { + extraPkgConfigPackages = [ + mpv-unwrapped + libass + ]; + }) android.platform-tools ]; diff --git a/pubspec.lock b/pubspec.lock index 19869cc..6d3aa22 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -760,6 +760,70 @@ packages: url: "https://pub.dev" source: hosted version: "0.13.0" + media_kit: + dependency: "direct main" + description: + name: media_kit + sha256: ae9e79597500c7ad6083a3c7b7b7544ddabfceacce7ae5c9709b0ec16a5d6643 + url: "https://pub.dev" + source: hosted + version: "1.2.6" + media_kit_libs_android_video: + dependency: transitive + description: + name: media_kit_libs_android_video + sha256: "3f6274e5ab2de512c286a25c327288601ee445ed8ac319e0ef0b66148bd8f76c" + url: "https://pub.dev" + source: hosted + version: "1.3.8" + media_kit_libs_ios_video: + dependency: transitive + description: + name: media_kit_libs_ios_video + sha256: b5382994eb37a4564c368386c154ad70ba0cc78dacdd3fb0cd9f30db6d837991 + url: "https://pub.dev" + source: hosted + version: "1.1.4" + media_kit_libs_linux: + dependency: transitive + description: + name: media_kit_libs_linux + sha256: "2b473399a49ec94452c4d4ae51cfc0f6585074398d74216092bf3d54aac37ecf" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + media_kit_libs_macos_video: + dependency: transitive + description: + name: media_kit_libs_macos_video + sha256: f26aa1452b665df288e360393758f84b911f70ffb3878032e1aabba23aa1032d + url: "https://pub.dev" + source: hosted + version: "1.1.4" + media_kit_libs_video: + dependency: "direct main" + description: + name: media_kit_libs_video + sha256: "2b235b5dac79c6020e01eef5022c6cc85fedc0df1738aadc6ea489daa12a92a9" + url: "https://pub.dev" + source: hosted + version: "1.0.7" + media_kit_libs_windows_video: + dependency: transitive + description: + name: media_kit_libs_windows_video + sha256: dff76da2778729ab650229e6b4ec6ec111eb5151431002cbd7ea304ff1f112ab + url: "https://pub.dev" + source: hosted + version: "1.0.11" + media_kit_video: + dependency: "direct main" + description: + name: media_kit_video + sha256: afaa509e7b7e0bf247557a3a740cde903a52c34ace9810f94500e127bd7b043d + url: "https://pub.dev" + source: hosted + version: "2.0.1" meta: dependency: transitive description: @@ -808,6 +872,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20" + url: "https://pub.dev" + source: hosted + version: "9.0.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.dev" + source: hosted + version: "3.2.1" path: dependency: "direct main" description: @@ -976,6 +1056,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + safe_local_storage: + dependency: transitive + description: + name: safe_local_storage + sha256: "287ea1f667c0b93cdc127dccc707158e2d81ee59fba0459c31a0c7da4d09c755" + url: "https://pub.dev" + source: hosted + version: "2.0.3" screen_retriever: dependency: transitive description: @@ -1277,6 +1365,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + uri_parser: + dependency: transitive + description: + name: uri_parser + sha256: "051c62e5f693de98ca9f130ee707f8916e2266945565926be3ff20659f7853ce" + url: "https://pub.dev" + source: hosted + version: "3.0.2" url_launcher: dependency: "direct main" description: @@ -1341,6 +1445,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.5" + uuid: + dependency: transitive + description: + name: uuid + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.dev" + source: hosted + version: "4.5.3" vector_graphics: dependency: transitive description: @@ -1381,6 +1493,22 @@ packages: url: "https://pub.dev" source: hosted version: "15.2.0" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: ddf3db70eaa10c37558ff817519b85d527dbd21034fd5d8e1c2e85f31588f1c1 + url: "https://pub.dev" + source: hosted + version: "1.5.2" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: b13f99e992e7ae6a152e16c5559d3c07ff445b13330192662494e614ca3e7d7b + url: "https://pub.dev" + source: hosted + version: "1.5.1" watcher: dependency: transitive description: @@ -1470,5 +1598,5 @@ packages: source: hosted version: "2.2.4" sdks: - dart: "3.11.4" + dart: "3.11.5" flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml index 56412f7..f124f40 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,7 +9,7 @@ flutter: uses-material-design: true environment: - sdk: "3.11.4" + sdk: "3.11.5" dependency_overrides: linkify: @@ -60,6 +60,9 @@ dependencies: url: https://github.com/Henry-Hiles/emoji_text_field flutter_blurhash: 0.9.1 super_sliver_list: 0.4.1 + media_kit: 1.2.6 + media_kit_video: 2.0.1 + media_kit_libs_video: 1.0.7 dev_dependencies: build_runner: 2.15.0 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index bde1c28..8c54692 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,8 @@ #include #include +#include +#include #include #include #include @@ -17,6 +19,10 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); + MediaKitVideoPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 7b6b425..f769d6e 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,8 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_system_colors file_selector_windows + media_kit_libs_windows_video + media_kit_video screen_retriever_windows url_launcher_windows window_manager