From c678bbcc56b2980932ea6bd5f710afc2eef8aa67 Mon Sep 17 00:00:00 2001 From: YeungKC <11473691+YeungKC@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:49:12 +0900 Subject: [PATCH 1/9] feat: migrate playback to media-kit --- .github/workflows/dart.yml | 1 + .github/workflows/manual-build.yml | 1 + AppImageBuilder-arm64.yml | 1 + AppImageBuilder.yml | 1 + README.md | 2 +- lib/main.dart | 2 + lib/ui/home/chat/files_preview.dart | 19 +- lib/ui/home/chat/files_preview_model.dart | 2 +- .../home/chat/voice_recorder_bottom_bar.dart | 1 + lib/ui/home/chat/voice_recorder_state.dart | 40 +++-- .../ogg_opus_audio_player.dart | 93 +++++----- .../item/video/media_kit_video_player.dart | 125 ++++++++++++++ .../message/item/video/progress_bar.dart | 46 ++--- .../message/item/video/video_message.dart | 22 +-- .../item/video/video_preview_page.dart | 26 +-- linux/flutter/generated_plugin_registrant.cc | 8 + linux/flutter/generated_plugins.cmake | 2 + macos/Flutter/GeneratedPluginRegistrant.swift | 8 +- pubspec.lock | 162 ++++++++++++------ pubspec.yaml | 4 +- .../flutter/generated_plugin_registrant.cc | 6 + windows/flutter/generated_plugins.cmake | 2 + 22 files changed, 396 insertions(+), 178 deletions(-) create mode 100644 lib/widgets/message/item/video/media_kit_video_player.dart diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 624d6c977c..90581c48e2 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -42,6 +42,7 @@ jobs: sudo apt-get install -y libsdl2-dev sudo apt-get install -y libwebkit2gtk-4.1-dev sudo apt-get install -y libopus-dev libogg-dev + sudo apt-get install -y libmpv-dev mpv sudo apt-get install -y libcurl4-openssl-dev flutter doctor diff --git a/.github/workflows/manual-build.yml b/.github/workflows/manual-build.yml index bffa6947de..27503608a7 100644 --- a/.github/workflows/manual-build.yml +++ b/.github/workflows/manual-build.yml @@ -95,6 +95,7 @@ jobs: sudo apt-get install -y libsdl2-dev sudo apt-get install -y libwebkit2gtk-4.1-dev sudo apt-get install -y libopus-dev libogg-dev + sudo apt-get install -y libmpv-dev mpv sudo apt-get install -y libcurl4-openssl-dev flutter doctor diff --git a/AppImageBuilder-arm64.yml b/AppImageBuilder-arm64.yml index a66e70fa2a..2accd53cec 100644 --- a/AppImageBuilder-arm64.yml +++ b/AppImageBuilder-arm64.yml @@ -30,6 +30,7 @@ AppDir: - fcitx5-frontend-gtk3 - libsdl2-2.0-0 - libopus0 + - libmpv2 exclude: - humanity-icon-theme - hicolor-icon-theme diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml index 5d770ca2fe..46400d009c 100644 --- a/AppImageBuilder.yml +++ b/AppImageBuilder.yml @@ -30,6 +30,7 @@ AppDir: - fcitx5-frontend-gtk3 - libsdl2-2.0-0 - libopus0 + - libmpv2 exclude: - humanity-icon-theme - hicolor-icon-theme diff --git a/README.md b/README.md index ddaa8ad145..5f2f8cebed 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,10 @@ sudo apt-get install libsqlite3-dev sudo apt-get install webkit2gtk-4.1 sudo apt-get install libsdl2-dev sudo apt-get install libopus-dev libogg-dev +sudo apt-get install libmpv-dev mpv ``` # License Released under the [GPLv3](https://github.com/MixinNetwork/flutter-app/blob/master/LICENSE) license. - diff --git a/lib/main.dart b/lib/main.dart index 29de4f3a96..d78561fcd6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:isolate/isolate.dart'; +import 'package:media_kit/media_kit.dart'; import 'package:overlay_support/overlay_support.dart'; import 'package:protocol_handler/protocol_handler.dart'; import 'package:rhttp/rhttp.dart'; @@ -43,6 +44,7 @@ Future _runApp(List args) async { await Rhttp.init(); WidgetsFlutterBinding.ensureInitialized(); + MediaKit.ensureInitialized(); await loadFallbackFonts(); diff --git a/lib/ui/home/chat/files_preview.dart b/lib/ui/home/chat/files_preview.dart index 4abeaf28d3..c8f49c0d03 100644 --- a/lib/ui/home/chat/files_preview.dart +++ b/lib/ui/home/chat/files_preview.dart @@ -14,12 +14,12 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image/image.dart' as image; +import 'package:media_kit_video/media_kit_video.dart'; import 'package:mime/mime.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; import 'package:super_context_menu/super_context_menu.dart'; import 'package:video_compress/video_compress.dart'; -import 'package:video_player/video_player.dart'; import '../../../constants/brightness_theme_data.dart'; import '../../../constants/constants.dart'; @@ -41,6 +41,7 @@ import '../../../widgets/high_light_text.dart'; import '../../../widgets/image.dart'; import '../../../widgets/interactive_decorated_box.dart'; import '../../../widgets/menu.dart'; +import '../../../widgets/message/item/video/media_kit_video_player.dart'; import '../../../widgets/mixin_image.dart'; import '../../provider/conversation_provider.dart'; import '../../provider/quote_message_provider.dart'; @@ -779,11 +780,12 @@ class _AnimatedListBuilder extends HookConsumerWidget { } final _videoControllerProvider = ChangeNotifierProvider.autoDispose - .family( - (ref, file) => VideoPlayerController.file(File(file.path)) - ..initialize() - ..setVolume(0) - ..setLooping(true), + .family( + (ref, file) => MediaKitVideoPlayer( + file.path, + looping: true, + muted: true, + ), ); final _videoBlurHashProvider = FutureProvider.autoDispose @@ -832,7 +834,10 @@ class _TileBigVideo extends HookConsumerWidget { Center( child: AspectRatio( aspectRatio: aspectRatio, - child: VideoPlayer(controller), + child: Video( + controller: controller.controller, + controls: null, + ), ), ), Positioned( diff --git a/lib/ui/home/chat/files_preview_model.dart b/lib/ui/home/chat/files_preview_model.dart index 9088b78339..cb92870450 100644 --- a/lib/ui/home/chat/files_preview_model.dart +++ b/lib/ui/home/chat/files_preview_model.dart @@ -16,7 +16,7 @@ sealed class _File { } if (resolvedFile.isImage) { return _ImageFile._(file: resolvedFile); - } else if (kPlatformIsDarwin && resolvedFile.isVideo) { + } else if (resolvedFile.isVideo) { return _VideoFile._(file: resolvedFile); } else { return _NormalFile._(file: resolvedFile); diff --git a/lib/ui/home/chat/voice_recorder_bottom_bar.dart b/lib/ui/home/chat/voice_recorder_bottom_bar.dart index 17151b644e..e0a57baea0 100644 --- a/lib/ui/home/chat/voice_recorder_bottom_bar.dart +++ b/lib/ui/home/chat/voice_recorder_bottom_bar.dart @@ -5,6 +5,7 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart' hide ChangeNotifierProvider; +import 'package:media_kit/media_kit.dart'; import 'package:ogg_opus_player/ogg_opus_player.dart'; import 'package:provider/provider.dart'; diff --git a/lib/ui/home/chat/voice_recorder_state.dart b/lib/ui/home/chat/voice_recorder_state.dart index e32af59861..e4172a473f 100644 --- a/lib/ui/home/chat/voice_recorder_state.dart +++ b/lib/ui/home/chat/voice_recorder_state.dart @@ -170,39 +170,45 @@ class VoiceRecorderNotifier extends ValueNotifier { } class _Player { - _Player(this.path); + _Player(this.path) : _player = Player() { + _subscriptions.addAll([ + _player.stream.playing.listen((playing) { + isPlaying.value = playing; + }), + _player.stream.completed.listen((completed) { + if (completed) { + unawaited(stop()); + } + }), + ]); + } final String path; final isPlaying = ValueNotifier(false); - double get position => _player?.currentPosition ?? 0.0; + double get position => + _player.state.position.inMicroseconds / Duration.microsecondsPerSecond; - OggOpusPlayer? _player; + final Player _player; + final _subscriptions = >[]; Future start() async { await AudioSession.instance.activePlayback(); - final player = OggOpusPlayer(path); - player.state.addListener(() { - final state = player.state.value; - isPlaying.value = state == PlayerState.playing; - if (state == PlayerState.ended) { - stop(); - } - }); - player.play(); - _player = player; + await _player.open(Media(path)); } Future stop() async { - _player?.pause(); - _player?.dispose(); - _player = null; + await _player.stop(); await AudioSession.instance.deactivate(); isPlaying.value = false; } void dispose() { - stop(); + unawaited(stop()); + for (final subscription in _subscriptions) { + unawaited(subscription.cancel()); + } + unawaited(_player.dispose()); } } diff --git a/lib/utils/audio_message_player/ogg_opus_audio_player.dart b/lib/utils/audio_message_player/ogg_opus_audio_player.dart index a523b5aa1f..2f5d0cde08 100644 --- a/lib/utils/audio_message_player/ogg_opus_audio_player.dart +++ b/lib/utils/audio_message_player/ogg_opus_audio_player.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:ogg_opus_player/ogg_opus_player.dart'; +import 'package:media_kit/media_kit.dart'; import 'package:rxdart/rxdart.dart'; import '../../db/mixin_database.dart'; @@ -31,9 +31,19 @@ enum PlaybackState { } class AudioMessagePlayer { + AudioMessagePlayer() { + _subscriptions.addAll([ + _player.stream.playing.listen(_handlePlaying), + _player.stream.completed.listen(_handleCompleted), + _player.stream.error.listen(_handleError), + ]); + } + final _currentPlaying = BehaviorSubject(); - OggOpusPlayer? _player; + final Player _player = Player(); + + final _subscriptions = >[]; final List _medias = []; @@ -49,6 +59,10 @@ class AudioMessagePlayer { void dispose() { stop(); + for (final subscription in _subscriptions) { + unawaited(subscription.cancel()); + } + unawaited(_player.dispose()); _medias.clear(); _index = -1; } @@ -75,8 +89,6 @@ class AudioMessagePlayer { void _playNext() { if (_index >= _medias.length - 1) { - // play ended. - _disposeCurrentPlayer(); _playbackState.value = PlaybackState.completed; return; } @@ -85,79 +97,64 @@ class AudioMessagePlayer { } else { _index++; } - _disposeCurrentPlayer(); final media = _medias[_index]; - final player = OggOpusPlayer(media.mediaPath); - assert(_player == null); - _player = player; - player.state.addListener(_handlePlayerState); - player - ..play() - ..setPlaybackRate(_playbackSpeed.value); _currentPlaying.value = current; + unawaited(_open(media)); } - void _handlePlayerState() { - final player = _player; - if (player == null) { - return; + Future _open(MessageMedia media) async { + try { + await _player.open(Media(media.mediaPath)); + await _player.setRate(_playbackSpeed.value); + } catch (error, stacktrace) { + e('play ${media.mediaPath} failed: $error $stacktrace'); + stop(); } - const interceptedEventType = { - PlayerState.idle, - PlayerState.error, - PlayerState.ended, - }; - final state = player.state.value; - if (!interceptedEventType.contains(state)) { - if (state == PlayerState.paused) { - _playbackState.value = PlaybackState.paused; - } else if (state == PlayerState.playing) { - _playbackState.value = PlaybackState.playing; - } + } + + void _handlePlaying(bool playing) { + if (playing) { + _playbackState.value = PlaybackState.playing; + } else if (_playbackState.value == PlaybackState.playing) { + _playbackState.value = PlaybackState.paused; } - if (state == PlayerState.ended) { + } + + void _handleCompleted(bool completed) { + if (completed) { _playNext(); - } else if (state == PlayerState.error) { - i('play ${current?.mediaPath} failed.'); - stop(); } } - Stream get playbackStream => _playbackState.stream; - - void _disposeCurrentPlayer() { - _player?.state.removeListener(_handlePlayerState); - _player?.dispose(); - _player = null; + void _handleError(String error) { + e('play ${current?.mediaPath} failed: $error'); + stop(); } + Stream get playbackStream => _playbackState.stream; + void stop() { - _disposeCurrentPlayer(); + unawaited(_player.stop()); _playbackState.value = PlaybackState.idle; } void pause() { - _player?.pause(); + unawaited(_player.pause()); } void resume() { - if (_player == null) { - e('resume failed, player is null.'); - return; - } assert( _playbackState.value == PlaybackState.paused, 'resume failed, player is not paused.', ); - _player?.play(); + unawaited(_player.play()); _playbackState.value = PlaybackState.playing; } - Duration currentPosition() => - Duration(milliseconds: ((_player?.currentPosition ?? 0) * 1000).toInt()); + Duration currentPosition() => _player.state.position; void setPlaybackSpeed(double speed) { _playbackSpeed.value = speed; - _player?.setPlaybackRate(speed); + unawaited(_player.setRate(speed)); } } diff --git a/lib/widgets/message/item/video/media_kit_video_player.dart b/lib/widgets/message/item/video/media_kit_video_player.dart new file mode 100644 index 0000000000..c81a177dae --- /dev/null +++ b/lib/widgets/message/item/video/media_kit_video_player.dart @@ -0,0 +1,125 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:media_kit/media_kit.dart'; +import 'package:media_kit_video/media_kit_video.dart'; +import 'package:mixin_logger/mixin_logger.dart'; + +class MediaKitVideoValue { + const MediaKitVideoValue({ + required this.position, + required this.duration, + required this.buffer, + required this.volume, + required this.isPlaying, + required this.width, + required this.height, + }); + + final Duration position; + final Duration duration; + final Duration buffer; + final double volume; + final bool isPlaying; + final int? width; + final int? height; + + bool get isInitialized => + width != null && height != null && duration > Duration.zero; + + double get aspectRatio { + final width = this.width; + final height = this.height; + if (width == null || height == null || width == 0 || height == 0) { + return 1; + } + return width / height; + } +} + +class MediaKitVideoPlayer extends ChangeNotifier { + MediaKitVideoPlayer( + this.path, { + bool autoPlay = false, + bool looping = false, + bool muted = false, + }) : player = Player() { + controller = VideoController(player); + _subscriptions.addAll([ + player.stream.playing.listen(_notify), + player.stream.position.listen(_notify), + player.stream.duration.listen(_notify), + player.stream.buffer.listen(_notify), + player.stream.volume.listen(_notify), + player.stream.width.listen(_notify), + player.stream.height.listen(_notify), + ]); + unawaited(_open(path, autoPlay: autoPlay, looping: looping, muted: muted)); + } + + final Player player; + final String path; + late final VideoController controller; + + final _subscriptions = >[]; + var _disposed = false; + + MediaKitVideoValue get value { + final state = player.state; + return MediaKitVideoValue( + position: state.position, + duration: state.duration, + buffer: state.buffer, + volume: (state.volume / 100).clamp(0.0, 1.0), + isPlaying: state.playing, + width: state.width, + height: state.height, + ); + } + + Future _open( + String path, { + required bool autoPlay, + required bool looping, + required bool muted, + }) async { + try { + await player.open(Media(path), play: autoPlay); + if (looping) { + await player.setPlaylistMode(PlaylistMode.single); + } + if (muted) { + await player.setVolume(0); + } + } catch (error, stackTrace) { + if (!_disposed) { + e('video playback failed: $path $error $stackTrace'); + } + } + } + + Future play() => player.play(); + + Future pause() => player.pause(); + + Future seekTo(Duration position) => player.seek(position); + + Future setVolume(double volume) => + player.setVolume(volume.clamp(0.0, 1.0) * 100); + + void _notify(T _) { + if (!_disposed) { + notifyListeners(); + } + } + + @override + void dispose() { + _disposed = true; + for (final subscription in _subscriptions) { + unawaited(subscription.cancel()); + } + unawaited(player.dispose()); + super.dispose(); + } +} diff --git a/lib/widgets/message/item/video/progress_bar.dart b/lib/widgets/message/item/video/progress_bar.dart index d0b18b9716..102adf16e3 100644 --- a/lib/widgets/message/item/video/progress_bar.dart +++ b/lib/widgets/message/item/video/progress_bar.dart @@ -1,9 +1,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:video_player/video_player.dart'; import '../../../../utils/logger.dart'; +import 'media_kit_video_player.dart'; /// From project: https://github.com/fluttercommunity/chewie class CupertinoVideoProgressBar extends StatelessWidget { @@ -16,7 +16,7 @@ class CupertinoVideoProgressBar extends StatelessWidget { super.key, }); - final VideoPlayerController controller; + final MediaKitVideoPlayer controller; final ChewieProgressColors? colors; final Function()? onDragStart; final Function()? onDragEnd; @@ -72,7 +72,7 @@ class VideoProgressBar extends StatefulWidget { super.key, }) : colors = colors ?? ChewieProgressColors(); - final VideoPlayerController controller; + final MediaKitVideoPlayer controller; final ChewieProgressColors colors; final Function()? onDragStart; final Function()? onDragEnd; @@ -96,7 +96,7 @@ class _VideoProgressBarState extends State { Offset? _latestDraggableOffset; - VideoPlayerController get controller => widget.controller; + MediaKitVideoPlayer get controller => widget.controller; @override void initState() { @@ -190,7 +190,7 @@ class StaticProgressBar extends StatelessWidget { }); final Offset? latestDraggableOffset; - final VideoPlayerValue value; + final MediaKitVideoValue value; final ChewieProgressColors colors; final double barHeight; @@ -224,7 +224,7 @@ class _ProgressBarPainter extends CustomPainter { required this.draggableValue, }); - VideoPlayerValue value; + MediaKitVideoValue value; ChewieProgressColors colors; final double barHeight; @@ -260,30 +260,30 @@ class _ProgressBarPainter extends CustomPainter { final playedPart = playedPartPercent > 1 ? size.width : playedPartPercent * size.width; - for (final range in value.buffered) { - final start = range.startFraction(value.duration) * size.width; - final end = range.endFraction(value.duration) * size.width; - canvas.drawRRect( + final bufferedPart = + value.buffer.inMilliseconds / value.duration.inMilliseconds; + final bufferedEnd = bufferedPart.clamp(0.0, 1.0) * size.width; + canvas + ..drawRRect( RRect.fromRectAndRadius( Rect.fromPoints( - Offset(start, baseOffset), - Offset(end, baseOffset + barHeight), + Offset(0, baseOffset), + Offset(bufferedEnd, baseOffset + barHeight), ), const Radius.circular(4), ), colors.bufferedPaint, - ); - } - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromPoints( - Offset(0, baseOffset), - Offset(playedPart, baseOffset + barHeight), + ) + ..drawRRect( + RRect.fromRectAndRadius( + Rect.fromPoints( + Offset(0, baseOffset), + Offset(playedPart, baseOffset + barHeight), + ), + const Radius.circular(4), ), - const Radius.circular(4), - ), - colors.playedPaint, - ); + colors.playedPaint, + ); if (drawShadow) { final shadowPath = Path() diff --git a/lib/widgets/message/item/video/video_message.dart b/lib/widgets/message/item/video/video_message.dart index 5ab1dc5541..52cc9f6a81 100644 --- a/lib/widgets/message/item/video/video_message.dart +++ b/lib/widgets/message/item/video/video_message.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; @@ -6,14 +5,11 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; -import 'package:open_file/open_file.dart'; import 'package:url_launcher/url_launcher_string.dart'; import '../../../../constants/resources.dart'; import '../../../../enum/media_status.dart'; import '../../../../utils/extension/extension.dart'; -import '../../../../utils/platform.dart'; -import '../../../../utils/uri_utils.dart'; import '../../../image.dart'; import '../../../interactive_decorated_box.dart'; import '../../../mixin_image.dart'; @@ -112,18 +108,12 @@ class MessageVideo extends HookConsumerWidget { message, isTranscriptPage, ); - if (kPlatformIsDarwin) { - showVideoPreviewPage( - context, - path, - message: message, - isTranscriptPage: isTranscriptPage, - ); - } else if (Platform.isIOS || Platform.isAndroid) { - OpenFile.open(path); - } else { - openUri(context, Uri.file(path).toString()); - } + showVideoPreviewPage( + context, + path, + message: message, + isTranscriptPage: isTranscriptPage, + ); } else if (message.mediaStatus == MediaStatus.pending) { context.accountServer.cancelProgressAttachmentJob(message.messageId); } else if (message.type.isLive && message.mediaUrl != null) { diff --git a/lib/widgets/message/item/video/video_preview_page.dart b/lib/widgets/message/item/video/video_preview_page.dart index e4aa9f0411..e9ae7a8a53 100644 --- a/lib/widgets/message/item/video/video_preview_page.dart +++ b/lib/widgets/message/item/video/video_preview_page.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'dart:math' as math; import 'dart:ui' as ui; @@ -9,7 +8,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:video_player/video_player.dart'; +import 'package:media_kit_video/media_kit_video.dart'; import '../../../../constants/resources.dart'; import '../../../../db/mixin_database.dart'; @@ -19,6 +18,7 @@ import '../../../action_button.dart'; import '../../../avatar_view/avatar_view.dart'; import '../../../user_selector/conversation_selector.dart'; import '../../message.dart'; +import 'media_kit_video_player.dart'; import 'progress_bar.dart'; import 'slider.dart'; @@ -37,7 +37,7 @@ Future showVideoPreviewPage( barrierDismissible: false, ); -final videoPlayerProvider = ChangeNotifierProvider(( +final videoPlayerProvider = ChangeNotifierProvider(( ref, ) { throw UnimplementedError(); @@ -122,12 +122,9 @@ class _VideoPreviewPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final controller = useMemoized(() { - final controller = VideoPlayerController.file(File(path)); - return controller - ..initialize() - ..play(); - }); + final controller = useMemoized( + () => MediaKitVideoPlayer(path, autoPlay: true), + ); useEffect(() => controller.dispose, [controller]); @@ -273,9 +270,6 @@ class _PlayerShortcuts extends HookConsumerWidget { ), _CloseIntent: CallbackAction<_CloseIntent>( onInvoke: (intent) { - ref.read(videoPlayerProvider) - ..pause() - ..dispose(); Navigator.pop(context); }, ), @@ -416,7 +410,13 @@ class VideoFrame extends ConsumerWidget { ); final controller = ref.watch(videoPlayerProvider.select((value) => value)); return Center( - child: AspectRatio(aspectRatio: aspect, child: VideoPlayer(controller)), + child: AspectRatio( + aspectRatio: aspect, + child: Video( + controller: controller.controller, + controls: null, + ), + ), ); } } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 728e2ddc80..041b835618 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -39,6 +41,12 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin"); irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_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) open_file_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin"); open_file_linux_plugin_register_with_registrar(open_file_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index daffb27115..15bdefada0 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -9,6 +9,8 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux flutter_app_icon_badge irondash_engine_context + media_kit_libs_linux + media_kit_video open_file_linux screen_retriever_linux sqlite3_flutter_libs diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 017f859b1e..ff90c80276 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -16,6 +16,8 @@ import flutter_app_icon_badge import flutter_local_notifications import irondash_engine_context import local_auth_darwin +import media_kit_libs_macos_video +import media_kit_video import network_info_plus import ogg_opus_player import open_file_mac @@ -27,7 +29,7 @@ import sqlite3_flutter_libs import super_native_extensions import url_launcher_macos import video_compress -import video_player_avfoundation +import wakelock_plus import webview_flutter_wkwebview import window_manager import window_size @@ -44,6 +46,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin")) LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) + MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) + MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin")) OggOpusPlayerPlugin.register(with: registry.registrar(forPlugin: "OggOpusPlayerPlugin")) OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) @@ -55,7 +59,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin")) - VideoPlayerPlugin.register(with: registry.registrar(forPlugin: "VideoPlayerPlugin")) + WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) WindowSizePlugin.register(with: registry.registrar(forPlugin: "WindowSizePlugin")) diff --git a/pubspec.lock b/pubspec.lock index 5ce0fea803..088069a0ae 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1183,10 +1183,10 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.20" material_color_utilities: dependency: transitive description: @@ -1195,14 +1195,78 @@ 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: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" mime: dependency: "direct main" description: @@ -1668,6 +1732,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + safe_local_storage: + dependency: transitive + description: + name: safe_local_storage + sha256: "494b982d5edb71030650ea463d939670e91b232b588323dc75229d2c5f23e7b7" + url: "https://pub.dev" + source: hosted + version: "2.0.6" screen_retriever: dependency: transitive description: @@ -1925,10 +1997,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.12" thirds: dependency: transitive description: @@ -1970,6 +2042,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.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: @@ -2070,10 +2158,10 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.2" very_good_analysis: dependency: "direct dev" description: @@ -2090,46 +2178,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.4" - video_player: - dependency: "direct main" - description: - name: video_player - sha256: "20ef311160e2ced020a62f38e4cc399a1bf289e722fc5fe37c9d7b18e44c9e8d" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - video_player_android: - dependency: transitive - description: - name: video_player_android - sha256: e229676f8fade3e0124482495aaa2b548872cc4018b6268adfca4868be16aa74 - url: "https://pub.dev" - source: hosted - version: "2.12.0" - video_player_avfoundation: - dependency: transitive - description: - name: video_player_avfoundation - sha256: c238f5f0a26845cd0bcc2956049b63065a4d3c40ddfc22c3414cd170bef7fff9 - url: "https://pub.dev" - source: hosted - version: "2.11.0" - video_player_platform_interface: - dependency: transitive - description: - name: video_player_platform_interface - sha256: "92c0fbabe20c788e71fd10d26cea998d0d253282e65d145aed0818731cf593ce" - url: "https://pub.dev" - source: hosted - version: "6.9.0" - video_player_web: - dependency: transitive - description: - name: video_player_web - sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" - url: "https://pub.dev" - source: hosted - version: "2.4.0" visibility_detector: dependency: "direct main" description: @@ -2146,6 +2194,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: "0618d1799f0b28bcf98255b4ee8313e6fc4d38589dc4ee5fe5840d57d1aff6da" + url: "https://pub.dev" + source: hosted + version: "1.6.0" watcher: dependency: "direct main" description: @@ -2294,4 +2358,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.12.0 <4.0.0" - flutter: ">=3.44.0" + flutter: ">=3.44.0 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 9d20ee4f72..ccc97b8aa4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -90,6 +90,9 @@ dependencies: lottie: ^3.3.3 map: ^2.0.2 markdown_widget: ^2.3.2+2 + media_kit: ^1.2.6 + media_kit_libs_video: ^1.0.7 + media_kit_video: ^2.0.1 mime: ^2.0.0 mixin_bot_sdk_dart: ^1.5.0 mixin_logger: ^0.1.3 @@ -142,7 +145,6 @@ dependencies: path: plugins/window_size ref: 12decbe0f592e14e03223f6f2c0c7e0e2dbd70a1 html: ^0.15.6 - video_player: ^2.11.1 cupertino_icons: ^1.0.9 video_compress: ^3.1.4 webcrypto: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index aad0feca15..71c1610f16 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -38,6 +40,10 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi")); LocalAuthPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("LocalAuthPlugin")); + MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); + MediaKitVideoPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); ProtocolHandlerWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 904207eea4..daeefbde67 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -10,6 +10,8 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_app_icon_badge irondash_engine_context local_auth_windows + media_kit_libs_windows_video + media_kit_video protocol_handler_windows screen_retriever_windows sqlite3_flutter_libs From dd3df12864fb9096330c33758c35da748bcde414 Mon Sep 17 00:00:00 2001 From: YeungKC <11473691+YeungKC@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:49:54 +0900 Subject: [PATCH 2/9] fix: preserve platform video file handling --- lib/ui/home/chat/files_preview_model.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ui/home/chat/files_preview_model.dart b/lib/ui/home/chat/files_preview_model.dart index cb92870450..9088b78339 100644 --- a/lib/ui/home/chat/files_preview_model.dart +++ b/lib/ui/home/chat/files_preview_model.dart @@ -16,7 +16,7 @@ sealed class _File { } if (resolvedFile.isImage) { return _ImageFile._(file: resolvedFile); - } else if (resolvedFile.isVideo) { + } else if (kPlatformIsDarwin && resolvedFile.isVideo) { return _VideoFile._(file: resolvedFile); } else { return _NormalFile._(file: resolvedFile); From 6365f09d3c37b9f21eee47c3ba4baf2d247d2ee6 Mon Sep 17 00:00:00 2001 From: YeungKC <11473691+YeungKC@users.noreply.github.com> Date: Wed, 5 Aug 2026 14:07:17 +0900 Subject: [PATCH 3/9] fix: hide video until first frame --- .../item/video/video_preview_page.dart | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/widgets/message/item/video/video_preview_page.dart b/lib/widgets/message/item/video/video_preview_page.dart index e9ae7a8a53..ce4578d1b9 100644 --- a/lib/widgets/message/item/video/video_preview_page.dart +++ b/lib/widgets/message/item/video/video_preview_page.dart @@ -400,7 +400,7 @@ class _Bar extends ConsumerWidget { ); } -class VideoFrame extends ConsumerWidget { +class VideoFrame extends HookConsumerWidget { const VideoFrame({super.key}); @override @@ -409,11 +409,35 @@ class VideoFrame extends ConsumerWidget { videoPlayerProvider.select((value) => value.value.aspectRatio), ); final controller = ref.watch(videoPlayerProvider.select((value) => value)); + final firstFrameRendered = useState(false); + + useEffect(() { + var disposed = false; + unawaited(() async { + try { + await controller.controller.waitUntilFirstFrameRendered; + if (!disposed) { + firstFrameRendered.value = true; + } + } catch (_) { + // Keep the transparent placeholder when the native video output fails. + } + }()); + return () { + disposed = true; + }; + }, [controller]); + + if (!firstFrameRendered.value) { + return const SizedBox.expand(); + } + return Center( child: AspectRatio( aspectRatio: aspect, child: Video( controller: controller.controller, + fill: Colors.transparent, controls: null, ), ), From 52caa88c5d47494d95c5b6351690ea79ced7803d Mon Sep 17 00:00:00 2001 From: YeungKC <11473691+YeungKC@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:33:44 +0900 Subject: [PATCH 4/9] fix: initialize playback before starting --- .../ogg_opus_audio_player.dart | 2 +- .../item/video/media_kit_video_player.dart | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/utils/audio_message_player/ogg_opus_audio_player.dart b/lib/utils/audio_message_player/ogg_opus_audio_player.dart index 2f5d0cde08..c2c59c157e 100644 --- a/lib/utils/audio_message_player/ogg_opus_audio_player.dart +++ b/lib/utils/audio_message_player/ogg_opus_audio_player.dart @@ -104,8 +104,8 @@ class AudioMessagePlayer { Future _open(MessageMedia media) async { try { - await _player.open(Media(media.mediaPath)); await _player.setRate(_playbackSpeed.value); + await _player.open(Media(media.mediaPath)); } catch (error, stacktrace) { e('play ${media.mediaPath} failed: $error $stacktrace'); stop(); diff --git a/lib/widgets/message/item/video/media_kit_video_player.dart b/lib/widgets/message/item/video/media_kit_video_player.dart index c81a177dae..95d7da19bb 100644 --- a/lib/widgets/message/item/video/media_kit_video_player.dart +++ b/lib/widgets/message/item/video/media_kit_video_player.dart @@ -54,7 +54,12 @@ class MediaKitVideoPlayer extends ChangeNotifier { player.stream.width.listen(_notify), player.stream.height.listen(_notify), ]); - unawaited(_open(path, autoPlay: autoPlay, looping: looping, muted: muted)); + _initialized = _open( + path, + autoPlay: autoPlay, + looping: looping, + muted: muted, + ); } final Player player; @@ -62,6 +67,7 @@ class MediaKitVideoPlayer extends ChangeNotifier { late final VideoController controller; final _subscriptions = >[]; + late final Future _initialized; var _disposed = false; MediaKitVideoValue get value { @@ -84,13 +90,13 @@ class MediaKitVideoPlayer extends ChangeNotifier { required bool muted, }) async { try { + if (muted) { + await player.setVolume(0); + } await player.open(Media(path), play: autoPlay); if (looping) { await player.setPlaylistMode(PlaylistMode.single); } - if (muted) { - await player.setVolume(0); - } } catch (error, stackTrace) { if (!_disposed) { e('video playback failed: $path $error $stackTrace'); @@ -98,7 +104,10 @@ class MediaKitVideoPlayer extends ChangeNotifier { } } - Future play() => player.play(); + Future play() async { + await _initialized; + await player.play(); + } Future pause() => player.pause(); From a8e9f6028ed8e177f03b8352c31050197eae5f15 Mon Sep 17 00:00:00 2001 From: YeungKC <11473691+YeungKC@users.noreply.github.com> Date: Tue, 11 Aug 2026 18:14:00 +0900 Subject: [PATCH 5/9] fix: complete media-kit packaging --- .github/workflows/manual-build.yml | 55 ++++++++ README.md | 5 + dist/deb/DEBIAN/control | 2 +- dist/flatpak/mixin_desktop.desktop | 10 ++ dist/flatpak/mixin_desktop.metainfo.xml | 35 ++++++ dist/flatpak/mixin_desktop.png | Bin 0 -> 26646 bytes dist/flatpak/one.mixin.messenger.desktop.yml | 119 ++++++++++++++++++ dist/snap/snap/snapcraft.yaml | 3 +- .../ogg_opus_audio_player.dart | 3 +- .../message/item/video/video_message.dart | 22 +++- 10 files changed, 245 insertions(+), 9 deletions(-) create mode 100644 dist/flatpak/mixin_desktop.desktop create mode 100644 dist/flatpak/mixin_desktop.metainfo.xml create mode 100644 dist/flatpak/mixin_desktop.png create mode 100644 dist/flatpak/one.mixin.messenger.desktop.yml diff --git a/.github/workflows/manual-build.yml b/.github/workflows/manual-build.yml index 27503608a7..26d1aec0aa 100644 --- a/.github/workflows/manual-build.yml +++ b/.github/workflows/manual-build.yml @@ -219,6 +219,61 @@ jobs: tag: ${{ github.ref }} overwrite: true + build-linux-flatpak: + needs: build-linux + if: contains(github.event.inputs.platforms, 'linux') || github.event.inputs.platforms == '' + runs-on: ${{ matrix.runner }} + permissions: + contents: read + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-50 + options: --privileged + strategy: + matrix: + include: + - arch: amd64 + flatpak_arch: x86_64 + runner: ubuntu-22.04 + artifact_suffix: "" + - arch: arm64 + flatpak_arch: aarch64 + runner: ubuntu-22.04-arm + artifact_suffix: "-arm64" + steps: + - uses: actions/checkout@v4 + + - name: Download app archive + uses: actions/download-artifact@v4.1.7 + with: + name: mixin_desktop_linux_${{ matrix.arch }}_portable + + - name: Prepare Flatpak source + run: mv mixin_desktop_linux_${{ matrix.arch }}_portable.tar.gz dist/flatpak/mixin_desktop_linux_portable.tar.gz + + - name: Build Flatpak + uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + arch: ${{ matrix.flatpak_arch }} + bundle: mixin_desktop_linux_${{ matrix.arch }}.flatpak + cache-key: flatpak-builder-${{ matrix.flatpak_arch }}-${{ github.sha }} + manifest-path: dist/flatpak/one.mixin.messenger.desktop.yml + + - name: Upload Flatpak artifact + uses: actions/upload-artifact@v4 + with: + name: mixin-desktop-flatpak${{ matrix.artifact_suffix }} + path: mixin_desktop_linux_${{ matrix.arch }}.flatpak + + - name: Upload Linux Flatpak + uses: svenstaro/upload-release-action@v2 + if: startsWith(github.ref, 'refs/tags/v') + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: mixin_desktop_linux_${{ matrix.arch }}.flatpak + asset_name: mixin_desktop_linux_${{ matrix.arch }}.flatpak + tag: ${{ github.ref }} + overwrite: true + build-linux-appimage: needs: build-linux runs-on: ${{ matrix.runner }} diff --git a/README.md b/README.md index 5f2f8cebed..2795f11d5f 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,11 @@ sudo apt-get install libopus-dev libogg-dev sudo apt-get install libmpv-dev mpv ``` +### Portable archive runtime + +The portable Linux archive uses the system's shared libraries. Install the +`libmpv` runtime before using it (`libmpv2` on Debian and Ubuntu). + # License diff --git a/dist/deb/DEBIAN/control b/dist/deb/DEBIAN/control index 5db60cbcc0..2c629562cc 100644 --- a/dist/deb/DEBIAN/control +++ b/dist/deb/DEBIAN/control @@ -5,5 +5,5 @@ Homepage: https://mixin.one/messenger Package: mixin-desktop Maintainer: Mixin Team Architecture: amd64 -Depends: libgtk-3-0 (>= 3.10.0), libsdl2-2.0-0, libwebkit2gtk-4.1-0, libopus0 +Depends: libgtk-3-0 (>= 3.10.0), libsdl2-2.0-0, libwebkit2gtk-4.1-0, libopus0, libmpv2 Description: Mixin messenger desktop diff --git a/dist/flatpak/mixin_desktop.desktop b/dist/flatpak/mixin_desktop.desktop new file mode 100644 index 0000000000..61acb6a1a9 --- /dev/null +++ b/dist/flatpak/mixin_desktop.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Name=Mixin Messenger +Comment=An encrypted messenger with Signal protocol and cryptocurrency wallet +GenericName=Internet Messenger +Categories=InstantMessaging +Version=1.0 +Exec=mixin_desktop +Terminal=false +Icon=mixin_desktop diff --git a/dist/flatpak/mixin_desktop.metainfo.xml b/dist/flatpak/mixin_desktop.metainfo.xml new file mode 100644 index 0000000000..b20e47cf65 --- /dev/null +++ b/dist/flatpak/mixin_desktop.metainfo.xml @@ -0,0 +1,35 @@ + + + + one.mixin.messenger.desktop + Mixin Messenger + Mixin Messenger + Mixin Team + https://mixin.one/mm + CC0-1.0 + GPL-3.0 + + pointing + keyboard + touch + + +

Mixin Messenger is an open-source signal protocol messenger.

+
+ one.mixin.messenger.desktop.desktop + + + https://raw.githubusercontent.com/MixinNetwork/flutter-app/main/dist/screenshots/linux_1.png + https://raw.githubusercontent.com/MixinNetwork/flutter-app/main/dist/screenshots/linux_2.png + https://raw.githubusercontent.com/MixinNetwork/flutter-app/main/dist/screenshots/linux_3.png + https://raw.githubusercontent.com/MixinNetwork/flutter-app/main/dist/screenshots/linux_4.png + + + + + + +
diff --git a/dist/flatpak/mixin_desktop.png b/dist/flatpak/mixin_desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..09a4eabd2b7a2ed7dc1bc67b650ccfacc8a7aaeb GIT binary patch literal 26646 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 z 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 i@BbeiI}- Date: Tue, 11 Aug 2026 22:32:56 +0800 Subject: [PATCH 6/9] fix: move Flatpak packaging to Flathub --- .github/workflows/manual-build.yml | 55 --------- dist/flatpak/mixin_desktop.desktop | 10 -- dist/flatpak/mixin_desktop.metainfo.xml | 35 ------ dist/flatpak/mixin_desktop.png | Bin 26646 -> 0 bytes dist/flatpak/one.mixin.messenger.desktop.yml | 119 ------------------- 5 files changed, 219 deletions(-) delete mode 100644 dist/flatpak/mixin_desktop.desktop delete mode 100644 dist/flatpak/mixin_desktop.metainfo.xml delete mode 100644 dist/flatpak/mixin_desktop.png delete mode 100644 dist/flatpak/one.mixin.messenger.desktop.yml diff --git a/.github/workflows/manual-build.yml b/.github/workflows/manual-build.yml index 26d1aec0aa..27503608a7 100644 --- a/.github/workflows/manual-build.yml +++ b/.github/workflows/manual-build.yml @@ -219,61 +219,6 @@ jobs: tag: ${{ github.ref }} overwrite: true - build-linux-flatpak: - needs: build-linux - if: contains(github.event.inputs.platforms, 'linux') || github.event.inputs.platforms == '' - runs-on: ${{ matrix.runner }} - permissions: - contents: read - container: - image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-50 - options: --privileged - strategy: - matrix: - include: - - arch: amd64 - flatpak_arch: x86_64 - runner: ubuntu-22.04 - artifact_suffix: "" - - arch: arm64 - flatpak_arch: aarch64 - runner: ubuntu-22.04-arm - artifact_suffix: "-arm64" - steps: - - uses: actions/checkout@v4 - - - name: Download app archive - uses: actions/download-artifact@v4.1.7 - with: - name: mixin_desktop_linux_${{ matrix.arch }}_portable - - - name: Prepare Flatpak source - run: mv mixin_desktop_linux_${{ matrix.arch }}_portable.tar.gz dist/flatpak/mixin_desktop_linux_portable.tar.gz - - - name: Build Flatpak - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 - with: - arch: ${{ matrix.flatpak_arch }} - bundle: mixin_desktop_linux_${{ matrix.arch }}.flatpak - cache-key: flatpak-builder-${{ matrix.flatpak_arch }}-${{ github.sha }} - manifest-path: dist/flatpak/one.mixin.messenger.desktop.yml - - - name: Upload Flatpak artifact - uses: actions/upload-artifact@v4 - with: - name: mixin-desktop-flatpak${{ matrix.artifact_suffix }} - path: mixin_desktop_linux_${{ matrix.arch }}.flatpak - - - name: Upload Linux Flatpak - uses: svenstaro/upload-release-action@v2 - if: startsWith(github.ref, 'refs/tags/v') - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: mixin_desktop_linux_${{ matrix.arch }}.flatpak - asset_name: mixin_desktop_linux_${{ matrix.arch }}.flatpak - tag: ${{ github.ref }} - overwrite: true - build-linux-appimage: needs: build-linux runs-on: ${{ matrix.runner }} diff --git a/dist/flatpak/mixin_desktop.desktop b/dist/flatpak/mixin_desktop.desktop deleted file mode 100644 index 61acb6a1a9..0000000000 --- a/dist/flatpak/mixin_desktop.desktop +++ /dev/null @@ -1,10 +0,0 @@ -[Desktop Entry] -Type=Application -Name=Mixin Messenger -Comment=An encrypted messenger with Signal protocol and cryptocurrency wallet -GenericName=Internet Messenger -Categories=InstantMessaging -Version=1.0 -Exec=mixin_desktop -Terminal=false -Icon=mixin_desktop diff --git a/dist/flatpak/mixin_desktop.metainfo.xml b/dist/flatpak/mixin_desktop.metainfo.xml deleted file mode 100644 index b20e47cf65..0000000000 --- a/dist/flatpak/mixin_desktop.metainfo.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - one.mixin.messenger.desktop - Mixin Messenger - Mixin Messenger - Mixin Team - https://mixin.one/mm - CC0-1.0 - GPL-3.0 - - pointing - keyboard - touch - - -

Mixin Messenger is an open-source signal protocol messenger.

-
- one.mixin.messenger.desktop.desktop - - - https://raw.githubusercontent.com/MixinNetwork/flutter-app/main/dist/screenshots/linux_1.png - https://raw.githubusercontent.com/MixinNetwork/flutter-app/main/dist/screenshots/linux_2.png - https://raw.githubusercontent.com/MixinNetwork/flutter-app/main/dist/screenshots/linux_3.png - https://raw.githubusercontent.com/MixinNetwork/flutter-app/main/dist/screenshots/linux_4.png - - - - - - -
diff --git a/dist/flatpak/mixin_desktop.png b/dist/flatpak/mixin_desktop.png deleted file mode 100644 index 09a4eabd2b7a2ed7dc1bc67b650ccfacc8a7aaeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26646 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 z 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 i@BbeiI}- Date: Tue, 11 Aug 2026 22:52:28 +0800 Subject: [PATCH 7/9] fix: use Jammy libmpv package for AppImage --- AppImageBuilder-arm64.yml | 2 +- AppImageBuilder.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AppImageBuilder-arm64.yml b/AppImageBuilder-arm64.yml index 2accd53cec..5d01991b15 100644 --- a/AppImageBuilder-arm64.yml +++ b/AppImageBuilder-arm64.yml @@ -30,7 +30,7 @@ AppDir: - fcitx5-frontend-gtk3 - libsdl2-2.0-0 - libopus0 - - libmpv2 + - libmpv1 exclude: - humanity-icon-theme - hicolor-icon-theme diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml index 46400d009c..7d31053552 100644 --- a/AppImageBuilder.yml +++ b/AppImageBuilder.yml @@ -30,7 +30,7 @@ AppDir: - fcitx5-frontend-gtk3 - libsdl2-2.0-0 - libopus0 - - libmpv2 + - libmpv1 exclude: - humanity-icon-theme - hicolor-icon-theme From e3822ddaa7c416ddd8ac90cad742b063d48fcf80 Mon Sep 17 00:00:00 2001 From: boyan <17426470+boyan01@users.noreply.github.com> Date: Wed, 12 Aug 2026 10:40:24 +0800 Subject: [PATCH 8/9] build(linux): move release baseline to Ubuntu 24.04 --- .github/workflows/dart.yml | 2 +- .github/workflows/manual-build.yml | 21 ++++++++++++--------- AppImageBuilder-arm64.yml | 14 +++++++------- AppImageBuilder.yml | 14 +++++++------- dist/deb/DEBIAN/control | 2 +- dist/snap/snap/snapcraft.yaml | 2 +- 6 files changed, 29 insertions(+), 26 deletions(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 90581c48e2..14dc60e499 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -16,7 +16,7 @@ permissions: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/manual-build.yml b/.github/workflows/manual-build.yml index 27503608a7..9867c939f8 100644 --- a/.github/workflows/manual-build.yml +++ b/.github/workflows/manual-build.yml @@ -45,10 +45,10 @@ jobs: matrix: include: - arch: amd64 - runner: ubuntu-22.04 + runner: ubuntu-24.04 flutter_arch: x64 - arch: arm64 - runner: ubuntu-22.04-arm + runner: ubuntu-24.04-arm flutter_arch: arm64 steps: - run: g++ --version @@ -170,11 +170,11 @@ jobs: matrix: include: - arch: amd64 - runner: ubuntu-22.04 + runner: ubuntu-24.04 flutter_arch: x64 artifact_suffix: "" - arch: arm64 - runner: ubuntu-22.04-arm + runner: ubuntu-24.04-arm flutter_arch: arm64 artifact_suffix: "-arm64" steps: @@ -228,13 +228,13 @@ jobs: matrix: include: - arch: amd64 - runner: ubuntu-22.04 + runner: ubuntu-24.04 flutter_arch: x64 appimage_arch: x86_64 recipe: ./AppImageBuilder.yml artifact_suffix: "" - arch: arm64 - runner: ubuntu-22.04-arm + runner: ubuntu-24.04-arm flutter_arch: arm64 appimage_arch: aarch64 recipe: ./AppImageBuilder-arm64.yml @@ -256,7 +256,7 @@ jobs: - name: Install appimage-builder dependencies run: | sudo apt-get update - sudo apt-get install -y python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace binutils coreutils + sudo apt-get install -y python3-venv patchelf desktop-file-utils libgdk-pixbuf-2.0-dev fakeroot strace binutils coreutils - name: Download appimagetool run: | @@ -268,10 +268,13 @@ jobs: sudo chmod +x /usr/local/bin/appimagetool - name: Install appimage-builder - run: sudo pip3 install appimage-builder==1.1.0 + run: | + python3 -m venv "${{ runner.temp }}/appimage-builder-venv" + "${{ runner.temp }}/appimage-builder-venv/bin/pip" install appimage-builder==1.1.0 - name: Build AppImage - run: appimage-builder --recipe ${{ matrix.recipe }} --skip-test + run: | + "${{ runner.temp }}/appimage-builder-venv/bin/appimage-builder" --recipe "${{ matrix.recipe }}" --skip-test - run: mv "Mixin Messenger-latest-${{ matrix.appimage_arch }}.AppImage" mixin_desktop_linux_${{ matrix.arch }}.AppImage - name: Upload AppImage Artifact diff --git a/AppImageBuilder-arm64.yml b/AppImageBuilder-arm64.yml index 5d01991b15..8de512388a 100644 --- a/AppImageBuilder-arm64.yml +++ b/AppImageBuilder-arm64.yml @@ -18,19 +18,19 @@ AppDir: arch: arm64 allow_unauthenticated: true sources: - - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe multiverse - - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe multiverse - - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted universe multiverse - - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse + - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ noble main restricted universe multiverse + - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ noble-updates main restricted universe multiverse + - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ noble-backports main restricted universe multiverse + - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ noble-security main restricted universe multiverse include: - - libgtk-3-0 + - libgtk-3-0t64 - libwebkit2gtk-4.1-0 # Bundle GTK input method clients, but keep the input method daemon on the host. - ibus-gtk3 - fcitx5-frontend-gtk3 - libsdl2-2.0-0 - libopus0 - - libmpv1 + - libmpv2 exclude: - humanity-icon-theme - hicolor-icon-theme @@ -46,7 +46,7 @@ AppDir: after_bundle: # Package extraction does not run Debian's GTK cache trigger. AppRun v2 maps # the generated build-time AppDir paths to the runtime AppDir mount point. - - LD_LIBRARY_PATH=AppDir/lib/aarch64-linux-gnu:AppDir/usr/lib/aarch64-linux-gnu AppDir/usr/lib/aarch64-linux-gnu/libgtk-3-0/gtk-query-immodules-3.0 AppDir/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/immodules/*.so > AppDir/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/immodules.cache + - LD_LIBRARY_PATH=AppDir/lib/aarch64-linux-gnu:AppDir/usr/lib/aarch64-linux-gnu AppDir/usr/lib/aarch64-linux-gnu/libgtk-3-0t64/gtk-query-immodules-3.0 AppDir/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/immodules/*.so > AppDir/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/immodules.cache runtime: env: GIO_MODULE_DIR: $APPDIR/usr/lib/aarch64-linux-gnu/gio/modules/ diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml index 7d31053552..e4895f4c62 100644 --- a/AppImageBuilder.yml +++ b/AppImageBuilder.yml @@ -18,19 +18,19 @@ AppDir: arch: amd64 allow_unauthenticated: true sources: - - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy main restricted universe multiverse - - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted universe multiverse - - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse - - sourceline: deb http://security.ubuntu.com/ubuntu jammy-security main restricted universe multiverse + - sourceline: deb http://archive.ubuntu.com/ubuntu/ noble main restricted universe multiverse + - sourceline: deb http://archive.ubuntu.com/ubuntu/ noble-updates main restricted universe multiverse + - sourceline: deb http://archive.ubuntu.com/ubuntu/ noble-backports main restricted universe multiverse + - sourceline: deb http://security.ubuntu.com/ubuntu noble-security main restricted universe multiverse include: - - libgtk-3-0 + - libgtk-3-0t64 - libwebkit2gtk-4.1-0 # Bundle GTK input method clients, but keep the input method daemon on the host. - ibus-gtk3 - fcitx5-frontend-gtk3 - libsdl2-2.0-0 - libopus0 - - libmpv1 + - libmpv2 exclude: - humanity-icon-theme - hicolor-icon-theme @@ -46,7 +46,7 @@ AppDir: after_bundle: # Package extraction does not run Debian's GTK cache trigger. AppRun v2 maps # the generated build-time AppDir paths to the runtime AppDir mount point. - - LD_LIBRARY_PATH=AppDir/lib/x86_64-linux-gnu:AppDir/usr/lib/x86_64-linux-gnu AppDir/usr/lib/x86_64-linux-gnu/libgtk-3-0/gtk-query-immodules-3.0 AppDir/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules/*.so > AppDir/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules.cache + - LD_LIBRARY_PATH=AppDir/lib/x86_64-linux-gnu:AppDir/usr/lib/x86_64-linux-gnu AppDir/usr/lib/x86_64-linux-gnu/libgtk-3-0t64/gtk-query-immodules-3.0 AppDir/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules/*.so > AppDir/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules.cache runtime: env: GIO_MODULE_DIR: $APPDIR/usr/lib/x86_64-linux-gnu/gio/modules/ diff --git a/dist/deb/DEBIAN/control b/dist/deb/DEBIAN/control index 2c629562cc..e69094c466 100644 --- a/dist/deb/DEBIAN/control +++ b/dist/deb/DEBIAN/control @@ -5,5 +5,5 @@ Homepage: https://mixin.one/messenger Package: mixin-desktop Maintainer: Mixin Team Architecture: amd64 -Depends: libgtk-3-0 (>= 3.10.0), libsdl2-2.0-0, libwebkit2gtk-4.1-0, libopus0, libmpv2 +Depends: libgtk-3-0t64 (>= 3.24.41), libsdl2-2.0-0, libwebkit2gtk-4.1-0, libopus0, libmpv2 Description: Mixin messenger desktop diff --git a/dist/snap/snap/snapcraft.yaml b/dist/snap/snap/snapcraft.yaml index 0de73e8a16..601ecf98f0 100644 --- a/dist/snap/snap/snapcraft.yaml +++ b/dist/snap/snap/snapcraft.yaml @@ -36,7 +36,7 @@ parts: plugin: dump stage-packages: - libsdl2-2.0-0 - - libgtk-3-0 + - libgtk-3-0t64 - libwebkit2gtk-4.1-0 - libopus0 - libmpv2 From 9636e585a268b34bc699544e749be3bda1eefc51 Mon Sep 17 00:00:00 2001 From: boyan <17426470+boyan01@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:02:31 +0800 Subject: [PATCH 9/9] fix(ci): pin packaging for appimage-builder --- .github/workflows/manual-build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/manual-build.yml b/.github/workflows/manual-build.yml index 9867c939f8..8e9bbcc643 100644 --- a/.github/workflows/manual-build.yml +++ b/.github/workflows/manual-build.yml @@ -270,7 +270,9 @@ jobs: - name: Install appimage-builder run: | python3 -m venv "${{ runner.temp }}/appimage-builder-venv" - "${{ runner.temp }}/appimage-builder-venv/bin/pip" install appimage-builder==1.1.0 + # appimage-builder 1.1.0 relies on LegacyVersion when comparing + # Debian-style versions such as 1.22.6ubuntu6. + "${{ runner.temp }}/appimage-builder-venv/bin/pip" install appimage-builder==1.1.0 "packaging<22" - name: Build AppImage run: |