diff --git a/mobile/lib/pages/common/gallery_viewer.page.dart b/mobile/lib/pages/common/gallery_viewer.page.dart index 0ff5608da1..2c0021b024 100644 --- a/mobile/lib/pages/common/gallery_viewer.page.dart +++ b/mobile/lib/pages/common/gallery_viewer.page.dart @@ -55,23 +55,12 @@ class GalleryViewerPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final totalAssets = useState(renderList.totalAssets); final isZoomed = useState(false); - final isPlayingMotionVideo = useState(false); + final isPlayingMotionVideo = useValueNotifier(false); final stackIndex = useState(0); final localPosition = useRef(null); final currentIndex = useValueNotifier(initialIndex); final loadAsset = renderList.loadAsset; - // // Update is playing motion video - ref.listen( - videoPlaybackValueProvider.select( - (playback) => playback.state == VideoPlaybackState.playing, - ), (_, isPlaying) { - final asset = ref.read(currentAssetProvider); - if (asset != null && asset.isMotionPhoto) { - isPlayingMotionVideo.value = isPlaying; - } - }); - Future precacheNextImage(int index) async { if (!context.mounted) { return; @@ -237,7 +226,8 @@ class GalleryViewerPage extends HookConsumerWidget { child: NativeVideoViewerPage( key: key, asset: asset, - placeholder: Image( + isPlayingMotionVideo: isPlayingMotionVideo, + image: Image( key: ValueKey(asset), image: ImmichImage.imageProvider( asset: asset, @@ -266,7 +256,7 @@ class GalleryViewerPage extends HookConsumerWidget { } } - if (newAsset.isImage && !isPlayingMotionVideo.value) { + if (newAsset.isImage && !newAsset.isMotionPhoto) { return buildImage(context, newAsset); } return buildVideo(context, newAsset); diff --git a/mobile/lib/pages/common/native_video_viewer.page.dart b/mobile/lib/pages/common/native_video_viewer.page.dart index 9612de8f35..623f19c4f0 100644 --- a/mobile/lib/pages/common/native_video_viewer.page.dart +++ b/mobile/lib/pages/common/native_video_viewer.page.dart @@ -24,12 +24,17 @@ import 'package:wakelock_plus/wakelock_plus.dart'; class NativeVideoViewerPage extends HookConsumerWidget { final Asset asset; final bool showControls; - final Widget placeholder; + final Widget image; + + /// Whether to display the video part of the motion photo + /// TODO: this should probably be a provider + final ValueNotifier? isPlayingMotionVideo; const NativeVideoViewerPage({ super.key, required this.asset, - required this.placeholder, + required this.image, + this.isPlayingMotionVideo, this.showControls = true, }); @@ -44,6 +49,12 @@ class NativeVideoViewerPage extends HookConsumerWidget { final lastVideoPosition = useRef(-1); final isBuffering = useRef(false); + if (isPlayingMotionVideo != null) { + useListenable(isPlayingMotionVideo); + } + final showMotionVideo = + isPlayingMotionVideo != null && isPlayingMotionVideo!.value; + // When a video is opened through the timeline, `isCurrent` will immediately be true. // When swiping from video A to video B, `isCurrent` will initially be true for video A and false for video B. // If the swipe is completed, `isCurrent` will be true for video B after a delay. @@ -413,19 +424,24 @@ class NativeVideoViewerPage extends HookConsumerWidget { return Stack( children: [ - placeholder, // this is always under the video to avoid flickering + // This remains under the video to avoid flickering + // For motion videos, this is the image portion of the asset + image, if (aspectRatio.value != null) - Center( - key: ValueKey(asset), - child: AspectRatio( + Visibility.maintain( + visible: asset.isVideo || showMotionVideo, + child: Center( key: ValueKey(asset), - aspectRatio: aspectRatio.value!, - child: isCurrent - ? NativeVideoPlayerView( - key: ValueKey(asset), - onViewReady: initController, - ) - : null, + child: AspectRatio( + key: ValueKey(asset), + aspectRatio: aspectRatio.value!, + child: isCurrent + ? NativeVideoPlayerView( + key: ValueKey(asset), + onViewReady: initController, + ) + : null, + ), ), ), if (showControls) const Center(child: CustomVideoPlayerControls()), diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 5e88d8879a..de8d041ed1 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -1086,6 +1086,7 @@ class NativeVideoViewerRoute extends PageRouteInfo { Key? key, required Asset asset, required Widget placeholder, + ValueNotifier? isPlayingMotionVideo, bool showControls = true, List? children, }) : super( @@ -1094,6 +1095,7 @@ class NativeVideoViewerRoute extends PageRouteInfo { key: key, asset: asset, placeholder: placeholder, + isPlayingMotionVideo: isPlayingMotionVideo, showControls: showControls, ), initialChildren: children, @@ -1108,7 +1110,8 @@ class NativeVideoViewerRoute extends PageRouteInfo { return NativeVideoViewerPage( key: args.key, asset: args.asset, - placeholder: args.placeholder, + image: args.placeholder, + isPlayingMotionVideo: args.isPlayingMotionVideo, showControls: args.showControls, ); }, @@ -1120,6 +1123,7 @@ class NativeVideoViewerRouteArgs { this.key, required this.asset, required this.placeholder, + this.isPlayingMotionVideo, this.showControls = true, }); @@ -1129,6 +1133,8 @@ class NativeVideoViewerRouteArgs { final Widget placeholder; + final ValueNotifier? isPlayingMotionVideo; + final bool showControls; @override diff --git a/mobile/lib/widgets/asset_viewer/custom_video_player_controls.dart b/mobile/lib/widgets/asset_viewer/custom_video_player_controls.dart index 0df8137417..3960ce2d67 100644 --- a/mobile/lib/widgets/asset_viewer/custom_video_player_controls.dart +++ b/mobile/lib/widgets/asset_viewer/custom_video_player_controls.dart @@ -18,7 +18,13 @@ class CustomVideoPlayerControls extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final assetIsVideo = ref.watch( + currentAssetProvider.select((asset) => asset != null && asset.isVideo), + ); final showControls = ref.watch(showControlsProvider); + final VideoPlaybackState state = + ref.watch(videoPlaybackValueProvider.select((value) => value.state)); + // A timer to hide the controls final hideTimer = useTimer( hideTimerDuration, @@ -27,20 +33,12 @@ class CustomVideoPlayerControls extends HookConsumerWidget { return; } - final state = ref.read(videoPlaybackValueProvider).state; // Do not hide on paused - if (state == VideoPlaybackState.paused) { - return; - } - - final asset = ref.read(currentAssetProvider); - if (asset != null && asset.isVideo) { + if (state != VideoPlaybackState.paused && assetIsVideo) { ref.read(showControlsProvider.notifier).show = false; } }, ); - final VideoPlaybackState state = - ref.watch(videoPlaybackValueProvider.select((value) => value.state)); final showBuffering = state == VideoPlaybackState.buffering; /// Shows the controls and starts the timer to hide them @@ -95,7 +93,7 @@ class CustomVideoPlayerControls extends HookConsumerWidget { iconColor: Colors.white, isFinished: state == VideoPlaybackState.completed, isPlaying: state == VideoPlaybackState.playing, - show: showControls, + show: assetIsVideo && showControls, onPressed: togglePlay, ), ), diff --git a/mobile/lib/widgets/memories/memory_card.dart b/mobile/lib/widgets/memories/memory_card.dart index 266c7636aa..477003c497 100644 --- a/mobile/lib/widgets/memories/memory_card.dart +++ b/mobile/lib/widgets/memories/memory_card.dart @@ -72,7 +72,7 @@ class MemoryCard extends StatelessWidget { key: ValueKey(asset.id), asset: asset, showControls: false, - placeholder: SizedBox.expand( + image: SizedBox.expand( child: ImmichImage( asset, fit: fit,