From a346a37743df2e601dcc125884de0508d2b031b3 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 9 Aug 2024 17:30:30 -0500 Subject: [PATCH] splitup the player --- .../lib/pages/common/gallery_viewer.page.dart | 44 ++++--- .../common/native_video_viewer.page.dart | 117 ++++++++++++++++++ .../lib/pages/common/video_viewer.page.dart | 24 ++-- ...tive_video_player_controller_provider.dart | 5 + .../asset_viewer/native_video_player.dart | 63 ++++++++++ .../widgets/asset_viewer/video_player.dart | 67 +++------- 6 files changed, 240 insertions(+), 80 deletions(-) create mode 100644 mobile/lib/pages/common/native_video_viewer.page.dart create mode 100644 mobile/lib/providers/asset_viewer/native_video_player_controller_provider.dart create mode 100644 mobile/lib/widgets/asset_viewer/native_video_player.dart diff --git a/mobile/lib/pages/common/gallery_viewer.page.dart b/mobile/lib/pages/common/gallery_viewer.page.dart index 57c75ca84d..01e60bf5f2 100644 --- a/mobile/lib/pages/common/gallery_viewer.page.dart +++ b/mobile/lib/pages/common/gallery_viewer.page.dart @@ -12,6 +12,7 @@ import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/pages/common/download_panel.dart'; +import 'package:immich_mobile/pages/common/native_video_viewer.page.dart'; import 'package:immich_mobile/pages/common/video_viewer.page.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart'; @@ -62,7 +63,6 @@ class GalleryViewerPage extends HookConsumerWidget { final localPosition = useState(null); final currentIndex = useState(initialIndex); final currentAsset = loadAsset(currentIndex.value); - // Update is playing motion video ref.listen(videoPlaybackValueProvider.select((v) => v.state), (_, state) { isPlayingVideo.value = state == VideoPlaybackState.playing; @@ -353,6 +353,9 @@ class GalleryViewerPage extends HookConsumerWidget { ), ); } else { + final useNativePlayer = + asset.isLocal && asset.livePhotoVideoId == null; + return PhotoViewGalleryPageOptions.customChild( onDragStart: (_, details, __) => localPosition.value = details.localPosition, @@ -367,19 +370,32 @@ class GalleryViewerPage extends HookConsumerWidget { maxScale: 1.0, minScale: 1.0, basePosition: Alignment.center, - child: VideoViewerPage( - key: ValueKey(a), - asset: a, - isMotionVideo: a.livePhotoVideoId != null, - loopVideo: shouldLoopVideo.value, - placeholder: Image( - image: provider, - fit: BoxFit.contain, - height: context.height, - width: context.width, - alignment: Alignment.center, - ), - ), + child: useNativePlayer + ? NativeVideoViewerPage( + key: ValueKey(a), + asset: a, + // loopVideo: shouldLoopVideo.value, + // placeholder: Image( + // image: provider, + // fit: BoxFit.contain, + // height: context.height, + // width: context.width, + // alignment: Alignment.center, + // ), + ) + : VideoViewerPage( + key: ValueKey(a), + asset: a, + isMotionVideo: a.livePhotoVideoId != null, + loopVideo: shouldLoopVideo.value, + placeholder: Image( + image: provider, + fit: BoxFit.contain, + height: context.height, + width: context.width, + alignment: Alignment.center, + ), + ), ); } }, diff --git a/mobile/lib/pages/common/native_video_viewer.page.dart b/mobile/lib/pages/common/native_video_viewer.page.dart new file mode 100644 index 0000000000..0a224089ab --- /dev/null +++ b/mobile/lib/pages/common/native_video_viewer.page.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/providers/asset_viewer/native_video_player_controller_provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/video_player_controller_provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart'; +import 'package:immich_mobile/widgets/asset_viewer/video_player.dart'; +import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart'; +import 'package:native_video_player/native_video_player.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; + +class NativeVideoViewerPage extends ConsumerStatefulWidget { + final Asset asset; + final Widget? placeholder; + + const NativeVideoViewerPage({ + super.key, + required this.asset, + this.placeholder, + }); + + @override + NativeVideoViewerPageState createState() => NativeVideoViewerPageState(); +} + +class NativeVideoViewerPageState extends ConsumerState { + @override + Widget build(BuildContext context) { + final size = MediaQuery.sizeOf(context); + double videoWidth = size.width; + double videoHeight = size.height; + + NativeVideoPlayerController? controller; + + void initController(NativeVideoPlayerController videoCtrl) { + controller = videoCtrl; + + controller?.onPlaybackReady.addListener(() { + // Emitted when the video loaded successfully and it's ready to play. + // At this point, videoInfo is available. + final videoInfo = controller?.videoInfo; + + setState(() { + if (videoInfo != null) { + videoWidth = videoInfo.width.toDouble(); + videoHeight = videoInfo.height.toDouble(); + + print(videoHeight); + print(videoWidth); + } + }); + + final videoDuration = videoInfo?.duration; + + controller?.play(); + }); + + controller?.onPlaybackStatusChanged.addListener(() { + final playbackStatus = controller?.playbackInfo?.status; + // playbackStatus can be playing, paused, or stopped. + }); + + controller?.onPlaybackPositionChanged.addListener(() { + final playbackPosition = controller?.playbackInfo?.position; + }); + + controller?.onPlaybackEnded.addListener(() { + // Emitted when the video has finished playing. + }); + } + + dispose() { + controller = null; + super.dispose(); + } + + return PopScope( + onPopInvoked: (pop) { + ref.read(videoPlaybackValueProvider.notifier).value = + VideoPlaybackValue.uninitialized(); + }, + child: SizedBox( + height: videoHeight, + width: videoWidth, + child: AspectRatio( + aspectRatio: 16 / 9, + child: NativeVideoPlayerView( + onViewReady: (c) async { + // Use a local file for the video player controller + final file = await widget.asset.local!.file; + if (file == null) { + throw Exception('No file found for the video'); + } + + final videoSource = await VideoSource.init( + path: file.path, + type: VideoSourceType.file, + ); + + await c.loadVideoSource(videoSource); + initController(c); + }, + ), + ), + ), + ); + } + // final Asset asset; + // final Widget? placeholder; + // final Duration hideControlsTimer; + // final bool showControls; + // final bool showDownloadingIndicator; + // final bool loopVideo; +} diff --git a/mobile/lib/pages/common/video_viewer.page.dart b/mobile/lib/pages/common/video_viewer.page.dart index 401e0fab73..573f7277f2 100644 --- a/mobile/lib/pages/common/video_viewer.page.dart +++ b/mobile/lib/pages/common/video_viewer.page.dart @@ -103,7 +103,7 @@ class VideoViewerPage extends HookConsumerWidget { // Done in a microtask to avoid setting the state while the is building if (!isMotionVideo) { Future.microtask(() { - ref.read(showControlsProvider.notifier).show = true; + ref.read(showControlsProvider.notifier).show = false; }); } @@ -148,20 +148,16 @@ class VideoViewerPage extends HookConsumerWidget { ), if (controller != null) SizedBox( - height: 16 / 9 * size.width, + height: size.height, width: size.width, - child: AspectRatio( - aspectRatio: 16 / 9, - child: VideoPlayerViewer( - controller: controller, - isMotionVideo: isMotionVideo, - placeholder: placeholder, - hideControlsTimer: hideControlsTimer, - showControls: showControls, - showDownloadingIndicator: showDownloadingIndicator, - loopVideo: loopVideo, - asset: asset, - ), + child: VideoPlayerViewer( + controller: controller, + isMotionVideo: isMotionVideo, + placeholder: placeholder, + hideControlsTimer: hideControlsTimer, + showControls: showControls, + showDownloadingIndicator: showDownloadingIndicator, + loopVideo: loopVideo, ), ), ], diff --git a/mobile/lib/providers/asset_viewer/native_video_player_controller_provider.dart b/mobile/lib/providers/asset_viewer/native_video_player_controller_provider.dart new file mode 100644 index 0000000000..f8e712a8b6 --- /dev/null +++ b/mobile/lib/providers/asset_viewer/native_video_player_controller_provider.dart @@ -0,0 +1,5 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:native_video_player/native_video_player.dart'; + +final nativePlayerControllerProvider = + StateProvider((ref) => NativeVideoPlayerController(0)); diff --git a/mobile/lib/widgets/asset_viewer/native_video_player.dart b/mobile/lib/widgets/asset_viewer/native_video_player.dart new file mode 100644 index 0000000000..26213da2cf --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/native_video_player.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:native_video_player/native_video_player.dart'; +import 'package:video_player/video_player.dart'; + +class NativeVideoPlayer extends HookConsumerWidget { + final VideoPlayerController controller; + final bool isMotionVideo; + final Widget? placeholder; + final Duration hideControlsTimer; + final bool showControls; + final bool showDownloadingIndicator; + final bool loopVideo; + final Asset asset; + + const NativeVideoPlayer({ + super.key, + required this.controller, + required this.isMotionVideo, + this.placeholder, + required this.hideControlsTimer, + required this.showControls, + required this.showDownloadingIndicator, + required this.loopVideo, + required this.asset, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return NativeVideoPlayerView( + onViewReady: (controller) async { + try { + String path = ''; + VideoSourceType type = VideoSourceType.file; + if (asset.isLocal && asset.livePhotoVideoId == null) { + // Use a local file for the video player controller + final file = await asset.local!.file; + if (file == null) { + throw Exception('No file found for the video'); + } + path = file.path; + type = VideoSourceType.file; + + final videoSource = await VideoSource.init( + path: path, + type: type, + ); + + await controller.loadVideoSource(videoSource); + await controller.play(); + + Future.delayed(const Duration(milliseconds: 100), () async { + await controller.setVolume(0.5); + }); + } + } catch (e) { + print('Error loading video: $e'); + } + }, + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/video_player.dart b/mobile/lib/widgets/asset_viewer/video_player.dart index 3e8a3c623c..ebf158b59a 100644 --- a/mobile/lib/widgets/asset_viewer/video_player.dart +++ b/mobile/lib/widgets/asset_viewer/video_player.dart @@ -1,11 +1,8 @@ import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/utils/hooks/chewiew_controller_hook.dart'; import 'package:immich_mobile/widgets/asset_viewer/custom_video_player_controls.dart'; -import 'package:native_video_player/native_video_player.dart'; import 'package:video_player/video_player.dart'; class VideoPlayerViewer extends HookConsumerWidget { @@ -16,7 +13,6 @@ class VideoPlayerViewer extends HookConsumerWidget { final bool showControls; final bool showDownloadingIndicator; final bool loopVideo; - final Asset asset; const VideoPlayerViewer({ super.key, @@ -27,59 +23,26 @@ class VideoPlayerViewer extends HookConsumerWidget { required this.showControls, required this.showDownloadingIndicator, required this.loopVideo, - required this.asset, }); @override Widget build(BuildContext context, WidgetRef ref) { - // final chewie = useChewieController( - // controller: controller, - // controlsSafeAreaMinimum: const EdgeInsets.only( - // bottom: 100, - // ), - // placeholder: SizedBox.expand(child: placeholder), - // customControls: CustomVideoPlayerControls( - // hideTimerDuration: hideControlsTimer, - // ), - // showControls: showControls && !isMotionVideo, - // hideControlsTimer: hideControlsTimer, - // loopVideo: loopVideo, - // ); + final chewie = useChewieController( + controller: controller, + controlsSafeAreaMinimum: const EdgeInsets.only( + bottom: 100, + ), + placeholder: SizedBox.expand(child: placeholder), + customControls: CustomVideoPlayerControls( + hideTimerDuration: hideControlsTimer, + ), + showControls: showControls && !isMotionVideo, + hideControlsTimer: hideControlsTimer, + loopVideo: loopVideo, + ); - // return Chewie( - // controller: chewie, - // ); - - return NativeVideoPlayerView( - onViewReady: (controller) async { - try { - String path = ''; - VideoSourceType type = VideoSourceType.file; - if (asset.isLocal && asset.livePhotoVideoId == null) { - // Use a local file for the video player controller - final file = await asset.local!.file; - if (file == null) { - throw Exception('No file found for the video'); - } - path = file.path; - type = VideoSourceType.file; - - final videoSource = await VideoSource.init( - path: path, - type: type, - ); - - await controller.loadVideoSource(videoSource); - await controller.play(); - - Future.delayed(const Duration(milliseconds: 100), () async { - await controller.setVolume(0.5); - }); - } - } catch (e) { - print('Error loading video: $e'); - } - }, + return Chewie( + controller: chewie, ); } }