From 9c65b470172628bd501d573b5eb3a74cbf6afe0b Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Mon, 27 Jan 2025 20:00:55 +0800 Subject: [PATCH] import fvp --- lib/hooks/use_fvp.dart | 84 +++++++++++++++++++ lib/hooks/use_player_core.dart | 17 ++-- lib/main.dart | 2 + lib/pages/player/control_bar.dart | 2 +- lib/pages/player/fvp_video.dart | 41 +++++++++ lib/pages/player/iris_player.dart | 75 ++++++++++++----- lib/utils/check_data_source_type.dart | 20 +++++ lib/widgets/custom_app_bar.dart | 6 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.lock | 64 ++++++++++++++ pubspec.yaml | 2 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 15 files changed, 295 insertions(+), 31 deletions(-) create mode 100644 lib/hooks/use_fvp.dart create mode 100644 lib/pages/player/fvp_video.dart create mode 100644 lib/utils/check_data_source_type.dart diff --git a/lib/hooks/use_fvp.dart b/lib/hooks/use_fvp.dart new file mode 100644 index 0000000..f9bddf3 --- /dev/null +++ b/lib/hooks/use_fvp.dart @@ -0,0 +1,84 @@ +import 'dart:io'; + +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:video_player/video_player.dart'; + +enum DataSourceType { + network, + file, + contentUri, +} + +VideoPlayerController useFvp({ + required String dataSource, + required DataSourceType dataSourceType, + bool autoPlay = false, + bool looping = false, + Future? closedCaptionFile, + VideoPlayerOptions? videoPlayerOptions, + Map httpHeaders = const {}, +}) { + final controller = useMemoized( + () { + switch (dataSourceType) { + case DataSourceType.network: + return VideoPlayerController.networkUrl( + Uri.parse(dataSource), + closedCaptionFile: closedCaptionFile, + videoPlayerOptions: videoPlayerOptions, + httpHeaders: httpHeaders, + ); + case DataSourceType.file: + return VideoPlayerController.file( + File(dataSource), + closedCaptionFile: closedCaptionFile, + videoPlayerOptions: videoPlayerOptions, + ); + case DataSourceType.contentUri: + return VideoPlayerController.contentUri( + Uri.parse(dataSource), + closedCaptionFile: closedCaptionFile, + videoPlayerOptions: videoPlayerOptions, + ); + } + }, + [ + dataSource, + closedCaptionFile, + videoPlayerOptions, + httpHeaders, + ], + ); + + useEffect( + () { + controller + ..initialize() + ..setLooping(looping); + + if (autoPlay) { + controller.play(); + } + + return controller.dispose; + }, + [ + dataSource, + closedCaptionFile, + videoPlayerOptions, + httpHeaders, + ], + ); + + final playing = useState(false); + + useEffect(() { + controller.addListener(() { + playing.value = controller.value.isPlaying; + }); + + return () => controller.removeListener(() {}); + }, [controller]); + + return controller; +} diff --git a/lib/hooks/use_player_core.dart b/lib/hooks/use_player_core.dart index 5293eea..f7f84b1 100644 --- a/lib/hooks/use_player_core.dart +++ b/lib/hooks/use_player_core.dart @@ -32,8 +32,6 @@ class PlayerCore { final AudioTrack audio; final List audios; final bool playing; - final VideoParams? videoParams; - final AudioParams? audioParams; final MediaType? mediaType; final Duration position; final Duration duration; @@ -42,6 +40,9 @@ class PlayerCore { final bool completed; final double rate; final FileItem? cover; + final double? aspect; + final int? width; + final int? height; final void Function(Duration) updatePosition; final void Function(bool) updateSeeking; final Future Function() saveProgress; @@ -55,8 +56,6 @@ class PlayerCore { this.audio, this.audios, this.playing, - this.videoParams, - this.audioParams, this.mediaType, this.position, this.duration, @@ -65,6 +64,9 @@ class PlayerCore { this.completed, this.rate, this.cover, + this.aspect, + this.width, + this.height, this.updatePosition, this.updateSeeking, this.saveProgress, @@ -145,7 +147,7 @@ PlayerCore usePlayerCore(BuildContext context) { bool playing = useStream(player.stream.playing).data ?? false; VideoParams? videoParams = useStream(player.stream.videoParams).data; - AudioParams? audioParams = useStream(player.stream.audioParams).data; + // AudioParams? audioParams = useStream(player.stream.audioParams).data; ValueNotifier position = useState(Duration.zero); Duration duration = useStream(player.stream.duration).data ?? Duration.zero; Duration buffer = useStream(player.stream.buffer).data ?? Duration.zero; @@ -338,8 +340,6 @@ PlayerCore usePlayerCore(BuildContext context) { audio, audios, playing, - videoParams, - audioParams, mediaType, duration == Duration.zero ? Duration.zero : position.value, duration, @@ -348,6 +348,9 @@ PlayerCore usePlayerCore(BuildContext context) { completed, rate, cover, + videoParams?.aspect, + videoParams?.w, + videoParams?.h, updatePosition, updateSeeking, saveProgress, diff --git a/lib/main.dart b/lib/main.dart index 68af999..56607be 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:app_links/app_links.dart'; +import 'package:fvp/fvp.dart' as fvp; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -27,6 +28,7 @@ void main(List arguments) async { WidgetsFlutterBinding.ensureInitialized(); MediaKit.ensureInitialized(); + fvp.registerWith(); final appLinks = AppLinks(); final initUri = await appLinks.getInitialLinkString(); diff --git a/lib/pages/player/control_bar.dart b/lib/pages/player/control_bar.dart index 131cba6..da276f0 100644 --- a/lib/pages/player/control_bar.dart +++ b/lib/pages/player/control_bar.dart @@ -294,7 +294,7 @@ class ControlBar extends HookWidget { showControl(); if (isFullScreen) { await windowManager.setFullScreen(false); - await resizeWindow(playerCore.videoParams?.aspect); + await resizeWindow(playerCore.aspect); } else { await windowManager.setFullScreen(true); } diff --git a/lib/pages/player/fvp_video.dart b/lib/pages/player/fvp_video.dart new file mode 100644 index 0000000..433792e --- /dev/null +++ b/lib/pages/player/fvp_video.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:iris/hooks/use_fvp.dart'; +import 'package:iris/models/file.dart'; +import 'package:iris/utils/check_data_source_type.dart'; +import 'package:video_player/video_player.dart'; + +class FvpVideo extends HookWidget { + const FvpVideo({ + super.key, + required this.file, + this.autoPlay = false, + this.looping = false, + this.fit = BoxFit.contain, + }); + + final FileItem file; + final bool autoPlay; + final bool looping; + final BoxFit fit; + + @override + Widget build(context) { + final controller = useFvp( + dataSource: file.uri, + dataSourceType: checkDataSourceType(file), + httpHeaders: file.auth != null ? {'authorization': file.auth!} : {}, + autoPlay: autoPlay, + looping: looping, + ); + + return FittedBox( + fit: fit, + child: SizedBox( + width: controller.value.size.width, + height: controller.value.size.height, + child: VideoPlayer(controller), + ), + ); + } +} diff --git a/lib/pages/player/iris_player.dart b/lib/pages/player/iris_player.dart index 413d4aa..124a20e 100644 --- a/lib/pages/player/iris_player.dart +++ b/lib/pages/player/iris_player.dart @@ -13,11 +13,13 @@ import 'package:iris/hooks/use_volume.dart'; import 'package:iris/info.dart'; import 'package:iris/models/file.dart'; import 'package:iris/models/storages/local.dart'; +import 'package:iris/models/store/app_state.dart'; import 'package:iris/pages/dialog/show_open_link_dialog.dart'; import 'package:iris/pages/player/audio.dart'; import 'package:iris/pages/player/control_bar_slider.dart'; import 'package:iris/pages/history.dart'; import 'package:iris/pages/play_queue.dart'; +import 'package:iris/pages/player/fvp_video.dart'; import 'package:iris/pages/show_open_link_bottom_sheet.dart'; import 'package:iris/pages/subtitle_and_audio_track.dart'; import 'package:iris/pages/settings/settings.dart'; @@ -37,20 +39,25 @@ import 'package:iris/pages/player/control_bar.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:window_manager/window_manager.dart'; +enum PlayerBackend { + mediaKit, + fvp, +} + class IrisPlayer extends HookWidget { const IrisPlayer({super.key}); @override Widget build(BuildContext context) { - final PlayerCore playerCore = usePlayerCore(context); - final PlayerController playerController = - usePlayerController(context, playerCore); + final playerType = PlayerBackend.mediaKit; final t = getLocalizations(context); final shuffle = useAppStore().select(context, (state) => state.shuffle); final fit = useAppStore().select(context, (state) => state.fit); final autoResize = useAppStore().select(context, (state) => state.autoResize); + final autoPlay = useAppStore().select(context, (state) => state.autoPlay); + final repeat = useAppStore().select(context, (state) => state.repeat); final playQueue = usePlayQueueStore().select(context, (state) => state.playQueue); @@ -75,6 +82,10 @@ class IrisPlayer extends HookWidget { : INFO.title, [currentPlay, currentPlayIndex, playQueue]); + final PlayerCore playerCore = usePlayerCore(context); + final PlayerController playerController = + usePlayerController(context, playerCore); + final focusNode = useFocusNode(); useEffect(() { @@ -106,10 +117,10 @@ class IrisPlayer extends HookWidget { useEffect(() { if (isDesktop) { - resizeWindow(!autoResize ? 0 : playerCore.videoParams?.aspect); + resizeWindow(!autoResize ? 0 : playerCore.aspect); } return; - }, [playerCore.videoParams?.aspect, autoResize]); + }, [playerCore.aspect, autoResize]); useEffect(() { if (appLifecycleState == AppLifecycleState.paused) { @@ -399,14 +410,20 @@ class IrisPlayer extends HookWidget { final videoViewSize = useMemoized(() { if (fit != BoxFit.none || - playerCore.videoParams?.w == null || - playerCore.videoParams?.h == null) { + playerCore.width == null || + playerCore.height == null) { return MediaQuery.of(context).size; } else { - return Size(playerCore.videoParams!.w! / scaleFactor, - playerCore.videoParams!.h! / scaleFactor); + return Size( + playerCore.width! / scaleFactor, playerCore.height! / scaleFactor); } - }, [fit, MediaQuery.of(context).size, playerCore.videoParams, scaleFactor]); + }, [ + fit, + MediaQuery.of(context).size, + playerCore.width, + playerCore.height, + scaleFactor + ]); final videoViewOffset = useMemoized( () => fit == BoxFit.none @@ -527,7 +544,7 @@ class IrisPlayer extends HookWidget { if (isDesktop) { if (await windowManager.isFullScreen()) { await windowManager.setFullScreen(false); - await resizeWindow(playerCore.videoParams?.aspect); + await resizeWindow(playerCore.aspect); } else { await windowManager.setFullScreen(true); } @@ -681,13 +698,33 @@ class IrisPlayer extends HookWidget { top: videoViewOffset.dy, width: videoViewSize.width, height: videoViewSize.height, - child: Video( - key: ValueKey(currentPlay?.file.getID()), - controller: playerCore.controller, - controls: NoVideoControls, - fit: fit == BoxFit.none ? BoxFit.contain : fit, - // wakelock: mediaType == 'video', - ), + child: currentPlay != null + ? () { + switch (playerType) { + case PlayerBackend.fvp: + return FvpVideo( + key: ValueKey(currentPlay.file.uri), + file: currentPlay.file, + autoPlay: autoPlay, + looping: + repeat == Repeat.one ? true : false, + fit: fit == BoxFit.none + ? BoxFit.contain + : fit, + ); + case PlayerBackend.mediaKit: + return Video( + key: ValueKey(currentPlay.file.uri), + controller: playerCore.controller, + controls: NoVideoControls, + fit: fit == BoxFit.none + ? BoxFit.contain + : fit, + // wakelock: mediaType == 'video', + ); + } + }() + : Container(), ) ], ), @@ -919,7 +956,7 @@ class IrisPlayer extends HookWidget { onDoubleTap: () async { if (isDesktop && await windowManager.isMaximized()) { await windowManager.unmaximize(); - await resizeWindow(playerCore.videoParams?.aspect); + await resizeWindow(playerCore.aspect); } else { await windowManager.maximize(); } diff --git a/lib/utils/check_data_source_type.dart b/lib/utils/check_data_source_type.dart new file mode 100644 index 0000000..3863407 --- /dev/null +++ b/lib/utils/check_data_source_type.dart @@ -0,0 +1,20 @@ +import 'dart:io'; +import 'package:iris/hooks/use_fvp.dart'; +import 'package:iris/models/file.dart'; +import 'package:iris/models/storages/storage.dart'; + +DataSourceType checkDataSourceType(FileItem file) { + if (Platform.isAndroid && file.uri.startsWith('content://')) { + return DataSourceType.contentUri; + } + + switch (file.storageType) { + case StorageType.internal: + case StorageType.sdcard: + case StorageType.usb: + return DataSourceType.file; + case StorageType.webdav: + case StorageType.none: + return DataSourceType.network; + } +} diff --git a/lib/widgets/custom_app_bar.dart b/lib/widgets/custom_app_bar.dart index 293caef..8f73e40 100644 --- a/lib/widgets/custom_app_bar.dart +++ b/lib/widgets/custom_app_bar.dart @@ -120,8 +120,7 @@ class CustomAppBar extends HookWidget { onPressed: () async { if (isFullScreen) { await windowManager.setFullScreen(false); - await resizeWindow( - playerCore.videoParams?.aspect); + await resizeWindow(playerCore.aspect); } else { await windowManager.setFullScreen(true); } @@ -141,8 +140,7 @@ class CustomAppBar extends HookWidget { onPressed: () async { if (isMaximized) { await windowManager.unmaximize(); - await resizeWindow( - playerCore.videoParams?.aspect); + await resizeWindow(playerCore.aspect); } else { await windowManager.maximize(); } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 651df0d..92d50d3 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_volume_controller_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterVolumeControllerPlugin"); flutter_volume_controller_plugin_register_with_registrar(flutter_volume_controller_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) gtk_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); gtk_plugin_register_with_registrar(gtk_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index b956928..b3e9076 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color flutter_secure_storage_linux flutter_volume_controller + fvp gtk media_kit_libs_linux media_kit_video diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 23d8cf7..567a35b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,6 +12,7 @@ import disks_desktop import dynamic_color import flutter_secure_storage_macos import flutter_volume_controller +import fvp import media_kit_libs_macos_video import media_kit_video import package_info_plus @@ -19,6 +20,7 @@ import path_provider_foundation import screen_brightness_macos import screen_retriever_macos import url_launcher_macos +import video_player_avfoundation import wakelock_plus import window_manager import window_size @@ -31,6 +33,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FlutterVolumeControllerPlugin.register(with: registry.registrar(forPlugin: "FlutterVolumeControllerPlugin")) + FvpPlugin.register(with: registry.registrar(forPlugin: "FvpPlugin")) MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) @@ -38,6 +41,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) WindowSizePlugin.register(with: registry.registrar(forPlugin: "WindowSizePlugin")) diff --git a/pubspec.lock b/pubspec.lock index f8f268f..048aa4e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -222,6 +222,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -514,6 +522,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + fvp: + dependency: "direct main" + description: + name: fvp + sha256: ad134b93f6400edd93e674ee15b9d365d0227b7fb1f013ac60f0dc601d387c78 + url: "https://pub.dev" + source: hosted + version: "0.29.0" glob: dependency: transitive description: @@ -546,6 +562,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + html: + dependency: transitive + description: + name: html + sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + url: "https://pub.dev" + source: hosted + version: "0.15.5" http: dependency: "direct main" description: @@ -1319,6 +1343,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: "4a8c3492d734f7c39c2588a3206707a05ee80cef52e8c7f3b2078d430c84bc17" + url: "https://pub.dev" + source: hosted + version: "2.9.2" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: "7018dbcb395e2bca0b9a898e73989e67c0c4a5db269528e1b036ca38bcca0d0b" + url: "https://pub.dev" + source: hosted + version: "2.7.17" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: "8a4e73a3faf2b13512978a43cf1cdda66feeeb900a0527f1fbfd7b19cf3458d3" + url: "https://pub.dev" + source: hosted + version: "2.6.7" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "229d7642ccd9f3dc4aba169609dd6b5f3f443bb4cc15b82f7785fcada5af9bbb" + url: "https://pub.dev" + source: hosted + version: "6.2.3" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "881b375a934d8ebf868c7fb1423b2bfaa393a0a265fa3f733079a86536064a10" + url: "https://pub.dev" + source: hosted + version: "2.3.3" vm_service: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 38ded35..2f87dee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -72,6 +72,8 @@ dependencies: saf_util: ^0.6.2 screen_brightness: ^0.2.2 flutter_volume_controller: ^1.3.3 + fvp: ^0.29.0 + video_player: ^2.9.2 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index a0be2f6..219a823 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); FlutterVolumeControllerPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterVolumeControllerPluginCApi")); + FvpPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FvpPluginCApi")); MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); MediaKitVideoPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index d6a51da..0c16c22 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color flutter_secure_storage_windows flutter_volume_controller + fvp media_kit_libs_windows_video media_kit_video permission_handler_windows