diff --git a/.github/workflows/linux_deploy.yml b/.github/workflows/linux_deploy.yml index cf16b4d3d..566668d3c 100644 --- a/.github/workflows/linux_deploy.yml +++ b/.github/workflows/linux_deploy.yml @@ -104,7 +104,7 @@ jobs: run: | docker exec debian bash -c ' apt update - apt install -y curl git unzip ninja-build libgtk-3-dev libsecret-1-dev libstdc++-12-dev nasm cmake libmpv-dev clang + apt install -y curl git unzip ninja-build libgtk-3-dev libsecret-1-dev libstdc++-12-dev nasm cmake clang rm -rf /var/lib/apt/lists/* ' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ec51418df..6dd4e6f13 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -45,6 +45,10 @@ PODS: - Flutter - flutter_secure_storage (6.0.0): - Flutter + - fvp (0.34.0): + - Flutter + - FlutterMacOS + - mdk (~> 0.34.0) - image_editor_common (1.0.0): - Flutter - image_gallery_saver (2.0.2): @@ -64,10 +68,7 @@ PODS: - Mantle (2.2.0): - Mantle/extobjc (= 2.2.0) - Mantle/extobjc (2.2.0) - - media_kit_libs_ios_video (1.0.4): - - Flutter - - media_kit_video (0.0.1): - - Flutter + - mdk (0.34.0) - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): @@ -96,10 +97,9 @@ PODS: - SwiftyGif (5.4.5) - url_launcher_ios (0.0.1): - Flutter - - volume_controller (0.0.1): - - Flutter - - wakelock_plus (0.0.1): + - video_player_avfoundation (0.0.1): - Flutter + - FlutterMacOS - webview_flutter_wkwebview (0.0.1): - Flutter - FlutterMacOS @@ -111,10 +111,9 @@ DEPENDENCIES: - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) - flutter_image_gallery_saver (from `.symlinks/plugins/flutter_image_gallery_saver/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - fvp (from `.symlinks/plugins/fvp/darwin`) - image_editor_common (from `.symlinks/plugins/image_editor_common/ios`) - image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`) - - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) - - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) @@ -124,8 +123,7 @@ DEPENDENCIES: - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - - volume_controller (from `.symlinks/plugins/volume_controller/ios`) - - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) + - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) SPEC REPOS: @@ -134,6 +132,7 @@ SPEC REPOS: - DKPhotoGallery - libwebp - Mantle + - mdk - SDWebImage - SDWebImageWebPCoder - SwiftyGif @@ -151,14 +150,12 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_image_gallery_saver/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" + fvp: + :path: ".symlinks/plugins/fvp/darwin" image_editor_common: :path: ".symlinks/plugins/image_editor_common/ios" image_gallery_saver: :path: ".symlinks/plugins/image_gallery_saver/ios" - media_kit_libs_ios_video: - :path: ".symlinks/plugins/media_kit_libs_ios_video/ios" - media_kit_video: - :path: ".symlinks/plugins/media_kit_video/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: @@ -177,43 +174,40 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/sqflite_darwin/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" - volume_controller: - :path: ".symlinks/plugins/volume_controller/ios" - wakelock_plus: - :path: ".symlinks/plugins/wakelock_plus/ios" + video_player_avfoundation: + :path: ".symlinks/plugins/video_player_avfoundation/darwin" webview_flutter_wkwebview: :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: - device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe + device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be + file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1 - flutter_image_gallery_saver: 0453c83412e9691abef94c04c8d180724f5083a8 - flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 - image_editor_common: 3de87e7c4804f4ae24c8f8a998362b98c105cac1 - image_gallery_saver: 14711d79da40581063e8842a11acf1969d781ed7 + flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e + flutter_image_gallery_saver: f356b5f265ba4e36f38b2fe96d0468e0712a6b37 + flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 + fvp: 0ba1ba9d2adfbb9baea21009d94b561330910027 + image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43 + image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d - media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854 - media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d - receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00 + mdk: c54ab482a3457f754b2bf6b4b615c117379f5885 + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1 SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3 SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preference_app_group: 7422922a188e05cf680a656e18e2786dcb5c58b3 - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 - sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f + shared_preference_app_group: 46aee3873e1da581d4904bece9876596d7f66725 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d - volume_controller: ca1cde542ee70fad77d388f82e9616488110942b - wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 - webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2 + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 + webview_flutter_wkwebview: a4af96a051138e28e29f60101d094683b9f82188 PODFILE CHECKSUM: fc9f5883117ffc5dbdfedd53e44b13710e7871f2 diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index c09cf07c8..0cf9560ce 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -1043,6 +1043,8 @@ }, "failedFileSave": "ファイルの保存に失敗したみたいや", + "loopPlayback": "ループ再生", + "misskeyGames": "Misskey Games", "cookieCliker": "Cookie Cliker", "bubbleGame": "Bubble Game", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 58fcae829..58d464b9e 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3318,6 +3318,12 @@ abstract class S { /// **'ファイルの保存に失敗したみたいや'** String get failedFileSave; + /// No description provided for @loopPlayback. + /// + /// In ja, this message translates to: + /// **'ループ再生'** + String get loopPlayback; + /// No description provided for @misskeyGames. /// /// In ja, this message translates to: diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 200124c8d..a5287f579 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1918,6 +1918,9 @@ class SJa extends S { @override String get failedFileSave => 'ファイルの保存に失敗したみたいや'; + @override + String get loopPlayback => 'ループ再生'; + @override String get misskeyGames => 'Misskey Games'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index b8b770b5a..7ed9fabce 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1900,15 +1900,18 @@ class SZh extends S { String get updatedAtDescendingOrder => '更新时间从新到旧'; @override - String get unsupportedFile => '対応してないファイルやわ'; + String get unsupportedFile => '不支持的文件'; @override String unsupportedFileWithFilename(String filename) { - return '$filenameは対応してないファイルやわ'; + return '$filename是不支持的文件'; } @override - String get failedFileSave => 'ファイルの保存に失敗したみたいや'; + String get failedFileSave => '文件保存失败'; + + @override + String get loopPlayback => '循环播放'; @override String get misskeyGames => 'Misskey Games'; diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 21c547c64..5a97643ce 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -942,6 +942,20 @@ "createdAtDescendingOrder": "创建时间从新到旧", "updatedAtAscendingOrder": "更新时间从旧到新", "updatedAtDescendingOrder": "更新时间从新到旧", + + "unsupportedFile": "不支持的文件", + "unsupportedFileWithFilename": "{filename}是不支持的文件", + "@unsupportedFileWithFilename": { + "placeholders": { + "filename": { + "type": "String" + } + } + }, + "failedFileSave": "文件保存失败", + + "loopPlayback": "循环播放", + "misskeyGames": "Misskey Games", "cookieCliker": "Cookie Cliker", "bubbleGame": "Bubble Game", diff --git a/lib/main.dart b/lib/main.dart index fc5f9d727..5a6cbe584 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,7 +7,6 @@ import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_localizations/flutter_localizations.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:media_kit/media_kit.dart"; import "package:miria/const.dart"; import "package:miria/l10n/app_localizations.dart"; import "package:miria/providers.dart"; @@ -20,7 +19,6 @@ import "package:window_manager/window_manager.dart"; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - MediaKit.ensureInitialized(); if (!kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux)) { await windowManager.ensureInitialized(); } diff --git a/lib/state_notifier/common/download_file_notifier.g.dart b/lib/state_notifier/common/download_file_notifier.g.dart index f50167d82..a8331126e 100644 --- a/lib/state_notifier/common/download_file_notifier.g.dart +++ b/lib/state_notifier/common/download_file_notifier.g.dart @@ -39,7 +39,7 @@ final class DownloadFileNotifierProvider } String _$downloadFileNotifierHash() => - r'570a3519348813c4ebee6976af9634c1350258f3'; + r'e8165329f9e2c6712e895cb1579d7346cbc7ccaf'; abstract class _$DownloadFileNotifier extends $Notifier { void build(); diff --git a/lib/view/common/note_file_dialog/media_player.dart b/lib/view/common/note_file_dialog/media_player.dart index 0580bb0bd..2a5de8b46 100644 --- a/lib/view/common/note_file_dialog/media_player.dart +++ b/lib/view/common/note_file_dialog/media_player.dart @@ -1,20 +1,25 @@ import "dart:async"; import "dart:io"; -import "dart:math"; +import "dart:math" as math; import "package:flutter/material.dart"; -import "package:media_kit/media_kit.dart"; -import "package:media_kit_video/media_kit_video.dart"; -import "package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart"; +import "package:flutter/services.dart"; +import "package:fvp/fvp.dart" as fvp; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:miria/const.dart"; import "package:miria/l10n/app_localizations.dart"; +import "package:miria/view/common/dialog/dialog_state.dart"; import "package:url_launcher/url_launcher_string.dart"; -import "package:volume_controller/volume_controller.dart"; +import "package:video_player/video_player.dart"; +import "package:video_player_android/video_player_android.dart"; +import "package:video_player_avfoundation/video_player_avfoundation.dart"; +import "package:video_player_platform_interface/video_player_platform_interface.dart"; +import "package:window_manager/window_manager.dart"; -class MediaPlayer extends StatefulWidget { +class MediaPlayer extends ConsumerStatefulWidget { final String url; final String fileType; final String? thumbnailUrl; - const MediaPlayer({ required this.url, required this.fileType, @@ -26,28 +31,23 @@ class MediaPlayer extends StatefulWidget { MediaPlayerState createState() => MediaPlayerState(); } -class MediaPlayerState extends State { - late final videoKey = GlobalKey(); - late final player = Player(); - late final controller = VideoController(player); - late final bool isAudioFile; - final List subscriptions = []; - - double aspectRatio = 1; - - bool isVisibleControlBar = false; - bool isEnabledButton = false; - bool isFullscreen = false; - Timer? timer; +class MediaPlayerState extends ConsumerState + with _MediaPlayerMixin { + @override + VideoPlayerController get videoController => controller; - Duration position = const Duration(); - Duration bufferPosition = const Duration(); - Duration duration = const Duration(); - final double iconSize = 30.0; - bool isSeeking = false; + late final VideoPlayerController controller; + late final VoidCallback _listener; + bool isErrorDialogShown = false; + bool isFullScreen = false; - bool get isDesktop => - Platform.isWindows || Platform.isMacOS || Platform.isLinux; + MediaPlayerState() { + _listener = () { + if (mounted) { + setState(() {}); + } + }; + } @override void initState() { @@ -58,52 +58,62 @@ class MediaPlayerState extends State { isEnabledButton = true; } - player.open(Media(widget.url)); - controller.rect.addListener(() { - final rect = controller.rect.value; - if (rect == null || rect.width == 0 || rect.height == 0) { - return; + // movファイル等でバッファリング問題が起きやすいため + // iOS/Androidではオーディオファイル以外にAVPlayer/ExoPlayerを使用する + if (!isDesktop && !isAudioFile) { + if (Platform.isAndroid && + VideoPlayerPlatform.instance is! AndroidVideoPlayer) { + VideoPlayerPlatform.instance = AndroidVideoPlayer(); + } else if (Platform.isIOS && + VideoPlayerPlatform.instance is! AVFoundationVideoPlayer) { + VideoPlayerPlatform.instance = AVFoundationVideoPlayer(); } - setState(() { - aspectRatio = rect.width / rect.height; - }); - }); + } else { + fvp.registerWith( + options: { + //"global": {"ffmpeg.log": "debug"}, + "player": { + "demux.buffer.ranges": "8", // ループ再生時のバッファリング緩和 + //"avformat.probesize": "10M", // 解析上限容量 + //"avformat.analyzeduration": "5M", // 解析上限時間(10M = 10s) + }, + }, + ); + } - subscriptions.addAll([ - controller.player.stream.position.listen((event) { - setState(() { - if (!isSeeking) { - position = event; - } - }); - }), - controller.player.stream.buffer.listen((event) { - setState(() { - bufferPosition = event; - }); - }), - controller.player.stream.duration.listen((event) { - setState(() { - duration = event; + controller = VideoPlayerController.networkUrl(Uri.parse(widget.url)); + controller + .initialize() + .then((_) { + if (!mounted) return; + setState(() { + controller.play(); + }); + }) + .catchError((error) async { + if (!mounted || isErrorDialogShown) return; + isErrorDialogShown = true; + await ref + .read(dialogStateNotifierProvider.notifier) + .showSimpleDialog( + message: (context) => S.of(context).thrownError, + ); + if (!mounted) return; + Navigator.of(context).pop(); }); - }), - ]); + controller.addListener(_listener); } @override void dispose() { - Future.microtask(() async { - for (final subscription in subscriptions) { - await subscription.cancel(); - } - await player.dispose(); - }); - VolumeController().removeListener(); + controller + ..removeListener(_listener) + ..dispose(); super.dispose(); } - Future _showMenu() { - return showModalBottomSheet( + Future showMenu() async { + await showModalBottomSheet( context: context, builder: (innerContext) { return ListView( @@ -114,9 +124,11 @@ class MediaPlayerState extends State { onTap: () async { Navigator.of(innerContext).pop(); Navigator.of(context).pop(); - await launchUrlString( - widget.url, - mode: LaunchMode.externalApplication, + unawaited( + launchUrlString( + widget.url, + mode: LaunchMode.externalApplication, + ), ); }, ), @@ -126,9 +138,27 @@ class MediaPlayerState extends State { title: Text(S.of(context).changeFullScreen), onTap: () async { Navigator.of(innerContext).pop(); - await videoKey.currentState?.enterFullscreen(); + isFullScreen = true; + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + _FullScreenMediaPlayer(controller: controller), + ), + ); + isFullScreen = false; }, ), + ListTile( + leading: const Icon(Icons.repeat), + title: Text(S.of(context).loopPlayback), + trailing: controller.value.isLooping + ? const Icon(Icons.check) + : null, + onTap: () async { + Navigator.of(innerContext).pop(); + await controller.setLooping(!controller.value.isLooping); + }, + ), ], ); }, @@ -137,58 +167,6 @@ class MediaPlayerState extends State { @override Widget build(BuildContext context) { - final themeData = MaterialVideoControlsThemeData( - seekBarPositionColor: Theme.of(context).primaryColor, - seekBarThumbColor: Theme.of(context).primaryColor, - backdropColor: Colors.transparent, - volumeGesture: false, - brightnessGesture: false, - displaySeekBar: false, - seekOnDoubleTap: false, - automaticallyImplySkipNextButton: false, - automaticallyImplySkipPreviousButton: false, - primaryButtonBar: [], - bottomButtonBar: [], - ); - - final themeDataFull = MaterialVideoControlsThemeData( - seekBarPositionColor: Theme.of(context).primaryColor, - seekBarThumbColor: Theme.of(context).primaryColor, - volumeGesture: false, - brightnessGesture: false, - displaySeekBar: true, - seekOnDoubleTap: true, - automaticallyImplySkipNextButton: false, - automaticallyImplySkipPreviousButton: false, - bottomButtonBarMargin: const EdgeInsets.only( - left: 16.0, - right: 8.0, - bottom: 16.0, - ), - seekBarMargin: const EdgeInsets.only(bottom: 16.0), - ); - - final themeDataDesktop = MaterialDesktopVideoControlsThemeData( - seekBarPositionColor: Theme.of(context).primaryColor, - seekBarThumbColor: Theme.of(context).primaryColor, - modifyVolumeOnScroll: false, - displaySeekBar: false, - automaticallyImplySkipNextButton: false, - automaticallyImplySkipPreviousButton: false, - primaryButtonBar: [], - bottomButtonBar: [], - playAndPauseOnTap: false, - ); - - final themeDataDesktopFull = MaterialDesktopVideoControlsThemeData( - seekBarPositionColor: Theme.of(context).primaryColor, - seekBarThumbColor: Theme.of(context).primaryColor, - modifyVolumeOnScroll: false, - automaticallyImplySkipNextButton: false, - automaticallyImplySkipPreviousButton: false, - playAndPauseOnTap: false, - ); - return Stack( children: [ Listener( @@ -204,54 +182,35 @@ class MediaPlayerState extends State { onPointerUp: (event) { startHideTimer(); }, - child: SizedBox( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - child: Center( + child: Center( + child: SizedBox.expand( child: Stack( children: [ Align( child: AspectRatio( - aspectRatio: aspectRatio, - child: MaterialVideoControlsTheme( - normal: themeData, - fullscreen: themeDataFull, - child: MaterialDesktopVideoControlsTheme( - normal: themeDataDesktop, - fullscreen: themeDataDesktopFull, - child: Video( - key: videoKey, - controller: controller, - controls: AdaptiveVideoControls, - fill: Colors.transparent, - onEnterFullscreen: () async { - isFullscreen = true; - await defaultEnterNativeFullscreen(); - videoKey.currentState?.update(fill: Colors.black); - }, - onExitFullscreen: () async { - await defaultExitNativeFullscreen(); - isFullscreen = false; - videoKey.currentState?.update( - fill: Colors.transparent, - ); - }, - ), - ), - ), + aspectRatio: controller.value.aspectRatio, + child: (!isFullScreen) + ? _VideoPlayer( + controller: controller, + isFullscreen: false, + ) + : Container(), ), ), - if (!isDesktop) - SizedBox( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - child: Container(color: Colors.transparent), + if (!controller.value.isInitialized || + controller.value.isBuffering) + const Center( + child: SizedBox.square( + dimension: 32, + child: CircularProgressIndicator(), + ), ), ], ), ), ), ), + AnimatedOpacity( curve: Curves.easeInOut, opacity: isVisibleControlBar ? 1.0 : 0.0, @@ -267,153 +226,673 @@ class MediaPlayerState extends State { maintainState: true, maintainAnimation: true, visible: isEnabledButton, - child: Stack( - children: [ - Positioned( - bottom: 0, - child: Container( - padding: const EdgeInsets.only(left: 10, right: 10, top: 5), - width: MediaQuery.of(context).size.width, - height: 100, - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - border: Border( - top: BorderSide(color: Theme.of(context).primaryColor), + child: SafeArea(child: _buildControlBar()), + ), + ), + ], + ); + } + + Widget _buildControlBar() { + if (!isSeeking) { + position = controller.value.position; + } + + final duration = controller.value.duration; + var maxBuffering = 0; + for (final range in controller.value.buffered) { + final end = range.end.inMilliseconds; + if (end > maxBuffering) { + maxBuffering = end; + } + } + + return IconTheme( + data: IconThemeData(size: 30.0, color: IconTheme.of(context).color), + child: Stack( + fit: StackFit.expand, + children: [ + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + padding: const EdgeInsets.only(left: 10, right: 10, top: 5), + height: 100, + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + border: Border( + top: BorderSide(color: Theme.of(context).primaryColor), + ), + ), + child: Listener( + behavior: HitTestBehavior.opaque, + onPointerDown: (event) { + cancelHideTimer(); + }, + onPointerUp: (event) { + startHideTimer(); + }, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + left: 0, + right: 0, + bottom: 10, ), - ), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only( - left: 0, - right: 0, - bottom: 10, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Row( - children: [ - Align( - alignment: Alignment.centerLeft, - child: IconButton( - iconSize: iconSize, - onPressed: () async { - cancelHideTimer(); - await controller.player.playOrPause(); - startHideTimer(); - }, - icon: StreamBuilder( - stream: - controller.player.stream.playing, - builder: (context, playing) => Icon( - playing.data == true - ? Icons.pause - : Icons.play_arrow, - ), - ), - ), - ), - Text( - "${position.label(reference: duration)} / ${duration.label(reference: duration)}", - textAlign: TextAlign.center, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Row( + children: [ + Align( + alignment: Alignment.centerLeft, + child: IconButton( + onPressed: () async { + if (controller.value.isPlaying) { + await controller.pause(); + } else { + await controller.play(); + } + }, + icon: Icon( + controller.value.isPlaying + ? Icons.pause + : Icons.play_arrow, ), - ], - ), - ), - IconButton( - iconSize: iconSize, - onPressed: () async { - cancelHideTimer(); - final isMute = - controller.player.state.volume == 0; - await controller.player.setVolume( - isMute ? 100 : 0, - ); - startHideTimer(); - }, - icon: StreamBuilder( - stream: controller.player.stream.volume, - builder: (context, playing) => Icon( - playing.data == 0 - ? Icons.volume_off - : Icons.volume_up, ), ), + Text( + formatDuration(position, reference: duration), + textAlign: TextAlign.center, + ), + const Text(" / "), + Text( + formatDuration(duration, reference: position), + textAlign: TextAlign.center, + ), + ], + ), + ), + const Padding(padding: EdgeInsets.only(right: 5)), + IconButton( + onPressed: () => showPlaybackSpeedMenu(context), + onLongPress: () => resetPlaybackSpeed(), + icon: Icon(Icons.speed), + ), + IconButton( + onPressed: () async { + await controller.setVolume( + controller.value.volume == 0 ? 1.0 : 0, + ); + }, + icon: Icon( + controller.value.volume == 0 + ? Icons.volume_off + : Icons.volume_up, + ), + ), + IconButton( + onPressed: () async { + await showMenu(); + }, + icon: const Icon(Icons.more_horiz), + ), + ], + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: SliderTheme( + data: SliderThemeData( + overlayShape: SliderComponentShape.noOverlay, + trackHeight: 5.0, + thumbShape: const RoundSliderThumbShape( + enabledThumbRadius: 10.0, ), - IconButton( - onPressed: () async { - cancelHideTimer(); - await _showMenu(); - startHideTimer(); - }, - icon: const Icon(Icons.more_horiz), - iconSize: iconSize, - ), - ], + ), + child: Slider( + thumbColor: Theme.of(context).primaryColor, + activeColor: Theme.of(context).primaryColor, + value: position.abs().inMilliseconds.toDouble(), + secondaryTrackValue: maxBuffering + .toDouble() + .clamp( + 0.0, + duration.abs().inMilliseconds.toDouble(), + ), + min: 0, + max: duration.abs().inMilliseconds.toDouble(), + onChangeStart: (value) { + cancelHideTimer(); + isSeeking = true; + }, + onChanged: (value) { + setState(() { + position = Duration( + milliseconds: value.toInt(), + ); + }); + }, + onChangeEnd: (value) async { + await controller.seekTo(position); + isSeeking = false; + startHideTimer(); + }, + ), ), ), - Row( + ], + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} + +class _VideoPlayer extends StatefulWidget { + final VideoPlayerController controller; + final bool isFullscreen; + const _VideoPlayer({required this.controller, this.isFullscreen = false}); + @override + _VideoPlayerState createState() => _VideoPlayerState(); +} + +class _VideoPlayerState extends State<_VideoPlayer> { + final FocusNode focusNode = FocusNode(); + final seekSeconds = 10; + final Set pressedKeys = {}; + + KeyEventResult handleKey(FocusNode node, KeyEvent event) { + if (event is KeyUpEvent) { + if (event.logicalKey == LogicalKeyboardKey.goBack) { + return KeyEventResult.ignored; + } + pressedKeys.remove(event.logicalKey); + return KeyEventResult.handled; + } + if (event is! KeyDownEvent) { + return KeyEventResult.ignored; + } + if (pressedKeys.contains(event.logicalKey)) { + return KeyEventResult.handled; + } + pressedKeys.add(event.logicalKey); + + if (event.logicalKey == LogicalKeyboardKey.escape) { + if (widget.isFullscreen) { + Navigator.of(context).pop(); + return KeyEventResult.handled; + } + } + + if (event.logicalKey == LogicalKeyboardKey.space) { + if (widget.controller.value.isPlaying) { + unawaited(widget.controller.pause()); + } else { + unawaited(widget.controller.play()); + } + return KeyEventResult.handled; + } + + if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + unawaited( + widget.controller.setVolume( + math.max(0.0, widget.controller.value.volume - 0.1), + ), + ); + return KeyEventResult.handled; + } + if (event.logicalKey == LogicalKeyboardKey.arrowUp) { + unawaited( + widget.controller.setVolume( + math.min(1.0, widget.controller.value.volume + 0.1), + ), + ); + return KeyEventResult.handled; + } + if (event.logicalKey == LogicalKeyboardKey.arrowRight) { + final v = + widget.controller.value.position + Duration(seconds: seekSeconds); + unawaited( + widget.controller.seekTo( + (v > widget.controller.value.duration) + ? widget.controller.value.duration + : v, + ), + ); + if (!widget.controller.value.isPlaying) { + unawaited(widget.controller.play()); + } + return KeyEventResult.handled; + } + + if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { + final v = + widget.controller.value.position - Duration(seconds: seekSeconds); + unawaited( + widget.controller.seekTo((v < Duration.zero) ? Duration.zero : v), + ); + + if (!widget.controller.value.isPlaying) { + unawaited(widget.controller.play()); + } + return KeyEventResult.handled; + } + + if (event.logicalKey == LogicalKeyboardKey.keyF) { + if (widget.isFullscreen) { + Navigator.of(context).pop(); + } else { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + _FullScreenMediaPlayer(controller: widget.controller), + ), + ); + } + return KeyEventResult.handled; + } + + return KeyEventResult.ignored; + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + focusNode.requestFocus(); + }); + } + + @override + Widget build(BuildContext context) { + return Focus( + focusNode: focusNode, + autofocus: true, + onKeyEvent: handleKey, + child: VideoPlayer(widget.controller), + ); + } +} + +class _FullScreenMediaPlayer extends StatefulWidget { + final VideoPlayerController controller; + const _FullScreenMediaPlayer({required this.controller}); + + @override + _FullScreenMediaPlayerState createState() => _FullScreenMediaPlayerState(); +} + +class _FullScreenMediaPlayerState extends State<_FullScreenMediaPlayer> + with _MediaPlayerMixin { + @override + VideoPlayerController get videoController => widget.controller; + late final VoidCallback _listener; + + double volume = 1.0; + + _FullScreenMediaPlayerState() { + _listener = () { + setState(() {}); + }; + } + + @override + void initState() { + super.initState(); + widget.controller.addListener(_listener); + volume = widget.controller.value.volume; + unawaited(enterFullScreen()); + } + + @override + void dispose() { + widget.controller.removeListener(_listener); + unawaited(exitFullScreen()); + super.dispose(); + } + + Future enterFullScreen() async { + if (isDesktop) { + await windowManager.setFullScreen(true); + return; + } + await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); + await SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + ]); + } + + Future exitFullScreen() async { + if (isDesktop) { + await windowManager.setFullScreen(false); + return; + } + await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + await SystemChrome.setPreferredOrientations([]); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + body: SafeArea( + child: Stack( + children: [ + Listener( + behavior: HitTestBehavior.translucent, + onPointerDown: (event) { + cancelHideTimer(); + setState(() { + isEnabledButton = true; + isVisibleControlBar = !isVisibleControlBar; + }); + }, + onPointerUp: (event) { + startHideTimer(); + }, + child: Center( + child: SizedBox.expand( + child: Stack( + children: [ + Align( + child: AspectRatio( + aspectRatio: widget.controller.value.aspectRatio, + child: _VideoPlayer( + controller: widget.controller, + isFullscreen: true, + ), + ), + ), + if (!widget.controller.value.isInitialized || + widget.controller.value.isBuffering) + const Center( + child: SizedBox.square( + dimension: 32, + child: CircularProgressIndicator(), + ), + ), + ], + ), + ), + ), + ), + AnimatedOpacity( + curve: Curves.easeInOut, + opacity: isVisibleControlBar ? 1.0 : 0.0, + duration: const Duration(milliseconds: 500), + onEnd: () { + if (mounted && !isVisibleControlBar) { + setState(() { + isEnabledButton = false; + }); + } + }, + child: Visibility( + maintainState: true, + maintainAnimation: true, + visible: isEnabledButton, + child: SafeArea(child: _buildControlBar()), + ), + ), + ], + ), + ), + ); + } + + Widget _buildControlBar() { + if (!isSeeking) { + position = widget.controller.value.position; + } + + final duration = widget.controller.value.duration; + var maxBuffering = 0; + for (final range in widget.controller.value.buffered) { + final end = range.end.inMilliseconds; + if (end > maxBuffering) { + maxBuffering = end; + } + } + return IconTheme( + data: IconThemeData(size: 30.0, color: Colors.white), + child: DefaultTextStyle( + style: TextStyle(color: Colors.white), + child: Stack( + fit: StackFit.expand, + children: [ + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + padding: const EdgeInsets.only(left: 10, right: 10, top: 5), + height: 90, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.transparent, Colors.black87], + ), + ), + child: Listener( + behavior: HitTestBehavior.opaque, + onPointerDown: (event) { + cancelHideTimer(); + }, + onPointerUp: (event) { + startHideTimer(); + }, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + left: 0, + right: 0, + bottom: 5, + ), + child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( - child: SliderTheme( - data: SliderThemeData( - overlayShape: SliderComponentShape.noOverlay, - trackHeight: 5.0, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 10.0, + child: Row( + children: [ + Align( + alignment: Alignment.centerLeft, + child: IconButton( + onPressed: () async { + if (widget.controller.value.isPlaying) { + await widget.controller.pause(); + } else { + await widget.controller.play(); + } + }, + icon: Icon( + widget.controller.value.isPlaying + ? Icons.pause + : Icons.play_arrow, + ), + ), ), - ), - child: Slider( - thumbColor: Theme.of(context).primaryColor, - activeColor: Theme.of(context).primaryColor, - value: min( - position.inMilliseconds, - duration.inMilliseconds, - ).toDouble(), - secondaryTrackValue: bufferPosition - .inMilliseconds - .toDouble(), - min: 0, - max: duration.inMilliseconds.toDouble(), - onChangeStart: (value) { - cancelHideTimer(); - isSeeking = true; - }, - onChanged: (value) { - setState(() { - position = Duration( - milliseconds: value.toInt(), + Text( + formatDuration( + position, + reference: duration, + ), + textAlign: TextAlign.center, + ), + const Text(" / "), + Text( + formatDuration( + duration, + reference: position, + ), + textAlign: TextAlign.center, + ), + IconButton( + onPressed: () async { + await widget.controller.setVolume( + widget.controller.value.volume == 0 + ? volume + : 0, ); - }); - }, - onChangeEnd: (value) async { - await controller.player.seek(position); - isSeeking = false; - startHideTimer(); - }, - ), + }, + icon: Icon( + widget.controller.value.volume == 0 + ? Icons.volume_off + : Icons.volume_up, + ), + ), + SliderTheme( + data: SliderThemeData( + overlayShape: + SliderComponentShape.noOverlay, + trackHeight: 3.0, + thumbShape: const RoundSliderThumbShape( + enabledThumbRadius: 6.0, + ), + ), + child: Slider( + thumbColor: Colors.white, + activeColor: Colors.white, + inactiveColor: Colors.grey, + value: widget.controller.value.volume, + min: 0, + max: 1.0, + onChangeStart: (value) { + cancelHideTimer(); + }, + onChanged: (value) async { + await widget.controller.setVolume( + value, + ); + volume = value; + }, + onChangeEnd: (value) async { + await widget.controller.setVolume( + value, + ); + volume = value; + startHideTimer(); + }, + ), + ), + ], + ), + ), + const Padding(padding: EdgeInsets.only(right: 5)), + IconButton( + onPressed: () => widget.controller.setLooping( + !widget.controller.value.isLooping, + ), + icon: Icon( + widget.controller.value.isLooping + ? Icons.repeat_on + : Icons.repeat, ), ), + IconButton( + onPressed: () => showPlaybackSpeedMenu(context), + onLongPress: () => resetPlaybackSpeed(), + icon: Icon(Icons.speed), + ), + + IconButton( + onPressed: () { + Navigator.of( + context, + ).removeRoute(ModalRoute.of(context)!); + }, + icon: const Icon(Icons.fullscreen_exit), + ), ], ), - ], - ), + ), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: SliderTheme( + data: SliderThemeData( + overlayShape: SliderComponentShape.noOverlay, + trackHeight: 3.0, + thumbShape: const RoundSliderThumbShape( + enabledThumbRadius: 6.0, + ), + ), + child: Slider( + thumbColor: Theme.of(context).primaryColor, + activeColor: Theme.of(context).primaryColor, + value: position.abs().inMilliseconds.toDouble(), + secondaryTrackValue: maxBuffering + .toDouble() + .clamp( + 0.0, + duration.abs().inMilliseconds.toDouble(), + ), + min: 0, + max: duration.abs().inMilliseconds.toDouble(), + onChangeStart: (value) { + cancelHideTimer(); + isSeeking = true; + }, + onChanged: (value) { + setState(() { + position = Duration( + milliseconds: value.toInt(), + ); + }); + }, + onChangeEnd: (value) async { + await widget.controller.seekTo(position); + isSeeking = false; + startHideTimer(); + }, + ), + ), + ), + ], + ), + ], ), ), - ], + ), ), - ), + ], ), - ], + ), ); } +} + +mixin _MediaPlayerMixin on State { + VideoPlayerController get videoController; + Timer? timer; + bool isVisibleControlBar = false; + bool isAudioFile = false; + bool isSeeking = false; + bool isEnabledButton = false; + + Duration position = const Duration(seconds: 0); void startHideTimer() { if (isAudioFile) return; @@ -427,7 +906,64 @@ class MediaPlayerState extends State { } void cancelHideTimer() { - if (isAudioFile) return; timer?.cancel(); } + + Future showPlaybackSpeedMenu(BuildContext context) async { + final speeds = [ + 0.25, + 0.50, + 0.75, + 1.00, + 1.25, + 1.50, + 2.00, + 2.25, + 2.50, + 2.75, + 3.0, + ]; + await showModalBottomSheet( + context: context, + builder: (context) { + return ListView( + children: [ + ...speeds.map((speed) { + return ListTile( + title: Text("${speed.toStringAsFixed(2)}x"), + leading: videoController.value.playbackSpeed == speed + ? const Icon(Icons.check) + : SizedBox(width: Theme.of(context).iconTheme.size), + onTap: () async { + await videoController.setPlaybackSpeed(speed); + if (!context.mounted) return; + Navigator.of(context).pop(); + }, + ); + }), + ], + ); + }, + ); + } + + Future resetPlaybackSpeed() async { + await videoController.setPlaybackSpeed(1.0); + } + + String formatDuration(Duration duration, {required Duration reference}) { + // ignore: parameter_assignments + duration = duration.abs(); + // ignore: parameter_assignments + reference = reference.abs(); + + String twoDigits(int n) => n.toString().padLeft(2, "0"); + final hours = twoDigits(duration.inHours); + final minutes = twoDigits(duration.inMinutes.remainder(60)); + final seconds = twoDigits(duration.inSeconds.remainder(60)); + + return (duration.inHours > 0 || reference.inHours > 0) + ? "$hours:$minutes:$seconds" + : "$minutes:$seconds"; + } } diff --git a/lib/view/common/note_file_dialog/media_viewer.dart b/lib/view/common/note_file_dialog/media_viewer.dart index 17101556f..49d7fb4fb 100644 --- a/lib/view/common/note_file_dialog/media_viewer.dart +++ b/lib/view/common/note_file_dialog/media_viewer.dart @@ -34,6 +34,7 @@ class MediaViewer extends HookConsumerWidget { url: file.thumbnailUrl.toString(), type: ImageType.imageThumbnail, fit: BoxFit.contain, + errorBuilder: null, ), Icon( Icons.play_circle, @@ -43,9 +44,7 @@ class MediaViewer extends HookConsumerWidget { ], ), ); - return SizedBox( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, + return SizedBox.expand( child: (enabledAutoPlay.value || (!enabledAutoPlay.value && !isThumbnailVisible.value)) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 276b93631..37c6184fd 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,8 +7,7 @@ #include "generated_plugin_registrant.h" #include -#include -#include +#include #include #include #include @@ -17,12 +16,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_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) fvp_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FvpPlugin"); + fvp_plugin_register_with_registrar(fvp_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 876a498ea..9125612b6 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,8 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_linux - media_kit_libs_linux - media_kit_video + fvp screen_retriever_linux url_launcher_linux window_manager diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index b495c9629..da5e660d6 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,9 +9,8 @@ import device_info_plus import file_picker import flutter_image_compress_macos import flutter_secure_storage_macos +import fvp import image_editor_common -import media_kit_libs_macos_video -import media_kit_video import package_info_plus import path_provider_foundation import screen_retriever_macos @@ -19,7 +18,7 @@ import share_plus import shared_preferences_foundation import sqflite_darwin import url_launcher_macos -import wakelock_plus +import video_player_avfoundation import webview_flutter_wkwebview import window_manager @@ -28,9 +27,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + FvpPlugin.register(with: registry.registrar(forPlugin: "FvpPlugin")) ImageEditorPlugin.register(with: registry.registrar(forPlugin: "ImageEditorPlugin")) - MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) - MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) @@ -38,7 +36,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index f78c85ac6..b3ec3629f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -734,6 +734,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + fvp: + dependency: "direct main" + description: + name: fvp + sha256: a2850b856e177cb48f3e49940613f2180f3375bcceab2f75e3d7b915009a2840 + url: "https://pub.dev" + source: hosted + version: "0.34.0" glob: dependency: transitive description: @@ -1018,72 +1026,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" - media_kit: - dependency: "direct main" - description: - path: media_kit - ref: "28292926cfd1e75611f7dcf39248124197d131cf" - resolved-ref: "28292926cfd1e75611f7dcf39248124197d131cf" - url: "https://github.com/4ster1sk/media-kit.git" - source: git - version: "1.1.11" - media_kit_libs_android_video: - dependency: transitive - description: - name: media_kit_libs_android_video - sha256: adff9b571b8ead0867f9f91070f8df39562078c0eb3371d88b9029a2d547d7b7 - url: "https://pub.dev" - source: hosted - version: "1.3.7" - 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: "958cc55e7065d9d01f52a2842dab2a0812a92add18489f1006d864fb5e42a3ef" - url: "https://pub.dev" - source: hosted - version: "1.0.6" - 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: - path: media_kit_video - ref: "28292926cfd1e75611f7dcf39248124197d131cf" - resolved-ref: "28292926cfd1e75611f7dcf39248124197d131cf" - url: "https://github.com/4ster1sk/media-kit.git" - source: git - version: "1.2.5" meta: dependency: transitive description: @@ -1438,14 +1380,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" - safe_local_storage: - dependency: transitive - description: - name: safe_local_storage - sha256: e9a21b6fec7a8aa62cc2585ff4c1b127df42f3185adbd2aca66b47abe2e80236 - url: "https://pub.dev" - source: hosted - version: "2.0.1" screen_retriever: dependency: transitive description: @@ -1811,22 +1745,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" - 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: ff4d2c720aca3f4f7d5445e23b11b2d15ef8af5ddce5164643f38ff962dcb270 - url: "https://pub.dev" - source: hosted - version: "3.0.0" url_launcher: dependency: "direct main" description: @@ -1939,46 +1857,62 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" - visibility_detector: + video_player: dependency: "direct main" description: - name: visibility_detector - sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 + name: video_player + sha256: "0d55b1f1a31e5ad4c4967bfaa8ade0240b07d20ee4af1dfef5f531056512961a" url: "https://pub.dev" source: hosted - version: "0.4.0+2" - vm_service: - dependency: transitive + version: "2.10.0" + video_player_android: + dependency: "direct main" description: - name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + name: video_player_android + sha256: "59e5a457ddcc1688f39e9aef0efb62aa845cf0cbbac47e44ac9730dc079a2385" url: "https://pub.dev" source: hosted - version: "15.0.0" - volume_controller: + version: "2.8.13" + video_player_avfoundation: + dependency: "direct main" + description: + name: video_player_avfoundation + sha256: f9a780aac57802b2892f93787e5ea53b5f43cc57dc107bee9436458365be71cd + url: "https://pub.dev" + source: hosted + version: "2.8.4" + video_player_platform_interface: dependency: "direct main" description: - name: volume_controller - sha256: c71d4c62631305df63b72da79089e078af2659649301807fa746088f365cb48e + name: video_player_platform_interface + sha256: cf2a1d29a284db648fd66cbd18aacc157f9862d77d2cc790f6f9678a46c1db5a url: "https://pub.dev" source: hosted - version: "2.0.8" - wakelock_plus: + version: "6.4.0" + video_player_web: dependency: transitive description: - name: wakelock_plus - sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678 + name: video_player_web + sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" url: "https://pub.dev" source: hosted - version: "1.3.2" - wakelock_plus_platform_interface: + version: "2.4.0" + visibility_detector: + dependency: "direct main" + description: + name: visibility_detector + sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 + url: "https://pub.dev" + source: hosted + version: "0.4.0+2" + vm_service: dependency: transitive description: - name: wakelock_plus_platform_interface - sha256: e10444072e50dbc4999d7316fd303f7ea53d31c824aa5eb05d7ccbdd98985207 + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "15.0.0" watcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b776a6ea4..82e06ff30 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,11 +66,7 @@ dependencies: webview_flutter: ^4.3.0 webview_flutter_android: ^4.7.0 webview_flutter_wkwebview: ^3.9.0 - media_kit: ^1.1.11 - media_kit_video: ^1.2.5 - media_kit_libs_video: ^1.0.5 flutter_colorpicker: ^1.1.0 - volume_controller: ^2.0.7 window_manager: ^0.5.0 shared_preference_app_group: ^1.0.0+1 stack_trace: ^1.11.1 @@ -83,6 +79,11 @@ dependencies: image: ^4.3.0 flutter_twemoji: ^1.0.1 mime: ^2.0.0 + fvp: ^0.34.0 + video_player: ^2.10.0 + video_player_avfoundation: ^2.8.4 + video_player_android: ^2.8.13 + video_player_platform_interface: ^6.4.0 dependency_overrides: image_editor: @@ -101,16 +102,6 @@ dependency_overrides: git: url: https://github.com/knottx/image_gallery_saver.git ref: knottx-latest - media_kit: - git: - url: https://github.com/4ster1sk/media-kit.git - ref: 28292926cfd1e75611f7dcf39248124197d131cf - path: ./media_kit/ - media_kit_video: - git: - url: https://github.com/4ster1sk/media-kit.git - ref: 28292926cfd1e75611f7dcf39248124197d131cf - path: ./media_kit_video/ flutter_cache_manager: git: url: https://github.com/Baseflow/flutter_cache_manager diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 4dba7a60a..b3c978f2f 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -54,7 +54,6 @@ parts: build-environment: - PATH: "$CRAFT_PART_BUILD/FlutterSDK/bin:$PATH" build-packages: - - libmpv-dev - libsecret-1-dev # flutterプラグインで使用されるパッケージ - clang @@ -65,7 +64,6 @@ parts: - unzip - jq stage-packages: - - libmpv2 - libsecret-1-0 override-pull: | craftctl default @@ -116,8 +114,3 @@ layout: # Fix resource relocation problem of zenity part /usr/share/zenity: symlink: $SNAP/usr/share/zenity - -lint: - ignore: - - library: - - lib/libmedia_kit_native_event_loop.so diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 5b11de801..0d2673c15 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,7 @@ #include "generated_plugin_registrant.h" #include -#include -#include +#include #include #include #include @@ -18,10 +17,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); - MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); - MediaKitVideoPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); + FvpPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FvpPluginCApi")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 05341afb1..58c57e59b 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,8 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows - media_kit_libs_windows_video - media_kit_video + fvp permission_handler_windows screen_retriever_windows share_plus