placeholder widget for video support

This commit is contained in:
Henry Hiles 2026-05-19 16:05:45 -04:00
commit 1a4ef800c6
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
12 changed files with 265 additions and 54 deletions

12
flake.lock generated
View file

@ -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": {

View file

@ -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();

View file

@ -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;

View file

@ -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",

View file

@ -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();
}
}

View file

@ -8,6 +8,8 @@
#include <dynamic_system_colors/dynamic_color_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
#include <media_kit_video/media_kit_video_plugin.h>
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
#include <window_manager/window_manager_plugin.h>
@ -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);

View file

@ -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

View file

@ -22,7 +22,14 @@ pkgs.mkShell {
go
git
jdk17
flutter
libGL
wayland
(flutter.override {
extraPkgConfigPackages = [
mpv-unwrapped
libass
];
})
android.platform-tools
];

View file

@ -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"

View file

@ -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

View file

@ -8,6 +8,8 @@
#include <dynamic_system_colors/dynamic_color_plugin_c_api.h>
#include <file_selector_windows/file_selector_windows.h>
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
#include <media_kit_video/media_kit_video_plugin_c_api.h>
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
#include <window_manager/window_manager_plugin.h>
@ -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(

View file

@ -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